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