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