blob: 66ac938034677f7c9c882fd2892b009da83f952d [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include <memory>
#include "cc/layers/picture_layer.h"
#include "third_party/blink/renderer/core/animation/element_animations.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/geometry/transform_state.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_object.h"
#include "third_party/blink/renderer/core/layout/layout_html_canvas.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_shift_tracker.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/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
#include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
#include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h"
#include "third_party/blink/renderer/core/paint/clip_path_clipper.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/css_mask_painter.h"
#include "third_party/blink/renderer/core/paint/frame_paint_timing.h"
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h"
#include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/graphics/compositor_filter_operations.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
static PhysicalRect ContentsRect(const LayoutObject& layout_object) {
if (!layout_object.IsBox())
return PhysicalRect();
if (auto* replaced = DynamicTo<LayoutReplaced>(layout_object))
return replaced->ReplacedContentRect();
return To<LayoutBox>(layout_object).PhysicalContentBoxRect();
}
static inline bool IsTextureLayerCanvas(const LayoutObject& layout_object) {
if (layout_object.IsCanvas()) {
auto* canvas = To<HTMLCanvasElement>(layout_object.GetNode());
if (canvas->SurfaceLayerBridge())
return false;
if (CanvasRenderingContext* context = canvas->RenderingContext())
return context->IsComposited();
}
return false;
}
static bool HasBoxDecorationsOrBackgroundImage(const ComputedStyle& style) {
return style.HasBoxDecorations() || style.HasBackgroundImage();
}
static WebPluginContainerImpl* GetPluginContainer(LayoutObject& layout_object) {
if (auto* embedded_object = DynamicTo<LayoutEmbeddedObject>(layout_object))
return embedded_object->Plugin();
return nullptr;
}
// Returns true if the compositor will be responsible for applying the sticky
// position offset for this composited layer.
static bool UsesCompositedStickyPosition(PaintLayer& layer) {
return layer.GetLayoutObject().StyleRef().HasStickyConstrainedPosition() &&
layer.AncestorScrollContainerLayer()->NeedsCompositedScrolling();
}
// Returns the sticky position offset that should be removed from a given layer
// for use in CompositedLayerMapping.
//
// If the layer is not using composited sticky position, this will return
// FloatPoint().
static FloatPoint StickyPositionOffsetForLayer(PaintLayer& layer) {
if (!UsesCompositedStickyPosition(layer))
return FloatPoint();
const StickyConstraintsMap& constraints_map =
layer.AncestorScrollContainerLayer()
->GetScrollableArea()
->GetStickyConstraintsMap();
const StickyPositionScrollingConstraints& constraints =
constraints_map.at(&layer);
return FloatPoint(constraints.GetOffsetForStickyPosition(constraints_map));
}
static bool NeedsDecorationOutlineLayer(const PaintLayer& paint_layer,
const LayoutObject& layout_object) {
const int min_border_width = std::min(
layout_object.StyleRef().BorderTopWidth(),
std::min(layout_object.StyleRef().BorderLeftWidth(),
std::min(layout_object.StyleRef().BorderRightWidth(),
layout_object.StyleRef().BorderBottomWidth())));
bool could_obscure_decorations =
(paint_layer.GetScrollableArea() &&
paint_layer.GetScrollableArea()->UsesCompositedScrolling()) ||
layout_object.IsCanvas() || IsA<LayoutVideo>(layout_object);
// Unlike normal outlines (whole width is outside of the offset), focus
// rings can be drawn with the center of the path aligned with the offset, so
// only 2/3 of the width is outside of the offset.
const int outline_drawn_inside =
layout_object.StyleRef().OutlineStyleIsAuto()
? std::ceil(
layout_object.StyleRef().GetOutlineStrokeWidthForFocusRing() /
3.f) +
1
: 0;
return could_obscure_decorations && layout_object.StyleRef().HasOutline() &&
(layout_object.StyleRef().OutlineOffsetInt() - outline_drawn_inside) <
-min_border_width;
}
CompositedLayerMapping::CompositedLayerMapping(PaintLayer& layer)
: owning_layer_(layer), pending_update_scope_(kGraphicsLayerUpdateNone) {
CreatePrimaryGraphicsLayer();
}
CompositedLayerMapping::~CompositedLayerMapping() {
RemoveSquashedLayers(non_scrolling_squashed_layers_);
RemoveSquashedLayers(squashed_layers_in_scrolling_contents_);
UpdateOverflowControlsLayers(false, false, false);
UpdateForegroundLayer(false);
UpdateMaskLayer(false);
UpdateScrollingContentsLayer(false);
UpdateSquashingLayers(false);
}
void CompositedLayerMapping::RemoveSquashedLayers(
Vector<GraphicsLayerPaintInfo>& squashed_layers) {
// Do not leave the destroyed pointer dangling on any Layers that painted to
// this mapping's squashing layer.
for (auto& squashed_layer : squashed_layers) {
PaintLayer* old_squashed_layer = squashed_layer.paint_layer;
// Assert on incorrect mappings between layers and groups
DCHECK_EQ(old_squashed_layer->GroupedMapping(), this);
if (old_squashed_layer->GroupedMapping() == this) {
old_squashed_layer->SetGroupedMapping(
nullptr, PaintLayer::kDoNotInvalidateLayerAndRemoveFromMapping);
old_squashed_layer->SetLostGroupedMapping(true);
}
}
}
std::unique_ptr<GraphicsLayer> CompositedLayerMapping::CreateGraphicsLayer(
CompositingReasons reasons,
SquashingDisallowedReasons squashing_disallowed_reasons) {
auto graphics_layer = std::make_unique<GraphicsLayer>(*this);
graphics_layer->SetCompositingReasons(reasons);
graphics_layer->SetSquashingDisallowedReasons(squashing_disallowed_reasons);
if (Node* owning_node = owning_layer_.GetLayoutObject().GetNode()) {
graphics_layer->SetOwnerNodeId(
static_cast<int>(DOMNodeIds::IdForNode(owning_node)));
}
return graphics_layer;
}
void CompositedLayerMapping::CreatePrimaryGraphicsLayer() {
graphics_layer_ =
CreateGraphicsLayer(owning_layer_.GetCompositingReasons(),
owning_layer_.GetSquashingDisallowedReasons());
graphics_layer_->SetHitTestable(true);
}
void CompositedLayerMapping::UpdateGraphicsLayerContentsOpaque(
bool should_check_children) {
if (BackgroundPaintsOntoGraphicsLayer()) {
bool contents_opaque = owning_layer_.BackgroundIsKnownToBeOpaqueInRect(
CompositedBounds(), should_check_children);
graphics_layer_->CcLayer().SetContentsOpaque(contents_opaque);
if (!contents_opaque) {
graphics_layer_->CcLayer().SetContentsOpaqueForText(
GetLayoutObject().TextIsKnownToBeOnOpaqueBackground());
}
} else {
// If we only paint the background onto the scrolling contents layer we
// are going to leave a hole in the m_graphicsLayer where the background
// is so it is not opaque.
graphics_layer_->CcLayer().SetContentsOpaque(false);
}
}
void CompositedLayerMapping::UpdateContentsOpaque() {
// If there is a foreground layer, children paint into that layer and
// not graphics_layer_, and so don't contribute to the opaqueness of the
// latter.
bool should_check_children = !foreground_layer_.get();
if (BackgroundPaintsOntoScrollingContentsLayer()) {
DCHECK(scrolling_contents_layer_);
// Backgrounds painted onto the foreground are clipped by the padding box
// rect.
// TODO(flackr): This should actually check the entire overflow rect
// within the scrolling contents layer but since we currently only trigger
// this for solid color backgrounds the answer will be the same.
bool contents_opaque = owning_layer_.BackgroundIsKnownToBeOpaqueInRect(
To<LayoutBox>(GetLayoutObject()).PhysicalPaddingBoxRect(),
should_check_children);
scrolling_contents_layer_->CcLayer().SetContentsOpaque(contents_opaque);
if (!contents_opaque) {
scrolling_contents_layer_->CcLayer().SetContentsOpaqueForText(
GetLayoutObject().TextIsKnownToBeOnOpaqueBackground());
}
UpdateGraphicsLayerContentsOpaque(should_check_children);
} else {
DCHECK(BackgroundPaintsOntoGraphicsLayer());
if (scrolling_contents_layer_)
scrolling_contents_layer_->CcLayer().SetContentsOpaque(false);
UpdateGraphicsLayerContentsOpaque(should_check_children);
}
if (non_scrolling_squashing_layer_) {
non_scrolling_squashing_layer_->CcLayer().SetContentsOpaque(false);
bool contents_opaque_for_text = true;
for (const GraphicsLayerPaintInfo& squashed_layer :
non_scrolling_squashed_layers_) {
if (!squashed_layer.paint_layer->GetLayoutObject()
.TextIsKnownToBeOnOpaqueBackground()) {
contents_opaque_for_text = false;
break;
}
}
non_scrolling_squashing_layer_->CcLayer().SetContentsOpaqueForText(
contents_opaque_for_text);
}
}
void CompositedLayerMapping::UpdateCompositedBounds() {
DCHECK_EQ(owning_layer_.Compositor()->Lifecycle().GetState(),
DocumentLifecycle::kInCompositingAssignmentsUpdate);
// FIXME: if this is really needed for performance, it would be better to
// store it on Layer.
composited_bounds_ = owning_layer_.BoundingBoxForCompositing();
}
void CompositedLayerMapping::UpdateCompositingReasons() {
// All other layers owned by this mapping will have the same compositing
// reason for their lifetime, so they are initialized only when created.
graphics_layer_->SetCompositingReasons(owning_layer_.GetCompositingReasons());
graphics_layer_->SetSquashingDisallowedReasons(
owning_layer_.GetSquashingDisallowedReasons());
}
bool CompositedLayerMapping::UpdateGraphicsLayerConfiguration(
const PaintLayer* compositing_container) {
DCHECK_EQ(owning_layer_.Compositor()->Lifecycle().GetState(),
DocumentLifecycle::kInCompositingAssignmentsUpdate);
// Note carefully: here we assume that the compositing state of all
// descendants have been updated already, so it is legitimate to compute and
// cache the composited bounds for this layer.
UpdateCompositedBounds();
PaintLayerCompositor* compositor = Compositor();
LayoutObject& layout_object = GetLayoutObject();
const ComputedStyle& style = layout_object.StyleRef();
bool layer_config_changed = false;
if (UpdateForegroundLayer(
compositor->NeedsContentsCompositingLayer(&owning_layer_)))
layer_config_changed = true;
if (UpdateScrollingContentsLayer(owning_layer_.NeedsCompositedScrolling()))
layer_config_changed = true;
// If the outline needs to draw over the composited scrolling contents layer
// or scrollbar layers (or video or webgl) it needs to be drawn into a
// separate layer.
bool needs_decoration_outline_layer =
NeedsDecorationOutlineLayer(owning_layer_, layout_object);
if (UpdateDecorationOutlineLayer(needs_decoration_outline_layer))
layer_config_changed = true;
if (UpdateOverflowControlsLayers(RequiresHorizontalScrollbarLayer(),
RequiresVerticalScrollbarLayer(),
RequiresScrollCornerLayer()))
layer_config_changed = true;
if (UpdateSquashingLayers(!non_scrolling_squashed_layers_.IsEmpty()))
layer_config_changed = true;
bool has_mask =
CSSMaskPainter::MaskBoundingBox(GetLayoutObject(), PhysicalOffset())
.has_value();
bool has_mask_based_clip_path =
ClipPathClipper::ShouldUseMaskBasedClip(GetLayoutObject());
if (UpdateMaskLayer(has_mask || has_mask_based_clip_path))
layer_config_changed = true;
if (layer_config_changed)
UpdateInternalHierarchy();
if (layout_object.IsLayoutEmbeddedContent()) {
if (WebPluginContainerImpl* plugin = GetPluginContainer(layout_object)) {
graphics_layer_->SetContentsToCcLayer(plugin->CcLayer());
} else if (auto* frame_owner =
DynamicTo<HTMLFrameOwnerElement>(layout_object.GetNode())) {
if (auto* remote = DynamicTo<RemoteFrame>(frame_owner->ContentFrame())) {
graphics_layer_->SetContentsToCcLayer(remote->GetCcLayer());
}
}
} else if (IsA<LayoutVideo>(layout_object)) {
auto* media_element = To<HTMLMediaElement>(layout_object.GetNode());
graphics_layer_->SetContentsToCcLayer(media_element->CcLayer());
} else if (layout_object.IsCanvas()) {
graphics_layer_->SetContentsToCcLayer(
To<HTMLCanvasElement>(layout_object.GetNode())->ContentsCcLayer());
layer_config_changed = true;
}
if (layer_config_changed) {
// Changes to either the internal hierarchy or the mask layer have an impact
// on painting phases, so we need to update when either are updated.
UpdatePaintingPhases();
}
UpdateElementId();
if (style.Preserves3D() && style.HasOpacity() &&
owning_layer_.Has3DTransformedDescendant()) {
UseCounter::Count(layout_object.GetDocument(),
WebFeature::kOpacityWithPreserve3DQuirk);
}
return layer_config_changed;
}
static PhysicalOffset ComputeOffsetFromCompositedAncestor(
const PaintLayer* layer,
const PaintLayer* composited_ancestor,
const PhysicalOffset& local_representative_point_for_fragmentation,
const FloatPoint& offset_for_sticky_position) {
// Add in the offset of the composited bounds from the coordinate space of
// the PaintLayer, since visualOffsetFromAncestor() requires the pre-offset
// input to be in the space of the PaintLayer. We also need to add in this
// offset before computation of visualOffsetFromAncestor(), because it affects
// fragmentation offset if compositedAncestor crosses a pagination boundary.
//
// Currently, visual fragmentation for composited layers is not implemented.
// For fragmented contents, we paint in the logical coordinates of the flow
// thread, then split the result by fragment boundary and paste each part
// into each fragment's physical position.
// Since composited layers don't support visual fragmentation, we have to
// choose a "representative" fragment to position the painted contents. This
// is where localRepresentativePointForFragmentation comes into play.
// The fragment that the representative point resides in will be chosen as
// the representative fragment for layer position purpose.
// For layers that are not fragmented, the point doesn't affect behavior as
// there is one and only one fragment.
PhysicalOffset offset = layer->VisualOffsetFromAncestor(
composited_ancestor, local_representative_point_for_fragmentation);
if (composited_ancestor)
offset += composited_ancestor->SubpixelAccumulation();
offset -= local_representative_point_for_fragmentation;
offset -= PhysicalOffset::FromFloatPointRound(offset_for_sticky_position);
return offset;
}
PhysicalOffset ComputeSubpixelAccumulation(
const PhysicalOffset& offset_from_composited_ancestor,
const PaintLayer& layer,
IntPoint& snapped_offset_from_composited_ancestor) {
snapped_offset_from_composited_ancestor =
RoundedIntPoint(offset_from_composited_ancestor);
PhysicalOffset subpixel_accumulation =
offset_from_composited_ancestor -
PhysicalOffset(snapped_offset_from_composited_ancestor);
if (subpixel_accumulation.IsZero()) {
return subpixel_accumulation;
}
if (layer.GetCompositingReasons() &
CompositingReason::kPreventingSubpixelAccumulationReasons) {
return PhysicalOffset();
}
if (layer.GetCompositingReasons() &
CompositingReason::kActiveTransformAnimation) {
if (const Element* element =
To<Element>(layer.GetLayoutObject().GetNode())) {
DCHECK(element->GetElementAnimations());
if (element->GetElementAnimations()->IsIdentityOrTranslation())
return subpixel_accumulation;
}
return PhysicalOffset();
}
if (!layer.Transform() || layer.Transform()->IsIdentityOrTranslation()) {
return subpixel_accumulation;
}
return PhysicalOffset();
}
void CompositedLayerMapping::ComputeBoundsOfOwningLayer(
const PaintLayer* composited_ancestor,
IntRect& local_bounds,
IntPoint& snapped_offset_from_composited_ancestor) {
// HACK(chrishtr): adjust for position of inlines.
PhysicalOffset local_representative_point_for_fragmentation;
if (owning_layer_.GetLayoutObject().IsLayoutInline()) {
local_representative_point_for_fragmentation =
To<LayoutInline>(owning_layer_.GetLayoutObject()).FirstLineBoxTopLeft();
}
// Blink will already have applied any necessary offset for sticky positioned
// elements. If the compositor is handling sticky offsets for this layer, we
// need to remove the Blink-side offset to avoid double-counting.
FloatPoint offset_for_sticky_position =
StickyPositionOffsetForLayer(owning_layer_);
PhysicalOffset offset_from_composited_ancestor =
ComputeOffsetFromCompositedAncestor(
&owning_layer_, composited_ancestor,
local_representative_point_for_fragmentation,
offset_for_sticky_position);
PhysicalOffset subpixel_accumulation = ComputeSubpixelAccumulation(
offset_from_composited_ancestor, owning_layer_,
snapped_offset_from_composited_ancestor);
// Invalidate the whole layer when subpixel accumulation changes, since
// the previous subpixel accumulation is baked into the display list.
// However, don't do so for directly composited layers, to avoid impacting
// performance.
if (subpixel_accumulation != owning_layer_.SubpixelAccumulation()) {
// Always invalidate if under-invalidation checking is on, to avoid
// false positives.
if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() ||
!(owning_layer_.GetCompositingReasons() &
CompositingReason::kComboAllDirectReasons))
GetLayoutObject().SetShouldCheckForPaintInvalidation();
}
// Otherwise discard the sub-pixel remainder because paint offset can't be
// transformed by a non-translation transform.
owning_layer_.SetSubpixelAccumulation(subpixel_accumulation);
base::Optional<IntRect> mask_bounding_box =
CSSMaskPainter::MaskBoundingBox(GetLayoutObject(), subpixel_accumulation);
base::Optional<FloatRect> clip_path_bounding_box =
ClipPathClipper::LocalClipPathBoundingBox(GetLayoutObject());
if (clip_path_bounding_box)
clip_path_bounding_box->MoveBy(FloatPoint(subpixel_accumulation));
// Override graphics layer size to the bound of mask layer, this is because
// the compositor implementation requires mask layer bound to match its
// host layer.
if (mask_bounding_box) {
local_bounds = *mask_bounding_box;
if (clip_path_bounding_box)
local_bounds.Intersect(EnclosingIntRect(*clip_path_bounding_box));
} else if (clip_path_bounding_box) {
local_bounds = EnclosingIntRect(*clip_path_bounding_box);
} else {
// Move the bounds by the subpixel accumulation so that it pixel-snaps
// relative to absolute pixels instead of local coordinates.
PhysicalRect local_raw_compositing_bounds = CompositedBounds();
local_raw_compositing_bounds.Move(subpixel_accumulation);
local_bounds = PixelSnappedIntRect(local_raw_compositing_bounds);
}
}
void CompositedLayerMapping::UpdateSquashingLayerGeometry(
const PaintLayer* compositing_container,
const IntPoint& snapped_offset_from_composited_ancestor,
Vector<GraphicsLayerPaintInfo>& layers,
Vector<PaintLayer*>& layers_needing_paint_invalidation) {
if (!non_scrolling_squashing_layer_)
return;
IntPoint graphics_layer_parent_location;
ComputeGraphicsLayerParentLocation(compositing_container,
graphics_layer_parent_location);
PhysicalOffset compositing_container_offset_from_parent_graphics_layer(
-graphics_layer_parent_location);
if (compositing_container) {
compositing_container_offset_from_parent_graphics_layer +=
compositing_container->SubpixelAccumulation();
}
const PaintLayer* common_transform_ancestor = nullptr;
if (compositing_container && compositing_container->Transform()) {
common_transform_ancestor = compositing_container;
} else if (compositing_container) {
common_transform_ancestor =
&compositing_container->TransformAncestorOrRoot();
} else {
common_transform_ancestor = owning_layer_.Root();
}
// FIXME: Cache these offsets.
PhysicalOffset compositing_container_offset_from_transformed_ancestor;
if (compositing_container) {
compositing_container_offset_from_transformed_ancestor =
compositing_container->ComputeOffsetFromAncestor(
*common_transform_ancestor);
}
PhysicalRect total_squash_bounds;
for (wtf_size_t i = 0; i < layers.size(); ++i) {
PhysicalRect squashed_bounds =
layers[i].paint_layer->BoundingBoxForCompositing();
// Store the local bounds of the Layer subtree before applying the offset.
layers[i].composited_bounds = squashed_bounds;
PhysicalOffset squashed_layer_offset_from_transformed_ancestor =
layers[i].paint_layer->ComputeOffsetFromAncestor(
*common_transform_ancestor);
PhysicalOffset squashed_layer_offset_from_compositing_container =
squashed_layer_offset_from_transformed_ancestor -
compositing_container_offset_from_transformed_ancestor;
squashed_bounds.Move(squashed_layer_offset_from_compositing_container);
total_squash_bounds.Unite(squashed_bounds);
}
// The totalSquashBounds is positioned with respect to compositingContainer.
// But the squashingLayer needs to be positioned with respect to the
// graphicsLayerParent. The conversion between compositingContainer and the
// graphicsLayerParent is already computed as
// compositingContainerOffsetFromParentGraphicsLayer.
total_squash_bounds.Move(
compositing_container_offset_from_parent_graphics_layer);
const IntRect squash_layer_bounds = EnclosingIntRect(total_squash_bounds);
const IntPoint squash_layer_origin = squash_layer_bounds.Location();
const PhysicalOffset squash_layer_origin_in_compositing_container_space =
PhysicalOffset(squash_layer_origin) -
compositing_container_offset_from_parent_graphics_layer;
// Now that the squashing bounds are known, we can convert the PaintLayer
// painting offsets from compositingContainer space to the squashing layer
// space.
//
// The painting offset we want to compute for each squashed PaintLayer is
// essentially the position of the squashed PaintLayer described w.r.t.
// compositingContainer's origin. So we just need to convert that point from
// compositingContainer space to the squashing layer's space. This is done by
// subtracting squashLayerOriginInCompositingContainerSpace, but then the
// offset overall needs to be negated because that's the direction that the
// painting code expects the offset to be.
for (wtf_size_t i = 0; i < layers.size(); ++i) {
const PhysicalOffset squashed_layer_offset_from_transformed_ancestor =
layers[i].paint_layer->ComputeOffsetFromAncestor(
*common_transform_ancestor);
const PhysicalOffset offset_from_squash_layer_origin =
(squashed_layer_offset_from_transformed_ancestor -
compositing_container_offset_from_transformed_ancestor) -
squash_layer_origin_in_compositing_container_space;
IntSize new_offset_from_layout_object =
-ToIntSize(RoundedIntPoint(offset_from_squash_layer_origin));
PhysicalOffset subpixel_accumulation =
offset_from_squash_layer_origin +
PhysicalOffset(new_offset_from_layout_object);
if (layers[i].offset_from_layout_object_set &&
layers[i].offset_from_layout_object != new_offset_from_layout_object) {
layers_needing_paint_invalidation.push_back(layers[i].paint_layer);
}
layers[i].offset_from_layout_object = new_offset_from_layout_object;
layers[i].offset_from_layout_object_set = true;
layers[i].paint_layer->SetSubpixelAccumulation(subpixel_accumulation);
}
non_scrolling_squashing_layer_->SetSize(
gfx::Size(squash_layer_bounds.Size()));
// We can't non_scrolling_squashing_layer_->SetOffsetFromLayoutObject().
// Squashing layer has special paint and invalidation logic that already
// compensated for compositing bounds, setting it here would end up
// double adjustment.
auto new_offset = squash_layer_bounds.Location() -
snapped_offset_from_composited_ancestor +
ToIntSize(graphics_layer_parent_location);
if (new_offset != non_scrolling_squashing_layer_offset_from_layout_object_) {
non_scrolling_squashing_layer_offset_from_layout_object_ = new_offset;
// Need to update squashing LayerState according to the new offset.
// GraphicsLayerUpdater does this.
layers_needing_paint_invalidation.push_back(&owning_layer_);
}
for (auto& layer : layers)
UpdateLocalClipRectForSquashedLayer(owning_layer_, layers, layer);
}
void CompositedLayerMapping::UpdateGraphicsLayerGeometry(
const PaintLayer* compositing_container,
Vector<PaintLayer*>& layers_needing_paint_invalidation) {
DCHECK_EQ(owning_layer_.Compositor()->Lifecycle().GetState(),
DocumentLifecycle::kInCompositingAssignmentsUpdate);
IntRect local_compositing_bounds;
IntPoint snapped_offset_from_composited_ancestor;
ComputeBoundsOfOwningLayer(compositing_container, local_compositing_bounds,
snapped_offset_from_composited_ancestor);
UpdateMainGraphicsLayerGeometry(local_compositing_bounds);
UpdateSquashingLayerGeometry(
compositing_container, snapped_offset_from_composited_ancestor,
non_scrolling_squashed_layers_, layers_needing_paint_invalidation);
UpdateMaskLayerGeometry();
UpdateDecorationOutlineLayerGeometry(local_compositing_bounds.Size());
UpdateScrollingContentsLayerGeometry(layers_needing_paint_invalidation);
UpdateForegroundLayerGeometry();
if (owning_layer_.GetScrollableArea() &&
owning_layer_.GetScrollableArea()->ScrollsOverflow())
owning_layer_.GetScrollableArea()->PositionOverflowControls();
UpdateContentsRect();
UpdateDrawsContentAndPaintsHitTest();
UpdateElementId();
UpdateContentsOpaque();
UpdateCompositingReasons();
}
void CompositedLayerMapping::UpdateMainGraphicsLayerGeometry(
const IntRect& local_compositing_bounds) {
graphics_layer_->SetOffsetFromLayoutObject(
ToIntSize(local_compositing_bounds.Location()));
graphics_layer_->SetSize(gfx::Size(local_compositing_bounds.Size()));
// m_graphicsLayer is the corresponding GraphicsLayer for this PaintLayer and
// its non-compositing descendants. So, the visibility flag for
// m_graphicsLayer should be true if there are any non-compositing visible
// layers.
bool contents_visible = owning_layer_.HasVisibleContent() ||
HasVisibleNonCompositingDescendant(&owning_layer_);
// TODO(sunxd): Investigate and possibly implement computing hit test regions
// in PaintTouchActionRects code path, so that cc has correct pointer-events
// information.
// For now, there is no need to set graphics_layer_'s hit testable bit here,
// because it is always hit testable from cc's perspective.
graphics_layer_->SetContentsVisible(contents_visible);
}
void CompositedLayerMapping::ComputeGraphicsLayerParentLocation(
const PaintLayer* compositing_container,
IntPoint& graphics_layer_parent_location) {
if (compositing_container) {
graphics_layer_parent_location =
IntPoint(compositing_container->GetCompositedLayerMapping()
->ParentForSublayers()
->OffsetFromLayoutObject());
} else if (!GetLayoutObject().GetFrame()->IsLocalRoot()) { // TODO(oopif)
DCHECK(!compositing_container);
graphics_layer_parent_location = IntPoint();
}
if (compositing_container &&
compositing_container->NeedsCompositedScrolling()) {
auto& layout_box = To<LayoutBox>(compositing_container->GetLayoutObject());
IntSize scroll_offset =
FlooredIntSize(layout_box.PixelSnappedScrolledContentOffset());
IntPoint scroll_origin =
compositing_container->GetScrollableArea()->ScrollOrigin();
scroll_origin.Move(-layout_box.OriginAdjustmentForScrollbars());
scroll_origin.Move(-layout_box.BorderLeft().ToInt(),
-layout_box.BorderTop().ToInt());
graphics_layer_parent_location = -(scroll_origin + scroll_offset);
}
}
void CompositedLayerMapping::UpdateMaskLayerGeometry() {
if (!mask_layer_)
return;
mask_layer_->SetSize(graphics_layer_->Size());
mask_layer_->SetOffsetFromLayoutObject(
graphics_layer_->OffsetFromLayoutObject());
}
void CompositedLayerMapping::UpdateScrollingContentsLayerGeometry(
Vector<PaintLayer*>& layers_needing_paint_invalidation) {
if (!scrolling_contents_layer_) {
DCHECK(squashed_layers_in_scrolling_contents_.IsEmpty());
return;
}
DCHECK(scrolling_contents_layer_);
auto& layout_box = To<LayoutBox>(GetLayoutObject());
IntRect overflow_clip_rect = PixelSnappedIntRect(
layout_box.OverflowClipRect(owning_layer_.SubpixelAccumulation()));
bool scroll_container_size_changed =
previous_scroll_container_size_ != overflow_clip_rect.Size();
if (scroll_container_size_changed)
previous_scroll_container_size_ = overflow_clip_rect.Size();
PaintLayerScrollableArea* scrollable_area = owning_layer_.GetScrollableArea();
IntSize scroll_size = scrollable_area->PixelSnappedContentsSize(
owning_layer_.SubpixelAccumulation());
// Ensure scrolling contents are at least as large as the scroll clip
scroll_size = scroll_size.ExpandedTo(overflow_clip_rect.Size());
auto* scrolling_coordinator = owning_layer_.GetScrollingCoordinator();
scrolling_coordinator->UpdateCompositorScrollOffset(*layout_box.GetFrame(),
*scrollable_area);
if (gfx::Size(scroll_size) != scrolling_contents_layer_->Size() ||
scroll_container_size_changed) {
scrolling_coordinator->ScrollableAreaScrollLayerDidChange(scrollable_area);
}
scrolling_contents_layer_->SetSize(gfx::Size(scroll_size));
scrolling_contents_layer_->SetOffsetFromLayoutObject(
overflow_clip_rect.Location() - scrollable_area->ScrollOrigin());
for (auto& layer : squashed_layers_in_scrolling_contents_) {
layer.composited_bounds = layer.paint_layer->BoundingBoxForCompositing();
PhysicalOffset offset_from_scrolling_contents_layer =
layer.paint_layer->ComputeOffsetFromAncestor(owning_layer_) +
owning_layer_.SubpixelAccumulation() -
PhysicalOffset(scrolling_contents_layer_->OffsetFromLayoutObject());
IntSize new_offset_from_layout_object =
-ToIntSize(RoundedIntPoint(offset_from_scrolling_contents_layer));
PhysicalOffset subpixel_accumulation =
offset_from_scrolling_contents_layer +
PhysicalOffset(new_offset_from_layout_object);
if (layer.offset_from_layout_object_set &&
layer.offset_from_layout_object != new_offset_from_layout_object) {
layers_needing_paint_invalidation.push_back(layer.paint_layer);
}
layer.offset_from_layout_object = new_offset_from_layout_object;
layer.offset_from_layout_object_set = true;
layer.paint_layer->SetSubpixelAccumulation(subpixel_accumulation);
}
for (auto& layer : squashed_layers_in_scrolling_contents_) {
UpdateLocalClipRectForSquashedLayer(
owning_layer_, squashed_layers_in_scrolling_contents_, layer);
}
}
bool CompositedLayerMapping::RequiresHorizontalScrollbarLayer() const {
return owning_layer_.GetScrollableArea() &&
owning_layer_.GetScrollableArea()->HorizontalScrollbar();
}
bool CompositedLayerMapping::RequiresVerticalScrollbarLayer() const {
return owning_layer_.GetScrollableArea() &&
owning_layer_.GetScrollableArea()->VerticalScrollbar();
}
bool CompositedLayerMapping::RequiresScrollCornerLayer() const {
return owning_layer_.GetScrollableArea() &&
!owning_layer_.GetScrollableArea()
->ScrollCornerAndResizerRect()
.IsEmpty();
}
void CompositedLayerMapping::UpdateForegroundLayerGeometry() {
if (!foreground_layer_)
return;
// Should be equivalent to local_compositing_bounds.
IntRect compositing_bounds(
IntPoint(graphics_layer_->OffsetFromLayoutObject()),
IntSize(graphics_layer_->Size()));
if (scrolling_contents_layer_) {
// Override compositing bounds to include full overflow if composited
// scrolling is used.
compositing_bounds =
IntRect(IntPoint(scrolling_contents_layer_->OffsetFromLayoutObject()),
IntSize(scrolling_contents_layer_->Size()));
}
foreground_layer_->SetOffsetFromLayoutObject(
ToIntSize(compositing_bounds.Location()));
foreground_layer_->SetSize(gfx::Size(compositing_bounds.Size()));
}
void CompositedLayerMapping::UpdateDecorationOutlineLayerGeometry(
const IntSize& relative_compositing_bounds_size) {
if (!decoration_outline_layer_)
return;
decoration_outline_layer_->SetSize(
gfx::Size(relative_compositing_bounds_size));
decoration_outline_layer_->SetOffsetFromLayoutObject(
graphics_layer_->OffsetFromLayoutObject());
}
void CompositedLayerMapping::UpdateInternalHierarchy() {
// foreground_layer_ has to be inserted in the correct order with child
// layers in SetSubLayers(), so it's not inserted here.
graphics_layer_->RemoveFromParent();
bool overflow_controls_after_scrolling_contents =
owning_layer_.IsRootLayer() ||
(owning_layer_.GetScrollableArea() &&
owning_layer_.GetScrollableArea()->HasOverlayOverflowControls());
if (overflow_controls_after_scrolling_contents && scrolling_contents_layer_)
graphics_layer_->AddChild(scrolling_contents_layer_.get());
if (layer_for_horizontal_scrollbar_)
graphics_layer_->AddChild(layer_for_horizontal_scrollbar_.get());
if (layer_for_vertical_scrollbar_)
graphics_layer_->AddChild(layer_for_vertical_scrollbar_.get());
if (layer_for_scroll_corner_)
graphics_layer_->AddChild(layer_for_scroll_corner_.get());
if (!overflow_controls_after_scrolling_contents && scrolling_contents_layer_)
graphics_layer_->AddChild(scrolling_contents_layer_.get());
if (decoration_outline_layer_)
graphics_layer_->AddChild(decoration_outline_layer_.get());
if (mask_layer_)
graphics_layer_->AddChild(mask_layer_.get());
if (non_scrolling_squashing_layer_)
graphics_layer_->AddChild(non_scrolling_squashing_layer_.get());
}
void CompositedLayerMapping::UpdatePaintingPhases() {
graphics_layer_->SetPaintingPhase(PaintingPhaseForPrimaryLayer());
if (scrolling_contents_layer_) {
GraphicsLayerPaintingPhase paint_phase =
kGraphicsLayerPaintOverflowContents |
kGraphicsLayerPaintCompositedScroll;
if (!foreground_layer_)
paint_phase |= kGraphicsLayerPaintForeground;
scrolling_contents_layer_->SetPaintingPhase(paint_phase);
}
if (foreground_layer_) {
GraphicsLayerPaintingPhase paint_phase = kGraphicsLayerPaintForeground;
if (scrolling_contents_layer_)
paint_phase |= kGraphicsLayerPaintOverflowContents;
foreground_layer_->SetPaintingPhase(paint_phase);
}
}
void CompositedLayerMapping::UpdateContentsRect() {
graphics_layer_->SetContentsRect(PixelSnappedIntRect(ContentsBox()));
}
void CompositedLayerMapping::UpdateDrawsContentAndPaintsHitTest() {
bool in_overlay_fullscreen_video = false;
if (IsA<LayoutVideo>(GetLayoutObject())) {
auto* video_element = To<HTMLVideoElement>(GetLayoutObject().GetNode());
if (video_element->IsFullscreen() &&
video_element->UsesOverlayFullscreenVideo())
in_overlay_fullscreen_video = true;
}
bool has_painted_content =
in_overlay_fullscreen_video ? false : ContainsPaintedContent();
graphics_layer_->SetDrawsContent(has_painted_content);
// |has_painted_content| is conservative (e.g., will be true if any descendant
// paints content, regardless of whether the descendant content is a hit test)
// but an exhaustive check of descendants that paint hit tests would be too
// expensive.
bool paints_hit_test = has_painted_content ||
GetLayoutObject().HasEffectiveAllowedTouchAction() ||
GetLayoutObject().InsideBlockingWheelEventHandler();
bool paints_scroll_hit_test =
((owning_layer_.GetScrollableArea() &&
owning_layer_.GetScrollableArea()->ScrollsOverflow()) ||
(GetPluginContainer(GetLayoutObject()) &&
GetPluginContainer(GetLayoutObject())->WantsWheelEvents()));
graphics_layer_->SetPaintsHitTest(paints_hit_test || paints_scroll_hit_test);
if (scrolling_contents_layer_) {
// scrolling_contents_layer_ only needs backing store if the scrolled
// contents need to paint.
bool has_painted_scrolling_contents =
!squashed_layers_in_scrolling_contents_.IsEmpty() ||
(owning_layer_.HasVisibleContent() &&
(GetLayoutObject().StyleRef().HasBackground() ||
GetLayoutObject().HasNonInitialBackdropFilter() || PaintsChildren()));
scrolling_contents_layer_->SetDrawsContent(has_painted_scrolling_contents);
scrolling_contents_layer_->SetPaintsHitTest(paints_hit_test);
}
if (has_painted_content && GetLayoutObject().IsCanvas() &&
To<LayoutHTMLCanvas>(GetLayoutObject())
.DrawsBackgroundOntoContentLayer()) {
has_painted_content = false;
}
// FIXME: we could refine this to only allocate backings for one of these
// layers if possible.
if (foreground_layer_) {
foreground_layer_->SetDrawsContent(has_painted_content);
foreground_layer_->SetPaintsHitTest(paints_hit_test);
}
if (decoration_outline_layer_)
decoration_outline_layer_->SetDrawsContent(true);
if (mask_layer_)
mask_layer_->SetDrawsContent(true);
}
bool CompositedLayerMapping::ToggleScrollbarLayerIfNeeded(
std::unique_ptr<GraphicsLayer>& layer,
bool needs_layer,
CompositingReasons reason) {
if (needs_layer == !!layer)
return false;
layer = needs_layer ? CreateGraphicsLayer(reason) : nullptr;
if (PaintLayerScrollableArea* scrollable_area =
owning_layer_.GetScrollableArea()) {
if (ScrollingCoordinator* scrolling_coordinator =
owning_layer_.GetScrollingCoordinator()) {
if (reason == CompositingReason::kLayerForHorizontalScrollbar) {
scrolling_coordinator->ScrollableAreaScrollbarLayerDidChange(
scrollable_area, kHorizontalScrollbar);
} else if (reason == CompositingReason::kLayerForVerticalScrollbar) {
scrolling_coordinator->ScrollableAreaScrollbarLayerDidChange(
scrollable_area, kVerticalScrollbar);
}
}
}
return true;
}
bool CompositedLayerMapping::UpdateOverflowControlsLayers(
bool needs_horizontal_scrollbar_layer,
bool needs_vertical_scrollbar_layer,
bool needs_scroll_corner_layer) {
if (PaintLayerScrollableArea* scrollable_area =
owning_layer_.GetScrollableArea()) {
// If the scrollable area is marked as needing a new scrollbar layer,
// destroy the layer now so that it will be created again below.
if (layer_for_horizontal_scrollbar_ && needs_horizontal_scrollbar_layer &&
scrollable_area->ShouldRebuildHorizontalScrollbarLayer()) {
ToggleScrollbarLayerIfNeeded(
layer_for_horizontal_scrollbar_, false,
CompositingReason::kLayerForHorizontalScrollbar);
}
if (layer_for_vertical_scrollbar_ && needs_vertical_scrollbar_layer &&
scrollable_area->ShouldRebuildVerticalScrollbarLayer()) {
ToggleScrollbarLayerIfNeeded(
layer_for_vertical_scrollbar_, false,
CompositingReason::kLayerForVerticalScrollbar);
}
scrollable_area->ResetRebuildScrollbarLayerFlags();
if (scrolling_contents_layer_ &&
scrollable_area->NeedsShowScrollbarLayers()) {
scrolling_contents_layer_->CcLayer().ShowScrollbars();
scrollable_area->DidShowScrollbarLayers();
}
}
// If the subtree is invisible, we don't actually need scrollbar layers.
// Only do this check if at least one of the bits is currently true.
// This is important because this method is called during the destructor
// of CompositedLayerMapping, which may happen during style recalc,
// and therefore visible content status may be invalid.
if (needs_horizontal_scrollbar_layer || needs_vertical_scrollbar_layer ||
needs_scroll_corner_layer) {
bool invisible = owning_layer_.SubtreeIsInvisible();
needs_horizontal_scrollbar_layer &= !invisible;
needs_vertical_scrollbar_layer &= !invisible;
needs_scroll_corner_layer &= !invisible;
}
bool horizontal_scrollbar_layer_changed = ToggleScrollbarLayerIfNeeded(
layer_for_horizontal_scrollbar_, needs_horizontal_scrollbar_layer,
CompositingReason::kLayerForHorizontalScrollbar);
bool vertical_scrollbar_layer_changed = ToggleScrollbarLayerIfNeeded(
layer_for_vertical_scrollbar_, needs_vertical_scrollbar_layer,
CompositingReason::kLayerForVerticalScrollbar);
bool scroll_corner_layer_changed = ToggleScrollbarLayerIfNeeded(
layer_for_scroll_corner_, needs_scroll_corner_layer,
CompositingReason::kLayerForScrollCorner);
return horizontal_scrollbar_layer_changed ||
vertical_scrollbar_layer_changed || scroll_corner_layer_changed;
}
void CompositedLayerMapping::PositionOverflowControlsLayers() {
if (GraphicsLayer* layer = LayerForHorizontalScrollbar()) {
Scrollbar* h_bar = owning_layer_.GetScrollableArea()->HorizontalScrollbar();
if (h_bar) {
IntRect frame_rect = h_bar->FrameRect();
layer->SetOffsetFromLayoutObject(ToIntSize(frame_rect.Location()));
layer->SetSize(gfx::Size(frame_rect.Size()));
if (layer->HasContentsLayer())
layer->SetContentsRect(IntRect(IntPoint(), frame_rect.Size()));
}
bool h_bar_visible = h_bar && !layer->HasContentsLayer();
layer->SetDrawsContent(h_bar_visible);
layer->SetHitTestable(h_bar_visible);
}
if (GraphicsLayer* layer = LayerForVerticalScrollbar()) {
Scrollbar* v_bar = owning_layer_.GetScrollableArea()->VerticalScrollbar();
if (v_bar) {
IntRect frame_rect = v_bar->FrameRect();
layer->SetOffsetFromLayoutObject(ToIntSize(frame_rect.Location()));
layer->SetSize(gfx::Size(frame_rect.Size()));
if (layer->HasContentsLayer())
layer->SetContentsRect(IntRect(IntPoint(), frame_rect.Size()));
}
bool v_bar_visible = v_bar && !layer->HasContentsLayer();
layer->SetDrawsContent(v_bar_visible);
layer->SetHitTestable(v_bar_visible);
}
if (GraphicsLayer* layer = LayerForScrollCorner()) {
const IntRect& scroll_corner_and_resizer =
owning_layer_.GetScrollableArea()->ScrollCornerAndResizerRect();
layer->SetOffsetFromLayoutObject(
ToIntSize(scroll_corner_and_resizer.Location()));
layer->SetSize(gfx::Size(scroll_corner_and_resizer.Size()));
layer->SetDrawsContent(!scroll_corner_and_resizer.IsEmpty());
layer->SetHitTestable(!scroll_corner_and_resizer.IsEmpty());
}
}
template <typename Function>
static void ApplyToGraphicsLayers(const CompositedLayerMapping* mapping,
const Function& function) {
auto null_checking_function = [&function](GraphicsLayer* layer) {
if (layer)
function(layer);
};
null_checking_function(mapping->MainGraphicsLayer());
null_checking_function(mapping->ScrollingContentsLayer());
null_checking_function(mapping->ForegroundLayer());
null_checking_function(mapping->MaskLayer());
null_checking_function(mapping->DecorationOutlineLayer());
null_checking_function(mapping->NonScrollingSquashingLayer());
null_checking_function(mapping->LayerForHorizontalScrollbar());
null_checking_function(mapping->LayerForVerticalScrollbar());
null_checking_function(mapping->LayerForScrollCorner());
}
// You receive an element id if you have an animation, or you're a scroller (and
// might impl animate).
//
// The element id for the scroll layers is assigned when they're constructed,
// since this is unconditional. However, the element id for the primary layer
// may change according to the rules above so we update those values here.
void CompositedLayerMapping::UpdateElementId() {
CompositorElementId element_id = CompositorElementIdFromUniqueObjectId(
owning_layer_.GetLayoutObject().UniqueId(),
CompositorElementIdNamespace::kPrimary);
graphics_layer_->SetElementId(element_id);
}
bool CompositedLayerMapping::UpdateForegroundLayer(
bool needs_foreground_layer) {
bool layer_changed = false;
if (needs_foreground_layer) {
if (!foreground_layer_) {
foreground_layer_ =
CreateGraphicsLayer(CompositingReason::kLayerForForeground);
foreground_layer_->SetHitTestable(true);
layer_changed = true;
}
} else if (foreground_layer_) {
foreground_layer_->RemoveFromParent();
foreground_layer_ = nullptr;
layer_changed = true;
}
return layer_changed;
}
bool CompositedLayerMapping::UpdateDecorationOutlineLayer(
bool needs_decoration_outline_layer) {
bool layer_changed = false;
if (needs_decoration_outline_layer) {
if (!decoration_outline_layer_) {
decoration_outline_layer_ =
CreateGraphicsLayer(CompositingReason::kLayerForDecoration);
decoration_outline_layer_->SetPaintingPhase(
kGraphicsLayerPaintDecoration);
layer_changed = true;
}
} else if (decoration_outline_layer_) {
decoration_outline_layer_ = nullptr;
layer_changed = true;
}
return layer_changed;
}
bool CompositedLayerMapping::UpdateMaskLayer(bool needs_mask_layer) {
bool layer_changed = false;
if (needs_mask_layer) {
if (!mask_layer_) {
mask_layer_ = CreateGraphicsLayer(CompositingReason::kLayerForMask);
mask_layer_->SetPaintingPhase(kGraphicsLayerPaintMask);
CompositorElementId element_id = CompositorElementIdFromUniqueObjectId(
GetLayoutObject().UniqueId(),
CompositorElementIdNamespace::kEffectMask);
mask_layer_->SetElementId(element_id);
if (GetLayoutObject().HasNonInitialBackdropFilter())
mask_layer_->CcLayer().SetIsBackdropFilterMask(true);
mask_layer_->SetHitTestable(true);
layer_changed = true;
}
} else if (mask_layer_) {
mask_layer_ = nullptr;
layer_changed = true;
}
return layer_changed;
}
bool CompositedLayerMapping::UpdateScrollingContentsLayer(
bool needs_scrolling_contents_layer) {
ScrollingCoordinator* scrolling_coordinator =
owning_layer_.GetScrollingCoordinator();
auto* scrollable_area = owning_layer_.GetScrollableArea();
if (scrollable_area)
scrollable_area->SetUsesCompositedScrolling(needs_scrolling_contents_layer);
bool layer_changed = false;
if (needs_scrolling_contents_layer) {
if (!scrolling_contents_layer_) {
// Inner layer which renders the content that scrolls.
scrolling_contents_layer_ =
CreateGraphicsLayer(CompositingReason::kLayerForScrollingContents);
scrolling_contents_layer_->SetHitTestable(true);
DCHECK(scrollable_area);
auto element_id = scrollable_area->GetScrollElementId();
scrolling_contents_layer_->SetElementId(element_id);
layer_changed = true;
if (scrolling_coordinator) {
scrolling_coordinator->ScrollableAreaScrollLayerDidChange(
scrollable_area);
}
}
} else if (scrolling_contents_layer_) {
scrolling_contents_layer_ = nullptr;
layer_changed = true;
if (scrolling_coordinator && scrollable_area) {
scrolling_coordinator->ScrollableAreaScrollLayerDidChange(
scrollable_area);
}
}
return layer_changed;
}
bool CompositedLayerMapping::UpdateSquashingLayers(
bool needs_squashing_layers) {
bool layers_changed = false;
if (needs_squashing_layers) {
if (!non_scrolling_squashing_layer_) {
non_scrolling_squashing_layer_ =
CreateGraphicsLayer(CompositingReason::kLayerForSquashingContents);
non_scrolling_squashing_layer_->SetDrawsContent(true);
non_scrolling_squashing_layer_->SetHitTestable(true);
layers_changed = true;
}
DCHECK(non_scrolling_squashing_layer_);
} else {
if (non_scrolling_squashing_layer_) {
non_scrolling_squashing_layer_->RemoveFromParent();
non_scrolling_squashing_layer_ = nullptr;
layers_changed = true;
}
DCHECK(!non_scrolling_squashing_layer_);
}
return layers_changed;
}
GraphicsLayerPaintingPhase
CompositedLayerMapping::PaintingPhaseForPrimaryLayer() const {
unsigned phase = kGraphicsLayerPaintBackground;
if (!foreground_layer_)
phase |= kGraphicsLayerPaintForeground;
if (!mask_layer_)
phase |= kGraphicsLayerPaintMask;
if (!decoration_outline_layer_)
phase |= kGraphicsLayerPaintDecoration;
if (scrolling_contents_layer_) {
phase &= ~kGraphicsLayerPaintForeground;
phase |= kGraphicsLayerPaintCompositedScroll;
}
return static_cast<GraphicsLayerPaintingPhase>(phase);
}
bool CompositedLayerMapping::PaintsChildren() const {
if (owning_layer_.HasVisibleContent() &&
owning_layer_.HasNonEmptyChildLayoutObjects())
return true;
if (HasVisibleNonCompositingDescendant(&owning_layer_))
return true;
return false;
}
static bool IsCompositedPlugin(LayoutObject& layout_object) {
return layout_object.IsEmbeddedObject() &&
layout_object.AdditionalCompositingReasons();
}
bool CompositedLayerMapping::HasVisibleNonCompositingDescendant(
PaintLayer* parent) {
if (!parent->HasVisibleDescendant())
return false;
PaintLayerPaintOrderIterator iterator(*parent, kAllChildren);
while (PaintLayer* child_layer = iterator.Next()) {
if (child_layer->HasCompositedLayerMapping())
continue;
if (child_layer->HasVisibleContent() ||
HasVisibleNonCompositingDescendant(child_layer))
return true;
}
return false;
}
bool CompositedLayerMapping::ContainsPaintedContent() const {
if (CompositedBounds().IsEmpty())
return false;
LayoutObject& layout_object = GetLayoutObject();
// FIXME: we could optimize cases where the image, video or canvas is known to
// fill the border box entirely, and set background color on the layer in that
// case, instead of allocating backing store and painting.
auto* layout_video = DynamicTo<LayoutVideo>(layout_object);
if (layout_video && layout_video->GetDisplayMode() == LayoutVideo::kVideo)
return owning_layer_.HasBoxDecorationsOrBackground();
if (layout_object.GetNode() && layout_object.GetNode()->IsDocumentNode()) {
if (owning_layer_.NeedsCompositedScrolling())
return BackgroundPaintsOntoGraphicsLayer();
// Look to see if the root object has a non-simple background
LayoutObject* root_object =
layout_object.GetDocument().documentElement()
? layout_object.GetDocument().documentElement()->GetLayoutObject()
: nullptr;
// Reject anything that has a border, a border-radius or outline,
// or is not a simple background (no background, or solid color).
if (root_object &&
HasBoxDecorationsOrBackgroundImage(root_object->StyleRef()))
return true;
// Now look at the body's layoutObject.
HTMLElement* body = layout_object.GetDocument().body();
LayoutObject* body_object =
IsA<HTMLBodyElement>(body) ? body->GetLayoutObject() : nullptr;
if (body_object &&
HasBoxDecorationsOrBackgroundImage(body_object->StyleRef()))
return true;
}
if (owning_layer_.HasVisibleBoxDecorations())
return true;
if (layout_object.HasMask()) // masks require special treatment
return true;
if (layout_object.IsAtomicInlineLevel() && !IsCompositedPlugin(layout_object))
return true;
if (layout_object.IsLayoutMultiColumnSet())
return true;
// FIXME: it's O(n^2). A better solution is needed.
return PaintsChildren();
}
void CompositedLayerMapping::ContentChanged(ContentChangeType change_type) {
if (change_type == kCanvasChanged && IsTextureLayerCanvas(GetLayoutObject()))
graphics_layer_->InvalidateContents();
}
// Return the offset from the top-left of this compositing layer at which the
// LayoutObject's contents are painted.
PhysicalOffset CompositedLayerMapping::ContentOffsetInCompositingLayer() const {
return owning_layer_.SubpixelAccumulation() -
PhysicalOffset(graphics_layer_->OffsetFromLayoutObject());
}
PhysicalRect CompositedLayerMapping::ContentsBox() const {
PhysicalRect contents_box = ContentsRect(GetLayoutObject());
contents_box.Move(ContentOffsetInCompositingLayer());
return contents_box;
}
bool CompositedLayerMapping::NeedsToReparentOverflowControls() const {
return owning_layer_.NeedsReorderOverlayOverflowControls();
}
wtf_size_t CompositedLayerMapping::MoveOverflowControlLayersInto(
GraphicsLayerVector& vector,
wtf_size_t position) {
wtf_size_t count = 0;
auto move_layer = [&](GraphicsLayer* layer) {
if (!layer)
return;
layer->RemoveFromParent();
vector.insert(position++, layer);
count++;
};
move_layer(layer_for_horizontal_scrollbar_.get());
move_layer(layer_for_vertical_scrollbar_.get());
move_layer(layer_for_scroll_corner_.get());
return count;
}
GraphicsLayer* CompositedLayerMapping::ParentForSublayers() const {
if (scrolling_contents_layer_)
return scrolling_contents_layer_.get();
return graphics_layer_.get();
}
void CompositedLayerMapping::SetSublayers(GraphicsLayerVector sublayers) {
GraphicsLayer* parent = ParentForSublayers();
// TODO(szager): Remove after diagnosing crash crbug.com/1092673
CHECK(parent);
// The caller should have inserted |foreground_layer_| into |sublayers|.
DCHECK(!foreground_layer_ || sublayers.Contains(foreground_layer_.get()));
if (parent == graphics_layer_.get()) {
// SetChildren() below will clobber all layers in |parent|, so we need to
// add layers that should stay in the children list into |sublayers|.
if (!NeedsToReparentOverflowControls()) {
if (layer_for_horizontal_scrollbar_)
sublayers.insert(0, layer_for_horizontal_scrollbar_.get());
if (layer_for_vertical_scrollbar_)
sublayers.insert(0, layer_for_vertical_scrollbar_.get());
if (layer_for_scroll_corner_)
sublayers.insert(0, layer_for_scroll_corner_.get());
}
if (decoration_outline_layer_)
sublayers.push_back(decoration_outline_layer_.get());
if (mask_layer_)
sublayers.push_back(mask_layer_.get());
if (non_scrolling_squashing_layer_)
sublayers.push_back(non_scrolling_squashing_layer_.get());
}
parent->SetChildren(sublayers);
}
GraphicsLayerUpdater::UpdateType CompositedLayerMapping::UpdateTypeForChildren(
GraphicsLayerUpdater::UpdateType update_type) const {
if (pending_update_scope_ >= kGraphicsLayerUpdateSubtree)
return GraphicsLayerUpdater::kForceUpdate;
return update_type;
}
GraphicsLayer* CompositedLayerMapping::SquashingLayer(
const PaintLayer& squashed_layer) const {
#if DCHECK_IS_ON()
AssertInSquashedLayersVector(squashed_layer);
#endif
if (MayBeSquashedIntoScrollingContents(squashed_layer)) {
DCHECK(ScrollingContentsLayer());
return ScrollingContentsLayer();
}
DCHECK(NonScrollingSquashingLayer());
return NonScrollingSquashingLayer();
}
void CompositedLayerMapping::SetNeedsCheckRasterInvalidation() {
ApplyToGraphicsLayers(this, [](GraphicsLayer* graphics_layer) {
if (graphics_layer->DrawsContent())
graphics_layer->SetNeedsCheckRasterInvalidation();
});
}
const GraphicsLayerPaintInfo* CompositedLayerMapping::ContainingSquashedLayer(
const LayoutObject* layout_object,
const Vector<GraphicsLayerPaintInfo>& layers,
unsigned max_squashed_layer_index) {
if (!layout_object)
return nullptr;
for (wtf_size_t i = 0; i < layers.size() && i < max_squashed_layer_index;
++i) {
if (layout_object->IsDescendantOf(
&layers[i].paint_layer->GetLayoutObject()))
return &layers[i];
}
return nullptr;
}
const GraphicsLayerPaintInfo*
CompositedLayerMapping::ContainingSquashedLayerInSquashingLayer(
const LayoutObject* layout_object,
unsigned max_squashed_layer_index) const {
return ContainingSquashedLayer(layout_object, non_scrolling_squashed_layers_,
max_squashed_layer_index);
}
void CompositedLayerMapping::UpdateLocalClipRectForSquashedLayer(
const PaintLayer& reference_layer,
const Vector<GraphicsLayerPaintInfo>& layers,
GraphicsLayerPaintInfo& paint_info) {
const LayoutObject* clipping_container =
paint_info.paint_layer->ClippingContainer();
if (clipping_container == reference_layer.ClippingContainer() ||
// When squashing into scrolling contents without other clips.
clipping_container == &reference_layer.GetLayoutObject()) {
paint_info.local_clip_rect_for_squashed_layer =
ClipRect(PhysicalRect(LayoutRect::InfiniteIntRect()));
paint_info.offset_from_clip_rect_root = PhysicalOffset();
paint_info.local_clip_rect_root = paint_info.paint_layer;
return;
}
DCHECK(clipping_container);
const GraphicsLayerPaintInfo* ancestor_paint_info =
ContainingSquashedLayer(clipping_container, layers, layers.size());
// Must be there, otherwise
// CompositingLayerAssigner::GetReasonsPreventingSquashing() would have
// disallowed squashing.
DCHECK(ancestor_paint_info);
ClipRectsContext clip_rects_context(
ancestor_paint_info->paint_layer,
&ancestor_paint_info->paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects);
ClipRect parent_clip_rect;
paint_info.paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateBackgroundClipRect(clip_rects_context, parent_clip_rect);
// Convert from ancestor to local coordinates.
IntSize ancestor_to_local_offset =
paint_info.offset_from_layout_object -
ancestor_paint_info->offset_from_layout_object;
parent_clip_rect.Move(PhysicalOffset(ancestor_to_local_offset));
paint_info.local_clip_rect_for_squashed_layer = parent_clip_rect;
paint_info.offset_from_clip_rect_root =
PhysicalOffset(ancestor_to_local_offset);
paint_info.local_clip_rect_root = ancestor_paint_info->paint_layer;
}
void CompositedLayerMapping::DoPaintTask(
const GraphicsLayerPaintInfo& paint_info,
const GraphicsLayer& graphics_layer,
PaintLayerFlags paint_layer_flags,
GraphicsContext& context,
const IntRect& clip /* In the coords of rootLayer */) const {
FontCachePurgePreventer font_cache_purge_preventer;
IntSize offset = paint_info.offset_from_layout_object;
// The dirtyRect is in the coords of the painting root.
IntRect dirty_rect(clip);
dirty_rect.Move(offset);
if (paint_layer_flags & (kPaintLayerPaintingOverflowContents)) {
dirty_rect.MoveBy(
RoundedIntPoint(paint_info.paint_layer->SubpixelAccumulation()));
} else {
PhysicalRect bounds = paint_info.composited_bounds;
bounds.Move(paint_info.paint_layer->SubpixelAccumulation());
dirty_rect.Intersect(PixelSnappedIntRect(bounds));
}
#if DCHECK_IS_ON()
if (!GetLayoutObject().View()->GetFrame() ||
!GetLayoutObject().View()->GetFrame()->ShouldThrottleRendering())
paint_info.paint_layer->GetLayoutObject().AssertSubtreeIsLaidOut();
#endif
float device_scale_factor = blink::DeviceScaleFactorDeprecated(
paint_info.paint_layer->GetLayoutObject().GetFrame());
context.SetDeviceScaleFactor(device_scale_factor);
Settings* settings = GetLayoutObject().GetFrame()->GetSettings();
context.SetDarkModeEnabled(
settings->GetForceDarkModeEnabled() &&
!GetLayoutObject().View()->StyleRef().DarkColorScheme());
// As a composited layer may be painted directly, we need to traverse the
// effect tree starting from the current node all the way up through the
// parents to determine which effects are opacity 0, for the purposes of
// correctly computing paint metrics such as First Contentful Paint and
// Largest Contentful Paint. For the latter we special-case the nodes where
// the opacity:0 depth is 1, so we need to only compute up to the first two
// opacity:0 effects in here and can ignore the rest.
base::Optional<IgnorePaintTimingScope> ignore_paint_timing_scope;
int num_ignores = 0;
DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0);
for (const auto* effect_node = &paint_info.paint_layer->GetLayoutObject()
.FirstFragment()
.PreEffect()
.Unalias();
effect_node && num_ignores < 2;
effect_node = effect_node->UnaliasedParent()) {
if (effect_node->Opacity() == 0.0f) {
if (!ignore_paint_timing_scope)
ignore_paint_timing_scope.emplace();
IgnorePaintTimingScope::IncrementIgnoreDepth();
++num_ignores;
}
}
if (paint_info.paint_layer->GetCompositingState() !=
kPaintsIntoGroupedBacking) {
// FIXME: GraphicsLayers need a way to split for multicol.
PaintLayerPaintingInfo painting_info(
paint_info.paint_layer, CullRect(dirty_rect), kGlobalPaintNormalPhase,
paint_info.paint_layer->SubpixelAccumulation());
PaintLayerPainter(*paint_info.paint_layer)
.PaintLayerContents(context, painting_info, paint_layer_flags);
} else {
PaintLayerPaintingInfo painting_info(
paint_info.paint_layer, CullRect(dirty_rect), kGlobalPaintNormalPhase,
paint_info.paint_layer->SubpixelAccumulation());
PaintLayerPainter(*paint_info.paint_layer)
.Paint(context, painting_info, paint_layer_flags);
}
}
// TODO(eseckler): Make recording distance configurable, e.g. for use in
// headless, where we would like to record an exact area.
static const int kPixelDistanceToRecord = 4000;
IntRect CompositedLayerMapping::RecomputeInterestRect(
const GraphicsLayer* graphics_layer) const {
DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled());
IntRect graphics_layer_bounds(IntPoint(), IntSize(graphics_layer->Size()));
FloatClipRect mapping_rect((FloatRect(graphics_layer_bounds)));
auto source_state = graphics_layer->GetPropertyTreeState();
LayoutView* root_view = owning_layer_.GetLayoutObject().View();
while (root_view->GetFrame()->OwnerLayoutObject())
root_view = root_view->GetFrame()->OwnerLayoutObject()->View();
auto root_view_state = root_view->FirstFragment().LocalBorderBoxProperties();
// 1. Move into local transform space.
mapping_rect.MoveBy(FloatPoint(graphics_layer->GetOffsetFromTransformNode()));
// 2. Map into visible space of the root LayoutView.
GeometryMapper::LocalToAncestorVisualRect(source_state, root_view_state,
mapping_rect);
FloatRect visible_content_rect(EnclosingIntRect(mapping_rect.Rect()));
FloatRect local_interest_rect;
// If the visible content rect is empty, then it makes no sense to map it back
// since there is nothing to map.
if (!visible_content_rect.IsEmpty()) {
local_interest_rect = visible_content_rect;
// 3. Map the visible content rect from root view space to local graphics
// layer space.
GeometryMapper::SourceToDestinationRect(root_view_state.Transform(),
source_state.Transform(),
local_interest_rect);
local_interest_rect.MoveBy(
-FloatPoint(graphics_layer->GetOffsetFromTransformNode()));
// TODO(chrishtr): the code below is a heuristic. Instead we should detect
// and return whether the mapping failed. In some cases,
// absoluteToLocalQuad can fail to map back to the local space, due to
// passing through non-invertible transforms or floating-point accuracy
// issues. Examples include rotation near 90 degrees or perspective. In such
// cases, fall back to painting the first kPixelDistanceToRecord pixels in
// each direction.
// Note that since the interest rect mapping above can produce extremely
// large numbers in cases of perspective, try our best to "normalize" the
// result by ensuring that none of the rect dimensions exceed some large,
// but reasonable, limit.
const float reasonable_pixel_limit = std::numeric_limits<int>::max() / 2.f;
auto unpadded_intersection = local_interest_rect;
// Note that by clamping X and Y, we are effectively moving the rect right /
// down. However, this will at most make us paint more content, which is
// better than erroneously deciding that the rect produced here is far
// offscreen.
if (unpadded_intersection.X() < -reasonable_pixel_limit)
unpadded_intersection.SetX(-reasonable_pixel_limit);
if (unpadded_intersection.Y() < -reasonable_pixel_limit)
unpadded_intersection.SetY(-reasonable_pixel_limit);
if (unpadded_intersection.MaxX() > reasonable_pixel_limit) {
unpadded_intersection.SetWidth(reasonable_pixel_limit -
unpadded_intersection.X());
}
if (unpadded_intersection.MaxY() > reasonable_pixel_limit) {
unpadded_intersection.SetHeight(reasonable_pixel_limit -
unpadded_intersection.Y());
}
unpadded_intersection.Intersect(FloatRect(graphics_layer_bounds));
// If our unpadded intersection is not empty, then use that before padding,
// since it can produce more stable results, and it would not produce any
// smaller area than if we used the original local interest rect.
if (!unpadded_intersection.IsEmpty())
local_interest_rect = unpadded_intersection;
// Expand by interest rect padding amount, scaled by the approximate scale
// of the GraphicsLayer relative to screen pixels. If width or height
// are zero or nearly zero, fall back to kPixelDistanceToRecord.
// This is the same as the else clause below.
float x_scale =
visible_content_rect.Width() > std::numeric_limits<float>::epsilon()
? local_interest_rect.Width() / visible_content_rect.Width()
: 1.0f;
float y_scale =
visible_content_rect.Height() > std::numeric_limits<float>::epsilon()
? local_interest_rect.Height() / visible_content_rect.Height()
: 1.0f;
// Take the max, to account for situations like rotation transforms, which
// swap x and y.
// Since at this point we can also have an extremely large scale due to
// perspective (see the comments above), cap it to something reasonable.
float scale = std::min(std::max(x_scale, y_scale),
reasonable_pixel_limit / kPixelDistanceToRecord);
local_interest_rect.Inflate(kPixelDistanceToRecord * scale);
} else {
// Expand by interest rect padding amount.
local_interest_rect.Inflate(kPixelDistanceToRecord);
}
return Intersection(EnclosingIntRect(local_interest_rect),
graphics_layer_bounds);
}
static const int kMinimumDistanceBeforeRepaint = 512;
bool CompositedLayerMapping::InterestRectChangedEnoughToRepaint(
const IntRect& previous_interest_rect,
const IntRect& new_interest_rect,
const IntSize& layer_size) {
DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled());
if (previous_interest_rect.IsEmpty() && new_interest_rect.IsEmpty())
return false;
// Repaint when going from empty to not-empty, to cover cases where the layer
// is painted for the first time, or otherwise becomes visible.
if (previous_interest_rect.IsEmpty())
return true;
// Repaint if the new interest rect includes area outside of a skirt around
// the existing interest rect.
IntRect expanded_previous_interest_rect(previous_interest_rect);
expanded_previous_interest_rect.Inflate(kMinimumDistanceBeforeRepaint);
if (!expanded_previous_interest_rect.Contains(new_interest_rect))
return true;
// Even if the new interest rect doesn't include enough new area to satisfy
// the condition above, repaint anyway if it touches a layer edge not touched
// by the existing interest rect. Because it's impossible to expose more area
// in the direction, repainting cannot be deferred until the exposed new area
// satisfies the condition above.
if (new_interest_rect.X() == 0 && previous_interest_rect.X() != 0)
return true;
if (new_interest_rect.Y() == 0 && previous_interest_rect.Y() != 0)
return true;
if (new_interest_rect.MaxX() == layer_size.Width() &&
previous_interest_rect.MaxX() != layer_size.Width())
return true;
if (new_interest_rect.MaxY() == layer_size.Height() &&
previous_interest_rect.MaxY() != layer_size.Height())
return true;
return false;
}
IntRect CompositedLayerMapping::ComputeInterestRect(
const GraphicsLayer* graphics_layer,
const IntRect& previous_interest_rect) const {
DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled());
// Use the previous interest rect if it covers the whole layer.
IntRect whole_layer_rect =
IntRect(IntPoint(), IntSize(graphics_layer->Size()));
if (!NeedsRepaint(*graphics_layer) &&
previous_interest_rect == whole_layer_rect)
return previous_interest_rect;
if (graphics_layer != graphics_layer_.get() &&
graphics_layer != non_scrolling_squashing_layer_.get() &&
graphics_layer != scrolling_contents_layer_.get())
return whole_layer_rect;
IntRect new_interest_rect = RecomputeInterestRect(graphics_layer);
if (NeedsRepaint(*graphics_layer) ||
InterestRectChangedEnoughToRepaint(previous_interest_rect,
new_interest_rect,
IntSize(graphics_layer->Size())))
return new_interest_rect;
return previous_interest_rect;
}
IntRect CompositedLayerMapping::PaintableRegion(
const GraphicsLayer* graphics_layer) const {
DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled());
const auto& fragment = OwningLayer().GetLayoutObject().FirstFragment();
CullRect cull_rect = graphics_layer == scrolling_contents_layer_.get() ||
(graphics_layer == foreground_layer_.get() &&
scrolling_contents_layer_)
? fragment.GetContentsCullRect()
: fragment.GetCullRect();
IntRect layer_rect(IntPoint(), IntSize(graphics_layer->Size()));
if (cull_rect.IsInfinite())
return layer_rect;
cull_rect.MoveBy(-graphics_layer->GetOffsetFromTransformNode());
return Intersection(cull_rect.Rect(), layer_rect);
}
LayoutSize CompositedLayerMapping::SubpixelAccumulation() const {
return owning_layer_.SubpixelAccumulation().ToLayoutSize();
}
bool CompositedLayerMapping::NeedsRepaint(
const GraphicsLayer& graphics_layer) const {
return IsScrollableAreaLayerWhichNeedsRepaint(&graphics_layer) ||
owning_layer_.SelfOrDescendantNeedsRepaint();
}
bool CompositedLayerMapping::AdjustForCompositedScrolling(
const GraphicsLayer* graphics_layer,
IntSize& offset) const {
if (graphics_layer == scrolling_contents_layer_.get() ||
graphics_layer == foreground_layer_.get()) {
if (PaintLayerScrollableArea* scrollable_area =
owning_layer_.GetScrollableArea()) {
if (scrollable_area->UsesCompositedScrolling()) {
// Note: this is the offset from the beginning of flow of the block, not
// the offset from the top/left of the overflow rect.
// offsetFromLayoutObject adds the origin offset from top/left to the
// beginning of flow.
ScrollOffset scroll_offset = scrollable_area->GetScrollOffset();
offset.Expand(-scroll_offset.Width(), -scroll_offset.Height());
return true;
}
}
}
return false;
}
static constexpr PaintLayerFlags PaintLayerFlagsFromGraphicsLayerPaintingPhase(
GraphicsLayerPaintingPhase graphics_layer_painting_phase) {
PaintLayerFlags paint_layer_flags = 0;
if (graphics_layer_painting_phase & kGraphicsLayerPaintBackground)
paint_layer_flags |= kPaintLayerPaintingCompositingBackgroundPhase;
else
paint_layer_flags |= kPaintLayerPaintingSkipRootBackground;
if (graphics_layer_painting_phase & kGraphicsLayerPaintForeground)
paint_layer_flags |= kPaintLayerPaintingCompositingForegroundPhase;
if (graphics_layer_painting_phase & kGraphicsLayerPaintMask)
paint_layer_flags |= kPaintLayerPaintingCompositingMaskPhase;
if (graphics_layer_painting_phase & kGraphicsLayerPaintOverflowContents)
paint_layer_flags |= kPaintLayerPaintingOverflowContents;
if (graphics_layer_painting_phase & kGraphicsLayerPaintCompositedScroll)
paint_layer_flags |= kPaintLayerPaintingCompositingScrollingPhase;
if (graphics_layer_painting_phase & kGraphicsLayerPaintDecoration)
paint_layer_flags |= kPaintLayerPaintingCompositingDecorationPhase;
return paint_layer_flags;
}
// Always paint all phases for squashed layers.
static constexpr PaintLayerFlags kPaintLayerFlagsForSquashedLayer =
PaintLayerFlagsFromGraphicsLayerPaintingPhase(
kGraphicsLayerPaintAllWithOverflowClip);
void CompositedLayerMapping::PaintContents(
const GraphicsLayer* graphics_layer,
GraphicsContext& context,
GraphicsLayerPaintingPhase graphics_layer_painting_phase,
const IntRect& interest_rect_arg) const {
IntRect interest_rect = RuntimeEnabledFeatures::CullRectUpdateEnabled()
? PaintableRegion(graphics_layer)
: interest_rect_arg;
FramePaintTiming frame_paint_timing(context, GetLayoutObject().GetFrame());
// https://code.google.com/p/chromium/issues/detail?id=343772
DisableCompositingQueryAsserts disabler;
#if DCHECK_IS_ON()
// FIXME: once the state machine is ready, this can be removed and we can
// refer to that instead.
if (Page* page = GetLayoutObject().GetFrame()->GetPage())
page->SetIsPainting(true);
#endif
DCHECK(owning_layer_.GetLayoutObject()
.GetFrameView()
->LocalFrameTreeAllowsThrottling());
TRACE_EVENT1(
"devtools.timeline,rail", "Paint", "data",
inspector_paint_event::Data(&owning_layer_.GetLayoutObject(),
PhysicalRect(interest_rect), graphics_layer));
PaintLayerFlags paint_layer_flags =
PaintLayerFlagsFromGraphicsLayerPaintingPhase(
graphics_layer_painting_phase);
if (graphics_layer == graphics_layer_.get() ||
graphics_layer == foreground_layer_.get() ||
graphics_layer == mask_layer_.get() ||
graphics_layer == scrolling_contents_layer_.get() ||
graphics_layer == decoration_outline_layer_.get()) {
if (BackgroundPaintsOntoScrollingContentsLayer()) {
if (graphics_layer == scrolling_contents_layer_.get())
paint_layer_flags &= ~kPaintLayerPaintingSkipRootBackground;
else if (!BackgroundPaintsOntoGraphicsLayer())
paint_layer_flags |= kPaintLayerPaintingSkipRootBackground;
}
GraphicsLayerPaintInfo paint_info;
paint_info.paint_layer = &owning_layer_;
paint_info.composited_bounds = CompositedBounds();
paint_info.offset_from_layout_object =
graphics_layer->OffsetFromLayoutObject();
AdjustForCompositedScrolling(graphics_layer,
paint_info.offset_from_layout_object);
// We have to use the same root as for hit testing, because both methods can
// compute and cache clipRects.
DoPaintTask(paint_info, *graphics_layer, paint_layer_flags, context,
interest_rect);
if (graphics_layer == scrolling_contents_layer_.get() &&
!squashed_layers_in_scrolling_contents_.IsEmpty()) {
// We have squashed_layers_in_scrolling_contents_ only if owning_layer_
// is not a stacking context, thus doesn't have foreground_layer_.
// (Otherwise we would need to squash into foreground_layer_.)
DCHECK(!foreground_layer_);
for (auto& squashed_layer : squashed_layers_in_scrolling_contents_) {
DoPaintTask(squashed_layer, *graphics_layer,
kPaintLayerFlagsForSquashedLayer, context, interest_rect);
}
}
} else if (graphics_layer == non_scrolling_squashing_layer_.get()) {
DCHECK_EQ(kPaintLayerFlagsForSquashedLayer, paint_layer_flags);
for (auto& squashed_layer : non_scrolling_squashed_layers_) {
DoPaintTask(squashed_layer, *graphics_layer, paint_layer_flags, context,
interest_rect);
}
} else if (IsScrollableAreaLayer(graphics_layer)) {
PaintScrollableArea(graphics_layer, context, interest_rect);
}
#if DCHECK_IS_ON()
if (Page* page = GetLayoutObject().GetFrame()->GetPage())
page->SetIsPainting(false);
#endif
}
void CompositedLayerMapping::PaintScrollableArea(
const GraphicsLayer* graphics_layer,
GraphicsContext& context,
const IntRect& interest_rect) const {
// cull_rect is in the space of the containing scrollable area in which
// Scrollbar::Paint() will paint the scrollbar.
CullRect cull_rect(interest_rect);
cull_rect.Move(graphics_layer->OffsetFromLayoutObject());
PaintLayerScrollableArea* scrollable_area = owning_layer_.GetScrollableArea();
ScrollableAreaPainter painter(*scrollable_area);
if (graphics_layer == LayerForHorizontalScrollbar()) {
if (Scrollbar* scrollbar = scrollable_area->HorizontalScrollbar())
painter.PaintScrollbar(context, *scrollbar, IntPoint(), cull_rect);
} else if (graphics_layer == LayerForVerticalScrollbar()) {
if (Scrollbar* scrollbar = scrollable_area->VerticalScrollbar())
painter.PaintScrollbar(context, *scrollbar, IntPoint(), cull_rect);
} else if (graphics_layer == LayerForScrollCorner()) {
painter.PaintScrollCorner(context, IntPoint(), cull_rect);
painter.PaintResizer(context, IntPoint(), cull_rect);
}
}
bool CompositedLayerMapping::IsScrollableAreaLayer(
const GraphicsLayer* graphics_layer) const {
return graphics_layer == LayerForHorizontalScrollbar() ||
graphics_layer == LayerForVerticalScrollbar() ||
graphics_layer == LayerForScrollCorner();
}
bool CompositedLayerMapping::IsScrollableAreaLayerWhichNeedsRepaint(
const GraphicsLayer* graphics_layer) const {
if (PaintLayerScrollableArea* scrollable_area =
owning_layer_.GetScrollableArea()) {
if (graphics_layer == LayerForHorizontalScrollbar())
return scrollable_area->HorizontalScrollbarNeedsPaintInvalidation();
if (graphics_layer == LayerForVerticalScrollbar())
return scrollable_area->VerticalScrollbarNeedsPaintInvalidation();
if (graphics_layer == LayerForScrollCorner())
return scrollable_area->ScrollCornerNeedsPaintInvalidation();
}
return false;
}
bool CompositedLayerMapping::ShouldSkipPaintingSubtree() const {
return GetLayoutObject().GetFrame()->ShouldThrottleRendering() ||
owning_layer_.IsUnderSVGHiddenContainer() ||
DisplayLockUtilities::NearestLockedExclusiveAncestor(
GetLayoutObject());
}
bool CompositedLayerMapping::IsTrackingRasterInvalidations() const {
return GetLayoutObject().GetFrameView()->IsTrackingRasterInvalidations();
}
void CompositedLayerMapping::GraphicsLayersDidChange() {
LocalFrameView* frame_view = GetLayoutObject().GetFrameView();
DCHECK(frame_view);
frame_view->SetPaintArtifactCompositorNeedsUpdate();
}
#if DCHECK_IS_ON()
void CompositedLayerMapping::VerifyNotPainting() {
DCHECK(!GetLayoutObject().GetFrame()->GetPage() ||
!GetLayoutObject().GetFrame()->GetPage()->IsPainting());
}
#endif
bool CompositedLayerMapping::UpdateSquashingLayerAssignmentInternal(
Vector<GraphicsLayerPaintInfo>& squashed_layers,
PaintLayer& squashed_layer,
wtf_size_t next_squashed_layer_index) {
GraphicsLayerPaintInfo paint_info;
paint_info.paint_layer = &squashed_layer;
// NOTE: composited bounds are updated elsewhere
// NOTE: offsetFromLayoutObject is updated elsewhere
// Change tracking on squashing layers: at the first sign of something
// changed, just invalidate the layer.
// FIXME: Perhaps we can find a tighter more clever mechanism later.
if (next_squashed_layer_index < squashed_layers.size()) {
if (paint_info.paint_layer ==
squashed_layers[next_squashed_layer_index].paint_layer)
return false;
squashed_layers.insert(next_squashed_layer_index, paint_info);
} else {
squashed_layers.push_back(paint_info);
}
// Must invalidate before adding the squashed layer to the mapping.
Compositor()->PaintInvalidationOnCompositingChange(&squashed_layer);
squashed_layer.SetGroupedMapping(
this, PaintLayer::kInvalidateLayerAndRemoveFromMapping);
return true;
}
bool CompositedLayerMapping::UpdateSquashingLayerAssignment(
PaintLayer& squashed_layer,
wtf_size_t next_squashed_layer_in_non_scrolling_squashing_layer_index,
wtf_size_t next_squashed_layer_in_scrolling_contents_index) {
if (MayBeSquashedIntoScrollingContents(squashed_layer)) {
return UpdateSquashingLayerAssignmentInternal(
squashed_layers_in_scrolling_contents_, squashed_layer,
next_squashed_layer_in_scrolling_contents_index);
}
return UpdateSquashingLayerAssignmentInternal(
non_scrolling_squashed_layers_, squashed_layer,
next_squashed_layer_in_non_scrolling_squashing_layer_index);
}
void CompositedLayerMapping::RemoveLayerFromSquashingGraphicsLayer(
const PaintLayer& layer) {
// We must try to remove the layer from both vectors because
// MayBeSquashedIntoScrollingContents() may not reflect the previous status.
for (wtf_size_t i = 0; i < non_scrolling_squashed_layers_.size(); ++i) {
if (non_scrolling_squashed_layers_[i].paint_layer == &layer) {
non_scrolling_squashed_layers_.EraseAt(i);
return;
}
}
for (wtf_size_t i = 0; i < squashed_layers_in_scrolling_contents_.size();
++i) {
if (squashed_layers_in_scrolling_contents_[i].paint_layer == &layer) {
squashed_layers_in_scrolling_contents_.EraseAt(i);
return;
}
}
// Assert on incorrect mappings between layers and groups
NOTREACHED();
}
static bool LayerInSquashedLayersVector(
const Vector<GraphicsLayerPaintInfo>& squashed_layers,
const PaintLayer& layer) {
for (auto& squashed_layer : squashed_layers) {
if (squashed_layer.paint_layer == &layer)
return true;
}
return false;
}
#if DCHECK_IS_ON()
void CompositedLayerMapping::AssertInSquashedLayersVector(
const PaintLayer& squashed_layer) const {
auto* in = &non_scrolling_squashed_layers_;
auto* out = &squashed_layers_in_scrolling_contents_;
if (MayBeSquashedIntoScrollingContents(squashed_layer))
std::swap(in, out);
DCHECK(LayerInSquashedLayersVector(*in, squashed_layer));
DCHECK(!LayerInSquashedLayersVector(*out, squashed_layer));
}
#endif
static void RemoveExtraSquashedLayers(
Vector<GraphicsLayerPaintInfo>& squashed_layers,
wtf_size_t new_count,
Vector<PaintLayer*>& layers_needing_paint_invalidation) {
DCHECK_GE(squashed_layers.size(), new_count);
if (squashed_layers.size() == new_count)
return;
for (auto i = new_count; i < squashed_layers.size(); i++)
layers_needing_paint_invalidation.push_back(squashed_layers[i].paint_layer);
squashed_layers.Shrink(new_count);
}
void CompositedLayerMapping::FinishAccumulatingSquashingLayers(
wtf_size_t new_non_scrolling_squashed_layer_count,
wtf_size_t new_squashed_layer_in_scrolling_contents_count,
Vector<PaintLayer*>& layers_needing_paint_invalidation) {
wtf_size_t first_removed_layer = layers_needing_paint_invalidation.size();
RemoveExtraSquashedLayers(non_scrolling_squashed_layers_,
new_non_scrolling_squashed_layer_count,
layers_needing_paint_invalidation);
RemoveExtraSquashedLayers(squashed_layers_in_scrolling_contents_,
new_squashed_layer_in_scrolling_contents_count,
layers_needing_paint_invalidation);
for (auto i = first_removed_layer;
i < layers_needing_paint_invalidation.size(); i++) {
PaintLayer* layer = layers_needing_paint_invalidation[i];
// Deal with layers that are no longer squashed. Need to check both vectors
// to exclude the layers that are still squashed. A layer may change from
// scrolling to non-scrolling or vice versa and still be squashed.
if (!LayerInSquashedLayersVector(non_scrolling_squashed_layers_, *layer) &&
!LayerInSquashedLayersVector(squashed_layers_in_scrolling_contents_,
*layer)) {
Compositor()->PaintInvalidationOnCompositingChange(layer);
layer->SetGroupedMapping(
nullptr, PaintLayer::kDoNotInvalidateLayerAndRemoveFromMapping);
layer->SetLostGroupedMapping(true);
}
}
}
String CompositedLayerMapping::DebugName(
const GraphicsLayer* graphics_layer) const {
String name;
if (graphics_layer == graphics_layer_.get()) {
name = owning_layer_.DebugName();
} else if (graphics_layer == non_scrolling_squashing_layer_.get()) {
name = "Squashing Layer (first squashed layer: " +
(non_scrolling_squashed_layers_.size() > 0
? non_scrolling_squashed_layers_[0].paint_layer->DebugName()
: "") +
")";
} else if (graphics_layer == foreground_layer_.get()) {
name = owning_layer_.DebugName() + " (foreground) Layer";
} else if (graphics_layer == mask_layer_.get()) {
name = "Mask Layer";
} else if (graphics_layer == layer_for_horizontal_scrollbar_.get()) {
name = "Horizontal Scrollbar Layer";
} else if (graphics_layer == layer_for_vertical_scrollbar_.get()) {
name = "Vertical Scrollbar Layer";
} else if (graphics_layer == layer_for_scroll_corner_.get()) {
name = "Scroll Corner Layer";
} else if (graphics_layer == scrolling_contents_layer_.get()) {
name = "Scrolling Contents Layer";
} else if (graphics_layer == decoration_outline_layer_.get()) {
name = "Decoration Layer";
} else {
NOTREACHED();
}
return name;
}
const ScrollableArea* CompositedLayerMapping::GetScrollableAreaForTesting(
const GraphicsLayer* layer) const {
if (layer == scrolling_contents_layer_.get())
return owning_layer_.GetScrollableArea();
return nullptr;
}
} // namespace blink