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