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 | |
65 | namespace WebCore { |
66 | |
67 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLImageElement); |
68 | |
69 | using namespace HTMLNames; |
70 | |
71 | HTMLImageElement::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 | |
84 | Ref<HTMLImageElement> HTMLImageElement::create(Document& document) |
85 | { |
86 | return adoptRef(*new HTMLImageElement(imgTag, document)); |
87 | } |
88 | |
89 | Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
90 | { |
91 | return adoptRef(*new HTMLImageElement(tagName, document, form)); |
92 | } |
93 | |
94 | HTMLImageElement::~HTMLImageElement() |
95 | { |
96 | if (m_form) |
97 | m_form->removeImgElement(this); |
98 | setPictureElement(nullptr); |
99 | } |
100 | |
101 | Ref<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 | |
111 | bool 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 | |
118 | void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& 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 | |
140 | const AtomicString& HTMLImageElement::imageSourceURL() const |
141 | { |
142 | return m_bestFitImageURL.isEmpty() ? attributeWithoutSynchronization(srcAttr) : m_bestFitImageURL; |
143 | } |
144 | |
145 | void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate) |
146 | { |
147 | m_bestFitImageURL = candidate.string.toAtomicString(); |
148 | m_currentSrc = AtomicString(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 | |
155 | ImageCandidate 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 | |
205 | void 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 | |
218 | void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& 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 AtomicString& 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 | |
264 | const AtomicString& 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 AtomicString& alt = attributeWithoutSynchronization(altAttr); |
270 | if (!alt.isNull()) |
271 | return alt; |
272 | // fall back to title attribute |
273 | return attributeWithoutSynchronization(titleAttr); |
274 | } |
275 | |
276 | RenderPtr<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 | |
284 | bool HTMLImageElement::canStartSelection() const |
285 | { |
286 | if (shadowRoot()) |
287 | return HTMLElement::canStartSelection(); |
288 | |
289 | return false; |
290 | } |
291 | |
292 | bool HTMLImageElement::supportsFocus() const |
293 | { |
294 | if (hasEditableImageAttribute()) |
295 | return true; |
296 | return HTMLElement::supportsFocus(); |
297 | } |
298 | |
299 | bool HTMLImageElement::isFocusable() const |
300 | { |
301 | if (hasEditableImageAttribute()) |
302 | return true; |
303 | return HTMLElement::isFocusable(); |
304 | } |
305 | |
306 | void 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 | |
329 | Node::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 | |
371 | void HTMLImageElement::didFinishInsertingNode() |
372 | { |
373 | if (hasEditableImageAttribute()) |
374 | updateEditableImage(); |
375 | } |
376 | |
377 | void 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 | |
395 | bool HTMLImageElement::hasEditableImageAttribute() const |
396 | { |
397 | if (!document().settings().editableImagesEnabled()) |
398 | return false; |
399 | return hasAttributeWithoutSynchronization(x_apple_editable_imageAttr); |
400 | } |
401 | |
402 | GraphicsLayer::EmbeddedViewID HTMLImageElement::editableImageViewID() const |
403 | { |
404 | if (!m_editableImage) |
405 | return 0; |
406 | return m_editableImage->embeddedViewID(); |
407 | } |
408 | |
409 | void 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 | |
451 | HTMLPictureElement* HTMLImageElement::pictureElement() const |
452 | { |
453 | return m_pictureElement.get(); |
454 | } |
455 | |
456 | void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement) |
457 | { |
458 | m_pictureElement = makeWeakPtr(pictureElement); |
459 | } |
460 | |
461 | unsigned 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 | |
486 | unsigned 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 | |
511 | int 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 | |
519 | int 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 | |
527 | bool 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 | |
536 | bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const |
537 | { |
538 | return attribute.name() == srcsetAttr |
539 | || HTMLElement::attributeContainsURL(attribute); |
540 | } |
541 | |
542 | String 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 | |
567 | bool HTMLImageElement::matchesUsemap(const AtomicStringImpl& name) const |
568 | { |
569 | return m_parsedUsemap.impl() == &name; |
570 | } |
571 | |
572 | HTMLMapElement* HTMLImageElement::associatedMapElement() const |
573 | { |
574 | return treeScope().getImageMap(m_parsedUsemap); |
575 | } |
576 | |
577 | const AtomicString& HTMLImageElement::alt() const |
578 | { |
579 | return attributeWithoutSynchronization(altAttr); |
580 | } |
581 | |
582 | bool HTMLImageElement::draggable() const |
583 | { |
584 | // Image elements are draggable by default. |
585 | return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(draggableAttr), "false" ); |
586 | } |
587 | |
588 | void HTMLImageElement::setHeight(unsigned value) |
589 | { |
590 | setUnsignedIntegralAttribute(heightAttr, value); |
591 | } |
592 | |
593 | URL HTMLImageElement::src() const |
594 | { |
595 | return document().completeURL(attributeWithoutSynchronization(srcAttr)); |
596 | } |
597 | |
598 | void HTMLImageElement::setSrc(const String& value) |
599 | { |
600 | setAttributeWithoutSynchronization(srcAttr, value); |
601 | } |
602 | |
603 | void HTMLImageElement::setWidth(unsigned value) |
604 | { |
605 | setUnsignedIntegralAttribute(widthAttr, value); |
606 | } |
607 | |
608 | int 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 | |
619 | int 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 | |
630 | bool HTMLImageElement::complete() const |
631 | { |
632 | return m_imageLoader.imageComplete(); |
633 | } |
634 | |
635 | DecodingMode HTMLImageElement::decodingMode() const |
636 | { |
637 | const AtomicString& 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 | |
645 | void HTMLImageElement::decode(Ref<DeferredPromise>&& promise) |
646 | { |
647 | return m_imageLoader.decode(WTFMove(promise)); |
648 | } |
649 | |
650 | void 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 | |
659 | void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
660 | { |
661 | m_imageLoader.elementDidMoveToNewDocument(); |
662 | HTMLElement::didMoveToNewDocument(oldDocument, newDocument); |
663 | } |
664 | |
665 | bool HTMLImageElement::isServerMap() const |
666 | { |
667 | if (!hasAttributeWithoutSynchronization(ismapAttr)) |
668 | return false; |
669 | |
670 | const AtomicString& 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 | |
679 | void HTMLImageElement::setCrossOrigin(const AtomicString& value) |
680 | { |
681 | setAttributeWithoutSynchronization(crossoriginAttr, value); |
682 | } |
683 | |
684 | String HTMLImageElement::crossOrigin() const |
685 | { |
686 | return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr)); |
687 | } |
688 | |
689 | #if ENABLE(ATTACHMENT_ELEMENT) |
690 | |
691 | void 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 | |
700 | RefPtr<HTMLAttachmentElement> HTMLImageElement::attachmentElement() const |
701 | { |
702 | if (auto shadowRoot = userAgentShadowRoot()) |
703 | return childrenOfType<HTMLAttachmentElement>(*shadowRoot).first(); |
704 | |
705 | return nullptr; |
706 | } |
707 | |
708 | const 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) |
722 | void 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 | |
738 | void 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 | |
756 | void 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 | |
774 | bool 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 | |
785 | bool 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. |
793 | bool 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) |
803 | bool 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 | |
817 | void 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 | |
827 | void 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 | |