| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights |
| * reserved. |
| * Copyright (C) 2010 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 "third_party/blink/renderer/core/html/html_image_element.h" |
| |
| #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h" |
| #include "third_party/blink/renderer/core/css/css_property_names.h" |
| #include "third_party/blink/renderer/core/css/media_query_matcher.h" |
| #include "third_party/blink/renderer/core/css/media_values_dynamic.h" |
| #include "third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/dom/attribute.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/node_traversal.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/frame/deprecation.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" |
| #include "third_party/blink/renderer/core/html/cross_origin_attribute.h" |
| #include "third_party/blink/renderer/core/html/forms/form_associated.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_element.h" |
| #include "third_party/blink/renderer/core/html/html_dimension.h" |
| #include "third_party/blink/renderer/core/html/html_image_fallback_helper.h" |
| #include "third_party/blink/renderer/core/html/html_picture_element.h" |
| #include "third_party/blink/renderer/core/html/html_source_element.h" |
| #include "third_party/blink/renderer/core/html/loading_attribute.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/html/parser/html_srcset_parser.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/layout_image.h" |
| #include "third_party/blink/renderer/core/layout/layout_object_factory.h" |
| #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" |
| #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" |
| #include "third_party/blink/renderer/core/media_type_names.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/style/content_data.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h" |
| #include "third_party/blink/renderer/platform/network/mime/content_type.h" |
| #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_policy.h" |
| |
| namespace blink { |
| |
| class HTMLImageElement::ViewportChangeListener final |
| : public MediaQueryListListener { |
| public: |
| explicit ViewportChangeListener(HTMLImageElement* element) |
| : element_(element) {} |
| |
| void NotifyMediaQueryChanged() override { |
| if (element_) |
| element_->NotifyViewportChanged(); |
| } |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(element_); |
| MediaQueryListListener::Trace(visitor); |
| } |
| |
| private: |
| Member<HTMLImageElement> element_; |
| }; |
| |
| HTMLImageElement::HTMLImageElement(Document& document, |
| const CreateElementFlags flags) |
| : HTMLImageElement(document, flags.IsCreatedByParser()) {} |
| |
| HTMLImageElement::HTMLImageElement(Document& document, bool created_by_parser) |
| : HTMLElement(html_names::kImgTag, document), |
| image_loader_(MakeGarbageCollected<HTMLImageLoader>(this)), |
| image_device_pixel_ratio_(1.0f), |
| source_(nullptr), |
| layout_disposition_(LayoutDisposition::kPrimaryContent), |
| form_was_set_by_parser_(false), |
| element_created_by_parser_(created_by_parser), |
| is_fallback_image_(false), |
| is_default_overridden_intrinsic_size_( |
| !document.IsImageDocument() && GetExecutionContext() && |
| !GetExecutionContext()->IsFeatureEnabled( |
| mojom::blink::DocumentPolicyFeature::kUnsizedMedia)), |
| is_legacy_format_or_unoptimized_image_(false), |
| is_ad_related_(false), |
| referrer_policy_(network::mojom::ReferrerPolicy::kDefault) { |
| SetHasCustomStyleCallbacks(); |
| } |
| |
| HTMLImageElement::~HTMLImageElement() = default; |
| |
| void HTMLImageElement::Trace(Visitor* visitor) const { |
| visitor->Trace(image_loader_); |
| visitor->Trace(listener_); |
| visitor->Trace(form_); |
| visitor->Trace(source_); |
| HTMLElement::Trace(visitor); |
| } |
| |
| void HTMLImageElement::NotifyViewportChanged() { |
| // Re-selecting the source URL in order to pick a more fitting resource |
| // And update the image's intrinsic dimensions when the viewport changes. |
| // Picking of a better fitting resource is UA dependant, not spec required. |
| SelectSourceURL(ImageLoader::kUpdateSizeChanged); |
| } |
| |
| HTMLImageElement* HTMLImageElement::CreateForJSConstructor(Document& document) { |
| HTMLImageElement* image = MakeGarbageCollected<HTMLImageElement>(document); |
| image->element_created_by_parser_ = false; |
| return image; |
| } |
| |
| HTMLImageElement* HTMLImageElement::CreateForJSConstructor(Document& document, |
| unsigned width) { |
| HTMLImageElement* image = MakeGarbageCollected<HTMLImageElement>(document); |
| image->setWidth(width); |
| image->element_created_by_parser_ = false; |
| return image; |
| } |
| |
| HTMLImageElement* HTMLImageElement::CreateForJSConstructor(Document& document, |
| unsigned width, |
| unsigned height) { |
| HTMLImageElement* image = MakeGarbageCollected<HTMLImageElement>(document); |
| image->setWidth(width); |
| image->setHeight(height); |
| image->element_created_by_parser_ = false; |
| return image; |
| } |
| |
| bool HTMLImageElement::IsPresentationAttribute( |
| const QualifiedName& name) const { |
| if (name == html_names::kWidthAttr || name == html_names::kHeightAttr || |
| name == html_names::kBorderAttr || name == html_names::kVspaceAttr || |
| name == html_names::kHspaceAttr || name == html_names::kAlignAttr || |
| name == html_names::kValignAttr) |
| return true; |
| return HTMLElement::IsPresentationAttribute(name); |
| } |
| |
| void HTMLImageElement::CollectStyleForPresentationAttribute( |
| const QualifiedName& name, |
| const AtomicString& value, |
| MutableCSSPropertyValueSet* style) { |
| if (name == html_names::kWidthAttr) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kWidth, value); |
| if (FastHasAttribute(html_names::kHeightAttr)) { |
| const AtomicString& height = FastGetAttribute(html_names::kHeightAttr); |
| ApplyAspectRatioToStyle(value, height, style); |
| } |
| } else if (name == html_names::kHeightAttr) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kHeight, value); |
| if (FastHasAttribute(html_names::kWidthAttr)) { |
| const AtomicString& width = FastGetAttribute(html_names::kWidthAttr); |
| ApplyAspectRatioToStyle(width, value, style); |
| } |
| } else if (name == html_names::kBorderAttr) { |
| ApplyBorderAttributeToStyle(value, style); |
| } else if (name == html_names::kVspaceAttr) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kMarginTop, value); |
| AddHTMLLengthToStyle(style, CSSPropertyID::kMarginBottom, value); |
| } else if (name == html_names::kHspaceAttr) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kMarginLeft, value); |
| AddHTMLLengthToStyle(style, CSSPropertyID::kMarginRight, value); |
| } else if (name == html_names::kAlignAttr) { |
| ApplyAlignmentAttributeToStyle(value, style); |
| } else if (name == html_names::kValignAttr) { |
| AddPropertyToPresentationAttributeStyle( |
| style, CSSPropertyID::kVerticalAlign, value); |
| } else { |
| HTMLElement::CollectStyleForPresentationAttribute(name, value, style); |
| } |
| } |
| |
| void HTMLImageElement::CollectExtraStyleForPresentationAttribute( |
| MutableCSSPropertyValueSet* style) { |
| if (!source_) |
| return; |
| |
| const AtomicString& width = source_->FastGetAttribute(html_names::kWidthAttr); |
| const AtomicString& height = |
| source_->FastGetAttribute(html_names::kHeightAttr); |
| if (!width && !height) |
| return; |
| |
| if (width) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kWidth, width); |
| } else { |
| AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kWidth, |
| CSSValueID::kAuto); |
| } |
| |
| if (height) { |
| AddHTMLLengthToStyle(style, CSSPropertyID::kHeight, height); |
| } else { |
| AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kHeight, |
| CSSValueID::kAuto); |
| } |
| |
| if (width && height) { |
| ApplyAspectRatioToStyle(width, height, style); |
| } else { |
| AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kAspectRatio, |
| CSSValueID::kAuto); |
| } |
| } |
| |
| const AtomicString HTMLImageElement::ImageSourceURL() const { |
| return best_fit_image_url_.IsNull() ? FastGetAttribute(html_names::kSrcAttr) |
| : best_fit_image_url_; |
| } |
| |
| HTMLFormElement* HTMLImageElement::formOwner() const { |
| return form_.Get(); |
| } |
| |
| void HTMLImageElement::FormRemovedFromTree(const Node& form_root) { |
| DCHECK(form_); |
| if (NodeTraversal::HighestAncestorOrSelf(*this) != form_root) |
| ResetFormOwner(); |
| } |
| |
| void HTMLImageElement::ResetFormOwner() { |
| form_was_set_by_parser_ = false; |
| HTMLFormElement* nearest_form = FindFormAncestor(); |
| if (form_) { |
| if (nearest_form == form_.Get()) |
| return; |
| form_->Disassociate(*this); |
| } |
| if (nearest_form) { |
| form_ = nearest_form; |
| form_->Associate(*this); |
| } else { |
| form_ = nullptr; |
| } |
| } |
| |
| void HTMLImageElement::SetBestFitURLAndDPRFromImageCandidate( |
| const ImageCandidate& candidate) { |
| best_fit_image_url_ = candidate.Url(); |
| float candidate_density = candidate.Density(); |
| float old_image_device_pixel_ratio = image_device_pixel_ratio_; |
| if (candidate_density >= 0) |
| image_device_pixel_ratio_ = 1.0 / candidate_density; |
| |
| bool intrinsic_sizing_viewport_dependant = false; |
| if (candidate.GetResourceWidth() > 0) { |
| intrinsic_sizing_viewport_dependant = true; |
| UseCounter::Count(GetDocument(), WebFeature::kSrcsetWDescriptor); |
| } else if (!candidate.SrcOrigin()) { |
| UseCounter::Count(GetDocument(), WebFeature::kSrcsetXDescriptor); |
| } |
| if (auto* layout_image = DynamicTo<LayoutImage>(GetLayoutObject())) { |
| layout_image->SetImageDevicePixelRatio(image_device_pixel_ratio_); |
| |
| if (old_image_device_pixel_ratio != image_device_pixel_ratio_) |
| layout_image->IntrinsicSizeChanged(); |
| } |
| |
| if (intrinsic_sizing_viewport_dependant) { |
| if (!listener_) |
| listener_ = MakeGarbageCollected<ViewportChangeListener>(this); |
| |
| GetDocument().GetMediaQueryMatcher().AddViewportListener(listener_); |
| } else if (listener_) { |
| GetDocument().GetMediaQueryMatcher().RemoveViewportListener(listener_); |
| } |
| } |
| |
| void HTMLImageElement::ParseAttribute( |
| const AttributeModificationParams& params) { |
| const QualifiedName& name = params.name; |
| if (name == html_names::kAltAttr || name == html_names::kTitleAttr) { |
| if (UserAgentShadowRoot()) { |
| Element* text = UserAgentShadowRoot()->getElementById("alttext"); |
| String alt_text_content = AltText(); |
| if (text && text->textContent() != alt_text_content) |
| text->setTextContent(alt_text_content); |
| } |
| } else if (name == html_names::kSrcAttr || name == html_names::kSrcsetAttr || |
| name == html_names::kSizesAttr) { |
| SelectSourceURL(ImageLoader::kUpdateIgnorePreviousError); |
| } else if (name == html_names::kUsemapAttr) { |
| SetIsLink(!params.new_value.IsNull()); |
| } else if (name == html_names::kReferrerpolicyAttr) { |
| network::mojom::ReferrerPolicy old_referrer_policy = referrer_policy_; |
| referrer_policy_ = network::mojom::ReferrerPolicy::kDefault; |
| if (!params.new_value.IsNull()) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kHTMLImageElementReferrerPolicyAttribute); |
| |
| SecurityPolicy::ReferrerPolicyFromString( |
| params.new_value, kSupportReferrerPolicyLegacyKeywords, |
| &referrer_policy_); |
| } |
| |
| if (referrer_policy_ != old_referrer_policy) { |
| GetImageLoader().UpdateFromElement( |
| ImageLoader::kUpdateIgnorePreviousError, referrer_policy_); |
| } |
| } else if (name == html_names::kDecodingAttr) { |
| UseCounter::Count(GetDocument(), WebFeature::kImageDecodingAttribute); |
| decoding_mode_ = ParseImageDecodingMode(params.new_value); |
| } else if (name == html_names::kLoadingAttr) { |
| LoadingAttributeValue loading = GetLoadingAttributeValue(params.new_value); |
| if (loading == LoadingAttributeValue::kEager || |
| (loading == LoadingAttributeValue::kAuto && GetDocument().GetFrame() && |
| GetDocument().GetFrame()->GetLazyLoadImageSetting() != |
| LocalFrame::LazyLoadImageSetting::kEnabledAutomatic)) { |
| GetImageLoader().LoadDeferredImage(referrer_policy_); |
| } |
| } else if (name == html_names::kImportanceAttr && |
| RuntimeEnabledFeatures::PriorityHintsEnabled( |
| GetExecutionContext())) { |
| // We only need to keep track of usage here, as the communication of the |
| // |importance| attribute to the loading pipeline takes place in |
| // ImageLoader. |
| UseCounter::Count(GetDocument(), WebFeature::kPriorityHints); |
| } else if (name == html_names::kCrossoriginAttr) { |
| // As per an image's relevant mutations [1], we must queue a new loading |
| // microtask when the `crossorigin` attribute state has changed. Note that |
| // the attribute value can change without the attribute state changing [2]. |
| // |
| // [1]: |
| // https://html.spec.whatwg.org/multipage/images.html#relevant-mutations |
| // [2]: https://github.com/whatwg/html/issues/4533#issuecomment-483417499 |
| CrossOriginAttributeValue new_crossorigin_state = |
| GetCrossOriginAttributeValue(params.new_value); |
| CrossOriginAttributeValue old_crossorigin_state = |
| GetCrossOriginAttributeValue(params.old_value); |
| |
| if (new_crossorigin_state != old_crossorigin_state) { |
| // Update the current state so we can detect future state changes. |
| GetImageLoader().UpdateFromElement( |
| ImageLoader::kUpdateIgnorePreviousError, referrer_policy_); |
| } |
| } else { |
| HTMLElement::ParseAttribute(params); |
| } |
| } |
| |
| String 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 AtomicString& alt = FastGetAttribute(html_names::kAltAttr); |
| if (!alt.IsNull()) |
| return alt; |
| // fall back to title attribute |
| return FastGetAttribute(html_names::kTitleAttr); |
| } |
| |
| void HTMLImageElement::InvalidateAttributeMapping() { |
| EnsureUniqueElementData().SetPresentationAttributeStyleIsDirty(true); |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kPictureSourceChanged)); |
| } |
| |
| bool HTMLImageElement::SupportedImageType( |
| const String& type, |
| const HashSet<String>* disabled_image_types) { |
| String trimmed_type = ContentType(type).GetType(); |
| // An empty type attribute is implicitly supported. |
| if (trimmed_type.IsEmpty()) |
| return true; |
| if (disabled_image_types && disabled_image_types->Contains(trimmed_type)) { |
| return false; |
| } |
| return MIMETypeRegistry::IsSupportedImagePrefixedMIMEType(trimmed_type); |
| } |
| |
| // http://picture.responsiveimages.org/#update-source-set |
| ImageCandidate HTMLImageElement::FindBestFitImageFromPictureParent() { |
| DCHECK(IsMainThread()); |
| Node* parent = parentNode(); |
| source_ = nullptr; |
| if (!parent || !IsA<HTMLPictureElement>(*parent)) |
| return ImageCandidate(); |
| HashSet<String> disabled_image_types; |
| probe::GetDisabledImageTypes(GetExecutionContext(), &disabled_image_types); |
| for (Node* child = parent->firstChild(); child; |
| child = child->nextSibling()) { |
| if (child == this) |
| return ImageCandidate(); |
| |
| auto* source = DynamicTo<HTMLSourceElement>(child); |
| if (!source) |
| continue; |
| |
| if (!source->FastGetAttribute(html_names::kSrcAttr).IsNull()) { |
| Deprecation::CountDeprecation(GetExecutionContext(), |
| WebFeature::kPictureSourceSrc); |
| } |
| String srcset = source->FastGetAttribute(html_names::kSrcsetAttr); |
| if (srcset.IsEmpty()) |
| continue; |
| String type = source->FastGetAttribute(html_names::kTypeAttr); |
| if (!SupportedImageType(type, &disabled_image_types)) |
| continue; |
| |
| if (!source->MediaQueryMatches()) |
| continue; |
| |
| ImageCandidate candidate = BestFitSourceForSrcsetAttribute( |
| GetDocument().DevicePixelRatio(), SourceSize(*source), |
| source->FastGetAttribute(html_names::kSrcsetAttr), &GetDocument()); |
| if (candidate.IsEmpty()) |
| continue; |
| source_ = source; |
| return candidate; |
| } |
| return ImageCandidate(); |
| } |
| |
| LayoutObject* HTMLImageElement::CreateLayoutObject(const ComputedStyle& style, |
| LegacyLayout legacy) { |
| const ContentData* content_data = style.GetContentData(); |
| if (content_data && content_data->IsImage()) { |
| const StyleImage* content_image = |
| To<ImageContentData>(content_data)->GetImage(); |
| bool error_occurred = content_image && content_image->CachedImage() && |
| content_image->CachedImage()->ErrorOccurred(); |
| if (!error_occurred) |
| return LayoutObject::CreateObject(this, style, legacy); |
| } |
| |
| switch (layout_disposition_) { |
| case LayoutDisposition::kFallbackContent: |
| return LayoutObjectFactory::CreateBlockFlow(*this, style, legacy); |
| case LayoutDisposition::kPrimaryContent: { |
| LayoutImage* image = new LayoutImage(this); |
| image->SetImageResource(MakeGarbageCollected<LayoutImageResource>()); |
| image->SetImageDevicePixelRatio(image_device_pixel_ratio_); |
| return image; |
| } |
| case LayoutDisposition::kCollapsed: // Falls through. |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| } |
| |
| void HTMLImageElement::AttachLayoutTree(AttachContext& context) { |
| HTMLElement::AttachLayoutTree(context); |
| if (auto* layout_image = DynamicTo<LayoutImage>(GetLayoutObject())) { |
| LayoutImageResource* layout_image_resource = layout_image->ImageResource(); |
| if (is_fallback_image_) |
| layout_image_resource->UseBrokenImage(); |
| |
| if (layout_image_resource->HasImage()) |
| return; |
| |
| if (!GetImageLoader().GetContent() && !layout_image_resource->CachedImage()) |
| return; |
| layout_image_resource->SetImageResource(GetImageLoader().GetContent()); |
| } |
| } |
| |
| Node::InsertionNotificationRequest HTMLImageElement::InsertedInto( |
| ContainerNode& insertion_point) { |
| if (!form_was_set_by_parser_ || |
| NodeTraversal::HighestAncestorOrSelf(insertion_point) != |
| NodeTraversal::HighestAncestorOrSelf(*form_.Get())) |
| ResetFormOwner(); |
| if (listener_) |
| GetDocument().GetMediaQueryMatcher().AddViewportListener(listener_); |
| bool was_added_to_picture_parent = false; |
| if (auto* picture_parent = DynamicTo<HTMLPictureElement>(parentNode())) { |
| picture_parent->AddListenerToSourceChildren(); |
| was_added_to_picture_parent = picture_parent == insertion_point; |
| } |
| |
| bool image_was_modified = false; |
| if (GetDocument().IsActive() && was_added_to_picture_parent) { |
| ImageCandidate candidate = FindBestFitImageFromPictureParent(); |
| if (!candidate.IsEmpty()) { |
| InvalidateAttributeMapping(); |
| SetBestFitURLAndDPRFromImageCandidate(candidate); |
| image_was_modified = true; |
| } |
| } |
| |
| if (image_was_modified || GetImageLoader().ShouldUpdateOnInsertedInto( |
| insertion_point, referrer_policy_)) { |
| GetImageLoader().UpdateFromElement(ImageLoader::kUpdateNormal, |
| referrer_policy_); |
| } |
| return HTMLElement::InsertedInto(insertion_point); |
| } |
| |
| void HTMLImageElement::RemovedFrom(ContainerNode& insertion_point) { |
| InvalidateAttributeMapping(); |
| if (!form_ || NodeTraversal::HighestAncestorOrSelf(*form_.Get()) != |
| NodeTraversal::HighestAncestorOrSelf(*this)) |
| ResetFormOwner(); |
| if (listener_) { |
| GetDocument().GetMediaQueryMatcher().RemoveViewportListener(listener_); |
| if (auto* picture_parent = DynamicTo<HTMLPictureElement>(parentNode())) |
| picture_parent->RemoveListenerFromSourceChildren(); |
| } |
| HTMLElement::RemovedFrom(insertion_point); |
| } |
| |
| unsigned HTMLImageElement::width() { |
| if (InActiveDocument()) { |
| GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| } |
| |
| if (!GetLayoutObject()) { |
| // check the attribute first for an explicit pixel value |
| // TODO(cbiesinger): The attribute could be a float or percentage value... |
| unsigned width = 0; |
| if (ParseHTMLNonNegativeInteger(FastGetAttribute(html_names::kWidthAttr), |
| width)) |
| return width; |
| |
| // if the image is available, use its width |
| if (ImageResourceContent* image_content = GetImageLoader().GetContent()) { |
| return image_content->IntrinsicSize(kRespectImageOrientation).Width(); |
| } |
| } |
| |
| return LayoutBoxWidth(); |
| } |
| |
| unsigned HTMLImageElement::height() { |
| if (InActiveDocument()) { |
| GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| } |
| |
| if (!GetLayoutObject()) { |
| // check the attribute first for an explicit pixel value |
| // TODO(cbiesinger): The attribute could be a float or percentage value... |
| unsigned height = 0; |
| if (ParseHTMLNonNegativeInteger(FastGetAttribute(html_names::kHeightAttr), |
| height)) |
| return height; |
| |
| // if the image is available, use its height |
| if (ImageResourceContent* image_content = GetImageLoader().GetContent()) { |
| return image_content->IntrinsicSize(kRespectImageOrientation).Height(); |
| } |
| } |
| |
| return LayoutBoxHeight(); |
| } |
| |
| LayoutSize HTMLImageElement::DensityCorrectedIntrinsicDimensions() const { |
| if (IsDefaultIntrinsicSize()) { |
| return LayoutSize(LayoutReplaced::kDefaultWidth, |
| LayoutReplaced::kDefaultHeight); |
| } |
| ImageResourceContent* image_content = GetImageLoader().GetContent(); |
| if (!image_content || !image_content->HasImage()) |
| return LayoutSize(); |
| |
| float pixel_density = image_device_pixel_ratio_; |
| if (image_content->HasDevicePixelRatioHeaderValue() && |
| image_content->DevicePixelRatioHeaderValue() > 0) |
| pixel_density = 1 / image_content->DevicePixelRatioHeaderValue(); |
| |
| RespectImageOrientationEnum respect_image_orientation = |
| LayoutObject::ShouldRespectImageOrientation(GetLayoutObject()); |
| |
| LayoutSize natural_size( |
| image_content->IntrinsicSize(respect_image_orientation)); |
| natural_size.Scale(pixel_density); |
| return natural_size; |
| } |
| |
| unsigned HTMLImageElement::naturalWidth() const { |
| return DensityCorrectedIntrinsicDimensions().Width().ToUnsigned(); |
| } |
| |
| unsigned HTMLImageElement::naturalHeight() const { |
| return DensityCorrectedIntrinsicDimensions().Height().ToUnsigned(); |
| } |
| |
| unsigned HTMLImageElement::LayoutBoxWidth() const { |
| LayoutBox* box = GetLayoutBox(); |
| return box ? AdjustForAbsoluteZoom::AdjustLayoutUnit(box->ContentWidth(), |
| *box) |
| .Round() |
| : 0; |
| } |
| |
| unsigned HTMLImageElement::LayoutBoxHeight() const { |
| LayoutBox* box = GetLayoutBox(); |
| return box ? AdjustForAbsoluteZoom::AdjustLayoutUnit(box->ContentHeight(), |
| *box) |
| .Round() |
| : 0; |
| } |
| |
| const String& HTMLImageElement::currentSrc() const { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/edits.html#dom-img-currentsrc |
| // The currentSrc IDL attribute must return the img element's current |
| // request's current URL. |
| |
| // Return the picked URL string in case of load error. |
| if (GetImageLoader().HadError()) |
| return best_fit_image_url_; |
| // Initially, the pending request turns into current request when it is |
| // either available or broken. Check for the resource being in error or |
| // having an image to determine these states. |
| ImageResourceContent* image_content = GetImageLoader().GetContent(); |
| if (!image_content || |
| (!image_content->ErrorOccurred() && !image_content->HasImage())) |
| return g_empty_atom; |
| |
| return image_content->Url().GetString(); |
| } |
| |
| bool HTMLImageElement::IsURLAttribute(const Attribute& attribute) const { |
| return attribute.GetName() == html_names::kSrcAttr || |
| attribute.GetName() == html_names::kLowsrcAttr || |
| attribute.GetName() == html_names::kLongdescAttr || |
| (attribute.GetName() == html_names::kUsemapAttr && |
| attribute.Value()[0] != '#') || |
| HTMLElement::IsURLAttribute(attribute); |
| } |
| |
| bool HTMLImageElement::HasLegalLinkAttribute(const QualifiedName& name) const { |
| return name == html_names::kSrcAttr || |
| HTMLElement::HasLegalLinkAttribute(name); |
| } |
| |
| const QualifiedName& HTMLImageElement::SubResourceAttributeName() const { |
| return html_names::kSrcAttr; |
| } |
| |
| bool HTMLImageElement::draggable() const { |
| // Image elements are draggable by default. |
| return !EqualIgnoringASCIICase(FastGetAttribute(html_names::kDraggableAttr), |
| "false"); |
| } |
| |
| void HTMLImageElement::setHeight(unsigned value) { |
| SetUnsignedIntegralAttribute(html_names::kHeightAttr, value); |
| } |
| |
| void HTMLImageElement::setWidth(unsigned value) { |
| SetUnsignedIntegralAttribute(html_names::kWidthAttr, value); |
| } |
| |
| int HTMLImageElement::x() const { |
| GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| LayoutObject* r = GetLayoutObject(); |
| if (!r) |
| return 0; |
| |
| // FIXME: This doesn't work correctly with transforms. |
| PhysicalOffset abs_pos = |
| r->LocalToAbsolutePoint(PhysicalOffset(), kIgnoreTransforms); |
| return abs_pos.left.ToInt(); |
| } |
| |
| int HTMLImageElement::y() const { |
| GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| LayoutObject* r = GetLayoutObject(); |
| if (!r) |
| return 0; |
| |
| // FIXME: This doesn't work correctly with transforms. |
| PhysicalOffset abs_pos = |
| r->LocalToAbsolutePoint(PhysicalOffset(), kIgnoreTransforms); |
| return abs_pos.top.ToInt(); |
| } |
| |
| ScriptPromise HTMLImageElement::decode(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| return GetImageLoader().Decode(script_state, exception_state); |
| } |
| |
| bool HTMLImageElement::complete() const { |
| return GetImageLoader().ImageComplete(); |
| } |
| |
| void HTMLImageElement::DidMoveToNewDocument(Document& old_document) { |
| GetImageLoader().ElementDidMoveToNewDocument(); |
| HTMLElement::DidMoveToNewDocument(old_document); |
| SelectSourceURL(ImageLoader::kUpdateIgnorePreviousError); |
| } |
| |
| bool HTMLImageElement::IsServerMap() const { |
| if (!FastHasAttribute(html_names::kIsmapAttr)) |
| return false; |
| |
| const AtomicString& usemap = FastGetAttribute(html_names::kUsemapAttr); |
| |
| // If the usemap attribute starts with '#', it refers to a map element in |
| // the document. |
| if (usemap[0] == '#') |
| return false; |
| |
| return GetDocument() |
| .CompleteURL(StripLeadingAndTrailingHTMLSpaces(usemap)) |
| .IsEmpty(); |
| } |
| |
| Image* HTMLImageElement::ImageContents() { |
| if (!GetImageLoader().ImageComplete() || !GetImageLoader().GetContent()) |
| return nullptr; |
| |
| return GetImageLoader().GetContent()->GetImage(); |
| } |
| |
| bool HTMLImageElement::IsInteractiveContent() const { |
| return FastHasAttribute(html_names::kUsemapAttr); |
| } |
| |
| FloatSize HTMLImageElement::DefaultDestinationSize( |
| const FloatSize& default_object_size, |
| const RespectImageOrientationEnum respect_orientation) const { |
| ImageResourceContent* image_content = CachedImage(); |
| if (!image_content || !image_content->HasImage()) |
| return FloatSize(); |
| |
| Image* image = image_content->GetImage(); |
| if (auto* svg_image = DynamicTo<SVGImage>(image)) |
| return svg_image->ConcreteObjectSize(default_object_size); |
| |
| LayoutSize size(image->Size(respect_orientation)); |
| if (GetLayoutObject() && GetLayoutObject()->IsLayoutImage() && |
| image->HasIntrinsicSize()) |
| size.Scale(To<LayoutImage>(GetLayoutObject())->ImageDevicePixelRatio()); |
| return FloatSize(size); |
| } |
| |
| static bool SourceSizeValue(const Element* element, |
| Document& current_document, |
| float& source_size) { |
| String sizes = element->FastGetAttribute(html_names::kSizesAttr); |
| bool exists = !sizes.IsNull(); |
| if (exists) |
| UseCounter::Count(current_document, WebFeature::kSizes); |
| source_size = |
| SizesAttributeParser(MediaValuesDynamic::Create(current_document), sizes, |
| current_document.GetExecutionContext()) |
| .length(); |
| return exists; |
| } |
| |
| FetchParameters::ResourceWidth HTMLImageElement::GetResourceWidth() const { |
| FetchParameters::ResourceWidth resource_width; |
| Element* element = source_.Get(); |
| resource_width.is_set = SourceSizeValue(element ? element : this, |
| GetDocument(), resource_width.width); |
| return resource_width; |
| } |
| |
| float HTMLImageElement::SourceSize(Element& element) { |
| float value; |
| // We don't care here if the sizes attribute exists, so we ignore the return |
| // value. If it doesn't exist, we just return the default. |
| SourceSizeValue(&element, GetDocument(), value); |
| return value; |
| } |
| |
| void HTMLImageElement::ForceReload() const { |
| GetImageLoader().UpdateFromElement(ImageLoader::kUpdateForcedReload, |
| referrer_policy_); |
| } |
| |
| void HTMLImageElement::SelectSourceURL( |
| ImageLoader::UpdateFromElementBehavior behavior) { |
| if (!GetDocument().IsActive()) |
| return; |
| // TODO(crbug.com/1175295): Remove this CHECK once the investigation is done. |
| CHECK(GetDocument().GetExecutionContext()); |
| |
| HTMLSourceElement* old_source = source_; |
| ImageCandidate candidate = FindBestFitImageFromPictureParent(); |
| if (candidate.IsEmpty()) { |
| candidate = BestFitSourceForImageAttributes( |
| GetDocument().DevicePixelRatio(), SourceSize(*this), |
| FastGetAttribute(html_names::kSrcAttr), |
| FastGetAttribute(html_names::kSrcsetAttr), &GetDocument()); |
| } |
| if (old_source != source_) |
| InvalidateAttributeMapping(); |
| AtomicString old_url = best_fit_image_url_; |
| SetBestFitURLAndDPRFromImageCandidate(candidate); |
| |
| // Step 5 in |
| // https://html.spec.whatwg.org/multipage/images.html#reacting-to-environment-changes |
| // Deliberately not compliant and avoiding checking image density, to avoid |
| // spurious downloads. See https://github.com/whatwg/html/issues/4646 |
| if (behavior != HTMLImageLoader::kUpdateSizeChanged || |
| best_fit_image_url_ != old_url) { |
| GetImageLoader().UpdateFromElement(behavior, referrer_policy_); |
| } |
| |
| if (GetImageLoader().ImageIsPotentiallyAvailable()) |
| EnsurePrimaryContent(); |
| else |
| EnsureCollapsedOrFallbackContent(); |
| } |
| |
| void HTMLImageElement::StartLoadingImageDocument( |
| ImageResourceContent* image_content) { |
| // This element is being used to load an image in an ImageDocument. The |
| // provided ImageResource is owned/managed by the ImageDocumentParser. Set it |
| // on our ImageLoader and then update the 'src' attribute to reflect the URL |
| // of the image. This latter step will also initiate the load from the |
| // ImageLoader's PoV. |
| GetImageLoader().SetImageDocumentContent(image_content); |
| setAttribute(html_names::kSrcAttr, AtomicString(image_content->Url())); |
| } |
| |
| void HTMLImageElement::DidAddUserAgentShadowRoot(ShadowRoot&) { |
| HTMLImageFallbackHelper::CreateAltTextShadowTree(*this); |
| } |
| |
| void HTMLImageElement::EnsureFallbackForGeneratedContent() { |
| // The special casing for generated content in CreateLayoutObject breaks the |
| // invariant that the layout object attached to this element will always be |
| // appropriate for |layout_disposition_|. Force recreate it. |
| // TODO(engedy): Remove this hack. See: https://crbug.com/671953. |
| SetLayoutDisposition(LayoutDisposition::kFallbackContent, |
| true /* force_reattach */); |
| } |
| |
| void HTMLImageElement::EnsureCollapsedOrFallbackContent() { |
| if (is_fallback_image_) |
| return; |
| |
| ImageResourceContent* image_content = GetImageLoader().GetContent(); |
| base::Optional<ResourceError> error = |
| image_content ? image_content->GetResourceError() : base::nullopt; |
| SetLayoutDisposition(error && error->ShouldCollapseInitiator() |
| ? LayoutDisposition::kCollapsed |
| : LayoutDisposition::kFallbackContent); |
| } |
| |
| void HTMLImageElement::EnsurePrimaryContent() { |
| SetLayoutDisposition(LayoutDisposition::kPrimaryContent); |
| } |
| |
| bool HTMLImageElement::IsCollapsed() const { |
| return layout_disposition_ == LayoutDisposition::kCollapsed; |
| } |
| |
| void HTMLImageElement::SetLayoutDisposition( |
| LayoutDisposition layout_disposition, |
| bool force_reattach) { |
| if (layout_disposition_ == layout_disposition && !force_reattach) |
| return; |
| |
| DCHECK(!GetDocument().InStyleRecalc()); |
| |
| layout_disposition_ = layout_disposition; |
| |
| if (layout_disposition_ == LayoutDisposition::kFallbackContent) { |
| EventDispatchForbiddenScope::AllowUserAgentEvents allow_events; |
| EnsureUserAgentShadowRoot(); |
| } |
| |
| // ComputedStyle depends on layout_disposition_. Trigger recalc. |
| SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::Create(style_change_reason::kUseFallback)); |
| // LayoutObject type depends on layout_disposition_. Trigger re-attach. |
| SetForceReattachLayoutTree(); |
| } |
| |
| scoped_refptr<ComputedStyle> HTMLImageElement::CustomStyleForLayoutObject( |
| const StyleRecalcContext& style_recalc_context) { |
| switch (layout_disposition_) { |
| case LayoutDisposition::kPrimaryContent: // Fall through. |
| case LayoutDisposition::kCollapsed: |
| return OriginalStyleForLayoutObject(style_recalc_context); |
| case LayoutDisposition::kFallbackContent: { |
| scoped_refptr<ComputedStyle> style = |
| OriginalStyleForLayoutObject(style_recalc_context); |
| HTMLImageFallbackHelper::CustomStyleForAltText(*this, *style); |
| return style; |
| } |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| } |
| |
| void HTMLImageElement::AssociateWith(HTMLFormElement* form) { |
| if (form && form->isConnected()) { |
| form_ = form; |
| form_was_set_by_parser_ = true; |
| form_->Associate(*this); |
| form_->DidAssociateByParser(); |
| } |
| } |
| |
| // Minimum height or width of the image to start lazyloading. |
| constexpr int kMinDimensionToLazyLoad = 10; |
| |
| HTMLImageElement::LazyLoadDimensionType |
| HTMLImageElement::GetAttributeLazyLoadDimensionType( |
| const String& attribute_value) { |
| HTMLDimension dimension; |
| if (ParseDimensionValue(attribute_value, dimension) && |
| dimension.IsAbsolute()) { |
| return dimension.Value() <= kMinDimensionToLazyLoad |
| ? LazyLoadDimensionType::kAbsoluteSmall |
| : LazyLoadDimensionType::kAbsoluteNotSmall; |
| } |
| return LazyLoadDimensionType::kNotAbsolute; |
| } |
| |
| HTMLImageElement::LazyLoadDimensionType |
| HTMLImageElement::GetInlineStyleDimensionsType( |
| const CSSPropertyValueSet* property_set) { |
| if (!property_set) |
| return LazyLoadDimensionType::kNotAbsolute; |
| const CSSValue* height = |
| property_set->GetPropertyCSSValue(CSSPropertyID::kHeight); |
| const CSSValue* width = |
| property_set->GetPropertyCSSValue(CSSPropertyID::kWidth); |
| const auto* width_prim = DynamicTo<CSSPrimitiveValue>(width); |
| const auto* height_prim = DynamicTo<CSSPrimitiveValue>(height); |
| if (!width_prim || !height_prim || !width_prim->IsPx() || |
| !height_prim->IsPx()) { |
| return LazyLoadDimensionType::kNotAbsolute; |
| } |
| return (height_prim->GetDoubleValue() <= kMinDimensionToLazyLoad) && |
| (width_prim->GetDoubleValue() <= kMinDimensionToLazyLoad) |
| ? LazyLoadDimensionType::kAbsoluteSmall |
| : LazyLoadDimensionType::kAbsoluteNotSmall; |
| } |
| |
| } // namespace blink |