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