5cc8b399ff08e2fd26c46bd1123e3b886f59a7f0
[WebKit-https.git] / Source / WebCore / html / HTMLImageElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  * Copyright (C) 2004-2016 Apple Inc. All rights reserved.
5  * Copyright (C) 2010 Google Inc. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24 #include "HTMLImageElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "CSSValueKeywords.h"
28 #include "CachedImage.h"
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "EditableImageReference.h"
32 #include "Editor.h"
33 #include "ElementIterator.h"
34 #include "EventNames.h"
35 #include "FrameView.h"
36 #include "HTMLAnchorElement.h"
37 #include "HTMLAttachmentElement.h"
38 #include "HTMLDocument.h"
39 #include "HTMLFormElement.h"
40 #include "HTMLParserIdioms.h"
41 #include "HTMLPictureElement.h"
42 #include "HTMLMapElement.h"
43 #include "HTMLSourceElement.h"
44 #include "HTMLSrcsetParser.h"
45 #include "Logging.h"
46 #include "MIMETypeRegistry.h"
47 #include "MediaList.h"
48 #include "MediaQueryEvaluator.h"
49 #include "NodeTraversal.h"
50 #include "PlatformMouseEvent.h"
51 #include "RenderImage.h"
52 #include "RenderView.h"
53 #include "RuntimeEnabledFeatures.h"
54 #include "Settings.h"
55 #include "ShadowRoot.h"
56 #include "SizesAttributeParser.h"
57 #include <wtf/IsoMallocInlines.h>
58 #include <wtf/text/StringBuilder.h>
59
60 #if ENABLE(SERVICE_CONTROLS)
61 #include "ImageControlsRootElement.h"
62 #endif
63
64 namespace WebCore {
65
66 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLImageElement);
67
68 using namespace HTMLNames;
69
70 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
71     : HTMLElement(tagName, document)
72     , m_imageLoader(*this)
73     , m_form(nullptr)
74     , m_formSetByParser(form)
75     , m_compositeOperator(CompositeSourceOver)
76     , m_imageDevicePixelRatio(1.0f)
77 #if ENABLE(SERVICE_CONTROLS)
78     , m_experimentalImageMenuEnabled(false)
79 #endif
80 {
81     ASSERT(hasTagName(imgTag));
82     setHasCustomStyleResolveCallbacks();
83 }
84
85 Ref<HTMLImageElement> HTMLImageElement::create(Document& document)
86 {
87     return adoptRef(*new HTMLImageElement(imgTag, document));
88 }
89
90 Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
91 {
92     return adoptRef(*new HTMLImageElement(tagName, document, form));
93 }
94
95 HTMLImageElement::~HTMLImageElement()
96 {
97     if (m_form)
98         m_form->removeImgElement(this);
99     setPictureElement(nullptr);
100 }
101
102 Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, Optional<unsigned> width, Optional<unsigned> height)
103 {
104     auto image = adoptRef(*new HTMLImageElement(imgTag, document));
105     if (width)
106         image->setWidth(width.value());
107     if (height)
108         image->setHeight(height.value());
109     return image;
110 }
111
112 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
113 {
114     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == valignAttr)
115         return true;
116     return HTMLElement::isPresentationAttribute(name);
117 }
118
119 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
120 {
121     if (name == widthAttr)
122         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
123     else if (name == heightAttr)
124         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
125     else if (name == borderAttr)
126         applyBorderAttributeToStyle(value, style);
127     else if (name == vspaceAttr) {
128         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
129         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
130     } else if (name == hspaceAttr) {
131         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
132         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
133     } else if (name == alignAttr)
134         applyAlignmentAttributeToStyle(value, style);
135     else if (name == valignAttr)
136         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
137     else
138         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
139 }
140
141 const AtomicString& HTMLImageElement::imageSourceURL() const
142 {
143     return m_bestFitImageURL.isEmpty() ? attributeWithoutSynchronization(srcAttr) : m_bestFitImageURL;
144 }
145
146 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
147 {
148     m_bestFitImageURL = candidate.string.toAtomicString();
149     m_currentSrc = AtomicString(document().completeURL(imageSourceURL()).string());
150     if (candidate.density >= 0)
151         m_imageDevicePixelRatio = 1 / candidate.density;
152     if (is<RenderImage>(renderer()))
153         downcast<RenderImage>(*renderer()).setImageDevicePixelRatio(m_imageDevicePixelRatio);
154 }
155
156 ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement()
157 {
158     auto picture = makeRefPtr(pictureElement());
159     if (!picture)
160         return { };
161
162     picture->clearViewportDependentResults();
163     document().removeViewportDependentPicture(*picture);
164
165     picture->clearAppearanceDependentResults();
166     document().removeAppearanceDependentPicture(*picture);
167
168     for (RefPtr<Node> child = picture->firstChild(); child && child != this; child = child->nextSibling()) {
169         if (!is<HTMLSourceElement>(*child))
170             continue;
171         auto& source = downcast<HTMLSourceElement>(*child);
172
173         auto& srcset = source.attributeWithoutSynchronization(srcsetAttr);
174         if (srcset.isEmpty())
175             continue;
176
177         auto& typeAttribute = source.attributeWithoutSynchronization(typeAttr);
178         if (!typeAttribute.isNull()) {
179             String type = typeAttribute.string();
180             type.truncate(type.find(';'));
181             type = stripLeadingAndTrailingHTMLSpaces(type);
182             if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(type))
183                 continue;
184         }
185
186         auto documentElement = makeRefPtr(document().documentElement());
187         MediaQueryEvaluator evaluator { document().printing() ? "print" : "screen", document(), documentElement ? documentElement->computedStyle() : nullptr };
188         auto* queries = source.parsedMediaAttribute(document());
189         LOG(MediaQueries, "HTMLImageElement %p bestFitSourceFromPictureElement evaluating media queries", this);
190         auto evaluation = !queries || evaluator.evaluate(*queries, picture->viewportDependentResults(), picture->appearanceDependentResults());
191         if (picture->hasViewportDependentResults())
192             document().addViewportDependentPicture(*picture);
193         if (picture->hasAppearanceDependentResults())
194             document().addAppearanceDependentPicture(*picture);
195         if (!evaluation)
196             continue;
197
198         auto sourceSize = SizesAttributeParser(source.attributeWithoutSynchronization(sizesAttr).string(), document()).length();
199         auto candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom(), srcset, sourceSize);
200         if (!candidate.isEmpty())
201             return candidate;
202     }
203     return { };
204 }
205
206 void HTMLImageElement::selectImageSource()
207 {
208     // First look for the best fit source from our <picture> parent if we have one.
209     ImageCandidate candidate = bestFitSourceFromPictureElement();
210     if (candidate.isEmpty()) {
211         // If we don't have a <picture> or didn't find a source, then we use our own attributes.
212         auto sourceSize = SizesAttributeParser(attributeWithoutSynchronization(sizesAttr).string(), document()).length();
213         candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), attributeWithoutSynchronization(srcAttr), attributeWithoutSynchronization(srcsetAttr), sourceSize);
214     }
215     setBestFitURLAndDPRFromImageCandidate(candidate);
216     m_imageLoader.updateFromElementIgnoringPreviousError();
217 }
218
219 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
220 {
221     if (name == altAttr) {
222         if (is<RenderImage>(renderer()))
223             downcast<RenderImage>(*renderer()).updateAltText();
224     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
225         selectImageSource();
226     else if (name == usemapAttr) {
227         if (isInTreeScope() && !m_parsedUsemap.isNull())
228             treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
229
230         m_parsedUsemap = parseHTMLHashNameReference(value);
231
232         if (isInTreeScope() && !m_parsedUsemap.isNull())
233             treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
234     } else if (name == compositeAttr) {
235         // FIXME: images don't support blend modes in their compositing attribute.
236         BlendMode blendOp = BlendMode::Normal;
237         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
238             m_compositeOperator = CompositeSourceOver;
239 #if ENABLE(SERVICE_CONTROLS)
240     } else if (name == webkitimagemenuAttr) {
241         m_experimentalImageMenuEnabled = !value.isNull();
242         updateImageControls();
243 #endif
244     } else if (name == x_apple_editable_imageAttr)
245         updateEditableImage();
246     else {
247         if (name == nameAttr) {
248             bool willHaveName = !value.isNull();
249             if (m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
250                 HTMLDocument& document = downcast<HTMLDocument>(this->document());
251                 const AtomicString& id = getIdAttribute();
252                 if (!id.isEmpty() && id != getNameAttribute()) {
253                     if (willHaveName)
254                         document.addDocumentNamedItem(*id.impl(), *this);
255                     else
256                         document.removeDocumentNamedItem(*id.impl(), *this);
257                 }
258             }
259             m_hadNameBeforeAttributeChanged = willHaveName;
260         }
261         HTMLElement::parseAttribute(name, value);
262     }
263 }
264
265 const AtomicString& HTMLImageElement::altText() const
266 {
267     // lets figure out the alt text.. magic stuff
268     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
269     // also heavily discussed by Hixie on bugzilla
270     const AtomicString& alt = attributeWithoutSynchronization(altAttr);
271     if (!alt.isNull())
272         return alt;
273     // fall back to title attribute
274     return attributeWithoutSynchronization(titleAttr);
275 }
276
277 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
278 {
279     if (style.hasContent())
280         return RenderElement::createFor(*this, WTFMove(style));
281
282     return createRenderer<RenderImage>(*this, WTFMove(style), nullptr, m_imageDevicePixelRatio);
283 }
284
285 bool HTMLImageElement::canStartSelection() const
286 {
287     if (shadowRoot())
288         return HTMLElement::canStartSelection();
289
290     return false;
291 }
292
293 bool HTMLImageElement::supportsFocus() const
294 {
295     if (hasEditableImageAttribute())
296         return true;
297     return HTMLElement::supportsFocus();
298 }
299
300 bool HTMLImageElement::isFocusable() const
301 {
302     if (hasEditableImageAttribute())
303         return true;
304     return HTMLElement::isFocusable();
305 }
306
307 void HTMLImageElement::didAttachRenderers()
308 {
309     if (!is<RenderImage>(renderer()))
310         return;
311     if (m_imageLoader.hasPendingBeforeLoadEvent())
312         return;
313
314 #if ENABLE(SERVICE_CONTROLS)
315     updateImageControls();
316 #endif
317
318     auto& renderImage = downcast<RenderImage>(*renderer());
319     RenderImageResource& renderImageResource = renderImage.imageResource();
320     if (renderImageResource.cachedImage())
321         return;
322     renderImageResource.setCachedImage(m_imageLoader.image());
323
324     // If we have no image at all because we have no src attribute, set
325     // image height and width for the alt text instead.
326     if (!m_imageLoader.image() && !renderImageResource.cachedImage())
327         renderImage.setImageSizeForAltText();
328 }
329
330 Node::InsertedIntoAncestorResult HTMLImageElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
331 {
332     if (m_formSetByParser) {
333         m_form = m_formSetByParser;
334         m_formSetByParser = nullptr;
335         m_form->registerImgElement(this);
336     }
337
338     if (m_form && rootElement() != m_form->rootElement()) {
339         m_form->removeImgElement(this);
340         m_form = nullptr;
341     }
342
343     if (!m_form) {
344         m_form = HTMLFormElement::findClosestFormAncestor(*this);
345         if (m_form)
346             m_form->registerImgElement(this);
347     }
348
349     // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
350     // in callbacks back to this node.
351     Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
352
353     if (insertionType.connectedToDocument && hasEditableImageAttribute())
354         insertNotificationRequest = InsertedIntoAncestorResult::NeedsPostInsertionCallback;
355
356     if (insertionType.treeScopeChanged && !m_parsedUsemap.isNull())
357         treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
358
359     if (is<HTMLPictureElement>(parentNode())) {
360         setPictureElement(&downcast<HTMLPictureElement>(*parentNode()));
361         selectImageSource();
362     }
363
364     // If we have been inserted from a renderer-less document,
365     // our loader may have not fetched the image, so do it now.
366     if (insertionType.connectedToDocument && !m_imageLoader.image())
367         m_imageLoader.updateFromElement();
368
369     return insertNotificationRequest;
370 }
371
372 void HTMLImageElement::didFinishInsertingNode()
373 {
374     if (hasEditableImageAttribute())
375         updateEditableImage();
376 }
377
378 void HTMLImageElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
379 {
380     if (m_form)
381         m_form->removeImgElement(this);
382
383     if (removalType.treeScopeChanged && !m_parsedUsemap.isNull())
384         oldParentOfRemovedTree.treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
385
386     if (is<HTMLPictureElement>(parentNode()))
387         setPictureElement(nullptr);
388
389     if (removalType.disconnectedFromDocument)
390         updateEditableImage();
391
392     m_form = nullptr;
393     HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
394 }
395
396 bool HTMLImageElement::hasEditableImageAttribute() const
397 {
398     if (!document().settings().editableImagesEnabled())
399         return false;
400     return hasAttributeWithoutSynchronization(x_apple_editable_imageAttr);
401 }
402
403 GraphicsLayer::EmbeddedViewID HTMLImageElement::editableImageViewID() const
404 {
405     if (!m_editableImage)
406         return 0;
407     return m_editableImage->embeddedViewID();
408 }
409
410 void HTMLImageElement::updateEditableImage()
411 {
412     if (!document().settings().editableImagesEnabled())
413         return;
414
415     auto* page = document().page();
416     if (!page)
417         return;
418
419     bool hasEditableAttribute = hasEditableImageAttribute();
420     bool isCurrentlyEditable = !!m_editableImage;
421     bool shouldBeEditable = isConnected() && hasEditableAttribute;
422
423 #if ENABLE(ATTACHMENT_ELEMENT)
424     // Create the inner attachment for editable images, or non-editable
425     // images that were cloned from editable image sources.
426     if (!attachmentElement() && (shouldBeEditable || !m_pendingClonedAttachmentID.isEmpty())) {
427         auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document());
428         if (!m_pendingClonedAttachmentID.isEmpty())
429             attachment->setUniqueIdentifier(WTFMove(m_pendingClonedAttachmentID));
430         else
431             attachment->ensureUniqueIdentifier();
432         setAttachmentElement(WTFMove(attachment));
433     }
434 #endif
435
436     if (shouldBeEditable == isCurrentlyEditable)
437         return;
438
439     if (!hasEditableAttribute) {
440         m_editableImage = nullptr;
441         return;
442     }
443
444     if (!m_editableImage)
445         m_editableImage = EditableImageReference::create(document());
446
447 #if ENABLE(ATTACHMENT_ELEMENT)
448     m_editableImage->associateWithAttachment(attachmentElement()->uniqueIdentifier());
449 #endif
450 }
451
452 HTMLPictureElement* HTMLImageElement::pictureElement() const
453 {
454     return m_pictureElement.get();
455 }
456     
457 void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement)
458 {
459     m_pictureElement = makeWeakPtr(pictureElement);
460 }
461     
462 unsigned HTMLImageElement::width(bool ignorePendingStylesheets)
463 {
464     if (!renderer()) {
465         // check the attribute first for an explicit pixel value
466         auto optionalWidth = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr));
467         if (optionalWidth)
468             return optionalWidth.value();
469
470         // if the image is available, use its width
471         if (m_imageLoader.image())
472             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width().toUnsigned();
473     }
474
475     if (ignorePendingStylesheets)
476         document().updateLayoutIgnorePendingStylesheets();
477     else
478         document().updateLayout();
479
480     RenderBox* box = renderBox();
481     if (!box)
482         return 0;
483     LayoutRect contentRect = box->contentBoxRect();
484     return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box);
485 }
486
487 unsigned HTMLImageElement::height(bool ignorePendingStylesheets)
488 {
489     if (!renderer()) {
490         // check the attribute first for an explicit pixel value
491         auto optionalHeight = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr));
492         if (optionalHeight)
493             return optionalHeight.value();
494
495         // if the image is available, use its height
496         if (m_imageLoader.image())
497             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height().toUnsigned();
498     }
499
500     if (ignorePendingStylesheets)
501         document().updateLayoutIgnorePendingStylesheets();
502     else
503         document().updateLayout();
504
505     RenderBox* box = renderBox();
506     if (!box)
507         return 0;
508     LayoutRect contentRect = box->contentBoxRect();
509     return adjustForAbsoluteZoom(snappedIntRect(contentRect).height(), *box);
510 }
511
512 int HTMLImageElement::naturalWidth() const
513 {
514     if (!m_imageLoader.image())
515         return 0;
516
517     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
518 }
519
520 int HTMLImageElement::naturalHeight() const
521 {
522     if (!m_imageLoader.image())
523         return 0;
524
525     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
526 }
527
528 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
529 {
530     return attribute.name() == srcAttr
531         || attribute.name() == lowsrcAttr
532         || attribute.name() == longdescAttr
533         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
534         || HTMLElement::isURLAttribute(attribute);
535 }
536
537 bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const
538 {
539     return attribute.name() == srcsetAttr
540         || HTMLElement::attributeContainsURL(attribute);
541 }
542
543 String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
544 {
545     if (attribute.name() == srcsetAttr) {
546         Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value()));
547         StringBuilder result;
548         for (const auto& candidate : imageCandidates) {
549             if (&candidate != &imageCandidates[0])
550                 result.appendLiteral(", ");
551             result.append(URL(base, candidate.string.toString()).string());
552             if (candidate.density != UninitializedDescriptor) {
553                 result.append(' ');
554                 result.appendNumber(candidate.density);
555                 result.append('x');
556             }
557             if (candidate.resourceWidth != UninitializedDescriptor) {
558                 result.append(' ');
559                 result.appendNumber(candidate.resourceWidth);
560                 result.append('w');
561             }
562         }
563         return result.toString();
564     }
565     return HTMLElement::completeURLsInAttributeValue(base, attribute);
566 }
567
568 bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const
569 {
570     return m_parsedUsemap.impl() == &name;
571 }
572
573 HTMLMapElement* HTMLImageElement::associatedMapElement() const
574 {
575     return treeScope().getImageMap(m_parsedUsemap);
576 }
577
578 const AtomicString& HTMLImageElement::alt() const
579 {
580     return attributeWithoutSynchronization(altAttr);
581 }
582
583 bool HTMLImageElement::draggable() const
584 {
585     // Image elements are draggable by default.
586     return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "false");
587 }
588
589 void HTMLImageElement::setHeight(unsigned value)
590 {
591     setUnsignedIntegralAttribute(heightAttr, value);
592 }
593
594 URL HTMLImageElement::src() const
595 {
596     return document().completeURL(attributeWithoutSynchronization(srcAttr));
597 }
598
599 void HTMLImageElement::setSrc(const String& value)
600 {
601     setAttributeWithoutSynchronization(srcAttr, value);
602 }
603
604 void HTMLImageElement::setWidth(unsigned value)
605 {
606     setUnsignedIntegralAttribute(widthAttr, value);
607 }
608
609 int HTMLImageElement::x() const
610 {
611     document().updateLayoutIgnorePendingStylesheets();
612     auto renderer = this->renderer();
613     if (!renderer)
614         return 0;
615
616     // FIXME: This doesn't work correctly with transforms.
617     return renderer->localToAbsolute().x();
618 }
619
620 int HTMLImageElement::y() const
621 {
622     document().updateLayoutIgnorePendingStylesheets();
623     auto renderer = this->renderer();
624     if (!renderer)
625         return 0;
626
627     // FIXME: This doesn't work correctly with transforms.
628     return renderer->localToAbsolute().y();
629 }
630
631 bool HTMLImageElement::complete() const
632 {
633     return m_imageLoader.imageComplete();
634 }
635
636 DecodingMode HTMLImageElement::decodingMode() const
637 {
638     const AtomicString& decodingMode = attributeWithoutSynchronization(decodingAttr);
639     if (equalLettersIgnoringASCIICase(decodingMode, "sync"))
640         return DecodingMode::Synchronous;
641     if (equalLettersIgnoringASCIICase(decodingMode, "async"))
642         return DecodingMode::Asynchronous;
643     return DecodingMode::Auto;
644 }
645     
646 void HTMLImageElement::decode(Ref<DeferredPromise>&& promise)
647 {
648     return m_imageLoader.decode(WTFMove(promise));
649 }
650
651 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
652 {
653     HTMLElement::addSubresourceAttributeURLs(urls);
654
655     addSubresourceURL(urls, document().completeURL(imageSourceURL()));
656     // FIXME: What about when the usemap attribute begins with "#"?
657     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr)));
658 }
659
660 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
661 {
662     m_imageLoader.elementDidMoveToNewDocument();
663     HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
664 }
665
666 bool HTMLImageElement::isServerMap() const
667 {
668     if (!hasAttributeWithoutSynchronization(ismapAttr))
669         return false;
670
671     const AtomicString& usemap = attributeWithoutSynchronization(usemapAttr);
672
673     // If the usemap attribute starts with '#', it refers to a map element in the document.
674     if (usemap.string()[0] == '#')
675         return false;
676
677     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
678 }
679
680 void HTMLImageElement::setCrossOrigin(const AtomicString& value)
681 {
682     setAttributeWithoutSynchronization(crossoriginAttr, value);
683 }
684
685 String HTMLImageElement::crossOrigin() const
686 {
687     return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
688 }
689
690 #if ENABLE(ATTACHMENT_ELEMENT)
691
692 void HTMLImageElement::setAttachmentElement(Ref<HTMLAttachmentElement>&& attachment)
693 {
694     if (auto existingAttachment = attachmentElement())
695         existingAttachment->remove();
696
697     attachment->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
698     ensureUserAgentShadowRoot().appendChild(WTFMove(attachment));
699 }
700
701 RefPtr<HTMLAttachmentElement> HTMLImageElement::attachmentElement() const
702 {
703     if (auto shadowRoot = userAgentShadowRoot())
704         return childrenOfType<HTMLAttachmentElement>(*shadowRoot).first();
705
706     return nullptr;
707 }
708
709 const String& HTMLImageElement::attachmentIdentifier() const
710 {
711     if (!m_pendingClonedAttachmentID.isEmpty())
712         return m_pendingClonedAttachmentID;
713
714     if (auto attachment = attachmentElement())
715         return attachment->uniqueIdentifier();
716
717     return nullAtom();
718 }
719
720 #endif // ENABLE(ATTACHMENT_ELEMENT)
721
722 #if ENABLE(SERVICE_CONTROLS)
723 void HTMLImageElement::updateImageControls()
724 {
725     // If this image element is inside a shadow tree then it is part of an image control.
726     if (isInShadowTree())
727         return;
728
729     if (!document().settings().imageControlsEnabled())
730         return;
731
732     bool hasControls = hasImageControls();
733     if (!m_experimentalImageMenuEnabled && hasControls)
734         destroyImageControls();
735     else if (m_experimentalImageMenuEnabled && !hasControls)
736         tryCreateImageControls();
737 }
738
739 void HTMLImageElement::tryCreateImageControls()
740 {
741     ASSERT(m_experimentalImageMenuEnabled);
742     ASSERT(!hasImageControls());
743
744     auto imageControls = ImageControlsRootElement::tryCreate(document());
745     if (!imageControls)
746         return;
747
748     ensureUserAgentShadowRoot().appendChild(*imageControls);
749
750     auto* renderObject = renderer();
751     if (!renderObject)
752         return;
753
754     downcast<RenderImage>(*renderObject).setHasShadowControls(true);
755 }
756
757 void HTMLImageElement::destroyImageControls()
758 {
759     auto shadowRoot = userAgentShadowRoot();
760     if (!shadowRoot)
761         return;
762
763     if (RefPtr<Node> node = shadowRoot->firstChild()) {
764         ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement());
765         shadowRoot->removeChild(*node);
766     }
767
768     auto* renderObject = renderer();
769     if (!renderObject)
770         return;
771
772     downcast<RenderImage>(*renderObject).setHasShadowControls(false);
773 }
774
775 bool HTMLImageElement::hasImageControls() const
776 {
777     if (auto shadowRoot = userAgentShadowRoot()) {
778         RefPtr<Node> node = shadowRoot->firstChild();
779         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement());
780         return node;
781     }
782
783     return false;
784 }
785
786 bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const
787 {
788     return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
789 }
790 #endif // ENABLE(SERVICE_CONTROLS)
791
792 #if PLATFORM(IOS_FAMILY)
793 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
794 bool HTMLImageElement::willRespondToMouseClickEvents()
795 {
796     auto renderer = this->renderer();
797     if (!renderer || renderer->style().touchCalloutEnabled())
798         return true;
799     return HTMLElement::willRespondToMouseClickEvents();
800 }
801 #endif
802
803 #if USE(SYSTEM_PREVIEW)
804 bool HTMLImageElement::isSystemPreviewImage() const
805 {
806     if (!RuntimeEnabledFeatures::sharedFeatures().systemPreviewEnabled())
807         return false;
808
809     const auto* parent = parentElement();
810     if (is<HTMLAnchorElement>(parent))
811         return downcast<HTMLAnchorElement>(parent)->isSystemPreviewLink();
812     if (is<HTMLPictureElement>(parent))
813         return downcast<HTMLPictureElement>(parent)->isSystemPreviewImage();
814     return false;
815 }
816 #endif
817
818 void HTMLImageElement::copyNonAttributePropertiesFromElement(const Element& source)
819 {
820     auto& sourceImage = static_cast<const HTMLImageElement&>(source);
821 #if ENABLE(ATTACHMENT_ELEMENT)
822     m_pendingClonedAttachmentID = !sourceImage.m_pendingClonedAttachmentID.isEmpty() ? sourceImage.m_pendingClonedAttachmentID : sourceImage.attachmentIdentifier();
823 #endif
824     m_editableImage = sourceImage.m_editableImage;
825     Element::copyNonAttributePropertiesFromElement(source);
826 }
827
828 void HTMLImageElement::defaultEventHandler(Event& event)
829 {
830     if (hasEditableImageAttribute() && event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) {
831         focus();
832         event.setDefaultHandled();
833         return;
834     }
835     HTMLElement::defaultEventHandler(event);
836 }
837
838 }