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