Editable images sometimes don't become focused when tapped
[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 "Chrome.h"
30 #include "ChromeClient.h"
31 #include "EditableImageReference.h"
32 #include "Editor.h"
33 #include "ElementIterator.h"
34 #include "EventNames.h"
35 #include "FrameView.h"
36 #include "HTMLAnchorElement.h"
37 #include "HTMLAttachmentElement.h"
38 #include "HTMLDocument.h"
39 #include "HTMLFormElement.h"
40 #include "HTMLParserIdioms.h"
41 #include "HTMLPictureElement.h"
42 #include "HTMLMapElement.h"
43 #include "HTMLSourceElement.h"
44 #include "HTMLSrcsetParser.h"
45 #include "Logging.h"
46 #include "MIMETypeRegistry.h"
47 #include "MediaList.h"
48 #include "MediaQueryEvaluator.h"
49 #include "NodeTraversal.h"
50 #include "PlatformMouseEvent.h"
51 #include "RenderImage.h"
52 #include "RenderView.h"
53 #include "RuntimeEnabledFeatures.h"
54 #include "Settings.h"
55 #include "ShadowRoot.h"
56 #include "SizesAttributeParser.h"
57 #include <wtf/IsoMallocInlines.h>
58 #include <wtf/text/StringBuilder.h>
59
60 #if ENABLE(SERVICE_CONTROLS)
61 #include "ImageControlsRootElement.h"
62 #endif
63
64 namespace WebCore {
65
66 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLImageElement);
67
68 using namespace HTMLNames;
69
70 typedef HashMap<const HTMLImageElement*, WeakPtr<HTMLPictureElement>> PictureOwnerMap;
71 static PictureOwnerMap* gPictureOwnerMap = nullptr;
72
73 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
74     : HTMLElement(tagName, document)
75     , m_imageLoader(*this)
76     , m_form(nullptr)
77     , m_formSetByParser(form)
78     , m_compositeOperator(CompositeSourceOver)
79     , m_imageDevicePixelRatio(1.0f)
80 #if ENABLE(SERVICE_CONTROLS)
81     , m_experimentalImageMenuEnabled(false)
82 #endif
83 {
84     ASSERT(hasTagName(imgTag));
85     setHasCustomStyleResolveCallbacks();
86 }
87
88 Ref<HTMLImageElement> HTMLImageElement::create(Document& document)
89 {
90     return adoptRef(*new HTMLImageElement(imgTag, document));
91 }
92
93 Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
94 {
95     return adoptRef(*new HTMLImageElement(tagName, document, form));
96 }
97
98 HTMLImageElement::~HTMLImageElement()
99 {
100     if (m_form)
101         m_form->removeImgElement(this);
102     setPictureElement(nullptr);
103 }
104
105 Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, Optional<unsigned> width, Optional<unsigned> height)
106 {
107     auto image = adoptRef(*new HTMLImageElement(imgTag, document));
108     if (width)
109         image->setWidth(width.value());
110     if (height)
111         image->setHeight(height.value());
112     return image;
113 }
114
115 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
116 {
117     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
118         return true;
119     return HTMLElement::isPresentationAttribute(name);
120 }
121
122 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
123 {
124     if (name == widthAttr)
125         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
126     else if (name == heightAttr)
127         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
128     else if (name == borderAttr)
129         applyBorderAttributeToStyle(value, style);
130     else if (name == vspaceAttr) {
131         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
132         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
133     } else if (name == hspaceAttr) {
134         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
135         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
136     } else if (name == alignAttr)
137         applyAlignmentAttributeToStyle(value, style);
138     else if (name == valignAttr)
139         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
140     else
141         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
142 }
143
144 const AtomicString& HTMLImageElement::imageSourceURL() const
145 {
146     return m_bestFitImageURL.isEmpty() ? attributeWithoutSynchronization(srcAttr) : m_bestFitImageURL;
147 }
148
149 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
150 {
151     m_bestFitImageURL = candidate.string.toAtomicString();
152     m_currentSrc = AtomicString(document().completeURL(imageSourceURL()).string());
153     if (candidate.density >= 0)
154         m_imageDevicePixelRatio = 1 / candidate.density;
155     if (is<RenderImage>(renderer()))
156         downcast<RenderImage>(*renderer()).setImageDevicePixelRatio(m_imageDevicePixelRatio);
157 }
158
159 ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement()
160 {
161     auto picture = makeRefPtr(pictureElement());
162     if (!picture)
163         return { };
164
165     picture->clearViewportDependentResults();
166     document().removeViewportDependentPicture(*picture);
167
168     picture->clearAppearanceDependentResults();
169     document().removeAppearanceDependentPicture(*picture);
170
171     for (RefPtr<Node> child = picture->firstChild(); child && child != this; child = child->nextSibling()) {
172         if (!is<HTMLSourceElement>(*child))
173             continue;
174         auto& source = downcast<HTMLSourceElement>(*child);
175
176         auto& srcset = source.attributeWithoutSynchronization(srcsetAttr);
177         if (srcset.isEmpty())
178             continue;
179
180         auto& typeAttribute = source.attributeWithoutSynchronization(typeAttr);
181         if (!typeAttribute.isNull()) {
182             String type = typeAttribute.string();
183             type.truncate(type.find(';'));
184             type = stripLeadingAndTrailingHTMLSpaces(type);
185             if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(type))
186                 continue;
187         }
188
189         auto documentElement = makeRefPtr(document().documentElement());
190         MediaQueryEvaluator evaluator { document().printing() ? "print" : "screen", document(), documentElement ? documentElement->computedStyle() : nullptr };
191         auto* queries = source.parsedMediaAttribute(document());
192         LOG(MediaQueries, "HTMLImageElement %p bestFitSourceFromPictureElement evaluating media queries", this);
193         auto evaluation = !queries || evaluator.evaluate(*queries, picture->viewportDependentResults(), picture->appearanceDependentResults());
194         if (picture->hasViewportDependentResults())
195             document().addViewportDependentPicture(*picture);
196         if (picture->hasAppearanceDependentResults())
197             document().addAppearanceDependentPicture(*picture);
198         if (!evaluation)
199             continue;
200
201         auto sourceSize = SizesAttributeParser(source.attributeWithoutSynchronization(sizesAttr).string(), document()).length();
202         auto candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom(), srcset, sourceSize);
203         if (!candidate.isEmpty())
204             return candidate;
205     }
206     return { };
207 }
208
209 void HTMLImageElement::selectImageSource()
210 {
211     // First look for the best fit source from our <picture> parent if we have one.
212     ImageCandidate candidate = bestFitSourceFromPictureElement();
213     if (candidate.isEmpty()) {
214         // If we don't have a <picture> or didn't find a source, then we use our own attributes.
215         auto sourceSize = SizesAttributeParser(attributeWithoutSynchronization(sizesAttr).string(), document()).length();
216         candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), attributeWithoutSynchronization(srcAttr), attributeWithoutSynchronization(srcsetAttr), sourceSize);
217     }
218     setBestFitURLAndDPRFromImageCandidate(candidate);
219     m_imageLoader.updateFromElementIgnoringPreviousError();
220 }
221
222 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
223 {
224     if (name == altAttr) {
225         if (is<RenderImage>(renderer()))
226             downcast<RenderImage>(*renderer()).updateAltText();
227     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
228         selectImageSource();
229     else if (name == usemapAttr) {
230         if (isConnected() && !m_parsedUsemap.isNull())
231             treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
232
233         m_parsedUsemap = parseHTMLHashNameReference(value);
234
235         if (isConnected() && !m_parsedUsemap.isNull())
236             treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
237     } else if (name == compositeAttr) {
238         // FIXME: images don't support blend modes in their compositing attribute.
239         BlendMode blendOp = BlendMode::Normal;
240         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
241             m_compositeOperator = CompositeSourceOver;
242 #if ENABLE(SERVICE_CONTROLS)
243     } else if (name == webkitimagemenuAttr) {
244         m_experimentalImageMenuEnabled = !value.isNull();
245         updateImageControls();
246 #endif
247     } else if (name == x_apple_editable_imageAttr)
248         updateEditableImage();
249     else {
250         if (name == nameAttr) {
251             bool willHaveName = !value.isNull();
252             if (m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
253                 HTMLDocument& document = downcast<HTMLDocument>(this->document());
254                 const AtomicString& id = getIdAttribute();
255                 if (!id.isEmpty() && id != getNameAttribute()) {
256                     if (willHaveName)
257                         document.addDocumentNamedItem(*id.impl(), *this);
258                     else
259                         document.removeDocumentNamedItem(*id.impl(), *this);
260                 }
261             }
262             m_hadNameBeforeAttributeChanged = willHaveName;
263         }
264         HTMLElement::parseAttribute(name, value);
265     }
266 }
267
268 const AtomicString& HTMLImageElement::altText() const
269 {
270     // lets figure out the alt text.. magic stuff
271     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
272     // also heavily discussed by Hixie on bugzilla
273     const AtomicString& alt = attributeWithoutSynchronization(altAttr);
274     if (!alt.isNull())
275         return alt;
276     // fall back to title attribute
277     return attributeWithoutSynchronization(titleAttr);
278 }
279
280 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
281 {
282     if (style.hasContent())
283         return RenderElement::createFor(*this, WTFMove(style));
284
285     return createRenderer<RenderImage>(*this, WTFMove(style), nullptr, m_imageDevicePixelRatio);
286 }
287
288 bool HTMLImageElement::canStartSelection() const
289 {
290     if (shadowRoot())
291         return HTMLElement::canStartSelection();
292
293     return false;
294 }
295
296 bool HTMLImageElement::supportsFocus() const
297 {
298     if (hasEditableImageAttribute())
299         return true;
300     return HTMLElement::supportsFocus();
301 }
302
303 bool HTMLImageElement::isFocusable() const
304 {
305     if (hasEditableImageAttribute())
306         return true;
307     return HTMLElement::isFocusable();
308 }
309
310 void HTMLImageElement::didAttachRenderers()
311 {
312     if (!is<RenderImage>(renderer()))
313         return;
314     if (m_imageLoader.hasPendingBeforeLoadEvent())
315         return;
316
317 #if ENABLE(SERVICE_CONTROLS)
318     updateImageControls();
319 #endif
320
321     auto& renderImage = downcast<RenderImage>(*renderer());
322     RenderImageResource& renderImageResource = renderImage.imageResource();
323     if (renderImageResource.cachedImage())
324         return;
325     renderImageResource.setCachedImage(m_imageLoader.image());
326
327     // If we have no image at all because we have no src attribute, set
328     // image height and width for the alt text instead.
329     if (!m_imageLoader.image() && !renderImageResource.cachedImage())
330         renderImage.setImageSizeForAltText();
331 }
332
333 Node::InsertedIntoAncestorResult HTMLImageElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
334 {
335     if (m_formSetByParser) {
336         m_form = m_formSetByParser;
337         m_formSetByParser = nullptr;
338         m_form->registerImgElement(this);
339     }
340
341     if (m_form && rootElement() != m_form->rootElement()) {
342         m_form->removeImgElement(this);
343         m_form = nullptr;
344     }
345
346     if (!m_form) {
347         m_form = HTMLFormElement::findClosestFormAncestor(*this);
348         if (m_form)
349             m_form->registerImgElement(this);
350     }
351
352     // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
353     // in callbacks back to this node.
354     Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
355
356     if (insertionType.connectedToDocument && hasEditableImageAttribute())
357         insertNotificationRequest = InsertedIntoAncestorResult::NeedsPostInsertionCallback;
358
359     if (insertionType.connectedToDocument && !m_parsedUsemap.isNull())
360         treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
361
362     if (is<HTMLPictureElement>(parentNode())) {
363         setPictureElement(&downcast<HTMLPictureElement>(*parentNode()));
364         selectImageSource();
365     }
366
367     // If we have been inserted from a renderer-less document,
368     // our loader may have not fetched the image, so do it now.
369     if (insertionType.connectedToDocument && !m_imageLoader.image())
370         m_imageLoader.updateFromElement();
371
372     return insertNotificationRequest;
373 }
374
375 void HTMLImageElement::didFinishInsertingNode()
376 {
377     if (hasEditableImageAttribute())
378         updateEditableImage();
379 }
380
381 void HTMLImageElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
382 {
383     if (m_form)
384         m_form->removeImgElement(this);
385
386     if (removalType.disconnectedFromDocument && !m_parsedUsemap.isNull())
387         oldParentOfRemovedTree.treeScope().removeImageElementByUsemap(*m_parsedUsemap.impl(), *this);
388
389     if (is<HTMLPictureElement>(parentNode()))
390         setPictureElement(nullptr);
391
392     if (removalType.disconnectedFromDocument)
393         updateEditableImage();
394
395     m_form = nullptr;
396     HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
397 }
398
399 bool HTMLImageElement::hasEditableImageAttribute() const
400 {
401     if (!document().settings().editableImagesEnabled())
402         return false;
403     return hasAttributeWithoutSynchronization(x_apple_editable_imageAttr);
404 }
405
406 GraphicsLayer::EmbeddedViewID HTMLImageElement::editableImageViewID() const
407 {
408     if (!m_editableImage)
409         return 0;
410     return m_editableImage->embeddedViewID();
411 }
412
413 void HTMLImageElement::updateEditableImage()
414 {
415     if (!document().settings().editableImagesEnabled())
416         return;
417
418     auto* page = document().page();
419     if (!page)
420         return;
421
422     bool hasEditableAttribute = hasEditableImageAttribute();
423     bool isCurrentlyEditable = !!m_editableImage;
424     bool shouldBeEditable = isConnected() && hasEditableAttribute;
425
426 #if ENABLE(ATTACHMENT_ELEMENT)
427     // Create the inner attachment for editable images, or non-editable
428     // images that were cloned from editable image sources.
429     if (!attachmentElement() && (shouldBeEditable || !m_pendingClonedAttachmentID.isEmpty())) {
430         auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document());
431         if (!m_pendingClonedAttachmentID.isEmpty())
432             attachment->setUniqueIdentifier(WTFMove(m_pendingClonedAttachmentID));
433         else
434             attachment->ensureUniqueIdentifier();
435         setAttachmentElement(WTFMove(attachment));
436     }
437 #endif
438
439     if (shouldBeEditable == isCurrentlyEditable)
440         return;
441
442     if (!hasEditableAttribute) {
443         m_editableImage = nullptr;
444         return;
445     }
446
447     if (!m_editableImage)
448         m_editableImage = EditableImageReference::create(document());
449
450 #if ENABLE(ATTACHMENT_ELEMENT)
451     m_editableImage->associateWithAttachment(attachmentElement()->uniqueIdentifier());
452 #endif
453 }
454
455 HTMLPictureElement* HTMLImageElement::pictureElement() const
456 {
457     if (!gPictureOwnerMap || !gPictureOwnerMap->contains(this))
458         return nullptr;
459     auto result = gPictureOwnerMap->get(this);
460     if (!result)
461         gPictureOwnerMap->remove(this);
462     return result.get();
463 }
464     
465 void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement)
466 {
467     if (!pictureElement) {
468         if (gPictureOwnerMap)
469             gPictureOwnerMap->remove(this);
470         return;
471     }
472     
473     if (!gPictureOwnerMap)
474         gPictureOwnerMap = new PictureOwnerMap();
475     gPictureOwnerMap->add(this, makeWeakPtr(*pictureElement));
476 }
477     
478 unsigned HTMLImageElement::width(bool ignorePendingStylesheets)
479 {
480     if (!renderer()) {
481         // check the attribute first for an explicit pixel value
482         auto optionalWidth = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr));
483         if (optionalWidth)
484             return optionalWidth.value();
485
486         // if the image is available, use its width
487         if (m_imageLoader.image())
488             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width().toUnsigned();
489     }
490
491     if (ignorePendingStylesheets)
492         document().updateLayoutIgnorePendingStylesheets();
493     else
494         document().updateLayout();
495
496     RenderBox* box = renderBox();
497     if (!box)
498         return 0;
499     LayoutRect contentRect = box->contentBoxRect();
500     return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box);
501 }
502
503 unsigned HTMLImageElement::height(bool ignorePendingStylesheets)
504 {
505     if (!renderer()) {
506         // check the attribute first for an explicit pixel value
507         auto optionalHeight = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr));
508         if (optionalHeight)
509             return optionalHeight.value();
510
511         // if the image is available, use its height
512         if (m_imageLoader.image())
513             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height().toUnsigned();
514     }
515
516     if (ignorePendingStylesheets)
517         document().updateLayoutIgnorePendingStylesheets();
518     else
519         document().updateLayout();
520
521     RenderBox* box = renderBox();
522     if (!box)
523         return 0;
524     LayoutRect contentRect = box->contentBoxRect();
525     return adjustForAbsoluteZoom(snappedIntRect(contentRect).height(), *box);
526 }
527
528 int HTMLImageElement::naturalWidth() const
529 {
530     if (!m_imageLoader.image())
531         return 0;
532
533     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
534 }
535
536 int HTMLImageElement::naturalHeight() const
537 {
538     if (!m_imageLoader.image())
539         return 0;
540
541     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
542 }
543
544 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
545 {
546     return attribute.name() == srcAttr
547         || attribute.name() == lowsrcAttr
548         || attribute.name() == longdescAttr
549         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
550         || HTMLElement::isURLAttribute(attribute);
551 }
552
553 bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const
554 {
555     return attribute.name() == srcsetAttr
556         || HTMLElement::attributeContainsURL(attribute);
557 }
558
559 String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
560 {
561     if (attribute.name() == srcsetAttr) {
562         Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value()));
563         StringBuilder result;
564         for (const auto& candidate : imageCandidates) {
565             if (&candidate != &imageCandidates[0])
566                 result.appendLiteral(", ");
567             result.append(URL(base, candidate.string.toString()).string());
568             if (candidate.density != UninitializedDescriptor) {
569                 result.append(' ');
570                 result.appendNumber(candidate.density);
571                 result.append('x');
572             }
573             if (candidate.resourceWidth != UninitializedDescriptor) {
574                 result.append(' ');
575                 result.appendNumber(candidate.resourceWidth);
576                 result.append('w');
577             }
578         }
579         return result.toString();
580     }
581     return HTMLElement::completeURLsInAttributeValue(base, attribute);
582 }
583
584 bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const
585 {
586     return m_parsedUsemap.impl() == &name;
587 }
588
589 HTMLMapElement* HTMLImageElement::associatedMapElement() const
590 {
591     return treeScope().getImageMap(m_parsedUsemap);
592 }
593
594 const AtomicString& HTMLImageElement::alt() const
595 {
596     return attributeWithoutSynchronization(altAttr);
597 }
598
599 bool HTMLImageElement::draggable() const
600 {
601     // Image elements are draggable by default.
602     return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "false");
603 }
604
605 void HTMLImageElement::setHeight(unsigned value)
606 {
607     setUnsignedIntegralAttribute(heightAttr, value);
608 }
609
610 URL HTMLImageElement::src() const
611 {
612     return document().completeURL(attributeWithoutSynchronization(srcAttr));
613 }
614
615 void HTMLImageElement::setSrc(const String& value)
616 {
617     setAttributeWithoutSynchronization(srcAttr, value);
618 }
619
620 void HTMLImageElement::setWidth(unsigned value)
621 {
622     setUnsignedIntegralAttribute(widthAttr, value);
623 }
624
625 int HTMLImageElement::x() const
626 {
627     document().updateLayoutIgnorePendingStylesheets();
628     auto renderer = this->renderer();
629     if (!renderer)
630         return 0;
631
632     // FIXME: This doesn't work correctly with transforms.
633     return renderer->localToAbsolute().x();
634 }
635
636 int HTMLImageElement::y() const
637 {
638     document().updateLayoutIgnorePendingStylesheets();
639     auto renderer = this->renderer();
640     if (!renderer)
641         return 0;
642
643     // FIXME: This doesn't work correctly with transforms.
644     return renderer->localToAbsolute().y();
645 }
646
647 bool HTMLImageElement::complete() const
648 {
649     return m_imageLoader.imageComplete();
650 }
651
652 DecodingMode HTMLImageElement::decodingMode() const
653 {
654     const AtomicString& decodingMode = attributeWithoutSynchronization(decodingAttr);
655     if (equalLettersIgnoringASCIICase(decodingMode, "sync"))
656         return DecodingMode::Synchronous;
657     if (equalLettersIgnoringASCIICase(decodingMode, "async"))
658         return DecodingMode::Asynchronous;
659     return DecodingMode::Auto;
660 }
661     
662 void HTMLImageElement::decode(Ref<DeferredPromise>&& promise)
663 {
664     return m_imageLoader.decode(WTFMove(promise));
665 }
666
667 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
668 {
669     HTMLElement::addSubresourceAttributeURLs(urls);
670
671     addSubresourceURL(urls, document().completeURL(imageSourceURL()));
672     // FIXME: What about when the usemap attribute begins with "#"?
673     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr)));
674 }
675
676 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
677 {
678     m_imageLoader.elementDidMoveToNewDocument();
679     HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
680 }
681
682 bool HTMLImageElement::isServerMap() const
683 {
684     if (!hasAttributeWithoutSynchronization(ismapAttr))
685         return false;
686
687     const AtomicString& usemap = attributeWithoutSynchronization(usemapAttr);
688
689     // If the usemap attribute starts with '#', it refers to a map element in the document.
690     if (usemap.string()[0] == '#')
691         return false;
692
693     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
694 }
695
696 void HTMLImageElement::setCrossOrigin(const AtomicString& value)
697 {
698     setAttributeWithoutSynchronization(crossoriginAttr, value);
699 }
700
701 String HTMLImageElement::crossOrigin() const
702 {
703     return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
704 }
705
706 #if ENABLE(ATTACHMENT_ELEMENT)
707
708 void HTMLImageElement::setAttachmentElement(Ref<HTMLAttachmentElement>&& attachment)
709 {
710     if (auto existingAttachment = attachmentElement())
711         existingAttachment->remove();
712
713     attachment->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
714     ensureUserAgentShadowRoot().appendChild(WTFMove(attachment));
715 }
716
717 RefPtr<HTMLAttachmentElement> HTMLImageElement::attachmentElement() const
718 {
719     if (auto shadowRoot = userAgentShadowRoot())
720         return childrenOfType<HTMLAttachmentElement>(*shadowRoot).first();
721
722     return nullptr;
723 }
724
725 const String& HTMLImageElement::attachmentIdentifier() const
726 {
727     if (!m_pendingClonedAttachmentID.isEmpty())
728         return m_pendingClonedAttachmentID;
729
730     if (auto attachment = attachmentElement())
731         return attachment->uniqueIdentifier();
732
733     return nullAtom();
734 }
735
736 #endif // ENABLE(ATTACHMENT_ELEMENT)
737
738 #if ENABLE(SERVICE_CONTROLS)
739 void HTMLImageElement::updateImageControls()
740 {
741     // If this image element is inside a shadow tree then it is part of an image control.
742     if (isInShadowTree())
743         return;
744
745     if (!document().settings().imageControlsEnabled())
746         return;
747
748     bool hasControls = hasImageControls();
749     if (!m_experimentalImageMenuEnabled && hasControls)
750         destroyImageControls();
751     else if (m_experimentalImageMenuEnabled && !hasControls)
752         tryCreateImageControls();
753 }
754
755 void HTMLImageElement::tryCreateImageControls()
756 {
757     ASSERT(m_experimentalImageMenuEnabled);
758     ASSERT(!hasImageControls());
759
760     auto imageControls = ImageControlsRootElement::tryCreate(document());
761     if (!imageControls)
762         return;
763
764     ensureUserAgentShadowRoot().appendChild(*imageControls);
765
766     auto* renderObject = renderer();
767     if (!renderObject)
768         return;
769
770     downcast<RenderImage>(*renderObject).setHasShadowControls(true);
771 }
772
773 void HTMLImageElement::destroyImageControls()
774 {
775     auto shadowRoot = userAgentShadowRoot();
776     if (!shadowRoot)
777         return;
778
779     if (RefPtr<Node> node = shadowRoot->firstChild()) {
780         ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement());
781         shadowRoot->removeChild(*node);
782     }
783
784     auto* renderObject = renderer();
785     if (!renderObject)
786         return;
787
788     downcast<RenderImage>(*renderObject).setHasShadowControls(false);
789 }
790
791 bool HTMLImageElement::hasImageControls() const
792 {
793     if (auto shadowRoot = userAgentShadowRoot()) {
794         RefPtr<Node> node = shadowRoot->firstChild();
795         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement());
796         return node;
797     }
798
799     return false;
800 }
801
802 bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const
803 {
804     return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
805 }
806 #endif // ENABLE(SERVICE_CONTROLS)
807
808 #if PLATFORM(IOS_FAMILY)
809 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
810 bool HTMLImageElement::willRespondToMouseClickEvents()
811 {
812     auto renderer = this->renderer();
813     if (!renderer || renderer->style().touchCalloutEnabled())
814         return true;
815     return HTMLElement::willRespondToMouseClickEvents();
816 }
817 #endif
818
819 #if USE(SYSTEM_PREVIEW)
820 bool HTMLImageElement::isSystemPreviewImage() const
821 {
822     if (!RuntimeEnabledFeatures::sharedFeatures().systemPreviewEnabled())
823         return false;
824
825     const auto* parent = parentElement();
826     if (is<HTMLAnchorElement>(parent))
827         return downcast<HTMLAnchorElement>(parent)->isSystemPreviewLink();
828     if (is<HTMLPictureElement>(parent))
829         return downcast<HTMLPictureElement>(parent)->isSystemPreviewImage();
830     return false;
831 }
832 #endif
833
834 void HTMLImageElement::copyNonAttributePropertiesFromElement(const Element& source)
835 {
836     auto& sourceImage = static_cast<const HTMLImageElement&>(source);
837 #if ENABLE(ATTACHMENT_ELEMENT)
838     m_pendingClonedAttachmentID = !sourceImage.m_pendingClonedAttachmentID.isEmpty() ? sourceImage.m_pendingClonedAttachmentID : sourceImage.attachmentIdentifier();
839 #endif
840     m_editableImage = sourceImage.m_editableImage;
841     Element::copyNonAttributePropertiesFromElement(source);
842 }
843
844 void HTMLImageElement::defaultEventHandler(Event& event)
845 {
846     if (hasEditableImageAttribute() && event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) {
847         focus();
848         event.setDefaultHandled();
849         return;
850     }
851     HTMLElement::defaultEventHandler(event);
852 }
853
854 }