Add an argument indicating the type of insertion to Node::insertedInto
[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 "Logging.h"
37 #include "MIMETypeRegistry.h"
38 #include "MediaList.h"
39 #include "MediaQueryEvaluator.h"
40 #include "NodeTraversal.h"
41 #include "RenderImage.h"
42 #include "RenderView.h"
43 #include "Settings.h"
44 #include "ShadowRoot.h"
45 #include "SizesAttributeParser.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, std::optional<unsigned> width, std::optional<unsigned> height)
92 {
93     auto image = adoptRef(*new HTMLImageElement(imgTag, document));
94     if (width)
95         image->setWidth(width.value());
96     if (height)
97         image->setHeight(height.value());
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() ? attributeWithoutSynchronization(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.attributeWithoutSynchronization(srcsetAttr);
158         if (srcset.isEmpty())
159             continue;
160
161         auto& typeAttribute = source.attributeWithoutSynchronization(typeAttr);
162         if (!typeAttribute.isNull()) {
163             String type = typeAttribute.string();
164             type.truncate(type.find(';'));
165             type = stripLeadingAndTrailingHTMLSpaces(type);
166             if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageOrSVGMIMEType(type))
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.parsedMediaAttribute();
173         LOG(MediaQueries, "HTMLImageElement %p bestFitSourceFromPictureElement evaluating media queries", this);
174         auto evaluation = !queries || evaluator.evaluate(*queries, picture->viewportDependentResults());
175         if (picture->hasViewportDependentResults())
176             document().addViewportDependentPicture(*picture);
177         if (!evaluation)
178             continue;
179
180         auto sourceSize = SizesAttributeParser(source.attributeWithoutSynchronization(sizesAttr).string(), document()).length();
181         auto candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom(), srcset, sourceSize);
182         if (!candidate.isEmpty())
183             return candidate;
184     }
185     return { };
186 }
187
188 void HTMLImageElement::selectImageSource()
189 {
190     // First look for the best fit source from our <picture> parent if we have one.
191     ImageCandidate candidate = bestFitSourceFromPictureElement();
192     if (candidate.isEmpty()) {
193         // If we don't have a <picture> or didn't find a source, then we use our own attributes.
194         auto sourceSize = SizesAttributeParser(attributeWithoutSynchronization(sizesAttr).string(), document()).length();
195         candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), attributeWithoutSynchronization(srcAttr), attributeWithoutSynchronization(srcsetAttr), sourceSize);
196     }
197     setBestFitURLAndDPRFromImageCandidate(candidate);
198     m_imageLoader.updateFromElementIgnoringPreviousError();
199 }
200
201 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
202 {
203     if (name == altAttr) {
204         if (is<RenderImage>(renderer()))
205             downcast<RenderImage>(*renderer()).updateAltText();
206     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
207         selectImageSource();
208     else if (name == usemapAttr) {
209         if (isConnected() && !m_parsedUsemap.isNull())
210             document().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
211
212         m_parsedUsemap = parseHTMLHashNameReference(value);
213
214         if (isConnected() && !m_parsedUsemap.isNull())
215             document().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
216     } else if (name == compositeAttr) {
217         // FIXME: images don't support blend modes in their compositing attribute.
218         BlendMode blendOp = BlendModeNormal;
219         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
220             m_compositeOperator = CompositeSourceOver;
221 #if ENABLE(SERVICE_CONTROLS)
222     } else if (name == webkitimagemenuAttr) {
223         m_experimentalImageMenuEnabled = !value.isNull();
224         updateImageControls();
225 #endif
226     } else {
227         if (name == nameAttr) {
228             bool willHaveName = !value.isNull();
229             if (m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
230                 HTMLDocument& document = downcast<HTMLDocument>(this->document());
231                 const AtomicString& id = getIdAttribute();
232                 if (!id.isEmpty() && id != getNameAttribute()) {
233                     if (willHaveName)
234                         document.addDocumentNamedItem(*id.impl(), *this);
235                     else
236                         document.removeDocumentNamedItem(*id.impl(), *this);
237                 }
238             }
239             m_hadNameBeforeAttributeChanged = willHaveName;
240         }
241         HTMLElement::parseAttribute(name, value);
242     }
243 }
244
245 const AtomicString& HTMLImageElement::altText() const
246 {
247     // lets figure out the alt text.. magic stuff
248     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
249     // also heavily discussed by Hixie on bugzilla
250     const AtomicString& alt = attributeWithoutSynchronization(altAttr);
251     if (!alt.isNull())
252         return alt;
253     // fall back to title attribute
254     return attributeWithoutSynchronization(titleAttr);
255 }
256
257 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
258 {
259     if (style.hasContent())
260         return RenderElement::createFor(*this, WTFMove(style));
261
262     return createRenderer<RenderImage>(*this, WTFMove(style), nullptr, m_imageDevicePixelRatio);
263 }
264
265 bool HTMLImageElement::canStartSelection() const
266 {
267     if (shadowRoot())
268         return HTMLElement::canStartSelection();
269
270     return false;
271 }
272
273 void HTMLImageElement::didAttachRenderers()
274 {
275     if (!is<RenderImage>(renderer()))
276         return;
277     if (m_imageLoader.hasPendingBeforeLoadEvent())
278         return;
279
280 #if ENABLE(SERVICE_CONTROLS)
281     updateImageControls();
282 #endif
283
284     auto& renderImage = downcast<RenderImage>(*renderer());
285     RenderImageResource& renderImageResource = renderImage.imageResource();
286     if (renderImageResource.cachedImage())
287         return;
288     renderImageResource.setCachedImage(m_imageLoader.image());
289
290     // If we have no image at all because we have no src attribute, set
291     // image height and width for the alt text instead.
292     if (!m_imageLoader.image() && !renderImageResource.cachedImage())
293         renderImage.setImageSizeForAltText();
294 }
295
296 Node::InsertedIntoResult HTMLImageElement::insertedInto(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
297 {
298     if (m_formSetByParser) {
299         m_form = m_formSetByParser;
300         m_formSetByParser = nullptr;
301         m_form->registerImgElement(this);
302     }
303
304     if (m_form && rootElement() != m_form->rootElement()) {
305         m_form->removeImgElement(this);
306         m_form = nullptr;
307     }
308
309     if (!m_form) {
310         m_form = HTMLFormElement::findClosestFormAncestor(*this);
311         if (m_form)
312             m_form->registerImgElement(this);
313     }
314     // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
315     // in callbacks back to this node.
316     Node::InsertedIntoResult insertNotificationRequest = HTMLElement::insertedInto(insertionType, parentOfInsertedTree);
317
318     if (insertionType.connectedToDocument && !m_parsedUsemap.isNull())
319         document().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
320
321     if (is<HTMLPictureElement>(parentNode())) {
322         setPictureElement(&downcast<HTMLPictureElement>(*parentNode()));
323         selectImageSource();
324     }
325
326     // If we have been inserted from a renderer-less document,
327     // our loader may have not fetched the image, so do it now.
328     if (insertionType.connectedToDocument && !m_imageLoader.image())
329         m_imageLoader.updateFromElement();
330
331     return insertNotificationRequest;
332 }
333
334 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
335 {
336     if (m_form)
337         m_form->removeImgElement(this);
338
339     if (insertionPoint.isConnected() && !m_parsedUsemap.isNull())
340         document().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
341     
342     if (is<HTMLPictureElement>(parentNode()))
343         setPictureElement(nullptr);
344     
345     m_form = nullptr;
346     HTMLElement::removedFrom(insertionPoint);
347 }
348
349 HTMLPictureElement* HTMLImageElement::pictureElement() const
350 {
351     if (!gPictureOwnerMap || !gPictureOwnerMap->contains(this))
352         return nullptr;
353     HTMLPictureElement* result = gPictureOwnerMap->get(this).get();
354     if (!result)
355         gPictureOwnerMap->remove(this);
356     return result;
357 }
358     
359 void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement)
360 {
361     if (!pictureElement) {
362         if (gPictureOwnerMap)
363             gPictureOwnerMap->remove(this);
364         return;
365     }
366     
367     if (!gPictureOwnerMap)
368         gPictureOwnerMap = new PictureOwnerMap();
369     gPictureOwnerMap->add(this, pictureElement->createWeakPtr());
370 }
371     
372 unsigned HTMLImageElement::width(bool ignorePendingStylesheets)
373 {
374     if (!renderer()) {
375         // check the attribute first for an explicit pixel value
376         auto optionalWidth = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr));
377         if (optionalWidth)
378             return optionalWidth.value();
379
380         // if the image is available, use its width
381         if (m_imageLoader.image())
382             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width().toUnsigned();
383     }
384
385     if (ignorePendingStylesheets)
386         document().updateLayoutIgnorePendingStylesheets();
387     else
388         document().updateLayout();
389
390     RenderBox* box = renderBox();
391     if (!box)
392         return 0;
393     LayoutRect contentRect = box->contentBoxRect();
394     return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box);
395 }
396
397 unsigned HTMLImageElement::height(bool ignorePendingStylesheets)
398 {
399     if (!renderer()) {
400         // check the attribute first for an explicit pixel value
401         auto optionalHeight = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr));
402         if (optionalHeight)
403             return optionalHeight.value();
404
405         // if the image is available, use its height
406         if (m_imageLoader.image())
407             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height().toUnsigned();
408     }
409
410     if (ignorePendingStylesheets)
411         document().updateLayoutIgnorePendingStylesheets();
412     else
413         document().updateLayout();
414
415     RenderBox* box = renderBox();
416     if (!box)
417         return 0;
418     LayoutRect contentRect = box->contentBoxRect();
419     return adjustForAbsoluteZoom(snappedIntRect(contentRect).height(), *box);
420 }
421
422 int HTMLImageElement::naturalWidth() const
423 {
424     if (!m_imageLoader.image())
425         return 0;
426
427     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
428 }
429
430 int HTMLImageElement::naturalHeight() const
431 {
432     if (!m_imageLoader.image())
433         return 0;
434
435     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
436 }
437
438 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
439 {
440     return attribute.name() == srcAttr
441         || attribute.name() == lowsrcAttr
442         || attribute.name() == longdescAttr
443         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
444         || HTMLElement::isURLAttribute(attribute);
445 }
446
447 bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const
448 {
449     return attribute.name() == srcsetAttr
450         || HTMLElement::attributeContainsURL(attribute);
451 }
452
453 String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
454 {
455     if (attribute.name() == srcsetAttr) {
456         Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value()));
457         StringBuilder result;
458         for (const auto& candidate : imageCandidates) {
459             if (&candidate != &imageCandidates[0])
460                 result.appendLiteral(", ");
461             result.append(URL(base, candidate.string.toString()).string());
462             if (candidate.density != UninitializedDescriptor) {
463                 result.append(' ');
464                 result.appendNumber(candidate.density);
465                 result.append('x');
466             }
467             if (candidate.resourceWidth != UninitializedDescriptor) {
468                 result.append(' ');
469                 result.appendNumber(candidate.resourceWidth);
470                 result.append('w');
471             }
472         }
473         return result.toString();
474     }
475     return HTMLElement::completeURLsInAttributeValue(base, attribute);
476 }
477
478 bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const
479 {
480     return m_parsedUsemap.impl() == &name;
481 }
482
483 const AtomicString& HTMLImageElement::alt() const
484 {
485     return attributeWithoutSynchronization(altAttr);
486 }
487
488 bool HTMLImageElement::draggable() const
489 {
490     // Image elements are draggable by default.
491     return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "false");
492 }
493
494 void HTMLImageElement::setHeight(unsigned value)
495 {
496     setUnsignedIntegralAttribute(heightAttr, value);
497 }
498
499 URL HTMLImageElement::src() const
500 {
501     return document().completeURL(attributeWithoutSynchronization(srcAttr));
502 }
503
504 void HTMLImageElement::setSrc(const String& value)
505 {
506     setAttributeWithoutSynchronization(srcAttr, value);
507 }
508
509 void HTMLImageElement::setWidth(unsigned value)
510 {
511     setUnsignedIntegralAttribute(widthAttr, value);
512 }
513
514 int HTMLImageElement::x() const
515 {
516     document().updateLayoutIgnorePendingStylesheets();
517     auto renderer = this->renderer();
518     if (!renderer)
519         return 0;
520
521     // FIXME: This doesn't work correctly with transforms.
522     return renderer->localToAbsolute().x();
523 }
524
525 int HTMLImageElement::y() const
526 {
527     document().updateLayoutIgnorePendingStylesheets();
528     auto renderer = this->renderer();
529     if (!renderer)
530         return 0;
531
532     // FIXME: This doesn't work correctly with transforms.
533     return renderer->localToAbsolute().y();
534 }
535
536 bool HTMLImageElement::complete() const
537 {
538     return m_imageLoader.imageComplete();
539 }
540
541 void HTMLImageElement::decode(Ref<DeferredPromise>&& promise)
542 {
543     return m_imageLoader.decode(WTFMove(promise));
544 }
545
546 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
547 {
548     HTMLElement::addSubresourceAttributeURLs(urls);
549
550     addSubresourceURL(urls, document().completeURL(imageSourceURL()));
551     // FIXME: What about when the usemap attribute begins with "#"?
552     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr)));
553 }
554
555 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
556 {
557     m_imageLoader.elementDidMoveToNewDocument();
558     HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
559 }
560
561 bool HTMLImageElement::isServerMap() const
562 {
563     if (!hasAttributeWithoutSynchronization(ismapAttr))
564         return false;
565
566     const AtomicString& usemap = attributeWithoutSynchronization(usemapAttr);
567
568     // If the usemap attribute starts with '#', it refers to a map element in the document.
569     if (usemap.string()[0] == '#')
570         return false;
571
572     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
573 }
574
575 void HTMLImageElement::setCrossOrigin(const AtomicString& value)
576 {
577     setAttributeWithoutSynchronization(crossoriginAttr, value);
578 }
579
580 String HTMLImageElement::crossOrigin() const
581 {
582     return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
583 }
584
585 #if ENABLE(SERVICE_CONTROLS)
586 void HTMLImageElement::updateImageControls()
587 {
588     // If this image element is inside a shadow tree then it is part of an image control.
589     if (isInShadowTree())
590         return;
591
592     if (!document().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 }