blob: 54af65bba48da4493a79c3daf016a656957e67ab [file] [log] [blame]
/*
* Copyright (C) 2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ImageControlsMac.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "CommonAtomStrings.h"
#include "ContextMenuController.h"
#include "ElementInlines.h"
#include "ElementRareData.h"
#include "Event.h"
#include "EventHandler.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "HTMLAttachmentElement.h"
#include "HTMLButtonElement.h"
#include "HTMLDivElement.h"
#include "HTMLImageElement.h"
#include "HTMLNames.h"
#include "HTMLStyleElement.h"
#include "MouseEvent.h"
#include "RenderAttachment.h"
#include "RenderImage.h"
#include "ShadowRoot.h"
#include "UserAgentStyleSheets.h"
#include <wtf/text/AtomString.h>
namespace WebCore {
namespace ImageControlsMac {
#if ENABLE(SERVICE_CONTROLS)
static const AtomString& imageControlsElementIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("image-controls"_s);
return identifier;
}
static const AtomString& imageControlsButtonIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("image-controls-button"_s);
return identifier;
}
bool hasControls(const HTMLElement& element)
{
auto shadowRoot = element.shadowRoot();
if (!shadowRoot || shadowRoot->mode() != ShadowRootMode::UserAgent)
return false;
return shadowRoot->hasElementWithId(*imageControlsElementIdentifier().impl());
}
bool isImageControlsButtonElement(const Node& node)
{
return is<Element>(node) && downcast<Element>(node).getIdAttribute() == imageControlsButtonIdentifier();
}
bool isInsideImageControls(const Node& node)
{
RefPtr host = node.shadowHost();
if (!is<HTMLElement>(host.get()) || !hasControls(downcast<HTMLElement>(*host)))
return false;
return is<Element>(node) && downcast<Element>(node).getIdAttribute() == imageControlsElementIdentifier();
}
void createImageControls(HTMLElement& element)
{
Ref document = element.document();
Ref shadowRoot = element.ensureUserAgentShadowRoot();
auto controlLayer = HTMLDivElement::create(document.get());
controlLayer->setIdAttribute(imageControlsElementIdentifier());
controlLayer->setAttributeWithoutSynchronization(HTMLNames::contenteditableAttr, falseAtom());
shadowRoot->appendChild(controlLayer);
static MainThreadNeverDestroyed<const String> shadowStyle(StringImpl::createWithoutCopying(imageControlsMacUserAgentStyleSheet, sizeof(imageControlsMacUserAgentStyleSheet)));
auto style = HTMLStyleElement::create(HTMLNames::styleTag, document.get(), false);
style->setTextContent(String { shadowStyle });
shadowRoot->appendChild(WTFMove(style));
auto button = HTMLButtonElement::create(HTMLNames::buttonTag, element.document(), nullptr);
button->setIdAttribute(imageControlsButtonIdentifier());
controlLayer->appendChild(button);
if (auto* renderObject = element.renderer(); is<RenderImage>(renderObject))
downcast<RenderImage>(*renderObject).setHasShadowControls(true);
}
static Image* imageFromImageElementNode(Node& node)
{
auto* renderer = node.renderer();
if (!is<RenderImage>(renderer))
return nullptr;
auto* image = downcast<RenderImage>(*renderer).cachedImage();
if (!image || image->errorOccurred())
return nullptr;
return image->imageForRenderer(renderer);
}
bool handleEvent(HTMLElement& element, Event& event)
{
if (event.type() != eventNames().clickEvent)
return false;
RefPtr frame = element.document().frame();
if (!frame)
return false;
Page* page = element.document().page();
if (!page)
return false;
if (!is<MouseEvent>(event))
return false;
auto& mouseEvent = downcast<MouseEvent>(event);
if (!is<Node>(mouseEvent.target()))
return false;
auto& node = downcast<Node>(*mouseEvent.target());
if (ImageControlsMac::isImageControlsButtonElement(node)) {
Ref element = downcast<Element>(node);
auto* renderer = element->renderer();
if (!renderer)
return false;
RefPtr view = frame->view();
if (!view)
return false;
auto point = view->contentsToWindow(renderer->absoluteBoundingBoxRect()).minXMaxYCorner();
if (RefPtr shadowHost = dynamicDowncast<HTMLImageElement>(node.shadowHost())) {
auto* image = imageFromImageElementNode(*shadowHost);
if (!image)
return false;
page->chrome().client().handleImageServiceClick(point, *image, *shadowHost);
} else if (RefPtr shadowHost = dynamicDowncast<HTMLAttachmentElement>(node.shadowHost()))
page->chrome().client().handlePDFServiceClick(point, *shadowHost);
event.setDefaultHandled();
return true;
}
return false;
}
static bool isImageMenuEnabled(HTMLElement& element)
{
if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
return imageElement->isImageMenuEnabled();
if (RefPtr attachmentElement = dynamicDowncast<HTMLAttachmentElement>(element))
return attachmentElement->isImageMenuEnabled();
return false;
}
void updateImageControls(HTMLElement& element)
{
// If this is an image element and it is inside a shadow tree then it is part of an image control.
if (element.isInShadowTree())
return;
if (!element.document().settings().imageControlsEnabled())
return;
element.document().eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakElement = WeakPtr { element }] {
RefPtr protectedElement = weakElement.get();
if (!protectedElement)
return;
ASSERT(is<HTMLAttachmentElement>(*protectedElement) || is<HTMLImageElement>(*protectedElement));
bool imageMenuEnabled = isImageMenuEnabled(*protectedElement);
bool hasControls = hasImageControls(*protectedElement);
if (!imageMenuEnabled && hasControls)
destroyImageControls(*protectedElement);
else if (imageMenuEnabled && !hasControls)
tryCreateImageControls(*protectedElement);
});
}
void tryCreateImageControls(HTMLElement& element)
{
ASSERT(isImageMenuEnabled(element));
ASSERT(!hasImageControls(element));
createImageControls(element);
}
void destroyImageControls(HTMLElement& element)
{
auto shadowRoot = element.userAgentShadowRoot();
if (!shadowRoot)
return;
if (RefPtr node = shadowRoot->firstChild()) {
if (!is<HTMLElement>(*node))
return;
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ImageControlsMac::hasControls(downcast<HTMLElement>(*node)));
shadowRoot->removeChild(*node);
}
auto* renderObject = element.renderer();
if (!renderObject)
return;
if (auto* renderImage = dynamicDowncast<RenderImage>(*renderObject))
renderImage->setHasShadowControls(false);
else if (auto* renderAttachment = dynamicDowncast<RenderAttachment>(*renderObject))
renderAttachment->setHasShadowControls(false);
}
bool hasImageControls(const HTMLElement& element)
{
return hasControls(element);
}
#endif // ENABLE(SERVICE_CONTROLS)
} // namespace ImageControlsMac
} // namespace WebCore