blob: 5f6f8d3d20144e525323b64370287267dc68c461 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011 Apple Inc.
* All rights reserved.
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) Research In Motion Limited 2011-2012. 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/layout/layout_image.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/html/media/media_element_parser_helpers.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h"
#include "third_party/blink/renderer/core/layout/layout_video.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/paint/image_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/timing/image_element_timing.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "ui/gfx/geometry/size_conversions.h"
namespace blink {
LayoutImage::LayoutImage(Element* element)
: LayoutReplaced(element, PhysicalSize()) {}
LayoutImage* LayoutImage::CreateAnonymous(PseudoElement& pseudo) {
LayoutImage* image = MakeGarbageCollected<LayoutImage>(nullptr);
image->SetDocumentForAnonymous(&pseudo.GetDocument());
return image;
}
LayoutImage::~LayoutImage() = default;
void LayoutImage::Trace(Visitor* visitor) const {
visitor->Trace(image_resource_);
LayoutReplaced::Trace(visitor);
}
void LayoutImage::WillBeDestroyed() {
NOT_DESTROYED();
DCHECK(image_resource_);
image_resource_->Shutdown();
LayoutReplaced::WillBeDestroyed();
}
void LayoutImage::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
NOT_DESTROYED();
LayoutReplaced::StyleDidChange(diff, old_style);
RespectImageOrientationEnum old_orientation =
old_style ? old_style->ImageOrientation()
: ComputedStyleInitialValues::InitialImageOrientation();
if (StyleRef().ImageOrientation() != old_orientation) {
IntrinsicSizeChanged();
}
}
void LayoutImage::SetImageResource(LayoutImageResource* image_resource) {
NOT_DESTROYED();
DCHECK(!image_resource_);
image_resource_ = image_resource;
image_resource_->Initialize(this);
}
void LayoutImage::ImageChanged(WrappedImagePtr new_image,
CanDeferInvalidation defer) {
NOT_DESTROYED();
DCHECK(View());
DCHECK(View()->GetFrameView());
if (DocumentBeingDestroyed())
return;
if (HasBoxDecorationBackground() || HasMask() || HasShapeOutside() ||
HasReflection())
LayoutReplaced::ImageChanged(new_image, defer);
if (!image_resource_)
return;
if (new_image != image_resource_->ImagePtr())
return;
auto* html_image_element = DynamicTo<HTMLImageElement>(GetNode());
if (IsGeneratedContent() && html_image_element &&
image_resource_->ErrorOccurred()) {
html_image_element->EnsureFallbackForGeneratedContent();
return;
}
// If error occurred, image marker should be replaced by a LayoutText.
// NotifyOfSubtreeChange to make list item updating its marker content.
if (IsListMarkerImage() && image_resource_->ErrorOccurred()) {
LayoutObject* item = this;
while (item->IsAnonymous())
item = item->Parent();
DCHECK(item);
if (item->NotifyOfSubtreeChange())
item->GetNode()->MarkAncestorsWithChildNeedsStyleRecalc();
}
// Per the spec, we let the server-sent header override srcset/other sources
// of dpr.
// https://github.com/igrigorik/http-client-hints/blob/master/draft-grigorik-http-client-hints-01.txt#L255
if (image_resource_->CachedImage() &&
image_resource_->CachedImage()->HasDevicePixelRatioHeaderValue()) {
UseCounter::Count(GetDocument(), WebFeature::kClientHintsContentDPR);
image_device_pixel_ratio_ =
1 / image_resource_->CachedImage()->DevicePixelRatioHeaderValue();
}
if (!did_increment_visually_non_empty_pixel_count_) {
// At a zoom level of 1 the image is guaranteed to have an integer size.
View()->GetFrameView()->IncrementVisuallyNonEmptyPixelCount(
gfx::ToFlooredSize(ImageSizeOverriddenByIntrinsicSize(1.0f)));
did_increment_visually_non_empty_pixel_count_ = true;
}
// The replaced content transform depends on the intrinsic size (see:
// FragmentPaintPropertyTreeBuilder::UpdateReplacedContentTransform).
SetNeedsPaintPropertyUpdate();
InvalidatePaintAndMarkForLayoutIfNeeded(defer);
}
void LayoutImage::UpdateIntrinsicSizeIfNeeded(const PhysicalSize& new_size) {
NOT_DESTROYED();
if (image_resource_->ErrorOccurred())
return;
SetIntrinsicSize(new_size);
}
bool LayoutImage::NeedsLayoutOnIntrinsicSizeChange() const {
NOT_DESTROYED();
// Flex layout algorithm uses the intrinsic image width/height even if
// width/height are specified.
if (IsFlexItemIncludingNG())
return true;
const auto& style = StyleRef();
// TODO(https://crbug.com/313072): Should this test min/max-height as well?
bool is_fixed_sized =
style.LogicalWidth().IsFixed() && style.LogicalHeight().IsFixed() &&
(style.LogicalMinWidth().IsFixed() || style.LogicalMinWidth().IsAuto()) &&
(style.LogicalMaxWidth().IsFixed() || style.LogicalMaxWidth().IsNone());
return !is_fixed_sized;
}
void LayoutImage::InvalidatePaintAndMarkForLayoutIfNeeded(
CanDeferInvalidation defer) {
NOT_DESTROYED();
PhysicalSize old_intrinsic_size = IntrinsicSize();
PhysicalSize new_intrinsic_size = PhysicalSize::FromSizeFRound(
ImageSizeOverriddenByIntrinsicSize(StyleRef().EffectiveZoom()));
UpdateIntrinsicSizeIfNeeded(new_intrinsic_size);
// In the case of generated image content using :before/:after/content, we
// might not be in the layout tree yet. In that case, we just need to update
// our intrinsic size. layout() will be called after we are inserted in the
// tree which will take care of what we are doing here.
if (!ContainingBlock())
return;
if (old_intrinsic_size != new_intrinsic_size) {
SetIntrinsicLogicalWidthsDirty();
if (NeedsLayoutOnIntrinsicSizeChange()) {
SetNeedsLayoutAndFullPaintInvalidation(
layout_invalidation_reason::kSizeChanged);
return;
}
}
SetShouldDoFullPaintInvalidationWithoutLayoutChange(
PaintInvalidationReason::kImage);
if (defer == CanDeferInvalidation::kYes && ImageResource() &&
ImageResource()->MaybeAnimated())
SetShouldDelayFullPaintInvalidation();
}
void LayoutImage::PaintReplaced(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) const {
NOT_DESTROYED();
if (ChildPaintBlockedByDisplayLock())
return;
ImagePainter(*this).PaintReplaced(paint_info, paint_offset);
}
void LayoutImage::Paint(const PaintInfo& paint_info) const {
NOT_DESTROYED();
ImagePainter(*this).Paint(paint_info);
}
void LayoutImage::AreaElementFocusChanged(HTMLAreaElement* area_element) {
NOT_DESTROYED();
DCHECK_EQ(area_element->ImageElement(), GetNode());
if (area_element->GetPath(this).IsEmpty())
return;
InvalidatePaintAndMarkForLayoutIfNeeded(CanDeferInvalidation::kYes);
}
bool LayoutImage::ForegroundIsKnownToBeOpaqueInRect(
const PhysicalRect& local_rect,
unsigned) const {
NOT_DESTROYED();
if (ChildPaintBlockedByDisplayLock())
return false;
if (!image_resource_->HasImage() || image_resource_->ErrorOccurred())
return false;
ImageResourceContent* image_content = image_resource_->CachedImage();
if (!image_content || !image_content->IsLoaded())
return false;
if (!PhysicalContentBoxRect().Contains(local_rect))
return false;
EFillBox background_clip = StyleRef().BackgroundClip();
// Background paints under borders.
if (background_clip == EFillBox::kBorder && StyleRef().HasBorder() &&
!StyleRef().BorderObscuresBackground())
return false;
// Background shows in padding area.
if ((background_clip == EFillBox::kBorder ||
background_clip == EFillBox::kPadding) &&
StyleRef().MayHavePadding())
return false;
// Object-position may leave parts of the content box empty, regardless of the
// value of object-fit.
if (StyleRef().ObjectPosition() !=
ComputedStyleInitialValues::InitialObjectPosition())
return false;
// Object-fit may leave parts of the content box empty.
EObjectFit object_fit = StyleRef().GetObjectFit();
if (object_fit != EObjectFit::kFill && object_fit != EObjectFit::kCover)
return false;
// Check for image with alpha.
DEVTOOLS_TIMELINE_TRACE_EVENT_WITH_CATEGORIES(
TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
inspector_paint_image_event::Data, this, *image_content);
return image_content->GetImage()->CurrentFrameKnownToBeOpaque();
}
bool LayoutImage::ComputeBackgroundIsKnownToBeObscured() const {
NOT_DESTROYED();
if (!StyleRef().HasBackground())
return false;
return ForegroundIsKnownToBeOpaqueInRect(BackgroundPaintedExtent(), 0);
}
HTMLMapElement* LayoutImage::ImageMap() const {
NOT_DESTROYED();
auto* i = DynamicTo<HTMLImageElement>(GetNode());
return i ? i->GetTreeScope().GetImageMap(
i->FastGetAttribute(html_names::kUsemapAttr))
: nullptr;
}
bool LayoutImage::NodeAtPoint(HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestPhase phase) {
NOT_DESTROYED();
HitTestResult temp_result(result);
bool inside = LayoutReplaced::NodeAtPoint(temp_result, hit_test_location,
accumulated_offset, phase);
if (!inside && result.GetHitTestRequest().ListBased())
result.Append(temp_result);
if (inside)
result = temp_result;
return inside;
}
bool LayoutImage::HasOverriddenIntrinsicSize() const {
NOT_DESTROYED();
if (!RuntimeEnabledFeatures::ExperimentalPoliciesEnabled())
return false;
auto* image_element = DynamicTo<HTMLImageElement>(GetNode());
return image_element && image_element->IsDefaultIntrinsicSize();
}
gfx::SizeF LayoutImage::ImageSizeOverriddenByIntrinsicSize(
float multiplier) const {
NOT_DESTROYED();
if (!HasOverriddenIntrinsicSize())
return image_resource_->ImageSize(multiplier);
gfx::SizeF overridden_intrinsic_size(kDefaultWidth, kDefaultHeight);
if (multiplier != 1) {
overridden_intrinsic_size.Scale(multiplier);
if (overridden_intrinsic_size.width() < 1.0f)
overridden_intrinsic_size.set_width(1.0f);
if (overridden_intrinsic_size.height() < 1.0f)
overridden_intrinsic_size.set_height(1.0f);
}
return overridden_intrinsic_size;
}
bool LayoutImage::OverrideIntrinsicSizingInfo(
IntrinsicSizingInfo& intrinsic_sizing_info) const {
NOT_DESTROYED();
if (!HasOverriddenIntrinsicSize())
return false;
gfx::SizeF overridden_intrinsic_size(kDefaultWidth, kDefaultHeight);
intrinsic_sizing_info.size = overridden_intrinsic_size;
intrinsic_sizing_info.aspect_ratio = intrinsic_sizing_info.size;
return true;
}
bool LayoutImage::CanApplyObjectViewBox() const {
if (!EmbeddedSVGImage()) {
return true;
}
// Only apply object-view-box if the image has both natural width/height.
const IntrinsicSizingInfo info =
image_resource_->GetNaturalDimensions(StyleRef().EffectiveZoom());
return info.has_width && info.has_height;
}
void LayoutImage::ComputeIntrinsicSizingInfo(
IntrinsicSizingInfo& intrinsic_sizing_info) const {
NOT_DESTROYED();
DCHECK(!ShouldApplySizeContainment());
if (!OverrideIntrinsicSizingInfo(intrinsic_sizing_info)) {
if (EmbeddedSVGImage()) {
intrinsic_sizing_info =
image_resource_->GetNaturalDimensions(StyleRef().EffectiveZoom());
if (auto view_box_size = ComputeObjectViewBoxSizeForIntrinsicSizing()) {
DCHECK(intrinsic_sizing_info.has_width);
DCHECK(intrinsic_sizing_info.has_height);
intrinsic_sizing_info.size = *view_box_size;
}
// The value returned by LayoutImageResource will be in zoomed CSS
// pixels, but for the 'scale-down' object-fit value we want "zoomed
// device pixels", so undo the DPR part here.
if (StyleRef().GetObjectFit() == EObjectFit::kScaleDown) {
intrinsic_sizing_info.size.InvScale(ImageDevicePixelRatio());
}
return;
}
LayoutReplaced::ComputeIntrinsicSizingInfo(intrinsic_sizing_info);
}
// Don't compute an intrinsic ratio to preserve historical WebKit behavior if
// we're painting alt text and/or a broken image.
// Video is excluded from this behavior because video elements have a default
// aspect ratio that a failed poster image load should not override.
if (image_resource_ && image_resource_->ErrorOccurred() &&
!IsA<LayoutVideo>(this)) {
intrinsic_sizing_info.aspect_ratio = gfx::SizeF(1, 1);
return;
}
}
SVGImage* LayoutImage::EmbeddedSVGImage() const {
NOT_DESTROYED();
if (!image_resource_)
return nullptr;
ImageResourceContent* cached_image = image_resource_->CachedImage();
// TODO(japhet): This shouldn't need to worry about cache validation.
// https://crbug.com/761026
if (!cached_image || cached_image->IsCacheValidator())
return nullptr;
return DynamicTo<SVGImage>(cached_image->GetImage());
}
void LayoutImage::UpdateAfterLayout() {
NOT_DESTROYED();
LayoutBox::UpdateAfterLayout();
Node* node = GetNode();
if (auto* image_element = DynamicTo<HTMLImageElement>(node)) {
media_element_parser_helpers::CheckUnsizedMediaViolation(
this, image_element->IsDefaultIntrinsicSize());
image_element->SetAutoSizesUsecounter();
} else if (auto* video_element = DynamicTo<HTMLVideoElement>(node)) {
media_element_parser_helpers::CheckUnsizedMediaViolation(
this, video_element->IsDefaultIntrinsicSize());
}
}
void LayoutImage::MutableForPainting::UpdatePaintedRect(
const PhysicalRect& paint_rect) {
// As an optimization for sprite sheets, an image may use the cull rect when
// generating the display item. We need to invalidate the display item if
// this rect changes.
auto& image = To<LayoutImage>(layout_object_);
if (image.last_paint_rect_ != paint_rect) {
static_cast<const DisplayItemClient&>(layout_object_).Invalidate();
}
image.last_paint_rect_ = paint_rect;
}
} // namespace blink