diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/html/HTMLImageElement.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/html/HTMLImageElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLImageElement.cpp | 446 |
1 files changed, 343 insertions, 103 deletions
diff --git a/Source/WebCore/html/HTMLImageElement.cpp b/Source/WebCore/html/HTMLImageElement.cpp index 31aeb27d9..94869cf54 100644 --- a/Source/WebCore/html/HTMLImageElement.cpp +++ b/Source/WebCore/html/HTMLImageElement.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2004-2016 Apple Inc. All rights reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * * This library is free software; you can redistribute it and/or @@ -23,59 +23,78 @@ #include "config.h" #include "HTMLImageElement.h" -#include "Attribute.h" #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" #include "CachedImage.h" -#include "EventNames.h" #include "FrameView.h" -#include "HTMLAnchorElement.h" #include "HTMLDocument.h" #include "HTMLFormElement.h" #include "HTMLParserIdioms.h" -#include "Page.h" +#include "HTMLPictureElement.h" +#include "HTMLSourceElement.h" +#include "HTMLSrcsetParser.h" +#include "MIMETypeRegistry.h" +#include "MediaList.h" +#include "MediaQueryEvaluator.h" +#include "NodeTraversal.h" #include "RenderImage.h" +#include "RenderView.h" +#include "Settings.h" +#include "ShadowRoot.h" +#include "SizesAttributeParser.h" +#include <wtf/text/StringBuilder.h> + +#if ENABLE(SERVICE_CONTROLS) +#include "ImageControlsRootElement.h" +#endif namespace WebCore { using namespace HTMLNames; +typedef HashMap<const HTMLImageElement*, WeakPtr<HTMLPictureElement>> PictureOwnerMap; +static PictureOwnerMap* gPictureOwnerMap = nullptr; + HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) : HTMLElement(tagName, document) - , m_imageLoader(this) - , m_form(form) + , m_imageLoader(*this) + , m_form(nullptr) + , m_formSetByParser(form) , m_compositeOperator(CompositeSourceOver) + , m_imageDevicePixelRatio(1.0f) +#if ENABLE(SERVICE_CONTROLS) + , m_experimentalImageMenuEnabled(false) +#endif { ASSERT(hasTagName(imgTag)); setHasCustomStyleResolveCallbacks(); - if (form) - form->registerImgElement(this); } -PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document) +Ref<HTMLImageElement> HTMLImageElement::create(Document& document) { - return adoptRef(new HTMLImageElement(imgTag, document)); + return adoptRef(*new HTMLImageElement(imgTag, document)); } -PassRefPtr<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) +Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) { - return adoptRef(new HTMLImageElement(tagName, document, form)); + return adoptRef(*new HTMLImageElement(tagName, document, form)); } HTMLImageElement::~HTMLImageElement() { if (m_form) m_form->removeImgElement(this); + setPictureElement(nullptr); } -PassRefPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight) +Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, std::optional<unsigned> width, std::optional<unsigned> height) { - RefPtr<HTMLImageElement> image = adoptRef(new HTMLImageElement(imgTag, document)); - if (optionalWidth) - image->setWidth(*optionalWidth); - if (optionalHeight) - image->setHeight(*optionalHeight); - return image.release(); + auto image = adoptRef(*new HTMLImageElement(imgTag, document)); + if (width) + image->setWidth(width.value()); + if (height) + image->setHeight(height.value()); + return image; } bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const @@ -109,77 +128,136 @@ void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& const AtomicString& HTMLImageElement::imageSourceURL() const { - return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL; + return m_bestFitImageURL.isEmpty() ? attributeWithoutSynchronization(srcAttr) : m_bestFitImageURL; +} + +void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate) +{ + m_bestFitImageURL = candidate.string.toAtomicString(); + m_currentSrc = AtomicString(document().completeURL(imageSourceURL()).string()); + if (candidate.density >= 0) + m_imageDevicePixelRatio = 1 / candidate.density; + if (is<RenderImage>(renderer())) + downcast<RenderImage>(*renderer()).setImageDevicePixelRatio(m_imageDevicePixelRatio); +} + +ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement() +{ + auto* picture = pictureElement(); + if (!picture) + return { }; + picture->clearViewportDependentResults(); + document().removeViewportDependentPicture(*picture); + for (Node* child = picture->firstChild(); child && child != this; child = child->nextSibling()) { + if (!is<HTMLSourceElement>(*child)) + continue; + auto& source = downcast<HTMLSourceElement>(*child); + + auto& srcset = source.attributeWithoutSynchronization(srcsetAttr); + if (srcset.isEmpty()) + continue; + + auto& typeAttribute = source.attributeWithoutSynchronization(typeAttr); + if (!typeAttribute.isNull()) { + String type = typeAttribute.string(); + type.truncate(type.find(';')); + type = stripLeadingAndTrailingHTMLSpaces(type); + if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageMIMEType(type) && !equalLettersIgnoringASCIICase(type, "image/svg+xml")) + continue; + } + + auto* documentElement = document().documentElement(); + MediaQueryEvaluator evaluator { document().printing() ? "print" : "screen", document(), documentElement ? documentElement->computedStyle() : nullptr }; + auto* queries = source.mediaQuerySet(); + auto evaluation = !queries || evaluator.evaluate(*queries, picture->viewportDependentResults()); + if (picture->hasViewportDependentResults()) + document().addViewportDependentPicture(*picture); + if (!evaluation) + continue; + + auto sourceSize = SizesAttributeParser(source.attributeWithoutSynchronization(sizesAttr).string(), document()).length(); + auto candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom, srcset, sourceSize); + if (!candidate.isEmpty()) + return candidate; + } + return { }; +} + +void HTMLImageElement::selectImageSource() +{ + // First look for the best fit source from our <picture> parent if we have one. + ImageCandidate candidate = bestFitSourceFromPictureElement(); + if (candidate.isEmpty()) { + // If we don't have a <picture> or didn't find a source, then we use our own attributes. + auto sourceSize = SizesAttributeParser(attributeWithoutSynchronization(sizesAttr).string(), document()).length(); + candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), attributeWithoutSynchronization(srcAttr), attributeWithoutSynchronization(srcsetAttr), sourceSize); + } + setBestFitURLAndDPRFromImageCandidate(candidate); + m_imageLoader.updateFromElementIgnoringPreviousError(); } void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == altAttr) { - if (renderer() && renderer()->isRenderImage()) - toRenderImage(renderer())->updateAltText(); - } else if (name == srcAttr || name == srcsetAttr) { - m_bestFitImageURL = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr)); - m_imageLoader.updateFromElementIgnoringPreviousError(); - } else if (name == usemapAttr) { - setIsLink(!value.isNull() && !shouldProhibitLinks(this)); - - if (inDocument() && !m_lowercasedUsemap.isNull()) - document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this); - - // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off. - // FIXME: We should check that the first character is '#'. - // FIXME: HTML5 specification says we should strip any leading string before '#'. - // FIXME: HTML5 specification says we should ignore usemap attributes without #. - if (value.length() > 1) - m_lowercasedUsemap = value.string().substring(1).lower(); - else - m_lowercasedUsemap = nullAtom; - - if (inDocument() && !m_lowercasedUsemap.isNull()) - document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this); - } else if (name == onbeforeloadAttr) - setAttributeEventListener(eventNames().beforeloadEvent, name, value); - else if (name == compositeAttr) { + if (is<RenderImage>(renderer())) + downcast<RenderImage>(*renderer()).updateAltText(); + } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr) + selectImageSource(); + else if (name == usemapAttr) { + if (isConnected() && !m_parsedUsemap.isNull()) + document().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this); + + m_parsedUsemap = parseHTMLHashNameReference(value); + + if (isConnected() && !m_parsedUsemap.isNull()) + document().addImageElementByUsemap(*m_parsedUsemap.impl(), *this); + } else if (name == compositeAttr) { // FIXME: images don't support blend modes in their compositing attribute. BlendMode blendOp = BlendModeNormal; if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp)) m_compositeOperator = CompositeSourceOver; +#if ENABLE(SERVICE_CONTROLS) + } else if (name == webkitimagemenuAttr) { + m_experimentalImageMenuEnabled = !value.isNull(); + updateImageControls(); +#endif } else { if (name == nameAttr) { bool willHaveName = !value.isNull(); - if (hasName() != willHaveName && inDocument() && document().isHTMLDocument()) { - HTMLDocument* document = toHTMLDocument(&this->document()); + if (m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) { + HTMLDocument& document = downcast<HTMLDocument>(this->document()); const AtomicString& id = getIdAttribute(); if (!id.isEmpty() && id != getNameAttribute()) { if (willHaveName) - document->addDocumentNamedItem(*id.impl(), *this); + document.addDocumentNamedItem(*id.impl(), *this); else - document->removeDocumentNamedItem(*id.impl(), *this); + document.removeDocumentNamedItem(*id.impl(), *this); } } + m_hadNameBeforeAttributeChanged = willHaveName; } HTMLElement::parseAttribute(name, value); } } -String HTMLImageElement::altText() const +const AtomicString& HTMLImageElement::altText() const { // lets figure out the alt text.. magic stuff // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen // also heavily discussed by Hixie on bugzilla - String alt = getAttribute(altAttr); + const AtomicString& alt = attributeWithoutSynchronization(altAttr); + if (!alt.isNull()) + return alt; // fall back to title attribute - if (alt.isNull()) - alt = getAttribute(titleAttr); - return alt; + return attributeWithoutSynchronization(titleAttr); } -RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(PassRef<RenderStyle> style) +RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) { - if (style.get().hasContent()) - return RenderElement::createFor(*this, std::move(style)); + if (style.hasContent()) + return RenderElement::createFor(*this, WTFMove(style)); - return createRenderer<RenderImage>(*this, std::move(style)); + return createRenderer<RenderImage>(*this, WTFMove(style), nullptr, m_imageDevicePixelRatio); } bool HTMLImageElement::canStartSelection() const @@ -192,12 +270,17 @@ bool HTMLImageElement::canStartSelection() const void HTMLImageElement::didAttachRenderers() { - if (!renderer() || !renderer()->isRenderImage()) + if (!is<RenderImage>(renderer())) return; if (m_imageLoader.hasPendingBeforeLoadEvent()) return; - RenderImage* renderImage = toRenderImage(renderer()); - RenderImageResource& renderImageResource = renderImage->imageResource(); + +#if ENABLE(SERVICE_CONTROLS) + updateImageControls(); +#endif + + auto& renderImage = downcast<RenderImage>(*renderer()); + RenderImageResource& renderImageResource = renderImage.imageResource(); if (renderImageResource.hasImage()) return; renderImageResource.setCachedImage(m_imageLoader.image()); @@ -205,27 +288,42 @@ void HTMLImageElement::didAttachRenderers() // If we have no image at all because we have no src attribute, set // image height and width for the alt text instead. if (!m_imageLoader.image() && !renderImageResource.cachedImage()) - renderImage->setImageSizeForAltText(); + renderImage.setImageSizeForAltText(); } Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint) { - if (!m_form) { // m_form can be non-null if it was set in constructor. + if (m_formSetByParser) { + m_form = m_formSetByParser; + m_formSetByParser = nullptr; + m_form->registerImgElement(this); + } + + if (m_form && rootElement() != m_form->rootElement()) { + m_form->removeImgElement(this); + m_form = nullptr; + } + + if (!m_form) { m_form = HTMLFormElement::findClosestFormAncestor(*this); if (m_form) m_form->registerImgElement(this); } - // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result // in callbacks back to this node. Node::InsertionNotificationRequest insertNotificationRequest = HTMLElement::insertedInto(insertionPoint); - if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull()) - document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this); + if (insertionPoint.isConnected() && !m_parsedUsemap.isNull()) + document().addImageElementByUsemap(*m_parsedUsemap.impl(), *this); + + if (is<HTMLPictureElement>(parentNode())) { + setPictureElement(&downcast<HTMLPictureElement>(*parentNode())); + selectImageSource(); + } // If we have been inserted from a renderer-less document, // our loader may have not fetched the image, so do it now. - if (insertionPoint.inDocument() && !m_imageLoader.image()) + if (insertionPoint.isConnected() && !m_imageLoader.image()) m_imageLoader.updateFromElement(); return insertNotificationRequest; @@ -236,25 +334,50 @@ void HTMLImageElement::removedFrom(ContainerNode& insertionPoint) if (m_form) m_form->removeImgElement(this); - if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull()) - document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this); - - m_form = 0; + if (insertionPoint.isConnected() && !m_parsedUsemap.isNull()) + document().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this); + + if (is<HTMLPictureElement>(parentNode())) + setPictureElement(nullptr); + + m_form = nullptr; HTMLElement::removedFrom(insertionPoint); } -int HTMLImageElement::width(bool ignorePendingStylesheets) +HTMLPictureElement* HTMLImageElement::pictureElement() const +{ + if (!gPictureOwnerMap || !gPictureOwnerMap->contains(this)) + return nullptr; + HTMLPictureElement* result = gPictureOwnerMap->get(this).get(); + if (!result) + gPictureOwnerMap->remove(this); + return result; +} + +void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement) +{ + if (!pictureElement) { + if (gPictureOwnerMap) + gPictureOwnerMap->remove(this); + return; + } + + if (!gPictureOwnerMap) + gPictureOwnerMap = new PictureOwnerMap(); + gPictureOwnerMap->add(this, pictureElement->createWeakPtr()); +} + +unsigned HTMLImageElement::width(bool ignorePendingStylesheets) { if (!renderer()) { // check the attribute first for an explicit pixel value - bool ok; - int width = getAttribute(widthAttr).toInt(&ok); - if (ok) - return width; + std::optional<unsigned> width = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr)); + if (width) + return width.value(); // if the image is available, use its width if (m_imageLoader.image()) - return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width(); + return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width().toUnsigned(); } if (ignorePendingStylesheets) @@ -263,21 +386,23 @@ int HTMLImageElement::width(bool ignorePendingStylesheets) document().updateLayout(); RenderBox* box = renderBox(); - return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), *box) : 0; + if (!box) + return 0; + LayoutRect contentRect = box->contentBoxRect(); + return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box); } -int HTMLImageElement::height(bool ignorePendingStylesheets) +unsigned HTMLImageElement::height(bool ignorePendingStylesheets) { if (!renderer()) { // check the attribute first for an explicit pixel value - bool ok; - int height = getAttribute(heightAttr).toInt(&ok); - if (ok) - return height; + std::optional<unsigned> height = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr)); + if (height) + return height.value(); // if the image is available, use its height if (m_imageLoader.image()) - return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height(); + return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height().toUnsigned(); } if (ignorePendingStylesheets) @@ -286,7 +411,10 @@ int HTMLImageElement::height(bool ignorePendingStylesheets) document().updateLayout(); RenderBox* box = renderBox(); - return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), *box) : 0; + if (!box) + return 0; + LayoutRect contentRect = box->contentBoxRect(); + return adjustForAbsoluteZoom(snappedIntRect(contentRect).height(), *box); } int HTMLImageElement::naturalWidth() const @@ -314,45 +442,76 @@ bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const || HTMLElement::isURLAttribute(attribute); } -bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const +bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const +{ + return attribute.name() == srcsetAttr + || HTMLElement::attributeContainsURL(attribute); +} + +String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const +{ + if (attribute.name() == srcsetAttr) { + Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value())); + StringBuilder result; + for (const auto& candidate : imageCandidates) { + if (&candidate != &imageCandidates[0]) + result.appendLiteral(", "); + result.append(URL(base, candidate.string.toString()).string()); + if (candidate.density != UninitializedDescriptor) { + result.append(' '); + result.appendNumber(candidate.density); + result.append('x'); + } + if (candidate.resourceWidth != UninitializedDescriptor) { + result.append(' '); + result.appendNumber(candidate.resourceWidth); + result.append('w'); + } + } + return result.toString(); + } + return HTMLElement::completeURLsInAttributeValue(base, attribute); +} + +bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const { - ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name); - return m_lowercasedUsemap.impl() == &name; + return m_parsedUsemap.impl() == &name; } const AtomicString& HTMLImageElement::alt() const { - return getAttribute(altAttr); + return attributeWithoutSynchronization(altAttr); } bool HTMLImageElement::draggable() const { // Image elements are draggable by default. - return !equalIgnoringCase(getAttribute(draggableAttr), "false"); + return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "false"); } -void HTMLImageElement::setHeight(int value) +void HTMLImageElement::setHeight(unsigned value) { - setIntegralAttribute(heightAttr, value); + setUnsignedIntegralAttribute(heightAttr, value); } URL HTMLImageElement::src() const { - return document().completeURL(getAttribute(srcAttr)); + return document().completeURL(attributeWithoutSynchronization(srcAttr)); } void HTMLImageElement::setSrc(const String& value) { - setAttribute(srcAttr, value); + setAttributeWithoutSynchronization(srcAttr, value); } -void HTMLImageElement::setWidth(int value) +void HTMLImageElement::setWidth(unsigned value) { - setIntegralAttribute(widthAttr, value); + setUnsignedIntegralAttribute(widthAttr, value); } int HTMLImageElement::x() const { + document().updateLayoutIgnorePendingStylesheets(); auto renderer = this->renderer(); if (!renderer) return 0; @@ -363,6 +522,7 @@ int HTMLImageElement::x() const int HTMLImageElement::y() const { + document().updateLayoutIgnorePendingStylesheets(); auto renderer = this->renderer(); if (!renderer) return 0; @@ -380,12 +540,12 @@ void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const { HTMLElement::addSubresourceAttributeURLs(urls); - addSubresourceURL(urls, src()); + addSubresourceURL(urls, document().completeURL(imageSourceURL())); // FIXME: What about when the usemap attribute begins with "#"? - addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr))); + addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr))); } -void HTMLImageElement::didMoveToNewDocument(Document* oldDocument) +void HTMLImageElement::didMoveToNewDocument(Document& oldDocument) { m_imageLoader.elementDidMoveToNewDocument(); HTMLElement::didMoveToNewDocument(oldDocument); @@ -393,11 +553,11 @@ void HTMLImageElement::didMoveToNewDocument(Document* oldDocument) bool HTMLImageElement::isServerMap() const { - if (!fastHasAttribute(ismapAttr)) + if (!hasAttributeWithoutSynchronization(ismapAttr)) return false; - const AtomicString& usemap = fastGetAttribute(usemapAttr); - + const AtomicString& usemap = attributeWithoutSynchronization(usemapAttr); + // If the usemap attribute starts with '#', it refers to a map element in the document. if (usemap.string()[0] == '#') return false; @@ -405,6 +565,86 @@ bool HTMLImageElement::isServerMap() const return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty(); } +void HTMLImageElement::setCrossOrigin(const AtomicString& value) +{ + setAttributeWithoutSynchronization(crossoriginAttr, value); +} + +String HTMLImageElement::crossOrigin() const +{ + return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr)); +} + +#if ENABLE(SERVICE_CONTROLS) +void HTMLImageElement::updateImageControls() +{ + // If this image element is inside a shadow tree then it is part of an image control. + if (isInShadowTree()) + return; + + if (!document().settings().imageControlsEnabled()) + return; + + bool hasControls = hasImageControls(); + if (!m_experimentalImageMenuEnabled && hasControls) + destroyImageControls(); + else if (m_experimentalImageMenuEnabled && !hasControls) + tryCreateImageControls(); +} + +void HTMLImageElement::tryCreateImageControls() +{ + ASSERT(m_experimentalImageMenuEnabled); + ASSERT(!hasImageControls()); + + auto imageControls = ImageControlsRootElement::tryCreate(document()); + if (!imageControls) + return; + + ensureUserAgentShadowRoot().appendChild(*imageControls); + + auto* renderObject = renderer(); + if (!renderObject) + return; + + downcast<RenderImage>(*renderObject).setHasShadowControls(true); +} + +void HTMLImageElement::destroyImageControls() +{ + ShadowRoot* shadowRoot = userAgentShadowRoot(); + if (!shadowRoot) + return; + + if (Node* node = shadowRoot->firstChild()) { + ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement()); + shadowRoot->removeChild(*node); + } + + auto* renderObject = renderer(); + if (!renderObject) + return; + + downcast<RenderImage>(*renderObject).setHasShadowControls(false); +} + +bool HTMLImageElement::hasImageControls() const +{ + if (ShadowRoot* shadowRoot = userAgentShadowRoot()) { + Node* node = shadowRoot->firstChild(); + ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement()); + return node; + } + + return false; +} + +bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const +{ + return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child); +} +#endif // ENABLE(SERVICE_CONTROLS) + #if PLATFORM(IOS) // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic. bool HTMLImageElement::willRespondToMouseClickEvents() |