Avoid downloading the wrong image for <picture> elements.
[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, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
5  * Copyright (C) 2010 Google Inc. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24 #include "HTMLImageElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "CSSValueKeywords.h"
28 #include "CachedImage.h"
29 #include "EventNames.h"
30 #include "FrameView.h"
31 #include "HTMLAnchorElement.h"
32 #include "HTMLDocument.h"
33 #include "HTMLFormElement.h"
34 #include "HTMLParserIdioms.h"
35 #include "HTMLPictureElement.h"
36 #include "HTMLSourceElement.h"
37 #include "HTMLSrcsetParser.h"
38 #include "MIMETypeRegistry.h"
39 #include "MediaList.h"
40 #include "MediaQueryEvaluator.h"
41 #include "Page.h"
42 #include "RenderImage.h"
43 #include "Settings.h"
44 #include "ShadowRoot.h"
45 #include "SourceSizeList.h"
46 #include <wtf/text/StringBuilder.h>
47
48 #if ENABLE(SERVICE_CONTROLS)
49 #include "ImageControlsRootElement.h"
50 #endif
51
52 namespace WebCore {
53
54 using namespace HTMLNames;
55
56 typedef HashMap<const Node*, Node*> 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     setPictureNode(nullptr);
89 }
90
91 Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
92 {
93     Ref<HTMLImageElement> image = adoptRef(*new HTMLImageElement(imgTag, document));
94     if (optionalWidth)
95         image->setWidth(*optionalWidth);
96     if (optionalHeight)
97         image->setHeight(*optionalHeight);
98     return image;
99 }
100
101 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
102 {
103     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
104         return true;
105     return HTMLElement::isPresentationAttribute(name);
106 }
107
108 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
109 {
110     if (name == widthAttr)
111         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
112     else if (name == heightAttr)
113         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
114     else if (name == borderAttr)
115         applyBorderAttributeToStyle(value, style);
116     else if (name == vspaceAttr) {
117         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
118         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
119     } else if (name == hspaceAttr) {
120         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
121         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
122     } else if (name == alignAttr)
123         applyAlignmentAttributeToStyle(value, style);
124     else if (name == valignAttr)
125         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
126     else
127         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
128 }
129
130 const AtomicString& HTMLImageElement::imageSourceURL() const
131 {
132     return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
133 }
134
135 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
136 {
137     m_bestFitImageURL = candidate.string.toString();
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* pictureOwner = pictureNode();
148     if (!pictureOwner)
149         return { };
150     auto* picture = downcast<HTMLPictureElement>(pictureOwner);
151     picture->clearViewportDependentResults();
152     document().removeViewportDependentPicture(*picture);
153     for (Node* child = picture->firstChild(); child && child != this; child = child->nextSibling()) {
154         if (!is<HTMLSourceElement>(*child))
155             continue;
156         auto& source = downcast<HTMLSourceElement>(*child);
157         auto& srcset = source.fastGetAttribute(srcsetAttr);
158         if (srcset.isEmpty())
159             continue;
160         if (source.hasAttribute(typeAttr)) {
161             String type = source.fastGetAttribute(typeAttr).string();
162             int indexOfSemicolon = type.find(';');
163             if (indexOfSemicolon >= 0)
164                 type.truncate(indexOfSemicolon);
165             type = stripLeadingAndTrailingHTMLSpaces(type);
166             type = type.lower();
167             if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageMIMEType(type) && type != "image/svg+xml")
168                 continue;
169         }
170         MediaQueryEvaluator evaluator(document().printing() ? "print" : "screen", document().frame(), document().documentElement()->computedStyle());
171         bool evaluation = evaluator.evalCheckingViewportDependentResults(source.mediaQuerySet(), picture->viewportDependentResults());
172         if (picture->hasViewportDependentResults())
173             document().addViewportDependentPicture(*picture);
174         if (!evaluation)
175             continue;
176
177         float sourceSize = parseSizesAttribute(source.fastGetAttribute(sizesAttr).string(), document().renderView(), document().frame());
178         ImageCandidate candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom, source.fastGetAttribute(srcsetAttr), sourceSize);
179         if (!candidate.isEmpty())
180             return candidate;
181     }
182     return { };
183 }
184
185 void HTMLImageElement::selectImageSource()
186 {
187     // First look for the best fit source from our <picture> parent if we have one.
188     ImageCandidate candidate = bestFitSourceFromPictureElement();
189     if (candidate.isEmpty()) {
190         // If we don't have a <picture> or didn't find a source, then we use our own attributes.
191         float sourceSize = parseSizesAttribute(fastGetAttribute(sizesAttr).string(), document().renderView(), document().frame());
192         candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr), sourceSize);
193     }
194     setBestFitURLAndDPRFromImageCandidate(candidate);
195     m_imageLoader.updateFromElementIgnoringPreviousError();
196 }
197
198 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
199 {
200     if (name == altAttr) {
201         if (is<RenderImage>(renderer()))
202             downcast<RenderImage>(*renderer()).updateAltText();
203     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
204         selectImageSource();
205     else if (name == usemapAttr) {
206         if (inDocument() && !m_lowercasedUsemap.isNull())
207             document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
208
209         // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off.
210         // FIXME: We should check that the first character is '#'.
211         // FIXME: HTML5 specification says we should strip any leading string before '#'.
212         // FIXME: HTML5 specification says we should ignore usemap attributes without #.
213         if (value.length() > 1)
214             m_lowercasedUsemap = value.string().substring(1).lower();
215         else
216             m_lowercasedUsemap = nullAtom;
217
218         if (inDocument() && !m_lowercasedUsemap.isNull())
219             document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.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 && inDocument() && 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 = fastGetAttribute(altAttr);
255     if (!alt.isNull())
256         return alt;
257     // fall back to title attribute
258     return fastGetAttribute(titleAttr);
259 }
260
261 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&)
262 {
263     if (style.get().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.hasImage())
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::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
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) {
309         m_form = HTMLFormElement::findClosestFormAncestor(*this);
310         if (m_form)
311             m_form->registerImgElement(this);
312     }
313     // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
314     // in callbacks back to this node.
315     Node::InsertionNotificationRequest insertNotificationRequest = HTMLElement::insertedInto(insertionPoint);
316
317     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
318         document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
319
320     if (is<HTMLPictureElement>(parentNode())) {
321         setPictureNode(parentNode());
322         selectImageSource();
323     }
324
325     // If we have been inserted from a renderer-less document,
326     // our loader may have not fetched the image, so do it now.
327     if (insertionPoint.inDocument() && !m_imageLoader.image())
328         m_imageLoader.updateFromElement();
329
330     return insertNotificationRequest;
331 }
332
333 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
334 {
335     if (m_form)
336         m_form->removeImgElement(this);
337
338     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
339         document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
340     
341     if (is<HTMLPictureElement>(parentNode()))
342         setPictureNode(nullptr);
343     
344     m_form = nullptr;
345     HTMLElement::removedFrom(insertionPoint);
346 }
347
348 Node* HTMLImageElement::pictureNode() const
349 {
350     if (!gPictureOwnerMap)
351         return nullptr;
352     return gPictureOwnerMap->get(this);
353 }
354     
355 void HTMLImageElement::setPictureNode(Node* node)
356 {
357     if (!node) {
358         if (gPictureOwnerMap)
359             gPictureOwnerMap->remove(this);
360         return;
361     }
362     
363     if (!gPictureOwnerMap)
364         gPictureOwnerMap = new PictureOwnerMap();
365     gPictureOwnerMap->add(this, node);
366 }
367     
368 int HTMLImageElement::width(bool ignorePendingStylesheets)
369 {
370     if (!renderer()) {
371         // check the attribute first for an explicit pixel value
372         bool ok;
373         int width = getAttribute(widthAttr).toInt(&ok);
374         if (ok)
375             return width;
376
377         // if the image is available, use its width
378         if (m_imageLoader.image())
379             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
380     }
381
382     if (ignorePendingStylesheets)
383         document().updateLayoutIgnorePendingStylesheets();
384     else
385         document().updateLayout();
386
387     RenderBox* box = renderBox();
388     if (!box)
389         return 0;
390     LayoutRect contentRect = box->contentBoxRect();
391     return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box);
392 }
393
394 int HTMLImageElement::height(bool ignorePendingStylesheets)
395 {
396     if (!renderer()) {
397         // check the attribute first for an explicit pixel value
398         bool ok;
399         int height = getAttribute(heightAttr).toInt(&ok);
400         if (ok)
401             return height;
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();
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('x');
469             }
470         }
471         return result.toString();
472     }
473     return HTMLElement::completeURLsInAttributeValue(base, attribute);
474 }
475
476 bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
477 {
478     ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name);
479     return m_lowercasedUsemap.impl() == &name;
480 }
481
482 const AtomicString& HTMLImageElement::alt() const
483 {
484     return fastGetAttribute(altAttr);
485 }
486
487 bool HTMLImageElement::draggable() const
488 {
489     // Image elements are draggable by default.
490     return !equalIgnoringCase(fastGetAttribute(draggableAttr), "false");
491 }
492
493 void HTMLImageElement::setHeight(int value)
494 {
495     setIntegralAttribute(heightAttr, value);
496 }
497
498 URL HTMLImageElement::src() const
499 {
500     return document().completeURL(fastGetAttribute(srcAttr));
501 }
502
503 void HTMLImageElement::setSrc(const String& value)
504 {
505     setAttribute(srcAttr, value);
506 }
507
508 void HTMLImageElement::setWidth(int value)
509 {
510     setIntegralAttribute(widthAttr, value);
511 }
512
513 int HTMLImageElement::x() const
514 {
515     document().updateLayoutIgnorePendingStylesheets();
516     auto renderer = this->renderer();
517     if (!renderer)
518         return 0;
519
520     // FIXME: This doesn't work correctly with transforms.
521     return renderer->localToAbsolute().x();
522 }
523
524 int HTMLImageElement::y() const
525 {
526     document().updateLayoutIgnorePendingStylesheets();
527     auto renderer = this->renderer();
528     if (!renderer)
529         return 0;
530
531     // FIXME: This doesn't work correctly with transforms.
532     return renderer->localToAbsolute().y();
533 }
534
535 bool HTMLImageElement::complete() const
536 {
537     return m_imageLoader.imageComplete();
538 }
539
540 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
541 {
542     HTMLElement::addSubresourceAttributeURLs(urls);
543
544     addSubresourceURL(urls, src());
545     // FIXME: What about when the usemap attribute begins with "#"?
546     addSubresourceURL(urls, document().completeURL(fastGetAttribute(usemapAttr)));
547 }
548
549 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
550 {
551     m_imageLoader.elementDidMoveToNewDocument();
552     HTMLElement::didMoveToNewDocument(oldDocument);
553 }
554
555 bool HTMLImageElement::isServerMap() const
556 {
557     if (!fastHasAttribute(ismapAttr))
558         return false;
559
560     const AtomicString& usemap = fastGetAttribute(usemapAttr);
561
562     // If the usemap attribute starts with '#', it refers to a map element in the document.
563     if (usemap.string()[0] == '#')
564         return false;
565
566     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
567 }
568
569 #if ENABLE(SERVICE_CONTROLS)
570 void HTMLImageElement::updateImageControls()
571 {
572     // If this image element is inside a shadow tree then it is part of an image control.
573     if (isInShadowTree())
574         return;
575
576     Settings* settings = document().settings();
577     if (!settings || !settings->imageControlsEnabled())
578         return;
579
580     bool hasControls = hasImageControls();
581     if (!m_experimentalImageMenuEnabled && hasControls)
582         destroyImageControls();
583     else if (m_experimentalImageMenuEnabled && !hasControls)
584         createImageControls();
585 }
586
587 void HTMLImageElement::createImageControls()
588 {
589     ASSERT(m_experimentalImageMenuEnabled);
590     ASSERT(!hasImageControls());
591
592     RefPtr<ImageControlsRootElement> imageControls = ImageControlsRootElement::maybeCreate(document());
593     if (!imageControls)
594         return;
595
596     ensureUserAgentShadowRoot().appendChild(imageControls.releaseNonNull());
597
598     auto* renderObject = renderer();
599     if (!renderObject)
600         return;
601
602     downcast<RenderImage>(*renderObject).setHasShadowControls(true);
603 }
604
605 void HTMLImageElement::destroyImageControls()
606 {
607     ShadowRoot* shadowRoot = userAgentShadowRoot();
608     if (!shadowRoot)
609         return;
610
611     if (Node* node = shadowRoot->firstChild()) {
612         ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement());
613         shadowRoot->removeChild(*node);
614     }
615
616     auto* renderObject = renderer();
617     if (!renderObject)
618         return;
619
620     downcast<RenderImage>(*renderObject).setHasShadowControls(false);
621 }
622
623 bool HTMLImageElement::hasImageControls() const
624 {
625     if (ShadowRoot* shadowRoot = userAgentShadowRoot()) {
626         Node* node = shadowRoot->firstChild();
627         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement());
628         return node;
629     }
630
631     return false;
632 }
633
634 bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const
635 {
636     return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
637 }
638 #endif // ENABLE(SERVICE_CONTROLS)
639
640 #if PLATFORM(IOS)
641 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
642 bool HTMLImageElement::willRespondToMouseClickEvents()
643 {
644     auto renderer = this->renderer();
645     if (!renderer || renderer->style().touchCalloutEnabled())
646         return true;
647     return HTMLElement::willRespondToMouseClickEvents();
648 }
649 #endif
650
651 }