gPictureOwnerMap is unnecessary
[WebKit-https.git] / Source / WebCore / html / HTMLImageElement.cpp
index b2a277a..5cc8b39 100644 (file)
@@ -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
 #include "CSSPropertyNames.h"
 #include "CSSValueKeywords.h"
 #include "CachedImage.h"
+#include "Chrome.h"
+#include "ChromeClient.h"
+#include "EditableImageReference.h"
+#include "Editor.h"
+#include "ElementIterator.h"
 #include "EventNames.h"
 #include "FrameView.h"
 #include "HTMLAnchorElement.h"
+#include "HTMLAttachmentElement.h"
 #include "HTMLDocument.h"
 #include "HTMLFormElement.h"
 #include "HTMLParserIdioms.h"
+#include "HTMLPictureElement.h"
+#include "HTMLMapElement.h"
+#include "HTMLSourceElement.h"
 #include "HTMLSrcsetParser.h"
-#include "Page.h"
+#include "Logging.h"
+#include "MIMETypeRegistry.h"
+#include "MediaList.h"
+#include "MediaQueryEvaluator.h"
+#include "NodeTraversal.h"
+#include "PlatformMouseEvent.h"
 #include "RenderImage.h"
+#include "RenderView.h"
+#include "RuntimeEnabledFeatures.h"
 #include "Settings.h"
 #include "ShadowRoot.h"
-#include "SourceSizeList.h"
+#include "SizesAttributeParser.h"
+#include <wtf/IsoMallocInlines.h>
 #include <wtf/text/StringBuilder.h>
 
 #if ENABLE(SERVICE_CONTROLS)
 
 namespace WebCore {
 
+WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLImageElement);
+
 using namespace HTMLNames;
 
 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
     : HTMLElement(tagName, document)
     , m_imageLoader(*this)
-    , m_form(form)
+    , m_form(nullptr)
+    , m_formSetByParser(form)
     , m_compositeOperator(CompositeSourceOver)
     , m_imageDevicePixelRatio(1.0f)
 #if ENABLE(SERVICE_CONTROLS)
@@ -60,8 +80,6 @@ HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& docum
 {
     ASSERT(hasTagName(imgTag));
     setHasCustomStyleResolveCallbacks();
-    if (form)
-        form->registerImgElement(this);
 }
 
 Ref<HTMLImageElement> HTMLImageElement::create(Document& document)
@@ -78,21 +96,22 @@ HTMLImageElement::~HTMLImageElement()
 {
     if (m_form)
         m_form->removeImgElement(this);
+    setPictureElement(nullptr);
 }
 
-Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
+Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, Optional<unsigned> width, Optional<unsigned> height)
 {
-    Ref<HTMLImageElement> image = adoptRef(*new HTMLImageElement(imgTag, document));
-    if (optionalWidth)
-        image->setWidth(*optionalWidth);
-    if (optionalHeight)
-        image->setHeight(*optionalHeight);
+    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
 {
-    if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
+    if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == valignAttr)
         return true;
     return HTMLElement::isPresentationAttribute(name);
 }
@@ -121,49 +140,100 @@ 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.toString();
-#if ENABLE(PICTURE_SIZES)
+    m_bestFitImageURL = candidate.string.toAtomicString();
     m_currentSrc = AtomicString(document().completeURL(imageSourceURL()).string());
-#endif
     if (candidate.density >= 0)
         m_imageDevicePixelRatio = 1 / candidate.density;
     if (is<RenderImage>(renderer()))
         downcast<RenderImage>(*renderer()).setImageDevicePixelRatio(m_imageDevicePixelRatio);
 }
 
+ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement()
+{
+    auto picture = makeRefPtr(pictureElement());
+    if (!picture)
+        return { };
+
+    picture->clearViewportDependentResults();
+    document().removeViewportDependentPicture(*picture);
+
+    picture->clearAppearanceDependentResults();
+    document().removeAppearanceDependentPicture(*picture);
+
+    for (RefPtr<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::isSupportedImageVideoOrSVGMIMEType(type))
+                continue;
+        }
+
+        auto documentElement = makeRefPtr(document().documentElement());
+        MediaQueryEvaluator evaluator { document().printing() ? "print" : "screen", document(), documentElement ? documentElement->computedStyle() : nullptr };
+        auto* queries = source.parsedMediaAttribute(document());
+        LOG(MediaQueries, "HTMLImageElement %p bestFitSourceFromPictureElement evaluating media queries", this);
+        auto evaluation = !queries || evaluator.evaluate(*queries, picture->viewportDependentResults(), picture->appearanceDependentResults());
+        if (picture->hasViewportDependentResults())
+            document().addViewportDependentPicture(*picture);
+        if (picture->hasAppearanceDependentResults())
+            document().addAppearanceDependentPicture(*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 (is<RenderImage>(renderer()))
             downcast<RenderImage>(*renderer()).updateAltText();
-    } else if (name == srcAttr || name == srcsetAttr) {
-        float sourceSize = parseSizesAttribute(fastGetAttribute(sizesAttr).string(), document().renderView(), document().frame());
-        ImageCandidate candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr), sourceSize);
-        setBestFitURLAndDPRFromImageCandidate(candidate);
-        m_imageLoader.updateFromElementIgnoringPreviousError();
-    } else if (name == usemapAttr) {
-        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;
+    } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
+        selectImageSource();
+    else if (name == usemapAttr) {
+        if (isInTreeScope() && !m_parsedUsemap.isNull())
+            treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
 
-        if (inDocument() && !m_lowercasedUsemap.isNull())
-            document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
+        m_parsedUsemap = parseHTMLHashNameReference(value);
+
+        if (isInTreeScope() && !m_parsedUsemap.isNull())
+            treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
     } else if (name == compositeAttr) {
         // FIXME: images don't support blend modes in their compositing attribute.
-        BlendMode blendOp = BlendModeNormal;
+        BlendMode blendOp = BlendMode::Normal;
         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
             m_compositeOperator = CompositeSourceOver;
 #if ENABLE(SERVICE_CONTROLS)
@@ -171,10 +241,12 @@ void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicStr
         m_experimentalImageMenuEnabled = !value.isNull();
         updateImageControls();
 #endif
-    } else {
+    } else if (name == x_apple_editable_imageAttr)
+        updateEditableImage();
+    else {
         if (name == nameAttr) {
             bool willHaveName = !value.isNull();
-            if (m_hadNameBeforeAttributeChanged != willHaveName && inDocument() && is<HTMLDocument>(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()) {
@@ -195,19 +267,19 @@ 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
-    const AtomicString& alt = fastGetAttribute(altAttr);
+    const AtomicString& alt = attributeWithoutSynchronization(altAttr);
     if (!alt.isNull())
         return alt;
     // fall back to title attribute
-    return fastGetAttribute(titleAttr);
+    return attributeWithoutSynchronization(titleAttr);
 }
 
-RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&)
+RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
 {
-    if (style.get().hasContent())
-        return RenderElement::createFor(*this, WTF::move(style));
+    if (style.hasContent())
+        return RenderElement::createFor(*this, WTFMove(style));
 
-    return createRenderer<RenderImage>(*this, WTF::move(style), nullptr, m_imageDevicePixelRatio);
+    return createRenderer<RenderImage>(*this, WTFMove(style), nullptr, m_imageDevicePixelRatio);
 }
 
 bool HTMLImageElement::canStartSelection() const
@@ -218,6 +290,20 @@ bool HTMLImageElement::canStartSelection() const
     return false;
 }
 
+bool HTMLImageElement::supportsFocus() const
+{
+    if (hasEditableImageAttribute())
+        return true;
+    return HTMLElement::supportsFocus();
+}
+
+bool HTMLImageElement::isFocusable() const
+{
+    if (hasEditableImageAttribute())
+        return true;
+    return HTMLElement::isFocusable();
+}
+
 void HTMLImageElement::didAttachRenderers()
 {
     if (!is<RenderImage>(renderer()))
@@ -231,7 +317,7 @@ void HTMLImageElement::didAttachRenderers()
 
     auto& renderImage = downcast<RenderImage>(*renderer());
     RenderImageResource& renderImageResource = renderImage.imageResource();
-    if (renderImageResource.hasImage())
+    if (renderImageResource.cachedImage())
         return;
     renderImageResource.setCachedImage(m_imageLoader.image());
 
@@ -241,9 +327,20 @@ void HTMLImageElement::didAttachRenderers()
         renderImage.setImageSizeForAltText();
 }
 
-Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
+Node::InsertedIntoAncestorResult HTMLImageElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
 {
-    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);
@@ -251,43 +348,128 @@ Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode&
 
     // 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);
+    Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+
+    if (insertionType.connectedToDocument && hasEditableImageAttribute())
+        insertNotificationRequest = InsertedIntoAncestorResult::NeedsPostInsertionCallback;
 
-    if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
-        document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
+    if (insertionType.treeScopeChanged && !m_parsedUsemap.isNull())
+        treeScope().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 (insertionType.connectedToDocument && !m_imageLoader.image())
         m_imageLoader.updateFromElement();
 
     return insertNotificationRequest;
 }
 
-void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
+void HTMLImageElement::didFinishInsertingNode()
+{
+    if (hasEditableImageAttribute())
+        updateEditableImage();
+}
+
+void HTMLImageElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
 {
     if (m_form)
         m_form->removeImgElement(this);
 
-    if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
-        document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
+    if (removalType.treeScopeChanged && !m_parsedUsemap.isNull())
+        oldParentOfRemovedTree.treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
+
+    if (is<HTMLPictureElement>(parentNode()))
+        setPictureElement(nullptr);
+
+    if (removalType.disconnectedFromDocument)
+        updateEditableImage();
+
+    m_form = nullptr;
+    HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
+}
+
+bool HTMLImageElement::hasEditableImageAttribute() const
+{
+    if (!document().settings().editableImagesEnabled())
+        return false;
+    return hasAttributeWithoutSynchronization(x_apple_editable_imageAttr);
+}
 
-    m_form = 0;
-    HTMLElement::removedFrom(insertionPoint);
+GraphicsLayer::EmbeddedViewID HTMLImageElement::editableImageViewID() const
+{
+    if (!m_editableImage)
+        return 0;
+    return m_editableImage->embeddedViewID();
 }
 
-int HTMLImageElement::width(bool ignorePendingStylesheets)
+void HTMLImageElement::updateEditableImage()
+{
+    if (!document().settings().editableImagesEnabled())
+        return;
+
+    auto* page = document().page();
+    if (!page)
+        return;
+
+    bool hasEditableAttribute = hasEditableImageAttribute();
+    bool isCurrentlyEditable = !!m_editableImage;
+    bool shouldBeEditable = isConnected() && hasEditableAttribute;
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    // Create the inner attachment for editable images, or non-editable
+    // images that were cloned from editable image sources.
+    if (!attachmentElement() && (shouldBeEditable || !m_pendingClonedAttachmentID.isEmpty())) {
+        auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document());
+        if (!m_pendingClonedAttachmentID.isEmpty())
+            attachment->setUniqueIdentifier(WTFMove(m_pendingClonedAttachmentID));
+        else
+            attachment->ensureUniqueIdentifier();
+        setAttachmentElement(WTFMove(attachment));
+    }
+#endif
+
+    if (shouldBeEditable == isCurrentlyEditable)
+        return;
+
+    if (!hasEditableAttribute) {
+        m_editableImage = nullptr;
+        return;
+    }
+
+    if (!m_editableImage)
+        m_editableImage = EditableImageReference::create(document());
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    m_editableImage->associateWithAttachment(attachmentElement()->uniqueIdentifier());
+#endif
+}
+
+HTMLPictureElement* HTMLImageElement::pictureElement() const
+{
+    return m_pictureElement.get();
+}
+    
+void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement)
+{
+    m_pictureElement = makeWeakPtr(pictureElement);
+}
+    
+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;
+        auto optionalWidth = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr));
+        if (optionalWidth)
+            return optionalWidth.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)
@@ -302,18 +484,17 @@ int HTMLImageElement::width(bool ignorePendingStylesheets)
     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;
+        auto optionalHeight = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr));
+        if (optionalHeight)
+            return optionalHeight.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)
@@ -376,7 +557,7 @@ String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Att
             if (candidate.resourceWidth != UninitializedDescriptor) {
                 result.append(' ');
                 result.appendNumber(candidate.resourceWidth);
-                result.append('x');
+                result.append('w');
             }
         }
         return result.toString();
@@ -384,41 +565,45 @@ String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Att
     return HTMLElement::completeURLsInAttributeValue(base, attribute);
 }
 
-bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
+bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const
+{
+    return m_parsedUsemap.impl() == &name;
+}
+
+HTMLMapElement* HTMLImageElement::associatedMapElement() const
 {
-    ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name);
-    return m_lowercasedUsemap.impl() == &name;
+    return treeScope().getImageMap(m_parsedUsemap);
 }
 
 const AtomicString& HTMLImageElement::alt() const
 {
-    return fastGetAttribute(altAttr);
+    return attributeWithoutSynchronization(altAttr);
 }
 
 bool HTMLImageElement::draggable() const
 {
     // Image elements are draggable by default.
-    return !equalIgnoringCase(fastGetAttribute(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(fastGetAttribute(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
@@ -448,28 +633,43 @@ bool HTMLImageElement::complete() const
     return m_imageLoader.imageComplete();
 }
 
+DecodingMode HTMLImageElement::decodingMode() const
+{
+    const AtomicString& decodingMode = attributeWithoutSynchronization(decodingAttr);
+    if (equalLettersIgnoringASCIICase(decodingMode, "sync"))
+        return DecodingMode::Synchronous;
+    if (equalLettersIgnoringASCIICase(decodingMode, "async"))
+        return DecodingMode::Asynchronous;
+    return DecodingMode::Auto;
+}
+    
+void HTMLImageElement::decode(Ref<DeferredPromise>&& promise)
+{
+    return m_imageLoader.decode(WTFMove(promise));
+}
+
 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(fastGetAttribute(usemapAttr)));
+    addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr)));
 }
 
-void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
+void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
 {
     m_imageLoader.elementDidMoveToNewDocument();
-    HTMLElement::didMoveToNewDocument(oldDocument);
+    HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
 }
 
 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;
@@ -477,6 +677,48 @@ 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(ATTACHMENT_ELEMENT)
+
+void HTMLImageElement::setAttachmentElement(Ref<HTMLAttachmentElement>&& attachment)
+{
+    if (auto existingAttachment = attachmentElement())
+        existingAttachment->remove();
+
+    attachment->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
+    ensureUserAgentShadowRoot().appendChild(WTFMove(attachment));
+}
+
+RefPtr<HTMLAttachmentElement> HTMLImageElement::attachmentElement() const
+{
+    if (auto shadowRoot = userAgentShadowRoot())
+        return childrenOfType<HTMLAttachmentElement>(*shadowRoot).first();
+
+    return nullptr;
+}
+
+const String& HTMLImageElement::attachmentIdentifier() const
+{
+    if (!m_pendingClonedAttachmentID.isEmpty())
+        return m_pendingClonedAttachmentID;
+
+    if (auto attachment = attachmentElement())
+        return attachment->uniqueIdentifier();
+
+    return nullAtom();
+}
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
+
 #if ENABLE(SERVICE_CONTROLS)
 void HTMLImageElement::updateImageControls()
 {
@@ -484,27 +726,26 @@ void HTMLImageElement::updateImageControls()
     if (isInShadowTree())
         return;
 
-    Settings* settings = document().settings();
-    if (!settings || !settings->imageControlsEnabled())
+    if (!document().settings().imageControlsEnabled())
         return;
 
     bool hasControls = hasImageControls();
     if (!m_experimentalImageMenuEnabled && hasControls)
         destroyImageControls();
     else if (m_experimentalImageMenuEnabled && !hasControls)
-        createImageControls();
+        tryCreateImageControls();
 }
 
-void HTMLImageElement::createImageControls()
+void HTMLImageElement::tryCreateImageControls()
 {
     ASSERT(m_experimentalImageMenuEnabled);
     ASSERT(!hasImageControls());
 
-    RefPtr<ImageControlsRootElement> imageControls = ImageControlsRootElement::maybeCreate(document());
+    auto imageControls = ImageControlsRootElement::tryCreate(document());
     if (!imageControls)
         return;
 
-    ensureUserAgentShadowRoot().appendChild(imageControls);
+    ensureUserAgentShadowRoot().appendChild(*imageControls);
 
     auto* renderObject = renderer();
     if (!renderObject)
@@ -515,13 +756,13 @@ void HTMLImageElement::createImageControls()
 
 void HTMLImageElement::destroyImageControls()
 {
-    ShadowRoot* shadowRoot = userAgentShadowRoot();
+    auto shadowRoot = userAgentShadowRoot();
     if (!shadowRoot)
         return;
 
-    if (Node* node = shadowRoot->firstChild()) {
+    if (RefPtr<Node> node = shadowRoot->firstChild()) {
         ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement());
-        shadowRoot->removeChild(node);
+        shadowRoot->removeChild(*node);
     }
 
     auto* renderObject = renderer();
@@ -533,8 +774,8 @@ void HTMLImageElement::destroyImageControls()
 
 bool HTMLImageElement::hasImageControls() const
 {
-    if (ShadowRoot* shadowRoot = userAgentShadowRoot()) {
-        Node* node = shadowRoot->firstChild();
+    if (auto shadowRoot = userAgentShadowRoot()) {
+        RefPtr<Node> node = shadowRoot->firstChild();
         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement());
         return node;
     }
@@ -548,7 +789,7 @@ bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const
 }
 #endif // ENABLE(SERVICE_CONTROLS)
 
-#if PLATFORM(IOS)
+#if PLATFORM(IOS_FAMILY)
 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
 bool HTMLImageElement::willRespondToMouseClickEvents()
 {
@@ -559,4 +800,39 @@ bool HTMLImageElement::willRespondToMouseClickEvents()
 }
 #endif
 
+#if USE(SYSTEM_PREVIEW)
+bool HTMLImageElement::isSystemPreviewImage() const
+{
+    if (!RuntimeEnabledFeatures::sharedFeatures().systemPreviewEnabled())
+        return false;
+
+    const auto* parent = parentElement();
+    if (is<HTMLAnchorElement>(parent))
+        return downcast<HTMLAnchorElement>(parent)->isSystemPreviewLink();
+    if (is<HTMLPictureElement>(parent))
+        return downcast<HTMLPictureElement>(parent)->isSystemPreviewImage();
+    return false;
+}
+#endif
+
+void HTMLImageElement::copyNonAttributePropertiesFromElement(const Element& source)
+{
+    auto& sourceImage = static_cast<const HTMLImageElement&>(source);
+#if ENABLE(ATTACHMENT_ELEMENT)
+    m_pendingClonedAttachmentID = !sourceImage.m_pendingClonedAttachmentID.isEmpty() ? sourceImage.m_pendingClonedAttachmentID : sourceImage.attachmentIdentifier();
+#endif
+    m_editableImage = sourceImage.m_editableImage;
+    Element::copyNonAttributePropertiesFromElement(source);
+}
+
+void HTMLImageElement::defaultEventHandler(Event& event)
+{
+    if (hasEditableImageAttribute() && event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) {
+        focus();
+        event.setDefaultHandled();
+        return;
+    }
+    HTMLElement::defaultEventHandler(event);
+}
+
 }