| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2004-2022 Apple Inc. All rights reserved. |
| * Copyright (C) 2010-2015 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "HTMLImageElement.h" |
| |
| #include "CSSPropertyNames.h" |
| #include "CSSSerializationContext.h" |
| #include "CSSValueKeywords.h" |
| #include "CachedImage.h" |
| #include "Chrome.h" |
| #include "ChromeClient.h" |
| #include "CommonAtomStrings.h" |
| #include "ContainerNodeInlines.h" |
| #include "Editor.h" |
| #include "ElementChildIteratorInlines.h" |
| #include "ElementRareData.h" |
| #include "EventLoop.h" |
| #include "EventNames.h" |
| #include "HTMLAnchorElement.h" |
| #include "HTMLAttachmentElement.h" |
| #include "HTMLDocument.h" |
| #include "HTMLFormElement.h" |
| #include "HTMLImageLoader.h" |
| #include "HTMLMapElement.h" |
| #include "HTMLParserIdioms.h" |
| #include "HTMLPictureElement.h" |
| #include "HTMLSourceElement.h" |
| #include "HTMLSrcsetParser.h" |
| #include "JSRequestPriority.h" |
| #include "LazyLoadImageObserver.h" |
| #include "LocalFrameView.h" |
| #include "Logging.h" |
| #include "MIMETypeRegistry.h" |
| #include "MediaQueryEvaluator.h" |
| #include "MouseEvent.h" |
| #include "NodeInlines.h" |
| #include "NodeName.h" |
| #include "NodeTraversal.h" |
| #include "PlatformMouseEvent.h" |
| #include "RenderBoxInlines.h" |
| #include "RenderElementInlines.h" |
| #include "RenderImage.h" |
| #include "RenderView.h" |
| #include "RequestPriority.h" |
| #include "ScriptController.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "SizesAttributeParser.h" |
| #include <wtf/TZoneMallocInlines.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| #include "ImageControlsMac.h" |
| #endif |
| |
| #if ENABLE(SPATIAL_IMAGE_CONTROLS) |
| #include "SpatialImageControls.h" |
| #endif |
| |
| namespace WebCore { |
| |
| WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(HTMLImageElement); |
| |
| using namespace HTMLNames; |
| |
| HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
| : HTMLElement(tagName, document, { TypeFlag::HasCustomStyleResolveCallbacks, TypeFlag::HasDidMoveToNewDocument }) |
| , FormAssociatedElement(form) |
| , ActiveDOMObject(document) |
| , m_imageLoader(makeUniqueWithoutRefCountedCheck<HTMLImageLoader>(*this)) |
| , m_imageDevicePixelRatio(1.0f) |
| { |
| ASSERT(hasTagName(imgTag)); |
| } |
| |
| Ref<HTMLImageElement> HTMLImageElement::create(Document& document) |
| { |
| auto image = adoptRef(*new HTMLImageElement(imgTag, document)); |
| image->suspendIfNeeded(); |
| return image; |
| } |
| |
| Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
| { |
| auto image = adoptRef(*new HTMLImageElement(tagName, document, form)); |
| image->suspendIfNeeded(); |
| return image; |
| } |
| |
| HTMLImageElement::~HTMLImageElement() |
| { |
| disconnectFromIntersectionObservers(); |
| document().removeDynamicMediaQueryDependentImage(*this); |
| setForm(nullptr); |
| #if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL) |
| if (RefPtr page = document().page()) |
| page->removeIndividuallyPlayingAnimationElement(*this); |
| #endif |
| } |
| |
| void HTMLImageElement::resetFormOwner() |
| { |
| setForm(HTMLFormElement::findClosestFormAncestor(*this)); |
| } |
| |
| void HTMLImageElement::setFormInternal(RefPtr<HTMLFormElement>&& newForm) |
| { |
| if (auto* form = FormAssociatedElement::form()) |
| form->unregisterImgElement(*this); |
| FormAssociatedElement::setFormInternal(newForm.copyRef()); |
| if (newForm) |
| newForm->registerImgElement(*this); |
| } |
| |
| void HTMLImageElement::formOwnerRemovedFromTree(const Node& formRoot) |
| { |
| auto& rootNode = traverseToRootNode(); // Do not rely on rootNode() because our IsInTreeScope can be outdated. |
| if (&rootNode != &formRoot) |
| setForm(nullptr); |
| } |
| |
| Ref<HTMLImageElement> HTMLImageElement::createForLegacyFactoryFunction(Document& document, std::optional<unsigned> width, std::optional<unsigned> height) |
| { |
| auto image = adoptRef(*new HTMLImageElement(imgTag, document)); |
| if (width) |
| image->setUnsignedIntegralAttribute(widthAttr, width.value()); |
| if (height) |
| image->setUnsignedIntegralAttribute(heightAttr, height.value()); |
| image->suspendIfNeeded(); |
| return image; |
| } |
| |
| bool HTMLImageElement::hasPresentationalHintsForAttribute(const QualifiedName& name) const |
| { |
| switch (name.nodeName()) { |
| case AttributeNames::widthAttr: |
| case AttributeNames::heightAttr: |
| case AttributeNames::borderAttr: |
| case AttributeNames::vspaceAttr: |
| case AttributeNames::hspaceAttr: |
| case AttributeNames::valignAttr: |
| return true; |
| default: |
| break; |
| } |
| return HTMLElement::hasPresentationalHintsForAttribute(name); |
| } |
| |
| void HTMLImageElement::collectPresentationalHintsForAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style) |
| { |
| switch (name.nodeName()) { |
| case AttributeNames::widthAttr: |
| addHTMLMultiLengthToStyle(style, CSSPropertyWidth, value); |
| applyAspectRatioFromWidthAndHeightAttributesToStyle(value, attributeWithoutSynchronization(heightAttr), style); |
| break; |
| case AttributeNames::heightAttr: |
| addHTMLMultiLengthToStyle(style, CSSPropertyHeight, value); |
| applyAspectRatioFromWidthAndHeightAttributesToStyle(attributeWithoutSynchronization(widthAttr), value, style); |
| break; |
| case AttributeNames::borderAttr: |
| applyBorderAttributeToStyle(value, style); |
| break; |
| case AttributeNames::vspaceAttr: |
| addHTMLLengthToStyle(style, CSSPropertyMarginTop, value); |
| addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value); |
| break; |
| case AttributeNames::hspaceAttr: |
| addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value); |
| addHTMLLengthToStyle(style, CSSPropertyMarginRight, value); |
| break; |
| case AttributeNames::alignAttr: |
| applyAlignmentAttributeToStyle(value, style); |
| break; |
| case AttributeNames::valignAttr: |
| addPropertyToPresentationalHintStyle(style, CSSPropertyVerticalAlign, value); |
| break; |
| default: |
| HTMLElement::collectPresentationalHintsForAttribute(name, value, style); |
| break; |
| } |
| } |
| |
| void HTMLImageElement::collectExtraStyleForPresentationalHints(MutableStyleProperties& style) |
| { |
| if (!sourceElement()) |
| return; |
| auto& widthAttrFromSource = sourceElement()->attributeWithoutSynchronization(widthAttr); |
| auto& heightAttrFromSource = sourceElement()->attributeWithoutSynchronization(heightAttr); |
| // If both width and height attributes of <source> is undefined, the style's value should not |
| // be overwritten. Otherwise, <souce> will overwrite it. I.e., if <source> only has one attribute |
| // defined, the other one and aspect-ratio shouldn't be set to auto. |
| if (widthAttrFromSource.isNull() && heightAttrFromSource.isNull()) |
| return; |
| |
| if (!widthAttrFromSource.isNull()) |
| addHTMLLengthToStyle(style, CSSPropertyWidth, widthAttrFromSource); |
| else |
| addPropertyToPresentationalHintStyle(style, CSSPropertyWidth, CSSValueAuto); |
| |
| if (!heightAttrFromSource.isNull()) |
| addHTMLLengthToStyle(style, CSSPropertyHeight, heightAttrFromSource); |
| else |
| addPropertyToPresentationalHintStyle(style, CSSPropertyHeight, CSSValueAuto); |
| |
| if (!widthAttrFromSource.isNull() && !heightAttrFromSource.isNull()) |
| applyAspectRatioFromWidthAndHeightAttributesToStyle(widthAttrFromSource, heightAttrFromSource, style); |
| else |
| addPropertyToPresentationalHintStyle(style, CSSPropertyAspectRatio, CSSValueAuto); |
| } |
| |
| const AtomString& HTMLImageElement::imageSourceURL() const |
| { |
| return m_bestFitImageURL.isEmpty() ? attributeWithoutSynchronization(srcAttr) : m_bestFitImageURL; |
| } |
| |
| const AtomString& HTMLImageElement::currentSrc() |
| { |
| if (m_currentSrc.isNull()) { |
| if (!m_currentURL.isEmpty()) |
| m_currentSrc = AtomString(m_currentURL.string()); |
| } |
| return m_currentSrc; |
| } |
| |
| void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate) |
| { |
| m_bestFitImageURL = candidate.string.toAtomString(); |
| m_currentURL = document().completeURL(imageSourceURL()); |
| m_currentSrc = { }; |
| if (candidate.density >= 0) |
| m_imageDevicePixelRatio = 1 / candidate.density; |
| if (CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer())) |
| renderImage->setImageDevicePixelRatio(m_imageDevicePixelRatio); |
| } |
| |
| static String extractMIMETypeFromTypeAttributeForLookup(const String& typeAttribute) |
| { |
| auto semicolonIndex = typeAttribute.find(';'); |
| if (semicolonIndex == notFound) |
| return typeAttribute.trim(isASCIIWhitespace); |
| return StringView(typeAttribute).left(semicolonIndex).trim(isASCIIWhitespace<char16_t>).toStringWithoutCopying(); |
| } |
| |
| ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement() |
| { |
| RefPtr picture = pictureElement(); |
| if (!picture) |
| return { }; |
| |
| ImageCandidate candidate; |
| |
| for (RefPtr<Node> child = picture->firstChild(); child && child != this; child = child->nextSibling()) { |
| auto* source = dynamicDowncast<HTMLSourceElement>(*child); |
| if (!source) |
| continue; |
| |
| auto& srcset = source->attributeWithoutSynchronization(srcsetAttr); |
| if (srcset.isEmpty()) |
| continue; |
| |
| auto& typeAttribute = source->attributeWithoutSynchronization(typeAttr); |
| if (!typeAttribute.isNull()) { |
| auto type = extractMIMETypeFromTypeAttributeForLookup(typeAttribute); |
| if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(type)) |
| continue; |
| } |
| |
| RefPtr documentElement = document().documentElement(); |
| MQ::MediaQueryEvaluator evaluator { document().printing() ? printAtom() : screenAtom(), document(), documentElement ? documentElement->computedStyle() : nullptr }; |
| auto& queries = source->parsedMediaAttribute(document()); |
| LOG(MediaQueries, "HTMLImageElement %p bestFitSourceFromPictureElement evaluating media queries", this); |
| |
| auto result = evaluator.evaluate(queries); |
| |
| if (!evaluator.collectDynamicDependencies(queries).isEmpty()) |
| m_dynamicMediaQueryResults.append({ queries, result }); |
| |
| if (!result) |
| continue; |
| |
| SizesAttributeParser sizesParser(source->attributeWithoutSynchronization(sizesAttr).string(), document()); |
| |
| m_dynamicMediaQueryResults.appendVector(sizesParser.dynamicMediaQueryResults()); |
| |
| auto sourceSize = sizesParser.length(); |
| |
| candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom(), srcset, sourceSize, [&](auto& candidate) { |
| return m_imageLoader->shouldIgnoreCandidateWhenLoadingFromArchive(candidate); |
| }); |
| |
| if (!candidate.isEmpty()) { |
| setSourceElement(source); |
| break; |
| } |
| } |
| |
| return candidate; |
| } |
| |
| void HTMLImageElement::setIsUserAgentShadowRootResource() |
| { |
| m_imageLoader->setElementIsUserAgentShadowRootResource(true); |
| } |
| |
| void HTMLImageElement::evaluateDynamicMediaQueryDependencies() |
| { |
| RefPtr documentElement = document().documentElement(); |
| MQ::MediaQueryEvaluator evaluator { document().printing() ? printAtom() : screenAtom(), document(), documentElement ? documentElement->computedStyle() : nullptr }; |
| |
| auto hasChanges = [&] { |
| for (auto& results : m_dynamicMediaQueryResults) { |
| if (results.result != evaluator.evaluate(results.mediaQueryList)) |
| return true; |
| } |
| return false; |
| }(); |
| |
| if (!hasChanges) |
| return; |
| |
| selectImageSource(RelevantMutation::No); |
| } |
| |
| void HTMLImageElement::selectImageSource(RelevantMutation relevantMutation) |
| { |
| m_dynamicMediaQueryResults = { }; |
| document().removeDynamicMediaQueryDependentImage(*this); |
| |
| // First look for the best fit source from our <picture> parent if we have one. |
| ImageCandidate candidate = bestFitSourceFromPictureElement(); |
| if (candidate.isEmpty()) { |
| setSourceElement(nullptr); |
| auto srcAttribute = attributeWithoutSynchronization(srcAttr); |
| auto srcsetAttribute = attributeWithoutSynchronization(srcsetAttr); |
| // This is extremely common case. We should not invoke SizesAttributeParser at all. |
| if (srcsetAttribute.isNull()) { |
| if (srcAttribute.isNull()) |
| candidate = { }; |
| else |
| candidate = ImageCandidate(StringViewWithUnderlyingString(srcAttribute, srcAttribute), DescriptorParsingResult(), ImageCandidate::SrcOrigin); |
| } else { |
| // If we don't have a <picture> or didn't find a source, then we use our own attributes. |
| SizesAttributeParser sizesParser(attributeWithoutSynchronization(sizesAttr).string(), document()); |
| m_dynamicMediaQueryResults.appendVector(sizesParser.dynamicMediaQueryResults()); |
| auto sourceSize = sizesParser.length(); |
| candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), srcAttribute, srcsetAttribute, sourceSize, [&](auto& candidate) { |
| return m_imageLoader->shouldIgnoreCandidateWhenLoadingFromArchive(candidate); |
| }); |
| } |
| } |
| setBestFitURLAndDPRFromImageCandidate(candidate); |
| m_imageLoader->updateFromElementIgnoringPreviousError(relevantMutation); |
| |
| if (!m_dynamicMediaQueryResults.isEmpty()) |
| document().addDynamicMediaQueryDependentImage(*this); |
| } |
| |
| bool HTMLImageElement::hasLazyLoadableAttributeValue(StringView attributeValue) |
| { |
| return equalLettersIgnoringASCIICase(attributeValue, "lazy"_s); |
| } |
| |
| void HTMLImageElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason) |
| { |
| HTMLElement::attributeChanged(name, oldValue, newValue, attributeModificationReason); |
| |
| switch (name.nodeName()) { |
| case AttributeNames::altAttr: |
| if (auto* renderImage = dynamicDowncast<RenderImage>(renderer())) |
| renderImage->updateAltText(); |
| break; |
| case AttributeNames::srcAttr: |
| case AttributeNames::srcsetAttr: |
| case AttributeNames::sizesAttr: |
| if (oldValue != newValue) |
| selectImageSource(RelevantMutation::Yes); |
| else |
| m_imageLoader->updateFromElementIgnoringPreviousErrorToSameValue(); |
| break; |
| case AttributeNames::usemapAttr: |
| if (isInTreeScope() && !m_parsedUsemap.isNull()) |
| treeScope().removeImageElementByUsemap(m_parsedUsemap, *this); |
| m_parsedUsemap = parseHTMLHashNameReference(newValue); |
| if (isInTreeScope() && !m_parsedUsemap.isNull()) |
| treeScope().addImageElementByUsemap(m_parsedUsemap, *this); |
| break; |
| case AttributeNames::loadingAttr: |
| // No action needed for eager to lazy transition. |
| if (!hasLazyLoadableAttributeValue(newValue)) |
| loadDeferredImage(); |
| break; |
| case AttributeNames::referrerpolicyAttr: { |
| auto oldReferrerPolicy = parseReferrerPolicy(oldValue, ReferrerPolicySource::ReferrerPolicyAttribute).value_or(ReferrerPolicy::EmptyString); |
| auto newReferrerPolicy = parseReferrerPolicy(newValue, ReferrerPolicySource::ReferrerPolicyAttribute).value_or(ReferrerPolicy::EmptyString); |
| if (oldReferrerPolicy != newReferrerPolicy) |
| m_imageLoader->updateFromElementIgnoringPreviousError(RelevantMutation::Yes); |
| break; |
| } |
| case AttributeNames::crossoriginAttr: |
| if (parseCORSSettingsAttribute(oldValue) != parseCORSSettingsAttribute(newValue)) |
| m_imageLoader->updateFromElementIgnoringPreviousError(RelevantMutation::Yes); |
| break; |
| case AttributeNames::nameAttr: { |
| bool willHaveName = !newValue.isEmpty(); |
| if (auto* document = dynamicDowncast<HTMLDocument>(this->document()); m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && document) { |
| const AtomString& id = getIdAttribute(); |
| if (!id.isEmpty() && id != getNameAttribute()) { |
| if (willHaveName) |
| document->addDocumentNamedItem(id, *this); |
| else |
| document->removeDocumentNamedItem(id, *this); |
| } |
| } |
| m_hadNameBeforeAttributeChanged = willHaveName; |
| break; |
| } |
| #if ENABLE(SPATIAL_IMAGE_CONTROLS) |
| case AttributeNames::controlsAttr: |
| SpatialImageControls::updateSpatialImageControls(*this); |
| break; |
| #endif |
| default: |
| break; |
| } |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| if (isImageMenuEnabled()) |
| ImageControlsMac::updateImageControls(*this); |
| #endif |
| } |
| |
| void HTMLImageElement::loadDeferredImage() |
| { |
| m_imageLoader->loadDeferredImage(); |
| } |
| |
| const AtomString& HTMLImageElement::altText() const |
| { |
| // lets figure out the alt text.. magic stuff |
| // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
| // also heavily discussed by Hixie on bugzilla |
| const AtomString& alt = attributeWithoutSynchronization(altAttr); |
| if (!alt.isNull()) |
| return alt; |
| // fall back to title attribute |
| return attributeWithoutSynchronization(titleAttr); |
| } |
| |
| RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| if (style.hasContent()) |
| return RenderElement::createFor(*this, WTFMove(style)); |
| |
| return createRenderer<RenderImage>(RenderObject::Type::Image, *this, WTFMove(style), nullptr, m_imageDevicePixelRatio); |
| } |
| |
| bool HTMLImageElement::isReplaced(const RenderStyle& style) const |
| { |
| return !style.hasContent(); |
| } |
| |
| bool HTMLImageElement::canStartSelection() const |
| { |
| if (shadowRoot()) |
| return HTMLElement::canStartSelection(); |
| |
| return false; |
| } |
| |
| bool HTMLImageElement::isInteractiveContent() const |
| { |
| return hasAttributeWithoutSynchronization(usemapAttr); |
| } |
| |
| void HTMLImageElement::didAttachRenderers() |
| { |
| CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer()); |
| if (!renderImage) |
| return; |
| if (m_imageLoader->hasPendingBeforeLoadEvent()) |
| return; |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| ImageControlsMac::updateImageControls(*this); |
| #endif |
| |
| RenderImageResource& renderImageResource = renderImage->imageResource(); |
| if (renderImageResource.cachedImage()) |
| return; |
| renderImageResource.setCachedImage(m_imageLoader->protectedImage()); |
| |
| // If we have no image at all because we have no src attribute, set |
| // image height and width for the alt text instead. |
| if (!m_imageLoader->image() && !renderImageResource.cachedImage()) |
| renderImage->setImageSizeForAltText(); |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLImageElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| FormAssociatedElement::elementInsertedIntoAncestor(*this, insertionType); |
| if (!form()) |
| resetFormOwner(); |
| |
| // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result |
| // in callbacks back to this node. |
| Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| |
| if (insertionType.treeScopeChanged && !m_parsedUsemap.isNull()) |
| treeScope().addImageElementByUsemap(m_parsedUsemap, *this); |
| |
| if (auto* parentPicture = dynamicDowncast<HTMLPictureElement>(parentOfInsertedTree); parentPicture && &parentOfInsertedTree == parentElement()) { |
| // FIXME: When the hack in HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface to eagerly call setPictureElement is removed, we can just assert !pictureElement(). |
| ASSERT(!pictureElement() || pictureElement() == &parentOfInsertedTree); |
| setPictureElement(parentPicture); |
| selectImageSource(RelevantMutation::Yes); |
| return insertNotificationRequest; |
| } |
| |
| // If we have been inserted from a renderer-less document, |
| // our loader may have not fetched the image, so do it now. |
| if (insertionType.connectedToDocument && !m_imageLoader->image()) |
| m_imageLoader->updateFromElement(); |
| |
| return insertNotificationRequest; |
| } |
| |
| void HTMLImageElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| if (removalType.treeScopeChanged && !m_parsedUsemap.isNull()) |
| oldParentOfRemovedTree.treeScope().removeImageElementByUsemap(m_parsedUsemap, *this); |
| |
| if (is<HTMLPictureElement>(oldParentOfRemovedTree) && !parentElement()) { |
| ASSERT(pictureElement() == &oldParentOfRemovedTree); |
| setPictureElement(nullptr); |
| selectImageSource(RelevantMutation::Yes); |
| } |
| |
| HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| FormAssociatedElement::elementRemovedFromAncestor(*this, removalType); |
| } |
| |
| HTMLPictureElement* HTMLImageElement::pictureElement() const |
| { |
| return m_pictureElement.get(); |
| } |
| |
| void HTMLImageElement::setPictureElement(HTMLPictureElement* pictureElement) |
| { |
| m_pictureElement = pictureElement; |
| } |
| |
| unsigned HTMLImageElement::width() |
| { |
| if (inRenderedDocument()) |
| protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); |
| |
| if (!renderer()) { |
| // check the attribute first for an explicit pixel value |
| auto optionalWidth = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(widthAttr)); |
| if (optionalWidth) |
| return optionalWidth.value(); |
| |
| // if the image is available, use its width |
| if (m_imageLoader->image()) |
| return m_imageLoader->image()->imageSizeForRenderer(renderer(), 1.0f).width().toUnsigned(); |
| } |
| |
| RenderBox* box = renderBox(); |
| if (!box) |
| return 0; |
| LayoutRect contentRect = box->contentBoxRect(); |
| return adjustLayoutUnitForAbsoluteZoom(contentRect.width(), *box).round(); |
| } |
| |
| unsigned HTMLImageElement::height() |
| { |
| if (inRenderedDocument()) |
| protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); |
| |
| if (!renderer()) { |
| // check the attribute first for an explicit pixel value |
| auto optionalHeight = parseHTMLNonNegativeInteger(attributeWithoutSynchronization(heightAttr)); |
| if (optionalHeight) |
| return optionalHeight.value(); |
| |
| // if the image is available, use its height |
| if (m_imageLoader->image()) |
| return m_imageLoader->image()->imageSizeForRenderer(renderer(), 1.0f).height().toUnsigned(); |
| } |
| |
| RenderBox* box = renderBox(); |
| if (!box) |
| return 0; |
| LayoutRect contentRect = box->contentBoxRect(); |
| return adjustLayoutUnitForAbsoluteZoom(contentRect.height(), *box).round(); |
| } |
| |
| float HTMLImageElement::effectiveImageDevicePixelRatio() const |
| { |
| if (!m_imageLoader->image()) |
| return 1.0f; |
| |
| auto* image = m_imageLoader->image()->image(); |
| |
| if (image && image->drawsSVGImage()) |
| return 1.0f; |
| |
| return m_imageDevicePixelRatio; |
| } |
| |
| unsigned HTMLImageElement::naturalWidth() const |
| { |
| if (!m_imageLoader->image()) |
| return 0; |
| |
| return m_imageLoader->image()->unclampedImageSizeForRenderer(renderer(), effectiveImageDevicePixelRatio()).width().toUnsigned(); |
| } |
| |
| unsigned HTMLImageElement::naturalHeight() const |
| { |
| if (!m_imageLoader->image()) |
| return 0; |
| |
| return m_imageLoader->image()->unclampedImageSizeForRenderer(renderer(), effectiveImageDevicePixelRatio()).height().toUnsigned(); |
| } |
| |
| bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const |
| { |
| return attribute.name() == srcAttr |
| || attribute.name() == lowsrcAttr |
| || attribute.name() == longdescAttr |
| || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') |
| || HTMLElement::isURLAttribute(attribute); |
| } |
| |
| bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const |
| { |
| return attribute.name() == srcsetAttr |
| || HTMLElement::attributeContainsURL(attribute); |
| } |
| |
| String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute, ResolveURLs resolveURLs) const |
| { |
| if (attribute.name() == srcsetAttr) { |
| if (resolveURLs == ResolveURLs::No) |
| return attribute.value(); |
| |
| Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value())); |
| |
| if (resolveURLs == ResolveURLs::NoExcludingURLsForPrivacy) { |
| bool needsToResolveURLs = false; |
| for (const auto& candidate : imageCandidates) { |
| auto urlString = candidate.string.toString(); |
| auto completeURL = base.isNull() ? document().completeURL(urlString) : URL(base, urlString); |
| if (document().shouldMaskURLForBindings(completeURL)) { |
| needsToResolveURLs = true; |
| break; |
| } |
| } |
| |
| if (!needsToResolveURLs) |
| return attribute.value(); |
| } |
| |
| StringBuilder result; |
| for (const auto& candidate : imageCandidates) { |
| if (&candidate != &imageCandidates[0]) |
| result.append(", "_s); |
| result.append(resolveURLStringIfNeeded(candidate.string.toString(), resolveURLs, base)); |
| if (candidate.density != UninitializedDescriptor) |
| result.append(' ', candidate.density, 'x'); |
| if (candidate.resourceWidth != UninitializedDescriptor) |
| result.append(' ', candidate.resourceWidth, 'w'); |
| } |
| |
| return result.toString(); |
| } |
| |
| return HTMLElement::completeURLsInAttributeValue(base, attribute, resolveURLs); |
| } |
| |
| Attribute HTMLImageElement::replaceURLsInAttributeValue(const Attribute& attribute, const CSS::SerializationContext& serializationContext) const |
| { |
| if (attribute.name() != srcsetAttr) |
| return attribute; |
| |
| if (serializationContext.replacementURLStrings.isEmpty()) |
| return attribute; |
| |
| return Attribute { srcsetAttr, AtomString { replaceURLsInSrcsetAttribute(*this, StringView(attribute.value()), serializationContext) } }; |
| } |
| |
| bool HTMLImageElement::matchesUsemap(const AtomString& name) const |
| { |
| return m_parsedUsemap == name; |
| } |
| |
| RefPtr<HTMLMapElement> HTMLImageElement::associatedMapElement() const |
| { |
| return treeScope().getImageMap(m_parsedUsemap); |
| } |
| |
| int HTMLImageElement::x() const |
| { |
| protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); |
| auto renderer = this->renderer(); |
| if (!renderer) |
| return 0; |
| |
| // FIXME: This doesn't work correctly with transforms. |
| return renderer->localToAbsolute().x(); |
| } |
| |
| int HTMLImageElement::y() const |
| { |
| protectedDocument()->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); |
| auto renderer = this->renderer(); |
| if (!renderer) |
| return 0; |
| |
| // FIXME: This doesn't work correctly with transforms. |
| return renderer->localToAbsolute().y(); |
| } |
| |
| bool HTMLImageElement::complete() const |
| { |
| return m_imageLoader->imageComplete(); |
| } |
| |
| String HTMLImageElement::decoding() const |
| { |
| switch (decodingMode()) { |
| case DecodingMode::Auto: |
| break; |
| case DecodingMode::Synchronous: |
| return "sync"_s; |
| case DecodingMode::Asynchronous: |
| return "async"_s; |
| } |
| return autoAtom(); |
| } |
| |
| DecodingMode HTMLImageElement::decodingMode() const |
| { |
| const AtomString& decodingMode = attributeWithoutSynchronization(decodingAttr); |
| if (equalLettersIgnoringASCIICase(decodingMode, "sync"_s)) |
| return DecodingMode::Synchronous; |
| if (equalLettersIgnoringASCIICase(decodingMode, "async"_s)) |
| return DecodingMode::Asynchronous; |
| return DecodingMode::Auto; |
| } |
| |
| void HTMLImageElement::decode(Ref<DeferredPromise>&& promise) |
| { |
| return m_imageLoader->decode(WTFMove(promise)); |
| } |
| |
| void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const |
| { |
| HTMLElement::addSubresourceAttributeURLs(urls); |
| |
| addSubresourceURL(urls, document().completeURL(imageSourceURL())); |
| // FIXME: What about when the usemap attribute begins with "#"? |
| addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(usemapAttr))); |
| } |
| |
| void HTMLImageElement::addCandidateSubresourceURLs(ListHashSet<URL>& urls) const |
| { |
| auto src = attributeWithoutSynchronization(srcAttr); |
| if (!src.isEmpty()) { |
| URL url { resolveURLStringIfNeeded(src) }; |
| if (!url.isNull()) |
| urls.add(url); |
| } |
| |
| getURLsFromSrcsetAttribute(*this, attributeWithoutSynchronization(srcsetAttr), urls); |
| } |
| |
| void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
| { |
| ActiveDOMObject::didMoveToNewDocument(newDocument); |
| oldDocument.removeDynamicMediaQueryDependentImage(*this); |
| |
| selectImageSource(RelevantMutation::No); |
| m_imageLoader->elementDidMoveToNewDocument(oldDocument); |
| HTMLElement::didMoveToNewDocument(oldDocument, newDocument); |
| if (RefPtr element = pictureElement()) |
| element->sourcesChanged(); |
| } |
| |
| bool HTMLImageElement::isServerMap() const |
| { |
| if (!hasAttributeWithoutSynchronization(ismapAttr)) |
| return false; |
| |
| const AtomString& usemap = attributeWithoutSynchronization(usemapAttr); |
| |
| // If the usemap attribute starts with '#', it refers to a map element in the document. |
| if (usemap.string()[0] == '#') |
| return false; |
| |
| return document().completeURL(usemap).isEmpty(); |
| } |
| |
| String HTMLImageElement::crossOrigin() const |
| { |
| return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr)); |
| } |
| |
| bool HTMLImageElement::allowsOrientationOverride() const |
| { |
| if (auto* cachedImage = this->cachedImage()) |
| return cachedImage->allowsOrientationOverride(); |
| return true; |
| } |
| |
| Image* HTMLImageElement::image() const |
| { |
| if (auto* cachedImage = this->cachedImage()) |
| return cachedImage->image(); |
| return nullptr; |
| } |
| |
| bool HTMLImageElement::allowsAnimation() const |
| { |
| if (auto* image = this->image()) |
| return image->allowsAnimation().value_or(document().page() ? document().page()->imageAnimationEnabled() : false); |
| return false; |
| } |
| |
| #if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL) |
| void HTMLImageElement::setAllowsAnimation(std::optional<bool> allowsAnimation) |
| { |
| if (!document().settings().imageAnimationControlEnabled()) |
| return; |
| |
| if (auto* image = this->image()) { |
| image->setAllowsAnimation(allowsAnimation); |
| if (auto* renderer = this->renderer()) |
| renderer->repaint(); |
| |
| if (RefPtr page = document().page()) { |
| if (allowsAnimation.value_or(false)) |
| page->addIndividuallyPlayingAnimationElement(*this); |
| else |
| page->removeIndividuallyPlayingAnimationElement(*this); |
| } |
| } |
| } |
| #endif |
| |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| |
| void HTMLImageElement::setAttachmentElement(Ref<HTMLAttachmentElement>&& attachment) |
| { |
| AttachmentAssociatedElement::setAttachmentElement(WTFMove(attachment)); |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| bool shouldEnableImageMenu = true; |
| #if ENABLE(MULTI_REPRESENTATION_HEIC) |
| shouldEnableImageMenu = !isMultiRepresentationHEIC(); |
| #endif |
| setImageMenuEnabled(shouldEnableImageMenu); |
| #endif // ENABLE(SERVICE_CONTROLS) |
| } |
| |
| #endif // ENABLE(ATTACHMENT_ELEMENT) |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const |
| { |
| return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child); |
| } |
| #endif |
| |
| #if PLATFORM(IOS_FAMILY) |
| // FIXME: We should find a better place for the touch callout logic. See rdar://problem/48937767. |
| bool HTMLImageElement::willRespondToMouseClickEventsWithEditability(Editability editability, IgnoreTouchCallout ignoreTouchCallout) const |
| { |
| auto renderer = this->renderer(); |
| if (ignoreTouchCallout == IgnoreTouchCallout::No && (!renderer || renderer->style().touchCallout() == Style::WebkitTouchCallout::Default)) |
| return true; |
| return HTMLElement::willRespondToMouseClickEventsWithEditability(editability); |
| } |
| |
| bool HTMLImageElement::willRespondToMouseClickEventsWithEditability(Editability editability) const |
| { |
| return willRespondToMouseClickEventsWithEditability(editability, IgnoreTouchCallout::No); |
| } |
| #endif |
| |
| #if USE(SYSTEM_PREVIEW) |
| bool HTMLImageElement::isSystemPreviewImage() const |
| { |
| if (!document().settings().systemPreviewEnabled()) |
| return false; |
| |
| auto* parent = parentElement(); |
| if (auto* anchorElement = dynamicDowncast<HTMLAnchorElement>(parent)) |
| return anchorElement->isSystemPreviewLink(); |
| if (auto* pictureElement = dynamicDowncast<HTMLPictureElement>(parent)) |
| return pictureElement->isSystemPreviewImage(); |
| return false; |
| } |
| #endif |
| |
| #if ENABLE(MULTI_REPRESENTATION_HEIC) |
| bool HTMLImageElement::isMultiRepresentationHEIC() const |
| { |
| if (!m_sourceElement) |
| return false; |
| |
| auto& typeAttribute = m_sourceElement->attributeWithoutSynchronization(typeAttr); |
| return typeAttribute == "image/x-apple-adaptive-glyph"_s; |
| } |
| #endif |
| |
| void HTMLImageElement::copyNonAttributePropertiesFromElement(const Element& source) |
| { |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| auto& sourceImage = downcast<HTMLImageElement>(source); |
| copyAttachmentAssociatedPropertiesFromElement(sourceImage); |
| #endif |
| Element::copyNonAttributePropertiesFromElement(source); |
| } |
| |
| CachedImage* HTMLImageElement::cachedImage() const |
| { |
| return m_imageLoader->image(); |
| } |
| |
| void HTMLImageElement::setLoadManually(bool loadManually) |
| { |
| m_imageLoader->setLoadManually(loadManually); |
| } |
| |
| bool HTMLImageElement::virtualHasPendingActivity() const |
| { |
| return m_imageLoader->hasPendingActivity(); |
| } |
| |
| size_t HTMLImageElement::pendingDecodePromisesCountForTesting() const |
| { |
| return m_imageLoader->pendingDecodePromisesCountForTesting(); |
| } |
| |
| bool HTMLImageElement::usesSrcsetOrPicture() const |
| { |
| return !attributeWithoutSynchronization(srcsetAttr).isNull() || !!pictureElement(); |
| } |
| |
| AtomString HTMLImageElement::srcsetForBindings() const |
| { |
| return getAttributeForBindings(srcsetAttr); |
| } |
| |
| bool HTMLImageElement::isDeferred() const |
| { |
| return m_imageLoader->isDeferred(); |
| } |
| |
| bool HTMLImageElement::isLazyLoadable() const |
| { |
| if (!document().frame() || !document().frame()->script().canExecuteScripts(ReasonForCallingCanExecuteScripts::NotAboutToExecuteScript)) |
| return false; |
| return hasLazyLoadableAttributeValue(attributeWithoutSynchronization(HTMLNames::loadingAttr)); |
| } |
| |
| String HTMLImageElement::referrerPolicyForBindings() const |
| { |
| return referrerPolicyToString(referrerPolicy()); |
| } |
| |
| ReferrerPolicy HTMLImageElement::referrerPolicy() const |
| { |
| return parseReferrerPolicy(attributeWithoutSynchronization(referrerpolicyAttr), ReferrerPolicySource::ReferrerPolicyAttribute).value_or(ReferrerPolicy::EmptyString); |
| } |
| |
| HTMLSourceElement* HTMLImageElement::sourceElement() const |
| { |
| return m_sourceElement.get(); |
| } |
| |
| void HTMLImageElement::setSourceElement(HTMLSourceElement* sourceElement) |
| { |
| if (m_sourceElement == sourceElement) |
| return; |
| m_sourceElement = sourceElement; |
| invalidateAttributeMapping(); |
| } |
| |
| void HTMLImageElement::invalidateAttributeMapping() |
| { |
| ensureUniqueElementData().setPresentationalHintStyleIsDirty(true); |
| invalidateStyle(); |
| } |
| |
| Ref<Element> HTMLImageElement::cloneElementWithoutAttributesAndChildren(Document& document, CustomElementRegistry*) const |
| { |
| auto clone = create(document); |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| cloneAttachmentAssociatedElementWithoutAttributesAndChildren(clone, document); |
| #endif |
| return clone; |
| } |
| |
| String HTMLImageElement::fetchPriorityForBindings() const |
| { |
| return convertEnumerationToString(fetchPriority()); |
| } |
| |
| RequestPriority HTMLImageElement::fetchPriority() const |
| { |
| return parseEnumerationFromString<RequestPriority>(attributeWithoutSynchronization(fetchpriorityAttr)).value_or(RequestPriority::Auto); |
| } |
| |
| bool HTMLImageElement::originClean(const SecurityOrigin& origin) const |
| { |
| UNUSED_PARAM(origin); |
| |
| auto* cachedImage = this->cachedImage(); |
| if (!cachedImage) |
| return true; |
| |
| RefPtr image = cachedImage->image(); |
| if (!image) |
| return true; |
| |
| if (image->renderingTaintsOrigin()) |
| return false; |
| |
| if (image->sourceURL().protocolIsData()) |
| return true; |
| |
| if (cachedImage->isCORSCrossOrigin()) |
| return false; |
| |
| ASSERT(cachedImage->origin()); |
| ASSERT(origin.toString() == cachedImage->origin()->toString()); |
| return true; |
| } |
| |
| IntersectionObserverData& HTMLImageElement::ensureIntersectionObserverData() |
| { |
| if (!m_intersectionObserverData) |
| m_intersectionObserverData = makeUnique<IntersectionObserverData>(); |
| return *m_intersectionObserverData; |
| } |
| |
| IntersectionObserverData* HTMLImageElement::intersectionObserverDataIfExists() |
| { |
| return m_intersectionObserverData.get(); |
| } |
| |
| } |