| /* |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights |
| * reserved. |
| * |
| * Portions are Copyright (C) 1998 Netscape Communications Corporation. |
| * |
| * Other contributors: |
| * Robert O'Callahan <roc+@cs.cmu.edu> |
| * David Baron <dbaron@fas.harvard.edu> |
| * Christian Biesinger <cbiesinger@web.de> |
| * Randall Jesup <rjesup@wgate.com> |
| * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> |
| * Josh Soref <timeless@mac.com> |
| * Boris Zbarsky <bzbarsky@mit.edu> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
| * |
| * Alternatively, the contents of this file may be used under the terms |
| * of either the Mozilla Public License Version 1.1, found at |
| * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
| * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
| * (the "GPL"), in which case the provisions of the MPL or the GPL are |
| * applicable instead of those above. If you wish to allow use of your |
| * version of this file only under the terms of one of those two |
| * licenses (the MPL or the GPL) and not to allow others to use your |
| * version of this file under the LGPL, indicate your decision by |
| * deletingthe provisions above and replace them with the notice and |
| * other provisions required by the MPL or the GPL, as the case may be. |
| * If you do not delete the provisions above, a recipient may use your |
| * version of this file under any of the LGPL, the MPL or the GPL. |
| */ |
| |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| |
| #include <limits> |
| |
| #include "base/allocator/partition_allocator/partition_alloc.h" |
| #include "base/containers/adapters.h" |
| #include "build/build_config.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/core/animation/scroll_timeline.h" |
| #include "third_party/blink/renderer/core/css/css_property_names.h" |
| #include "third_party/blink/renderer/core/css/style_request.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/layout/fragmentainer_iterator.h" |
| #include "third_party/blink/renderer/core/layout/geometry/transform_state.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_request.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/hit_testing_transform_state.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_flow_thread.h" |
| #include "third_party/blink/renderer/core/layout/layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/layout_tree_as_text.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.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/page/scrolling/sticky_position_scrolling_constraints.h" |
| #include "third_party/blink/renderer/core/paint/box_reflection_utils.h" |
| #include "third_party/blink/renderer/core/paint/clip_path_clipper.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/filter_effect_builder.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.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/rounded_border_geometry.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/style/reference_clip_path_operation.h" |
| #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h" |
| #include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" |
| #include "third_party/blink/renderer/platform/geometry/float_point_3d.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rect.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/filters/filter.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h" |
| #include "third_party/blink/renderer/platform/wtf/size_assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| static CompositingQueryMode g_compositing_query_mode = |
| kCompositingQueriesAreOnlyAllowedInCertainDocumentLifecyclePhases; |
| |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| struct SameSizeAsPaintLayer : DisplayItemClient { |
| // The bit fields may fit into the machine word of DisplayItemClient which |
| // has only 8-bit data. |
| unsigned bit_fields1 : 24; |
| unsigned bit_fields2; |
| void* pointers[10]; |
| #if DCHECK_IS_ON() |
| void* pointer; |
| #endif |
| LayoutUnit layout_units[4]; |
| IntSize size; |
| Persistent<PaintLayerScrollableArea> scrollable_area; |
| CullRect previous_cull_rect; |
| }; |
| |
| ASSERT_SIZE(PaintLayer, SameSizeAsPaintLayer); |
| #endif |
| |
| inline PhysicalRect PhysicalVisualOverflowRectAllowingUnset( |
| const LayoutBoxModelObject& layout_object) { |
| #if DCHECK_IS_ON() |
| NGInkOverflow::ReadUnsetAsNoneScope read_unset_as_none; |
| #endif |
| return layout_object.PhysicalVisualOverflowRect(); |
| } |
| |
| PaintLayer* SlowContainingLayer(const PaintLayer* ancestor, |
| bool* skipped_ancestor, |
| LayoutObject* layout_object) { |
| // This is a universal approach to find the containing layer, but it is |
| // slower. |
| absl::optional<LayoutObject::AncestorSkipInfo> skip_info; |
| if (skipped_ancestor) |
| skip_info.emplace(&ancestor->GetLayoutObject()); |
| while (auto* container = layout_object->Container( |
| skipped_ancestor ? &*skip_info : nullptr)) { |
| if (skipped_ancestor) { |
| if (skip_info->AncestorSkipped()) |
| *skipped_ancestor = true; |
| skip_info.emplace(&ancestor->GetLayoutObject()); |
| } |
| if (container->HasLayer()) |
| return To<LayoutBoxModelObject>(container)->Layer(); |
| layout_object = container; |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| PaintLayerRareData::PaintLayerRareData() |
| : enclosing_pagination_layer(nullptr), |
| potential_compositing_reasons_from_style(CompositingReason::kNone), |
| potential_compositing_reasons_from_non_style(CompositingReason::kNone), |
| compositing_reasons(CompositingReason::kNone), |
| squashing_disallowed_reasons(SquashingDisallowedReason::kNone), |
| grouped_mapping(nullptr) {} |
| |
| PaintLayerRareData::~PaintLayerRareData() = default; |
| |
| PaintLayer::PaintLayer(LayoutBoxModelObject& layout_object) |
| : is_root_layer_(IsA<LayoutView>(layout_object)), |
| has_visible_content_(false), |
| needs_descendant_dependent_flags_update_(true), |
| needs_visual_overflow_recalc_(true), |
| has_visible_descendant_(false), |
| #if DCHECK_IS_ON() |
| // The root layer (LayoutView) does not need position update at start |
| // because its Location() is always 0. |
| needs_position_update_(!IsRootLayer()), |
| #endif |
| has3d_transformed_descendant_(false), |
| needs_ancestor_dependent_compositing_inputs_update_( |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()), |
| child_needs_compositing_inputs_update_( |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()), |
| has_compositing_descendant_(false), |
| should_isolate_composited_descendants_(false), |
| lost_grouped_mapping_(false), |
| self_needs_repaint_(false), |
| descendant_needs_repaint_(false), |
| needs_cull_rect_update_(false), |
| forces_children_cull_rect_update_(false), |
| descendant_needs_cull_rect_update_(false), |
| previous_paint_result_(kFullyPainted), |
| needs_paint_phase_descendant_outlines_(false), |
| needs_paint_phase_float_(false), |
| has_non_isolated_descendant_with_blend_mode_(false), |
| has_fixed_position_descendant_(false), |
| has_sticky_position_descendant_(false), |
| has_non_contained_absolute_position_descendant_(false), |
| has_stacked_descendant_in_current_stacking_context_(false), |
| self_painting_status_changed_(false), |
| filter_on_effect_node_dirty_(false), |
| backdrop_filter_on_effect_node_dirty_(false), |
| is_under_svg_hidden_container_(false), |
| descendant_has_direct_or_scrolling_compositing_reason_(false), |
| needs_compositing_reasons_update_( |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()), |
| descendant_may_need_compositing_requirements_update_(false), |
| needs_compositing_layer_assignment_(false), |
| descendant_needs_compositing_layer_assignment_(false), |
| has_self_painting_layer_descendant_(false), |
| needs_reorder_overlay_overflow_controls_(false), |
| static_inline_edge_(InlineEdge::kInlineStart), |
| static_block_edge_(BlockEdge::kBlockStart), |
| needs_paint_offset_translation_for_compositing_(false), |
| needs_check_raster_invalidation_(false), |
| #if DCHECK_IS_ON() |
| layer_list_mutation_allowed_(true), |
| #endif |
| layout_object_(&layout_object), |
| parent_(nullptr), |
| previous_(nullptr), |
| next_(nullptr), |
| first_(nullptr), |
| last_(nullptr), |
| static_inline_position_(0), |
| static_block_position_(0), |
| ancestor_scroll_container_layer_(nullptr) |
| #if DCHECK_IS_ON() |
| , |
| stacking_parent_(nullptr) |
| #endif |
| { |
| is_self_painting_layer_ = ShouldBeSelfPaintingLayer(); |
| |
| UpdateScrollableArea(); |
| } |
| |
| PaintLayer::~PaintLayer() { |
| if (rare_data_ && rare_data_->resource_info) { |
| const ComputedStyle& style = GetLayoutObject().StyleRef(); |
| if (style.HasFilter()) |
| style.Filter().RemoveClient(*rare_data_->resource_info); |
| if (auto* reference_clip = |
| DynamicTo<ReferenceClipPathOperation>(style.ClipPath())) |
| reference_clip->RemoveClient(*rare_data_->resource_info); |
| rare_data_->resource_info->ClearLayer(); |
| } |
| |
| if (GroupedMapping()) { |
| DisableCompositingQueryAsserts disabler; |
| SetGroupedMapping(nullptr, kInvalidateLayerAndRemoveFromMapping); |
| } |
| |
| // Child layers will be deleted by their corresponding layout objects, so |
| // we don't need to delete them ourselves. |
| if (HasCompositedLayerMapping()) |
| ClearCompositedLayerMapping(true); |
| |
| // Reset this flag before disposing scrollable_area_ to prevent |
| // PaintLayerScrollableArea::WillRemoveScrollbar() from dirtying the z-order |
| // list of the stacking context. If this layer is removed from the parent, |
| // the z-order list should have been invalidated in RemoveChild(). |
| needs_reorder_overlay_overflow_controls_ = false; |
| |
| if (scrollable_area_) |
| scrollable_area_->Dispose(); |
| |
| #if DCHECK_IS_ON() |
| // stacking_parent_ should be cleared because DirtyStackingContextZOrderLists |
| // should have been called. |
| if (!GetLayoutObject().DocumentBeingDestroyed()) |
| DCHECK(!stacking_parent_); |
| #endif |
| } |
| |
| String PaintLayer::DebugName() const { |
| return GetLayoutObject().DebugName(); |
| } |
| |
| DOMNodeId PaintLayer::OwnerNodeId() const { |
| return static_cast<const DisplayItemClient&>(GetLayoutObject()).OwnerNodeId(); |
| } |
| |
| PaintLayerCompositor* PaintLayer::Compositor() const { |
| if (!GetLayoutObject().View()) |
| return nullptr; |
| return GetLayoutObject().View()->Compositor(); |
| } |
| |
| void PaintLayer::ContentChanged(ContentChangeType change_type) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| |
| // updateLayerCompositingState will query compositingReasons for accelerated |
| // overflow scrolling. This is tripped by |
| // web_tests/compositing/content-changed-chicken-egg.html |
| DisableCompositingQueryAsserts disabler; |
| |
| if (Compositor()) { |
| SetNeedsCompositingInputsUpdate(); |
| |
| if (change_type == kCanvasContextChanged) { |
| // Although we're missing test coverage, we need to call |
| // GraphicsLayer::SetContentsToCcLayer with the new cc::Layer for this |
| // canvas. See http://crbug.com/349195 |
| if (HasCompositedLayerMapping()) { |
| GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateSubtree); |
| } |
| } |
| } |
| } |
| |
| bool PaintLayer::PaintsWithFilters() const { |
| if (!GetLayoutObject().HasFilterInducingProperty()) |
| return false; |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // https://code.google.com/p/chromium/issues/detail?id=343759 |
| DisableCompositingQueryAsserts disabler; |
| return !GetCompositedLayerMapping() || |
| GetCompositingState() != kPaintsIntoOwnBacking; |
| } else { |
| return true; |
| } |
| } |
| |
| PhysicalOffset PaintLayer::SubpixelAccumulation() const { |
| return rare_data_ ? rare_data_->subpixel_accumulation : PhysicalOffset(); |
| } |
| |
| void PaintLayer::SetSubpixelAccumulation(const PhysicalOffset& accumulation) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| if (rare_data_ || !accumulation.IsZero()) |
| EnsureRareData().subpixel_accumulation = accumulation; |
| } |
| |
| void PaintLayer::UpdateLayerPositionsAfterLayout() { |
| TRACE_EVENT0("blink,benchmark", |
| "PaintLayer::updateLayerPositionsAfterLayout"); |
| RUNTIME_CALL_TIMER_SCOPE( |
| V8PerIsolateData::MainThreadIsolate(), |
| RuntimeCallStats::CounterId::kUpdateLayerPositionsAfterLayout); |
| |
| const LayoutBlock* enclosing_scrollport_box = |
| GetLayoutObject().EnclosingScrollportBox(); |
| UpdateLayerPositionRecursive( |
| enclosing_scrollport_box ? enclosing_scrollport_box->Layer() : nullptr); |
| UpdatePaginationRecursive(EnclosingPaginationLayer()); |
| } |
| |
| void PaintLayer::UpdateLayerPositionRecursive( |
| const PaintLayer* enclosing_scroller) { |
| auto old_location = location_without_position_offset_; |
| auto old_offset_for_in_flow_rel_position = OffsetForInFlowRelPosition(); |
| UpdateLayerPosition(); |
| |
| if (location_without_position_offset_ != old_location) { |
| SetNeedsCompositingInputsUpdate(); |
| } else { |
| // TODO(chrishtr): compute this invalidation in layout instead of here. |
| auto offset_for_in_flow_rel_position = |
| rare_data_ ? rare_data_->offset_for_in_flow_rel_position |
| : PhysicalOffset(); |
| if (offset_for_in_flow_rel_position != old_offset_for_in_flow_rel_position) |
| SetNeedsCompositingInputsUpdate(); |
| } |
| |
| const PaintLayer* previous_enclosing_scroller = |
| AncestorScrollContainerLayer(); |
| UpdateAncestorScrollContainerLayer(enclosing_scroller); |
| if (enclosing_scroller && |
| GetLayoutObject().StyleRef().HasStickyConstrainedPosition() && |
| (NeedsCompositingInputsUpdate() || |
| GetLayoutObject().NeedsPaintPropertyUpdate())) { |
| if (enclosing_scroller != previous_enclosing_scroller) { |
| // Old ancestor scroller should no longer have these constraints. |
| DCHECK(!previous_enclosing_scroller || |
| !previous_enclosing_scroller->GetScrollableArea() || |
| !previous_enclosing_scroller->GetScrollableArea() |
| ->GetStickyConstraintsMap() |
| .Contains(this)); |
| |
| // If our ancestor scroller has changed and the previous one was the |
| // root layer, we are no longer viewport constrained. |
| if (previous_enclosing_scroller && |
| previous_enclosing_scroller->IsRootLayer()) { |
| GetLayoutObject() |
| .View() |
| ->GetFrameView() |
| ->RemoveViewportConstrainedObject( |
| GetLayoutObject(), |
| LocalFrameView::ViewportConstrainedType::kSticky); |
| } |
| } |
| |
| if (enclosing_scroller->IsRootLayer()) { |
| GetLayoutObject().View()->GetFrameView()->AddViewportConstrainedObject( |
| GetLayoutObject(), LocalFrameView::ViewportConstrainedType::kSticky); |
| } |
| GetLayoutObject().UpdateStickyPositionConstraints(); |
| |
| // Sticky position constraints and ancestor overflow scroller affect |
| // the sticky layer position, so we need to update it again here. |
| UpdateLayerPosition(); |
| } |
| |
| // Display-locked elements always have a PaintLayer, meaning that the |
| // PaintLayer traversal won't skip locked elements. Thus, we don't have to do |
| // an ancestor check, and simply skip iterating children when this element is |
| // locked for child layout. |
| if (GetLayoutObject().ChildLayoutBlockedByDisplayLock()) |
| return; |
| |
| if (GetLayoutObject().IsScrollContainer()) |
| enclosing_scroller = this; |
| for (PaintLayer* child = FirstChild(); child; child = child->NextSibling()) |
| child->UpdateLayerPositionRecursive(enclosing_scroller); |
| } |
| |
| bool PaintLayer::SticksToScroller() const { |
| if (!GetLayoutObject().StyleRef().HasStickyConstrainedPosition()) |
| return false; |
| return AncestorScrollContainerLayer()->GetScrollableArea(); |
| } |
| |
| bool PaintLayer::FixedToViewport() const { |
| if (GetLayoutObject().StyleRef().GetPosition() != EPosition::kFixed) |
| return false; |
| return GetLayoutObject().Container() == GetLayoutObject().View(); |
| } |
| |
| bool PaintLayer::ScrollsWithRespectTo(const PaintLayer* other) const { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| if (FixedToViewport() != other->FixedToViewport()) |
| return true; |
| // If either element sticks we cannot trivially determine that the layers do |
| // not scroll with respect to each other. |
| if (SticksToScroller() || other->SticksToScroller()) |
| return true; |
| return AncestorScrollingLayer() != other->AncestorScrollingLayer(); |
| } |
| |
| bool PaintLayer::IsAffectedByScrollOf(const PaintLayer* ancestor) const { |
| if (this == ancestor) |
| return false; |
| |
| const PaintLayer* current_layer = this; |
| while (current_layer && current_layer != ancestor) { |
| bool ancestor_escaped = false; |
| const PaintLayer* container = |
| current_layer->ContainingLayer(ancestor, &ancestor_escaped); |
| if (ancestor_escaped) |
| return false; |
| // Workaround the bug that LayoutView is mistakenly considered |
| // a fixed-pos container. |
| if (current_layer->GetLayoutObject().IsFixedPositioned() && |
| container->IsRootLayer()) |
| return false; |
| current_layer = container; |
| } |
| return current_layer == ancestor; |
| } |
| |
| bool PaintLayer::IsTopMostNotAffectedByScrollOf( |
| const PaintLayer* ancestor) const { |
| // Returns true if |this| is the top-most fixed-pos layer between |this| |
| // (inclusive) and |ancestor. |
| |
| // Should only call this method for layers that we already know are not |
| // affected by the scroll offset of the ancestor (implying this element or |
| // an ancestor must be fixed). |
| DCHECK(!IsAffectedByScrollOf(ancestor)); |
| |
| // Only fixed-pos elements can be top-most. |
| if (!GetLayoutObject().IsFixedPositioned()) |
| return false; |
| |
| PaintLayer* curr = Parent(); |
| while (curr && curr != ancestor) { |
| if (curr->GetLayoutObject().IsFixedPositioned()) |
| return false; |
| curr = curr->Parent(); |
| } |
| |
| return true; |
| } |
| |
| void PaintLayer::UpdateTransformationMatrix() { |
| if (TransformationMatrix* transform = Transform()) { |
| LayoutBox* box = GetLayoutBox(); |
| DCHECK(box); |
| transform->MakeIdentity(); |
| box->StyleRef().ApplyTransform( |
| *transform, box->Size(), ComputedStyle::kIncludeTransformOrigin, |
| ComputedStyle::kIncludeMotionPath, |
| ComputedStyle::kIncludeIndependentTransformProperties); |
| MakeMatrixRenderable( |
| *transform, |
| box->GetDocument().GetSettings()->GetAcceleratedCompositingEnabled()); |
| } |
| } |
| |
| void PaintLayer::UpdateTransform(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| // It's possible for the old and new style transform data to be equivalent |
| // while hasTransform() differs, as it checks a number of conditions aside |
| // from just the matrix, including but not limited to animation state. |
| if (old_style && old_style->HasTransform() == new_style.HasTransform() && |
| new_style.TransformDataEquivalent(*old_style)) { |
| return; |
| } |
| |
| // LayoutObject::HasTransformRelatedProperty is also true when there is |
| // transform-style: preserve-3d or perspective set, so check style too. |
| bool has_transform = GetLayoutObject().HasTransformRelatedProperty() && |
| new_style.HasTransform(); |
| bool had3d_transform = Has3DTransform(); |
| |
| bool had_transform = Transform(); |
| if (has_transform != had_transform) { |
| if (has_transform) |
| EnsureRareData().transform = std::make_unique<TransformationMatrix>(); |
| else |
| rare_data_->transform.reset(); |
| } |
| |
| UpdateTransformationMatrix(); |
| |
| if (had3d_transform != Has3DTransform()) { |
| SetNeedsCompositingInputsUpdateInternal(); |
| MarkAncestorChainForFlagsUpdate(); |
| } |
| |
| if (LocalFrameView* frame_view = GetLayoutObject().GetDocument().View()) |
| frame_view->SetNeedsUpdateGeometries(); |
| } |
| |
| TransformationMatrix PaintLayer::CurrentTransform() const { |
| if (TransformationMatrix* transform = Transform()) |
| return *transform; |
| return TransformationMatrix(); |
| } |
| |
| TransformationMatrix PaintLayer::RenderableTransform( |
| GlobalPaintFlags global_paint_flags) const { |
| TransformationMatrix* transform = Transform(); |
| if (!transform) |
| return TransformationMatrix(); |
| |
| if (global_paint_flags & kGlobalPaintFlattenCompositingLayers) { |
| TransformationMatrix matrix = *transform; |
| MakeMatrixRenderable(matrix, false /* flatten 3d */); |
| return matrix; |
| } |
| |
| return *transform; |
| } |
| |
| void PaintLayer::ConvertFromFlowThreadToVisualBoundingBoxInAncestor( |
| const PaintLayer* ancestor_layer, |
| PhysicalRect& rect) const { |
| PaintLayer* pagination_layer = EnclosingPaginationLayer(); |
| DCHECK(pagination_layer); |
| auto& flow_thread = To<LayoutFlowThread>(pagination_layer->GetLayoutObject()); |
| |
| // First make the flow thread rectangle relative to the flow thread, not to |
| // |layer|. |
| PhysicalOffset offset_within_pagination_layer; |
| ConvertToLayerCoords(pagination_layer, offset_within_pagination_layer); |
| rect.Move(offset_within_pagination_layer); |
| |
| // Then make the rectangle visual, relative to the fragmentation context. |
| // Split our box up into the actual fragment boxes that layout in the |
| // columns/pages and unite those together to get our true bounding box. |
| rect = PhysicalRectToBeNoop( |
| flow_thread.FragmentsBoundingBox(rect.ToLayoutRect())); |
| |
| // Finally, make the visual rectangle relative to |ancestorLayer|. |
| if (ancestor_layer->EnclosingPaginationLayer() != pagination_layer) { |
| rect.Move(pagination_layer->VisualOffsetFromAncestor(ancestor_layer)); |
| return; |
| } |
| // The ancestor layer is inside the same pagination layer as |layer|, so we |
| // need to subtract the visual distance from the ancestor layer to the |
| // pagination layer. |
| rect.Move(-ancestor_layer->VisualOffsetFromAncestor(pagination_layer)); |
| } |
| |
| void PaintLayer::UpdatePaginationRecursive(bool needs_pagination_update) { |
| if (rare_data_) |
| rare_data_->enclosing_pagination_layer = nullptr; |
| |
| if (GetLayoutObject().IsLayoutFlowThread()) |
| needs_pagination_update = true; |
| |
| if (needs_pagination_update) { |
| // Each paginated layer has to paint on its own. There is no recurring into |
| // child layers. Each layer has to be checked individually and genuinely |
| // know if it is going to have to split itself up when painting only its |
| // contents (and not any other descendant layers). We track an |
| // enclosingPaginationLayer instead of using a simple bit, since we want to |
| // be able to get back to that layer easily. |
| if (LayoutFlowThread* containing_flow_thread = |
| GetLayoutObject().FlowThreadContainingBlock()) |
| EnsureRareData().enclosing_pagination_layer = |
| containing_flow_thread->Layer(); |
| } |
| |
| // If this element prevents child painting, then we can skip updating |
| // pagination info, since it won't be used anyway. |
| if (GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| return; |
| |
| for (PaintLayer* child = FirstChild(); child; child = child->NextSibling()) |
| child->UpdatePaginationRecursive(needs_pagination_update); |
| } |
| |
| void PaintLayer::ClearPaginationRecursive() { |
| if (rare_data_) |
| rare_data_->enclosing_pagination_layer = nullptr; |
| for (PaintLayer* child = FirstChild(); child; child = child->NextSibling()) |
| child->ClearPaginationRecursive(); |
| } |
| |
| const PaintLayer& PaintLayer::TransformAncestorOrRoot() const { |
| return TransformAncestor() ? *TransformAncestor() |
| : *GetLayoutObject().View()->Layer(); |
| } |
| |
| void PaintLayer::MapPointInPaintInvalidationContainerToBacking( |
| const LayoutBoxModelObject& paint_invalidation_container, |
| PhysicalOffset& point) { |
| PaintLayer* paint_invalidation_layer = paint_invalidation_container.Layer(); |
| if (!paint_invalidation_layer->GroupedMapping()) |
| return; |
| |
| GraphicsLayer* squashing_layer = |
| paint_invalidation_layer->GroupedMapping()->SquashingLayer( |
| *paint_invalidation_layer); |
| |
| auto source_state = |
| paint_invalidation_container.FirstFragment().LocalBorderBoxProperties(); |
| auto dest_state = squashing_layer->GetPropertyTreeState(); |
| |
| // Move the point into the source_state transform space, map to dest_state |
| // transform space, then move into squashing layer state. |
| point += |
| paint_invalidation_container.PrimaryStitchingFragment().PaintOffset(); |
| point = PhysicalOffset::FromFloatPointRound( |
| GeometryMapper::SourceToDestinationProjection(source_state.Transform(), |
| dest_state.Transform()) |
| .MapPoint(FloatPoint(point))); |
| point -= PhysicalOffset(squashing_layer->GetOffsetFromTransformNode()); |
| } |
| |
| void PaintLayer::DirtyVisibleContentStatus() { |
| MarkAncestorChainForFlagsUpdate(); |
| // Non-self-painting layers paint into their ancestor layer, and count as part |
| // of the "visible contents" of the parent, so we need to dirty it. |
| if (!IsSelfPaintingLayer()) |
| Parent()->DirtyVisibleContentStatus(); |
| } |
| |
| void PaintLayer::MarkAncestorChainForFlagsUpdate( |
| DescendantDependentFlagsUpdateFlag flag) { |
| #if DCHECK_IS_ON() |
| DCHECK(flag == DoesNotNeedDescendantDependentUpdate || |
| !layout_object_->GetDocument() |
| .View() |
| ->IsUpdatingDescendantDependentFlags()); |
| #endif |
| for (PaintLayer* layer = this; layer; layer = layer->Parent()) { |
| if (layer->needs_descendant_dependent_flags_update_ && |
| layer->GetLayoutObject().NeedsPaintPropertyUpdate()) |
| break; |
| if (flag == NeedsDescendantDependentUpdate) |
| layer->needs_descendant_dependent_flags_update_ = true; |
| layer->GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| } |
| } |
| |
| void PaintLayer::UpdateDescendantDependentFlags() { |
| if (needs_descendant_dependent_flags_update_) { |
| bool old_has_non_isolated_descendant_with_blend_mode = |
| has_non_isolated_descendant_with_blend_mode_; |
| has_visible_descendant_ = false; |
| has_non_isolated_descendant_with_blend_mode_ = false; |
| has_fixed_position_descendant_ = false; |
| has_sticky_position_descendant_ = false; |
| has_non_contained_absolute_position_descendant_ = false; |
| has_stacked_descendant_in_current_stacking_context_ = false; |
| has_self_painting_layer_descendant_ = false; |
| |
| bool can_contain_abs = |
| GetLayoutObject().CanContainAbsolutePositionObjects(); |
| |
| auto* first_child = [this]() -> PaintLayer* { |
| if (GetLayoutObject().ChildPrePaintBlockedByDisplayLock()) { |
| GetLayoutObject() |
| .GetDisplayLockContext() |
| ->NotifyCompositingDescendantDependentFlagUpdateWasBlocked(); |
| return nullptr; |
| } |
| return FirstChild(); |
| }(); |
| |
| for (PaintLayer* child = first_child; child; child = child->NextSibling()) { |
| const ComputedStyle& child_style = child->GetLayoutObject().StyleRef(); |
| |
| child->UpdateDescendantDependentFlags(); |
| |
| if (child->has_visible_content_ || child->has_visible_descendant_) |
| has_visible_descendant_ = true; |
| |
| has_non_isolated_descendant_with_blend_mode_ |= |
| (!child->GetLayoutObject().IsStackingContext() && |
| child->HasNonIsolatedDescendantWithBlendMode()) || |
| child_style.HasBlendMode(); |
| |
| has_fixed_position_descendant_ |= |
| child->HasFixedPositionDescendant() || |
| child_style.GetPosition() == EPosition::kFixed; |
| has_sticky_position_descendant_ |= |
| child->HasStickyPositionDescendant() || |
| child_style.GetPosition() == EPosition::kSticky; |
| |
| if (!can_contain_abs) { |
| has_non_contained_absolute_position_descendant_ |= |
| (child->HasNonContainedAbsolutePositionDescendant() || |
| child_style.GetPosition() == EPosition::kAbsolute); |
| } |
| |
| if (!has_stacked_descendant_in_current_stacking_context_) { |
| if (child->GetLayoutObject().IsStacked()) { |
| has_stacked_descendant_in_current_stacking_context_ = true; |
| } else if (!child->GetLayoutObject().IsStackingContext()) { |
| has_stacked_descendant_in_current_stacking_context_ = |
| child->has_stacked_descendant_in_current_stacking_context_; |
| } |
| } |
| |
| has_self_painting_layer_descendant_ = |
| has_self_painting_layer_descendant_ || |
| child->HasSelfPaintingLayerDescendant() || |
| child->IsSelfPaintingLayer(); |
| } |
| |
| UpdateStackingNode(); |
| |
| if (old_has_non_isolated_descendant_with_blend_mode != |
| static_cast<bool>(has_non_isolated_descendant_with_blend_mode_)) { |
| // The LayoutView DisplayItemClient owns painting of the background |
| // of the HTML element. When blending isolation of the HTML element's |
| // descendants change, there will be an addition or removal of an |
| // isolation effect node for the HTML element to add (or remove) |
| // isolated blending, and that case we need to re-paint the LayoutView. |
| if (Parent() && Parent()->IsRootLayer()) |
| GetLayoutObject().View()->SetBackgroundNeedsFullPaintInvalidation(); |
| GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| } |
| needs_descendant_dependent_flags_update_ = false; |
| |
| if (IsSelfPaintingLayer() && needs_visual_overflow_recalc_) { |
| PhysicalRect old_visual_rect = |
| PhysicalVisualOverflowRectAllowingUnset(GetLayoutObject()); |
| GetLayoutObject().RecalcVisualOverflow(); |
| if (old_visual_rect != GetLayoutObject().PhysicalVisualOverflowRect()) { |
| SetNeedsCompositingInputsUpdateInternal(); |
| MarkAncestorChainForFlagsUpdate(DoesNotNeedDescendantDependentUpdate); |
| } |
| } |
| needs_visual_overflow_recalc_ = false; |
| } |
| |
| bool previously_has_visible_content = has_visible_content_; |
| if (GetLayoutObject().StyleRef().Visibility() == EVisibility::kVisible) { |
| has_visible_content_ = true; |
| } else { |
| // layer may be hidden but still have some visible content, check for this |
| has_visible_content_ = false; |
| LayoutObject* r = GetLayoutObject().SlowFirstChild(); |
| while (r) { |
| if (r->StyleRef().Visibility() == EVisibility::kVisible && |
| (!r->HasLayer() || !r->EnclosingLayer()->IsSelfPaintingLayer())) { |
| has_visible_content_ = true; |
| break; |
| } |
| LayoutObject* layout_object_first_child = r->SlowFirstChild(); |
| if (layout_object_first_child && |
| (!r->HasLayer() || !r->EnclosingLayer()->IsSelfPaintingLayer())) { |
| r = layout_object_first_child; |
| } else if (r->NextSibling()) { |
| r = r->NextSibling(); |
| } else { |
| do { |
| r = r->Parent(); |
| if (r == &GetLayoutObject()) |
| r = nullptr; |
| } while (r && !r->NextSibling()); |
| if (r) |
| r = r->NextSibling(); |
| } |
| } |
| } |
| |
| if (HasVisibleContent() != previously_has_visible_content) { |
| SetNeedsCompositingInputsUpdateInternal(); |
| // We need to tell layout_object_ to recheck its rect because we |
| // pretend that invisible LayoutObjects have 0x0 rects. Changing |
| // visibility therefore changes our rect and we need to visit |
| // this LayoutObject during the PrePaintTreeWalk. |
| layout_object_->SetShouldCheckForPaintInvalidation(); |
| } |
| |
| Update3DTransformedDescendantStatus(); |
| } |
| |
| void PaintLayer::Update3DTransformedDescendantStatus() { |
| has3d_transformed_descendant_ = false; |
| |
| // Transformed or preserve-3d descendants can only be in the z-order lists, |
| // not in the normal flow list, so we only need to check those. |
| PaintLayerPaintOrderIterator iterator(*this, kStackedChildren); |
| while (PaintLayer* child_layer = iterator.Next()) { |
| bool child_has3d = false; |
| // If the child lives in a 3d hierarchy, then the layer at the root of |
| // that hierarchy needs the m_has3DTransformedDescendant set. |
| if (child_layer->Preserves3D() && |
| (child_layer->Has3DTransform() || |
| child_layer->Has3DTransformedDescendant())) |
| child_has3d = true; |
| else if (child_layer->Has3DTransform()) |
| child_has3d = true; |
| |
| if (child_has3d) { |
| has3d_transformed_descendant_ = true; |
| break; |
| } |
| } |
| } |
| |
| void PaintLayer::UpdateLayerPosition() { |
| // LayoutBoxes will call UpdateSizeAndScrollingAfterLayout() from |
| // LayoutBox::UpdateAfterLayout, but LayoutInlines will still need to update |
| // their size. |
| if (GetLayoutObject().IsLayoutInline()) |
| UpdateSize(); |
| PhysicalOffset local_point; |
| if (LayoutBox* box = GetLayoutBox()) { |
| local_point += box->PhysicalLocation(); |
| } |
| |
| if (!GetLayoutObject().IsOutOfFlowPositioned() && |
| !GetLayoutObject().IsColumnSpanAll()) { |
| // We must adjust our position by walking up the layout tree looking for the |
| // nearest enclosing object with a layer. |
| LayoutObject* curr = GetLayoutObject().Container(); |
| while (curr && !curr->HasLayer()) { |
| if (curr->IsBox() && !curr->IsLegacyTableRow()) { |
| // Rows and cells share the same coordinate space (that of the section). |
| // Omit them when computing our xpos/ypos. |
| local_point += To<LayoutBox>(curr)->PhysicalLocation(); |
| } |
| curr = curr->Container(); |
| } |
| if (curr && curr->IsLegacyTableRow()) { |
| // Put ourselves into the row coordinate space. |
| local_point -= To<LayoutBox>(curr)->PhysicalLocation(); |
| } |
| } |
| |
| if (PaintLayer* containing_layer = ContainingLayer()) { |
| auto& container = containing_layer->GetLayoutObject(); |
| if (GetLayoutObject().IsOutOfFlowPositioned() && |
| container.IsLayoutInline() && |
| container.CanContainOutOfFlowPositionedElement( |
| GetLayoutObject().StyleRef().GetPosition())) { |
| // Adjust offset for absolute under in-flow positioned inline. |
| PhysicalOffset offset = |
| To<LayoutInline>(container).OffsetForInFlowPositionedInline( |
| To<LayoutBox>(GetLayoutObject())); |
| local_point += offset; |
| } |
| } |
| |
| if (GetLayoutObject().IsInFlowPositioned() && |
| GetLayoutObject().IsRelPositioned()) { |
| auto new_offset = GetLayoutObject().OffsetForInFlowPosition(); |
| if (rare_data_ || !new_offset.IsZero()) |
| EnsureRareData().offset_for_in_flow_rel_position = new_offset; |
| } else if (rare_data_) { |
| rare_data_->offset_for_in_flow_rel_position = PhysicalOffset(); |
| } |
| location_without_position_offset_ = local_point; |
| |
| #if DCHECK_IS_ON() |
| needs_position_update_ = false; |
| #endif |
| } |
| |
| bool PaintLayer::UpdateSize() { |
| LayoutSize old_size = size_; |
| if (IsRootLayer()) { |
| size_ = LayoutSize(GetLayoutObject().GetDocument().View()->Size()); |
| } else if (GetLayoutObject().IsInline() && |
| GetLayoutObject().IsLayoutInline()) { |
| auto& inline_flow = To<LayoutInline>(GetLayoutObject()); |
| IntRect line_box = EnclosingIntRect(inline_flow.PhysicalLinesBoundingBox()); |
| size_ = LayoutSize(line_box.Size()); |
| } else if (LayoutBox* box = GetLayoutBox()) { |
| size_ = box->Size(); |
| } |
| if (old_size != size_) |
| SetNeedsCompositingInputsUpdate(); |
| |
| return old_size != size_; |
| } |
| |
| void PaintLayer::UpdateSizeAndScrollingAfterLayout() { |
| bool did_resize = UpdateSize(); |
| if (RequiresScrollableArea()) { |
| DCHECK(scrollable_area_); |
| scrollable_area_->UpdateAfterLayout(); |
| if (did_resize) |
| scrollable_area_->VisibleSizeChanged(); |
| } |
| } |
| |
| PaintLayer* PaintLayer::ContainingLayer(const PaintLayer* ancestor, |
| bool* skipped_ancestor) const { |
| // If we have specified an ancestor, surely the caller needs to know whether |
| // we skipped it. |
| DCHECK(!ancestor || skipped_ancestor); |
| if (skipped_ancestor) |
| *skipped_ancestor = false; |
| |
| LayoutObject& layout_object = GetLayoutObject(); |
| if (layout_object.IsOutOfFlowPositioned()) { |
| // In NG, the containing block chain goes directly from a column spanner to |
| // the multi-column container. Thus, for an OOF nested inside a spanner, we |
| // need to find its containing layer through its containing block to handle |
| // this case correctly. Therefore, we technically only need to take this |
| // path for OOFs inside an NG spanner. However, doing so for all OOF |
| // descendants of a multicol container is reasonable enough. |
| if (layout_object.IsInsideFlowThread()) |
| return SlowContainingLayer(ancestor, skipped_ancestor, &layout_object); |
| auto can_contain_this_layer = |
| layout_object.IsFixedPositioned() |
| ? &LayoutObject::CanContainFixedPositionObjects |
| : &LayoutObject::CanContainAbsolutePositionObjects; |
| |
| PaintLayer* curr = Parent(); |
| while (curr && !((&curr->GetLayoutObject())->*can_contain_this_layer)()) { |
| if (skipped_ancestor && curr == ancestor) |
| *skipped_ancestor = true; |
| curr = curr->Parent(); |
| } |
| return curr; |
| } |
| |
| // If the parent layer is not a block, there might be floating objects |
| // between this layer (included) and parent layer which need to escape the |
| // inline parent to find the actual containing layer through the containing |
| // block chain. |
| // Column span need to find the containing layer through its containing block. |
| if ((!Parent() || Parent()->GetLayoutObject().IsLayoutBlock()) && |
| !layout_object.IsColumnSpanAll()) |
| return Parent(); |
| |
| return SlowContainingLayer(ancestor, skipped_ancestor, &layout_object); |
| } |
| |
| PhysicalOffset PaintLayer::ComputeOffsetFromAncestor( |
| const PaintLayer& ancestor_layer) const { |
| const LayoutBoxModelObject& ancestor_object = |
| ancestor_layer.GetLayoutObject(); |
| PhysicalOffset result = GetLayoutObject().LocalToAncestorPoint( |
| PhysicalOffset(), &ancestor_object, kIgnoreTransforms); |
| if (ancestor_object.UsesCompositedScrolling()) { |
| result += PhysicalOffset( |
| To<LayoutBox>(ancestor_object).PixelSnappedScrolledContentOffset()); |
| } |
| return result; |
| } |
| |
| PaintLayer* PaintLayer::CompositingContainer() const { |
| if (IsReplacedNormalFlowStacking()) |
| return Parent(); |
| if (!GetLayoutObject().IsStacked()) { |
| if (IsSelfPaintingLayer() || GetLayoutObject().IsColumnSpanAll()) |
| return Parent(); |
| return ContainingLayer(); |
| } |
| return AncestorStackingContext(); |
| } |
| |
| PaintLayer* PaintLayer::AncestorStackingContext() const { |
| for (PaintLayer* ancestor = Parent(); ancestor; |
| ancestor = ancestor->Parent()) { |
| if (ancestor->GetLayoutObject().IsStackingContext()) |
| return ancestor; |
| } |
| return nullptr; |
| } |
| |
| bool PaintLayer::IsPaintInvalidationContainer() const { |
| return GetCompositingState() == kPaintsIntoOwnBacking || |
| GetCompositingState() == kPaintsIntoGroupedBacking; |
| } |
| |
| // Note: enclosingCompositingLayer does not include squashed layers. Compositing |
| // stacking children of squashed layers receive graphics layers that are |
| // parented to the compositing ancestor of the squashed layer. |
| PaintLayer* PaintLayer::EnclosingLayerWithCompositedLayerMapping( |
| IncludeSelfOrNot include_self) const { |
| DCHECK(IsAllowedToQueryCompositingState()); |
| |
| if ((include_self == kIncludeSelf) && |
| GetCompositingState() != kNotComposited && |
| GetCompositingState() != kPaintsIntoGroupedBacking) |
| return const_cast<PaintLayer*>(this); |
| |
| for (PaintLayer* curr = CompositingContainer(); curr; |
| curr = curr->CompositingContainer()) { |
| if (curr->GetCompositingState() != kNotComposited && |
| curr->GetCompositingState() != kPaintsIntoGroupedBacking) |
| return curr; |
| } |
| |
| return nullptr; |
| } |
| |
| // Return the enclosingCompositedLayerForPaintInvalidation for the given Layer |
| // including crossing frame boundaries. |
| PaintLayer* |
| PaintLayer::EnclosingLayerForPaintInvalidationCrossingFrameBoundaries() const { |
| const PaintLayer* layer = this; |
| PaintLayer* composited_layer = nullptr; |
| while (!composited_layer) { |
| composited_layer = layer->EnclosingLayerForPaintInvalidation(); |
| if (!composited_layer) { |
| CHECK(layer->GetLayoutObject().GetFrame()); |
| auto* owner = layer->GetLayoutObject().GetFrame()->OwnerLayoutObject(); |
| if (!owner) |
| break; |
| layer = owner->EnclosingLayer(); |
| } |
| } |
| return composited_layer; |
| } |
| |
| PaintLayer* PaintLayer::EnclosingLayerForPaintInvalidation() const { |
| DCHECK(IsAllowedToQueryCompositingState()); |
| |
| if (IsPaintInvalidationContainer()) |
| return const_cast<PaintLayer*>(this); |
| |
| for (PaintLayer* curr = CompositingContainer(); curr; |
| curr = curr->CompositingContainer()) { |
| if (curr->IsPaintInvalidationContainer()) |
| return curr; |
| } |
| |
| return nullptr; |
| } |
| |
| bool PaintLayer::CanBeCompositedForDirectReasons() const { |
| return DirectCompositingReasons() && IsSelfPaintingLayer(); |
| } |
| |
| bool PaintLayer::CanBeComposited() const { |
| LocalFrameView* frame_view = GetLayoutObject().GetFrameView(); |
| // Elements within an invisible frame must not be composited because they are |
| // not drawn. |
| if (frame_view && !frame_view->IsVisible()) |
| return false; |
| |
| DCHECK(!frame_view->ShouldThrottleRendering()); |
| |
| const bool has_compositor_animation = |
| CompositingReasonFinder::CompositingReasonsForAnimation( |
| GetLayoutObject()) != CompositingReason::kNone; |
| |
| return frame_view->GetFrame() |
| .GetSettings() |
| ->GetAcceleratedCompositingEnabled() && |
| (has_compositor_animation || !SubtreeIsInvisible()) && |
| IsSelfPaintingLayer() && !GetLayoutObject().IsLayoutFlowThread() && |
| // Don't composite <foreignObject> for the moment, to reduce instances |
| // of the "fundamental compositing bug" breaking painting order. |
| // With CompositeSVG, foreignObjects will be correctly composited after |
| // paint in PaintArtifactCompositor without a GraphicsLayer. |
| // Composited descendants of foreignObject will still break painting |
| // order which will be fixed in CompositeAfterPaint. |
| !GetLayoutObject().IsSVGForeignObject(); |
| } |
| |
| PaintLayer* |
| PaintLayer::EnclosingDirectlyCompositableLayerCrossingFrameBoundaries() const { |
| const PaintLayer* layer = this; |
| PaintLayer* composited_layer = nullptr; |
| while (!composited_layer) { |
| composited_layer = layer->EnclosingDirectlyCompositableLayer(kIncludeSelf); |
| if (!composited_layer) { |
| CHECK(layer->GetLayoutObject().GetFrame()); |
| auto* owner = layer->GetLayoutObject().GetFrame()->OwnerLayoutObject(); |
| if (!owner) |
| break; |
| layer = owner->EnclosingLayer(); |
| } |
| } |
| return composited_layer; |
| } |
| |
| PaintLayer* PaintLayer::EnclosingDirectlyCompositableLayer( |
| IncludeSelfOrNot include_self_or_not) const { |
| DCHECK(IsAllowedToQueryCompositingInputs()); |
| if (include_self_or_not == kIncludeSelf && CanBeCompositedForDirectReasons()) |
| return const_cast<PaintLayer*>(this); |
| |
| for (PaintLayer* curr = CompositingContainer(); curr; |
| curr = curr->CompositingContainer()) { |
| if (curr->CanBeCompositedForDirectReasons()) |
| return curr; |
| } |
| |
| return nullptr; |
| } |
| |
| void PaintLayer::SetNeedsCompositingInputsUpdate(bool mark_ancestor_flags) { |
| SetNeedsCompositingInputsUpdateInternal(); |
| |
| // TODO(chrishtr): These are a bit of a heavy hammer, because not all |
| // things which require compositing inputs update require a descendant- |
| // dependent flags update. Reduce call sites after CAP launch allows |
| /// removal of CompositingInputsUpdater. |
| if (mark_ancestor_flags) |
| MarkAncestorChainForFlagsUpdate(NeedsDescendantDependentUpdate); |
| } |
| |
| void PaintLayer::SetNeedsGraphicsLayerRebuild() { |
| if (Compositor()) |
| Compositor()->SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| } |
| |
| void PaintLayer::SetNeedsCheckRasterInvalidation() { |
| DCHECK_EQ(GetLayoutObject().GetDocument().Lifecycle().GetState(), |
| DocumentLifecycle::kInPrePaint); |
| needs_check_raster_invalidation_ = true; |
| // We need to mark |this| as needing layer assignment also, because |
| // CompositingLayerAssigner is where we transfer the raster invalidation |
| // checking bit from PaintLayer to GraphicsLayer. |
| SetNeedsCompositingLayerAssignment(); |
| } |
| |
| void PaintLayer::SetNeedsVisualOverflowRecalc() { |
| DCHECK(IsSelfPaintingLayer()); |
| #if DCHECK_IS_ON() |
| GetLayoutObject().InvalidateVisualOverflow(); |
| #endif |
| needs_visual_overflow_recalc_ = true; |
| MarkAncestorChainForFlagsUpdate(); |
| } |
| |
| void PaintLayer::SetChildNeedsCompositingInputsUpdateUpToAncestor( |
| PaintLayer* ancestor) { |
| DCHECK(ancestor); |
| |
| for (auto* layer = this; layer && layer != ancestor; layer = layer->Parent()) |
| layer->child_needs_compositing_inputs_update_ = true; |
| |
| ancestor->child_needs_compositing_inputs_update_ = true; |
| } |
| |
| const IntRect PaintLayer::ClippedAbsoluteBoundingBox() const { |
| PhysicalRect mapping_rect = LocalBoundingBoxForCompositingOverlapTest(); |
| GetLayoutObject().MapToVisualRectInAncestorSpace( |
| GetLayoutObject().View(), mapping_rect, kUseGeometryMapper); |
| return PixelSnappedIntRect(mapping_rect); |
| } |
| const IntRect PaintLayer::UnclippedAbsoluteBoundingBox() const { |
| return EnclosingIntRect(GetLayoutObject().LocalToAbsoluteRect( |
| LocalBoundingBoxForCompositingOverlapTest(), |
| kUseGeometryMapperMode | kIgnoreScrollOffsetOfAncestor)); |
| } |
| |
| void PaintLayer::SetNeedsCompositingInputsUpdateInternal() { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| needs_ancestor_dependent_compositing_inputs_update_ = true; |
| |
| // We might call this function on a locked element. Now, locked elements might |
| // have a persistent dirty child bit, meaning that the below loop won't mark |
| // the breakcrumb bit further up the chain (since this element appears to |
| // already have a breadcrumb). However, since the element itself needs an |
| // ancestor dependent update, we need to force the propagation at least one |
| // level to the parent. This ensures that the real dirty bit |
| // (|needs_ancestor_dependent_compositing_inputs_update_|) can be discovered |
| // by the compositing update walk. |
| bool child_flag_may_persist_after_update = |
| GetLayoutObject().ChildPrePaintBlockedByDisplayLock(); |
| |
| PaintLayer* initial_layer = child_needs_compositing_inputs_update_ && |
| child_flag_may_persist_after_update |
| ? Parent() |
| : this; |
| |
| PaintLayer* last_ancestor = nullptr; |
| for (PaintLayer* current = initial_layer; |
| current && !current->child_needs_compositing_inputs_update_; |
| current = current->Parent()) { |
| last_ancestor = current; |
| current->child_needs_compositing_inputs_update_ = true; |
| if (Compositor() && |
| (current != initial_layer || |
| !current->GetLayoutObject().IsStickyPositioned()) && |
| current->GetLayoutObject().ShouldApplyStrictContainment()) |
| break; |
| } |
| |
| if (Compositor()) { |
| Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateAfterCompositingInputChange); |
| |
| if (last_ancestor) |
| Compositor()->UpdateCompositingInputsRoot(last_ancestor); |
| } |
| } |
| |
| void PaintLayer::UpdateAncestorDependentCompositingInputs( |
| const PaintLayer* opacity_ancestor, |
| const PaintLayer* transform_ancestor, |
| const PaintLayer* filter_ancestor, |
| const PaintLayer* clip_path_ancestor, |
| const PaintLayer* mask_ancestor, |
| const PaintLayer* ancestor_scrolling_layer, |
| const PaintLayer* nearest_fixed_position_layer, |
| const PaintLayer* scroll_parent, |
| const PaintLayer* clip_parent, |
| const PaintLayer* nearest_contained_layout_layer, |
| const LayoutBoxModelObject* clipping_container) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| if (!ancestor_dependent_compositing_inputs_) { |
| ancestor_dependent_compositing_inputs_ = |
| std::make_unique<AncestorDependentCompositingInputs>(); |
| } |
| ancestor_dependent_compositing_inputs_->opacity_ancestor = opacity_ancestor; |
| ancestor_dependent_compositing_inputs_->transform_ancestor = |
| transform_ancestor; |
| ancestor_dependent_compositing_inputs_->filter_ancestor = filter_ancestor; |
| ancestor_dependent_compositing_inputs_->clip_path_ancestor = |
| clip_path_ancestor; |
| ancestor_dependent_compositing_inputs_->mask_ancestor = mask_ancestor; |
| ancestor_dependent_compositing_inputs_->ancestor_scrolling_layer = |
| ancestor_scrolling_layer; |
| ancestor_dependent_compositing_inputs_->nearest_fixed_position_layer = |
| nearest_fixed_position_layer; |
| ancestor_dependent_compositing_inputs_->scroll_parent = scroll_parent; |
| ancestor_dependent_compositing_inputs_->clip_parent = clip_parent; |
| ancestor_dependent_compositing_inputs_->nearest_contained_layout_layer = |
| nearest_contained_layout_layer; |
| ancestor_dependent_compositing_inputs_->clipping_container = |
| clipping_container; |
| needs_ancestor_dependent_compositing_inputs_update_ = false; |
| } |
| |
| void PaintLayer::ClearChildNeedsCompositingInputsUpdate() { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| DCHECK(!NeedsCompositingInputsUpdate()); |
| child_needs_compositing_inputs_update_ = false; |
| } |
| |
| bool PaintLayer::HasNonIsolatedDescendantWithBlendMode() const { |
| DCHECK(!needs_descendant_dependent_flags_update_); |
| if (has_non_isolated_descendant_with_blend_mode_) |
| return true; |
| if (GetLayoutObject().IsSVGRoot()) { |
| return To<LayoutSVGRoot>(GetLayoutObject()) |
| .HasNonIsolatedBlendingDescendants(); |
| } |
| return false; |
| } |
| |
| void PaintLayer::SetCompositingReasons(CompositingReasons reasons, |
| CompositingReasons mask) { |
| CompositingReasons old_reasons = |
| rare_data_ ? rare_data_->compositing_reasons : CompositingReason::kNone; |
| if ((old_reasons & mask) == (reasons & mask)) |
| return; |
| CompositingReasons new_reasons = (reasons & mask) | (old_reasons & ~mask); |
| if (rare_data_ || new_reasons != CompositingReason::kNone) |
| EnsureRareData().compositing_reasons = new_reasons; |
| } |
| |
| void PaintLayer::SetSquashingDisallowedReasons( |
| SquashingDisallowedReasons reasons) { |
| SquashingDisallowedReasons old_reasons = |
| rare_data_ ? rare_data_->squashing_disallowed_reasons |
| : SquashingDisallowedReason::kNone; |
| if (old_reasons == reasons) |
| return; |
| if (rare_data_ || reasons != SquashingDisallowedReason::kNone) |
| EnsureRareData().squashing_disallowed_reasons = reasons; |
| } |
| |
| void PaintLayer::SetHasCompositingDescendant(bool has_compositing_descendant) { |
| if (has_compositing_descendant_ == |
| static_cast<unsigned>(has_compositing_descendant)) |
| return; |
| |
| has_compositing_descendant_ = has_compositing_descendant; |
| |
| if (HasCompositedLayerMapping()) |
| GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateLocal); |
| } |
| |
| void PaintLayer::SetShouldIsolateCompositedDescendants( |
| bool should_isolate_composited_descendants) { |
| if (should_isolate_composited_descendants_ == |
| static_cast<unsigned>(should_isolate_composited_descendants)) |
| return; |
| |
| should_isolate_composited_descendants_ = |
| should_isolate_composited_descendants; |
| |
| if (HasCompositedLayerMapping()) |
| GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateLocal); |
| } |
| |
| bool PaintLayer::HasAncestorWithFilterThatMovesPixels() const { |
| for (const PaintLayer* curr = this; curr; curr = curr->Parent()) { |
| if (curr->HasFilterThatMovesPixels()) |
| return true; |
| } |
| return false; |
| } |
| |
| void* PaintLayer::operator new(size_t sz) { |
| return WTF::Partitions::LayoutPartition()->Alloc( |
| sz, WTF_HEAP_PROFILER_TYPE_NAME(PaintLayer)); |
| } |
| |
| void PaintLayer::operator delete(void* ptr) { |
| WTF::Partitions::LayoutPartition()->Free(ptr); |
| } |
| |
| void PaintLayer::AddChild(PaintLayer* child, PaintLayer* before_child) { |
| #if DCHECK_IS_ON() |
| DCHECK(layer_list_mutation_allowed_); |
| #endif |
| |
| PaintLayer* prev_sibling = |
| before_child ? before_child->PreviousSibling() : LastChild(); |
| if (prev_sibling) { |
| child->SetPreviousSibling(prev_sibling); |
| prev_sibling->SetNextSibling(child); |
| DCHECK_NE(prev_sibling, child); |
| } else { |
| SetFirstChild(child); |
| } |
| |
| if (before_child) { |
| before_child->SetPreviousSibling(child); |
| child->SetNextSibling(before_child); |
| DCHECK_NE(before_child, child); |
| } else { |
| SetLastChild(child); |
| } |
| |
| child->parent_ = this; |
| |
| // The ancestor scroll container layer is calculated during compositing inputs |
| // update and should not be set yet. |
| CHECK(!child->AncestorScrollContainerLayer()); |
| |
| SetNeedsCompositingInputsUpdate(); |
| |
| if (Compositor()) { |
| if (!child->GetLayoutObject().IsStacked() && |
| !GetLayoutObject().DocumentBeingDestroyed()) |
| Compositor()->SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| } |
| |
| if (child->GetLayoutObject().IsStacked() || child->FirstChild()) { |
| // Dirty the z-order list in which we are contained. The |
| // ancestorStackingContextNode() can be null in the case where we're |
| // building up generated content layers. This is ok, since the lists will |
| // start off dirty in that case anyway. |
| child->DirtyStackingContextZOrderLists(); |
| } |
| |
| // Non-self-painting children paint into this layer, so the visible contents |
| // status of this layer is affected. |
| if (!child->IsSelfPaintingLayer()) |
| DirtyVisibleContentStatus(); |
| |
| MarkAncestorChainForFlagsUpdate(); |
| |
| // Need to force requirements update, due to change of stacking order. |
| SetNeedsCompositingRequirementsUpdate(); |
| |
| child->SetNeedsRepaint(); |
| } |
| |
| void PaintLayer::RemoveChild(PaintLayer* old_child) { |
| #if DCHECK_IS_ON() |
| DCHECK(layer_list_mutation_allowed_); |
| #endif |
| |
| old_child->MarkCompositingContainerChainForNeedsRepaint(); |
| |
| if (old_child->PreviousSibling()) |
| old_child->PreviousSibling()->SetNextSibling(old_child->NextSibling()); |
| if (old_child->NextSibling()) |
| old_child->NextSibling()->SetPreviousSibling(old_child->PreviousSibling()); |
| |
| if (first_ == old_child) |
| first_ = old_child->NextSibling(); |
| if (last_ == old_child) |
| last_ = old_child->PreviousSibling(); |
| |
| if (!GetLayoutObject().DocumentBeingDestroyed()) { |
| if (Compositor()) { |
| if (!old_child->GetLayoutObject().IsStacked()) |
| Compositor()->SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| |
| if (Compositor()->GetCompositingInputsRoot() == old_child) |
| Compositor()->ClearCompositingInputsRoot(); |
| } |
| // Dirty the z-order list in which we are contained. |
| old_child->DirtyStackingContextZOrderLists(); |
| SetNeedsCompositingInputsUpdate(); |
| } |
| |
| if (GetLayoutObject().StyleRef().Visibility() != EVisibility::kVisible) |
| DirtyVisibleContentStatus(); |
| |
| old_child->SetPreviousSibling(nullptr); |
| old_child->SetNextSibling(nullptr); |
| old_child->parent_ = nullptr; |
| |
| // Remove any ancestor scroll container layers which descended into the |
| // removed child. |
| if (old_child->AncestorScrollContainerLayer()) { |
| old_child->RemoveAncestorScrollContainerLayer( |
| old_child->AncestorScrollContainerLayer()); |
| } |
| |
| if (old_child->has_visible_content_ || old_child->has_visible_descendant_) |
| MarkAncestorChainForFlagsUpdate(); |
| |
| if (old_child->EnclosingPaginationLayer()) |
| old_child->ClearPaginationRecursive(); |
| } |
| |
| void PaintLayer::RemoveOnlyThisLayerAfterStyleChange( |
| const ComputedStyle* old_style) { |
| if (!parent_) |
| return; |
| |
| if (old_style && GetLayoutObject().IsStacked(*old_style)) |
| DirtyStackingContextZOrderLists(); |
| |
| bool did_set_paint_invalidation = false; |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // Destructing PaintLayer would cause CompositedLayerMapping and composited |
| // layers to be destructed and detach from layer tree immediately. Layers |
| // could have dangling scroll/clip parent if compositing update were |
| // omitted. |
| if (LocalFrameView* frame_view = layout_object_->GetDocument().View()) |
| frame_view->SetNeedsForcedCompositingUpdate(); |
| |
| // We need the current compositing status. |
| DisableCompositingQueryAsserts disabler; |
| if (IsPaintInvalidationContainer()) { |
| // Our children will be reparented and contained by a new paint |
| // invalidation container, so need paint invalidation. CompositingUpdate |
| // can't see this layer (which has been removed) so won't do this for us. |
| ObjectPaintInvalidator(GetLayoutObject()) |
| .InvalidatePaintIncludingNonCompositingDescendants(); |
| GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation(); |
| did_set_paint_invalidation = true; |
| } |
| } |
| |
| if (!did_set_paint_invalidation && IsSelfPaintingLayer()) { |
| if (PaintLayer* enclosing_self_painting_layer = |
| parent_->EnclosingSelfPaintingLayer()) |
| enclosing_self_painting_layer->MergeNeedsPaintPhaseFlagsFrom(*this); |
| } |
| |
| PaintLayer* next_sib = NextSibling(); |
| |
| // Now walk our kids and reattach them to our parent. |
| PaintLayer* current = first_; |
| while (current) { |
| PaintLayer* next = current->NextSibling(); |
| RemoveChild(current); |
| parent_->AddChild(current, next_sib); |
| current = next; |
| } |
| |
| // Remove us from the parent. |
| parent_->RemoveChild(this); |
| layout_object_->DestroyLayer(); |
| } |
| |
| void PaintLayer::InsertOnlyThisLayerAfterStyleChange() { |
| if (!parent_ && GetLayoutObject().Parent()) { |
| // We need to connect ourselves when our layoutObject() has a parent. |
| // Find our enclosingLayer and add ourselves. |
| PaintLayer* parent_layer = GetLayoutObject().Parent()->EnclosingLayer(); |
| DCHECK(parent_layer); |
| PaintLayer* before_child = GetLayoutObject().Parent()->FindNextLayer( |
| parent_layer, &GetLayoutObject()); |
| parent_layer->AddChild(this, before_child); |
| } |
| |
| // Remove all descendant layers from the hierarchy and add them to the new |
| // position. |
| for (LayoutObject* curr = GetLayoutObject().SlowFirstChild(); curr; |
| curr = curr->NextSibling()) |
| curr->MoveLayers(parent_, this); |
| |
| if (IsSelfPaintingLayer() && parent_) { |
| if (PaintLayer* enclosing_self_painting_layer = |
| parent_->EnclosingSelfPaintingLayer()) |
| MergeNeedsPaintPhaseFlagsFrom(*enclosing_self_painting_layer); |
| } |
| } |
| |
| // Returns the layer reached on the walk up towards the ancestor. |
| static inline const PaintLayer* AccumulateOffsetTowardsAncestor( |
| const PaintLayer* layer, |
| const PaintLayer* ancestor_layer, |
| PhysicalOffset& location) { |
| DCHECK(ancestor_layer != layer); |
| |
| const LayoutBoxModelObject& layout_object = layer->GetLayoutObject(); |
| |
| if (layout_object.IsFixedPositioned() && |
| (!ancestor_layer || ancestor_layer == layout_object.View()->Layer())) { |
| // If the fixed layer's container is the root, just add in the offset of the |
| // view. We can obtain this by calling localToAbsolute() on the LayoutView. |
| location += |
| layout_object.LocalToAbsolutePoint(PhysicalOffset(), kIgnoreTransforms); |
| return ancestor_layer; |
| } |
| |
| bool found_ancestor_first = false; |
| PaintLayer* containing_layer = |
| ancestor_layer |
| ? layer->ContainingLayer(ancestor_layer, &found_ancestor_first) |
| : layer->ContainingLayer(ancestor_layer, nullptr); |
| |
| if (found_ancestor_first) { |
| // Found ancestorLayer before the containing layer, so compute offset of |
| // both relative to the container and subtract. |
| PhysicalOffset this_coords; |
| layer->ConvertToLayerCoords(containing_layer, this_coords); |
| |
| PhysicalOffset ancestor_coords; |
| ancestor_layer->ConvertToLayerCoords(containing_layer, ancestor_coords); |
| |
| location += (this_coords - ancestor_coords); |
| return ancestor_layer; |
| } |
| |
| if (!containing_layer) |
| return nullptr; |
| |
| location += layer->LocationWithoutPositionOffset(); |
| if (layer->GetLayoutObject().IsRelPositioned()) { |
| location += layer->OffsetForInFlowRelPosition(); |
| } else if (layer->GetLayoutObject().IsInFlowPositioned()) { |
| location += layer->GetLayoutObject().OffsetForInFlowPosition(); |
| } |
| location -= |
| PhysicalOffset(containing_layer->PixelSnappedScrolledContentOffset()); |
| |
| return containing_layer; |
| } |
| |
| void PaintLayer::ConvertToLayerCoords(const PaintLayer* ancestor_layer, |
| PhysicalOffset& location) const { |
| if (ancestor_layer == this) |
| return; |
| |
| const PaintLayer* curr_layer = this; |
| while (curr_layer && curr_layer != ancestor_layer) |
| curr_layer = |
| AccumulateOffsetTowardsAncestor(curr_layer, ancestor_layer, location); |
| } |
| |
| void PaintLayer::ConvertToLayerCoords(const PaintLayer* ancestor_layer, |
| PhysicalRect& rect) const { |
| PhysicalOffset delta; |
| ConvertToLayerCoords(ancestor_layer, delta); |
| rect.Move(delta); |
| } |
| |
| PhysicalOffset PaintLayer::VisualOffsetFromAncestor( |
| const PaintLayer* ancestor_layer, |
| PhysicalOffset offset) const { |
| if (ancestor_layer == this) |
| return offset; |
| PaintLayer* pagination_layer = EnclosingPaginationLayer(); |
| if (pagination_layer == this) |
| pagination_layer = Parent()->EnclosingPaginationLayer(); |
| if (!pagination_layer) { |
| ConvertToLayerCoords(ancestor_layer, offset); |
| return offset; |
| } |
| |
| auto& flow_thread = To<LayoutFlowThread>(pagination_layer->GetLayoutObject()); |
| ConvertToLayerCoords(pagination_layer, offset); |
| offset = PhysicalOffsetToBeNoop( |
| flow_thread.FlowThreadPointToVisualPoint(offset.ToLayoutPoint())); |
| if (ancestor_layer == pagination_layer) |
| return offset; |
| |
| if (ancestor_layer->EnclosingPaginationLayer() != pagination_layer) { |
| offset += pagination_layer->VisualOffsetFromAncestor(ancestor_layer); |
| } else { |
| // The ancestor layer is also inside the pagination layer, so we need to |
| // subtract the visual distance from the ancestor layer to the pagination |
| // layer. |
| offset -= ancestor_layer->VisualOffsetFromAncestor(pagination_layer); |
| } |
| return offset; |
| } |
| |
| void PaintLayer::DidUpdateScrollsOverflow() { |
| UpdateSelfPaintingLayer(); |
| } |
| |
| void PaintLayer::UpdateStackingNode() { |
| #if DCHECK_IS_ON() |
| DCHECK(layer_list_mutation_allowed_); |
| #endif |
| |
| bool needs_stacking_node = |
| has_stacked_descendant_in_current_stacking_context_ && |
| GetLayoutObject().IsStackingContext(); |
| |
| if (needs_stacking_node != !!stacking_node_) { |
| if (needs_stacking_node) |
| stacking_node_ = std::make_unique<PaintLayerStackingNode>(*this); |
| else |
| stacking_node_.reset(); |
| } |
| |
| if (stacking_node_) |
| stacking_node_->UpdateZOrderLists(); |
| } |
| |
| bool PaintLayer::RequiresScrollableArea() const { |
| if (!GetLayoutBox()) |
| return false; |
| if (GetLayoutObject().IsScrollContainer()) |
| return true; |
| // Iframes with the resize property can be resized. This requires |
| // scroll corner painting, which is implemented, in part, by |
| // PaintLayerScrollableArea. |
| if (GetLayoutBox()->CanResize()) |
| return true; |
| // With custom scrollbars unfortunately we may need a PaintLayerScrollableArea |
| // to be able to calculate the size of scrollbar gutters. |
| const ComputedStyle& style = GetLayoutObject().StyleRef(); |
| if (style.IsScrollbarGutterStable() && |
| style.OverflowBlockDirection() == EOverflow::kHidden && |
| style.HasPseudoElementStyle(kPseudoIdScrollbar)) { |
| return true; |
| } |
| return false; |
| } |
| |
| void PaintLayer::UpdateScrollableArea() { |
| if (RequiresScrollableArea() == !!scrollable_area_) |
| return; |
| |
| if (!scrollable_area_) { |
| scrollable_area_ = MakeGarbageCollected<PaintLayerScrollableArea>(*this); |
| } else { |
| scrollable_area_->Dispose(); |
| scrollable_area_.Clear(); |
| GetLayoutObject().SetBackgroundPaintLocation( |
| kBackgroundPaintInGraphicsLayer); |
| } |
| |
| GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| // To clear z-ordering information of overlay overflow controls. |
| if (NeedsReorderOverlayOverflowControls()) |
| DirtyStackingContextZOrderLists(); |
| if (auto* compositor = Compositor()) |
| compositor->SetNeedsCompositingUpdate(kCompositingUpdateRebuildTree); |
| } |
| |
| bool PaintLayer::HasOverflowControls() const { |
| return scrollable_area_ && (scrollable_area_->HasScrollbar() || |
| scrollable_area_->ScrollCorner() || |
| GetLayoutObject().StyleRef().HasResize()); |
| } |
| |
| void PaintLayer::AppendSingleFragmentIgnoringPaginationForHitTesting( |
| PaintLayerFragments& fragments, |
| ShouldRespectOverflowClipType respect_overflow_clip) const { |
| PaintLayerFragment fragment; |
| fragment.fragment_data = &GetLayoutObject().FirstFragment(); |
| ClipRectsContext clip_rects_context(this, fragment.fragment_data, |
| kExcludeOverlayScrollbarSizeForHitTesting, |
| respect_overflow_clip); |
| Clipper(GeometryMapperOption::kUseGeometryMapper) |
| .CalculateRects(clip_rects_context, fragment.fragment_data, nullptr, |
| fragment.layer_bounds, fragment.background_rect, |
| fragment.foreground_rect); |
| if (GetLayoutObject().CanTraversePhysicalFragments()) { |
| // Make sure that we actually traverse the fragment tree, by providing a |
| // physical fragment. Otherwise we'd fall back to LayoutObject traversal. |
| if (const auto* layout_box = GetLayoutBox()) |
| fragment.physical_fragment = layout_box->GetPhysicalFragment(0); |
| } |
| |
| fragments.push_back(fragment); |
| } |
| |
| bool PaintLayer::ShouldFragmentCompositedBounds( |
| const PaintLayer* compositing_layer) const { |
| if (!EnclosingPaginationLayer()) |
| return false; |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return true; |
| if (Transform() && |
| !PaintsWithDirectReasonIntoOwnBacking(kGlobalPaintNormalPhase)) |
| return true; |
| if (!compositing_layer) { |
| compositing_layer = |
| EnclosingDirectlyCompositableLayerCrossingFrameBoundaries(); |
| } |
| if (!compositing_layer) |
| return true; |
| // Composited layers may not be fragmented. |
| return !compositing_layer->EnclosingPaginationLayer(); |
| } |
| |
| void PaintLayer::CollectFragments( |
| PaintLayerFragments& fragments, |
| const PaintLayer* root_layer, |
| const CullRect* painting_cull_rect, |
| OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior, |
| ShouldRespectOverflowClipType respect_overflow_clip, |
| const PhysicalOffset* offset_from_root, |
| const PhysicalOffset& sub_pixel_accumulation) const { |
| PaintLayerFragment fragment; |
| const auto& first_fragment_data = GetLayoutObject().FirstFragment(); |
| const auto& first_root_fragment_data = |
| root_layer->GetLayoutObject().FirstFragment(); |
| |
| // If both |this| and |root_layer| are fragmented and are inside the same |
| // pagination container, then try to match fragments from |root_layer| to |
| // |this|, so that any fragment clip for |root_layer|'s fragment matches |
| // |this|'s. Note we check both ShouldFragmentCompositedBounds() and next |
| // fragment here because the former may return false even if |this| is |
| // fragmented, e.g. for fixed-position objects in paged media, and the next |
| // fragment can be null even if the first fragment is actually in a fragmented |
| // context when the current layer appears in only one of the multiple |
| // fragments of the pagination container. |
| bool is_fragmented = |
| ShouldFragmentCompositedBounds() || first_fragment_data.NextFragment(); |
| bool should_match_fragments = |
| is_fragmented && |
| root_layer->EnclosingPaginationLayer() == EnclosingPaginationLayer(); |
| |
| // The inherited offset_from_root does not include any pagination offsets. |
| // In the presence of fragmentation, we cannot use it. |
| bool offset_from_root_can_be_used = offset_from_root && !is_fragmented; |
| wtf_size_t physical_fragment_idx = 0u; |
| for (auto* fragment_data = &first_fragment_data; fragment_data; |
| fragment_data = fragment_data->NextFragment(), physical_fragment_idx++) { |
| // If CullRectUpdateEnabled, skip fragment geometry logic if we are |
| // collecting fragments for painting. |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() || |
| !painting_cull_rect) { |
| const FragmentData* root_fragment_data; |
| if (root_layer == this) { |
| root_fragment_data = fragment_data; |
| } else if (should_match_fragments) { |
| for (root_fragment_data = &first_root_fragment_data; root_fragment_data; |
| root_fragment_data = root_fragment_data->NextFragment()) { |
| if (root_fragment_data->LogicalTopInFlowThread() == |
| fragment_data->LogicalTopInFlowThread()) |
| break; |
| } |
| } else { |
| root_fragment_data = &first_root_fragment_data; |
| } |
| |
| bool cant_find_fragment = !root_fragment_data; |
| if (cant_find_fragment) { |
| DCHECK(should_match_fragments); |
| // Fall back to the first fragment, in order to have |
| // PaintLayerClipper at least compute |fragment.layer_bounds|. |
| root_fragment_data = &first_root_fragment_data; |
| } |
| |
| ClipRectsContext clip_rects_context( |
| root_layer, root_fragment_data, overlay_scrollbar_clip_behavior, |
| respect_overflow_clip, sub_pixel_accumulation); |
| |
| absl::optional<CullRect> fragment_cull_rect; |
| if (painting_cull_rect) { |
| // |cull_rect| is in the coordinate space of |root_layer| (i.e. the |
| // space of |root_layer|'s first fragment). Map the rect to the space of |
| // the current root fragment. |
| auto rect = painting_cull_rect->Rect(); |
| first_root_fragment_data.MapRectToFragment(*root_fragment_data, rect); |
| fragment_cull_rect.emplace(rect); |
| } |
| |
| Clipper(GeometryMapperOption::kUseGeometryMapper) |
| .CalculateRects( |
| clip_rects_context, fragment_data, |
| fragment_cull_rect ? &*fragment_cull_rect : nullptr, |
| fragment.layer_bounds, fragment.background_rect, |
| fragment.foreground_rect, |
| offset_from_root_can_be_used ? offset_from_root : nullptr); |
| |
| if (cant_find_fragment) { |
| // If we couldn't find a matching fragment when |should_match_fragments| |
| // was true, then fall back to no clip. |
| fragment.background_rect.Reset(); |
| fragment.foreground_rect.Reset(); |
| } |
| |
| fragment.root_fragment_data = root_fragment_data; |
| } |
| |
| fragment.fragment_data = fragment_data; |
| |
| if (GetLayoutObject().CanTraversePhysicalFragments()) { |
| if (const auto* layout_box = GetLayoutBox()) { |
| fragment.physical_fragment = |
| layout_box->GetPhysicalFragment(physical_fragment_idx); |
| DCHECK(fragment.physical_fragment); |
| } |
| } |
| |
| fragments.push_back(fragment); |
| } |
| } |
| |
| PaintLayer::HitTestRecursionData::HitTestRecursionData( |
| const PhysicalRect& rect_arg, |
| const HitTestLocation& location_arg, |
| const HitTestLocation& original_location_arg) |
| : rect(rect_arg), |
| location(location_arg), |
| original_location(original_location_arg), |
| intersects_location(location_arg.Intersects(rect_arg)) {} |
| |
| bool PaintLayer::HitTest(const HitTestLocation& hit_test_location, |
| HitTestResult& result, |
| const PhysicalRect& hit_test_area) { |
| DCHECK(IsSelfPaintingLayer() || HasSelfPaintingLayerDescendant()); |
| |
| // LayoutView should make sure to update layout before entering hit testing |
| DCHECK(!GetLayoutObject().GetFrame()->View()->LayoutPending()); |
| DCHECK(!GetLayoutObject().GetDocument().GetLayoutView()->NeedsLayout()); |
| |
| const HitTestRequest& request = result.GetHitTestRequest(); |
| |
| HitTestRecursionData recursion_data(hit_test_area, hit_test_location, |
| hit_test_location); |
| PaintLayer* inside_layer = |
| HitTestLayer(this, nullptr, result, recursion_data, false); |
| if (!inside_layer && IsRootLayer()) { |
| bool fallback = false; |
| // If we didn't hit any layers but are still inside the document |
| // bounds, then we should fallback to hitting the document. |
| // For rect-based hit test, we do the fallback only when the hit-rect |
| // is totally within the document bounds. |
| if (hit_test_area.Contains(hit_test_location.BoundingBox())) { |
| fallback = true; |
| |
| // Mouse dragging outside the main document should also be |
| // delivered to the document. |
| // TODO(miletus): Capture behavior inconsistent with iframes |
| // crbug.com/522109. |
| // TODO(majidvp): This should apply more consistently across different |
| // event types and we should not use RequestType for it. Perhaps best for |
| // it to be done at a higher level. See http://crbug.com/505825 |
| } else if ((request.Active() || request.Release()) && |
| !request.IsChildFrameHitTest()) { |
| fallback = true; |
| } |
| if (fallback) { |
| GetLayoutObject().UpdateHitTestResult(result, hit_test_location.Point()); |
| inside_layer = this; |
| |
| // Don't cache this result since it really wasn't a true hit. |
| result.SetCacheable(false); |
| } |
| } |
| |
| // Now determine if the result is inside an anchor - if the urlElement isn't |
| // already set. |
| Node* node = result.InnerNode(); |
| if (node && !result.URLElement()) |
| result.SetURLElement(node->EnclosingLinkEventParentOrSelf()); |
| |
| // Now return whether we were inside this layer (this will always be true for |
| // the root layer). |
| return inside_layer; |
| } |
| |
| Node* PaintLayer::EnclosingNode() const { |
| for (LayoutObject* r = &GetLayoutObject(); r; r = r->Parent()) { |
| if (Node* e = r->GetNode()) |
| return e; |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| bool PaintLayer::IsInTopLayer() const { |
| auto* element = DynamicTo<Element>(GetLayoutObject().GetNode()); |
| return element && element->IsInTopLayer(); |
| } |
| |
| // Compute the z-offset of the point in the transformState. |
| // This is effectively projecting a ray normal to the plane of ancestor, finding |
| // where that ray intersects target, and computing the z delta between those two |
| // points. |
| static double ComputeZOffset(const HitTestingTransformState& transform_state) { |
| // We got an affine transform, so no z-offset |
| if (transform_state.accumulated_transform_.IsAffine()) |
| return 0; |
| |
| // Flatten the point into the target plane |
| FloatPoint target_point = transform_state.MappedPoint(); |
| |
| // Now map the point back through the transform, which computes Z. |
| FloatPoint3D backmapped_point = |
| transform_state.accumulated_transform_.MapPoint( |
| FloatPoint3D(target_point)); |
| return backmapped_point.Z(); |
| } |
| |
| HitTestingTransformState PaintLayer::CreateLocalTransformState( |
| PaintLayer* root_layer, |
| PaintLayer* container_layer, |
| const HitTestRecursionData& recursion_data, |
| const HitTestingTransformState* container_transform_state, |
| const PhysicalOffset& translation_offset) const { |
| // If we're already computing transform state, then it's relative to the |
| // container (which we know is non-null). |
| // If this is the first time we need to make transform state, then base it |
| // off of hitTestLocation, which is relative to rootLayer. |
| HitTestingTransformState transform_state = |
| container_transform_state |
| ? *container_transform_state |
| : HitTestingTransformState(recursion_data.location.TransformedPoint(), |
| recursion_data.location.TransformedRect(), |
| FloatQuad(FloatRect(recursion_data.rect))); |
| |
| if (container_transform_state && |
| RuntimeEnabledFeatures::TransformInteropEnabled() && |
| (!container_layer || &container_layer->GetLayoutObject() != |
| GetLayoutObject().NearestAncestorForElement())) { |
| // Our parent *layer* is preserve-3d, but that preserve-3d doesn't |
| // apply to this layer because our element is not a child of our |
| // parent layer's element. |
| transform_state.Flatten(); |
| } |
| |
| PhysicalOffset offset; |
| if (container_transform_state) |
| ConvertToLayerCoords(container_layer, offset); |
| else |
| ConvertToLayerCoords(root_layer, offset); |
| |
| offset += translation_offset; |
| |
| LayoutObject* container_layout_object = |
| container_layer ? &container_layer->GetLayoutObject() : nullptr; |
| if (GetLayoutObject().ShouldUseTransformFromContainer( |
| container_layout_object)) { |
| TransformationMatrix container_transform; |
| GetLayoutObject().GetTransformFromContainer(container_layout_object, offset, |
| container_transform); |
| transform_state.ApplyTransform( |
| container_transform, HitTestingTransformState::kAccumulateTransform); |
| } else { |
| transform_state.Translate(offset.left.ToInt(), offset.top.ToInt(), |
| HitTestingTransformState::kAccumulateTransform); |
| } |
| |
| return transform_state; |
| } |
| |
| static bool IsHitCandidateForDepthOrder( |
| const PaintLayer* hit_layer, |
| bool can_depth_sort, |
| double* z_offset, |
| const HitTestingTransformState* transform_state) { |
| if (!hit_layer) |
| return false; |
| |
| // The hit layer is depth-sorting with other layers, so just say that it was |
| // hit. |
| if (can_depth_sort) |
| return true; |
| |
| // We need to look at z-depth to decide if this layer was hit. |
| if (z_offset) { |
| DCHECK(transform_state); |
| // This is actually computing our z, but that's OK because the hitLayer is |
| // coplanar with us. |
| double child_z_offset = ComputeZOffset(*transform_state); |
| if (child_z_offset > *z_offset) { |
| *z_offset = child_z_offset; |
| return true; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Calling IsDescendantOf is sad (slow), but it's the only way to tell |
| // whether a hit test candidate is a descendant of the stop node. |
| static bool IsHitCandidateForStopNode(const LayoutObject& candidate, |
| const LayoutObject* stop_node) { |
| return !stop_node || (&candidate == stop_node) || |
| !candidate.IsDescendantOf(stop_node); |
| } |
| |
| // hitTestLocation and hitTestRect are relative to rootLayer. |
| // A 'flattening' layer is one preserves3D() == false. |
| // transformState.m_accumulatedTransform holds the transform from the containing |
| // flattening layer. |
| // transformState.m_lastPlanarPoint is the hitTestLocation in the plane of the |
| // containing flattening layer. |
| // transformState.m_lastPlanarQuad is the hitTestRect as a quad in the plane of |
| // the containing flattening layer. |
| // |
| // If zOffset is non-null (which indicates that the caller wants z offset |
| // information), *zOffset on return is the z offset of the hit point relative to |
| // the containing flattening layer. |
| PaintLayer* PaintLayer::HitTestLayer(PaintLayer* root_layer, |
| PaintLayer* container_layer, |
| HitTestResult& result, |
| const HitTestRecursionData& recursion_data, |
| bool applied_transform, |
| HitTestingTransformState* transform_state, |
| double* z_offset, |
| bool check_resizer_only) { |
| const LayoutObject& layout_object = GetLayoutObject(); |
| DCHECK_GE(layout_object.GetDocument().Lifecycle().GetState(), |
| DocumentLifecycle::kPrePaintClean); |
| |
| if (!IsSelfPaintingLayer() && !HasSelfPaintingLayerDescendant()) |
| return nullptr; |
| |
| if ((result.GetHitTestRequest().GetType() & |
| HitTestRequest::kIgnoreZeroOpacityObjects) && |
| !layout_object.HasNonZeroEffectiveOpacity()) { |
| return nullptr; |
| } |
| |
| ShouldRespectOverflowClipType clip_behavior = kRespectOverflowClip; |
| if (result.GetHitTestRequest().IgnoreClipping()) |
| clip_behavior = kIgnoreOverflowClip; |
| |
| // For the global root scroller, hit test the layout viewport scrollbars |
| // first, as they are visually presented on top of the content. |
| if (layout_object.IsGlobalRootScroller()) { |
| // There are a number of early outs below that don't apply to the the |
| // global root scroller. |
| DCHECK(!Transform()); |
| DCHECK(!Preserves3D()); |
| DCHECK(!layout_object.HasClipPath()); |
| if (scrollable_area_) { |
| IntPoint point = scrollable_area_->ConvertFromRootFrameToVisualViewport( |
| RoundedIntPoint(recursion_data.location.Point())); |
| |
| DCHECK(GetLayoutBox()); |
| if (GetLayoutBox()->HitTestOverflowControl(result, HitTestLocation(point), |
| PhysicalOffset())) |
| return this; |
| } |
| } |
| |
| // We can only reach an SVG foreign object's PaintLayer from |
| // LayoutSVGForeignObject::NodeAtFloatPoint (because |
| // IsReplacedNormalFlowStacking() true for LayoutSVGForeignObject), |
| // where the hit_test_rect has already been transformed to local coordinates. |
| bool use_transform = Transform() && !layout_object.IsSVGForeignObject(); |
| |
| // Apply a transform if we have one. |
| if (use_transform && !applied_transform) { |
| if (EnclosingPaginationLayer()) { |
| return HitTestTransformedLayerInFragments( |
| root_layer, container_layer, result, recursion_data, transform_state, |
| z_offset, check_resizer_only, clip_behavior); |
| } |
| |
| // Make sure the parent's clip rects have been calculated. |
| if (Parent()) { |
| ClipRect clip_rect; |
| Clipper(GeometryMapperOption::kUseGeometryMapper) |
| .CalculateBackgroundClipRect( |
| ClipRectsContext( |
| root_layer, &root_layer->GetLayoutObject().FirstFragment(), |
| kExcludeOverlayScrollbarSizeForHitTesting, clip_behavior), |
| clip_rect); |
| // Go ahead and test the enclosing clip now. |
| if (!clip_rect.Intersects(recursion_data.location)) |
| return nullptr; |
| } |
| |
| return HitTestLayerByApplyingTransform(root_layer, container_layer, result, |
| recursion_data, transform_state, |
| z_offset, check_resizer_only); |
| } |
| |
| // Don't hit test the clip-path area when checking for occlusion. This is |
| // necessary because SVG doesn't support rect-based hit testing, so |
| // HitTestClippedOutByClipPath may erroneously return true for a rect-based |
| // hit test). |
| bool is_occlusion_test = result.GetHitTestRequest().GetType() & |
| HitTestRequest::kHitTestVisualOverflow; |
| if (!is_occlusion_test && layout_object.HasClipPath() && |
| HitTestClippedOutByClipPath(root_layer, recursion_data.location)) { |
| return nullptr; |
| } |
| |
| // The natural thing would be to keep HitTestingTransformState on the stack, |
| // but it's big, so we heap-allocate. |
| HitTestingTransformState* local_transform_state = nullptr; |
| STACK_UNINITIALIZED absl::optional<HitTestingTransformState> storage; |
| |
| if (applied_transform) { |
| // We computed the correct state in the caller (above code), so just |
| // reference it. |
| DCHECK(transform_state); |
| local_transform_state = transform_state; |
| } else if (transform_state || has3d_transformed_descendant_ || |
| Preserves3D()) { |
| // We need transform state for the first time, or to offset the container |
| // state, so create it here. |
| storage = CreateLocalTransformState(root_layer, container_layer, |
| recursion_data, transform_state); |
| local_transform_state = &*storage; |
| } |
| |
| // Check for hit test on backface if backface-visibility is 'hidden' |
| if (local_transform_state && layout_object.StyleRef().BackfaceVisibility() == |
| EBackfaceVisibility::kHidden) { |
| STACK_UNINITIALIZED TransformationMatrix inverted_matrix = |
| local_transform_state->accumulated_transform_.Inverse(); |
| // If the z-vector of the matrix is negative, the back is facing towards the |
| // viewer. |
| if (inverted_matrix.M33() < 0) |
| return nullptr; |
| } |
| |
| HitTestingTransformState* unflattened_transform_state = local_transform_state; |
| STACK_UNINITIALIZED absl::optional<HitTestingTransformState> |
| unflattened_storage; |
| if (local_transform_state && !Preserves3D()) { |
| // Keep a copy of the pre-flattening state, for computing z-offsets for the |
| // container |
| unflattened_storage.emplace(*local_transform_state); |
| unflattened_transform_state = &*unflattened_storage; |
| // This layer is flattening, so flatten the state passed to descendants. |
| local_transform_state->Flatten(); |
| } |
| |
| // The following are used for keeping track of the z-depth of the hit point of |
| // 3d-transformed descendants. |
| double local_z_offset = -std::numeric_limits<double>::infinity(); |
| double* z_offset_for_descendants_ptr = nullptr; |
| double* z_offset_for_contents_ptr = nullptr; |
| |
| bool depth_sort_descendants = false; |
| if (Preserves3D()) { |
| depth_sort_descendants = true; |
| // Our layers can depth-test with our container, so share the z depth |
| // pointer with the container, if it passed one down. |
| z_offset_for_descendants_ptr = z_offset ? z_offset : &local_z_offset; |
| z_offset_for_contents_ptr = z_offset ? z_offset : &local_z_offset; |
| } else if (z_offset) { |
| z_offset_for_descendants_ptr = nullptr; |
| // Container needs us to give back a z offset for the hit layer. |
| z_offset_for_contents_ptr = z_offset; |
| } |
| |
| // Collect the fragments. This will compute the clip rectangles for each |
| // layer fragment. |
| STACK_UNINITIALIZED absl::optional<PaintLayerFragments> layer_fragments; |
| if (recursion_data.intersects_location) { |
| layer_fragments.emplace(); |
| if (applied_transform) { |
| DCHECK_EQ(root_layer, this); |
| AppendSingleFragmentIgnoringPaginationForHitTesting(*layer_fragments, |
| clip_behavior); |
| } else { |
| CollectFragments(*layer_fragments, root_layer, nullptr, |
| kExcludeOverlayScrollbarSizeForHitTesting, |
| clip_behavior); |
| } |
| |
| // See if the hit test pos is inside the resizer of current layer. This |
| // should be done before walking child layers to avoid that the resizer |
| // clickable area is obscured by the positive child layers. |
| if (scrollable_area_ && scrollable_area_->HitTestResizerInFragments( |
| *layer_fragments, recursion_data.location)) { |
| if (Node* node_for_resizer = layout_object.NodeForHitTest()) |
| result.SetInnerNode(node_for_resizer); |
| return this; |
| } |
| } |
| |
| if (check_resizer_only) |
| return nullptr; |
| |
| // See if the hit test pos is inside the resizer of the child layers which |
| // has reordered the painting of the overlay overflow controls. |
| if (stacking_node_) { |
| for (auto* layer : base::Reversed( |
| stacking_node_->OverlayOverflowControlsReorderedList())) { |
| if (layer->HitTestLayer( |
| root_layer, nullptr, result, recursion_data, |
| false /*applied_transform*/, local_transform_state, |
| z_offset_for_descendants_ptr, true /*check_resizer_only*/)) { |
| return layer; |
| } |
| } |
| } |
| |
| // This variable tracks which layer the mouse ends up being inside. |
| PaintLayer* candidate_layer = nullptr; |
| |
| // Begin by walking our list of positive layers from highest z-index down to |
| // the lowest z-index. |
| PaintLayer* hit_layer = HitTestChildren( |
| kPositiveZOrderChildren, root_layer, result, recursion_data, |
| local_transform_state, z_offset_for_descendants_ptr, z_offset, |
| unflattened_transform_state, depth_sort_descendants); |
| if (hit_layer) { |
| if (!depth_sort_descendants) |
| return hit_layer; |
| candidate_layer = hit_layer; |
| } |
| |
| // Now check our overflow objects. |
| hit_layer = HitTestChildren( |
| kNormalFlowChildren, root_layer, result, recursion_data, |
| local_transform_state, z_offset_for_descendants_ptr, z_offset, |
| unflattened_transform_state, depth_sort_descendants); |
| if (hit_layer) { |
| if (!depth_sort_descendants) |
| return hit_layer; |
| candidate_layer = hit_layer; |
| } |
| |
| const LayoutObject* stop_node = result.GetHitTestRequest().GetStopNode(); |
| PhysicalOffset offset; |
| if (recursion_data.intersects_location) { |
| // Next we want to see if the mouse pos is inside the child LayoutObjects of |
| // the layer. Check every fragment in reverse order. |
| if (IsSelfPaintingLayer() && |
| !layout_object.ChildPaintBlockedByDisplayLock()) { |
| // Hit test with a temporary HitTestResult, because we only want to commit |
| // to 'result' if we know we're frontmost. |
| STACK_UNINITIALIZED HitTestResult temp_result( |
| result.GetHitTestRequest(), recursion_data.original_location); |
| temp_result.SetInertNode(result.InertNode()); |
| bool inside_fragment_foreground_rect = false; |
| |
| if (HitTestContentsForFragments( |
| *layer_fragments, offset, temp_result, recursion_data.location, |
| kHitTestDescendants, inside_fragment_foreground_rect) && |
| IsHitCandidateForDepthOrder(this, false, z_offset_for_contents_ptr, |
| unflattened_transform_state) && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| if (result.GetHitTestRequest().ListBased()) |
| result.Append(temp_result); |
| else |
| result = temp_result; |
| if (!depth_sort_descendants) |
| return this; |
| // Foreground can depth-sort with descendant layers, so keep this as a |
| // candidate. |
| candidate_layer = this; |
| } else if (inside_fragment_foreground_rect && |
| result.GetHitTestRequest().ListBased() && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| result.Append(temp_result); |
| } else if (result.GetHitTestRequest().RetargetForInert() && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| result.SetInertNode(temp_result.InertNode()); |
| } |
| } |
| } |
| |
| // Now check our negative z-index children. |
| hit_layer = HitTestChildren( |
| kNegativeZOrderChildren, root_layer, result, recursion_data, |
| local_transform_state, z_offset_for_descendants_ptr, z_offset, |
| unflattened_transform_state, depth_sort_descendants); |
| if (hit_layer) { |
| if (!depth_sort_descendants) |
| return hit_layer; |
| candidate_layer = hit_layer; |
| } |
| |
| // If we found a layer, return. Child layers, and foreground always render |
| // in front of background. |
| if (candidate_layer) |
| return candidate_layer; |
| |
| if (recursion_data.intersects_location && IsSelfPaintingLayer()) { |
| STACK_UNINITIALIZED HitTestResult temp_result( |
| result.GetHitTestRequest(), recursion_data.original_location); |
| temp_result.SetInertNode(result.InertNode()); |
| bool inside_fragment_background_rect = false; |
| if (HitTestContentsForFragments(*layer_fragments, offset, temp_result, |
| recursion_data.location, kHitTestSelf, |
| inside_fragment_background_rect) && |
| IsHitCandidateForDepthOrder(this, false, z_offset_for_contents_ptr, |
| unflattened_transform_state) && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| if (result.GetHitTestRequest().ListBased()) |
| result.Append(temp_result); |
| else |
| result = temp_result; |
| return this; |
| } else if (result.GetHitTestRequest().RetargetForInert() && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| result.SetInertNode(temp_result.InertNode()); |
| } |
| if (inside_fragment_background_rect && |
| result.GetHitTestRequest().ListBased() && |
| IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) { |
| result.Append(temp_result); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool PaintLayer::HitTestContentsForFragments( |
| const PaintLayerFragments& layer_fragments, |
| const PhysicalOffset& offset, |
| HitTestResult& result, |
| const HitTestLocation& hit_test_location, |
| HitTestFilter hit_test_filter, |
| bool& inside_clip_rect) const { |
| if (layer_fragments.IsEmpty()) |
| return false; |
| |
| for (int i = layer_fragments.size() - 1; i >= 0; --i) { |
| const PaintLayerFragment& fragment = layer_fragments.at(i); |
| if ((hit_test_filter == kHitTestSelf && |
| !fragment.background_rect.Intersects(hit_test_location)) || |
| (hit_test_filter == kHitTestDescendants && |
| !fragment.foreground_rect.Intersects(hit_test_location))) |
| continue; |
| inside_clip_rect = true; |
| PhysicalOffset fragment_offset = offset; |
| fragment_offset += fragment.layer_bounds.offset; |
| |
| if (UNLIKELY(layer_fragments.size() > 1 && |
| GetLayoutObject().IsLayoutInline() && |
| GetLayoutObject().CanTraversePhysicalFragments())) { |
| // When hit-testing a relatively positioned inline, we'll search for it in |
| // each fragment of the containing block. Each fragment has its own |
| // offset, and we need to do one fragment at a time. |
| HitTestLocation location_for_fragment(hit_test_location, i); |
| if (HitTestContents(result, fragment.physical_fragment, fragment_offset, |
| location_for_fragment, hit_test_filter)) |
| return true; |
| } else if (HitTestContents(result, fragment.physical_fragment, |
| fragment_offset, hit_test_location, |
| hit_test_filter)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| PaintLayer* PaintLayer::HitTestTransformedLayerInFragments( |
| PaintLayer* root_layer, |
| PaintLayer* container_layer, |
| HitTestResult& result, |
| const HitTestRecursionData& recursion_data, |
| HitTestingTransformState* transform_state, |
| double* z_offset, |
| bool check_resizer_only, |
| ShouldRespectOverflowClipType clip_behavior) { |
| PaintLayerFragments enclosing_pagination_fragments; |
| // FIXME: We're missing a sub-pixel offset here crbug.com/348728 |
| |
| EnclosingPaginationLayer()->CollectFragments( |
| enclosing_pagination_fragments, root_layer, nullptr, |
| kExcludeOverlayScrollbarSizeForHitTesting, clip_behavior, nullptr, |
| PhysicalOffset()); |
| |
| for (const auto& fragment : enclosing_pagination_fragments) { |
| // Apply the page/column clip for this fragment, as well as any clips |
| // established by layers in between us and the enclosing pagination layer. |
| PhysicalRect clip_rect = fragment.background_rect.Rect(); |
| if (!recursion_data.location.Intersects(clip_rect)) |
| continue; |
| |
| PaintLayer* hit_layer = HitTestLayerByApplyingTransform( |
| root_layer, container_layer, result, recursion_data, transform_state, |
| z_offset, check_resizer_only, |
| fragment.fragment_data->LegacyPaginationOffset()); |
| if (hit_layer) |
| return hit_layer; |
| } |
| |
| return nullptr; |
| } |
| |
| PaintLayer* PaintLayer::HitTestLayerByApplyingTransform( |
| PaintLayer* root_layer, |
| PaintLayer* container_layer, |
| HitTestResult& result, |
| const HitTestRecursionData& recursion_data, |
| HitTestingTransformState* transform_state, |
| double* z_offset, |
| bool check_resizer_only, |
| const PhysicalOffset& translation_offset) { |
| // Create a transform state to accumulate this transform. |
| HitTestingTransformState new_transform_state = |
| CreateLocalTransformState(root_layer, container_layer, recursion_data, |
| transform_state, translation_offset); |
| |
| // If the transform can't be inverted, then don't hit test this layer at all. |
| if (!new_transform_state.accumulated_transform_.IsInvertible()) |
| return nullptr; |
| |
| // Compute the point and the hit test rect in the coords of this layer by |
| // using the values from the transformState, which store the point and quad in |
| // the coords of the last flattened layer, and the accumulated transform which |
| // lets up map through preserve-3d layers. |
| // |
| // We can't just map hitTestLocation and hitTestRect because they may have |
| // been flattened (losing z) by our container. |
| FloatPoint local_point = new_transform_state.MappedPoint(); |
| PhysicalRect bounds_of_mapped_area = new_transform_state.BoundsOfMappedArea(); |
| absl::optional<HitTestLocation> new_location; |
| if (recursion_data.location.IsRectBasedTest()) |
| new_location.emplace(local_point, new_transform_state.MappedQuad()); |
| else |
| new_location.emplace(local_point, new_transform_state.BoundsOfMappedQuad()); |
| HitTestRecursionData new_recursion_data(bounds_of_mapped_area, *new_location, |
| recursion_data.original_location); |
| |
| // Now do a hit test with the root layer shifted to be us. |
| return HitTestLayer(this, container_layer, result, new_recursion_data, true, |
| &new_transform_state, z_offset, check_resizer_only); |
| } |
| |
| bool PaintLayer::HitTestContents(HitTestResult& result, |
| const NGPhysicalBoxFragment* physical_fragment, |
| const PhysicalOffset& fragment_offset, |
| const HitTestLocation& hit_test_location, |
| HitTestFilter hit_test_filter) const { |
| DCHECK(IsSelfPaintingLayer() || HasSelfPaintingLayerDescendant()); |
| |
| bool did_hit; |
| if (physical_fragment) { |
| did_hit = NGBoxFragmentPainter(*physical_fragment) |
| .HitTestAllPhases(result, hit_test_location, fragment_offset, |
| hit_test_filter); |
| } else { |
| did_hit = GetLayoutObject().HitTestAllPhases( |
| result, hit_test_location, fragment_offset, hit_test_filter); |
| } |
| |
| if (!did_hit) { |
| // It's wrong to set innerNode, but then claim that you didn't hit anything, |
| // unless it is a list-based test. |
| DCHECK(!result.InnerNode() || (result.GetHitTestRequest().ListBased() && |
| result.ListBasedTestResult().size())); |
| return false; |
| } |
| |
| if (!result.InnerNode()) { |
| // We hit something anonymous, and we didn't find a DOM node ancestor in |
| // this layer. |
| |
| if (GetLayoutObject().IsLayoutFlowThread()) { |
| // For a flow thread it's safe to just say that we didn't hit anything. |
| // That means that we'll continue as normally, and eventually hit a column |
| // set sibling instead. Column sets are also anonymous, but, unlike flow |
| // threads, they don't establish layers, so we'll fall back and hit the |
| // multicol container parent (which should have a DOM node). |
| return false; |
| } |
| |
| Node* e = EnclosingNode(); |
| // FIXME: should be a call to result.setNodeAndPosition. What we would |
| // really want to do here is to return and look for the nearest |
| // non-anonymous ancestor, and ignore aunts and uncles on our way. It's bad |
| // to look for it manually like we do here, and give up on setting a local |
| // point in the result, because that has bad implications for text selection |
| // and caretRangeFromPoint(). See crbug.com/461791 |
| // This code path only ever hits in fullscreen tests. |
| result.SetInnerNode(e); |
| } |
| return true; |
| } |
| |
| bool PaintLayer::IsReplacedNormalFlowStacking() const { |
| return GetLayoutObject().IsSVGForeignObject(); |
| } |
| |
| void PaintLayer::SetNeedsCompositingLayerAssignment() { |
| needs_compositing_layer_assignment_ = true; |
| PropagateDescendantNeedsCompositingLayerAssignment(); |
| } |
| |
| void PaintLayer::PropagateDescendantNeedsCompositingLayerAssignment() { |
| for (PaintLayer* curr = CompositingContainer(); |
| curr && !curr->StackingDescendantNeedsCompositingLayerAssignment(); |
| curr = curr->CompositingContainer()) { |
| curr->descendant_needs_compositing_layer_assignment_ = true; |
| } |
| } |
| |
| void PaintLayer::ClearNeedsCompositingLayerAssignment() { |
| needs_compositing_layer_assignment_ = false; |
| descendant_needs_compositing_layer_assignment_ = false; |
| } |
| |
| void PaintLayer::SetNeedsCompositingRequirementsUpdate() { |
| for (PaintLayer* curr = this; |
| curr && !curr->DescendantMayNeedCompositingRequirementsUpdate(); |
| curr = curr->Parent()) { |
| curr->descendant_may_need_compositing_requirements_update_ = true; |
| } |
| } |
| |
| PaintLayer* PaintLayer::HitTestChildren( |
| PaintLayerIteration children_to_visit, |
| PaintLayer* root_layer, |
| HitTestResult& result, |
| const HitTestRecursionData& recursion_data, |
| HitTestingTransformState* transform_state, |
| double* z_offset_for_descendants, |
| double* z_offset, |
| HitTestingTransformState* unflattened_transform_state, |
| bool depth_sort_descendants) { |
| if (!HasSelfPaintingLayerDescendant()) |
| return nullptr; |
| |
| if (GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| return nullptr; |
| |
| const LayoutObject* stop_node = result.GetHitTestRequest().GetStopNode(); |
| PaintLayer* stop_layer = stop_node ? stop_node->PaintingLayer() : nullptr; |
| |
| PaintLayer* result_layer = nullptr; |
| PaintLayerPaintOrderReverseIterator iterator(*this, children_to_visit); |
| while (PaintLayer* child_layer = iterator.Next()) { |
| if (child_layer->IsReplacedNormalFlowStacking()) |
| continue; |
| |
| // Avoid the call to child_layer->HitTestLayer() if possible. |
| if (stop_layer == this && |
| !IsHitCandidateForStopNode(child_layer->GetLayoutObject(), stop_node)) { |
| continue; |
| } |
| |
| PaintLayer* hit_layer = nullptr; |
| STACK_UNINITIALIZED HitTestResult temp_result( |
| result.GetHitTestRequest(), recursion_data.original_location); |
| temp_result.SetInertNode(result.InertNode()); |
| hit_layer = child_layer->HitTestLayer( |
| root_layer, this, temp_result, recursion_data, false, transform_state, |
| z_offset_for_descendants); |
| |
| // If it is a list-based test, we can safely append the temporary result |
| // since it might had hit nodes but not necesserily had hitLayer set. |
| if (result.GetHitTestRequest().ListBased()) |
| result.Append(temp_result); |
| |
| if (IsHitCandidateForDepthOrder(hit_layer, depth_sort_descendants, z_offset, |
| unflattened_transform_state)) { |
| result_layer = hit_layer; |
| if (!result.GetHitTestRequest().ListBased()) |
| result = temp_result; |
| if (!depth_sort_descendants) |
| break; |
| } else if (result.GetHitTestRequest().RetargetForInert()) { |
| result.SetInertNode(temp_result.InertNode()); |
| } |
| } |
| |
| return result_layer; |
| } |
| |
| void PaintLayer::UpdateFilterReferenceBox() { |
| if (!HasFilterThatMovesPixels()) |
| return; |
| PhysicalRect result = LocalBoundingBox(); |
| ExpandRectForSelfPaintingDescendants( |
| *this, result, kIncludeTransforms | kIncludeCompositedChildLayers); |
| FloatRect reference_box = FloatRect(result); |
| if (!ResourceInfo() || ResourceInfo()->FilterReferenceBox() != reference_box) |
| GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| EnsureResourceInfo().SetFilterReferenceBox(reference_box); |
| } |
| |
| FloatRect PaintLayer::FilterReferenceBox() const { |
| #if DCHECK_IS_ON() |
| DCHECK_GE(GetLayoutObject().GetDocument().Lifecycle().GetState(), |
| DocumentLifecycle::kInPrePaint); |
| #endif |
| if (ResourceInfo()) |
| return ResourceInfo()->FilterReferenceBox(); |
| return FloatRect(); |
| } |
| |
| FloatRect PaintLayer::BackdropFilterReferenceBox() const { |
| return FloatRect(GetLayoutObject().BorderBoundingBox()); |
| } |
| |
| gfx::RRectF PaintLayer::BackdropFilterBounds() const { |
| gfx::RRectF backdrop_filter_bounds = |
| gfx::RRectF(RoundedBorderGeometry::PixelSnappedRoundedBorder( |
| GetLayoutObject().StyleRef(), |
| PhysicalRect::EnclosingRect(BackdropFilterReferenceBox()))); |
| return backdrop_filter_bounds; |
| } |
| |
| bool PaintLayer::HitTestClippedOutByClipPath( |
| PaintLayer* root_layer, |
| const HitTestLocation& hit_test_location) const { |
| DCHECK(GetLayoutObject().HasClipPath()); |
| DCHECK(IsSelfPaintingLayer()); |
| DCHECK(root_layer); |
| |
| PhysicalRect origin; |
| if (EnclosingPaginationLayer()) |
| ConvertFromFlowThreadToVisualBoundingBoxInAncestor(root_layer, origin); |
| else |
| ConvertToLayerCoords(root_layer, origin); |
| |
| FloatPoint point(hit_test_location.Point() - origin.offset); |
| FloatRect reference_box( |
| ClipPathClipper::LocalReferenceBox(GetLayoutObject())); |
| |
| ClipPathOperation* clip_path_operation = |
| GetLayoutObject().StyleRef().ClipPath(); |
| DCHECK(clip_path_operation); |
| if (clip_path_operation->GetType() == ClipPathOperation::SHAPE) { |
| ShapeClipPathOperation* clip_path = |
| To<ShapeClipPathOperation>(clip_path_operation); |
| return !clip_path |
| ->GetPath(reference_box, |
| GetLayoutObject().StyleRef().EffectiveZoom()) |
| .Contains(point); |
| } |
| DCHECK_EQ(clip_path_operation->GetType(), ClipPathOperation::REFERENCE); |
| LayoutSVGResourceClipper* clipper = |
| GetSVGResourceAsType(*ResourceInfo(), clip_path_operation); |
| if (!clipper) |
| return false; |
| // If the clipPath is using "userspace on use" units, then the origin of |
| // the coordinate system is the top-left of the reference box, so adjust |
| // the point accordingly. |
| if (clipper->ClipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) |
| point.MoveBy(-reference_box.Location()); |
| // Unzoom the point and the reference box, since the <clipPath> geometry is |
| // not zoomed. |
| float inverse_zoom = 1 / GetLayoutObject().StyleRef().EffectiveZoom(); |
| point.Scale(inverse_zoom, inverse_zoom); |
| reference_box.Scale(inverse_zoom); |
| HitTestLocation location(point); |
| return !clipper->HitTestClipContent(reference_box, location); |
| } |
| |
| bool PaintLayer::IntersectsDamageRect( |
| const PhysicalRect& layer_bounds, |
| const PhysicalRect& damage_rect, |
| const PhysicalOffset& offset_from_root) const { |
| // Always examine the canvas and the root. |
| // FIXME: Could eliminate the isDocumentElement() check if we fix background |
| // painting so that the LayoutView paints the root's background. |
| if (IsRootLayer() || GetLayoutObject().IsDocumentElement()) |
| return true; |
| |
| // If we aren't an inline flow, and our layer bounds do intersect the damage |
| // rect, then we can go ahead and return true. |
| LayoutView* view = GetLayoutObject().View(); |
| DCHECK(view); |
| if (view && !GetLayoutObject().IsLayoutInline()) { |
| if (layer_bounds.Intersects(damage_rect)) |
| return true; |
| } |
| |
| // Otherwise we need to compute the bounding box of this single layer and see |
| // if it intersects the damage rect. |
| return PhysicalBoundingBox(offset_from_root).Intersects(damage_rect); |
| } |
| |
| PhysicalRect PaintLayer::LocalBoundingBox() const { |
| PhysicalRect rect = GetLayoutObject().PhysicalVisualOverflowRect(); |
| if (GetLayoutObject().IsEffectiveRootScroller() || IsRootLayer()) { |
| rect.Unite( |
| PhysicalRect(rect.offset, GetLayoutObject().View()->ViewRect().size)); |
| } |
| return rect; |
| } |
| |
| PhysicalRect PaintLayer::ClippedLocalBoundingBox( |
| const PaintLayer& ancestor_layer) const { |
| return Intersection(LocalBoundingBox(), |
| Clipper(GeometryMapperOption::kUseGeometryMapper) |
| .LocalClipRect(ancestor_layer)); |
| } |
| |
| PhysicalRect PaintLayer::PhysicalBoundingBox( |
| const PaintLayer* ancestor_layer) const { |
| PhysicalOffset offset_from_root; |
| ConvertToLayerCoords(ancestor_layer, offset_from_root); |
| return PhysicalBoundingBox(offset_from_root); |
| } |
| |
| PhysicalRect PaintLayer::PhysicalBoundingBox( |
| const PhysicalOffset& offset_from_root) const { |
| PhysicalRect result = LocalBoundingBox(); |
| result.Move(offset_from_root); |
| return result; |
| } |
| |
| PhysicalRect PaintLayer::FragmentsBoundingBox( |
| const PaintLayer* ancestor_layer) const { |
| if (!EnclosingPaginationLayer()) |
| return PhysicalBoundingBox(ancestor_layer); |
| |
| PhysicalRect result = LocalBoundingBox(); |
| ConvertFromFlowThreadToVisualBoundingBoxInAncestor(ancestor_layer, result); |
| return result; |
| } |
| |
| PhysicalRect PaintLayer::LocalBoundingBoxForCompositingOverlapTest() const { |
| // Returns the bounding box, in the local coordinate system for this layer, |
| // for the content that this paint layer is responsible for compositing. This |
| // doesn't include the content painted by self-painting descendants such as |
| // composited absolute positioned children. But the bounds is suitable for |
| // calculations such as squashing sparsity. To get the bounds that includes |
| // the visible extent of this layer and its children for overlap testing, use |
| // ExpandedBoundingBoxForCompositingOverlapTest. |
| |
| // Apply NeverIncludeTransformForAncestorLayer, because the geometry map in |
| // CompositingInputsUpdater will take care of applying the transform of |this| |
| // (== the ancestorLayer argument to boundingBoxForCompositing). |
| // TODO(trchen): Layer fragmentation is inhibited across compositing boundary. |
| // Should we return the unfragmented bounds for overlap testing? Or perhaps |
| // assume fragmented layers always overlap? |
| PhysicalRect bounding_box = FragmentsBoundingBox(this); |
| const ComputedStyle& style = GetLayoutObject().StyleRef(); |
| |
| if (PaintsWithFilters()) |
| bounding_box = MapRectForFilter(bounding_box); |
| |
| if (style.HasBackdropFilter() && |
| style.BackdropFilter().HasFilterThatMovesPixels()) { |
| bounding_box = PhysicalRect::EnclosingRect( |
| style.BackdropFilter().MapRect(FloatRect(bounding_box))); |
| } |
| |
| return bounding_box; |
| } |
| |
| IntRect PaintLayer::ExpandedBoundingBoxForCompositingOverlapTest( |
| bool use_clipped_bounding_rect) const { |
| // Returns the bounding box for this layer and self-painted composited |
| // children which are otherwise not included in |
| // LocalBoundingBoxForCompositingOverlapTest. Use the bounds from this layer |
| // for overlap testing that cares about the bounds of this layer and all its |
| // children. |
| IntRect abs_bounds = use_clipped_bounding_rect |
| ? ClippedAbsoluteBoundingBox() |
| : UnclippedAbsoluteBoundingBox(); |
| PaintLayer* root_layer = GetLayoutObject().View()->Layer(); |
| // |abs_bounds| does not include root scroller offset, as in it's in absolute |
| // coordinates, for everything but fixed-pos objects (and their children) |
| // which are in viewport coords. Adjusting these to all be in absolute coords |
| // happens here. This adjustment is delayed until this point in time as doing |
| // it during compositing inputs update would embed the scroll offset at the |
| // time the compositing inputs was ran when converting from viewport to |
| // absolute, making the resulting rects unusable for any other scroll offset. |
| if (root_layer->GetScrollableArea() && !abs_bounds.IsEmpty() && |
| !IsAffectedByScrollOf(root_layer)) { |
| PaintLayerScrollableArea* scrollable_area = root_layer->GetScrollableArea(); |
| ScrollOffset current_scroll_offset = scrollable_area->GetScrollOffset(); |
| |
| if (IsTopMostNotAffectedByScrollOf(root_layer)) { |
| // For overlap testing, expand the rect used for fixed-pos content in |
| // two ways. First, include any children such that overlap testing |
| // against the top-most fixed-pos layer is guaranteed to detect any |
| // overlap where a self-painting composited child of the fixed-pos layer |
| // exceeds the fixed-pos layer's bounds. Second, expand the rect to |
| // include the area it could cover if the view were to be scrolled to |
| // its minimum and maximum extents. This allows skipping overlap testing |
| // on scroll offset changes. Note that bounds expansion does not happen |
| // for fixed under a non-view container (under xform or filter for |
| // example) as those fixed still are affected by the view's scroll offset. |
| // This is checked for with the IsAffectedByScrollOf call earlier. |
| |
| // Expand the rect to include children that are not already included in |
| // |layer|'s bounds. |
| PhysicalRect children_bounds; |
| if (!GetLayoutObject().ChildPaintBlockedByDisplayLock()) { |
| PaintLayerPaintOrderIterator iterator(*this, kAllChildren); |
| while (PaintLayer* child_layer = iterator.Next()) { |
| // Note that we intentionally include children irrespective of if they |
| // are composited or not. |
| children_bounds.Unite(child_layer->BoundingBoxForCompositingInternal( |
| *this, this, |
| kIncludeAncestorClips | kIncludeTransforms | |
| kIncludeCompositedChildLayers)); |
| } |
| if (!children_bounds.IsEmpty()) { |
| GetLayoutObject().MapToVisualRectInAncestorSpace( |
| GetLayoutObject().View(), children_bounds, kUseGeometryMapper); |
| abs_bounds.Unite(EnclosingIntRect(children_bounds)); |
| } |
| } |
| |
| // Expand bounds to include min/max scroll extents |
| ScrollOffset max_scroll_delta = |
| scrollable_area->MaximumScrollOffset() - current_scroll_offset; |
| ScrollOffset min_scroll_delta = |
| current_scroll_offset - scrollable_area->MinimumScrollOffset(); |
| abs_bounds.Expand( |
| IntRectOutsets(min_scroll_delta.Height(), max_scroll_delta.Width(), |
| max_scroll_delta.Height(), min_scroll_delta.Width())); |
| } |
| } |
| return abs_bounds; |
| } |
| |
| void PaintLayer::ExpandRectForSelfPaintingDescendants( |
| const PaintLayer& composited_layer, |
| PhysicalRect& result, |
| unsigned options) const { |
| // If we're locked, then the subtree does not contribute painted output. |
| // Furthermore, we might not have up-to-date sizing and position information |
| // in the subtree, so skip recursing into the subtree. |
| if (GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| return; |
| |
| DCHECK_EQ(result, (options & kIncludeAncestorClips) |
| ? ClippedLocalBoundingBox(composited_layer) |
| : LocalBoundingBox()); |
| // The input |result| is based on LayoutObject::PhysicalVisualOverflowRect() |
| // which already includes bounds non-self-painting descendants. |
| if (!HasSelfPaintingLayerDescendant()) |
| return; |
| |
| if (const auto* box = GetLayoutBox()) { |
| // If the layer clips overflow and all descendants are contained, then no |
| // need to expand for children. Not checking kIncludeAncestorClips because |
| // the clip of the current layer is always applied. The doesn't check |
| // whether the non-contained descendants are actual descendants of this |
| // layer in paint order because it's not easy. |
| if (box->ShouldClipOverflowAlongBothAxis() && |
| !HasNonContainedAbsolutePositionDescendant() && |
| !(HasFixedPositionDescendant() && |
| !box->CanContainFixedPositionObjects())) { |
| return; |
| } |
| } |
| |
| PaintLayerPaintOrderIterator iterator(*this, kAllChildren); |
| while (PaintLayer* child_layer = iterator.Next()) { |
| // Here we exclude both directly composited layers and squashing layers |
| // because those Layers don't paint into the graphics layer |
| // for this Layer. For example, the bounds of squashed Layers |
| // will be included in the computation of the appropriate squashing |
| // GraphicsLayer. |
| if ((options & kIncludeCompositedChildLayers) || |
| child_layer->GetCompositingState() == kNotComposited) { |
| result.Unite(child_layer->BoundingBoxForCompositingInternal( |
| composited_layer, this, options)); |
| } |
| } |
| } |
| |
| PhysicalRect PaintLayer::BoundingBoxForCompositing() const { |
| return BoundingBoxForCompositingInternal( |
| *this, nullptr, |
| kIncludeAncestorClips | kMaybeIncludeTransformForAncestorLayer); |
| } |
| |
| bool PaintLayer::ShouldApplyTransformToBoundingBox( |
| const PaintLayer& composited_layer, |
| unsigned options) const { |
| DCHECK(!(options & kIncludeTransforms) || |
| !(options & kMaybeIncludeTransformForAncestorLayer)); |
| if (!Transform()) |
| return false; |
| if (options & kIncludeTransforms) |
| return true; |
| if (PaintsWithTransform(kGlobalPaintNormalPhase)) { |
| if (this != &composited_layer) |
| return true; |
| if (options & kMaybeIncludeTransformForAncestorLayer) |
| return true; |
| } |
| return false; |
| } |
| |
| PhysicalRect PaintLayer::BoundingBoxForCompositingInternal( |
| const PaintLayer& composited_layer, |
| const PaintLayer* stacking_parent, |
| unsigned options) const { |
| DCHECK_GE(GetLayoutObject().GetDocument().Lifecycle().GetState(), |
| DocumentLifecycle::kInPrePaint); |
| if (!IsSelfPaintingLayer()) |
| return PhysicalRect(); |
| |
| // FIXME: This could be improved to do a check like |
| // hasVisibleNonCompositingDescendantLayers() (bug 92580). |
| if (this != &composited_layer && !HasVisibleContent() && |
| !HasVisibleDescendant()) |
| return PhysicalRect(); |
| |
| if (GetLayoutObject().IsEffectiveRootScroller() || IsRootLayer()) { |
| // In root layer scrolling mode, the main GraphicsLayer is the size of the |
| // layout viewport. In non-RLS mode, it is the union of the layout viewport |
| // and the document's layout overflow rect. |
| IntRect result = IntRect(); |
| if (LocalFrameView* frame_view = GetLayoutObject().GetFrameView()) |
| result = IntRect(IntPoint(), frame_view->Size()); |
| return PhysicalRect(result); |
| } |
| |
| // The layer created for the LayoutFlowThread is just a helper for painting |
| // and hit-testing, and should not contribute to the bounding box. The |
| // LayoutMultiColumnSets will contribute the correct size for the layout |
| // content of the multicol container. |
| if (GetLayoutObject().IsLayoutFlowThread()) |
| return PhysicalRect(); |
| |
| PhysicalRect result; |
| if (options & kIncludeAncestorClips) { |
| // If there is a clip applied by an ancestor to this PaintLayer but below or |
| // equal to |composited_layer|, apply that clip. This optimizes the size |
| // of the composited layer to exclude clipped-out regions of descendants. |
| result = ClippedLocalBoundingBox(composited_layer); |
| } else { |
| result = LocalBoundingBox(); |
| } |
| |
| ExpandRectForSelfPaintingDescendants(composited_layer, result, options); |
| |
| // Only enlarge by the filter outsets if we know the filter is going to be |
| // rendered in software. Accelerated filters will handle their own outsets. |
| if (PaintsWithFilters()) |
| result = MapRectForFilter(result); |
| |
| if (ShouldApplyTransformToBoundingBox(composited_layer, options)) { |
| result = |
| PhysicalRect::EnclosingRect(Transform()->MapRect(FloatRect(result))); |
| } |
| |
| if (ShouldFragmentCompositedBounds(&composited_layer)) { |
| ConvertFromFlowThreadToVisualBoundingBoxInAncestor(&composited_layer, |
| result); |
| return result; |
| } |
| |
| if (stacking_parent) { |
| PhysicalOffset delta; |
| ConvertToLayerCoords(stacking_parent, delta); |
| result.Move(delta); |
| } |
| return result; |
| } |
| |
| CompositingState PaintLayer::GetCompositingState() const { |
| #if DCHECK_IS_ON() |
| DCHECK(IsAllowedToQueryCompositingState()) |
| << " " << GetLayoutObject().GetDocument().Lifecycle().ToString(); |
| #endif |
| |
| // This is computed procedurally so there is no redundant state variable that |
| // can get out of sync from the real actual compositing state. |
| |
| if (GroupedMapping()) { |
| DCHECK(!GetCompositedLayerMapping()); |
| return kPaintsIntoGroupedBacking; |
| } |
| |
| if (!GetCompositedLayerMapping()) |
| return kNotComposited; |
| |
| return kPaintsIntoOwnBacking; |
| } |
| |
| bool PaintLayer::IsAllowedToQueryCompositingState() const { |
| if (g_compositing_query_mode == kCompositingQueriesAreAllowed || |
| RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return true; |
| if (!GetLayoutObject().GetFrameView()->IsUpdatingLifecycle()) |
| return true; |
| return GetLayoutObject().GetDocument().Lifecycle().GetState() >= |
| DocumentLifecycle::kInCompositingAssignmentsUpdate; |
| } |
| |
| bool PaintLayer::IsAllowedToQueryCompositingInputs() const { |
| if (g_compositing_query_mode == kCompositingQueriesAreAllowed || |
| RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return true; |
| return GetLayoutObject().GetDocument().Lifecycle().GetState() >= |
| DocumentLifecycle::kCompositingInputsClean; |
| } |
| |
| CompositedLayerMapping* PaintLayer::GetCompositedLayerMapping() const { |
| DCHECK(IsAllowedToQueryCompositingState()); |
| return rare_data_ ? rare_data_->composited_layer_mapping.get() : nullptr; |
| } |
| |
| GraphicsLayer* PaintLayer::GraphicsLayerBacking(const LayoutObject* obj) const { |
| switch (GetCompositingState()) { |
| case kNotComposited: |
| return nullptr; |
| case kPaintsIntoGroupedBacking: |
| return GroupedMapping()->SquashingLayer(*this); |
| default: |
| return (obj != &GetLayoutObject() && |
| GetCompositedLayerMapping()->ScrollingContentsLayer()) |
| ? GetCompositedLayerMapping()->ScrollingContentsLayer() |
| : GetCompositedLayerMapping()->MainGraphicsLayer(); |
| } |
| } |
| |
| void PaintLayer::EnsureCompositedLayerMapping() { |
| if (HasCompositedLayerMapping()) |
| return; |
| |
| EnsureRareData().composited_layer_mapping = |
| std::make_unique<CompositedLayerMapping>(*this); |
| rare_data_->composited_layer_mapping->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateSubtree); |
| } |
| |
| void PaintLayer::ClearCompositedLayerMapping(bool layer_being_destroyed) { |
| DCHECK(HasCompositedLayerMapping()); |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| |
| DisableCompositingQueryAsserts disabler; |
| if (!layer_being_destroyed) { |
| // We need to make sure our descendants get a geometry update. In principle, |
| // we could call setNeedsGraphicsLayerUpdate on our children, but that would |
| // require walking the z-order lists to find them. Instead, we |
| // over-invalidate by marking our parent as needing a geometry update. |
| if (PaintLayer* compositing_parent = |
| EnclosingLayerWithCompositedLayerMapping(kExcludeSelf)) |
| compositing_parent->GetCompositedLayerMapping() |
| ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree); |
| } |
| DCHECK(rare_data_); |
| rare_data_->composited_layer_mapping.reset(); |
| } |
| |
| void PaintLayer::SetGroupedMapping(CompositedLayerMapping* grouped_mapping, |
| SetGroupMappingOptions options) { |
| CompositedLayerMapping* old_grouped_mapping = GroupedMapping(); |
| if (grouped_mapping == old_grouped_mapping) |
| return; |
| |
| if (options == kInvalidateLayerAndRemoveFromMapping && old_grouped_mapping) { |
| old_grouped_mapping->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateSubtree); |
| old_grouped_mapping->RemoveLayerFromSquashingGraphicsLayer(*this); |
| } |
| if (rare_data_ || grouped_mapping) |
| EnsureRareData().grouped_mapping = grouped_mapping; |
| #if DCHECK_IS_ON() |
| if (grouped_mapping) |
| grouped_mapping->AssertInSquashedLayersVector(*this); |
| #endif |
| if (options == kInvalidateLayerAndRemoveFromMapping && grouped_mapping) |
| grouped_mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree); |
| } |
| |
| bool PaintLayer::NeedsCompositedScrolling() const { |
| return scrollable_area_ && scrollable_area_->NeedsCompositedScrolling(); |
| } |
| |
| bool PaintLayer::PaintsWithTransform( |
| GlobalPaintFlags global_paint_flags) const { |
| return Transform() && !PaintsIntoOwnBacking(global_paint_flags); |
| } |
| |
| bool PaintLayer::PaintsIntoOwnBacking( |
| GlobalPaintFlags global_paint_flags) const { |
| return !(global_paint_flags & kGlobalPaintFlattenCompositingLayers) && |
| GetCompositingState() == kPaintsIntoOwnBacking; |
| } |
| |
| bool PaintLayer::PaintsWithDirectReasonIntoOwnBacking( |
| GlobalPaintFlags global_paint_flags) const { |
| return !(global_paint_flags & kGlobalPaintFlattenCompositingLayers) && |
| CanBeCompositedForDirectReasons(); |
| } |
| |
| bool PaintLayer::PaintsIntoOwnOrGroupedBacking( |
| GlobalPaintFlags global_paint_flags) const { |
| return !(global_paint_flags & kGlobalPaintFlattenCompositingLayers) && |
| GetCompositingState() != kNotComposited; |
| } |
| |
| bool PaintLayer::SupportsSubsequenceCaching() const { |
| if (EnclosingPaginationLayer()) |
| return false; |
| |
| // SVG root and SVG foreign object paint atomically. |
| if (GetLayoutObject().IsSVGRoot() || GetLayoutObject().IsSVGForeignObject()) |
| return true; |
| |
| // Don't create subsequence for the document element because the subsequence |
| // for LayoutView serves the same purpose. This can avoid unnecessary paint |
| // chunks that would otherwise be forced by the subsequence. |
| if (GetLayoutObject().IsDocumentElement()) |
| return false; |
| |
| // Create subsequence for only stacked objects whose paintings are atomic. |
| return GetLayoutObject().IsStacked(); |
| } |
| |
| ScrollingCoordinator* PaintLayer::GetScrollingCoordinator() { |
| Page* page = GetLayoutObject().GetFrame()->GetPage(); |
| return (!page) ? nullptr : page->GetScrollingCoordinator(); |
| } |
| |
| bool PaintLayer::CompositesWithTransform() const { |
| return TransformAncestor() || Transform(); |
| } |
| |
| bool PaintLayer::BackgroundIsKnownToBeOpaqueInRect( |
| const PhysicalRect& local_rect, |
| bool should_check_children) const { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| // We can't use hasVisibleContent(), because that will be true if our |
| // layoutObject is hidden, but some child is visible and that child doesn't |
| // cover the entire rect. |
| if (GetLayoutObject().StyleRef().Visibility() != EVisibility::kVisible) |
| return false; |
| |
| if (GetLayoutObject().HasMask() || GetLayoutObject().HasClipPath()) |
| return false; |
| |
| if (PaintsWithFilters() && |
| GetLayoutObject().StyleRef().Filter().HasFilterThatAffectsOpacity()) |
| return false; |
| |
| // FIXME: Handle simple transforms. |
| if (Transform() && GetCompositingState() != kPaintsIntoOwnBacking) |
| return false; |
| |
| if (GetLayoutObject().StyleRef().GetPosition() == EPosition::kFixed && |
| GetCompositingState() != kPaintsIntoOwnBacking) |
| return false; |
| |
| // FIXME: We currently only check the immediate layoutObject, |
| // which will miss many cases where additional layout objects paint |
| // into this layer. |
| if (GetLayoutObject().BackgroundIsKnownToBeOpaqueInRect(local_rect)) |
| return true; |
| |
| if (!should_check_children) |
| return false; |
| |
| // We can't consult child layers if we clip, since they might cover |
| // parts of the rect that are clipped out. |
| if (GetLayoutObject().HasClipRelatedProperty()) |
| return false; |
| |
| // TODO(schenney): This could be improved by unioning the opaque regions of |
| // all the children. That would require a refactoring because currently |
| // children just check they at least cover the given rect, but a unioning |
| // method would require children to compute and report their rects. |
| return ChildBackgroundIsKnownToBeOpaqueInRect(local_rect); |
| } |
| |
| bool PaintLayer::ChildBackgroundIsKnownToBeOpaqueInRect( |
| const PhysicalRect& local_rect) const { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| PaintLayerPaintOrderReverseIterator reverse_iterator(*this, kAllChildren); |
| while (PaintLayer* child_layer = reverse_iterator.Next()) { |
| // Stop at composited paint boundaries and non-self-painting layers. |
| if (child_layer->IsPaintInvalidationContainer()) |
| continue; |
| |
| if (!child_layer->CanUseConvertToLayerCoords()) |
| continue; |
| |
| if (child_layer->PaintsWithTransparency(kGlobalPaintNormalPhase)) |
| continue; |
| |
| PhysicalOffset child_offset; |
| PhysicalRect child_local_rect(local_rect); |
| child_layer->ConvertToLayerCoords(this, child_offset); |
| child_local_rect.Move(-child_offset); |
| |
| if (child_layer->BackgroundIsKnownToBeOpaqueInRect(child_local_rect, true)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool PaintLayer::ShouldBeSelfPaintingLayer() const { |
| return GetLayoutObject().LayerTypeRequired() == kNormalPaintLayer || |
| (scrollable_area_ && scrollable_area_->HasOverlayOverflowControls()) || |
| ScrollsOverflow() || |
| (GetLayoutObject().IsSVGRoot() && |
| To<LayoutSVGRoot>(GetLayoutObject()) |
| .HasDescendantCompositingReasons()); |
| } |
| |
| void PaintLayer::UpdateSelfPaintingLayer() { |
| bool is_self_painting_layer = ShouldBeSelfPaintingLayer(); |
| if (IsSelfPaintingLayer() == is_self_painting_layer) |
| return; |
| |
| // Invalidate the old subsequences which may no longer contain some |
| // descendants of this layer because of the self painting status change. |
| SetNeedsRepaint(); |
| is_self_painting_layer_ = is_self_painting_layer; |
| self_painting_status_changed_ = true; |
| // Self-painting change can change the compositing container chain; |
| // invalidate the new chain in addition to the old one. |
| MarkCompositingContainerChainForNeedsRepaint(); |
| |
| if (is_self_painting_layer) |
| SetNeedsVisualOverflowRecalc(); |
| |
| if (PaintLayer* parent = Parent()) { |
| parent->MarkAncestorChainForFlagsUpdate(); |
| |
| if (PaintLayer* enclosing_self_painting_layer = |
| parent->EnclosingSelfPaintingLayer()) { |
| if (is_self_painting_layer) |
| MergeNeedsPaintPhaseFlagsFrom(*enclosing_self_painting_layer); |
| else |
| enclosing_self_painting_layer->MergeNeedsPaintPhaseFlagsFrom(*this); |
| } |
| } |
| } |
| |
| PaintLayer* PaintLayer::EnclosingSelfPaintingLayer() { |
| PaintLayer* layer = this; |
| while (layer && !layer->IsSelfPaintingLayer()) |
| layer = layer->Parent(); |
| return layer; |
| } |
| |
| bool PaintLayer::HasNonEmptyChildLayoutObjects() const { |
| // Some HTML can cause whitespace text nodes to have layoutObjects, like: |
| // <div> |
| // <img src=...> |
| // </div> |
| // so test for 0x0 LayoutTexts here |
| for (const auto* child = GetLayoutObject().SlowFirstChild(); child; |
| child = child->NextSibling()) { |
| if (!child->HasLayer()) { |
| if (child->IsLayoutInline() || !child->IsBox()) |
| return true; |
| |
| const auto* box = To<LayoutBox>(child); |
| if (!box->Size().IsZero() || box->HasVisualOverflow()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PaintLayer::HasBoxDecorationsOrBackground() const { |
| return GetLayoutObject().StyleRef().HasBoxDecorations() || |
| GetLayoutObject().StyleRef().HasBackground(); |
| } |
| |
| bool PaintLayer::HasVisibleBoxDecorations() const { |
| if (!HasVisibleContent()) |
| return false; |
| |
| return HasBoxDecorationsOrBackground() || HasOverflowControls(); |
| } |
| |
| void PaintLayer::UpdateFilters(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| if (!filter_on_effect_node_dirty_) { |
| filter_on_effect_node_dirty_ = |
| old_style ? !old_style->FilterDataEquivalent(new_style) || |
| !old_style->ReflectionDataEquivalent(new_style) |
| : new_style.HasFilterInducingProperty(); |
| } |
| |
| if (!new_style.HasFilterInducingProperty() && |
| (!old_style || !old_style->HasFilterInducingProperty())) |
| return; |
| |
| const bool had_resource_info = ResourceInfo(); |
| if (new_style.HasFilterInducingProperty()) |
| new_style.Filter().AddClient(EnsureResourceInfo()); |
| if (had_resource_info && old_style) |
| old_style->Filter().RemoveClient(*ResourceInfo()); |
| } |
| |
| void PaintLayer::UpdateBackdropFilters(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| if (!backdrop_filter_on_effect_node_dirty_) { |
| backdrop_filter_on_effect_node_dirty_ = |
| old_style ? !old_style->BackdropFilterDataEquivalent(new_style) |
| : new_style.HasBackdropFilter(); |
| } |
| } |
| |
| void PaintLayer::UpdateClipPath(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| ClipPathOperation* new_clip = new_style.ClipPath(); |
| ClipPathOperation* old_clip = old_style ? old_style->ClipPath() : nullptr; |
| if (!new_clip && !old_clip) |
| return; |
| const bool had_resource_info = ResourceInfo(); |
| if (auto* reference_clip = DynamicTo<ReferenceClipPathOperation>(new_clip)) |
| reference_clip->AddClient(EnsureResourceInfo()); |
| if (had_resource_info) { |
| if (auto* old_reference_clip = |
| DynamicTo<ReferenceClipPathOperation>(old_clip)) |
| old_reference_clip->RemoveClient(*ResourceInfo()); |
| } |
| } |
| |
| bool PaintLayer::AttemptDirectCompositingUpdate( |
| const StyleDifference& diff, |
| const ComputedStyle* old_style) { |
| CompositingReasons old_potential_compositing_reasons_from_style = |
| PotentialCompositingReasonsFromStyle(); |
| if (Compositor() && |
| (diff.HasDifference() || needs_compositing_reasons_update_)) |
| Compositor()->UpdatePotentialCompositingReasonsFromStyle(*this); |
| needs_compositing_reasons_update_ = false; |
| |
| // This function implements an optimization for transforms and opacity. |
| // A common pattern is for a touchmove handler to update the transform |
| // and/or an opacity of an element every frame while the user moves their |
| // finger across the screen. The conditions below recognize when the |
| // compositing state is set up to receive a direct transform or opacity |
| // update. |
| |
| if (!diff.HasAtMostPropertySpecificDifferences( |
| StyleDifference::kTransformChanged | |
| StyleDifference::kOpacityChanged)) |
| return false; |
| // The potentialCompositingReasonsFromStyle could have changed without |
| // a corresponding StyleDifference if an animation started or ended. |
| if (PotentialCompositingReasonsFromStyle() != |
| old_potential_compositing_reasons_from_style) |
| return false; |
| if (!rare_data_ || !rare_data_->composited_layer_mapping) |
| return false; |
| |
| // If a transform changed, we can't use the fast path. |
| if (diff.TransformChanged()) |
| return false; |
| |
| // We composite transparent Layers differently from non-transparent |
| // Layers even when the non-transparent Layers are already a |
| // stacking context. |
| if (diff.OpacityChanged() && |
| layout_object_->StyleRef().HasOpacity() != old_style->HasOpacity()) |
| return false; |
| |
| // Changes in pointer-events affect hit test visibility of the scrollable |
| // area and its |m_scrollsOverflow| value which determines if the layer |
| // requires composited scrolling or not. |
| if (scrollable_area_ && |
| layout_object_->StyleRef().PointerEvents() != old_style->PointerEvents()) |
| return false; |
| |
| UpdateTransform(old_style, GetLayoutObject().StyleRef()); |
| |
| // FIXME: Consider introducing a smaller graphics layer update scope |
| // that just handles transforms and opacity. GraphicsLayerUpdateLocal |
| // will also program bounds, clips, and many other properties that could |
| // not possibly have changed. |
| rare_data_->composited_layer_mapping->SetNeedsGraphicsLayerUpdate( |
| kGraphicsLayerUpdateLocal); |
| if (Compositor()) { |
| Compositor()->SetNeedsCompositingUpdate( |
| kCompositingUpdateAfterGeometryChange); |
| } |
| |
| if (RequiresScrollableArea()) { |
| DCHECK(scrollable_area_); |
| scrollable_area_->UpdateAfterStyleChange(old_style); |
| } |
| |
| return true; |
| } |
| |
| void PaintLayer::StyleDidChange(StyleDifference diff, |
| const ComputedStyle* old_style) { |
| UpdateScrollableArea(); |
| |
| if (AttemptDirectCompositingUpdate(diff, old_style)) { |
| if (diff.HasDifference()) |
| GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| return; |
| } |
| |
| if (PaintLayerStackingNode::StyleDidChange(*this, old_style)) |
| MarkAncestorChainForFlagsUpdate(); |
| |
| if (RequiresScrollableArea()) { |
| DCHECK(scrollable_area_); |
| scrollable_area_->UpdateAfterStyleChange(old_style); |
| } |
| |
| // Overlay scrollbars can make this layer self-painting so we need |
| // to recompute the bit once scrollbars have been updated. |
| UpdateSelfPaintingLayer(); |
| |
| if (!diff.CompositingReasonsChanged() && |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // For querying stale GetCompositingState(). |
| DisableCompositingQueryAsserts disable; |
| |
| // Compositing inputs update is required when the PaintLayer is currently |
| // composited. This is because even style changes as simple as background |
| // color change, or pointer-events state change, can update compositing |
| // state. |
| if (old_style && GetCompositingState() == kPaintsIntoOwnBacking) |
| SetNeedsCompositingInputsUpdate(); |
| } |
| |
| // HasAlphaChanged can affect whether a composited layer is opaque. |
| if (diff.NeedsLayout() || diff.HasAlphaChanged()) |
| SetNeedsCompositingInputsUpdate(); |
| |
| // A scroller that changes background color might become opaque or not |
| // opaque, which in turn affects whether it can be composited on low-DPI |
| // screens. |
| if (GetScrollableArea() && GetScrollableArea()->ScrollsOverflow() && |
| diff.HasDifference()) { |
| SetNeedsCompositingInputsUpdate(); |
| } |
| |
| // See also |LayoutObject::SetStyle| which handles these invalidations if a |
| // PaintLayer is not present. |
| if (diff.TransformChanged() || diff.OpacityChanged() || |
| diff.ZIndexChanged() || diff.FilterChanged() || diff.CssClipChanged() || |
| diff.BlendModeChanged() || diff.MaskChanged() || |
| diff.CompositingReasonsChanged()) { |
| GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| SetNeedsCompositingInputsUpdate(); |
| } |
| |
| const ComputedStyle& new_style = GetLayoutObject().StyleRef(); |
| // HasNonContainedAbsolutePositionDescendant depends on position changes. |
| if (!old_style || old_style->GetPosition() != new_style.GetPosition()) |
| MarkAncestorChainForFlagsUpdate(); |
| |
| UpdateTransform(old_style, new_style); |
| UpdateFilters(old_style, new_style); |
| UpdateBackdropFilters(old_style, new_style); |
| UpdateClipPath(old_style, new_style); |
| |
| if (diff.ZIndexChanged()) { |
| // We don't need to invalidate paint of objects when paint order |
| // changes. However, we do need to repaint the containing stacking |
| // context, in order to generate new paint chunks in the correct order. |
| // Raster invalidation will be issued if needed during paint. |
| if (auto* stacking_context = AncestorStackingContext()) |
| stacking_context->SetNeedsRepaint(); |
| } |
| |
| if (old_style) { |
| bool new_painted_output_invisible = |
| PaintLayerPainter::PaintedOutputInvisible(new_style); |
| if (PaintLayerPainter::PaintedOutputInvisible(*old_style) != |
| new_painted_output_invisible) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // Force repaint of the subtree for two purposes: |
| // 1. To ensure FCP/LCP will be reported. See crbug.com/1184903. |
| // 2. To update effectively_invisible flags of PaintChunks. |
| // TODO(crbug.com/1104218): Optimize this. |
| GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation(); |
| } else { |
| // Change of PaintedOutputInvisible() will affect existence of paint |
| // chunks, so needs repaint. |
| SetNeedsRepaint(); |
| } |
| } |
| } |
| } |
| |
| IntPoint PaintLayer::PixelSnappedScrolledContentOffset() const { |
| if (GetLayoutObject().IsScrollContainer()) |
| return GetLayoutBox()->PixelSnappedScrolledContentOffset(); |
| return IntPoint(); |
| } |
| |
| PaintLayerClipper PaintLayer::Clipper( |
| GeometryMapperOption geometry_mapper_option) const { |
| return PaintLayerClipper(*this, geometry_mapper_option == |
| GeometryMapperOption::kUseGeometryMapper); |
| } |
| |
| bool PaintLayer::ScrollsOverflow() const { |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) |
| return scrollable_area->ScrollsOverflow(); |
| |
| return false; |
| } |
| |
| FilterOperations PaintLayer::FilterOperationsIncludingReflection() const { |
| const auto& style = GetLayoutObject().StyleRef(); |
| FilterOperations filter_operations = style.Filter(); |
| if (GetLayoutObject().HasReflection() && GetLayoutObject().IsBox()) { |
| BoxReflection reflection = BoxReflectionForPaintLayer(*this, style); |
| filter_operations.Operations().push_back( |
| MakeGarbageCollected<BoxReflectFilterOperation>(reflection)); |
| } |
| return filter_operations; |
| } |
| |
| void PaintLayer::UpdateCompositorFilterOperationsForFilter( |
| CompositorFilterOperations& operations) { |
| auto filter = FilterOperationsIncludingReflection(); |
| FloatRect reference_box = FilterReferenceBox(); |
| |
| // CompositorFilter needs the reference box to be unzoomed. |
| float zoom = GetLayoutObject().StyleRef().EffectiveZoom(); |
| if (zoom != 1) |
| reference_box.Scale(1 / zoom); |
| |
| // Use the existing |operations| if there is no change. |
| if (!operations.IsEmpty() && !filter_on_effect_node_dirty_ && |
| reference_box == operations.ReferenceBox()) |
| return; |
| |
| operations = |
| FilterEffectBuilder(reference_box, zoom).BuildFilterOperations(filter); |
| filter_on_effect_node_dirty_ = false; |
| } |
| |
| void PaintLayer::UpdateCompositorFilterOperationsForBackdropFilter( |
| CompositorFilterOperations& operations, |
| gfx::RRectF& backdrop_filter_bounds) { |
| const auto& style = GetLayoutObject().StyleRef(); |
| if (style.BackdropFilter().IsEmpty()) { |
| operations.Clear(); |
| backdrop_filter_on_effect_node_dirty_ = false; |
| return; |
| } |
| |
| FloatRect reference_box = BackdropFilterReferenceBox(); |
| backdrop_filter_bounds = BackdropFilterBounds(); |
| // CompositorFilter needs the reference box to be unzoomed. |
| float zoom = style.EffectiveZoom(); |
| if (zoom != 1) |
| reference_box.Scale(1 / zoom); |
| |
| // Use the existing |operations| if there is no change. |
| if (!operations.IsEmpty() && !backdrop_filter_on_effect_node_dirty_ && |
| reference_box == operations.ReferenceBox()) |
| return; |
| |
| // Tack on regular filter values here - they need to be applied to the |
| // backdrop image as well, in addition to being applied to the painted content |
| // and children of the element. This is a bit of a hack - according to the |
| // spec, filters should apply to the entire render pass as a whole, including |
| // the backdrop-filtered content. However, because in the case that we have |
| // both filters and backdrop-filters on a single element, we create two effect |
| // nodes, and two render surfaces, and the backdrop-filter node comes first. |
| // To get around that, we add the "regular" filters to the backdrop filters to |
| // approximate. |
| FilterOperations filter_operations = style.BackdropFilter(); |
| filter_operations.Operations().AppendVector(style.Filter().Operations()); |
| // Use kClamp tile mode to avoid pixel moving filters bringing in black |
| // transparent pixels from the viewport edge. |
| operations = FilterEffectBuilder(reference_box, zoom, nullptr, nullptr, |
| SkTileMode::kClamp) |
| .BuildFilterOperations(filter_operations); |
| // Note that |operations| may be empty here, if the |filter_operations| list |
| // contains only invalid filters (e.g. invalid reference filters). See |
| // https://crbug.com/983157 for details. |
| backdrop_filter_on_effect_node_dirty_ = false; |
| } |
| |
| PaintLayerResourceInfo& PaintLayer::EnsureResourceInfo() { |
| PaintLayerRareData& rare_data = EnsureRareData(); |
| if (!rare_data.resource_info) { |
| rare_data.resource_info = |
| MakeGarbageCollected<PaintLayerResourceInfo>(this); |
| } |
| return *rare_data.resource_info; |
| } |
| |
| void PaintLayer::RemoveAncestorScrollContainerLayer( |
| const PaintLayer* removed_layer) { |
| // If the current scroll container layer does not match the removed layer |
| // the ancestor overflow layer has changed so we can stop searching. |
| if (AncestorScrollContainerLayer() && |
| AncestorScrollContainerLayer() != removed_layer) { |
| return; |
| } |
| |
| if (AncestorScrollContainerLayer()) { |
| // If the previous AncestorScrollContainerLayer is the root and this object |
| // is a sticky viewport constrained object, it is no longer known to be |
| // constrained by the root. |
| if (AncestorScrollContainerLayer()->IsRootLayer() && |
| GetLayoutObject().StyleRef().HasStickyConstrainedPosition()) { |
| if (LocalFrameView* frame_view = GetLayoutObject().GetFrameView()) { |
| frame_view->RemoveViewportConstrainedObject( |
| GetLayoutObject(), |
| LocalFrameView::ViewportConstrainedType::kSticky); |
| } |
| } |
| |
| if (PaintLayerScrollableArea* ancestor_scrollable_area = |
| AncestorScrollContainerLayer()->GetScrollableArea()) { |
| // TODO(pdr): When CompositeAfterPaint is enabled, we will need to |
| // invalidate the scroll paint property subtree for this so main thread |
| // scroll reasons are recomputed. |
| ancestor_scrollable_area->InvalidateStickyConstraintsFor(this); |
| } |
| } |
| UpdateAncestorScrollContainerLayer(nullptr); |
| PaintLayer* current = first_; |
| while (current) { |
| current->RemoveAncestorScrollContainerLayer(removed_layer); |
| current = current->NextSibling(); |
| } |
| } |
| |
| FloatRect PaintLayer::MapRectForFilter(const FloatRect& rect) const { |
| if (!HasFilterThatMovesPixels()) |
| return rect; |
| return FilterOperationsIncludingReflection().MapRect(rect); |
| } |
| |
| PhysicalRect PaintLayer::MapRectForFilter(const PhysicalRect& rect) const { |
| if (!HasFilterThatMovesPixels()) |
| return rect; |
| return PhysicalRect::EnclosingRect(MapRectForFilter(FloatRect(rect))); |
| } |
| |
| bool PaintLayer::HasFilterThatMovesPixels() const { |
| if (!HasFilterInducingProperty()) |
| return false; |
| const ComputedStyle& style = GetLayoutObject().StyleRef(); |
| if (style.HasFilter() && style.Filter().HasFilterThatMovesPixels()) |
| return true; |
| if (GetLayoutObject().HasReflection()) |
| return true; |
| return false; |
| } |
| |
| void PaintLayer::SetNeedsRepaint() { |
| SetSelfNeedsRepaint(); |
| // Do this unconditionally to ensure container chain is marked when |
| // compositing status of the layer changes. |
| MarkCompositingContainerChainForNeedsRepaint(); |
| } |
| |
| void PaintLayer::SetSelfNeedsRepaint() { |
| self_needs_repaint_ = true; |
| // Invalidate as a display item client. |
| static_cast<DisplayItemClient*>(this)->Invalidate(); |
| } |
| |
| void PaintLayer::SetDescendantNeedsRepaint() { |
| if (descendant_needs_repaint_) |
| return; |
| descendant_needs_repaint_ = true; |
| MarkCompositingContainerChainForNeedsRepaint(); |
| } |
| |
| void PaintLayer::MarkCompositingContainerChainForNeedsRepaint() { |
| PaintLayer* layer = this; |
| while (true) { |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // Need to access compositingState(). We've ensured correct flag setting |
| // when compositingState() changes. |
| DisableCompositingQueryAsserts disabler; |
| if (layer->GetCompositingState() == kPaintsIntoOwnBacking) |
| break; |
| if (CompositedLayerMapping* grouped_mapping = layer->GroupedMapping()) { |
| // TODO(wkorman): As we clean up the CompositedLayerMapping needsRepaint |
| // logic to delegate to scrollbars, we may be able to remove the line |
| // below as well. |
| grouped_mapping->OwningLayer().SetNeedsRepaint(); |
| break; |
| } |
| } |
| |
| // For a non-self-painting layer having self-painting descendant, the |
| // descendant will be painted through this layer's Parent() instead of |
| // this layer's Container(), so in addition to the CompositingContainer() |
| // chain, we also need to mark NeedsRepaint for Parent(). |
| // TODO(crbug.com/828103): clean up this. |
| if (layer->Parent() && !layer->IsSelfPaintingLayer()) |
| layer->Parent()->SetNeedsRepaint(); |
| |
| // Don't mark across frame boundary here. LocalFrameView::PaintTree() will |
| // propagate child frame NeedsRepaint flag into the owning frame. |
| PaintLayer* container = layer->CompositingContainer(); |
| if (!container || container->descendant_needs_repaint_) |
| break; |
| |
| // If the layer doesn't need painting itself (which means we're propagating |
| // a bit from its children) and it blocks child painting via display lock, |
| // then stop propagating the dirty bit. |
| if (!layer->SelfNeedsRepaint() && |
| layer->GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| break; |
| |
| container->descendant_needs_repaint_ = true; |
| layer = container; |
| } |
| } |
| |
| void PaintLayer::ClearNeedsRepaintRecursively() { |
| self_needs_repaint_ = false; |
| |
| // Don't clear dirty bits in a display-locked subtree. |
| if (GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| return; |
| |
| for (PaintLayer* child = FirstChild(); child; child = child->NextSibling()) |
| child->ClearNeedsRepaintRecursively(); |
| descendant_needs_repaint_ = false; |
| } |
| |
| const PaintLayer* PaintLayer::CommonAncestor(const PaintLayer* other) const { |
| DCHECK(other); |
| if (this == other) |
| return this; |
| |
| int this_depth = 0; |
| for (auto* layer = this; layer; layer = layer->Parent()) { |
| if (layer == other) |
| return layer; |
| this_depth++; |
| } |
| int other_depth = 0; |
| for (auto* layer = other; layer; layer = layer->Parent()) { |
| if (layer == this) |
| return layer; |
| other_depth++; |
| } |
| |
| const PaintLayer* this_iterator = this; |
| const PaintLayer* other_iterator = other; |
| for (; this_depth > other_depth; this_depth--) |
| this_iterator = this_iterator->Parent(); |
| for (; other_depth > this_depth; other_depth--) |
| other_iterator = other_iterator->Parent(); |
| |
| while (this_iterator) { |
| if (this_iterator == other_iterator) |
| return this_iterator; |
| this_iterator = this_iterator->Parent(); |
| other_iterator = other_iterator->Parent(); |
| } |
| |
| DCHECK(!this_iterator); |
| DCHECK(!other_iterator); |
| return nullptr; |
| } |
| |
| void PaintLayer::SetNeedsCullRectUpdate() { |
| DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled()); |
| |
| if (needs_cull_rect_update_) |
| return; |
| needs_cull_rect_update_ = true; |
| MarkCompositingContainerChainForNeedsCullRectUpdate(); |
| } |
| |
| void PaintLayer::SetForcesChildrenCullRectUpdate() { |
| DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled()); |
| |
| if (forces_children_cull_rect_update_) |
| return; |
| forces_children_cull_rect_update_ = true; |
| descendant_needs_cull_rect_update_ = true; |
| MarkCompositingContainerChainForNeedsCullRectUpdate(); |
| } |
| |
| void PaintLayer::MarkCompositingContainerChainForNeedsCullRectUpdate() { |
| // Mark compositing container chain for needing cull rect update. This is |
| // similar to MarkCompositingContainerChainForNeedsRepaint(). |
| PaintLayer* layer = this; |
| while (true) { |
| // For a non-self-painting layer having self-painting descendant, the |
| // descendant will be painted through this layer's Parent() instead of |
| // this layer's Container(), so in addition to the CompositingContainer() |
| // chain, we also need to mark NeedsRepaint for Parent(). |
| // TODO(crbug.com/828103): clean up this. |
| if (layer->Parent() && !layer->IsSelfPaintingLayer()) |
| layer->Parent()->SetNeedsCullRectUpdate(); |
| |
| PaintLayer* container = layer->CompositingContainer(); |
| if (!container) { |
| auto* owner = layer->GetLayoutObject().GetFrame()->OwnerLayoutObject(); |
| if (!owner) |
| break; |
| container = owner->EnclosingLayer(); |
| } |
| |
| DCHECK(!container->GetLayoutObject().ChildPrePaintBlockedByDisplayLock()); |
| |
| if (container->descendant_needs_cull_rect_update_) |
| break; |
| |
| container->descendant_needs_cull_rect_update_ = true; |
| layer = container; |
| } |
| } |
| |
| void PaintLayer::DirtyStackingContextZOrderLists() { |
| auto* stacking_context = AncestorStackingContext(); |
| if (!stacking_context) |
| return; |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // This invalidation code intentionally refers to stale state. |
| DisableCompositingQueryAsserts disabler; |
| |
| // Changes of stacking may result in graphics layers changing size |
| // due to new contents painting into them. |
| if (auto* mapping = stacking_context->GetCompositedLayerMapping()) |
| mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree); |
| } |
| |
| if (stacking_context->StackingNode()) |
| stacking_context->StackingNode()->DirtyZOrderLists(); |
| |
| MarkAncestorChainForFlagsUpdate(); |
| } |
| |
| DisableCompositingQueryAsserts::DisableCompositingQueryAsserts() |
| : disabler_(&g_compositing_query_mode, kCompositingQueriesAreAllowed) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| } |
| |
| } // namespace blink |
| |
| #if DCHECK_IS_ON() |
| void showLayerTree(const blink::PaintLayer* layer) { |
| if (!layer) { |
| LOG(ERROR) << "Cannot showLayerTree. Root is (nil)"; |
| return; |
| } |
| |
| absl::optional<blink::DisableCompositingQueryAsserts> disabler; |
| if (!blink::RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| disabler.emplace(); |
| |
| if (blink::LocalFrame* frame = layer->GetLayoutObject().GetFrame()) { |
| WTF::String output = |
| ExternalRepresentation(frame, |
| blink::kLayoutAsTextShowAllLayers | |
| blink::kLayoutAsTextShowLayerNesting | |
| blink::kLayoutAsTextShowCompositedLayers | |
| blink::kLayoutAsTextShowAddresses | |
| blink::kLayoutAsTextShowIDAndClass | |
| blink::kLayoutAsTextDontUpdateLayout | |
| blink::kLayoutAsTextShowLayoutState | |
| blink::kLayoutAsTextShowPaintProperties, |
| layer); |
| LOG(INFO) << output.Utf8(); |
| } |
| } |
| |
| void showLayerTree(const blink::LayoutObject* layoutObject) { |
| if (!layoutObject) { |
| LOG(ERROR) << "Cannot showLayerTree. Root is (nil)"; |
| return; |
| } |
| showLayerTree(layoutObject->EnclosingLayer()); |
| } |
| #endif |