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