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