blob: 68d5e26e2ddce398b0f245755f8bbb7791722fc5 [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 "core/layout/LayoutImage.h"
#include "core/dom/PseudoElement.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLAreaElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html_names.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutView.h"
#include "core/loader/resource/ImageResourceContent.h"
#include "core/paint/ImagePainter.h"
#include "core/svg/graphics/SVGImage.h"
namespace blink {
using namespace HTMLNames;
LayoutImage::LayoutImage(Element* element)
: LayoutReplaced(element, LayoutSize()),
did_increment_visually_non_empty_pixel_count_(false),
is_generated_content_(false),
image_device_pixel_ratio_(1.0f) {}
LayoutImage* LayoutImage::CreateAnonymous(PseudoElement& pseudo) {
LayoutImage* image = new LayoutImage(nullptr);
image->SetDocumentForAnonymous(&pseudo.GetDocument());
return image;
}
LayoutImage::~LayoutImage() = default;
void LayoutImage::WillBeDestroyed() {
DCHECK(image_resource_);
image_resource_->Shutdown();
LayoutReplaced::WillBeDestroyed();
}
void LayoutImage::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
LayoutReplaced::StyleDidChange(diff, old_style);
bool old_orientation =
old_style ? old_style->RespectImageOrientation()
: ComputedStyleInitialValues::InitialRespectImageOrientation();
if (Style() && Style()->RespectImageOrientation() != old_orientation)
IntrinsicSizeChanged();
}
void LayoutImage::SetImageResource(LayoutImageResource* image_resource) {
DCHECK(!image_resource_);
image_resource_ = image_resource;
image_resource_->Initialize(this);
}
void LayoutImage::ImageChanged(WrappedImagePtr new_image,
CanDeferInvalidation defer,
const IntRect* rect) {
DCHECK(View());
DCHECK(View()->GetFrameView());
if (DocumentBeingDestroyed())
return;
if (HasBoxDecorationBackground() || HasMask() || HasShapeOutside() ||
HasReflection())
LayoutReplaced::ImageChanged(new_image, defer, rect);
if (!image_resource_)
return;
if (new_image != image_resource_->ImagePtr())
return;
if (IsGeneratedContent() && IsHTMLImageElement(GetNode()) &&
image_resource_->ErrorOccurred()) {
ToHTMLImageElement(GetNode())->EnsureFallbackForGeneratedContent();
return;
}
// 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(&(View()->GetFrameView()->GetFrame()),
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(
FlooredIntSize(image_resource_->ImageSize(1.0f)));
did_increment_visually_non_empty_pixel_count_ = true;
}
InvalidatePaintAndMarkForLayoutIfNeeded(defer);
}
void LayoutImage::UpdateIntrinsicSizeIfNeeded(const LayoutSize& new_size) {
if (image_resource_->ErrorOccurred() || !image_resource_->HasImage())
return;
SetIntrinsicSize(new_size);
}
void LayoutImage::InvalidatePaintAndMarkForLayoutIfNeeded(
CanDeferInvalidation defer) {
LayoutSize old_intrinsic_size = IntrinsicSize();
LayoutSize new_intrinsic_size =
RoundedLayoutSize(image_resource_->ImageSize(Style()->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;
bool image_source_has_changed_size = old_intrinsic_size != new_intrinsic_size;
if (image_source_has_changed_size)
SetPreferredLogicalWidthsDirty();
// If the actual area occupied by the image has changed and it is not
// constrained by style then a layout is required.
bool image_size_is_constrained = Style()->LogicalWidth().IsSpecified() &&
Style()->LogicalHeight().IsSpecified();
// FIXME: We only need to recompute the containing block's preferred size if
// the containing block's size depends on the image's size (i.e., the
// container uses shrink-to-fit sizing). There's no easy way to detect that
// shrink-to-fit is needed, always force a layout.
bool containing_block_needs_to_recompute_preferred_size =
Style()->LogicalWidth().IsPercentOrCalc() ||
Style()->LogicalMaxWidth().IsPercentOrCalc() ||
Style()->LogicalMinWidth().IsPercentOrCalc();
if (image_source_has_changed_size &&
(!image_size_is_constrained ||
containing_block_needs_to_recompute_preferred_size)) {
SetNeedsLayoutAndFullPaintInvalidation(
LayoutInvalidationReason::kSizeChanged);
return;
}
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
defer == CanDeferInvalidation::kYes && ImageResource() &&
ImageResource()->MaybeAnimated()
? PaintInvalidationReason::kDelayedFull
: PaintInvalidationReason::kImage);
// Tell any potential compositing layers that the image needs updating.
ContentChanged(kImageChanged);
}
void LayoutImage::ImageNotifyFinished(ImageResourceContent* new_image) {
if (!image_resource_)
return;
if (DocumentBeingDestroyed())
return;
InvalidateBackgroundObscurationStatus();
if (new_image == image_resource_->CachedImage()) {
// tell any potential compositing layers
// that the image is done and they can reference it directly.
ContentChanged(kImageChanged);
}
}
void LayoutImage::PaintReplaced(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
ImagePainter(*this).PaintReplaced(paint_info, paint_offset);
}
void LayoutImage::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
ImagePainter(*this).Paint(paint_info, paint_offset);
}
void LayoutImage::AreaElementFocusChanged(HTMLAreaElement* area_element) {
DCHECK_EQ(area_element->ImageElement(), GetNode());
if (area_element->GetPath(this).IsEmpty())
return;
InvalidatePaintAndMarkForLayoutIfNeeded(CanDeferInvalidation::kYes);
}
bool LayoutImage::ForegroundIsKnownToBeOpaqueInRect(
const LayoutRect& local_rect,
unsigned) const {
if (!image_resource_->HasImage() || image_resource_->ErrorOccurred())
return false;
ImageResourceContent* image_content = image_resource_->CachedImage();
if (!image_content || !image_content->IsLoaded())
return false;
if (!ContentBoxRect().Contains(local_rect))
return false;
EFillBox background_clip = Style()->BackgroundClip();
// Background paints under borders.
if (background_clip == EFillBox::kBorder && Style()->HasBorder() &&
!Style()->BorderObscuresBackground())
return false;
// Background shows in padding area.
if ((background_clip == EFillBox::kBorder ||
background_clip == EFillBox::kPadding) &&
Style()->HasPadding())
return false;
// Object-position may leave parts of the content box empty, regardless of the
// value of object-fit.
if (Style()->ObjectPosition() !=
ComputedStyleInitialValues::InitialObjectPosition())
return false;
// Object-fit may leave parts of the content box empty.
EObjectFit object_fit = Style()->GetObjectFit();
if (object_fit != EObjectFit::kFill && object_fit != EObjectFit::kCover)
return false;
// Check for image with alpha.
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
"data", InspectorPaintImageEvent::Data(this, *image_content));
return image_content->GetImage()->CurrentFrameKnownToBeOpaque(
Image::kPreCacheMetadata);
}
bool LayoutImage::ComputeBackgroundIsKnownToBeObscured() const {
if (!StyleRef().HasBackground())
return false;
LayoutRect painted_extent;
if (!GetBackgroundPaintedExtent(painted_extent))
return false;
return ForegroundIsKnownToBeOpaqueInRect(painted_extent, 0);
}
LayoutUnit LayoutImage::MinimumReplacedHeight() const {
return image_resource_->ErrorOccurred() ? IntrinsicSize().Height()
: LayoutUnit();
}
HTMLMapElement* LayoutImage::ImageMap() const {
HTMLImageElement* i = ToHTMLImageElementOrNull(GetNode());
return i ? i->GetTreeScope().GetImageMap(i->FastGetAttribute(usemapAttr))
: nullptr;
}
bool LayoutImage::NodeAtPoint(HitTestResult& result,
const HitTestLocation& location_in_container,
const LayoutPoint& accumulated_offset,
HitTestAction hit_test_action) {
HitTestResult temp_result(result.GetHitTestRequest(),
result.GetHitTestLocation());
bool inside = LayoutReplaced::NodeAtPoint(
temp_result, location_in_container, accumulated_offset, hit_test_action);
if (!inside && result.GetHitTestRequest().ListBased())
result.Append(temp_result);
if (inside)
result = temp_result;
return inside;
}
void LayoutImage::ComputeIntrinsicSizingInfo(
IntrinsicSizingInfo& intrinsic_sizing_info) const {
LayoutReplaced::ComputeIntrinsicSizingInfo(intrinsic_sizing_info);
// Our intrinsicSize is empty if we're laying out generated images with
// relative width/height. Figure out the right intrinsic size to use.
if (intrinsic_sizing_info.size.IsEmpty() &&
image_resource_->ImageHasRelativeSize()) {
LayoutObject* containing_block =
IsOutOfFlowPositioned() ? Container() : ContainingBlock();
if (containing_block->IsBox()) {
LayoutBox* box = ToLayoutBox(containing_block);
intrinsic_sizing_info.size.SetWidth(
box->AvailableLogicalWidth().ToFloat());
intrinsic_sizing_info.size.SetHeight(
box->AvailableLogicalHeight(kIncludeMarginBorderPadding).ToFloat());
}
}
// 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() && !IsVideo()) {
intrinsic_sizing_info.aspect_ratio = FloatSize(1, 1);
return;
}
}
bool LayoutImage::NeedsPreferredWidthsRecalculation() const {
if (LayoutReplaced::NeedsPreferredWidthsRecalculation())
return true;
return EmbeddedReplacedContent();
}
bool LayoutImage::GetNestedIntrinsicSizingInfo(
IntrinsicSizingInfo& intrinsic_sizing_info) const {
if (LayoutReplaced* content_layout_object = EmbeddedReplacedContent()) {
content_layout_object->ComputeIntrinsicSizingInfo(intrinsic_sizing_info);
return true;
}
return false;
}
LayoutReplaced* LayoutImage::EmbeddedReplacedContent() const {
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;
Image* image = cached_image->GetImage();
if (!image->IsSVGImage())
return nullptr;
return ToSVGImage(image)->EmbeddedReplacedContent();
}
} // namespace blink