blob: 7ed646abe9b8607513223e87870af384de3b3e84 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h"
#include "third_party/blink/renderer/core/paint/fragment_data.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace {
enum class ScrollbarOrCorner {
kHorizontalScrollbar,
kVerticalScrollbar,
kScrollbarCorner,
};
}
namespace blink {
void CompositingLayerPropertyUpdater::Update(const LayoutObject& object) {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
if (!object.HasLayer())
return;
const auto* paint_layer = To<LayoutBoxModelObject>(object).Layer();
const auto* mapping = paint_layer->GetCompositedLayerMapping();
if (!mapping)
return;
const FragmentData& fragment_data = object.FirstFragment();
DCHECK(fragment_data.HasLocalBorderBoxProperties());
// SPv1 compositing forces single fragment for directly composited elements.
DCHECK(!fragment_data.NextFragment() ||
// We create multiple fragments for composited repeating fixed-position
// during printing.
object.GetDocument().Printing() ||
!object.CanBeCompositedForDirectReasons());
PhysicalOffset layout_snapped_paint_offset =
fragment_data.PaintOffset() - paint_layer->SubpixelAccumulation();
IntPoint snapped_paint_offset = RoundedIntPoint(layout_snapped_paint_offset);
#if DCHECK_IS_ON()
// A layer without visible contents can be composited due to animation.
// Since the layer itself has no visible subtree, there is no guarantee
// that all of its ancestors have a visible subtree. An ancestor with no
// visible subtree can be non-composited despite we expected it to, this
// resulted in the paint offset used by CompositedLayerMapping to mismatch.
bool subpixel_accumulation_may_be_bogus = paint_layer->SubtreeIsInvisible();
if (!subpixel_accumulation_may_be_bogus &&
layout_snapped_paint_offset != PhysicalOffset(snapped_paint_offset)) {
// TODO(crbug.com/925377): Fix the root cause.
DLOG(ERROR) << "Paint offset pixel snapping error for " << object
<< " expected: " << snapped_paint_offset
<< " actual: " << layout_snapped_paint_offset;
}
#endif
base::Optional<PropertyTreeStateOrAlias> container_layer_state;
auto SetContainerLayerState =
[&fragment_data, &snapped_paint_offset,
&container_layer_state](GraphicsLayer* graphics_layer) {
if (graphics_layer) {
if (!container_layer_state)
container_layer_state = fragment_data.LocalBorderBoxProperties();
graphics_layer->SetLayerState(
*container_layer_state,
snapped_paint_offset + graphics_layer->OffsetFromLayoutObject());
}
};
SetContainerLayerState(mapping->MainGraphicsLayer());
SetContainerLayerState(mapping->DecorationOutlineLayer());
bool is_root_scroller =
CompositingReasonFinder::RequiresCompositingForRootScroller(*paint_layer);
auto SetContainerLayerStateForScrollbars =
[&object, &is_root_scroller, &fragment_data, &snapped_paint_offset,
&container_layer_state](GraphicsLayer* graphics_layer,
ScrollbarOrCorner scrollbar_or_corner) {
if (!graphics_layer)
return;
PropertyTreeStateOrAlias scrollbar_layer_state =
container_layer_state.value_or(
fragment_data.LocalBorderBoxProperties());
// OverflowControlsClip should be applied within the scrollbar layers.
if (const auto* properties = fragment_data.PaintProperties()) {
if (const auto* clip = properties->OverflowControlsClip())
scrollbar_layer_state.SetClip(*clip);
if (scrollbar_or_corner == ScrollbarOrCorner::kHorizontalScrollbar) {
if (const auto* effect = properties->HorizontalScrollbarEffect()) {
scrollbar_layer_state.SetEffect(*effect);
}
} else if (scrollbar_or_corner ==
ScrollbarOrCorner::kVerticalScrollbar) {
if (const auto* effect = properties->VerticalScrollbarEffect())
scrollbar_layer_state.SetEffect(*effect);
}
}
if (is_root_scroller) {
// The root scrollbar needs to use a transform node above the
// overscroll elasticity layer because the root scrollbar should not
// bounce with overscroll.
const auto* frame_view = object.GetFrameView();
DCHECK(frame_view);
const auto* page = frame_view->GetPage();
const auto& viewport = page->GetVisualViewport();
if (const auto* transform =
viewport.GetOverscrollElasticityTransformNode()) {
DCHECK(transform->Parent());
scrollbar_layer_state.SetTransform(*transform->Parent());
}
}
graphics_layer->SetLayerState(
scrollbar_layer_state,
snapped_paint_offset + graphics_layer->OffsetFromLayoutObject());
};
SetContainerLayerStateForScrollbars(mapping->LayerForHorizontalScrollbar(),
ScrollbarOrCorner::kHorizontalScrollbar);
SetContainerLayerStateForScrollbars(mapping->LayerForVerticalScrollbar(),
ScrollbarOrCorner::kVerticalScrollbar);
SetContainerLayerStateForScrollbars(mapping->LayerForScrollCorner(),
ScrollbarOrCorner::kScrollbarCorner);
if (mapping->ScrollingContentsLayer()) {
// See comments for ScrollTranslation in object_paint_properties.h for the
// reason of adding ScrollOrigin().
auto contents_paint_offset =
snapped_paint_offset + To<LayoutBox>(object).ScrollOrigin();
auto SetScrollingContentsLayerState = [&fragment_data,
&contents_paint_offset](
GraphicsLayer* graphics_layer) {
if (graphics_layer) {
graphics_layer->SetLayerState(
fragment_data.ContentsProperties(),
contents_paint_offset + graphics_layer->OffsetFromLayoutObject());
}
};
SetScrollingContentsLayerState(mapping->ScrollingContentsLayer());
SetScrollingContentsLayerState(mapping->ForegroundLayer());
} else {
SetContainerLayerState(mapping->ForegroundLayer());
}
auto* main_graphics_layer = mapping->MainGraphicsLayer();
if (main_graphics_layer->ContentsLayer()) {
IntPoint offset;
// The offset should be zero when the layer has ReplacedContentTransform,
// because the offset has been baked into ReplacedContentTransform.
if (!fragment_data.PaintProperties() ||
!fragment_data.PaintProperties()->ReplacedContentTransform()) {
offset = main_graphics_layer->ContentsRect().Location() +
main_graphics_layer->GetOffsetFromTransformNode();
}
main_graphics_layer->SetContentsLayerState(
fragment_data.ContentsProperties(), offset);
}
if (auto* squashing_layer = mapping->NonScrollingSquashingLayer()) {
auto state = fragment_data.PreEffectProperties();
// The squashing layer's ClippingContainer is the common ancestor of clip
// state of all squashed layers, so we should use its clip state. This skips
// any control clips on the squashing layer's object which should not apply
// on squashed layers.
const auto* clipping_container = paint_layer->ClippingContainer();
state.SetClip(
clipping_container
? clipping_container->FirstFragment().ContentsProperties().Clip()
: ClipPaintPropertyNode::Root());
squashing_layer->SetLayerState(
state, snapped_paint_offset +
mapping->NonScrollingSquashingLayerOffsetFromLayoutObject());
}
if (auto* mask_layer = mapping->MaskLayer()) {
auto state = fragment_data.LocalBorderBoxProperties();
const auto* properties = fragment_data.PaintProperties();
DCHECK(properties);
DCHECK(properties->Mask() || properties->ClipPathMask());
DCHECK(properties->MaskClip());
state.SetEffect(properties->Mask() ? *properties->Mask()
: *properties->ClipPathMask());
state.SetClip(*properties->MaskClip());
mask_layer->SetLayerState(
state, snapped_paint_offset + mask_layer->OffsetFromLayoutObject());
}
if (RuntimeEnabledFeatures::CompositeSVGEnabled()) {
if (object.IsSVGRoot()) {
main_graphics_layer->SetShouldCreateLayersAfterPaint(
To<LayoutSVGRoot>(object).HasDescendantCompositingReasons() &&
main_graphics_layer->PaintsContentOrHitTest());
}
}
}
} // namespace blink