diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/paint')
137 files changed, 6023 insertions, 3600 deletions
diff --git a/chromium/third_party/blink/renderer/core/paint/DEPS b/chromium/third_party/blink/renderer/core/paint/DEPS index 00b071d198a..6fb36eba5f9 100644 --- a/chromium/third_party/blink/renderer/core/paint/DEPS +++ b/chromium/third_party/blink/renderer/core/paint/DEPS @@ -8,7 +8,7 @@ include_rules = [ ] specific_include_rules = { - "(theme_painter|theme_painter_default|object_painter_base)\.cc": [ + "(theme_painter|theme_painter_default|outline_painter)\.cc": [ "+ui/native_theme/native_theme.h", "+ui/native_theme/native_theme_base.h", "+ui/gfx/color_utils.h" diff --git a/chromium/third_party/blink/renderer/core/paint/OWNERS b/chromium/third_party/blink/renderer/core/paint/OWNERS index 51aafa98fea..3c3161d0130 100644 --- a/chromium/third_party/blink/renderer/core/paint/OWNERS +++ b/chromium/third_party/blink/renderer/core/paint/OWNERS @@ -1,11 +1,7 @@ # OWNERS specializing in painting code. - chrishtr@chromium.org -enne@chromium.org fmalita@chromium.org fs@opera.com pdr@chromium.org schenney@chromium.org -senorblanco@chromium.org -trchen@chromium.org wangxianzhu@chromium.org diff --git a/chromium/third_party/blink/renderer/core/paint/background_image_geometry.cc b/chromium/third_party/blink/renderer/core/paint/background_image_geometry.cc index 193ab56845d..67e8f8c2d90 100644 --- a/chromium/third_party/blink/renderer/core/paint/background_image_geometry.cc +++ b/chromium/third_party/blink/renderer/core/paint/background_image_geometry.cc @@ -62,7 +62,7 @@ PhysicalOffset AccumulatedScrollOffsetForFixedBackground( block && !skip_info.AncestorSkipped(); block = block->ContainingBlock(&skip_info)) { if (block->IsScrollContainer()) - result += PhysicalOffsetToBeNoop(block->ScrolledContentOffset()); + result += block->ScrolledContentOffset(); if (block == container) break; } @@ -355,7 +355,7 @@ PhysicalRect FixedAttachmentPositioningArea( return rect; // The LayoutView is the only object that can paint a fixed background into // its scrolling contents layer, so it gets a special adjustment here. - rect.offset = PhysicalOffsetToBeNoop(layout_view->ScrolledContentOffset()); + rect.offset = layout_view->ScrolledContentOffset(); } rect.Move(AccumulatedScrollOffsetForFixedBackground(obj, container)); @@ -692,8 +692,7 @@ void BackgroundImageGeometry::CalculateFillTileSize( ? snapped_positioning_area_size : unsnapped_positioning_area_size; PhysicalSize image_intrinsic_size = PhysicalSize::FromFloatSizeFloor( - image->ImageSize(positioning_box_->GetDocument(), - positioning_box_->StyleRef().EffectiveZoom(), + image->ImageSize(positioning_box_->StyleRef().EffectiveZoom(), FloatSize(positioning_area_size), LayoutObject::ShouldRespectImageOrientation(box_))); switch (type) { @@ -979,4 +978,14 @@ PhysicalOffset BackgroundImageGeometry::OffsetInBackground( return element_positioning_area_offset_; } +PhysicalOffset BackgroundImageGeometry::ComputeDestPhase() const { + // Given the size that the whole image should draw at, and the input phase + // requested by the content, and the space between repeated tiles, compute a + // phase that is no more than one size + space in magnitude. + const PhysicalSize step_per_tile = tile_size_ + repeat_spacing_; + const PhysicalOffset phase = {IntMod(-phase_.left, step_per_tile.width), + IntMod(-phase_.top, step_per_tile.height)}; + return snapped_dest_rect_.offset + phase; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/background_image_geometry.h b/chromium/third_party/blink/renderer/core/paint/background_image_geometry.h index 9f294ef3b73..52c2f4593bc 100644 --- a/chromium/third_party/blink/renderer/core/paint/background_image_geometry.h +++ b/chromium/third_party/blink/renderer/core/paint/background_image_geometry.h @@ -64,6 +64,9 @@ class BackgroundImageGeometry { const PhysicalRect& UnsnappedDestRect() const { return unsnapped_dest_rect_; } const PhysicalRect& SnappedDestRect() const { return snapped_dest_rect_; } + // Compute the phase relative to the (snapped) destination offset. + PhysicalOffset ComputeDestPhase() const; + // Tile size is the area into which to draw one copy of the image. It // need not be the same as the intrinsic size of the image; if not, // the image will be resized (via an image filter) when painted into diff --git a/chromium/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc b/chromium/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc index d060df86a42..0b95523e435 100644 --- a/chromium/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc +++ b/chromium/third_party/blink/renderer/core/paint/block_flow_paint_invalidator.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/paint/box_paint_invalidator.h" +#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_invalidator.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/paint/block_painter.cc b/chromium/third_party/blink/renderer/core/paint/block_painter.cc index 1fc8030a694..93455b24c9a 100644 --- a/chromium/third_party/blink/renderer/core/paint/block_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/block_painter.cc @@ -135,17 +135,8 @@ void BlockPainter::PaintChildren(const PaintInfo& paint_info) { if (paint_info.DescendantPaintingBlocked()) return; - // We may use legacy paint to paint the anonymous fieldset child. The layout - // object for the rendered legend will be a child of that one, and has to be - // skipped here, since it's handled by a special NG fieldset painter. - bool may_contain_rendered_legend = - layout_block_.IsAnonymousNGFieldsetContentWrapper(); for (LayoutBox* child = layout_block_.FirstChildBox(); child; child = child->NextSiblingBox()) { - if (may_contain_rendered_legend && child->IsRenderedLegend()) { - may_contain_rendered_legend = false; - continue; - } PaintChild(*child, paint_info); } } diff --git a/chromium/third_party/blink/renderer/core/paint/block_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/block_painter_test.cc index c9906300cb6..e75ba37e2be 100644 --- a/chromium/third_party/blink/renderer/core/paint/block_painter_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/block_painter_test.cc @@ -166,10 +166,10 @@ TEST_P(BlockPainterTest, BlockingWheelEventRectSubsequenceCaching) { // Trigger a repaint with the whole stacking-context subsequence cached. GetLayoutView().Layer()->SetNeedsRepaint(); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); - EXPECT_EQ(1u, counter.NumNewCachedItems()); - EXPECT_EQ(1u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(1u, counter.num_cached_items); + EXPECT_EQ(1u, counter.num_cached_subsequences); EXPECT_SUBSEQUENCE_FROM_CHUNK(hit_test_client, ContentPaintChunks().begin() + 1, 1); @@ -217,10 +217,10 @@ TEST_P(BlockPainterTest, WheelEventRectPaintCaching) { ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(2, &hit_test_data))); sibling_element->setAttribute(html_names::kStyleAttr, "background: green;"); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); // Only the background display item of the sibling should be invalidated. - EXPECT_EQ(1u, counter.NumNewCachedItems()); + EXPECT_EQ(1u, counter.num_cached_items); EXPECT_THAT(ContentPaintChunks(), ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(2, &hit_test_data))); @@ -432,10 +432,10 @@ TEST_P(BlockPainterTest, TouchActionRectSubsequenceCaching) { // Trigger a repaint with the whole stacking-context subsequence cached. GetLayoutView().Layer()->SetNeedsRepaint(); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); - EXPECT_EQ(1u, counter.NumNewCachedItems()); - EXPECT_EQ(1u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(1u, counter.num_cached_items); + EXPECT_EQ(1u, counter.num_cached_subsequences); EXPECT_SUBSEQUENCE_FROM_CHUNK(hit_test_client, ContentPaintChunks().begin() + 1, 1); @@ -480,10 +480,10 @@ TEST_P(BlockPainterTest, TouchActionRectPaintCaching) { ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(2, &hit_test_data))); sibling_element->setAttribute(html_names::kStyleAttr, "background: green;"); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); // Only the background display item of the sibling should be invalidated. - EXPECT_EQ(1u, counter.NumNewCachedItems()); + EXPECT_EQ(1u, counter.num_cached_items); EXPECT_THAT(ContentPaintChunks(), ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(2, &hit_test_data))); diff --git a/chromium/third_party/blink/renderer/core/paint/box_border_painter.cc b/chromium/third_party/blink/renderer/core/paint/box_border_painter.cc index d6ca5091100..2340e9f10f4 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_border_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/box_border_painter.cc @@ -6,10 +6,9 @@ #include <algorithm> -#include "base/stl_util.h" +#include "base/cxx17_backports.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/object_painter.h" -#include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h" #include "third_party/blink/renderer/core/style/border_edge.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -86,20 +85,6 @@ inline bool BorderStyleHasUnmatchedColorsAtCorner(EBorderStyle style, return false; } -inline bool ColorsMatchAtCorner(BoxSide side, - BoxSide adjacent_side, - const BorderEdge edges[]) { - if (!edges[static_cast<unsigned>(adjacent_side)].ShouldRender()) - return false; - - if (!edges[static_cast<unsigned>(side)].SharesColorWith( - edges[static_cast<unsigned>(adjacent_side)])) - return false; - - return !BorderStyleHasUnmatchedColorsAtCorner( - edges[static_cast<unsigned>(side)].BorderStyle(), side, adjacent_side); -} - inline bool BorderWillArcInnerEdge(const FloatSize& first_radius, const FloatSize& second_radius) { return !first_radius.IsZero() || !second_radius.IsZero(); @@ -140,15 +125,15 @@ inline bool BorderStylesRequireMiter(BoxSide side, FloatRect CalculateSideRect(const FloatRoundedRect& outer_border, const BorderEdge& edge, - int side) { + BoxSide side) { FloatRect side_rect = outer_border.Rect(); float width = edge.Width(); - if (side == static_cast<unsigned>(BoxSide::kTop)) + if (side == BoxSide::kTop) side_rect.SetHeight(width); - else if (side == static_cast<unsigned>(BoxSide::kBottom)) + else if (side == BoxSide::kBottom) side_rect.ShiftYEdgeTo(side_rect.MaxY() - width); - else if (side == static_cast<unsigned>(BoxSide::kLeft)) + else if (side == BoxSide::kLeft) side_rect.SetWidth(width); else side_rect.ShiftXEdgeTo(side_rect.MaxX() - width); @@ -156,38 +141,6 @@ FloatRect CalculateSideRect(const FloatRoundedRect& outer_border, return side_rect; } -FloatRect CalculateSideRectIncludingInner(const FloatRoundedRect& outer_border, - const BorderEdge edges[], - BoxSide side) { - FloatRect side_rect = outer_border.Rect(); - float width; - - switch (side) { - case BoxSide::kTop: - width = side_rect.Height() - - edges[static_cast<unsigned>(BoxSide::kBottom)].Width(); - side_rect.SetHeight(width); - break; - case BoxSide::kBottom: - width = side_rect.Height() - - edges[static_cast<unsigned>(BoxSide::kTop)].Width(); - side_rect.ShiftYEdgeTo(side_rect.MaxY() - width); - break; - case BoxSide::kLeft: - width = side_rect.Width() - - edges[static_cast<unsigned>(BoxSide::kRight)].Width(); - side_rect.SetWidth(width); - break; - case BoxSide::kRight: - width = side_rect.Width() - - edges[static_cast<unsigned>(BoxSide::kLeft)].Width(); - side_rect.ShiftXEdgeTo(side_rect.MaxX() - width); - break; - } - - return side_rect; -} - FloatRoundedRect CalculateAdjustedInnerBorder( const FloatRoundedRect& inner_border, BoxSide side) { @@ -276,20 +229,6 @@ FloatRoundedRect CalculateAdjustedInnerBorder( return FloatRoundedRect(new_rect, new_radii); } -LayoutRectOutsets DoubleStripeInsets(const BorderEdge edges[], - BorderEdge::DoubleBorderStripe stripe) { - // Insets are representes as negative outsets. - return LayoutRectOutsets( - -edges[static_cast<unsigned>(BoxSide::kTop)].GetDoubleBorderStripeWidth( - stripe), - -edges[static_cast<unsigned>(BoxSide::kRight)].GetDoubleBorderStripeWidth( - stripe), - -edges[static_cast<unsigned>(BoxSide::kBottom)] - .GetDoubleBorderStripeWidth(stripe), - -edges[static_cast<unsigned>(BoxSide::kLeft)].GetDoubleBorderStripeWidth( - stripe)); -} - void DrawSolidBorderRect(GraphicsContext& context, const FloatRect& border_rect, float border_width, @@ -383,20 +322,25 @@ static_assert(static_cast<unsigned>(BoxSide::kLeft) == 3, // before solid edges (inset/outset/groove/ridge/solid) to maximize overdraw // opportunities. const unsigned kStylePriority[] = { - 0 /* BorderStyleNone */, 0 /* BorderStyleHidden */, - 2 /* BorderStyleInset */, 2 /* BorderStyleGroove */, - 2 /* BorderStyleOutset */, 2 /* BorderStyleRidge */, - 1 /* BorderStyleDotted */, 1 /* BorderStyleDashed */, - 3 /* BorderStyleSolid */, 1 /* BorderStyleDouble */ + 0, // EBorderStyle::kNone + 0, // EBorderStyle::kHidden + 2, // EBorderStyle::kInset + 2, // EBorderStyle::kGroove + 2, // EBorderStyle::kOutset + 2, // EBorderStyle::kRidge, + 1, // EBorderStyle::kDotted + 1, // EBorderStyle::kDashed + 3, // EBorderStyle::kSolid + 1, // EBorderStyle::kDouble }; // Given the same style, prefer drawing in non-adjacent order to minimize the // number of sides which require miters. const unsigned kSidePriority[] = { - 0, /* BSTop */ - 2, /* BSRight */ - 1, /* BSBottom */ - 3, /* BSLeft */ + 0, // BoxSide::kTop + 2, // BoxSide::kRight + 1, // BoxSide::kBottom + 3, // BoxSide::kLeft }; // Edges sharing the same opacity. Stores both a side list and an edge bitfield @@ -421,12 +365,297 @@ void ClipQuad(GraphicsContext& context, context.ClipPath(path.detach(), antialiased ? kAntiAliased : kNotAntiAliased); } +void DrawDashedOrDottedBoxSide(GraphicsContext& context, + int x1, + int y1, + int x2, + int y2, + BoxSide side, + Color color, + int thickness, + EBorderStyle style, + bool antialias) { + DCHECK_GT(thickness, 0); + + GraphicsContextStateSaver state_saver(context); + context.SetShouldAntialias(antialias); + context.SetStrokeColor(color); + context.SetStrokeThickness(thickness); + context.SetStrokeStyle(style == EBorderStyle::kDashed ? kDashedStroke + : kDottedStroke); + + switch (side) { + case BoxSide::kBottom: + case BoxSide::kTop: { + int mid_y = y1 + thickness / 2; + context.DrawLine(IntPoint(x1, mid_y), IntPoint(x2, mid_y)); + break; + } + case BoxSide::kRight: + case BoxSide::kLeft: { + int mid_x = x1 + thickness / 2; + context.DrawLine(IntPoint(mid_x, y1), IntPoint(mid_x, y2)); + break; + } + } +} + +void DrawDoubleBoxSide(GraphicsContext& context, + int x1, + int y1, + int x2, + int y2, + int length, + BoxSide side, + Color color, + float thickness, + int adjacent_width1, + int adjacent_width2, + bool antialias) { + int third_of_thickness = (thickness + 1) / 3; + DCHECK_GT(third_of_thickness, 0); + + if (!adjacent_width1 && !adjacent_width2) { + StrokeStyle old_stroke_style = context.GetStrokeStyle(); + context.SetStrokeStyle(kNoStroke); + context.SetFillColor(color); + + bool was_antialiased = context.ShouldAntialias(); + context.SetShouldAntialias(antialias); + + switch (side) { + case BoxSide::kTop: + case BoxSide::kBottom: + context.DrawRect(IntRect(x1, y1, length, third_of_thickness)); + context.DrawRect( + IntRect(x1, y2 - third_of_thickness, length, third_of_thickness)); + break; + case BoxSide::kLeft: + case BoxSide::kRight: + context.DrawRect(IntRect(x1, y1, third_of_thickness, length)); + context.DrawRect( + IntRect(x2 - third_of_thickness, y1, third_of_thickness, length)); + break; + } + + context.SetShouldAntialias(was_antialiased); + context.SetStrokeStyle(old_stroke_style); + return; + } + + int adjacent1_big_third = + ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 3; + int adjacent2_big_third = + ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 3; + + switch (side) { + case BoxSide::kTop: + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), y1, + x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), + y1 + third_of_thickness, side, color, EBorderStyle::kSolid, + adjacent1_big_third, adjacent2_big_third, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), + y2 - third_of_thickness, + x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), y2, side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + break; + case BoxSide::kLeft: + BoxBorderPainter::DrawLineForBoxSide( + context, x1, y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), + x1 + third_of_thickness, + y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x2 - third_of_thickness, + y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), x2, + y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + break; + case BoxSide::kBottom: + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), y1, + x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), + y1 + third_of_thickness, side, color, EBorderStyle::kSolid, + adjacent1_big_third, adjacent2_big_third, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), + y2 - third_of_thickness, + x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), y2, side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + break; + case BoxSide::kRight: + BoxBorderPainter::DrawLineForBoxSide( + context, x1, y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), + x1 + third_of_thickness, + y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x2 - third_of_thickness, + y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), x2, + y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side, color, + EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, + antialias); + break; + default: + break; + } +} + +void DrawRidgeOrGrooveBoxSide(GraphicsContext& context, + int x1, + int y1, + int x2, + int y2, + BoxSide side, + Color color, + EBorderStyle style, + int adjacent_width1, + int adjacent_width2, + bool antialias) { + EBorderStyle s1; + EBorderStyle s2; + if (style == EBorderStyle::kGroove) { + s1 = EBorderStyle::kInset; + s2 = EBorderStyle::kOutset; + } else { + s1 = EBorderStyle::kOutset; + s2 = EBorderStyle::kInset; + } + + int adjacent1_big_half = + ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 2; + int adjacent2_big_half = + ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 2; + + switch (side) { + case BoxSide::kTop: + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max(-adjacent_width1, 0) / 2, y1, + x2 - std::max(-adjacent_width2, 0) / 2, (y1 + y2 + 1) / 2, side, + color, s1, adjacent1_big_half, adjacent2_big_half, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max(adjacent_width1 + 1, 0) / 2, (y1 + y2 + 1) / 2, + x2 - std::max(adjacent_width2 + 1, 0) / 2, y2, side, color, s2, + adjacent_width1 / 2, adjacent_width2 / 2, antialias); + break; + case BoxSide::kLeft: + BoxBorderPainter::DrawLineForBoxSide( + context, x1, y1 + std::max(-adjacent_width1, 0) / 2, + (x1 + x2 + 1) / 2, y2 - std::max(-adjacent_width2, 0) / 2, side, + color, s1, adjacent1_big_half, adjacent2_big_half, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, (x1 + x2 + 1) / 2, y1 + std::max(adjacent_width1 + 1, 0) / 2, + x2, y2 - std::max(adjacent_width2 + 1, 0) / 2, side, color, s2, + adjacent_width1 / 2, adjacent_width2 / 2, antialias); + break; + case BoxSide::kBottom: + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max(adjacent_width1, 0) / 2, y1, + x2 - std::max(adjacent_width2, 0) / 2, (y1 + y2 + 1) / 2, side, color, + s2, adjacent1_big_half, adjacent2_big_half, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, x1 + std::max(-adjacent_width1 + 1, 0) / 2, + (y1 + y2 + 1) / 2, x2 - std::max(-adjacent_width2 + 1, 0) / 2, y2, + side, color, s1, adjacent_width1 / 2, adjacent_width2 / 2, antialias); + break; + case BoxSide::kRight: + BoxBorderPainter::DrawLineForBoxSide( + context, x1, y1 + std::max(adjacent_width1, 0) / 2, (x1 + x2 + 1) / 2, + y2 - std::max(adjacent_width2, 0) / 2, side, color, s2, + adjacent1_big_half, adjacent2_big_half, antialias); + BoxBorderPainter::DrawLineForBoxSide( + context, (x1 + x2 + 1) / 2, + y1 + std::max(-adjacent_width1 + 1, 0) / 2, x2, + y2 - std::max(-adjacent_width2 + 1, 0) / 2, side, color, s1, + adjacent_width1 / 2, adjacent_width2 / 2, antialias); + break; + } +} + +void FillQuad(GraphicsContext& context, + const FloatPoint quad[], + const Color& color, + bool antialias) { + SkPathBuilder path; + path.moveTo(FloatPointToSkPoint(quad[0])); + path.lineTo(FloatPointToSkPoint(quad[1])); + path.lineTo(FloatPointToSkPoint(quad[2])); + path.lineTo(FloatPointToSkPoint(quad[3])); + PaintFlags flags(context.FillFlags()); + flags.setAntiAlias(antialias); + flags.setColor(color.Rgb()); + + context.DrawPath(path.detach(), flags); +} + +void DrawSolidBoxSide(GraphicsContext& context, + int x1, + int y1, + int x2, + int y2, + BoxSide side, + Color color, + int adjacent_width1, + int adjacent_width2, + bool antialias) { + DCHECK_GE(x2, x1); + DCHECK_GE(y2, y1); + + if (!adjacent_width1 && !adjacent_width2) { + // Tweak antialiasing to match the behavior of fillQuad(); + // this matters for rects in transformed contexts. + bool was_antialiased = context.ShouldAntialias(); + if (antialias != was_antialiased) + context.SetShouldAntialias(antialias); + context.FillRect(IntRect(x1, y1, x2 - x1, y2 - y1), color); + if (antialias != was_antialiased) + context.SetShouldAntialias(was_antialiased); + return; + } + + FloatPoint quad[4]; + switch (side) { + case BoxSide::kTop: + quad[0] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y1); + quad[1] = FloatPoint(x1 + std::max(adjacent_width1, 0), y2); + quad[2] = FloatPoint(x2 - std::max(adjacent_width2, 0), y2); + quad[3] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y1); + break; + case BoxSide::kBottom: + quad[0] = FloatPoint(x1 + std::max(adjacent_width1, 0), y1); + quad[1] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y2); + quad[2] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y2); + quad[3] = FloatPoint(x2 - std::max(adjacent_width2, 0), y1); + break; + case BoxSide::kLeft: + quad[0] = FloatPoint(x1, y1 + std::max(-adjacent_width1, 0)); + quad[1] = FloatPoint(x1, y2 - std::max(-adjacent_width2, 0)); + quad[2] = FloatPoint(x2, y2 - std::max(adjacent_width2, 0)); + quad[3] = FloatPoint(x2, y1 + std::max(adjacent_width1, 0)); + break; + case BoxSide::kRight: + quad[0] = FloatPoint(x1, y1 + std::max(adjacent_width1, 0)); + quad[1] = FloatPoint(x1, y2 - std::max(adjacent_width2, 0)); + quad[2] = FloatPoint(x2, y2 - std::max(-adjacent_width2, 0)); + quad[3] = FloatPoint(x2, y1 + std::max(-adjacent_width1, 0)); + break; + } + + FillQuad(context, quad, color, antialias); +} + } // anonymous namespace // Holds edges grouped by opacity and sorted in paint order. struct BoxBorderPainter::ComplexBorderInfo { - ComplexBorderInfo(const BoxBorderPainter& border_painter, bool anti_alias) - : anti_alias(anti_alias) { + explicit ComplexBorderInfo(const BoxBorderPainter& border_painter) { Vector<BoxSide, 4> sorted_sides; // First, collect all visible sides. @@ -442,10 +671,8 @@ struct BoxBorderPainter::ComplexBorderInfo { // alpha, style, side. std::sort(sorted_sides.begin(), sorted_sides.end(), [&border_painter](BoxSide a, BoxSide b) -> bool { - const BorderEdge& edge_a = - border_painter.edges_[static_cast<unsigned>(a)]; - const BorderEdge& edge_b = - border_painter.edges_[static_cast<unsigned>(b)]; + const BorderEdge& edge_a = border_painter.Edge(a); + const BorderEdge& edge_b = border_painter.Edge(b); const unsigned alpha_a = edge_a.color.Alpha(); const unsigned alpha_b = edge_b.color.Alpha(); @@ -475,15 +702,12 @@ struct BoxBorderPainter::ComplexBorderInfo { // Potentially used when drawing rounded borders. Path rounded_border_path; - bool anti_alias; - private: void BuildOpacityGroups(const BoxBorderPainter& border_painter, const Vector<BoxSide, 4>& sorted_sides) { unsigned current_alpha = 0; for (BoxSide side : sorted_sides) { - const BorderEdge& edge = - border_painter.edges_[static_cast<unsigned>(side)]; + const BorderEdge& edge = border_painter.Edge(side); const unsigned edge_alpha = edge.color.Alpha(); DCHECK_GT(edge_alpha, 0u); @@ -503,8 +727,7 @@ struct BoxBorderPainter::ComplexBorderInfo { } }; -void BoxBorderPainter::DrawDoubleBorder(GraphicsContext& context, - const PhysicalRect& border_rect) const { +void BoxBorderPainter::DrawDoubleBorder() const { DCHECK(is_uniform_color_); DCHECK(is_uniform_style_); DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble); @@ -516,30 +739,28 @@ void BoxBorderPainter::DrawDoubleBorder(GraphicsContext& context, const auto force_rectangular = !outer_.IsRounded() && !inner_.IsRounded(); // outer stripe - const LayoutRectOutsets outer_third_insets = - DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeOuter); + const LayoutRectOutsets outer_third_outsets = + DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeOuter); FloatRoundedRect outer_third_rect = - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, border_rect, outer_third_insets, sides_to_include_); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, border_rect_, outer_third_outsets, sides_to_include_); if (force_rectangular) outer_third_rect.SetRadii(FloatRoundedRect::Radii()); - DrawBleedAdjustedDRRect(context, bleed_avoidance_, outer_, outer_third_rect, + DrawBleedAdjustedDRRect(context_, bleed_avoidance_, outer_, outer_third_rect, color); // inner stripe - const LayoutRectOutsets inner_third_insets = - DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeInner); + const LayoutRectOutsets inner_third_outsets = + DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeInner); FloatRoundedRect inner_third_rect = - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, border_rect, inner_third_insets, sides_to_include_); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, border_rect_, inner_third_outsets, sides_to_include_); if (force_rectangular) inner_third_rect.SetRadii(FloatRoundedRect::Radii()); - context.FillDRRect(inner_third_rect, inner_, color); + context_.FillDRRect(inner_third_rect, inner_, color); } -bool BoxBorderPainter::PaintBorderFastPath( - GraphicsContext& context, - const PhysicalRect& border_rect) const { +bool BoxBorderPainter::PaintBorderFastPath() const { if (!is_uniform_color_ || !is_uniform_style_ || !inner_.IsRenderable()) return false; @@ -551,17 +772,17 @@ bool BoxBorderPainter::PaintBorderFastPath( if (FirstEdge().BorderStyle() == EBorderStyle::kSolid) { if (is_uniform_width_ && !outer_.IsRounded()) { // 4-side, solid, uniform-width, rectangular border => one drawRect() - DrawSolidBorderRect(context, outer_.Rect(), FirstEdge().Width(), + DrawSolidBorderRect(context_, outer_.Rect(), FirstEdge().Width(), FirstEdge().color); } else { // 4-side, solid border => one drawDRRect() - DrawBleedAdjustedDRRect(context, bleed_avoidance_, outer_, inner_, + DrawBleedAdjustedDRRect(context_, bleed_avoidance_, outer_, inner_, FirstEdge().color); } } else { // 4-side, double border => 2x drawDRRect() DCHECK(FirstEdge().BorderStyle() == EBorderStyle::kDouble); - DrawDoubleBorder(context, border_rect); + DrawDoubleBorder(); } return true; @@ -576,26 +797,29 @@ bool BoxBorderPainter::PaintBorderFastPath( Path path; path.SetWindRule(RULE_NONZERO); - for (unsigned int i = static_cast<unsigned>(BoxSide::kTop); - i <= static_cast<unsigned>(BoxSide::kLeft); ++i) { - const BorderEdge& curr_edge = edges_[i]; + for (auto side : + {BoxSide::kTop, BoxSide::kRight, BoxSide::kBottom, BoxSide::kLeft}) { + const BorderEdge& curr_edge = Edge(side); if (curr_edge.ShouldRender()) - path.AddRect(CalculateSideRect(outer_, curr_edge, i)); + path.AddRect(CalculateSideRect(outer_, curr_edge, side)); } - context.SetFillColor(FirstEdge().color); - context.FillPath(path); + context_.SetFillColor(FirstEdge().color); + context_.FillPath(path); return true; } return false; } -BoxBorderPainter::BoxBorderPainter(const PhysicalRect& border_rect, +BoxBorderPainter::BoxBorderPainter(GraphicsContext& context, + const PhysicalRect& border_rect, const ComputedStyle& style, BackgroundBleedAvoidance bleed_avoidance, PhysicalBoxSides sides_to_include) - : style_(style), + : context_(context), + border_rect_(border_rect), + style_(style), bleed_avoidance_(bleed_avoidance), sides_to_include_(sides_to_include), visible_edge_count_(0), @@ -622,23 +846,26 @@ BoxBorderPainter::BoxBorderPainter(const PhysicalRect& border_rect, // can pixel snap smaller. float max_width = outer_.Rect().Width(); float max_height = outer_.Rect().Height(); - edges_[static_cast<unsigned>(BoxSide::kTop)].ClampWidth(max_height); - edges_[static_cast<unsigned>(BoxSide::kRight)].ClampWidth(max_width); - edges_[static_cast<unsigned>(BoxSide::kBottom)].ClampWidth(max_height); - edges_[static_cast<unsigned>(BoxSide::kLeft)].ClampWidth(max_width); + Edge(BoxSide::kTop).ClampWidth(max_height); + Edge(BoxSide::kRight).ClampWidth(max_width); + Edge(BoxSide::kBottom).ClampWidth(max_height); + Edge(BoxSide::kLeft).ClampWidth(max_width); is_rounded_ = outer_.IsRounded(); } -BoxBorderPainter::BoxBorderPainter(const ComputedStyle& style, - const PhysicalRect& outer, - const PhysicalRect& inner, - const BorderEdge& uniform_edge_info) - : style_(style), +BoxBorderPainter::BoxBorderPainter(GraphicsContext& context, + const ComputedStyle& style, + const PhysicalRect& border_rect, + int inner_outset_x, + int inner_outset_y) + : context_(context), + border_rect_(border_rect), + outer_outset_x_(inner_outset_x + style.OutlineWidthInt()), + outer_outset_y_(inner_outset_y + style.OutlineWidthInt()), + style_(style), bleed_avoidance_(kBackgroundBleedNone), sides_to_include_(PhysicalBoxSides()), - outer_(FloatRect(outer)), - inner_(FloatRect(inner)), visible_edge_count_(0), first_visible_edge_(0), visible_edge_set_(0), @@ -647,10 +874,25 @@ BoxBorderPainter::BoxBorderPainter(const ComputedStyle& style, is_uniform_color_(true), is_rounded_(false), has_alpha_(false) { - for (auto& edge : edges_) - edge = uniform_edge_info; + DCHECK(style.HasOutline()); + BorderEdge edge(style.OutlineWidthInt(), + style.VisitedDependentColor(GetCSSPropertyOutlineColor()), + style.OutlineStyle()); + for (auto& e : edges_) + e = edge; ComputeBorderProperties(); + + outer_ = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style, border_rect, + LayoutRectOutsets(outer_outset_y_, outer_outset_x_, outer_outset_y_, + outer_outset_x_)); + is_rounded_ = outer_.IsRounded(); + + inner_ = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style, border_rect, + LayoutRectOutsets(inner_outset_y, inner_outset_x, inner_outset_y, + inner_outset_x)); } void BoxBorderPainter::ComputeBorderProperties() { @@ -685,30 +927,27 @@ void BoxBorderPainter::ComputeBorderProperties() { } } -void BoxBorderPainter::PaintBorder(const PaintInfo& info, - const PhysicalRect& rect) const { +void BoxBorderPainter::Paint() const { if (!visible_edge_count_ || outer_.Rect().IsEmpty()) return; - GraphicsContext& graphics_context = info.context; - - if (PaintBorderFastPath(graphics_context, rect)) + if (PaintBorderFastPath()) return; bool clip_to_outer_border = outer_.IsRounded(); - GraphicsContextStateSaver state_saver(graphics_context, clip_to_outer_border); + GraphicsContextStateSaver state_saver(context_, clip_to_outer_border); if (clip_to_outer_border) { // For BackgroundBleedClip{Only,Layer}, the outer rrect clip is already // applied. if (!BleedAvoidanceIsClipping(bleed_avoidance_)) - graphics_context.ClipRoundedRect(outer_); + context_.ClipRoundedRect(outer_); if (inner_.IsRenderable() && !inner_.IsEmpty()) - graphics_context.ClipOutRoundedRect(inner_); + context_.ClipOutRoundedRect(inner_); } - const ComplexBorderInfo border_info(*this, true); - PaintOpacityGroup(graphics_context, border_info, 0, 1); + const ComplexBorderInfo border_info(*this); + PaintOpacityGroup(border_info, 0, 1); } // In order to maximize the use of overdraw as a corner seam avoidance @@ -721,11 +960,11 @@ void BoxBorderPainter::PaintBorder(const PaintInfo& info, // transparency layers with adjusted/relative opacity [paintOpacityGroup] // 4) iterate over groups (increasing opacity order), painting actual group // contents and then ending their corresponding transparency layer -// [paintOpacityGroup] +// [PaintOpacityGroup] // // Layers are created in decreasing opacity order (top -> bottom), while actual // border sides are drawn in increasing opacity order (bottom -> top). At each -// level, opacity is adjusted to acount for accumulated/ancestor layer alpha. +// level, opacity is adjusted to account for accumulated/ancestor layer alpha. // Because opacity is applied via layers, the actual draw paint is opaque. // // As an example, let's consider a border with the following sides/opacities: @@ -756,7 +995,6 @@ void BoxBorderPainter::PaintBorder(const PaintInfo& info, // content - hence we can use overdraw to mask portions of the previous sides. // BorderEdgeFlags BoxBorderPainter::PaintOpacityGroup( - GraphicsContext& context, const ComplexBorderInfo& border_info, unsigned index, float effective_opacity) const { @@ -789,7 +1027,7 @@ BorderEdgeFlags BoxBorderPainter::PaintOpacityGroup( const float group_opacity = static_cast<float>(group.alpha) / 255; DCHECK_LT(group_opacity, effective_opacity); - context.BeginLayer(group_opacity / effective_opacity); + context_.BeginLayer(group_opacity / effective_opacity); effective_opacity = group_opacity; // Group opacity is applied via a layer => we draw the members using opaque @@ -802,27 +1040,26 @@ BorderEdgeFlags BoxBorderPainter::PaintOpacityGroup( // b) only triggers at all when mixing border sides with different opacities // c) it allows us to express the layer nesting algorithm more naturally BorderEdgeFlags completed_edges = - PaintOpacityGroup(context, border_info, index + 1, effective_opacity); + PaintOpacityGroup(border_info, index + 1, effective_opacity); // Paint the actual group edges with an alpha adjusted to account for // ancenstor layers opacity. for (BoxSide side : group.sides) { - PaintSide(context, border_info, side, paint_alpha, completed_edges); + PaintSide(border_info, side, paint_alpha, completed_edges); completed_edges |= EdgeFlagForSide(side); } if (needs_layer) - context.EndLayer(); + context_.EndLayer(); return completed_edges; } -void BoxBorderPainter::PaintSide(GraphicsContext& context, - const ComplexBorderInfo& border_info, +void BoxBorderPainter::PaintSide(const ComplexBorderInfo& border_info, BoxSide side, unsigned alpha, BorderEdgeFlags completed_edges) const { - const BorderEdge& edge = edges_[static_cast<unsigned>(side)]; + const BorderEdge& edge = Edge(side); DCHECK(edge.ShouldRender()); const Color color(edge.color.Red(), edge.color.Green(), edge.color.Blue(), alpha); @@ -843,9 +1080,8 @@ void BoxBorderPainter::PaintSide(GraphicsContext& context, else side_rect.SetHeight(floorf(edge.Width())); - PaintOneBorderSide(context, side_rect, BoxSide::kTop, BoxSide::kLeft, - BoxSide::kRight, path, border_info.anti_alias, color, - completed_edges); + PaintOneBorderSide(side_rect, BoxSide::kTop, BoxSide::kLeft, + BoxSide::kRight, path, color, completed_edges); break; } case BoxSide::kBottom: { @@ -858,9 +1094,8 @@ void BoxBorderPainter::PaintSide(GraphicsContext& context, else side_rect.ShiftYEdgeTo(side_rect.MaxY() - floorf(edge.Width())); - PaintOneBorderSide(context, side_rect, BoxSide::kBottom, BoxSide::kLeft, - BoxSide::kRight, path, border_info.anti_alias, color, - completed_edges); + PaintOneBorderSide(side_rect, BoxSide::kBottom, BoxSide::kLeft, + BoxSide::kRight, path, color, completed_edges); break; } case BoxSide::kLeft: { @@ -873,9 +1108,8 @@ void BoxBorderPainter::PaintSide(GraphicsContext& context, else side_rect.SetWidth(floorf(edge.Width())); - PaintOneBorderSide(context, side_rect, BoxSide::kLeft, BoxSide::kTop, - BoxSide::kBottom, path, border_info.anti_alias, color, - completed_edges); + PaintOneBorderSide(side_rect, BoxSide::kLeft, BoxSide::kTop, + BoxSide::kBottom, path, color, completed_edges); break; } case BoxSide::kRight: { @@ -888,9 +1122,8 @@ void BoxBorderPainter::PaintSide(GraphicsContext& context, else side_rect.ShiftXEdgeTo(side_rect.MaxX() - floorf(edge.Width())); - PaintOneBorderSide(context, side_rect, BoxSide::kRight, BoxSide::kTop, - BoxSide::kBottom, path, border_info.anti_alias, color, - completed_edges); + PaintOneBorderSide(side_rect, BoxSide::kRight, BoxSide::kTop, + BoxSide::kBottom, path, color, completed_edges); break; } default: @@ -901,10 +1134,8 @@ void BoxBorderPainter::PaintSide(GraphicsContext& context, BoxBorderPainter::MiterType BoxBorderPainter::ComputeMiter( BoxSide side, BoxSide adjacent_side, - BorderEdgeFlags completed_edges, - bool antialias) const { - const BorderEdge& adjacent_edge = - edges_[static_cast<unsigned>(adjacent_side)]; + BorderEdgeFlags completed_edges) const { + const BorderEdge& adjacent_edge = Edge(adjacent_side); // No miters for missing edges. if (!adjacent_edge.is_present) @@ -916,15 +1147,13 @@ BoxBorderPainter::MiterType BoxBorderPainter::ComputeMiter( // Color transitions require miters. Use miters compatible with the AA drawing // mode to avoid introducing extra clips. - if (!ColorsMatchAtCorner(side, adjacent_side, edges_)) - return antialias ? kSoftMiter : kHardMiter; + if (!ColorsMatchAtCorner(side, adjacent_side)) + return kSoftMiter; // Non-anti-aliased miters ensure correct same-color seaming when required by // style. - if (BorderStylesRequireMiter( - side, adjacent_side, - edges_[static_cast<unsigned>(side)].BorderStyle(), - adjacent_edge.BorderStyle())) + if (BorderStylesRequireMiter(side, adjacent_side, Edge(side).BorderStyle(), + adjacent_edge.BorderStyle())) return kHardMiter; // Overdraw the adjacent edge when the colors match and we have no style @@ -934,12 +1163,10 @@ BoxBorderPainter::MiterType BoxBorderPainter::ComputeMiter( bool BoxBorderPainter::MitersRequireClipping(MiterType miter1, MiterType miter2, - EBorderStyle style, - bool antialias) { + EBorderStyle style) { // Clipping is required if any of the present miters doesn't match the current // AA mode. - bool should_clip = antialias ? miter1 == kHardMiter || miter2 == kHardMiter - : miter1 == kSoftMiter || miter2 == kSoftMiter; + bool should_clip = miter1 == kHardMiter || miter2 == kHardMiter; // Some styles require clipping for any type of miter. should_clip = should_clip || ((miter1 != kNoMiter || miter2 != kNoMiter) && @@ -949,69 +1176,57 @@ bool BoxBorderPainter::MitersRequireClipping(MiterType miter1, } void BoxBorderPainter::PaintOneBorderSide( - GraphicsContext& graphics_context, const FloatRect& side_rect, BoxSide side, BoxSide adjacent_side1, BoxSide adjacent_side2, const Path* path, - bool antialias, Color color, BorderEdgeFlags completed_edges) const { - const BorderEdge& edge_to_render = edges_[static_cast<unsigned>(side)]; + const BorderEdge& edge_to_render = Edge(side); DCHECK(edge_to_render.Width()); - const BorderEdge& adjacent_edge1 = - edges_[static_cast<unsigned>(adjacent_side1)]; - const BorderEdge& adjacent_edge2 = - edges_[static_cast<unsigned>(adjacent_side2)]; + const BorderEdge& adjacent_edge1 = Edge(adjacent_side1); + const BorderEdge& adjacent_edge2 = Edge(adjacent_side2); if (path) { - MiterType miter1 = ColorsMatchAtCorner(side, adjacent_side1, edges_) - ? kHardMiter - : kSoftMiter; - MiterType miter2 = ColorsMatchAtCorner(side, adjacent_side2, edges_) - ? kHardMiter - : kSoftMiter; - - GraphicsContextStateSaver state_saver(graphics_context); + MiterType miter1 = + ColorsMatchAtCorner(side, adjacent_side1) ? kHardMiter : kSoftMiter; + MiterType miter2 = + ColorsMatchAtCorner(side, adjacent_side2) ? kHardMiter : kSoftMiter; + + GraphicsContextStateSaver state_saver(context_); if (inner_.IsRenderable()) - ClipBorderSidePolygon(graphics_context, side, miter1, miter2); + ClipBorderSidePolygon(side, miter1, miter2); else - ClipBorderSideForComplexInnerPath(graphics_context, side); + ClipBorderSideForComplexInnerPath(side); float stroke_thickness = std::max(std::max(edge_to_render.Width(), adjacent_edge1.Width()), adjacent_edge2.Width()); - DrawBoxSideFromPath(graphics_context, - PhysicalRect::EnclosingRect(outer_.Rect()), *path, - edge_to_render.Width(), stroke_thickness, side, color, - edge_to_render.BorderStyle()); + DrawBoxSideFromPath(*path, edge_to_render.Width(), stroke_thickness, side, + color, edge_to_render.BorderStyle()); } else { - MiterType miter1 = - ComputeMiter(side, adjacent_side1, completed_edges, antialias); - MiterType miter2 = - ComputeMiter(side, adjacent_side2, completed_edges, antialias); - bool should_clip = MitersRequireClipping( - miter1, miter2, edge_to_render.BorderStyle(), antialias); + MiterType miter1 = ComputeMiter(side, adjacent_side1, completed_edges); + MiterType miter2 = ComputeMiter(side, adjacent_side2, completed_edges); + bool should_clip = + MitersRequireClipping(miter1, miter2, edge_to_render.BorderStyle()); - GraphicsContextStateSaver clip_state_saver(graphics_context, should_clip); + GraphicsContextStateSaver clip_state_saver(context_, should_clip); if (should_clip) { - ClipBorderSidePolygon(graphics_context, side, miter1, miter2); - + ClipBorderSidePolygon(side, miter1, miter2); // Miters are applied via clipping, no need to draw them. miter1 = miter2 = kNoMiter; } - ObjectPainter::DrawLineForBoxSide( - graphics_context, side_rect.X(), side_rect.Y(), side_rect.MaxX(), - side_rect.MaxY(), side, color, edge_to_render.BorderStyle(), - miter1 != kNoMiter ? floorf(adjacent_edge1.Width()) : 0, - miter2 != kNoMiter ? floorf(adjacent_edge2.Width()) : 0, antialias); + DrawLineForBoxSide(context_, side_rect.X(), side_rect.Y(), side_rect.MaxX(), + side_rect.MaxY(), side, color, + edge_to_render.BorderStyle(), + miter1 != kNoMiter ? floorf(adjacent_edge1.Width()) : 0, + miter2 != kNoMiter ? floorf(adjacent_edge2.Width()) : 0, + /*antialias*/ true); } } -void BoxBorderPainter::DrawBoxSideFromPath(GraphicsContext& graphics_context, - const PhysicalRect& border_rect, - const Path& border_path, +void BoxBorderPainter::DrawBoxSideFromPath(const Path& border_path, float border_thickness, float stroke_thickness, BoxSide side, @@ -1029,22 +1244,20 @@ void BoxBorderPainter::DrawBoxSideFromPath(GraphicsContext& graphics_context, return; case EBorderStyle::kDotted: case EBorderStyle::kDashed: { - DrawDashedDottedBoxSideFromPath(graphics_context, border_rect, - border_thickness, stroke_thickness, color, + DrawDashedDottedBoxSideFromPath(border_thickness, stroke_thickness, color, border_style); return; } case EBorderStyle::kDouble: { - DrawDoubleBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, - color); + DrawDoubleBoxSideFromPath(border_path, border_thickness, stroke_thickness, + side, color); return; } case EBorderStyle::kRidge: case EBorderStyle::kGroove: { - DrawRidgeGrooveBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, - color, border_style); + DrawRidgeGrooveBoxSideFromPath(border_path, border_thickness, + stroke_thickness, side, color, + border_style); return; } case EBorderStyle::kInset: @@ -1059,37 +1272,29 @@ void BoxBorderPainter::DrawBoxSideFromPath(GraphicsContext& graphics_context, break; } - graphics_context.SetStrokeStyle(kNoStroke); - graphics_context.SetFillColor(color); - graphics_context.DrawRect(PixelSnappedIntRect(border_rect)); + context_.SetStrokeStyle(kNoStroke); + context_.SetFillColor(color); + context_.DrawRect(RoundedIntRect(outer_.Rect())); } void BoxBorderPainter::DrawDashedDottedBoxSideFromPath( - GraphicsContext& graphics_context, - const PhysicalRect& border_rect, float border_thickness, float stroke_thickness, Color color, EBorderStyle border_style) const { // Convert the path to be down the middle of the dots or dashes. - const LayoutRectOutsets center_offsets( - -edges_[static_cast<unsigned>(BoxSide::kTop)].UsedWidth() * 0.5, - -edges_[static_cast<unsigned>(BoxSide::kRight)].UsedWidth() * 0.5, - -edges_[static_cast<unsigned>(BoxSide::kBottom)].UsedWidth() * 0.5, - -edges_[static_cast<unsigned>(BoxSide::kLeft)].UsedWidth() * 0.5); Path centerline_path; centerline_path.AddRoundedRect( - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, border_rect, center_offsets, sides_to_include_)); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, border_rect_, CenterOutsets(), sides_to_include_)); - graphics_context.SetStrokeColor(color); + context_.SetStrokeColor(color); if (!StrokeData::StrokeIsDashed(border_thickness, border_style == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke)) { - DrawWideDottedBoxSideFromPath(graphics_context, centerline_path, - border_thickness); + DrawWideDottedBoxSideFromPath(centerline_path, border_thickness); return; } @@ -1098,33 +1303,29 @@ void BoxBorderPainter::DrawDashedDottedBoxSideFromPath( // the extra multiplier so that the clipping mask can antialias // the edges to prevent jaggies. const float thickness_multiplier = 2 * 1.1f; - graphics_context.SetStrokeThickness(stroke_thickness * thickness_multiplier); - graphics_context.SetStrokeStyle( + context_.SetStrokeThickness(stroke_thickness * thickness_multiplier); + context_.SetStrokeStyle( border_style == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke); // TODO(schenney): stroking the border path causes issues with tight corners: // https://bugs.chromium.org/p/chromium/issues/detail?id=344234 - graphics_context.StrokePath(centerline_path, centerline_path.length(), - border_thickness); + context_.StrokePath(centerline_path, centerline_path.length(), + border_thickness); } void BoxBorderPainter::DrawWideDottedBoxSideFromPath( - GraphicsContext& graphics_context, const Path& border_path, float border_thickness) const { - graphics_context.SetStrokeThickness(border_thickness); - graphics_context.SetStrokeStyle(kDottedStroke); - graphics_context.SetLineCap(kRoundCap); + context_.SetStrokeThickness(border_thickness); + context_.SetStrokeStyle(kDottedStroke); + context_.SetLineCap(kRoundCap); // TODO(schenney): stroking the border path causes issues with tight corners: // https://bugs.webkit.org/show_bug.cgi?id=58711 - graphics_context.StrokePath(border_path, border_path.length(), - border_thickness); + context_.StrokePath(border_path, border_path.length(), border_thickness); } void BoxBorderPainter::DrawDoubleBoxSideFromPath( - GraphicsContext& graphics_context, - const PhysicalRect& border_rect, const Path& border_path, float border_thickness, float stroke_thickness, @@ -1132,47 +1333,43 @@ void BoxBorderPainter::DrawDoubleBoxSideFromPath( Color color) const { // Draw inner border line { - GraphicsContextStateSaver state_saver(graphics_context); - const LayoutRectOutsets inner_insets = - DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeInner); + GraphicsContextStateSaver state_saver(context_); + const LayoutRectOutsets inner_outsets = + DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeInner); FloatRoundedRect inner_clip = - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, border_rect, inner_insets, sides_to_include_); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, border_rect_, inner_outsets, sides_to_include_); - graphics_context.ClipRoundedRect(inner_clip); - DrawBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, color, - EBorderStyle::kSolid); + context_.ClipRoundedRect(inner_clip); + DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side, + color, EBorderStyle::kSolid); } // Draw outer border line { - GraphicsContextStateSaver state_saver(graphics_context); - PhysicalRect outer_rect = border_rect; - LayoutRectOutsets outer_insets = - DoubleStripeInsets(edges_, BorderEdge::kDoubleBorderStripeOuter); + GraphicsContextStateSaver state_saver(context_); + PhysicalRect used_border_rect = border_rect_; + LayoutRectOutsets outer_outsets = + DoubleStripeOutsets(BorderEdge::kDoubleBorderStripeOuter); if (BleedAvoidanceIsClipping(bleed_avoidance_)) { - outer_rect.Inflate(LayoutUnit(1)); - outer_insets.SetTop(outer_insets.Top() - 1); - outer_insets.SetRight(outer_insets.Right() - 1); - outer_insets.SetBottom(outer_insets.Bottom() - 1); - outer_insets.SetLeft(outer_insets.Left() - 1); + used_border_rect.Inflate(LayoutUnit(1)); + outer_outsets.SetTop(outer_outsets.Top() - 1); + outer_outsets.SetRight(outer_outsets.Right() - 1); + outer_outsets.SetBottom(outer_outsets.Bottom() - 1); + outer_outsets.SetLeft(outer_outsets.Left() - 1); } FloatRoundedRect outer_clip = - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, outer_rect, outer_insets, sides_to_include_); - graphics_context.ClipOutRoundedRect(outer_clip); - DrawBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, color, - EBorderStyle::kSolid); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, used_border_rect, outer_outsets, sides_to_include_); + context_.ClipOutRoundedRect(outer_clip); + DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side, + color, EBorderStyle::kSolid); } } void BoxBorderPainter::DrawRidgeGrooveBoxSideFromPath( - GraphicsContext& graphics_context, - const PhysicalRect& border_rect, const Path& border_path, float border_thickness, float stroke_thickness, @@ -1190,43 +1387,56 @@ void BoxBorderPainter::DrawRidgeGrooveBoxSideFromPath( } // Paint full border - DrawBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, color, s1); + DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side, + color, s1); // Paint inner only - GraphicsContextStateSaver state_saver(graphics_context); - int top_width = edges_[static_cast<unsigned>(BoxSide::kTop)].UsedWidth() / 2; - int bottom_width = - edges_[static_cast<unsigned>(BoxSide::kBottom)].UsedWidth() / 2; - int left_width = - edges_[static_cast<unsigned>(BoxSide::kLeft)].UsedWidth() / 2; - int right_width = - edges_[static_cast<unsigned>(BoxSide::kRight)].UsedWidth() / 2; - + GraphicsContextStateSaver state_saver(context_); FloatRoundedRect clip_rect = - RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - style_, border_rect, - LayoutRectOutsets(-top_width, -right_width, -bottom_width, - -left_width), - sides_to_include_); - - graphics_context.ClipRoundedRect(clip_rect); - DrawBoxSideFromPath(graphics_context, border_rect, border_path, - border_thickness, stroke_thickness, side, color, s2); + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style_, border_rect_, CenterOutsets(), sides_to_include_); + + context_.ClipRoundedRect(clip_rect); + DrawBoxSideFromPath(border_path, border_thickness, stroke_thickness, side, + color, s2); } -void BoxBorderPainter::ClipBorderSideForComplexInnerPath( - GraphicsContext& graphics_context, +FloatRect BoxBorderPainter::CalculateSideRectIncludingInner( BoxSide side) const { - graphics_context.Clip(CalculateSideRectIncludingInner(outer_, edges_, side)); + FloatRect side_rect = outer_.Rect(); + float width; + + switch (side) { + case BoxSide::kTop: + width = side_rect.Height() - Edge(BoxSide::kBottom).Width(); + side_rect.SetHeight(width); + break; + case BoxSide::kBottom: + width = side_rect.Height() - Edge(BoxSide::kTop).Width(); + side_rect.ShiftYEdgeTo(side_rect.MaxY() - width); + break; + case BoxSide::kLeft: + width = side_rect.Width() - Edge(BoxSide::kRight).Width(); + side_rect.SetWidth(width); + break; + case BoxSide::kRight: + width = side_rect.Width() - Edge(BoxSide::kLeft).Width(); + side_rect.ShiftXEdgeTo(side_rect.MaxX() - width); + break; + } + + return side_rect; +} + +void BoxBorderPainter::ClipBorderSideForComplexInnerPath(BoxSide side) const { + context_.Clip(CalculateSideRectIncludingInner(side)); FloatRoundedRect adjusted_inner_rect = CalculateAdjustedInnerBorder(inner_, side); if (!adjusted_inner_rect.IsEmpty()) - graphics_context.ClipOutRoundedRect(adjusted_inner_rect); + context_.ClipOutRoundedRect(adjusted_inner_rect); } -void BoxBorderPainter::ClipBorderSidePolygon(GraphicsContext& graphics_context, - BoxSide side, +void BoxBorderPainter::ClipBorderSidePolygon(BoxSide side, MiterType first_miter, MiterType second_miter) const { DCHECK(first_miter != kNoMiter || second_miter != kNoMiter); @@ -1446,7 +1656,7 @@ void BoxBorderPainter::ClipBorderSidePolygon(GraphicsContext& graphics_context, } if (first_miter == second_miter) { - ClipQuad(graphics_context, edge_quad, first_miter == kSoftMiter); + ClipQuad(context_, edge_quad, first_miter == kSoftMiter); return; } @@ -1466,7 +1676,7 @@ void BoxBorderPainter::ClipBorderSidePolygon(GraphicsContext& graphics_context, clipping_quad[2] = bound_quad2; clipping_quad[3] = edge_quad[3]; - ClipQuad(graphics_context, clipping_quad, first_miter == kSoftMiter); + ClipQuad(context_, clipping_quad, first_miter == kSoftMiter); } if (second_miter != kNoMiter) { @@ -1479,7 +1689,105 @@ void BoxBorderPainter::ClipBorderSidePolygon(GraphicsContext& graphics_context, clipping_quad[2] -= extension_offset; clipping_quad[3] = edge_quad[3] - extension_offset; - ClipQuad(graphics_context, clipping_quad, second_miter == kSoftMiter); + ClipQuad(context_, clipping_quad, second_miter == kSoftMiter); + } +} + +LayoutRectOutsets BoxBorderPainter::DoubleStripeOutsets( + BorderEdge::DoubleBorderStripe stripe) const { + return LayoutRectOutsets( + outer_outset_y_ - Edge(BoxSide::kTop).GetDoubleBorderStripeWidth(stripe), + outer_outset_x_ - + Edge(BoxSide::kRight).GetDoubleBorderStripeWidth(stripe), + outer_outset_y_ - + Edge(BoxSide::kBottom).GetDoubleBorderStripeWidth(stripe), + outer_outset_x_ - + Edge(BoxSide::kLeft).GetDoubleBorderStripeWidth(stripe)); +} + +LayoutRectOutsets BoxBorderPainter::CenterOutsets() const { + return LayoutRectOutsets( + outer_outset_y_ - Edge(BoxSide::kTop).UsedWidth() * 0.5, + outer_outset_x_ - Edge(BoxSide::kRight).UsedWidth() * 0.5, + outer_outset_y_ - Edge(BoxSide::kBottom).UsedWidth() * 0.5, + outer_outset_x_ - Edge(BoxSide::kLeft).UsedWidth() * 0.5); +} + +bool BoxBorderPainter::ColorsMatchAtCorner(BoxSide side, + BoxSide adjacent_side) const { + if (!Edge(adjacent_side).ShouldRender()) + return false; + + if (!Edge(side).SharesColorWith(Edge(adjacent_side))) + return false; + + return !BorderStyleHasUnmatchedColorsAtCorner(Edge(side).BorderStyle(), side, + adjacent_side); +} + +void BoxBorderPainter::DrawLineForBoxSide(GraphicsContext& context, + float x1, + float y1, + float x2, + float y2, + BoxSide side, + Color color, + EBorderStyle style, + int adjacent_width1, + int adjacent_width2, + bool antialias) { + float thickness; + float length; + if (side == BoxSide::kTop || side == BoxSide::kBottom) { + thickness = y2 - y1; + length = x2 - x1; + } else { + thickness = x2 - x1; + length = y2 - y1; + } + + // We would like this check to be an ASSERT as we don't want to draw empty + // borders. However nothing guarantees that the following recursive calls to + // DrawLineForBoxSide() will have positive thickness and length. + if (length <= 0 || thickness <= 0) + return; + + if (style == EBorderStyle::kDouble && thickness < 3) + style = EBorderStyle::kSolid; + + switch (style) { + case EBorderStyle::kNone: + case EBorderStyle::kHidden: + return; + case EBorderStyle::kDotted: + case EBorderStyle::kDashed: + DrawDashedOrDottedBoxSide(context, x1, y1, x2, y2, side, color, thickness, + style, antialias); + break; + case EBorderStyle::kDouble: + DrawDoubleBoxSide(context, x1, y1, x2, y2, length, side, color, thickness, + adjacent_width1, adjacent_width2, antialias); + break; + case EBorderStyle::kRidge: + case EBorderStyle::kGroove: + DrawRidgeOrGrooveBoxSide(context, x1, y1, x2, y2, side, color, style, + adjacent_width1, adjacent_width2, antialias); + break; + case EBorderStyle::kInset: + // FIXME: Maybe we should lighten the colors on one side like Firefox. + // https://bugs.webkit.org/show_bug.cgi?id=58608 + if (side == BoxSide::kTop || side == BoxSide::kLeft) + color = color.Dark(); + FALLTHROUGH; + case EBorderStyle::kOutset: + if (style == EBorderStyle::kOutset && + (side == BoxSide::kBottom || side == BoxSide::kRight)) + color = color.Dark(); + FALLTHROUGH; + case EBorderStyle::kSolid: + DrawSolidBoxSide(context, x1, y1, x2, y2, side, color, adjacent_width1, + adjacent_width2, antialias); + break; } } diff --git a/chromium/third_party/blink/renderer/core/paint/box_border_painter.h b/chromium/third_party/blink/renderer/core/paint/box_border_painter.h index 42c60592d46..27fa15d11ef 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_border_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/box_border_painter.h @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h" #include "third_party/blink/renderer/core/layout/geometry/box_sides.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/style/border_edge.h" #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h" @@ -15,7 +16,6 @@ namespace blink { class ComputedStyle; class GraphicsContext; class Path; -struct PaintInfo; struct PhysicalRect; typedef unsigned BorderEdgeFlags; @@ -24,19 +24,68 @@ class BoxBorderPainter { STACK_ALLOCATED(); public: - BoxBorderPainter(const PhysicalRect& border_rect, + static void PaintBorder(GraphicsContext& context, + const PhysicalRect& border_rect, + const ComputedStyle& style, + BackgroundBleedAvoidance bleed_avoidance, + PhysicalBoxSides sides_to_include) { + BoxBorderPainter(context, border_rect, style, bleed_avoidance, + sides_to_include) + .Paint(); + } + + static void PaintSingleRectOutline(GraphicsContext& context, + const ComputedStyle& style, + const PhysicalRect& border_rect, + int inner_outset_x, + int inner_outset_y) { + BoxBorderPainter(context, style, border_rect, inner_outset_x, + inner_outset_y) + .Paint(); + } + + static void DrawBoxSide(GraphicsContext& context, + const IntRect& snapped_edge_rect, + BoxSide side, + Color color, + EBorderStyle style) { + DrawLineForBoxSide(context, snapped_edge_rect.X(), snapped_edge_rect.Y(), + snapped_edge_rect.MaxX(), snapped_edge_rect.MaxY(), side, + color, style, 0, 0, true); + } + + // TODO(crbug.com/1201762): The float parameters are truncated to int in the + // function, which implicitly snaps to whole pixels perhaps unexpectedly. To + // avoid the problem, we should use the above function which requires the + // caller to snap to whole pixels explicitly. + static void DrawLineForBoxSide(GraphicsContext&, + float x1, + float y1, + float x2, + float y2, + BoxSide, + Color, + EBorderStyle, + int adjacent_edge_width1, + int adjacent_edge_width2, + bool antialias); + + private: + // For PaintBorder(). + BoxBorderPainter(GraphicsContext&, + const PhysicalRect& border_rect, const ComputedStyle&, BackgroundBleedAvoidance, PhysicalBoxSides sides_to_include); + // For PaintSingleRectOutline(). + BoxBorderPainter(GraphicsContext&, + const ComputedStyle&, + const PhysicalRect& border_rect, + int inner_outset_x, + int inner_outset_y); - BoxBorderPainter(const ComputedStyle&, - const PhysicalRect& outer, - const PhysicalRect& inner, - const BorderEdge& uniform_edge_info); - - void PaintBorder(const PaintInfo&, const PhysicalRect& border_rect) const; + void Paint() const; - private: struct ComplexBorderInfo; enum MiterType { kNoMiter, @@ -46,82 +95,76 @@ class BoxBorderPainter { void ComputeBorderProperties(); - BorderEdgeFlags PaintOpacityGroup(GraphicsContext&, - const ComplexBorderInfo&, + BorderEdgeFlags PaintOpacityGroup(const ComplexBorderInfo&, unsigned index, float accumulated_opacity) const; - void PaintSide(GraphicsContext&, - const ComplexBorderInfo&, + void PaintSide(const ComplexBorderInfo&, BoxSide, unsigned alpha, BorderEdgeFlags) const; - void PaintOneBorderSide(GraphicsContext&, - const FloatRect& side_rect, + void PaintOneBorderSide(const FloatRect& side_rect, BoxSide, BoxSide adjacent_side1, BoxSide adjacent_side2, const Path*, - bool antialias, Color, BorderEdgeFlags) const; - bool PaintBorderFastPath(GraphicsContext&, - const PhysicalRect& border_rect) const; - void DrawDoubleBorder(GraphicsContext&, - const PhysicalRect& border_rect) const; - - void DrawBoxSideFromPath(GraphicsContext&, - const PhysicalRect&, - const Path&, + bool PaintBorderFastPath() const; + void DrawDoubleBorder() const; + + void DrawBoxSideFromPath(const Path&, float thickness, float draw_thickness, BoxSide, Color, EBorderStyle) const; - void DrawDashedDottedBoxSideFromPath(GraphicsContext&, - const PhysicalRect&, - float thickness, + void DrawDashedDottedBoxSideFromPath(float thickness, float draw_thickness, Color, EBorderStyle) const; - void DrawWideDottedBoxSideFromPath(GraphicsContext&, - const Path&, - float thickness) const; - void DrawDoubleBoxSideFromPath(GraphicsContext&, - const PhysicalRect&, - const Path&, + void DrawWideDottedBoxSideFromPath(const Path&, float thickness) const; + void DrawDoubleBoxSideFromPath(const Path&, float thickness, float draw_thickness, BoxSide, Color) const; - void DrawRidgeGrooveBoxSideFromPath(GraphicsContext&, - const PhysicalRect&, - const Path&, + void DrawRidgeGrooveBoxSideFromPath(const Path&, float thickness, float draw_thickness, BoxSide, Color, EBorderStyle) const; - void ClipBorderSidePolygon(GraphicsContext&, - BoxSide, - MiterType miter1, - MiterType miter2) const; - void ClipBorderSideForComplexInnerPath(GraphicsContext&, BoxSide) const; - - MiterType ComputeMiter(BoxSide, - BoxSide adjacent_side, - BorderEdgeFlags, - bool antialias) const; + void ClipBorderSidePolygon(BoxSide, MiterType miter1, MiterType miter2) const; + FloatRect CalculateSideRectIncludingInner(BoxSide) const; + void ClipBorderSideForComplexInnerPath(BoxSide) const; + + MiterType ComputeMiter(BoxSide, BoxSide adjacent_side, BorderEdgeFlags) const; static bool MitersRequireClipping(MiterType miter1, MiterType miter2, - EBorderStyle, - bool antialias); + EBorderStyle); + + LayoutRectOutsets DoubleStripeOutsets( + BorderEdge::DoubleBorderStripe stripe) const; + LayoutRectOutsets CenterOutsets() const; + + bool ColorsMatchAtCorner(BoxSide side, BoxSide adjacent_side) const; const BorderEdge& FirstEdge() const { DCHECK(visible_edge_set_); return edges_[first_visible_edge_]; } + BorderEdge& Edge(BoxSide side) { return edges_[static_cast<unsigned>(side)]; } + const BorderEdge& Edge(BoxSide side) const { + return edges_[static_cast<unsigned>(side)]; + } + + GraphicsContext& context_; + // const inputs + const PhysicalRect border_rect_; + const LayoutUnit outer_outset_x_; + const LayoutUnit outer_outset_y_; const ComputedStyle& style_; const BackgroundBleedAvoidance bleed_avoidance_; const PhysicalBoxSides sides_to_include_; diff --git a/chromium/third_party/blink/renderer/core/paint/box_decoration_data.h b/chromium/third_party/blink/renderer/core/paint/box_decoration_data.h index 958b9665987..18c9465a405 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_decoration_data.h +++ b/chromium/third_party/blink/renderer/core/paint/box_decoration_data.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_BOX_DECORATION_DATA_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_BOX_DECORATION_DATA_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h" #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" diff --git a/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.cc b/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.cc index de7526d1945..12b4e7b7ded 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.cc @@ -58,12 +58,13 @@ BoxModelObjectPainter::BoxModelObjectPainter(const LayoutBoxModelObject& box, flow_box_(flow_box) {} void BoxModelObjectPainter::PaintTextClipMask( - GraphicsContext& context, + const PaintInfo& paint_info, const IntRect& mask_rect, const PhysicalOffset& paint_offset, bool object_has_multiple_boxes) { - PaintInfo paint_info(context, CullRect(mask_rect), PaintPhase::kTextClip, - kGlobalPaintNormalPhase, 0); + PaintInfo mask_paint_info(paint_info.context, CullRect(mask_rect), + PaintPhase::kTextClip, kGlobalPaintNormalPhase, 0); + mask_paint_info.SetFragmentID(paint_info.FragmentID()); if (flow_box_) { LayoutSize local_offset = ToLayoutSize(flow_box_->Location()); if (object_has_multiple_boxes && @@ -75,10 +76,10 @@ void BoxModelObjectPainter::PaintTextClipMask( PhysicalOffset physical_local_offset(local_offset.Width(), local_offset.Height()); const RootInlineBox& root = flow_box_->Root(); - flow_box_->Paint(paint_info, paint_offset - physical_local_offset, + flow_box_->Paint(mask_paint_info, paint_offset - physical_local_offset, root.LineTop(), root.LineBottom()); } else if (auto* layout_block = DynamicTo<LayoutBlock>(box_model_)) { - layout_block->PaintObject(paint_info, paint_offset); + layout_block->PaintObject(mask_paint_info, paint_offset); } else { // We should go through the above path for LayoutInlines. DCHECK(!box_model_.IsLayoutInline()); @@ -98,7 +99,6 @@ PhysicalRect BoxModelObjectPainter::AdjustRectForScrolledContent( if (BoxDecorationData::IsPaintingScrollingBackground(paint_info, this_box)) return rect; - PhysicalRect scrolled_paint_rect = rect; GraphicsContext& context = paint_info.context; // Clip to the overflow area. // TODO(chrishtr): this should be pixel-snapped. @@ -106,8 +106,9 @@ PhysicalRect BoxModelObjectPainter::AdjustRectForScrolledContent( // Adjust the paint rect to reflect a scrolled content box with borders at // the ends. - PhysicalOffset offset(this_box.PixelSnappedScrolledContentOffset()); - scrolled_paint_rect.Move(-offset); + PhysicalRect scrolled_paint_rect = rect; + scrolled_paint_rect.offset -= + PhysicalOffset(this_box.PixelSnappedScrolledContentOffset()); LayoutRectOutsets border = AdjustedBorderOutsets(info); scrolled_paint_rect.SetWidth(border.Left() + this_box.ScrollWidth() + border.Right()); diff --git a/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.h b/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.h index 03f15100911..5134ba6ecad 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/box_model_object_painter.h @@ -37,7 +37,7 @@ class BoxModelObjectPainter : public BoxPainterBase { bool is_painting_scrolling_background) const override; bool IsPaintingScrollingBackground(const PaintInfo&) const override; - void PaintTextClipMask(GraphicsContext&, + void PaintTextClipMask(const PaintInfo&, const IntRect& mask_rect, const PhysicalOffset& paint_offset, bool object_has_multiple_boxes) override; diff --git a/chromium/third_party/blink/renderer/core/paint/box_painter_base.cc b/chromium/third_party/blink/renderer/core/paint/box_painter_base.cc index a701c72ddc0..2f5093bf4a9 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_painter_base.cc +++ b/chromium/third_party/blink/renderer/core/paint/box_painter_base.cc @@ -5,7 +5,6 @@ #include "third_party/blink/renderer/core/paint/box_painter_base.h" #include "third_party/abseil-cpp/absl/types/optional.h" -#include "third_party/blink/renderer/core/animation/element_animations.h" #include "third_party/blink/renderer/core/css/background_color_paint_image_generator.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/node_computed_style.h" @@ -192,7 +191,7 @@ void BoxPainterBase::PaintInsetBoxShadowWithInnerRect( const ComputedStyle& style) { if (!style.BoxShadow()) return; - auto bounds = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( + auto bounds = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( style, inner_rect, LayoutRectOutsets()); PaintInsetBoxShadow(info, bounds, style); } @@ -397,39 +396,6 @@ BoxPainterBase::FillLayerInfo::FillLayerInfo( namespace { -// Given the size that the whole image should draw at, and the input phase -// requested by the content, and the space between repeated tiles, return a -// phase that is no more than one size + space in magnitude. -PhysicalOffset ComputePhaseForBackground( - const BackgroundImageGeometry& geometry) { - const PhysicalSize step_per_tile = geometry.TileSize() + geometry.SpaceSize(); - return {IntMod(-geometry.Phase().left, step_per_tile.width), - IntMod(-geometry.Phase().top, step_per_tile.height)}; -} - -// Compute the image subset, in intrinsic image coordinates, that gets mapped -// onto the |subset|, when the whole image would be drawn with phase and size -// given by |phase_and_size|. Assumes |phase_and_size| contains |subset|. The -// location of the requested subset should be the painting snapped location, or -// whatever was used as a destination_offset in ComputePhaseForBackground. -// -// It is used to undo the offset added in ComputePhaseForBackground. The size -// of requested subset should be the unsnapped size so that the computed -// scale and location in the source image can be correctly determined. -FloatRect ComputeSubsetForBackground(const PhysicalRect& phase_and_size, - const PhysicalRect& subset, - const FloatSize& intrinsic_size) { - // TODO(schenney): Re-enable this after determining why it fails for - // CAP, and maybe other cases. - // DCHECK(phase_and_size.Contains(subset)); - const PhysicalOffset offset_in_tile = subset.offset - phase_and_size.offset; - const FloatSize scale(phase_and_size.Width() / intrinsic_size.Width(), - phase_and_size.Height() / intrinsic_size.Height()); - return FloatRect( - offset_in_tile.left / scale.Width(), offset_in_tile.top / scale.Height(), - subset.Width() / scale.Width(), subset.Height() / scale.Height()); -} - FloatRect SnapSourceRectIfNearIntegral(const FloatRect src_rect) { // Round to avoid filtering pulling in neighboring pixels, for the // common case of sprite maps, but only if we're close to an integral size. @@ -444,11 +410,85 @@ FloatRect SnapSourceRectIfNearIntegral(const FloatRect src_rect) { LayoutUnit::Epsilon() && std::abs(std::round(src_rect.MaxY()) - src_rect.MaxY()) <= LayoutUnit::Epsilon()) { - return FloatRect(RoundedIntRect(src_rect)); + IntRect rounded_src_rect = RoundedIntRect(src_rect); + // If we have snapped the image size to 0, revert the rounding. + if (rounded_src_rect.IsEmpty()) + return src_rect; + return FloatRect(rounded_src_rect); } return src_rect; } +absl::optional<FloatRect> OptimizeToSingleTileDraw( + const BackgroundImageGeometry& geometry, + const PhysicalRect& dest_rect, + Image* image, + RespectImageOrientationEnum respect_orientation) { + const PhysicalOffset dest_phase = geometry.ComputeDestPhase(); + + // Phase calculation uses the actual painted location, given by the + // border-snapped destination rect. + const PhysicalRect one_tile_rect(dest_phase, geometry.TileSize()); + + // We cannot optimize if the tile is misaligned. + if (!one_tile_rect.Contains(dest_rect)) + return absl::nullopt; + + const PhysicalOffset offset_in_tile = + geometry.SnappedDestRect().offset - dest_phase; + if (!image->HasIntrinsicSize()) { + // This is a generated image sized according to the tile size so we can use + // the snapped dest rect directly. + const PhysicalRect offset_tile(offset_in_tile, + geometry.SnappedDestRect().size); + return FloatRect(offset_tile); + } + + // Compute the image subset, in intrinsic image coordinates, that gets mapped + // onto the |dest_rect|, when the whole image would be drawn with phase and + // size given by |one_tile_rect|. Assumes |one_tile_rect| contains + // |dest_rect|. The location of the requested subset should be the painting + // snapped location. + // + // The size of requested subset should be the unsnapped size so that the + // computed scale and location in the source image can be correctly + // determined. + // + // image-resolution information is baked into the given parameters, but we + // need oriented size. + const FloatSize intrinsic_tile_size = image->SizeAsFloat(respect_orientation); + + // Subset computation needs the same location as was used above, but needs the + // unsnapped destination size to correctly calculate sprite subsets in the + // presence of zoom. + // TODO(schenney): Re-enable this after determining why it fails for + // CAP, and maybe other cases. + // DCHECK(one_tile_rect.Contains(dest_rect_for_subset)); + const FloatSize scale( + geometry.TileSize().width / intrinsic_tile_size.Width(), + geometry.TileSize().height / intrinsic_tile_size.Height()); + FloatRect visible_src_rect( + offset_in_tile.left / scale.Width(), offset_in_tile.top / scale.Height(), + geometry.UnsnappedDestRect().Width() / scale.Width(), + geometry.UnsnappedDestRect().Height() / scale.Height()); + + // Content providers almost always choose source pixels at integer locations, + // so snap to integers. This is particularly important for sprite maps. + // Calculation up to this point, in LayoutUnits, can lead to small variations + // from integer size, so it is safe to round without introducing major issues. + visible_src_rect = SnapSourceRectIfNearIntegral(visible_src_rect); + + // When respecting image orientation, the drawing code expects the source + // rect to be in the unrotated image space, but we have computed it here in + // the rotated space in order to position and size the background. Undo the + // src rect rotation if necessary. + if (respect_orientation && !image->HasDefaultOrientation()) { + visible_src_rect = image->CorrectSrcRectForImageOrientation( + intrinsic_tile_size, visible_src_rect); + } + return visible_src_rect; +} + // The unsnapped_subset_size should be the target painting area implied by the // content, without any snapping applied. It is necessary to correctly // compute the subset of the source image to paint into the destination. @@ -465,34 +505,6 @@ void DrawTiledBackground(GraphicsContext& context, RespectImageOrientationEnum respect_orientation) { DCHECK(!geometry.TileSize().IsEmpty()); - // Use the intrinsic size of the image if it has one, otherwise force the - // generated image to be the tile size. - FloatSize intrinsic_tile_size(image->Size()); - // image-resolution information is baked into the given parameters, but we - // need oriented size. That requires explicitly applying orientation here. - if (respect_orientation && - image->CurrentFrameOrientation().UsesWidthAsHeight()) { - intrinsic_tile_size = intrinsic_tile_size.TransposedSize(); - } - - FloatSize scale(1, 1); - if (!image->HasIntrinsicSize() || - // TODO(crbug.com/1042783): This is not checking for real empty image - // (for which we have checked and skipped the whole FillLayer), but for - // that a subpixel image size is rounded to empty, to avoid infinite tile - // scale that would be calculated in the |else| part. - // We should probably support subpixel size here. - intrinsic_tile_size.IsEmpty()) { - intrinsic_tile_size = FloatSize(geometry.TileSize()); - } else { - scale = - FloatSize(geometry.TileSize().width / intrinsic_tile_size.Width(), - geometry.TileSize().height / intrinsic_tile_size.Height()); - } - - const PhysicalOffset dest_phase = - geometry.SnappedDestRect().offset + ComputePhaseForBackground(geometry); - // Check and see if a single draw of the image can cover the entire area we // are supposed to tile. The dest_rect_for_subset must use the same // location that was used in ComputePhaseForBackground and the unsnapped @@ -500,30 +512,30 @@ void DrawTiledBackground(GraphicsContext& context, // location in the presence of border snapping and zoom. const PhysicalRect dest_rect_for_subset(geometry.SnappedDestRect().offset, geometry.UnsnappedDestRect().size); - const PhysicalRect one_tile_rect(dest_phase, geometry.TileSize()); - if (one_tile_rect.Contains(dest_rect_for_subset)) { - FloatRect visible_src_rect = ComputeSubsetForBackground( - one_tile_rect, dest_rect_for_subset, intrinsic_tile_size); - visible_src_rect = SnapSourceRectIfNearIntegral(visible_src_rect); - - // When respecting image orientation, the drawing code expects the source - // rect to be in the unrotated image space, but we have computed it here in - // the rotated space in order to position and size the background. Undo the - // src rect rotation if necessary. - if (respect_orientation && !image->HasDefaultOrientation()) { - visible_src_rect = image->CorrectSrcRectForImageOrientation( - intrinsic_tile_size, visible_src_rect); - } - + if (absl::optional<FloatRect> single_tile_src = OptimizeToSingleTileDraw( + geometry, dest_rect_for_subset, image, respect_orientation)) { context.DrawImage(image, Image::kSyncDecode, - FloatRect(geometry.SnappedDestRect()), &visible_src_rect, + FloatRect(geometry.SnappedDestRect()), &*single_tile_src, has_filter_property, op, respect_orientation); return; } // At this point we have decided to tile the image to fill the dest rect. + + // Use the intrinsic size of the image if it has one, otherwise force the + // generated image to be the tile size. + // image-resolution information is baked into the given parameters, but we + // need oriented size. That requires explicitly applying orientation here. + Image::SizeConfig size_config; + size_config.apply_orientation = respect_orientation; + const FloatSize intrinsic_tile_size = + image->SizeWithConfigAsFloat(size_config); + // Note that this tile rect uses the image's pre-scaled size. - FloatRect tile_rect(FloatPoint(), intrinsic_tile_size); + ImageTilingInfo tiling_info; + tiling_info.image_rect.SetSize(intrinsic_tile_size); + tiling_info.phase = FloatPoint(geometry.ComputeDestPhase()); + tiling_info.spacing = FloatSize(geometry.SpaceSize()); // Farther down the pipeline we will use the scaled tile size to determine // which dimensions to clamp or repeat in. We do not want to repeat when the @@ -535,82 +547,86 @@ void DrawTiledBackground(GraphicsContext& context, // values in that dimension. const PhysicalSize tile_dest_diff = geometry.TileSize() - geometry.SnappedDestRect().size; - if (tile_dest_diff.width.Abs() <= 0.5f) { - scale.SetWidth(geometry.SnappedDestRect().Width() / - intrinsic_tile_size.Width()); - } - if (tile_dest_diff.height.Abs() <= 0.5f) { - scale.SetHeight(geometry.SnappedDestRect().Height() / - intrinsic_tile_size.Height()); - } + const LayoutUnit ref_tile_width = tile_dest_diff.width.Abs() <= 0.5f + ? geometry.SnappedDestRect().Width() + : geometry.TileSize().width; + const LayoutUnit ref_tile_height = tile_dest_diff.height.Abs() <= 0.5f + ? geometry.SnappedDestRect().Height() + : geometry.TileSize().height; + tiling_info.scale = {ref_tile_width / tiling_info.image_rect.Width(), + ref_tile_height / tiling_info.image_rect.Height()}; // This call takes the unscaled image, applies the given scale, and paints // it into the snapped_dest_rect using phase from one_tile_rect and the // given repeat spacing. Note the phase is already scaled. context.DrawImageTiled(image, FloatRect(geometry.SnappedDestRect()), - tile_rect, scale, FloatPoint(dest_phase), - FloatSize(geometry.SpaceSize()), op, + tiling_info, has_filter_property, op, respect_orientation); } -// Returning false meaning that we cannot paint background color with -// BackgroundColorPaintWorklet. -bool GetBGColorPaintWorkletParams(const BoxPainterBase::FillLayerInfo& info, - const Document* document, - Node* node, - Vector<Color>* animated_colors, - Vector<double>* offsets, - absl::optional<double>* progress) { - if (!info.should_paint_color_with_paint_worklet_image) - return false; - BackgroundColorPaintImageGenerator* generator = - document->GetFrame()->GetBackgroundColorPaintImageGenerator(); - return generator->GetBGColorPaintWorkletParams(node, animated_colors, offsets, - progress); -} - -void FillRectWithPaintWorklet(const Document* document, - const BoxPainterBase::FillLayerInfo& info, - Node* node, - const FloatRoundedRect& dest_rect, - GraphicsContext& context, - const Vector<Color>& animated_colors, - const Vector<double>& offsets, - const absl::optional<double>& progress) { - FloatRect src_rect(FloatPoint(), dest_rect.Rect().Size()); +scoped_refptr<Image> GetBGColorPaintWorkletImage(const Document* document, + Node* node, + const FloatSize& image_size) { + LocalFrame* frame = document->GetFrame(); + if (!frame) + return nullptr; BackgroundColorPaintImageGenerator* generator = - document->GetFrame()->GetBackgroundColorPaintImageGenerator(); - scoped_refptr<Image> paint_worklet_image = generator->Paint( - src_rect.Size(), node, animated_colors, offsets, progress); - context.DrawImageRRect( - paint_worklet_image.get(), Image::kSyncDecode, dest_rect, src_rect, - node && node->ComputedStyleRef().HasFilterInducingProperty(), - SkBlendMode::kSrcOver, info.respect_image_orientation); + frame->GetBackgroundColorPaintImageGenerator(); + // The generator can be null in testing environment. + if (!generator) + return nullptr; + Vector<Color> animated_colors; + Vector<double> offsets; + absl::optional<double> progress; + if (!generator->GetBGColorPaintWorkletParams(node, &animated_colors, &offsets, + &progress)) { + return nullptr; + } + return generator->Paint(image_size, node, animated_colors, offsets, progress); } -// Returns true if we can paint the background color with paint worklet. +// Returns true if the background color was painted by the paint worklet. bool PaintBGColorWithPaintWorklet(const Document* document, const BoxPainterBase::FillLayerInfo& info, Node* node, const FloatRoundedRect& dest_rect, GraphicsContext& context) { - // TODO(xidachen): Consider merge this into GetBGColorPaintWorkletParams, so - // that function doesn't need to return these parameters. - Vector<Color> animated_colors; - Vector<double> offsets; - absl::optional<double> progress; - if (GetBGColorPaintWorkletParams(info, document, node, &animated_colors, - &offsets, &progress)) { - FillRectWithPaintWorklet(document, info, node, dest_rect, context, - animated_colors, offsets, progress); - return true; - } - return false; + if (!info.should_paint_color_with_paint_worklet_image) + return false; + scoped_refptr<Image> paint_worklet_image = + GetBGColorPaintWorkletImage(document, node, dest_rect.Rect().Size()); + if (!paint_worklet_image) + return false; + FloatRect src_rect(FloatPoint(), dest_rect.Rect().Size()); + context.DrawImageRRect(paint_worklet_image.get(), Image::kSyncDecode, + dest_rect, src_rect, + node && node->ComputedStyleRef().DisableForceDark()); + return true; +} + +void DidDrawImage( + Node* node, + const Image& image, + const StyleImage& style_image, + const PropertyTreeStateOrAlias& current_paint_chunk_properties, + const FloatRect& image_rect) { + if (!node || !style_image.IsImageResource()) + return; + const IntRect enclosing_rect = EnclosingIntRect(image_rect); + PaintTimingDetector::NotifyBackgroundImagePaint( + *node, image, To<StyleFetchedImage>(style_image), + current_paint_chunk_properties, enclosing_rect); + + LocalDOMWindow* window = node->GetDocument().domWindow(); + DCHECK(window); + ImageElementTiming::From(*window).NotifyBackgroundImagePainted( + *node, To<StyleFetchedImage>(style_image), current_paint_chunk_properties, + enclosing_rect); } inline bool PaintFastBottomLayer(const Document* document, Node* node, - const PaintInfo& paint_info, + GraphicsContext& context, const BoxPainterBase::FillLayerInfo& info, const PhysicalRect& rect, const FloatRoundedRect& border_rect, @@ -630,7 +646,6 @@ inline bool PaintFastBottomLayer(const Document* document, // Compute the destination rect for painting the color here because we may // need it for computing the image painting rect for optimization. - GraphicsContext& context = paint_info.context; FloatRoundedRect color_border = info.is_rounded_fill ? border_rect : FloatRoundedRect(PixelSnappedIntRect(rect)); @@ -638,9 +653,9 @@ inline bool PaintFastBottomLayer(const Document* document, // tile. The border for painting images may not be the same as the color due // to optimizations for the image painting destination that avoid painting // under the border. - PhysicalRect image_tile; + FloatRect src_rect; FloatRoundedRect image_border; - if (info.should_paint_image) { + if (info.should_paint_image && image) { // Avoid image shaders when printing (poorly supported in PDF). if (info.is_rounded_fill && info.is_printing) return false; @@ -651,32 +666,32 @@ inline bool PaintFastBottomLayer(const Document* document, ? color_border : FloatRoundedRect(FloatRect(geometry.SnappedDestRect())); - if (!image_border.Rect().IsEmpty()) { + const FloatRect& image_rect = image_border.Rect(); + if (!image_rect.IsEmpty()) { // We cannot optimize if the tile is too small. - if (geometry.TileSize().width < image_border.Rect().Width() || - geometry.TileSize().height < image_border.Rect().Height()) + if (geometry.TileSize().width < image_rect.Width() || + geometry.TileSize().height < image_rect.Height()) return false; - // Phase calculation uses the actual painted location, given by the - // border-snapped destination rect. - image_tile = PhysicalRect(geometry.SnappedDestRect().offset + - ComputePhaseForBackground(geometry), - geometry.TileSize()); - // Use FastAndLossyFromFloatRect when converting the image border rect. // At this point it should have been derived from a snapped rectangle, so // the conversion from float should be as precise as it can be. + const PhysicalRect dest_rect = + PhysicalRect::FastAndLossyFromFloatRect(image_rect); - // We cannot optimize if the tile is misaligned. - if (!image_tile.Contains( - PhysicalRect::FastAndLossyFromFloatRect(image_border.Rect()))) + absl::optional<FloatRect> single_tile_src = OptimizeToSingleTileDraw( + geometry, dest_rect, image, info.respect_image_orientation); + if (!single_tile_src) return false; + src_rect = *single_tile_src; } } // At this point we're committed to the fast path: the destination (r)rect // fits within a single tile, and we can paint it using direct draw(R)Rect() - // calls. + // calls. Furthermore, if an image should be painted, |src_rect| has been + // updated to account for positioning and size parameters by + // OptimizeToSingleTileDraw() in the above code block. absl::optional<RoundedInnerRectClipper> clipper; if (info.is_rounded_fill && !color_border.IsRenderable()) { // When the rrect is not renderable, we resort to clipping. @@ -698,47 +713,9 @@ inline bool PaintFastBottomLayer(const Document* document, } // Paint the image if needed. - if (!info.should_paint_image || !image || image_tile.IsEmpty()) + if (!info.should_paint_image || src_rect.IsEmpty()) return true; - // Generated images will be created at the desired tile size, so assume their - // intrinsic size is the requested tile size. - bool has_intrinsic_size = image->HasIntrinsicSize(); - const FloatSize intrinsic_tile_size = - !has_intrinsic_size - ? FloatSize(image_tile.size) - : FloatSize(image->Size(info.respect_image_orientation)); - - // Subset computation needs the same location as was used with - // ComputePhaseForBackground above, but needs the unsnapped destination - // size to correctly calculate sprite subsets in the presence of zoom. But if - // this is a generated image sized according to the tile size (which is a - // snapped value), use the snapped dest rect instead. - const PhysicalRect dest_rect_for_subset( - geometry.SnappedDestRect().offset, - !has_intrinsic_size ? geometry.SnappedDestRect().size - : geometry.UnsnappedDestRect().size); - // Content providers almost always choose source pixels at integer locations, - // so snap to integers. This is particuarly important for sprite maps. - // Calculation up to this point, in LayoutUnits, can lead to small variations - // from integer size, so it is safe to round without introducing major issues. - const FloatRect unrounded_subset = ComputeSubsetForBackground( - image_tile, dest_rect_for_subset, intrinsic_tile_size); - FloatRect src_rect = SnapSourceRectIfNearIntegral(unrounded_subset); - - // If we have snapped the image size to 0, revert the rounding. - if (src_rect.IsEmpty()) - src_rect = unrounded_subset; - - // When respecting image orientation, the drawing code expects the source rect - // to be in the unrotated image space, but we have computed it here in the - // rotated space in order to position and size the background. Undo the src - // rect rotation if necessaary. - if (info.respect_image_orientation && !image->HasDefaultOrientation()) { - src_rect = - image->CorrectSrcRectForImageOrientation(intrinsic_tile_size, src_rect); - } - DEVTOOLS_TIMELINE_TRACE_EVENT_WITH_CATEGORIES( TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage", inspector_paint_image_event::Data, node, *info.image, @@ -746,24 +723,13 @@ inline bool PaintFastBottomLayer(const Document* document, // Since there is no way for the developer to specify decode behavior, use // kSync by default. - context.DrawImageRRect( - image, Image::kSyncDecode, image_border, src_rect, - node && node->ComputedStyleRef().HasFilterInducingProperty(), - composite_op, info.respect_image_orientation); - - if (node && info.image && info.image->IsImageResource()) { - PaintTimingDetector::NotifyBackgroundImagePaint( - *node, *image, To<StyleFetchedImage>(*info.image), - paint_info.context.GetPaintController().CurrentPaintChunkProperties(), - RoundedIntRect(image_border.Rect())); - - LocalDOMWindow* window = node->GetDocument().domWindow(); - DCHECK(window); - ImageElementTiming::From(*window).NotifyBackgroundImagePainted( - *node, To<StyleFetchedImage>(*info.image), - context.GetPaintController().CurrentPaintChunkProperties(), - RoundedIntRect(image_border.Rect())); - } + context.DrawImageRRect(image, Image::kSyncDecode, image_border, src_rect, + node && node->ComputedStyleRef().DisableForceDark(), + composite_op, info.respect_image_orientation); + + DidDrawImage(node, *image, *info.image, + context.GetPaintController().CurrentPaintChunkProperties(), + image_border.Rect()); return true; } @@ -843,7 +809,7 @@ FloatRoundedRect RoundedBorderRectForClip( PhysicalRect border_rect = PhysicalRect::FastAndLossyFromFloatRect(border.Rect()); if (bg_layer.Clip() == EFillBox::kContent) { - border = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( + border = RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( style, border_rect, border_padding_insets, info.sides_to_include); } else if (bg_layer.Clip() == EFillBox::kPadding) { border = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( @@ -865,7 +831,7 @@ void PaintFillLayerBackground(const Document* document, // TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the // culling test by verifying whether the background image covers the entire // painting area. - if (info.is_bottom_layer && info.color.Alpha() && info.should_paint_color) { + if (info.should_paint_color) { IntRect background_rect(PixelSnappedIntRect(scrolled_paint_rect)); // Try to paint the background with a paint worklet first in case it will be // animated. Otherwise, paint it directly into the context. @@ -884,23 +850,12 @@ void PaintFillLayerBackground(const Document* document, TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage", inspector_paint_image_event::Data, node, *info.image, FloatRect(image->Rect()), FloatRect(scrolled_paint_rect)); - DrawTiledBackground( - context, image, geometry, composite_op, - node && node->ComputedStyleRef().HasFilterInducingProperty(), - info.respect_image_orientation); - if (node && info.image && info.image->IsImageResource()) { - PaintTimingDetector::NotifyBackgroundImagePaint( - *node, *image, To<StyleFetchedImage>(*info.image), - context.GetPaintController().CurrentPaintChunkProperties(), - EnclosingIntRect(geometry.SnappedDestRect())); - - LocalDOMWindow* window = node->GetDocument().domWindow(); - DCHECK(window); - ImageElementTiming::From(*window).NotifyBackgroundImagePainted( - *node, To<StyleFetchedImage>(*info.image), - context.GetPaintController().CurrentPaintChunkProperties(), - EnclosingIntRect(geometry.SnappedDestRect())); - } + DrawTiledBackground(context, image, geometry, composite_op, + node && node->ComputedStyleRef().DisableForceDark(), + info.respect_image_orientation); + DidDrawImage(node, *image, *info.image, + context.GetPaintController().CurrentPaintChunkProperties(), + FloatRect(geometry.SnappedDestRect())); } } @@ -940,36 +895,37 @@ void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info, BackgroundImageGeometry& geometry, bool object_has_multiple_boxes, const PhysicalSize& flow_box_size) { - GraphicsContext& context = paint_info.context; if (rect.IsEmpty()) return; - const FillLayerInfo info = + const FillLayerInfo fill_layer_info = GetFillLayerInfo(color, bg_layer, bleed_avoidance, IsPaintingScrollingBackground(paint_info)); // If we're not actually going to paint anything, abort early. - if (!info.should_paint_image && !info.should_paint_color) + if (!fill_layer_info.should_paint_image && + !fill_layer_info.should_paint_color) return; + GraphicsContext& context = paint_info.context; GraphicsContextStateSaver clip_with_scrolling_state_saver( - context, info.is_clipped_with_local_scrolling); + context, fill_layer_info.is_clipped_with_local_scrolling); auto scrolled_paint_rect = - AdjustRectForScrolledContent(paint_info, info, rect); + AdjustRectForScrolledContent(paint_info, fill_layer_info, rect); const auto did_adjust_paint_rect = scrolled_paint_rect != rect; scoped_refptr<Image> image; SkBlendMode composite_op = SkBlendMode::kSrcOver; absl::optional<ScopedInterpolationQuality> interpolation_quality_context; - if (info.should_paint_image) { + if (fill_layer_info.should_paint_image) { geometry.Calculate(paint_info.PaintContainer(), paint_info.phase, bg_layer, scrolled_paint_rect); - image = info.image->GetImage( + image = fill_layer_info.image->GetImage( geometry.ImageClient(), geometry.ImageDocument(), geometry.ImageStyle(style_), FloatSize(geometry.TileSize())); interpolation_quality_context.emplace(context, geometry.ImageInterpolationQuality()); - if (ShouldApplyBlendOperation(info, bg_layer)) { + if (ShouldApplyBlendOperation(fill_layer_info, bg_layer)) { composite_op = WebCoreCompositeToSkiaComposite(bg_layer.Composite(), bg_layer.GetBlendMode()); } @@ -979,8 +935,8 @@ void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info, LayoutRectOutsets padding = ComputePadding(); LayoutRectOutsets border_padding_insets = -(border + padding); FloatRoundedRect border_rect = RoundedBorderRectForClip( - style_, info, bg_layer, rect, object_has_multiple_boxes, flow_box_size, - bleed_avoidance, border_padding_insets); + style_, fill_layer_info, bg_layer, rect, object_has_multiple_boxes, + flow_box_size, bleed_avoidance, border_padding_insets); // Fast path for drawing simple color backgrounds. Do not use the fast // path with images if the dest rect has been adjusted for scrolling @@ -989,22 +945,22 @@ void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info, // if we are shrinking the background for bleed avoidance, because this // adjusts the border rects in a way that breaks the optimization. bool disable_fast_path = - info.should_paint_image && + fill_layer_info.should_paint_image && (bleed_avoidance == kBackgroundBleedShrinkBackground || did_adjust_paint_rect); if (!disable_fast_path && - PaintFastBottomLayer(document_, node_, paint_info, info, rect, + PaintFastBottomLayer(document_, node_, context, fill_layer_info, rect, border_rect, geometry, image.get(), composite_op)) { return; } absl::optional<RoundedInnerRectClipper> clip_to_border; - if (info.is_rounded_fill) + if (fill_layer_info.is_rounded_fill) clip_to_border.emplace(context, rect, border_rect); if (bg_layer.Clip() == EFillBox::kText) { - PaintFillLayerTextFillBox(context, info, image.get(), composite_op, - geometry, rect, scrolled_paint_rect, + PaintFillLayerTextFillBox(paint_info, fill_layer_info, image.get(), + composite_op, geometry, rect, scrolled_paint_rect, object_has_multiple_boxes); return; } @@ -1013,14 +969,17 @@ void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info, switch (bg_layer.Clip()) { case EFillBox::kPadding: case EFillBox::kContent: { - if (info.is_rounded_fill) + if (fill_layer_info.is_rounded_fill) break; // Clip to the padding or content boxes as necessary. PhysicalRect clip_rect = scrolled_paint_rect; - clip_rect.Contract(AdjustOutsetsForEdgeInclusion(border, info)); - if (bg_layer.Clip() == EFillBox::kContent) - clip_rect.Contract(AdjustOutsetsForEdgeInclusion(padding, info)); + clip_rect.Contract( + AdjustOutsetsForEdgeInclusion(border, fill_layer_info)); + if (bg_layer.Clip() == EFillBox::kContent) { + clip_rect.Contract( + AdjustOutsetsForEdgeInclusion(padding, fill_layer_info)); + } background_clip_state_saver.Save(); context.Clip(PixelSnappedIntRect(clip_rect)); break; @@ -1033,12 +992,13 @@ void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info, break; } - PaintFillLayerBackground(document_, context, info, node_, image.get(), - composite_op, geometry, scrolled_paint_rect); + PaintFillLayerBackground(document_, context, fill_layer_info, node_, + image.get(), composite_op, geometry, + scrolled_paint_rect); } void BoxPainterBase::PaintFillLayerTextFillBox( - GraphicsContext& context, + const PaintInfo& paint_info, const BoxPainterBase::FillLayerInfo& info, Image* image, SkBlendMode composite_op, @@ -1051,6 +1011,8 @@ void BoxPainterBase::PaintFillLayerTextFillBox( // rect with the border box of the background. IntRect mask_rect = PixelSnappedIntRect(rect); + GraphicsContext& context = paint_info.context; + // We draw the background into a separate layer, to be later masked with // yet another layer holding the text content. GraphicsContextStateSaver background_clip_state_saver(context, false); @@ -1067,7 +1029,7 @@ void BoxPainterBase::PaintFillLayerTextFillBox( // they should just add their contents to the clip. context.BeginLayer(1, SkBlendMode::kDstIn); - PaintTextClipMask(context, mask_rect, scrolled_paint_rect.offset, + PaintTextClipMask(paint_info, mask_rect, scrolled_paint_rect.offset, object_has_multiple_boxes); context.EndLayer(); // Text mask layer. @@ -1088,9 +1050,8 @@ void BoxPainterBase::PaintBorder(const ImageResourceObserver& obj, return; } - const BoxBorderPainter border_painter(rect, style, bleed_avoidance, - sides_to_include); - border_painter.PaintBorder(info, rect); + BoxBorderPainter::PaintBorder(info.context, rect, style, bleed_avoidance, + sides_to_include); } void BoxPainterBase::PaintMaskImages(const PaintInfo& paint_info, diff --git a/chromium/third_party/blink/renderer/core/paint/box_painter_base.h b/chromium/third_party/blink/renderer/core/paint/box_painter_base.h index 868f23d6e7a..f0cf368ecb7 100644 --- a/chromium/third_party/blink/renderer/core/paint/box_painter_base.h +++ b/chromium/third_party/blink/renderer/core/paint/box_painter_base.h @@ -22,7 +22,6 @@ class ComputedStyle; class Document; class FillLayer; class FloatRoundedRect; -class GraphicsContext; class ImageResourceObserver; class IntRect; class LayoutBox; @@ -145,7 +144,7 @@ class BoxPainterBase { virtual LayoutRectOutsets ComputeBorders() const = 0; virtual LayoutRectOutsets ComputePadding() const = 0; LayoutRectOutsets AdjustedBorderOutsets(const FillLayerInfo&) const; - void PaintFillLayerTextFillBox(GraphicsContext&, + void PaintFillLayerTextFillBox(const PaintInfo&, const FillLayerInfo&, Image*, SkBlendMode composite_op, @@ -153,7 +152,7 @@ class BoxPainterBase { const PhysicalRect&, const PhysicalRect& scrolled_paint_rect, bool object_has_multiple_boxes); - virtual void PaintTextClipMask(GraphicsContext&, + virtual void PaintTextClipMask(const PaintInfo&, const IntRect& mask_rect, const PhysicalOffset& paint_offset, bool object_has_multiple_boxes) = 0; diff --git a/chromium/third_party/blink/renderer/core/paint/build.gni b/chromium/third_party/blink/renderer/core/paint/build.gni index bc9e44b569e..7812d2a77be 100644 --- a/chromium/third_party/blink/renderer/core/paint/build.gni +++ b/chromium/third_party/blink/renderer/core/paint/build.gni @@ -137,6 +137,8 @@ blink_core_sources_paint = [ "ng/ng_table_cell_paint_invalidator.h", "ng/ng_table_painters.cc", "ng/ng_table_painters.h", + "ng/ng_text_combine_painter.cc", + "ng/ng_text_combine_painter.h", "ng/ng_text_fragment_painter.cc", "ng/ng_text_fragment_painter.h", "ng/ng_text_painter.cc", @@ -150,8 +152,8 @@ blink_core_sources_paint = [ "object_paint_properties.h", "object_painter.cc", "object_painter.h", - "object_painter_base.cc", - "object_painter_base.h", + "outline_painter.cc", + "outline_painter.h", "paint_event.h", "paint_info.h", "paint_invalidator.cc", diff --git a/chromium/third_party/blink/renderer/core/paint/clip_path_clipper.cc b/chromium/third_party/blink/renderer/core/paint/clip_path_clipper.cc index 066bff54d9c..99d2790ba2a 100644 --- a/chromium/third_party/blink/renderer/core/paint/clip_path_clipper.cc +++ b/chromium/third_party/blink/renderer/core/paint/clip_path_clipper.cc @@ -4,6 +4,9 @@ #include "third_party/blink/renderer/core/paint/clip_path_clipper.h" +#include "third_party/blink/renderer/core/css/clip_path_paint_image_generator.h" +#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h" @@ -13,6 +16,7 @@ #include "third_party/blink/renderer/core/style/clip_path_operation.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/graphics/image.h" #include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h" #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" @@ -40,10 +44,14 @@ LayoutSVGResourceClipper* ResolveElementReference( return nullptr; LayoutSVGResourceClipper* resource_clipper = GetSVGResourceAsType(*client, reference_clip_path_operation); - if (resource_clipper) { - SECURITY_DCHECK(!resource_clipper->NeedsLayout()); - resource_clipper->ClearInvalidationMask(); - } + if (!resource_clipper) + return nullptr; + + resource_clipper->ClearInvalidationMask(); + if (DisplayLockUtilities::LockedAncestorPreventingLayout(*resource_clipper)) + return nullptr; + + SECURITY_DCHECK(!resource_clipper->SelfNeedsLayout()); return resource_clipper; } @@ -55,6 +63,34 @@ static bool UsesZoomedReferenceBox(const LayoutObject& clip_path_owner) { return !clip_path_owner.IsSVGChild() || clip_path_owner.IsSVGForeignObject(); } +static void PaintWorkletBasedClip(GraphicsContext& context, + const LayoutObject& clip_path_owner, + const FloatRect& reference_box, + bool uses_zoomed_reference_box) { + DCHECK(RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled()); + DCHECK_EQ(clip_path_owner.StyleRef().ClipPath()->GetType(), + ClipPathOperation::SHAPE); + + float zoom = uses_zoomed_reference_box + ? clip_path_owner.StyleRef().EffectiveZoom() + : 1; + ClipPathPaintImageGenerator* generator = + clip_path_owner.GetFrame()->GetClipPathPaintImageGenerator(); + + scoped_refptr<Image> paint_worklet_image = + generator->Paint(zoom, reference_box, *clip_path_owner.GetNode()); + + // TODO(crbug.com/1223975): Fix bounding box. It should enclose affected area + // of the animation. + absl::optional<FloatRect> bounding_box = + ClipPathClipper::LocalClipPathBoundingBox(clip_path_owner); + DCHECK(bounding_box); + FloatRect src_rect(bounding_box.value()); + context.DrawImage(paint_worklet_image.get(), Image::kSyncDecode, src_rect, + &src_rect, clip_path_owner.StyleRef().DisableForceDark(), + SkBlendMode::kSrcOver, kRespectImageOrientation); +} + FloatRect ClipPathClipper::LocalReferenceBox(const LayoutObject& object) { if (object.IsSVGChild()) return SVGResources::ReferenceBoxForEffects(object); @@ -166,6 +202,8 @@ void ClipPathClipper::PaintClipPathAsMaskImage( DisplayItem::kSVGClip)) return; + // TODO(crbug.com/1223975): Fix paint rectangle for + // CompositeClipPathAnimation. DrawingRecorder recorder( context, display_item_client, DisplayItem::kSVGClip, EnclosingIntRect(properties->MaskClip()->UnsnappedClipRect().Rect())); @@ -174,44 +212,57 @@ void ClipPathClipper::PaintClipPathAsMaskImage( bool uses_zoomed_reference_box = UsesZoomedReferenceBox(layout_object); FloatRect reference_box = LocalReferenceBox(layout_object); - bool is_first = true; - bool rest_of_the_chain_already_appled = false; - const LayoutObject* current_object = &layout_object; - while (!rest_of_the_chain_already_appled && current_object) { - const ClipPathOperation* clip_path = current_object->StyleRef().ClipPath(); - if (!clip_path) - break; - // We wouldn't have reached here if the current clip-path is a shape, - // because it would have been applied as a path-based clip already. - LayoutSVGResourceClipper* resource_clipper = ResolveElementReference( - *current_object, To<ReferenceClipPathOperation>(*clip_path)); - if (!resource_clipper) - break; - - if (is_first) - context.Save(); - else - context.BeginLayer(1.f, SkBlendMode::kDstIn); - - if (resource_clipper->StyleRef().HasClipPath()) { - // Try to apply nested clip-path as path-based clip. - if (const absl::optional<Path>& path = PathBasedClipInternal( - *resource_clipper, uses_zoomed_reference_box, reference_box)) { - context.ClipPath(path->GetSkPath(), kAntiAliased); - rest_of_the_chain_already_appled = true; + // TODO(crbug.com/1223975): Currently for CompositeClipPathAnimation feature + // to be activated a node must have clip-path attribute. + if (RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled() && + layout_object.StyleRef().HasCurrentClipPathAnimation() && + layout_object.StyleRef().ClipPath()->GetType() == + ClipPathOperation::SHAPE) { + if (!layout_object.GetFrame()) + return; + PaintWorkletBasedClip(context, layout_object, reference_box, + uses_zoomed_reference_box); + } else { + bool is_first = true; + bool rest_of_the_chain_already_appled = false; + const LayoutObject* current_object = &layout_object; + while (!rest_of_the_chain_already_appled && current_object) { + const ClipPathOperation* clip_path = + current_object->StyleRef().ClipPath(); + if (!clip_path) + break; + // We wouldn't have reached here if the current clip-path is a shape, + // because it would have been applied as a path-based clip already. + LayoutSVGResourceClipper* resource_clipper = ResolveElementReference( + *current_object, To<ReferenceClipPathOperation>(*clip_path)); + if (!resource_clipper) + break; + + if (is_first) + context.Save(); + else + context.BeginLayer(1.f, SkBlendMode::kDstIn); + + if (resource_clipper->StyleRef().HasClipPath()) { + // Try to apply nested clip-path as path-based clip. + if (const absl::optional<Path>& path = PathBasedClipInternal( + *resource_clipper, uses_zoomed_reference_box, reference_box)) { + context.ClipPath(path->GetSkPath(), kAntiAliased); + rest_of_the_chain_already_appled = true; + } } - } - context.ConcatCTM(MaskToContentTransform( - *resource_clipper, uses_zoomed_reference_box, reference_box)); - context.DrawRecord(resource_clipper->CreatePaintRecord()); + context.ConcatCTM(MaskToContentTransform( + *resource_clipper, uses_zoomed_reference_box, reference_box)); + context.DrawRecord(resource_clipper->CreatePaintRecord()); - if (is_first) - context.Restore(); - else - context.EndLayer(); + if (is_first) + context.Restore(); + else + context.EndLayer(); - is_first = false; - current_object = resource_clipper; + is_first = false; + current_object = resource_clipper; + } } context.Restore(); } @@ -219,6 +270,12 @@ void ClipPathClipper::PaintClipPathAsMaskImage( bool ClipPathClipper::ShouldUseMaskBasedClip(const LayoutObject& object) { if (object.IsText() || !object.StyleRef().HasClipPath()) return false; + // TODO(crbug.com/1223975): Currently for CompositeClipPathAnimation feature + // to be activated a node must have clip-path attribute. + if (RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled() && + object.StyleRef().ClipPath()->GetType() == ClipPathOperation::SHAPE && + object.StyleRef().HasCurrentClipPathAnimation()) + return true; const auto* reference_clip = DynamicTo<ReferenceClipPathOperation>(object.StyleRef().ClipPath()); if (!reference_clip) @@ -232,6 +289,14 @@ bool ClipPathClipper::ShouldUseMaskBasedClip(const LayoutObject& object) { absl::optional<Path> ClipPathClipper::PathBasedClip( const LayoutObject& clip_path_owner) { + // TODO(crbug.com/1223975): Currently for CompositeClipPathAnimation feature + // to be activated a node must have clip-path attribute. + if (RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled() && + clip_path_owner.StyleRef().HasCurrentClipPathAnimation()) { + const ClipPathOperation& clip_path = *clip_path_owner.StyleRef().ClipPath(); + if (clip_path.GetType() == ClipPathOperation::SHAPE) + return absl::nullopt; + } return PathBasedClipInternal(clip_path_owner, UsesZoomedReferenceBox(clip_path_owner), LocalReferenceBox(clip_path_owner)); diff --git a/chromium/third_party/blink/renderer/core/paint/collapsed_border_painter.cc b/chromium/third_party/blink/renderer/core/paint/collapsed_border_painter.cc index b7147fd7f3f..5d84c77416d 100644 --- a/chromium/third_party/blink/renderer/core/paint/collapsed_border_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/collapsed_border_painter.cc @@ -5,7 +5,7 @@ #include "third_party/blink/renderer/core/paint/collapsed_border_painter.h" #include "third_party/blink/renderer/core/paint/block_painter.h" -#include "third_party/blink/renderer/core/paint/object_painter.h" +#include "third_party/blink/renderer/core/paint/box_border_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" #include "third_party/blink/renderer/core/paint/table_cell_painter.h" @@ -362,36 +362,36 @@ void CollapsedBorderPainter::PaintCollapsedBorders( rect.Y() - before_.outer_width, rect.Width() + before_.begin_outset + before_.end_outset, before_.outer_width + before_.inner_width); - ObjectPainter::DrawBoxSide(context, edge_rect, BoxSide::kTop, - before_.value->GetColor(), - CollapsedBorderStyle(before_.value->Style())); + BoxBorderPainter::DrawBoxSide(context, edge_rect, BoxSide::kTop, + before_.value->GetColor(), + CollapsedBorderStyle(before_.value->Style())); } if (after_.value) { IntRect edge_rect(rect.X() - after_.begin_outset, rect.MaxY() - after_.inner_width, rect.Width() + after_.begin_outset + after_.end_outset, after_.inner_width + after_.outer_width); - ObjectPainter::DrawBoxSide(context, edge_rect, BoxSide::kBottom, - after_.value->GetColor(), - CollapsedBorderStyle(after_.value->Style())); + BoxBorderPainter::DrawBoxSide(context, edge_rect, BoxSide::kBottom, + after_.value->GetColor(), + CollapsedBorderStyle(after_.value->Style())); } if (start_.value) { IntRect edge_rect(rect.X() - start_.outer_width, rect.Y() - start_.begin_outset, start_.outer_width + start_.inner_width, rect.Height() + start_.begin_outset + start_.end_outset); - ObjectPainter::DrawBoxSide(context, edge_rect, BoxSide::kLeft, - start_.value->GetColor(), - CollapsedBorderStyle(start_.value->Style())); + BoxBorderPainter::DrawBoxSide(context, edge_rect, BoxSide::kLeft, + start_.value->GetColor(), + CollapsedBorderStyle(start_.value->Style())); } if (end_.value) { IntRect edge_rect(rect.MaxX() - end_.inner_width, rect.Y() - end_.begin_outset, end_.inner_width + end_.outer_width, rect.Height() + end_.begin_outset + end_.end_outset); - ObjectPainter::DrawBoxSide(context, edge_rect, BoxSide::kRight, - end_.value->GetColor(), - CollapsedBorderStyle(end_.value->Style())); + BoxBorderPainter::DrawBoxSide(context, edge_rect, BoxSide::kRight, + end_.value->GetColor(), + CollapsedBorderStyle(end_.value->Style())); } } diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc index 68204175c25..ef97248755a 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc @@ -43,6 +43,7 @@ #include "third_party/blink/renderer/core/html/media/html_media_element.h" #include "third_party/blink/renderer/core/html/media/html_video_element.h" #include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/layout/geometry/transform_state.h" #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" @@ -143,7 +144,7 @@ static bool NeedsDecorationOutlineLayer(const PaintLayer& paint_layer, // only 2/3 of the width is outside of the offset. const int outline_drawn_inside = style.OutlineStyleIsAuto() - ? std::ceil(style.GetOutlineStrokeWidthForFocusRing() / 3.f) + 1 + ? std::ceil(style.FocusRingInnerStrokeWidth()) + 1 : 0; return could_obscure_decorations && style.HasOutline() && @@ -690,8 +691,7 @@ void CompositedLayerMapping::ComputeGraphicsLayerParentLocation( if (compositing_container && compositing_container->NeedsCompositedScrolling()) { auto& layout_box = To<LayoutBox>(compositing_container->GetLayoutObject()); - IntSize scroll_offset = - FlooredIntSize(layout_box.PixelSnappedScrolledContentOffset()); + IntPoint scroll_offset = layout_box.PixelSnappedScrolledContentOffset(); IntPoint scroll_origin = compositing_container->GetScrollableArea()->ScrollOrigin(); scroll_origin.Move(-layout_box.OriginAdjustmentForScrollbars()); diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc b/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc index f0c7eaceb34..352db784fcb 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc @@ -1899,7 +1899,6 @@ TEST_P(CompositedLayerMappingTest, // Unlike CompositingTest.WillChangeTransformHintInSVG, will-change hints on the // SVG element itself should not opt into creating layers after paint. TEST_P(CompositedLayerMappingTest, WillChangeTransformHintOnSVG) { - ScopedCompositeSVGForTest enable_feature(true); SetBodyInnerHTML(R"HTML( <svg width="99" height="99" id="willChange" style="will-change: transform;"> <rect width="100%" height="100%" fill="blue"></rect> @@ -1914,7 +1913,6 @@ TEST_P(CompositedLayerMappingTest, WillChangeTransformHintOnSVG) { // Test that will-change changes inside SVG correctly update whether the // graphics layer should create layers after paint. TEST_P(CompositedLayerMappingTest, WillChangeTransformHintInSVGChanged) { - ScopedCompositeSVGForTest enable_feature(true); SetBodyInnerHTML(R"HTML( <svg width="99" height="99" id="svg" style="will-change: transform;"> <rect id="rect" width="100%" height="100%" fill="blue"></rect> diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc index 10e6f5be696..bd35955cb37 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc @@ -198,12 +198,10 @@ void CompositingLayerPropertyUpdater::Update(const LayoutObject& object) { state, snapped_paint_offset + mask_layer->OffsetFromLayoutObject()); } - if (RuntimeEnabledFeatures::CompositeSVGEnabled()) { - if (object.IsSVGRoot()) { - main_graphics_layer->SetShouldCreateLayersAfterPaint( - To<LayoutSVGRoot>(object).HasDescendantCompositingReasons() && - main_graphics_layer->PaintsContentOrHitTest()); - } + if (object.IsSVGRoot()) { + main_graphics_layer->SetShouldCreateLayersAfterPaint( + To<LayoutSVGRoot>(object).HasDescendantCompositingReasons() && + main_graphics_layer->PaintsContentOrHitTest()); } } diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc index 054da3469e4..c033093e054 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc @@ -111,7 +111,7 @@ static bool ShouldPreferCompositingForLayoutView( static CompositingReasons BackfaceInvisibility3DAncestorReason( const PaintLayer& layer) { - if (RuntimeEnabledFeatures::TransformInteropEnabled()) { + if (RuntimeEnabledFeatures::BackfaceVisibilityInteropEnabled()) { if (auto* compositing_container = layer.CompositingContainer()) { if (compositing_container->GetLayoutObject() .StyleRef() @@ -153,8 +153,7 @@ CompositingReasons CompositingReasonFinder::DirectReasonsForPaintProperties( if (RequiresCompositingForRootScroller(*layer)) reasons |= CompositingReason::kRootScroller; - if (RequiresCompositingForScrollDependentPosition(*layer)) - reasons |= CompositingReason::kScrollDependentPosition; + reasons |= CompositingReasonsForScrollDependentPosition(*layer); if (RequiresCompositingForAffectedByOuterViewportBoundsDelta(object)) reasons |= CompositingReason::kAffectedByOuterViewportBoundsDelta; @@ -162,6 +161,16 @@ CompositingReasons CompositingReasonFinder::DirectReasonsForPaintProperties( if (style.HasBackdropFilter()) reasons |= CompositingReason::kBackdropFilter; + reasons |= BackfaceInvisibility3DAncestorReason(*layer); + + if (auto* element = DynamicTo<Element>(object.GetNode())) { + if (element->ShouldCompositeForDocumentTransition()) + reasons |= CompositingReason::kDocumentTransitionSharedElement; + } + + if (object.CanHaveAdditionalCompositingReasons()) + reasons |= object.AdditionalCompositingReasons(); + if (auto* scrollable_area = layer->GetScrollableArea()) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { bool force_prefer_compositing_to_lcd_text = @@ -181,16 +190,6 @@ CompositingReasons CompositingReasonFinder::DirectReasonsForPaintProperties( reasons |= CompositingReason::kOverflowScrolling; } - reasons |= BackfaceInvisibility3DAncestorReason(*layer); - - if (auto* element = DynamicTo<Element>(object.GetNode())) { - if (element->ShouldCompositeForDocumentTransition()) - reasons |= CompositingReason::kDocumentTransitionSharedElement; - } - - if (object.CanHaveAdditionalCompositingReasons()) - reasons |= object.AdditionalCompositingReasons(); - return reasons; } @@ -198,8 +197,6 @@ CompositingReasons CompositingReasonFinder::DirectReasonsForSVGChildPaintProperties( const LayoutObject& object) { DCHECK(object.IsSVGChild()); - if (!RuntimeEnabledFeatures::CompositeSVGEnabled()) - return CompositingReason::kNone; if (object.IsText()) return CompositingReason::kNone; @@ -280,8 +277,7 @@ CompositingReasons CompositingReasonFinder::NonStyleDeterminedDirectReasons( } } - if (RequiresCompositingForScrollDependentPosition(layer)) - direct_reasons |= CompositingReason::kScrollDependentPosition; + direct_reasons |= CompositingReasonsForScrollDependentPosition(layer); if (RequiresCompositingForAffectedByOuterViewportBoundsDelta(layout_object)) direct_reasons |= CompositingReason::kAffectedByOuterViewportBoundsDelta; @@ -324,8 +320,6 @@ CompositingReasons CompositingReasonFinder::NonStyleDeterminedDirectReasons( static bool ObjectTypeSupportsCompositedTransformAnimation( const LayoutObject& object) { if (object.IsSVGChild()) { - if (!RuntimeEnabledFeatures::CompositeSVGEnabled()) - return false; // Transforms are not supported on hidden containers, inlines, or text. return !object.IsSVGHiddenContainer() && !object.IsLayoutInline() && !object.IsText(); @@ -390,8 +384,10 @@ bool CompositingReasonFinder::RequiresCompositingForRootScroller( return layer.GetLayoutObject().IsGlobalRootScroller(); } -bool CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( +CompositingReasons +CompositingReasonFinder::CompositingReasonsForScrollDependentPosition( const PaintLayer& layer) { + CompositingReasons reasons = CompositingReason::kNone; // Don't promote fixed position elements that are descendants of a non-view // container, e.g. transformed elements. They will stay fixed wrt the // container rather than the enclosing frame. @@ -400,20 +396,19 @@ bool CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( // position elements are composited under overflow: hidden, which can still // have smooth scroll animations. LocalFrameView* frame_view = layer.GetLayoutObject().GetFrameView(); - return frame_view->LayoutViewport()->HasOverflow(); + if (frame_view->LayoutViewport()->HasOverflow()) + reasons |= CompositingReason::kFixedPosition; } // Don't promote sticky position elements that cannot move with scrolls. - if (layer.SticksToScroller()) { - // We check for |HasOverflow| instead of |ScrollsOverflow| to ensure sticky - // position elements are composited under overflow: hidden, which can still - // have smooth scroll animations. - return layer.AncestorScrollContainerLayer() - ->GetScrollableArea() - ->HasOverflow(); - } + // We check for |HasOverflow| instead of |ScrollsOverflow| to ensure sticky + // position elements are composited under overflow: hidden, which can still + // have smooth scroll animations. + if (layer.SticksToScroller() && + layer.AncestorScrollContainerLayer()->GetScrollableArea()->HasOverflow()) + reasons |= CompositingReason::kStickyPosition; - return false; + return reasons; } bool CompositingReasonFinder:: diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h index 7640882046b..e865fa835aa 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h +++ b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h @@ -48,7 +48,8 @@ class CORE_EXPORT CompositingReasonFinder { const LayoutObject&); static bool RequiresCompositingForRootScroller(const PaintLayer&); - static bool RequiresCompositingForScrollDependentPosition(const PaintLayer&); + static CompositingReasons CompositingReasonsForScrollDependentPosition( + const PaintLayer&); static bool RequiresCompositingForAffectedByOuterViewportBoundsDelta( const LayoutObject&); diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc index 67da1f6ba88..0379506f503 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc @@ -6,6 +6,7 @@ #include "base/test/scoped_feature_list.h" #include "third_party/blink/public/common/features.h" +#include "third_party/blink/renderer/core/css/resolver/style_resolver.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" #include "third_party/blink/renderer/core/layout/layout_block.h" @@ -140,27 +141,31 @@ TEST_F(CompositingReasonFinderTest, OnlyScrollingStickyPositionPromoted) { auto& sticky_scrolling = *To<LayoutBoxModelObject>(GetLayoutObjectByElementId("sticky-scrolling")); - EXPECT_TRUE( - CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( - *sticky_scrolling.Layer())); + EXPECT_EQ( + CompositingReasonFinder::CompositingReasonsForScrollDependentPosition( + *sticky_scrolling.Layer()), + CompositingReason::kStickyPosition); auto& sticky_no_scrolling = *To<LayoutBoxModelObject>( GetLayoutObjectByElementId("sticky-no-scrolling")); - EXPECT_FALSE( - CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( - *sticky_no_scrolling.Layer())); + EXPECT_EQ( + CompositingReasonFinder::CompositingReasonsForScrollDependentPosition( + *sticky_no_scrolling.Layer()), + CompositingReason::kNone); auto& overflow_hidden_scrolling = *To<LayoutBoxModelObject>( GetLayoutObjectByElementId("overflow-hidden-scrolling")); - EXPECT_TRUE( - CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( - *overflow_hidden_scrolling.Layer())); + EXPECT_EQ( + CompositingReasonFinder::CompositingReasonsForScrollDependentPosition( + *overflow_hidden_scrolling.Layer()), + CompositingReason::kStickyPosition); auto& overflow_hidden_no_scrolling = *To<LayoutBoxModelObject>( GetLayoutObjectByElementId("overflow-hidden-no-scrolling")); - EXPECT_FALSE( - CompositingReasonFinder::RequiresCompositingForScrollDependentPosition( - *overflow_hidden_no_scrolling.Layer())); + EXPECT_EQ( + CompositingReasonFinder::CompositingReasonsForScrollDependentPosition( + *overflow_hidden_no_scrolling.Layer()), + CompositingReason::kNone); if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { EXPECT_EQ(kPaintsIntoOwnBacking, @@ -252,27 +257,31 @@ TEST_F(CompositingReasonFinderTest, PromoteCrossOriginIframe) { <!DOCTYPE html> <iframe id=iframe></iframe> )HTML"); - UpdateAllLifecyclePhasesForTest(); HTMLFrameOwnerElement* iframe = To<HTMLFrameOwnerElement>(GetDocument().getElementById("iframe")); ASSERT_TRUE(iframe); + iframe->contentDocument()->OverrideIsInitialEmptyDocument(); + To<LocalFrame>(iframe->ContentFrame())->View()->BeginLifecycleUpdates(); ASSERT_FALSE(iframe->ContentFrame()->IsCrossOriginToMainFrame()); + UpdateAllLifecyclePhasesForTest(); LayoutView* iframe_layout_view = To<LocalFrame>(iframe->ContentFrame())->ContentLayoutObject(); ASSERT_TRUE(iframe_layout_view); PaintLayer* iframe_layer = iframe_layout_view->Layer(); ASSERT_TRUE(iframe_layer); - EXPECT_EQ(kNotComposited, iframe_layer->DirectCompositingReasons()); + EXPECT_EQ(CompositingReason::kNone, iframe_layer->DirectCompositingReasons()); + EXPECT_FALSE(iframe_layer->GetScrollableArea()->NeedsCompositedScrolling()); + EXPECT_EQ(CompositingReason::kNone, + CompositingReasonFinder::DirectReasonsForPaintProperties( + *iframe_layout_view)); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> <iframe id=iframe sandbox></iframe> )HTML"); iframe = To<HTMLFrameOwnerElement>(GetDocument().getElementById("iframe")); - To<LocalFrame>(iframe->ContentFrame()) - ->GetDocument() - ->OverrideIsInitialEmptyDocument(); + iframe->contentDocument()->OverrideIsInitialEmptyDocument(); To<LocalFrame>(iframe->ContentFrame())->View()->BeginLifecycleUpdates(); UpdateAllLifecyclePhasesForTest(); iframe_layout_view = @@ -282,11 +291,25 @@ TEST_F(CompositingReasonFinderTest, PromoteCrossOriginIframe) { ASSERT_TRUE(iframe->ContentFrame()->IsCrossOriginToMainFrame()); EXPECT_EQ(CompositingReason::kIFrame, iframe_layer->DirectCompositingReasons()); + EXPECT_FALSE(iframe_layer->GetScrollableArea()->NeedsCompositedScrolling()); + EXPECT_EQ(CompositingReason::kIFrame, + CompositingReasonFinder::DirectReasonsForPaintProperties( + *iframe_layout_view)); + + // Make the iframe contents scrollable. + iframe->contentDocument()->body()->setAttribute(html_names::kStyleAttr, + "height: 2000px"); + UpdateAllLifecyclePhasesForTest(); + EXPECT_TRUE(iframe_layer->GetScrollableArea()->NeedsCompositedScrolling()); + EXPECT_EQ(CompositingReason::kIFrame | CompositingReason::kOverflowScrolling, + CompositingReasonFinder::DirectReasonsForPaintProperties( + *iframe_layout_view)); } TEST_F(CompositingReasonFinderTest, CompositeWithBackfaceVisibilityAncestorAndPreserve3D) { - ScopedTransformInteropForTest enabled(true); + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> @@ -307,7 +330,8 @@ TEST_F(CompositingReasonFinderTest, TEST_F(CompositingReasonFinderTest, CompositeWithBackfaceVisibilityAncestorAndPreserve3DWithInterveningDiv) { - ScopedTransformInteropForTest enabled(true); + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> @@ -330,7 +354,8 @@ TEST_F(CompositingReasonFinderTest, TEST_F(CompositingReasonFinderTest, CompositeWithBackfaceVisibilityAncestorWithInterveningStackingDiv) { - ScopedTransformInteropForTest enabled(true); + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> @@ -358,7 +383,8 @@ TEST_F(CompositingReasonFinderTest, TEST_F(CompositingReasonFinderTest, CompositeWithBackfaceVisibilityAncestorAndFlattening) { - ScopedTransformInteropForTest enabled(true); + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> @@ -378,7 +404,8 @@ TEST_F(CompositingReasonFinderTest, } TEST_F(CompositingReasonFinderTest, CompositeWithBackfaceVisibility) { - ScopedTransformInteropForTest enabled(true); + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); SetBodyInnerHTML(R"HTML( <!DOCTYPE html> diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_test.cc index 2606987c68e..c0078d40a28 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/compositing_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/compositing_test.cc @@ -5,6 +5,8 @@ #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "cc/layers/picture_layer.h" +#include "cc/layers/recording_source.h" +#include "cc/layers/surface_layer.h" #include "cc/trees/compositor_commit_data.h" #include "cc/trees/effect_node.h" #include "cc/trees/layer_tree_host.h" @@ -25,6 +27,7 @@ #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/svg_names.h" +#include "third_party/blink/renderer/core/testing/fake_remote_frame_host.h" #include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h" #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" @@ -245,7 +248,6 @@ TEST_P(CompositingTest, WillChangeTransformHint) { } TEST_P(CompositingTest, WillChangeTransformHintInSVG) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(), R"HTML( <!doctype html> <style> @@ -268,7 +270,6 @@ TEST_P(CompositingTest, WillChangeTransformHintInSVG) { } TEST_P(CompositingTest, Compositing3DTransformOnSVGModelObject) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(), R"HTML( <!doctype html> <svg width="200" height="200"> @@ -310,7 +311,6 @@ TEST_P(CompositingTest, Compositing3DTransformOnSVGModelObject) { } TEST_P(CompositingTest, Compositing3DTransformOnSVGBlock) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(), R"HTML( <!doctype html> <svg width="200" height="200"> @@ -354,7 +354,6 @@ TEST_P(CompositingTest, Compositing3DTransformOnSVGBlock) { // Inlines do not support the transform property and should not be composited // due to 3D transforms. TEST_P(CompositingTest, NotCompositing3DTransformOnSVGInline) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(), R"HTML( <!doctype html> <svg width="200" height="200"> @@ -379,7 +378,6 @@ TEST_P(CompositingTest, NotCompositing3DTransformOnSVGInline) { } TEST_P(CompositingTest, PaintPropertiesWhenCompositingSVG) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(), R"HTML( <!doctype html> <style> @@ -886,7 +884,6 @@ TEST_P(CompositingSimTest, DirectTransformPropertyUpdate) { } TEST_P(CompositingSimTest, DirectSVGTransformPropertyUpdate) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(R"HTML( <!doctype html> <style> @@ -1002,11 +999,6 @@ TEST_P(CompositingSimTest, DirectTransformPropertyUpdateCausesChange) { // so that the browser controls movement adjustments needed by bottom-fixed // elements will work. TEST_P(CompositingSimTest, AffectedByOuterViewportBoundsDelta) { - // TODO(bokan): This test will have to be reevaluated for CAP. It looks like - // the fixed layer isn't composited in CAP. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -1024,10 +1016,6 @@ TEST_P(CompositingSimTest, AffectedByOuterViewportBoundsDelta) { auto* fixed_element = GetElementById("fixed"); auto* fixed_element_layer = CcLayerByDOMElementId("fixed"); - DCHECK_EQ(fixed_element_layer->element_id(), - CompositorElementIdFromUniqueObjectId( - fixed_element->GetLayoutObject()->UniqueId(), - CompositorElementIdNamespace::kPrimary)); // Fix the DIV to the bottom of the viewport. Since the viewport height will // expand/contract, the fixed element will need to be moved as the bounds @@ -1108,12 +1096,6 @@ TEST_P(CompositingSimTest, DirectTransformOriginPropertyUpdate) { // This test is similar to |LayerSubtreeTransformPropertyChanged| but for // effect property node changes. TEST_P(CompositingSimTest, LayerSubtreeEffectPropertyChanged) { - // TODO(crbug.com/765003): CAP may make different layerization decisions and - // we cannot guarantee that both divs will be composited in this test. When - // CAP gets closer to launch, this test should be updated to pass. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -1140,16 +1122,7 @@ TEST_P(CompositingSimTest, LayerSubtreeEffectPropertyChanged) { auto* outer_element = GetElementById("outer"); auto* outer_element_layer = CcLayerByDOMElementId("outer"); - DCHECK_EQ(outer_element_layer->element_id(), - CompositorElementIdFromUniqueObjectId( - outer_element->GetLayoutObject()->UniqueId(), - CompositorElementIdNamespace::kPrimary)); - auto* inner_element = GetElementById("inner"); auto* inner_element_layer = CcLayerByDOMElementId("inner"); - DCHECK_EQ(inner_element_layer->element_id(), - CompositorElementIdFromUniqueObjectId( - inner_element->GetLayoutObject()->UniqueId(), - CompositorElementIdNamespace::kPrimary)); // Initially, no layer should have |subtree_property_changed| set. EXPECT_FALSE(outer_element_layer->subtree_property_changed()); @@ -1161,11 +1134,9 @@ TEST_P(CompositingSimTest, LayerSubtreeEffectPropertyChanged) { // both layers. outer_element->setAttribute(html_names::kStyleAttr, "filter: blur(20px)"); UpdateAllLifecyclePhases(); - // TODO(wangxianzhu): Probably avoid setting this flag on transform change. EXPECT_TRUE(outer_element_layer->subtree_property_changed()); // Set by blink::PropertyTreeManager. EXPECT_TRUE(GetEffectNode(outer_element_layer)->effect_changed); - // TODO(wangxianzhu): Probably avoid setting this flag on transform change. EXPECT_TRUE(inner_element_layer->subtree_property_changed()); EXPECT_FALSE(GetEffectNode(inner_element_layer)->effect_changed); @@ -1229,12 +1200,6 @@ TEST_P(CompositingSimTest, LayerSubtreeClipPropertyChanged) { } TEST_P(CompositingSimTest, LayerSubtreeOverflowClipPropertyChanged) { - // TODO(crbug.com/765003): CAP may make different layerization decisions and - // we cannot guarantee that both divs will be composited in this test. When - // CAP gets closer to launch, this test should be updated to pass. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -1262,12 +1227,7 @@ TEST_P(CompositingSimTest, LayerSubtreeOverflowClipPropertyChanged) { auto* outer_element = GetElementById("outer"); auto* outer_element_layer = CcLayerByDOMElementId("outer"); - auto* inner_element = GetElementById("inner"); auto* inner_element_layer = CcLayerByDOMElementId("inner"); - DCHECK_EQ(inner_element_layer->element_id(), - CompositorElementIdFromUniqueObjectId( - inner_element->GetLayoutObject()->UniqueId(), - CompositorElementIdNamespace::kPrimary)); // Initially, no layer should have |subtree_property_changed| set. EXPECT_FALSE(outer_element_layer->subtree_property_changed()); @@ -1482,11 +1442,6 @@ TEST_P(CompositingSimTest, RootScrollingContentsSafeOpaqueBackgroundColor) { } TEST_P(CompositingSimTest, NonDrawableLayersIgnoredForRenderSurfaces) { - // TODO(crbug.com/765003): CAP may make different layerization decisions. When - // CAP gets closer to launch, this test should be updated to pass. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -1746,13 +1701,6 @@ TEST_P(CompositingTest, EffectNodesShouldHaveStableIds) { } TEST_P(CompositingSimTest, ImplSideScrollSkipsCommit) { - // TODO(crbug.com/1046544): This test fails with CompositeAfterPaint because - // PaintArtifactCompositor::Update is run for scroll offset changes. When we - // have an early-out to avoid SetNeedsCommit for non-changing interest-rects, - // this test will pass. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - InitializeWithHTML(R"HTML( <div id='scroller' style='will-change: transform; overflow: scroll; width: 100px; height: 100px'> @@ -2006,11 +1954,10 @@ TEST_P(CompositingSimTest, MultipleChunkBackgroundColorChangeRepaintUpdate) { EXPECT_EQ(scrolling_contents->background_color(), SK_ColorWHITE); } -// Similar to |BackgroundColorChangeUsesRepaintUpdate| but with CompositeSVG. -// This test changes paint for a composited SVG element, as well as a regular -// HTML element in the presence of composited SVG. +// Similar to |BackgroundColorChangeUsesRepaintUpdate| but with post-paint +// composited SVG. This test changes paint for a composited SVG element, as well +// as a regular HTML element in the presence of composited SVG. TEST_P(CompositingSimTest, SVGColorChangeUsesRepaintUpdate) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -2150,6 +2097,63 @@ TEST_P(CompositingSimTest, ChangingContentsOpaqueForTextRequiresFullUpdate) { EXPECT_FALSE(CcLayerByDOMElementId("target")->contents_opaque_for_text()); } +TEST_P(CompositingSimTest, ContentsOpaqueForTextWithSubpixelSizeSimpleBg) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <div id="target" style="will-change: transform; background: white; + width: 100.6px; height: 10.3px"> + TEXT + </div> + )HTML"); + Compositor().BeginFrame(); + auto* cc_layer = CcLayerByDOMElementId("target"); + // In CompositeAfterPaint, we adjust visual rect of the DrawingDisplayItem + // with simple painting to the bounds of the painting. + EXPECT_EQ(gfx::Size(101, 10), cc_layer->bounds()); + EXPECT_TRUE(cc_layer->contents_opaque()); + EXPECT_TRUE(cc_layer->contents_opaque_for_text()); +} + +TEST_P(CompositingSimTest, ContentsOpaqueForTextWithSubpixelSizeComplexBg) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <div id="target" style="will-change: transform; background: white; + border: 2px inset blue; + width: 100.6px; height: 10.3px"> + TEXT + </div> + )HTML"); + Compositor().BeginFrame(); + auto* cc_layer = CcLayerByDOMElementId("target"); + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + EXPECT_EQ(gfx::Size(105, 15), cc_layer->bounds()); + EXPECT_FALSE(cc_layer->contents_opaque()); + } else { + // Pre-CAP always pixel-snaps composited layer bounds, which might be + // incorrect in some corner cases where we don't pixel-snap painting. + EXPECT_EQ(gfx::Size(105, 14), cc_layer->bounds()); + EXPECT_TRUE(cc_layer->contents_opaque()); + } + EXPECT_TRUE(cc_layer->contents_opaque_for_text()); +} + +TEST_P(CompositingSimTest, ContentsOpaqueForTextWithPartialBackground) { + // This test works only with the new text opaque algorithm. + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) + return; + + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <div id="target" style="will-change: transform; padding: 10px"> + <div style="background: white">TEXT</div> + </div> + )HTML"); + Compositor().BeginFrame(); + auto* cc_layer = CcLayerByDOMElementId("target"); + EXPECT_FALSE(cc_layer->contents_opaque()); + EXPECT_TRUE(cc_layer->contents_opaque_for_text()); +} + TEST_P(CompositingSimTest, FullCompositingUpdateReasons) { InitializeWithHTML(R"HTML( <!DOCTYPE html> @@ -2218,9 +2222,9 @@ TEST_P(CompositingSimTest, FullCompositingUpdateReasons) { PaintArtifactCompositor::PreviousUpdateType::kFull); } -// Similar to |FullCompositingUpdateReasons| but for changes in CompositeSVG. -TEST_P(CompositingSimTest, FullCompositingUpdateReasonInCompositeSVG) { - ScopedCompositeSVGForTest enable_feature(true); +// Similar to |FullCompositingUpdateReasons| but for changes in post-paint +// composited SVG. +TEST_P(CompositingSimTest, FullCompositingUpdateReasonWithCompositedSVG) { InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -2252,7 +2256,6 @@ TEST_P(CompositingSimTest, FullCompositingUpdateReasonInCompositeSVG) { } TEST_P(CompositingSimTest, FullCompositingUpdateForJustCreatedChunks) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -2290,7 +2293,6 @@ TEST_P(CompositingSimTest, FullCompositingUpdateForJustCreatedChunks) { } TEST_P(CompositingSimTest, FullCompositingUpdateForUncachableChunks) { - ScopedCompositeSVGForTest enable_feature(true); InitializeWithHTML(R"HTML( <!DOCTYPE html> <style> @@ -2376,4 +2378,194 @@ TEST_P(CompositingSimTest, DecompositeScrollerInHiddenIframe) { EXPECT_FALSE(scroller->GetScrollableArea()->NeedsCompositedScrolling()); } +TEST_P(CompositingSimTest, ForeignLayersInMovedSubsequence) { + SimRequest main_resource("https://origin-a.com/a.html", "text/html"); + LoadURL("https://origin-a.com/a.html"); + main_resource.Complete(R"HTML( + <!DOCTYPE html> + <style> iframe { isolation: isolate; } </style> + <iframe sandbox src="https://origin-b.com/b.html"></iframe> + <div id="target" style="background: blue;">a</div> + )HTML"); + + frame_test_helpers::TestWebRemoteFrameClient remote_frame_client; + FakeRemoteFrameHost remote_frame_host; + remote_frame_host.Init(remote_frame_client.GetRemoteAssociatedInterfaces()); + WebRemoteFrameImpl* remote_frame = + frame_test_helpers::CreateRemote(&remote_frame_client); + MainFrame().FirstChild()->Swap(remote_frame); + + Compositor().BeginFrame(); + + auto remote_surface_layer = cc::SurfaceLayer::Create(); + remote_frame->GetFrame()->SetCcLayerForTesting(remote_surface_layer, true); + Compositor().BeginFrame(); + + // Initially, no update is needed. + EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate()); + + // Clear the previous update to ensure we record a new one in the next update. + paint_artifact_compositor()->ClearPreviousUpdateForTesting(); + + // Modifying paint in a simple way only requires a repaint update. + auto* target_element = GetElementById("target"); + target_element->setAttribute(html_names::kStyleAttr, "background: green;"); + Compositor().BeginFrame(); + EXPECT_EQ(paint_artifact_compositor()->PreviousUpdateForTesting(), + PaintArtifactCompositor::PreviousUpdateType::kRepaint); + + remote_frame->Detach(); +} + +// While not required for correctness, it is important for performance that +// snapped backgrounds use solid color layers which avoid tiling. +TEST_P(CompositingSimTest, SolidColorLayersWithSnapping) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <style> + #snapDown { + width: 60.1px; + height: 100px; + will-change: opacity; + background: blue; + } + #snapUp { + width: 60.9px; + height: 100px; + will-change: opacity; + background: blue; + } + </style> + <div id="snapDown"></div> + <div id="snapUp"></div> + )HTML"); + + Compositor().BeginFrame(); + + auto* snap_down = + static_cast<const cc::PictureLayer*>(CcLayerByDOMElementId("snapDown")); + EXPECT_TRUE(snap_down->GetRecordingSourceForTesting()->is_solid_color()); + auto* snap_up = + static_cast<const cc::PictureLayer*>(CcLayerByDOMElementId("snapUp")); + EXPECT_TRUE(snap_up->GetRecordingSourceForTesting()->is_solid_color()); +} + +TEST_P(CompositingSimTest, SolidColorLayerWithSubpixelTransform) { + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) + return; + + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <style> + #forceCompositing { + position: absolute; + width: 100px; + height: 100px; + will-change: transform; + } + #target { + position: absolute; + top: 0; + left: 0; + width: 60.9px; + height: 60.1px; + transform: translate(0.4px, 0.6px); + background: blue; + } + </style> + <div id="forceCompositing"></div> + <div id="target"></div> + )HTML"); + + Compositor().BeginFrame(); + + auto* target = + static_cast<const cc::PictureLayer*>(CcLayerByDOMElementId("target")); + EXPECT_TRUE(target->GetRecordingSourceForTesting()->is_solid_color()); + EXPECT_NEAR(0.4, target->offset_to_transform_parent().x(), 0.001); + EXPECT_NEAR(0.6, target->offset_to_transform_parent().y(), 0.001); +} + +// While not required for correctness, it is important for performance (e.g., +// the MotionMark Focus benchmark) that we do not decomposite effect nodes (see: +// |PaintArtifactCompositor::DecompositeEffect|) when the author has specified +// 3D transforms which are frequently used as a generic compositing trigger. +TEST_P(CompositingSimTest, EffectCompositedWith3DTransform) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <style> + div { + width: 100px; + height: 100px; + background: rebeccapurple; + transform: translate3d(1px, 1px, 0); + } + </style> + <div id="opacity" style="opacity: 0.5;"></div> + <div id="filter" style="filter: blur(1px);"></div> + )HTML"); + Compositor().BeginFrame(); + + auto* opacity_effect = GetEffectNode(CcLayerByDOMElementId("opacity")); + EXPECT_TRUE(opacity_effect); + EXPECT_EQ(opacity_effect->opacity, 0.5f); + EXPECT_TRUE(opacity_effect->filters.IsEmpty()); + + auto* filter_effect = GetEffectNode(CcLayerByDOMElementId("filter")); + EXPECT_TRUE(filter_effect); + EXPECT_EQ(filter_effect->opacity, 1.f); + EXPECT_FALSE(filter_effect->filters.IsEmpty()); +} + +// The main thread will not have a chance to update the painted content of an +// animation running on the compositor, so ensure the cc::Layer with animating +// opacity has content when starting the animation, even if the opacity is +// initially 0. +TEST_P(CompositingSimTest, CompositorAnimationOfOpacityHasPaintedContent) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <style> + @keyframes opacity { + 0% { opacity: 0; } + 99% { opacity: 0; } + 100% { opacity: 0.5; } + } + #animation { + animation-name: opacity; + animation-duration: 999s; + width: 100px; + height: 100px; + background: lightblue; + } + </style> + <div id="animation"></div> + )HTML"); + Compositor().BeginFrame(); + EXPECT_TRUE(CcLayerByDOMElementId("animation")->DrawsContent()); +} + +TEST_P(CompositingSimTest, CompositorAnimationOfNonInvertibleTransform) { + InitializeWithHTML(R"HTML( + <!DOCTYPE html> + <style> + @keyframes anim { + 0% { transform: scale(0); } + 99% { transform: scale(0); } + 100% { transform: scale(1); } + } + #animation { + animation-name: anim; + animation-duration: 999s; + width: 100px; + height: 100px; + background: lightblue; + } + </style> + <div id="animation"></div> + )HTML"); + Compositor().BeginFrame(); + EXPECT_TRUE(CcLayerByDOMElementId("animation")); + EXPECT_TRUE(CcLayerByDOMElementId("animation")->DrawsContent()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/chromium/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc index 72c31017cc4..96f0d086e68 100644 --- a/chromium/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc +++ b/chromium/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc @@ -174,7 +174,7 @@ void PaintLayerCompositor::UpdateAssignmentsIfNeededRecursive( UpdateAssignmentsIfNeededRecursiveInternal(target_state, compositing_reasons_stats); UMA_HISTOGRAM_CUSTOM_COUNTS("Blink.Compositing.LayerPromotionCount.Overlap", - compositing_reasons_stats.overlap_layers, 1, 100, + compositing_reasons_stats.overlap_layers, 1, 1000, 5); UMA_HISTOGRAM_CUSTOM_COUNTS( "Blink.Compositing.LayerPromotionCount.ActiveAnimation", diff --git a/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.cc b/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.cc index bc3a8a1da42..e024453dfc3 100644 --- a/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.cc +++ b/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.cc @@ -49,34 +49,37 @@ bool SetFragmentContentsCullRect(PaintLayer& layer, } // anonymous namespace -CullRectUpdater::CullRectUpdater(PaintLayer& root_layer) - : root_layer_(root_layer), - root_state_(root_layer.GetLayoutObject() - .FirstFragment() - .LocalBorderBoxProperties() - .Unalias()) { - DCHECK(root_layer.IsRootLayer()); +void CullRectUpdater::Update() { + DCHECK(starting_layer_.IsRootLayer()); + UpdateInternal(CullRect::Infinite()); +#if DCHECK_IS_ON() + if (VLOG_IS_ON(2)) { + VLOG(2) << "PaintLayer tree after cull rect update:"; + showLayerTree(&starting_layer_); + } +#endif } void CullRectUpdater::UpdateInternal(const CullRect& input_cull_rect) { DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled()); - DCHECK(root_layer_.IsRootLayer()); - if (root_layer_.GetLayoutObject().GetFrameView()->ShouldThrottleRendering()) + const auto& object = starting_layer_.GetLayoutObject(); + if (object.GetFrameView()->ShouldThrottleRendering()) return; + root_state_ = + object.View()->FirstFragment().LocalBorderBoxProperties().Unalias(); bool should_use_infinite = - PaintLayerPainter(root_layer_).ShouldUseInfiniteCullRect(); - auto& fragment = - root_layer_.GetLayoutObject().GetMutableForPainting().FirstFragment(); + PaintLayerPainter(starting_layer_).ShouldUseInfiniteCullRect(); + auto& fragment = object.GetMutableForPainting().FirstFragment(); SetFragmentCullRect( - root_layer_, fragment, + starting_layer_, fragment, should_use_infinite ? CullRect::Infinite() : input_cull_rect); bool force_update_children = SetFragmentContentsCullRect( - root_layer_, fragment, + starting_layer_, fragment, should_use_infinite ? CullRect::Infinite() : ComputeFragmentContentsCullRect( - root_layer_, fragment, input_cull_rect)); - UpdateForDescendants(root_layer_, force_update_children); + starting_layer_, fragment, input_cull_rect)); + UpdateForDescendants(starting_layer_, force_update_children); } void CullRectUpdater::UpdateRecursively(PaintLayer& layer, @@ -89,13 +92,27 @@ void CullRectUpdater::UpdateRecursively(PaintLayer& layer, // This defines the scope of force_proactive_update_ (which may be set by // ComputeFragmentCullRect() and ComputeFragmentContentsCullRect()) to the // subtree. - base::AutoReset<bool> reset(&force_proactive_update_, - force_proactive_update_); + base::AutoReset<bool> reset_force_update(&force_proactive_update_, + force_proactive_update_); if (force_update_self || should_proactively_update || layer.NeedsCullRectUpdate()) force_update_children |= UpdateForSelf(layer, parent_painting_layer); + absl::optional<base::AutoReset<bool>> reset_subtree_is_out_of_cull_rect; + if (!subtree_is_out_of_cull_rect_ && layer.KnownToClipSubtree() && + !layer.GetLayoutObject().FirstFragment().NextFragment()) { + const auto* box = layer.GetLayoutBox(); + DCHECK(box); + PhysicalRect overflow_rect = box->PhysicalSelfVisualOverflowRect(); + overflow_rect.Move(box->FirstFragment().PaintOffset()); + if (!box->FirstFragment().GetCullRect().Intersects( + EnclosingIntRect(overflow_rect))) { + reset_subtree_is_out_of_cull_rect.emplace(&subtree_is_out_of_cull_rect_, + true); + } + } + if (force_update_children || layer.DescendantNeedsCullRectUpdate()) UpdateForDescendants(layer, force_update_children); @@ -134,9 +151,9 @@ void CullRectUpdater::UpdateForDescendants(PaintLayer& layer, // <div id="stacked-child" style="position: relative"></div> // </div> // </div> - // If |child|'s contents cull rect changes, we need to update |stack-child|'s - // cull rect because it's clipped by |child|. The is done in the following - // order: + // If |child|'s contents cull rect changes, we need to update + // |stacked-child|'s cull rect because it's clipped by |child|. The is done in + // the following order: // UpdateForDescendants(|layer|) // UpdateRecursively(|child|) (in the following loop) // |stacked-child|->SetNeedsCullRectUpdate() @@ -171,46 +188,55 @@ bool CullRectUpdater::UpdateForSelf(PaintLayer& layer, parent_painting_layer.GetLayoutObject().FirstFragment(); auto& first_fragment = layer.GetLayoutObject().GetMutableForPainting().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. + // If both |layer| and |parent_painting_layer| are fragmented and are inside + // the same pagination container, then try to match fragments from + // |parent_painting_layer| to |layer|, so that any fragment clip for + // |parent_painting_layer|'s fragment matches |layer|'s. Note we check both + // ShouldFragmentCompositedBounds() and next fragment here because the former + // may return false even if |layer| 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 = layer.ShouldFragmentCompositedBounds() || first_fragment.NextFragment(); bool should_match_fragments = is_fragmented && parent_painting_layer.EnclosingPaginationLayer() == layer.EnclosingPaginationLayer(); bool force_update_children = false; + bool should_use_infinite_cull_rect = + !subtree_is_out_of_cull_rect_ && + PaintLayerPainter(layer).ShouldUseInfiniteCullRect(); for (auto* fragment = &first_fragment; fragment; fragment = fragment->NextFragment()) { - const FragmentData* parent_fragment = nullptr; - if (should_match_fragments) { - for (parent_fragment = &first_parent_fragment; parent_fragment; - parent_fragment = parent_fragment->NextFragment()) { - if (parent_fragment->LogicalTopInFlowThread() == - fragment->LogicalTopInFlowThread()) - break; - } - } else { - parent_fragment = &first_parent_fragment; - } - CullRect cull_rect; CullRect contents_cull_rect; - if (!parent_fragment || - PaintLayerPainter(layer).ShouldUseInfiniteCullRect()) { - cull_rect = CullRect::Infinite(); - contents_cull_rect = CullRect::Infinite(); + if (subtree_is_out_of_cull_rect_) { + // PaintLayerPainter may skip the subtree including this layer, so we + // need to SetPreviousPaintResult() here. + layer.SetPreviousPaintResult(kMayBeClippedByCullRect); } else { - cull_rect = ComputeFragmentCullRect(layer, *fragment, *parent_fragment); - contents_cull_rect = - ComputeFragmentContentsCullRect(layer, *fragment, cull_rect); + const FragmentData* parent_fragment = nullptr; + if (!should_use_infinite_cull_rect) { + if (should_match_fragments) { + for (parent_fragment = &first_parent_fragment; parent_fragment; + parent_fragment = parent_fragment->NextFragment()) { + if (parent_fragment->FragmentID() == fragment->FragmentID()) + break; + } + } else { + parent_fragment = &first_parent_fragment; + } + } + + if (should_use_infinite_cull_rect || !parent_fragment) { + cull_rect = CullRect::Infinite(); + contents_cull_rect = CullRect::Infinite(); + } else { + cull_rect = ComputeFragmentCullRect(layer, *fragment, *parent_fragment); + contents_cull_rect = + ComputeFragmentContentsCullRect(layer, *fragment, cull_rect); + } } SetFragmentCullRect(layer, *fragment, cull_rect); @@ -276,32 +302,29 @@ bool CullRectUpdater::ShouldProactivelyUpdate(const PaintLayer& layer) const { return layer.SelfOrDescendantNeedsRepaint(); } -OverriddenCullRectScope::OverriddenCullRectScope(LocalFrameView& frame_view, +OverriddenCullRectScope::OverriddenCullRectScope(PaintLayer& starting_layer, const CullRect& cull_rect) - : frame_view_(frame_view) { + : starting_layer_(starting_layer) { if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) return; - PaintLayer* root_layer = frame_view_.GetLayoutView()->Layer(); - DCHECK(root_layer); - - if (frame_view.GetFrame().IsLocalRoot() && - !root_layer->NeedsCullRectUpdate() && - !root_layer->DescendantNeedsCullRectUpdate() && + if (starting_layer.GetLayoutObject().GetFrame()->IsLocalRoot() && + !starting_layer.NeedsCullRectUpdate() && + !starting_layer.DescendantNeedsCullRectUpdate() && cull_rect == - root_layer->GetLayoutObject().FirstFragment().GetCullRect()) { - // The cull rects calculated during PrePaint are good. + starting_layer.GetLayoutObject().FirstFragment().GetCullRect()) { + // The current cull rects are good. return; } updated_ = true; - root_layer->SetNeedsCullRectUpdate(); - CullRectUpdater(*root_layer).UpdateInternal(cull_rect); + starting_layer.SetNeedsCullRectUpdate(); + CullRectUpdater(starting_layer).UpdateInternal(cull_rect); } OverriddenCullRectScope::~OverriddenCullRectScope() { if (RuntimeEnabledFeatures::CullRectUpdateEnabled() && updated_) - frame_view_.GetLayoutView()->Layer()->SetNeedsCullRectUpdate(); + starting_layer_.SetNeedsCullRectUpdate(); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.h b/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.h index 0e5d933f72a..87899064187 100644 --- a/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.h +++ b/chromium/third_party/blink/renderer/core/paint/cull_rect_updater.h @@ -14,7 +14,6 @@ namespace blink { class FragmentData; class PaintLayer; -class LocalFrameView; // This class is used for updating the cull rects of PaintLayer fragments (see: // |FragmentData::cull_rect_| and |FragmentData::contents_cull_rect_|. @@ -28,9 +27,10 @@ class CORE_EXPORT CullRectUpdater { STACK_ALLOCATED(); public: - explicit CullRectUpdater(PaintLayer& root_layer); + explicit CullRectUpdater(PaintLayer& starting_layer) + : starting_layer_(starting_layer) {} - void Update() { UpdateInternal(CullRect::Infinite()); } + void Update(); private: friend class OverriddenCullRectScope; @@ -51,24 +51,27 @@ class CORE_EXPORT CullRectUpdater { const CullRect& cull_rect); bool ShouldProactivelyUpdate(const PaintLayer&) const; - PaintLayer& root_layer_; - PropertyTreeState root_state_; + PaintLayer& starting_layer_; + PropertyTreeState root_state_ = PropertyTreeState::Uninitialized(); bool force_proactive_update_ = false; + bool subtree_is_out_of_cull_rect_ = false; }; // Used when painting with a custom top-level cull rect, e.g. when printing a -// page. It temporarily overrides the cull rect on the PaintLayer of the -// LocalFrameView and marks the PaintLayer as needing to recalculate the cull -// rect when leaving this scope. +// page. It temporarily overrides the cull rect on the PaintLayer (which must be +// a stacking context) and marks the PaintLayer as needing to recalculate the +// cull rect when leaving this scope. +// TODO(crbug.com/1215251): Avoid repaint after the scope if the scope is used +// to paint into a separate PaintController. class OverriddenCullRectScope { STACK_ALLOCATED(); public: - OverriddenCullRectScope(LocalFrameView&, const CullRect&); + OverriddenCullRectScope(PaintLayer&, const CullRect&); ~OverriddenCullRectScope(); private: - LocalFrameView& frame_view_; + PaintLayer& starting_layer_; bool updated_ = false; }; diff --git a/chromium/third_party/blink/renderer/core/paint/document_marker_painter.cc b/chromium/third_party/blink/renderer/core/paint/document_marker_painter.cc index 7c2ed4a4276..f7ff03b59c0 100644 --- a/chromium/third_party/blink/renderer/core/paint/document_marker_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/document_marker_painter.cc @@ -4,7 +4,7 @@ #include "third_party/blink/renderer/core/paint/document_marker_painter.h" -#include "base/stl_util.h" +#include "base/cxx17_backports.h" #include "build/build_config.h" #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" @@ -56,7 +56,8 @@ sk_sp<PaintRecord> RecordMarker(Color blink_color) { PaintRecorder recorder; recorder.beginRecording(kMarkerWidth, kMarkerHeight); - recorder.getRecordingCanvas()->drawPath(path.detach(), flags); + recorder.getRecordingCanvas()->cc::PaintCanvas::drawPath(path.detach(), + flags); return recorder.finishRecordingAsPicture(); } @@ -285,8 +286,7 @@ TextPaintStyle DocumentMarkerPainter::ComputeTextPaintStyleFrom( if (marker.GetType() != DocumentMarker::kTextFragment) { const Color platform_text_color = LayoutTheme::GetTheme().PlatformTextSearchColor( - marker.IsActiveMatch(), document.InForcedColorsMode(), - style.UsedColorScheme()); + marker.IsActiveMatch(), style.UsedColorScheme()); if (platform_text_color == text_color) return {}; text_color = platform_text_color; diff --git a/chromium/third_party/blink/renderer/core/paint/embedded_object_painter.cc b/chromium/third_party/blink/renderer/core/paint/embedded_object_painter.cc index bebbbd985ff..c88a78dd3f3 100644 --- a/chromium/third_party/blink/renderer/core/paint/embedded_object_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/embedded_object_painter.cc @@ -25,10 +25,10 @@ static const float kReplacementTextRoundedRectOpacity = 0.20f; static const float kReplacementTextRoundedRectRadius = 5; static const float kReplacementTextTextOpacity = 0.55f; -static Font ReplacementTextFont() { +static Font ReplacementTextFont(const Document* document) { FontDescription font_description; LayoutTheme::GetTheme().SystemFont(CSSValueID::kWebkitSmallControl, - font_description); + font_description, document); font_description.SetWeight(BoldWeightValue()); font_description.SetComputedSize(font_description.SpecifiedSize()); Font font(font_description); @@ -56,7 +56,7 @@ void EmbeddedObjectPainter::PaintReplaced(const PaintInfo& paint_info, BoxDrawingRecorder recorder(context, layout_embedded_object_, paint_info.phase, paint_offset); - Font font = ReplacementTextFont(); + Font font = ReplacementTextFont(&layout_embedded_object_.GetDocument()); const SimpleFontData* font_data = font.PrimaryFont(); DCHECK(font_data); if (!font_data) diff --git a/chromium/third_party/blink/renderer/core/paint/filter_effect_builder.cc b/chromium/third_party/blink/renderer/core/paint/filter_effect_builder.cc index 858cddfe305..0298359969a 100644 --- a/chromium/third_party/blink/renderer/core/paint/filter_effect_builder.cc +++ b/chromium/third_party/blink/renderer/core/paint/filter_effect_builder.cc @@ -312,6 +312,16 @@ FilterEffect* FilterEffectBuilder::BuildFilterEffect( convolve_matrix_operation->KernelMatrix()); break; } + case FilterOperation::COMPONENT_TRANSFER: { + ComponentTransferFilterOperation* component_transfer_operation = + To<ComponentTransferFilterOperation>(filter_operation); + effect = MakeGarbageCollected<FEComponentTransfer>( + parent_filter, component_transfer_operation->RedFunc(), + component_transfer_operation->GreenFunc(), + component_transfer_operation->BlueFunc(), + component_transfer_operation->AlphaFunc()); + break; + } default: break; } @@ -385,6 +395,7 @@ CompositorFilterOperations FilterEffectBuilder::BuildFilterOperations( } case FilterOperation::LUMINANCE_TO_ALPHA: case FilterOperation::CONVOLVE_MATRIX: + case FilterOperation::COMPONENT_TRANSFER: // These filter types only exist for Canvas filters. NOTREACHED(); break; diff --git a/chromium/third_party/blink/renderer/core/paint/find_paint_offset_needing_update.h b/chromium/third_party/blink/renderer/core/paint/find_paint_offset_needing_update.h index 2b21d3fb3a2..e902fca391b 100644 --- a/chromium/third_party/blink/renderer/core/paint/find_paint_offset_needing_update.h +++ b/chromium/third_party/blink/renderer/core/paint/find_paint_offset_needing_update.h @@ -9,6 +9,7 @@ #if DCHECK_IS_ON() +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/paint/fragment_data.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" diff --git a/chromium/third_party/blink/renderer/core/paint/find_properties_needing_update.h b/chromium/third_party/blink/renderer/core/paint/find_properties_needing_update.h index 1737801c869..1f765b7abec 100644 --- a/chromium/third_party/blink/renderer/core/paint/find_properties_needing_update.h +++ b/chromium/third_party/blink/renderer/core/paint/find_properties_needing_update.h @@ -9,7 +9,6 @@ #if DCHECK_IS_ON() -#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/paint/object_paint_properties.h" #include "third_party/blink/renderer/core/paint/paint_property_tree_builder.h" diff --git a/chromium/third_party/blink/renderer/core/paint/first_meaningful_paint_detector.cc b/chromium/third_party/blink/renderer/core/paint/first_meaningful_paint_detector.cc index 30e45501136..27c75f8989b 100644 --- a/chromium/third_party/blink/renderer/core/paint/first_meaningful_paint_detector.cc +++ b/chromium/third_party/blink/renderer/core/paint/first_meaningful_paint_detector.cc @@ -178,7 +178,6 @@ void FirstMeaningfulPaintDetector::ReportPresentationTime( // // TODO(crbug.com/738235): Consider not reporting any timestamp when failing // for reasons other than kDidNotSwapSwapFails. - paint_timing_->ReportSwapResultHistogram(result); provisional_first_meaningful_paint_presentation_ = timestamp; probe::PaintTiming(GetDocument(), "firstMeaningfulPaintCandidate", diff --git a/chromium/third_party/blink/renderer/core/paint/fragment_data.h b/chromium/third_party/blink/renderer/core/paint/fragment_data.h index e54f4b38b7b..641bc33c1f7 100644 --- a/chromium/third_party/blink/renderer/core/paint/fragment_data.h +++ b/chromium/third_party/blink/renderer/core/paint/fragment_data.h @@ -52,12 +52,32 @@ class CORE_EXPORT FragmentData { } void SetLayer(std::unique_ptr<PaintLayer>); + // A fragment ID unique within the LayoutObject. In NG block fragmentation, + // this is the fragmentainer index. In legacy block fragmentation, it's the + // flow thread block-offset. + wtf_size_t FragmentID() const { + return rare_data_ ? rare_data_->fragment_id : 0; + } + void SetFragmentID(wtf_size_t id) { + if (!rare_data_ && id == 0) + return; + EnsureRareData().fragment_id = id; + } + LayoutUnit LogicalTopInFlowThread() const { - return rare_data_ ? rare_data_->logical_top_in_flow_thread : LayoutUnit(); +#if DCHECK_IS_ON() + DCHECK(!rare_data_ || rare_data_->has_set_flow_thread_offset_ || + !rare_data_->fragment_id); +#endif + return LayoutUnit::FromRawValue(static_cast<int>(FragmentID())); } + void SetLogicalTopInFlowThread(LayoutUnit top) { - if (rare_data_ || top) - EnsureRareData().logical_top_in_flow_thread = top; + SetFragmentID(top.RawValue()); +#if DCHECK_IS_ON() + if (rare_data_) + rare_data_->has_set_flow_thread_offset_ = true; +#endif } // The pagination offset is the additional factor to add in to map from flow @@ -225,7 +245,7 @@ class CORE_EXPORT FragmentData { // Fragment specific data. PhysicalOffset legacy_pagination_offset; - LayoutUnit logical_top_in_flow_thread; + wtf_size_t fragment_id = 0; std::unique_ptr<ObjectPaintProperties> paint_properties; std::unique_ptr<RefCountedPropertyTreeState> local_border_box_properties; bool is_clip_path_cache_valid = false; @@ -234,6 +254,15 @@ class CORE_EXPORT FragmentData { CullRect cull_rect_; CullRect contents_cull_rect_; std::unique_ptr<FragmentData> next_fragment_; + +#if DCHECK_IS_ON() + // Legacy block fragmentation sets the flow thread offset for each + // FragmentData object, and this is used as its fragment_id, whereas NG + // block fragmentation uses the fragmentainer index instead. Here's a flag + // which can be used to assert that legacy code which expects flow thread + // offsets actually gets that. + bool has_set_flow_thread_offset_ = false; +#endif }; RareData& EnsureRareData(); diff --git a/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.cc b/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.cc index 6c6dbb81d23..f2c1b45dc7b 100644 --- a/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.cc +++ b/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.cc @@ -48,6 +48,9 @@ Color ForcedSystemForegroundColor(PseudoId pseudo_id, case kPseudoIdSelection: keyword = CSSValueID::kHighlighttext; break; + case kPseudoIdHighlight: + keyword = CSSValueID::kHighlighttext; + break; default: NOTREACHED(); break; @@ -66,6 +69,9 @@ Color ForcedSystemBackgroundColor(PseudoId pseudo_id, case kPseudoIdSelection: keyword = CSSValueID::kHighlight; break; + case kPseudoIdHighlight: + keyword = CSSValueID::kHighlight; + break; default: NOTREACHED(); break; @@ -89,8 +95,12 @@ Color HighlightThemeForegroundColor(const Document& document, style.UsedColorScheme()); case kPseudoIdTargetText: return LayoutTheme::GetTheme().PlatformTextSearchColor( - false /* active match */, document.InForcedColorsMode(), - style.UsedColorScheme()); + false /* active match */, style.UsedColorScheme()); + case kPseudoIdHighlight: + // TODO(ffiori): not assigning any visual effects to custom highlights by + // default as the spec doesn't define it. See + // https://github.com/w3c/csswg-drafts/issues/6375. + return style.VisitedDependentColor(color_property); default: NOTREACHED(); return Color(); @@ -112,16 +122,22 @@ Color HighlightThemeBackgroundColor(const Document& document, return Color(shared_highlighting::kFragmentTextBackgroundColorARGB); return LayoutTheme::GetTheme().PlatformTextSearchHighlightColor( - false /* active match */, document.InForcedColorsMode(), - style.UsedColorScheme()); + false /* active match */, style.UsedColorScheme()); + case kPseudoIdHighlight: + // TODO(ffiori): not assigning any visual effects to custom highlights by + // default as the spec doesn't define it. See + // https://github.com/w3c/csswg-drafts/issues/6375. + return style.VisitedDependentColor(GetCSSPropertyBackgroundColor()); default: NOTREACHED(); return Color(); } } -scoped_refptr<const ComputedStyle> HighlightPseudoStyle(Node* node, - PseudoId pseudo) { +scoped_refptr<const ComputedStyle> HighlightPseudoStyle( + Node* node, + PseudoId pseudo, + const AtomicString& pseudo_argument) { if (!node) return nullptr; @@ -155,10 +171,10 @@ scoped_refptr<const ComputedStyle> HighlightPseudoStyle(Node* node, // cache the styles for ::selection if there are no :window-inactive // selector, or if the page is active. return element->UncachedStyleForPseudoElement( - StyleRequest(pseudo, element->GetComputedStyle())); + StyleRequest(pseudo, element->GetComputedStyle(), pseudo_argument)); } - return element->CachedStyleForPseudoElement(pseudo); + return element->CachedStyleForPseudoElement(pseudo, pseudo_argument); } Color HighlightColor(const Document& document, @@ -166,7 +182,8 @@ Color HighlightColor(const Document& document, Node* node, PseudoId pseudo, const CSSProperty& color_property, - const GlobalPaintFlags global_paint_flags) { + const GlobalPaintFlags global_paint_flags, + const AtomicString& pseudo_argument = g_null_atom) { if (pseudo == kPseudoIdSelection) { // If the element is unselectable, or we are only painting the selection, // don't override the foreground color with the selection foreground color. @@ -177,7 +194,7 @@ Color HighlightColor(const Document& document, } scoped_refptr<const ComputedStyle> pseudo_style = - HighlightPseudoStyle(node, pseudo); + HighlightPseudoStyle(node, pseudo, pseudo_argument); mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); if (pseudo_style) { @@ -199,7 +216,8 @@ Color HighlightPaintingUtils::HighlightBackgroundColor( const Document& document, const ComputedStyle& style, Node* node, - PseudoId pseudo) { + PseudoId pseudo, + const AtomicString& pseudo_argument) { if (pseudo == kPseudoIdSelection) { if (node && !NodeIsSelectable(style, node)) return Color::kTransparent; @@ -207,7 +225,7 @@ Color HighlightPaintingUtils::HighlightBackgroundColor( mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); if (scoped_refptr<const ComputedStyle> pseudo_style = - HighlightPseudoStyle(node, pseudo)) { + HighlightPseudoStyle(node, pseudo, pseudo_argument)) { if (!document.InForcedColorsMode() || pseudo_style->ForcedColorAdjust() == EForcedColorAdjust::kNone) { Color highlight_color = @@ -259,10 +277,11 @@ Color HighlightPaintingUtils::HighlightForegroundColor( const ComputedStyle& style, Node* node, PseudoId pseudo, - const GlobalPaintFlags global_paint_flags) { + const GlobalPaintFlags global_paint_flags, + const AtomicString& pseudo_argument) { return HighlightColor(document, style, node, pseudo, - GetCSSPropertyWebkitTextFillColor(), - global_paint_flags); + GetCSSPropertyWebkitTextFillColor(), global_paint_flags, + pseudo_argument); } Color HighlightPaintingUtils::HighlightEmphasisMarkColor( @@ -282,7 +301,8 @@ TextPaintStyle HighlightPaintingUtils::HighlightPaintingStyle( Node* node, PseudoId pseudo, const TextPaintStyle& text_style, - const PaintInfo& paint_info) { + const PaintInfo& paint_info, + const AtomicString& pseudo_argument) { TextPaintStyle highlight_style = text_style; bool uses_text_as_clip = paint_info.phase == PaintPhase::kTextClip; const GlobalPaintFlags global_paint_flags = paint_info.GetGlobalPaintFlags(); @@ -293,13 +313,13 @@ TextPaintStyle HighlightPaintingUtils::HighlightPaintingStyle( if (!uses_text_as_clip) { highlight_style.fill_color = HighlightForegroundColor( - document, style, node, pseudo, global_paint_flags); + document, style, node, pseudo, global_paint_flags, pseudo_argument); highlight_style.emphasis_mark_color = HighlightEmphasisMarkColor( document, style, node, pseudo, global_paint_flags); } if (scoped_refptr<const ComputedStyle> pseudo_style = - HighlightPseudoStyle(node, pseudo)) { + HighlightPseudoStyle(node, pseudo, pseudo_argument)) { highlight_style.stroke_color = uses_text_as_clip ? Color::kBlack : pseudo_style->VisitedDependentColor( diff --git a/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.h b/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.h index c29608ddc58..f1dbe602376 100644 --- a/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.h +++ b/chromium/third_party/blink/renderer/core/paint/highlight_painting_utils.h @@ -11,6 +11,7 @@ #include "third_party/blink/renderer/core/paint/paint_phase.h" #include "third_party/blink/renderer/core/style/applied_text_decoration.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" namespace blink { @@ -28,26 +29,32 @@ class CORE_EXPORT HighlightPaintingUtils { static absl::optional<AppliedTextDecoration> HighlightTextDecoration( const ComputedStyle& style, const ComputedStyle& pseudo_style); - static Color HighlightBackgroundColor(const Document&, - const ComputedStyle&, - Node*, - PseudoId); - static Color HighlightForegroundColor(const Document&, - const ComputedStyle&, - Node*, - PseudoId, - const GlobalPaintFlags); + static Color HighlightBackgroundColor( + const Document&, + const ComputedStyle&, + Node*, + PseudoId, + const AtomicString& pseudo_argument = g_null_atom); + static Color HighlightForegroundColor( + const Document&, + const ComputedStyle&, + Node*, + PseudoId, + const GlobalPaintFlags, + const AtomicString& pseudo_argument = g_null_atom); static Color HighlightEmphasisMarkColor(const Document&, const ComputedStyle&, Node*, PseudoId, const GlobalPaintFlags); - static TextPaintStyle HighlightPaintingStyle(const Document&, - const ComputedStyle&, - Node*, - PseudoId, - const TextPaintStyle& text_style, - const PaintInfo&); + static TextPaintStyle HighlightPaintingStyle( + const Document&, + const ComputedStyle&, + Node*, + PseudoId, + const TextPaintStyle& text_style, + const PaintInfo&, + const AtomicString& pseudo_argument = g_null_atom); }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc index ba995d59323..9b3ff951616 100644 --- a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc +++ b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc @@ -3,10 +3,13 @@ // found in the LICENSE file. #include "third_party/blink/renderer/core/paint/image_paint_timing_detector.h" +#include "base/feature_list.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" +#include "third_party/blink/public/common/features.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/visual_viewport.h" #include "third_party/blink/renderer/core/layout/layout_image_resource.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h" #include "third_party/blink/renderer/core/page/chrome_client.h" @@ -66,7 +69,9 @@ static bool LargeImageFirst(const base::WeakPtr<ImageRecord>& a, ImagePaintTimingDetector::ImagePaintTimingDetector( LocalFrameView* frame_view, PaintTimingCallbackManager* callback_manager) - : records_manager_(frame_view), + : uses_page_viewport_( + base::FeatureList::IsEnabled(features::kUsePageViewportInLCP)), + records_manager_(frame_view), frame_view_(frame_view), callback_manager_(callback_manager) {} @@ -314,8 +319,16 @@ uint64_t ImagePaintTimingDetector::ComputeImageRectSize( frame_view_->GetPaintTimingDetector().BlinkSpaceToDIPs( FloatRect(image_border)); if (!viewport_size_.has_value()) { + // If the flag to use page viewport is enabled, we use the page viewport + // (aka the main frame viewport) for all frames, including iframes. This + // prevents us from discarding images with size equal to the size of its + // embedding iframe. + IntRect viewport_int_rect = + uses_page_viewport_ + ? frame_view_->GetPage()->GetVisualViewport().VisibleContentRect() + : frame_view_->GetScrollableArea()->VisibleContentRect(); FloatRect viewport = frame_view_->GetPaintTimingDetector().BlinkSpaceToDIPs( - FloatRect(frame_view_->GetScrollableArea()->VisibleContentRect())); + FloatRect(viewport_int_rect)); viewport_size_ = viewport.Size().Area(); } // An SVG image size is computed with respect to the virtual viewport of the diff --git a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.h b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.h index 802c124f9c2..dd7fa784da3 100644 --- a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.h +++ b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector.h @@ -344,6 +344,8 @@ class CORE_EXPORT ImagePaintTimingDetector final // image. This value is reset when paint is finished and is computed if unset // when needed. 0 means that the size has not been computed. absl::optional<uint64_t> viewport_size_; + // Whether the viewport size used is the page viewport. + bool uses_page_viewport_; ImageRecordsManager records_manager_; Member<LocalFrameView> frame_view_; diff --git a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc index d7f5982f4e3..b5429832e59 100644 --- a/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc @@ -385,10 +385,10 @@ TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_TraceEvent_Candidate) { EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId node_id; EXPECT_TRUE(arg_dict->GetInteger("DOMNodeId", &node_id)); EXPECT_GT(node_id, 0); @@ -455,10 +455,10 @@ TEST_P(ImagePaintTimingDetectorTest, EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId node_id; EXPECT_TRUE(arg_dict->GetInteger("DOMNodeId", &node_id)); EXPECT_GT(node_id, 0); @@ -521,10 +521,10 @@ TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_TraceEvent_NoCandidate) { EXPECT_EQ("loading", events[0]->category); EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId candidate_index; EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index)); EXPECT_EQ(candidate_index, 1); @@ -539,10 +539,10 @@ TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_TraceEvent_NoCandidate) { // Use block to reuse the temp variable names. { EXPECT_TRUE(events[1]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[1]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId candidate_index; EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index)); EXPECT_EQ(candidate_index, 3); diff --git a/chromium/third_party/blink/renderer/core/paint/image_painter.cc b/chromium/third_party/blink/renderer/core/paint/image_painter.cc index 367f07a6919..674a6801271 100644 --- a/chromium/third_party/blink/renderer/core/paint/image_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/image_painter.cc @@ -13,6 +13,7 @@ #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/html/html_area_element.h" #include "third_party/blink/renderer/core/html/html_image_element.h" +#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" #include "third_party/blink/renderer/core/layout/layout_image.h" #include "third_party/blink/renderer/core/layout/layout_replaced.h" @@ -21,6 +22,7 @@ #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/image_element_timing.h" +#include "third_party/blink/renderer/core/paint/outline_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" @@ -127,11 +129,8 @@ void ImagePainter::PaintAreaElementFocusRing(const PaintInfo& paint_info) { PhysicalRect focus_rect = layout_image_.PhysicalContentBoxRect(); focus_rect.Move(paint_offset); paint_info.context.Clip(PixelSnappedIntRect(focus_rect)); - paint_info.context.DrawFocusRing( - path, area_element_style->GetOutlineStrokeWidthForFocusRing(), - area_element_style->OutlineOffsetInt(), - layout_image_.ResolveColor(*area_element_style, - GetCSSPropertyOutlineColor())); + OutlinePainter::PaintFocusRingPath(paint_info.context, path, + *area_element_style); paint_info.context.Restore(); } @@ -254,7 +253,7 @@ void ImagePainter::PaintIntoRect(GraphicsContext& context, // Does not set an observer for the placeholder image, setting it to null. scoped_refptr<PlaceholderImage> placeholder_image = PlaceholderImage::Create(nullptr, image->Size(), - image->Data() ? image->Data()->size() : 0); + image->HasData() ? image->DataSize() : 0); placeholder_image->SetIconAndTextScaleFactor( layout_image_.GetFrame()->PageZoomFactor()); image = std::move(placeholder_image); @@ -263,7 +262,7 @@ void ImagePainter::PaintIntoRect(GraphicsContext& context, context.DrawImage(image.get(), decode_mode, FloatRect(pixel_snapped_dest_rect), &src_rect, - layout_image_.StyleRef().HasFilterInducingProperty(), + layout_image_.StyleRef().DisableForceDark(), SkBlendMode::kSrcOver, respect_orientation); if (ImageResourceContent* image_content = image_resource.CachedImage()) { diff --git a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.cc b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.cc index 4eac70704b3..c86a356c39c 100644 --- a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.cc @@ -31,6 +31,7 @@ #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_shader.h" +#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/skia/include/effects/SkGradientShader.h" namespace blink { @@ -172,7 +173,12 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, inline_text_box_.LogicalHeight())); absl::optional<SelectionBoundsRecorder> selection_recorder; - if (have_selection && paint_info.phase == PaintPhase::kForeground && + // Empty selections might be the boundary of the document selection, and thus + // need to get recorded. + const bool should_record_selection = + have_selection || + inline_text_box_.GetLineLayoutItem().GetLayoutObject()->IsSelected(); + if (should_record_selection && paint_info.phase == PaintPhase::kForeground && !is_printing) { const FrameSelection& frame_selection = InlineLayoutObject().GetFrame()->Selection(); @@ -183,7 +189,8 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, selection_state)) { PhysicalRect selection_rect = GetSelectionRect<InlineTextBoxPainter::PaintOptions::kNormal>( - context, box_rect, style_to_use, style_to_use.GetFont()); + context, box_rect, style_to_use, style_to_use.GetFont(), nullptr, + /* allow_empty_selection*/ true); TextDirection direction = inline_text_box_.IsLeftToRightDirection() ? TextDirection::kLtr @@ -413,10 +420,9 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, ? absl::optional<AppliedTextDecoration>( selection_style.selection_text_decoration) : absl::nullopt; - decoration_info.emplace(box_origin, local_origin, width, - inline_text_box_.Root().BaselineType(), - style_to_use, selection_text_decoration, - decorating_box_style); + decoration_info.emplace( + local_origin, width, inline_text_box_.Root().BaselineType(), + style_to_use, selection_text_decoration, decorating_box_style); TextDecorationOffset decoration_offset(decoration_info->Style(), &inline_text_box_, decorating_box); text_painter.PaintDecorationsExceptLineThrough( @@ -661,6 +667,10 @@ void InlineTextBoxPainter::PaintDocumentMarkers( styleable_marker, style, font); } } break; + case DocumentMarker::kHighlight: + inline_text_box_.PaintDocumentMarker(paint_info, box_origin, marker, + style, font, false); + break; default: // Marker is not painted, or painting code has not been added yet break; @@ -728,11 +738,14 @@ PhysicalRect InlineTextBoxPainter::GetSelectionRect( const PhysicalRect& box_rect, const ComputedStyle& style, const Font& font, - LayoutTextCombine* combined_text) { + LayoutTextCombine* combined_text, + bool allow_empty_selection) { // See if we have a selection to paint at all. int start_pos, end_pos; inline_text_box_.SelectionStartEnd(start_pos, end_pos); - if (start_pos >= end_pos) + if (start_pos > end_pos) + return PhysicalRect(); + if (!allow_empty_selection && start_pos == end_pos) return PhysicalRect(); // If the text is truncated, let the thing being painted in the truncation @@ -833,8 +846,11 @@ PhysicalRect InlineTextBoxPainter::PaintSelection( // If the text color ends up being the same as the selection background, // invert the selection background. - if (text_color == c) + if (text_color == c) { + UseCounter::Count(layout_item.GetDocument(), + WebFeature::kSelectionBackgroundColorInversion); c = Color(0xff - c.Red(), 0xff - c.Green(), 0xff - c.Blue()); + } GraphicsContextStateSaver state_saver(context); @@ -935,9 +951,7 @@ void InlineTextBoxPainter::PaintTextMarkerBackground( TextRun run = inline_text_box_.ConstructTextRun(style); Color color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor( - marker.IsActiveMatch(), - inline_text_box_.GetLineLayoutItem().GetDocument().InForcedColorsMode(), - style.UsedColorScheme()); + marker.IsActiveMatch(), style.UsedColorScheme()); GraphicsContext& context = paint_info.context; GraphicsContextStateSaver state_saver(context); diff --git a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.h b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.h index 42c30224e34..a9bb0b494bb 100644 --- a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter.h @@ -91,7 +91,8 @@ class InlineTextBoxPainter { const PhysicalRect& box_rect, const ComputedStyle&, const Font&, - LayoutTextCombine* = nullptr); + LayoutTextCombine* = nullptr, + bool allow_empty_selection = false); void PaintStyleableMarkerUnderline(GraphicsContext&, const PhysicalOffset& box_origin, diff --git a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter_test.cc index 1b505e57d1e..52c697610dd 100644 --- a/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/inline_text_box_painter_test.cc @@ -7,6 +7,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/editing/testing/selection_sample.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" using testing::ElementsAre; @@ -28,64 +29,4 @@ TEST_P(InlineTextBoxPainterTest, LineBreak) { EXPECT_EQ(6u, ContentDisplayItems().size()); } -class InlineTextBoxPainterNonNGTest : public PaintControllerPaintTest, - public ScopedLayoutNGForTest { - public: - InlineTextBoxPainterNonNGTest() : ScopedLayoutNGForTest(false) {} -}; - -INSTANTIATE_PAINT_TEST_SUITE_P(InlineTextBoxPainterNonNGTest); - -TEST_P(InlineTextBoxPainterNonNGTest, RecordedSelectionAll) { - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - SetBodyInnerHTML("<span>A<br>B<br>C</span>"); - - GetDocument().GetFrame()->Selection().SetHandleVisibleForTesting(); - GetDocument().GetFrame()->Selection().SelectAll(); - UpdateAllLifecyclePhasesForTest(); - - auto chunks = ContentPaintChunks(); - EXPECT_EQ(chunks.size(), 1u); - EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value()); - EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value()); - PaintedSelectionBound start = - chunks.begin()->layer_selection_data->start.value(); - EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); - EXPECT_EQ(start.edge_start, IntPoint(8, 8)); - EXPECT_EQ(start.edge_end, IntPoint(8, 9)); - - PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value(); - EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); - EXPECT_EQ(end.edge_start, IntPoint(9, 10)); - EXPECT_EQ(end.edge_end, IntPoint(9, 11)); -} - -TEST_P(InlineTextBoxPainterNonNGTest, RecordedSelectionMultiline) { - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - - GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping( - SelectionSample::SetSelectionText( - GetDocument().body(), - "<div style='white-space:pre'>f^oo\nbar\nb|az</div>")); - GetDocument().GetFrame()->Selection().SetHandleVisibleForTesting(); - UpdateAllLifecyclePhasesForTest(); - - auto chunks = ContentPaintChunks(); - EXPECT_EQ(chunks.size(), 1u); - EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value()); - EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value()); - PaintedSelectionBound start = - chunks.begin()->layer_selection_data->start.value(); - EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); - EXPECT_EQ(start.edge_start, IntPoint(8, 8)); - EXPECT_EQ(start.edge_end, IntPoint(8, 9)); - - PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value(); - EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); - EXPECT_EQ(end.edge_start, IntPoint(9, 10)); - EXPECT_EQ(end.edge_end, IntPoint(9, 11)); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/line_box_list_painter.cc b/chromium/third_party/blink/renderer/core/paint/line_box_list_painter.cc index 6841df62c20..355247b1ebe 100644 --- a/chromium/third_party/blink/renderer/core/paint/line_box_list_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/line_box_list_painter.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/paint/line_box_list_painter.h" +#include "third_party/blink/renderer/core/css/style_engine.h" #include "third_party/blink/renderer/core/layout/api/line_layout_box_model.h" #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" diff --git a/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.cc index 7a381c48e5e..9b50fd32cfe 100644 --- a/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.cc +++ b/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.cc @@ -32,6 +32,7 @@ #include "base/memory/ptr_util.h" #include "cc/layers/picture_layer.h" #include "cc/paint/display_item_list.h" +#include "skia/ext/skia_matrix_44.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/web/blink.h" #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h" @@ -62,7 +63,6 @@ #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" #include "third_party/blink/renderer/platform/web_test_support.h" #include "third_party/blink/renderer/platform/wtf/vector.h" -#include "third_party/skia/include/core/SkMatrix44.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/size_f.h" @@ -217,7 +217,9 @@ void LinkHighlightImpl::StartHighlightAnimationIfNeeded() { timing_function)); auto keyframe_model = std::make_unique<CompositorKeyframeModel>( - *curve, compositor_target_property::OPACITY, 0, 0); + *curve, 0, 0, + CompositorKeyframeModel::TargetPropertyId( + compositor_target_property::OPACITY)); compositor_animation_->AddKeyframeModel(std::move(keyframe_model)); } @@ -244,7 +246,7 @@ void LinkHighlightImpl::UpdateAfterPrePaint() { return; DCHECK(!object->GetFrameView()->ShouldThrottleRendering()); - size_t fragment_count = 0; + wtf_size_t fragment_count = 0; for (const auto* fragment = &object->FirstFragment(); fragment; fragment = fragment->NextFragment()) ++fragment_count; diff --git a/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.h b/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.h index 3f9b2dae2a1..34cd91faed8 100644 --- a/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.h +++ b/chromium/third_party/blink/renderer/core/paint/link_highlight_impl.h @@ -79,7 +79,7 @@ class CORE_EXPORT LinkHighlightImpl final : public CompositorAnimationDelegate, void Paint(GraphicsContext&); wtf_size_t FragmentCountForTesting() const { return fragments_.size(); } - cc::PictureLayer* LayerForTesting(size_t index) const { + cc::PictureLayer* LayerForTesting(wtf_size_t index) const { return fragments_[index].Layer(); } diff --git a/chromium/third_party/blink/renderer/core/paint/list_marker_painter.cc b/chromium/third_party/blink/renderer/core/paint/list_marker_painter.cc index 0ead31d5967..f8695af86b6 100644 --- a/chromium/third_party/blink/renderer/core/paint/list_marker_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/list_marker_painter.cc @@ -8,7 +8,6 @@ #include "third_party/blink/renderer/core/layout/layout_list_item.h" #include "third_party/blink/renderer/core/layout/layout_list_marker.h" #include "third_party/blink/renderer/core/layout/list_marker.h" -#include "third_party/blink/renderer/core/layout/list_marker_text.h" #include "third_party/blink/renderer/core/paint/box_model_object_painter.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/highlight_painting_utils.h" @@ -86,8 +85,8 @@ void ListMarkerPainter::PaintSymbol(const PaintInfo& paint_info, const ComputedStyle& style, const LayoutRect& marker) { DCHECK(object); - DCHECK(style.GetListStyleType()); - DCHECK(style.GetListStyleType()->IsCounterStyle()); + DCHECK(style.ListStyleType()); + DCHECK(style.ListStyleType()->IsCounterStyle()); GraphicsContext& context = paint_info.context; ScopedDarkModeElementRoleOverride list_symbol( &context, DarkModeFilter::ElementRole::kListSymbol); @@ -101,7 +100,7 @@ void ListMarkerPainter::PaintSymbol(const PaintInfo& paint_info, context.SetStrokeStyle(kSolidStroke); context.SetStrokeThickness(1.0f); IntRect snapped_rect = PixelSnappedIntRect(marker); - const AtomicString& type = style.GetListStyleType()->GetCounterStyleName(); + const AtomicString& type = style.ListStyleType()->GetCounterStyleName(); if (type == "disc") { context.FillEllipse(FloatRect(snapped_rect)); } else if (type == "circle") { @@ -232,17 +231,9 @@ void ListMarkerPainter::Paint(const PaintInfo& paint_info) { String prefix_str; String suffix_str; - if (RuntimeEnabledFeatures::CSSAtRuleCounterStyleEnabled()) { - const CounterStyle& counter_style = layout_list_marker_.GetCounterStyle(); - prefix_str = counter_style.GetPrefix(); - suffix_str = counter_style.GetSuffix(); - } else { - UChar chars[] = { - list_marker_text::Suffix(layout_list_marker_.StyleRef().ListStyleType(), - layout_list_marker_.ListItem()->Value()), - ' '}; - suffix_str = String(chars, 2); - } + const CounterStyle& counter_style = layout_list_marker_.GetCounterStyle(); + prefix_str = counter_style.GetPrefix(); + suffix_str = counter_style.GetSuffix(); TextRun prefix_run = ConstructTextRun(font, prefix_str, layout_list_marker_.StyleRef(), layout_list_marker_.StyleRef().Direction()); diff --git a/chromium/third_party/blink/renderer/core/paint/multi_column_set_painter.cc b/chromium/third_party/blink/renderer/core/paint/multi_column_set_painter.cc index cfc9b9d21c7..043e55ae4c2 100644 --- a/chromium/third_party/blink/renderer/core/paint/multi_column_set_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/multi_column_set_painter.cc @@ -6,7 +6,7 @@ #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h" #include "third_party/blink/renderer/core/paint/block_painter.h" -#include "third_party/blink/renderer/core/paint/object_painter.h" +#include "third_party/blink/renderer/core/paint/box_border_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/platform/geometry/layout_point.h" #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" @@ -64,8 +64,8 @@ void MultiColumnSetPainter::PaintColumnRules( for (auto& bound : column_rule_bounds) { IntRect pixel_snapped_rule_rect = PixelSnappedIntRect(bound); - ObjectPainter::DrawBoxSide(paint_info.context, pixel_snapped_rule_rect, - box_side, rule_color, rule_style); + BoxBorderPainter::DrawBoxSide(paint_info.context, pixel_snapped_rule_rect, + box_side, rule_color, rule_style); } } diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc index 360f0a19e45..1642dea2ade 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h" #include "base/containers/adapters.h" +#include "third_party/blink/renderer/core/css/style_engine.h" #include "third_party/blink/renderer/core/editing/drag_caret.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/frame/local_frame.h" @@ -14,6 +15,7 @@ #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_table_cell.h" #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" +#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" @@ -21,8 +23,10 @@ #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" #include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" +#include "third_party/blink/renderer/core/layout/pointer_events_hit_rules.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/paint/background_image_geometry.h" +#include "third_party/blink/renderer/core/paint/box_border_painter.h" #include "third_party/blink/renderer/core/paint/box_decoration_data.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" @@ -31,6 +35,7 @@ #include "third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h" #include "third_party/blink/renderer/core/paint/ng/ng_mathml_painter.h" #include "third_party/blink/renderer/core/paint/ng/ng_table_painters.h" +#include "third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h" #include "third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h" #include "third_party/blink/renderer/core/paint/object_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" @@ -40,6 +45,7 @@ #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h" #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" +#include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.h" #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" #include "third_party/blink/renderer/core/paint/theme_painter.h" #include "third_party/blink/renderer/core/paint/url_metadata_utils.h" @@ -111,7 +117,21 @@ inline bool IsVisibleToHitTest(const ComputedStyle& style, inline bool IsVisibleToHitTest(const NGFragmentItem& item, const HitTestRequest& request) { const ComputedStyle& style = item.Style(); - return IsVisibleToPaint(item, style) && IsVisibleToHitTest(style, request); + if (item.Type() != NGFragmentItem::kSvgText) + return IsVisibleToPaint(item, style) && IsVisibleToHitTest(style, request); + + if (item.IsHiddenForPaint()) + return false; + PointerEventsHitRules hit_rules(PointerEventsHitRules::SVG_TEXT_HITTESTING, + request, style.PointerEvents()); + if (hit_rules.require_visible && style.Visibility() != EVisibility::kVisible) + return false; + if (hit_rules.can_hit_bounding_box || + (hit_rules.can_hit_stroke && + (style.HasStroke() || !hit_rules.require_stroke)) || + (hit_rules.can_hit_fill && (style.HasFill() || !hit_rules.require_fill))) + return IsVisibleToHitTest(style, request); + return false; } inline bool IsVisibleToHitTest(const NGPhysicalFragment& fragment, @@ -348,13 +368,13 @@ bool ShouldPaintCarets(const NGPhysicalBoxFragment& fragment) { } // anonymous namespace -PhysicalRect NGBoxFragmentPainter::SelfInkOverflow() const { +PhysicalRect NGBoxFragmentPainter::InkOverflowIncludingFilters() const { if (box_item_) return box_item_->SelfInkOverflow(); const NGPhysicalFragment& fragment = PhysicalFragment(); DCHECK(!fragment.IsInlineBox()); return To<LayoutBox>(fragment.GetLayoutObject()) - ->PhysicalSelfVisualOverflowRect(); + ->PhysicalVisualOverflowRectIncludingFilters(); } void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info) { @@ -375,8 +395,32 @@ void NGBoxFragmentPainter::PaintInternal(const PaintInfo& paint_info) { return; PaintInfo& info = paint_state.MutablePaintInfo(); - PhysicalOffset paint_offset = paint_state.PaintOffset(); - PaintPhase original_phase = info.phase; + const PhysicalOffset paint_offset = paint_state.PaintOffset(); + const PaintPhase original_phase = info.phase; + + // For text-combine-upright:all, we need to realize canvas here for scaling + // to fit text content in 1em and shear for "font-style: oblique -15deg". + absl::optional<DrawingRecorder> recorder; + absl::optional<GraphicsContextStateSaver> graphics_context_state_saver; + const auto* const text_combine = + DynamicTo<LayoutNGTextCombine>(box_fragment_.GetLayoutObject()); + if (UNLIKELY(text_combine)) { + if (text_combine->NeedsAffineTransformInPaint()) { + if (original_phase == PaintPhase::kForeground) + PaintCaretsIfNeeded(paint_state, paint_info, paint_offset); + if (!paint_info.context.InDrawingRecorder()) { + if (DrawingRecorder::UseCachedDrawingIfPossible( + paint_info.context, GetDisplayItemClient(), paint_info.phase)) + return; + recorder.emplace(paint_info.context, GetDisplayItemClient(), + paint_info.phase, + text_combine->VisualRectForPaint(paint_offset)); + } + graphics_context_state_saver.emplace(paint_info.context); + paint_info.context.ConcatCTM( + text_combine->ComputeAffineTransformForPaint(paint_offset)); + } + } ScopedPaintTimingDetectorBlockPaintHook scoped_paint_timing_detector_block_paint_hook; @@ -436,22 +480,9 @@ void NGBoxFragmentPainter::PaintInternal(const PaintInfo& paint_info) { // If the caret's node's fragment's containing block is this block, and // the paint action is PaintPhaseForeground, then paint the caret. - if (original_phase == PaintPhase::kForeground && - ShouldPaintCarets(box_fragment_)) { - // Apply overflow clip if needed. - // reveal-caret-of-multiline-contenteditable.html needs this. - // TDOO(yoisn): We should share this code with |BlockPainter::Paint()| - absl::optional<ScopedPaintChunkProperties> paint_chunk_properties; - if (const auto* fragment = paint_state.FragmentToPaint()) { - if (const auto* properties = fragment->PaintProperties()) { - if (const auto* overflow_clip = properties->OverflowClip()) { - paint_chunk_properties.emplace( - paint_info.context.GetPaintController(), *overflow_clip, - *box_fragment_.GetLayoutObject(), DisplayItem::kCaret); - } - } - } - PaintCarets(paint_info, paint_offset); + if (original_phase == PaintPhase::kForeground && LIKELY(!recorder)) { + DCHECK(!text_combine || !text_combine->NeedsAffineTransformInPaint()); + PaintCaretsIfNeeded(paint_state, paint_info, paint_offset); } if (ShouldPaintSelfOutline(original_phase)) { @@ -459,10 +490,28 @@ void NGBoxFragmentPainter::PaintInternal(const PaintInfo& paint_info) { PaintObject(info, paint_offset); } + if (UNLIKELY(text_combine) && + NGTextCombinePainter::ShouldPaint(*text_combine)) { + if (recorder) { + // Paint text decorations and emphasis marks without scaling and share. + DCHECK(text_combine->NeedsAffineTransformInPaint()); + graphics_context_state_saver->Restore(); + } else if (!paint_info.context.InDrawingRecorder()) { + if (DrawingRecorder::UseCachedDrawingIfPossible( + paint_info.context, GetDisplayItemClient(), paint_info.phase)) + return; + recorder.emplace(paint_info.context, GetDisplayItemClient(), + paint_info.phase, + text_combine->VisualRectForPaint(paint_offset)); + } + NGTextCombinePainter::Paint(info, paint_offset, *text_combine); + } + // We paint scrollbars after we painted other things, so that the scrollbars // will sit above them. info.phase = original_phase; if (box_fragment_.IsScrollContainer()) { + DCHECK(!text_combine); ScrollableAreaPainter(*PhysicalFragment().Layer()->GetScrollableArea()) .PaintOverflowControls(info, RoundedIntPoint(paint_offset)); } @@ -503,7 +552,7 @@ void NGBoxFragmentPainter::PaintObject( const PaintPhase paint_phase = paint_info.phase; const NGPhysicalBoxFragment& fragment = PhysicalFragment(); const ComputedStyle& style = fragment.Style(); - bool is_visible = IsVisibleToPaint(fragment, style); + const bool is_visible = IsVisibleToPaint(fragment, style); if (ShouldPaintSelfBlockBackground(paint_phase)) { if (is_visible) { PaintBoxDecorationBackground(paint_info, paint_offset, @@ -561,13 +610,6 @@ void NGBoxFragmentPainter::PaintObject( } } else if (!fragment.IsInlineFormattingContext()) { PaintBlockChildren(paint_info, paint_offset); - - if (is_visible && paint_info.phase == PaintPhase::kForeground && - box_fragment_.IsTableNG()) { - NGTablePainter(box_fragment_) - .PaintCollapsedBorders(paint_info, paint_offset, - VisualRect(paint_offset)); - } } } @@ -581,6 +623,15 @@ void NGBoxFragmentPainter::PaintObject( if (!is_visible) return; + + // Collapsed borders paint *after* children have painted their backgrounds. + if (box_fragment_.IsTableNG() && + paint_phase == PaintPhase::kDescendantBlockBackgroundsOnly) { + NGTablePainter(box_fragment_) + .PaintCollapsedBorders(paint_info, paint_offset, + VisualRect(paint_offset)); + } + if (ShouldPaintSelfOutline(paint_phase)) { if (NGOutlineUtils::HasPaintedOutline(style, fragment.GetNode())) { NGFragmentPainter(fragment, GetDisplayItemClient()) @@ -595,14 +646,32 @@ void NGBoxFragmentPainter::PaintObject( } } -void NGBoxFragmentPainter::PaintCarets(const PaintInfo& paint_info, - const PhysicalOffset& paint_offset) { - const NGPhysicalBoxFragment& fragment = PhysicalFragment(); - LocalFrame* frame = fragment.GetLayoutObject()->GetFrame(); - if (ShouldPaintCursorCaret(fragment)) +void NGBoxFragmentPainter::PaintCaretsIfNeeded( + const ScopedPaintState& paint_state, + const PaintInfo& paint_info, + const PhysicalOffset& paint_offset) { + if (!ShouldPaintCarets(box_fragment_)) + return; + + // Apply overflow clip if needed. + // reveal-caret-of-multiline-contenteditable.html needs this. + // TDOO(yoisn): We should share this code with |BlockPainter::Paint()| + absl::optional<ScopedPaintChunkProperties> paint_chunk_properties; + if (const auto* fragment = paint_state.FragmentToPaint()) { + if (const auto* properties = fragment->PaintProperties()) { + if (const auto* overflow_clip = properties->OverflowClip()) { + paint_chunk_properties.emplace( + paint_info.context.GetPaintController(), *overflow_clip, + *box_fragment_.GetLayoutObject(), DisplayItem::kCaret); + } + } + } + + LocalFrame* frame = box_fragment_.GetLayoutObject()->GetFrame(); + if (ShouldPaintCursorCaret(box_fragment_)) frame->Selection().PaintCaret(paint_info.context, paint_offset); - if (ShouldPaintDragCaret(fragment)) { + if (ShouldPaintDragCaret(box_fragment_)) { frame->GetPage()->GetDragCaret().PaintDragCaret(frame, paint_info.context, paint_offset); } @@ -652,6 +721,11 @@ void NGBoxFragmentPainter::PaintBlockFlowContents( DCHECK(items_); NGInlineCursor children(fragment, *items_); + if (fragment.IsSvgText()) { + ScopedSVGPaintState paint_state(*fragment.GetLayoutObject(), paint_info); + PaintLineBoxChildren(&children, paint_info.ForDescendants(), paint_offset); + return; + } PaintLineBoxChildren(&children, paint_info.ForDescendants(), paint_offset); } @@ -659,63 +733,74 @@ void NGBoxFragmentPainter::PaintBlockChildren(const PaintInfo& paint_info, PhysicalOffset paint_offset) { DCHECK(!box_fragment_.IsInlineFormattingContext()); PaintInfo paint_info_for_descendants = paint_info.ForDescendants(); + paint_info_for_descendants.SetIsInFragmentTraversal(); for (const NGLink& child : box_fragment_.Children()) { const NGPhysicalFragment& child_fragment = *child; DCHECK(child_fragment.IsBox()); if (child_fragment.HasSelfPaintingLayer() || child_fragment.IsFloating()) continue; + PaintBlockChild(child, paint_info, paint_info_for_descendants, + paint_offset); + } +} - const auto& box_child_fragment = To<NGPhysicalBoxFragment>(child_fragment); - if (box_child_fragment.CanTraverse()) { - if (!box_child_fragment.GetLayoutObject()) { - // It's normally FragmentData that provides us with the paint offset. - // FragmentData is (at least currently) associated with a LayoutObject. - // If we have no LayoutObject, we have no FragmentData, so we need to - // calculate the offset on our own (which is very simple, anyway). - // Bypass Paint() and jump directly to PaintObject(), to skip the code - // that assumes that we have a LayoutObject (and FragmentData). - PhysicalOffset child_offset = paint_offset + child.offset; - - if (box_child_fragment.IsFragmentainerBox()) { - // This is a fragmentainer, and when node inside a fragmentation - // context paints multiple block fragments, we need to distinguish - // between them somehow, for paint caching to work. Therefore, - // establish a display item scope here. - unsigned identifier = - FragmentainerUniqueIdentifier(box_child_fragment); - ScopedDisplayItemFragment scope(paint_info.context, identifier); - NGBoxFragmentPainter(box_child_fragment) - .PaintObject(paint_info, child_offset); - continue; - } - +void NGBoxFragmentPainter::PaintBlockChild( + const NGLink& child, + const PaintInfo& paint_info, + const PaintInfo& paint_info_for_descendants, + PhysicalOffset paint_offset) { + const NGPhysicalFragment& child_fragment = *child; + DCHECK(child_fragment.IsBox()); + DCHECK(!child_fragment.HasSelfPaintingLayer()); + DCHECK(!child_fragment.IsFloating()); + const auto& box_child_fragment = To<NGPhysicalBoxFragment>(child_fragment); + if (box_child_fragment.CanTraverse()) { + if (!box_child_fragment.GetLayoutObject()) { + // It's normally FragmentData that provides us with the paint offset. + // FragmentData is (at least currently) associated with a LayoutObject. + // If we have no LayoutObject, we have no FragmentData, so we need to + // calculate the offset on our own (which is very simple, anyway). + // Bypass Paint() and jump directly to PaintObject(), to skip the code + // that assumes that we have a LayoutObject (and FragmentData). + PhysicalOffset child_offset = paint_offset + child.offset; + + if (box_child_fragment.IsFragmentainerBox()) { + // This is a fragmentainer, and when node inside a fragmentation + // context paints multiple block fragments, we need to distinguish + // between them somehow, for paint caching to work. Therefore, + // establish a display item scope here. + unsigned identifier = FragmentainerUniqueIdentifier(box_child_fragment); + ScopedDisplayItemFragment scope(paint_info.context, identifier); NGBoxFragmentPainter(box_child_fragment) .PaintObject(paint_info, child_offset); - continue; + return; } NGBoxFragmentPainter(box_child_fragment) - .Paint(paint_info_for_descendants); - continue; + .PaintObject(paint_info, child_offset); + return; } - // Fall back to flow-thread painting when reaching a column (the flow thread - // is treated as a self-painting PaintLayer when fragment traversal is - // disabled, so nothing to do here). - if (box_child_fragment.IsColumnBox()) - continue; + NGBoxFragmentPainter(box_child_fragment).Paint(paint_info_for_descendants); + return; + } - auto* layout_object = child_fragment.GetLayoutObject(); - DCHECK(layout_object); - if (child_fragment.IsPaintedAtomically() && - child_fragment.IsLegacyLayoutRoot()) { - ObjectPainter(*layout_object) - .PaintAllPhasesAtomically(paint_info_for_descendants); - } else { - // TODO(ikilpatrick): Once FragmentItem ships we should call the - // NGBoxFragmentPainter directly for NG objects. - layout_object->Paint(paint_info_for_descendants); - } + // Fall back to flow-thread painting when reaching a column (the flow thread + // is treated as a self-painting PaintLayer when fragment traversal is + // disabled, so nothing to do here). + if (box_child_fragment.IsColumnBox()) + return; + + auto* layout_object = child_fragment.GetLayoutObject(); + DCHECK(layout_object); + if (child_fragment.IsPaintedAtomically() && + child_fragment.IsLegacyLayoutRoot()) { + ObjectPainter(*layout_object) + .PaintAllPhasesAtomically(paint_info_for_descendants); + } else { + // TODO(ikilpatrick): Once FragmentItem ships we should call the + // NGBoxFragmentPainter directly for NG objects. + layout_object->Paint(paint_info_for_descendants); } } @@ -1128,6 +1213,20 @@ void NGBoxFragmentPainter::PaintBoxDecorationBackgroundWithRectImpl( paint_info.context.EndLayer(); } +void NGBoxFragmentPainter::PaintBoxDecorationBackgroundForBlockInInline( + NGInlineCursor* children, + const PaintInfo& paint_info, + const PhysicalOffset& paint_offset) { + for (; *children; children->MoveToNext()) { + const NGFragmentItem* item = children->Current().Item(); + if (item->Type() != NGFragmentItem::kBox) + continue; + const NGPhysicalBoxFragment* fragment = item->BoxFragment(); + if (fragment && fragment->IsBlockInInline()) + PaintBoxItem(*item, *fragment, *children, paint_info, paint_offset); + } +} + void NGBoxFragmentPainter::PaintColumnRules( const PaintInfo& paint_info, const PhysicalOffset& paint_offset) { @@ -1218,8 +1317,8 @@ void NGBoxFragmentPainter::PaintColumnRules( rule.Move(paint_offset); IntRect snapped_rule = PixelSnappedIntRect(rule); - ObjectPainter::DrawBoxSide(paint_info.context, snapped_rule, box_side, - rule_color, rule_style); + BoxBorderPainter::DrawBoxSide(paint_info.context, snapped_rule, box_side, + rule_color, rule_style); recorder.UniteVisualRect(snapped_rule); previous_column = current_column; @@ -1312,7 +1411,7 @@ void NGBoxFragmentPainter::PaintInlineItems(const PaintInfo& paint_info, } switch (item->Type()) { case NGFragmentItem::kText: - case NGFragmentItem::kSVGText: + case NGFragmentItem::kSvgText: case NGFragmentItem::kGeneratedText: if (!item->IsHiddenForPaint()) PaintTextItem(*cursor, paint_info, paint_offset, parent_offset); @@ -1375,8 +1474,14 @@ void NGBoxFragmentPainter::PaintLineBoxChildren( paint_info.phase != PaintPhase::kTextClip && paint_info.phase != PaintPhase::kMask && paint_info.phase != PaintPhase::kDescendantOutlinesOnly && - paint_info.phase != PaintPhase::kOutline) + paint_info.phase != PaintPhase::kOutline) { + if (UNLIKELY(ShouldPaintDescendantBlockBackgrounds(paint_info.phase))) { + // When block-in-inline, block backgrounds need to be painted. + PaintBoxDecorationBackgroundForBlockInInline(children, paint_info, + paint_offset); + } return; + } // The only way an inline could paint like this is if it has a layer. const auto* layout_object = box_fragment_.GetLayoutObject(); @@ -1556,9 +1661,19 @@ void NGBoxFragmentPainter::PaintBoxItem( return; } - DCHECK(child_fragment.IsInlineBox()); - NGInlineBoxFragmentPainter(cursor, item, child_fragment) - .Paint(paint_info, paint_offset); + if (child_fragment.IsInlineBox()) { + NGInlineBoxFragmentPainter(cursor, item, child_fragment) + .Paint(paint_info, paint_offset); + return; + } + + // Block-in-inline + DCHECK(RuntimeEnabledFeatures::LayoutNGBlockInInlineEnabled()); + DCHECK(!child_fragment.GetLayoutObject()->IsInline()); + PaintInfo paint_info_for_descendants = paint_info.ForDescendants(); + paint_info_for_descendants.SetIsInFragmentTraversal(); + PaintBlockChild({&child_fragment, item.OffsetInContainerFragment()}, + paint_info, paint_info_for_descendants, paint_offset); } void NGBoxFragmentPainter::PaintBoxItem(const NGFragmentItem& item, @@ -1616,14 +1731,15 @@ bool NGBoxFragmentPainter::ShouldPaint( return false; } -void NGBoxFragmentPainter::PaintTextClipMask(GraphicsContext& context, +void NGBoxFragmentPainter::PaintTextClipMask(const PaintInfo& paint_info, const IntRect& mask_rect, const PhysicalOffset& paint_offset, bool object_has_multiple_boxes) { - PaintInfo paint_info(context, CullRect(mask_rect), PaintPhase::kTextClip, - kGlobalPaintNormalPhase, 0); + PaintInfo mask_paint_info(paint_info.context, CullRect(mask_rect), + PaintPhase::kTextClip, kGlobalPaintNormalPhase, 0); + mask_paint_info.SetFragmentID(paint_info.FragmentID()); if (!object_has_multiple_boxes) { - PaintObject(paint_info, paint_offset); + PaintObject(mask_paint_info, paint_offset); return; } @@ -1631,7 +1747,7 @@ void NGBoxFragmentPainter::PaintTextClipMask(GraphicsContext& context, DCHECK(box_item_); NGInlineBoxFragmentPainter inline_box_painter(*inline_box_cursor_, *box_item_); - PaintTextClipMask(paint_info, + PaintTextClipMask(mask_paint_info, paint_offset - box_item_->OffsetInContainerFragment(), &inline_box_painter); } @@ -1669,8 +1785,8 @@ PhysicalRect NGBoxFragmentPainter::AdjustRectForScrolledContent( // Adjust the paint rect to reflect a scrolled content box with borders at // the ends. - PhysicalOffset offset(physical.PixelSnappedScrolledContentOffset()); - scrolled_paint_rect.Move(-offset); + scrolled_paint_rect.offset -= + PhysicalOffset(physical.PixelSnappedScrolledContentOffset()); LayoutRectOutsets borders = AdjustedBorderOutsets(info); scrolled_paint_rect.size = physical.ScrollSize() + PhysicalSize(borders.Size()); @@ -1708,10 +1824,11 @@ BoxPainterBase::FillLayerInfo NGBoxFragmentPainter::GetFillLayerInfo( is_painting_scrolling_background); } +template <typename T> bool NGBoxFragmentPainter::HitTestContext::AddNodeToResult( Node* node, const NGPhysicalBoxFragment* box_fragment, - const PhysicalRect& bounds_rect, + const T& bounds_rect, const PhysicalOffset& offset) const { if (node && !result->InnerNode()) result->SetNodeAndPosition(node, box_fragment, location.Point() - offset); @@ -1719,10 +1836,11 @@ bool NGBoxFragmentPainter::HitTestContext::AddNodeToResult( kStopHitTesting; } +template <typename T> bool NGBoxFragmentPainter::HitTestContext::AddNodeToResultWithContentOffset( Node* node, const NGPhysicalBoxFragment& container, - const PhysicalRect& bounds_rect, + const T& bounds_rect, PhysicalOffset offset) const { if (container.IsScrollContainer()) offset += PhysicalOffset(container.PixelSnappedScrolledContentOffset()); @@ -1754,12 +1872,19 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test, physical_offset)) return false; + bool pointer_events_bounding_box = false; bool hit_test_self = fragment.IsInSelfHitTestingPhase(hit_test.action); if (hit_test_self) { // Table row and table section are never a hit target. + // SVG <text> is not a hit target except if 'pointer-events: bounding-box'. if (PhysicalFragment().IsTableNGRow() || - PhysicalFragment().IsTableNGSection()) + PhysicalFragment().IsTableNGSection()) { hit_test_self = false; + } else if (fragment.IsSvgText()) { + pointer_events_bounding_box = + fragment.Style().PointerEvents() == EPointerEvents::kBoundingBox; + hit_test_self = pointer_events_bounding_box; + } } if (hit_test_self && box_fragment_.IsScrollContainer() && @@ -1813,11 +1938,20 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test, if (hit_test_self && IsVisibleToHitTest(box_fragment_, hit_test.result->GetHitTestRequest())) { PhysicalRect bounds_rect(physical_offset, size); - if (UNLIKELY(hit_test.result->GetHitTestRequest().GetType() & - HitTestRequest::kHitTestVisualOverflow)) { - bounds_rect = SelfInkOverflow(); + if (UNLIKELY( + hit_test.result->GetHitTestRequest().IsHitTestVisualOverflow())) { + // We'll include overflow from children here (in addition to self-overflow + // caused by filters), because we want to record a match if we hit the + // overflow of a child below the stop node. This matches legacy behavior + // in LayoutBox::NodeAtPoint(); see call to + // PhysicalVisualOverflowRectIncludingFilters(). + bounds_rect = InkOverflowIncludingFilters(); bounds_rect.Move(physical_offset); } + if (UNLIKELY(pointer_events_bounding_box)) { + bounds_rect = PhysicalRect::EnclosingRect( + PhysicalFragment().GetLayoutObject()->ObjectBoundingBox()); + } // TODO(kojii): Don't have good explanation why only inline box needs to // snap, but matches to legacy and fixes crbug.com/976606. if (fragment.IsInlineBox()) @@ -1891,16 +2025,27 @@ bool NGBoxFragmentPainter::HitTestTextItem( if (!IsVisibleToHitTest(text_item, hit_test.result->GetHitTestRequest())) return false; - // TODO(layout-dev): Clip to line-top/bottom. - const PhysicalOffset offset = - hit_test.inline_root_offset + text_item.OffsetInContainerFragment(); - PhysicalRect border_rect(offset, text_item.Size()); - PhysicalRect rect(PixelSnappedIntRect(border_rect)); - if (UNLIKELY(hit_test.result->GetHitTestRequest().GetType() & - HitTestRequest::kHitTestVisualOverflow)) { - rect = text_item.SelfInkOverflow(); - rect.Move(border_rect.offset); + if (text_item.Type() == NGFragmentItem::kSvgText && + text_item.HasSvgTransformForBoundingBox()) { + const FloatQuad quad = text_item.SvgUnscaledQuad(); + if (!hit_test.location.Intersects(quad)) + return false; + return hit_test.AddNodeToResultWithContentOffset( + text_item.NodeForHitTest(), cursor.ContainerFragment(), quad, + hit_test.inline_root_offset); } + + const auto* const text_combine = + DynamicTo<LayoutNGTextCombine>(box_fragment_.GetLayoutObject()); + + // TODO(layout-dev): Clip to line-top/bottom. + const PhysicalRect rect = + UNLIKELY(text_combine) + ? text_combine->ComputeTextBoundsRectForHitTest( + text_item, hit_test.inline_root_offset) + : text_item.ComputeTextBoundsRectForHitTest( + hit_test.inline_root_offset, + hit_test.result->GetHitTestRequest().IsHitTestVisualOverflow()); if (!hit_test.location.Intersects(rect)) return false; @@ -1943,6 +2088,9 @@ bool NGBoxFragmentPainter::HitTestLineBoxFragment( bounds_rect))) return false; + if (cursor.ContainerFragment().IsSvgText()) + return false; + // Now hit test ourselves. if (!hit_test.location.Intersects(bounds_rect)) return false; @@ -2033,14 +2181,18 @@ bool NGBoxFragmentPainter::HitTestChildBoxItem( return true; } + if (cursor.ContainerFragment().IsSvgText() && + item.Style().PointerEvents() != EPointerEvents::kBoundingBox) + return false; + // Now hit test ourselves. if (hit_test.action == kHitTestForeground && IsVisibleToHitTest(item, hit_test.result->GetHitTestRequest())) { const PhysicalOffset child_offset = hit_test.inline_root_offset + item.OffsetInContainerFragment(); PhysicalRect bounds_rect(child_offset, item.Size()); - if (UNLIKELY(hit_test.result->GetHitTestRequest().GetType() & - HitTestRequest::kHitTestVisualOverflow)) { + if (UNLIKELY( + hit_test.result->GetHitTestRequest().IsHitTestVisualOverflow())) { bounds_rect = item.SelfInkOverflow(); bounds_rect.Move(child_offset); } diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h index 1d7e9b305de..0338e7ada07 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h @@ -82,7 +82,7 @@ class CORE_EXPORT NGBoxFragmentPainter : public BoxPainterBase { bool is_painting_scrolling_background) const override; bool IsPaintingScrollingBackground(const PaintInfo&) const override; - void PaintTextClipMask(GraphicsContext&, + void PaintTextClipMask(const PaintInfo&, const IntRect& mask_rect, const PhysicalOffset& paint_offset, bool object_has_multiple_boxes) override; @@ -115,11 +115,20 @@ class CORE_EXPORT NGBoxFragmentPainter : public BoxPainterBase { const PhysicalRect& paint_rect, const DisplayItemClient&); + void PaintBoxDecorationBackgroundForBlockInInline( + NGInlineCursor* children, + const PaintInfo&, + const PhysicalOffset& paint_offset); + void PaintColumnRules(const PaintInfo&, const PhysicalOffset& paint_offset); void PaintInternal(const PaintInfo&); void PaintAllPhasesAtomically(const PaintInfo&); void PaintBlockChildren(const PaintInfo&, PhysicalOffset); + void PaintBlockChild(const NGLink& child, + const PaintInfo& paint_info, + const PaintInfo& paint_info_for_descendants, + PhysicalOffset paint_offset); void PaintInlineItems(const PaintInfo&, const PhysicalOffset& paint_offset, const PhysicalOffset& parent_offset, @@ -167,7 +176,9 @@ class CORE_EXPORT NGBoxFragmentPainter : public BoxPainterBase { const PhysicalRect&, const Color& background_color, BackgroundBleedAvoidance = kBackgroundBleedNone); - void PaintCarets(const PaintInfo&, const PhysicalOffset& paint_offset); + void PaintCaretsIfNeeded(const ScopedPaintState&, + const PaintInfo&, + const PhysicalOffset& paint_offset); // This should be called in the background paint phase even if there is no // other painted content. @@ -193,17 +204,21 @@ class CORE_EXPORT NGBoxFragmentPainter : public BoxPainterBase { // Add |node| to |HitTestResult|. Returns true if the hit-testing should // stop. + // T is PhysicalRect or FloatQuad. + template <typename T> bool AddNodeToResult(Node* node, const NGPhysicalBoxFragment* box_fragment, - const PhysicalRect& bounds_rect, + const T& bounds_rect, const PhysicalOffset& offset) const; // Same as |AddNodeToResult|, except that |offset| is in the content // coordinate system rather than the container coordinate system. They // differ when |container| is a scroll container. + // T is PhysicalRect or FloatQuad. + template <typename T> bool AddNodeToResultWithContentOffset( Node* node, const NGPhysicalBoxFragment& container, - const PhysicalRect& bounds_rect, + const T& bounds_rect, PhysicalOffset offset) const; HitTestAction action; @@ -288,7 +303,7 @@ class CORE_EXPORT NGBoxFragmentPainter : public BoxPainterBase { const DisplayItemClient& GetDisplayItemClient() const { return display_item_client_; } - PhysicalRect SelfInkOverflow() const; + PhysicalRect InkOverflowIncludingFilters() const; const NGPhysicalBoxFragment& box_fragment_; const DisplayItemClient& display_item_client_; @@ -315,8 +330,6 @@ inline NGBoxFragmentPainter::NGBoxFragmentPainter( DCHECK_EQ(inline_box_cursor_->Current().Item(), box_item_); if (box_item_) DCHECK_EQ(box_item_->BoxFragment(), &box); - DCHECK_EQ(box.IsInlineBox(), !!inline_box_cursor_); - DCHECK_EQ(box.IsInlineBox(), !!box_item_); #endif } @@ -336,7 +349,6 @@ inline NGBoxFragmentPainter::NGBoxFragmentPainter( &inline_box_cursor, &item) { DCHECK_EQ(item.BoxFragment(), &fragment); - DCHECK(fragment.IsInlineBox()); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc index 4ad80e25d81..9d9bfd812c1 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc @@ -200,4 +200,26 @@ TEST_P(NGBoxFragmentPainterTest, SelectionTablePainting) { auto record = builder.EndRecording(); } +TEST_P(NGBoxFragmentPainterTest, ClippedText) { + SetBodyInnerHTML(R"HTML( + <div id="target" style="overflow: hidden; position: relative; + width: 100px; height: 100px"> + A<br>B<br>C<br>D + </div> + )HTML"); + // Initially all the texts are painted. + auto num_all_display_items = ContentDisplayItems().size(); + auto* target = GetDocument().getElementById("target"); + + target->SetInlineStyleProperty(CSSPropertyID::kHeight, "0px"); + UpdateAllLifecyclePhasesForTest(); + // None of the texts should be painted. + EXPECT_EQ(num_all_display_items - 4, ContentDisplayItems().size()); + + target->SetInlineStyleProperty(CSSPropertyID::kHeight, "1px"); + UpdateAllLifecyclePhasesForTest(); + // Only "A" should be painted. + EXPECT_EQ(num_all_display_items - 3, ContentDisplayItems().size()); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc index dd8d402df61..d8e252149e5 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h" #include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h" +#include "third_party/blink/renderer/core/paint/outline_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" @@ -35,7 +36,8 @@ void NGFragmentPainter::PaintOutline(const PaintInfo& paint_info, visual_rect.Inflate(style_to_use.OutlineOutsetExtent()); DrawingRecorder recorder(paint_info.context, display_item_client, paint_info.phase, visual_rect); - PaintOutlineRects(paint_info, outline_rects, style_to_use); + OutlinePainter::PaintOutlineRects(paint_info.context, outline_rects, + style_to_use); } void NGFragmentPainter::AddURLRectIfNeeded(const PaintInfo& paint_info, diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h index 679d9aad40e..b62e6145a01 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h @@ -6,7 +6,6 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_FRAGMENT_PAINTER_H_ #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" -#include "third_party/blink/renderer/core/paint/object_painter_base.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h" namespace blink { @@ -15,8 +14,8 @@ struct PaintInfo; struct PhysicalOffset; // Generic fragment painter for paint logic shared between all types of -// fragments. LayoutNG version of ObjectPainter, based on ObjectPainterBase. -class NGFragmentPainter : public ObjectPainterBase { +// fragments. LayoutNG version of ObjectPainter. +class NGFragmentPainter { STACK_ALLOCATED(); public: diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.cc index ac1af42b6b1..6ef75fc91e1 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.cc @@ -8,9 +8,11 @@ #include "third_party/blink/renderer/core/editing/editor.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" +#include "third_party/blink/renderer/core/editing/markers/highlight_marker.h" #include "third_party/blink/renderer/core/editing/markers/styleable_marker.h" #include "third_party/blink/renderer/core/editing/markers/text_marker_base.h" #include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/highlight/highlight.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/paint/document_marker_painter.h" @@ -18,6 +20,7 @@ #include "third_party/blink/renderer/core/paint/inline_text_box_painter.h" #include "third_party/blink/renderer/core/paint/ng/ng_text_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" +#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" namespace blink { @@ -110,8 +113,11 @@ Color SelectionBackgroundColor(const Document& document, // If the text color ends up being the same as the selection background, // invert the selection background. - if (text_color == color) + if (text_color == color) { + UseCounter::Count(node->GetDocument(), + WebFeature::kSelectionBackgroundColorInversion); return Color(0xff - color.Red(), 0xff - color.Green(), 0xff - color.Blue()); + } return color; } @@ -231,15 +237,14 @@ void NGHighlightPainter::SelectionPaintState:: } } -NGHighlightPainter::NGHighlightPainter( - NGTextPainter& text_painter, - const PaintInfo& paint_info, - const NGInlineCursor& cursor, - const NGFragmentItem& fragment_item, - const PhysicalOffset& box_origin, - const ComputedStyle& style, - absl::optional<SelectionPaintState> selection, - bool is_printing) +NGHighlightPainter::NGHighlightPainter(NGTextPainter& text_painter, + const PaintInfo& paint_info, + const NGInlineCursor& cursor, + const NGFragmentItem& fragment_item, + const PhysicalOffset& box_origin, + const ComputedStyle& style, + SelectionPaintState* selection, + bool is_printing) : text_painter_(text_painter), paint_info_(paint_info), cursor_(cursor), @@ -301,8 +306,7 @@ void NGHighlightPainter::Paint(Phase phase) { Color color; if (marker->GetType() == DocumentMarker::kTextMatch) { color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor( - text_marker.IsActiveMatch(), document.InForcedColorsMode(), - style_.UsedColorScheme()); + text_marker.IsActiveMatch(), style_.UsedColorScheme()); } else { color = HighlightPaintingUtils::HighlightBackgroundColor( document, style_, node_, kPseudoIdTargetText); @@ -347,6 +351,49 @@ void NGHighlightPainter::Paint(Phase phase) { } } break; + case DocumentMarker::kHighlight: { + const auto& highlight_marker = To<HighlightMarker>(*marker); + const Document& document = node_->GetDocument(); + + // Paint background + if (phase == kBackground) { + Color background_color = + HighlightPaintingUtils::HighlightBackgroundColor( + document, style_, node_, kPseudoIdHighlight, + highlight_marker.GetHighlightName()); + + PaintRect(paint_info_.context, PhysicalOffset(box_origin_), + fragment_item_.LocalRect(text, paint_start_offset, + paint_end_offset), + background_color); + break; + } + + DCHECK_EQ(phase, kForeground); + Color text_color = style_.VisitedDependentColor(GetCSSPropertyColor()); + + TextPaintStyle text_style; + text_style.current_color = text_style.fill_color = + text_style.stroke_color = text_style.emphasis_mark_color = + text_color; + text_style.stroke_width = style_.TextStrokeWidth(); + text_style.color_scheme = style_.UsedColorScheme(); + text_style.shadow = nullptr; + + const TextPaintStyle final_text_style = + HighlightPaintingUtils::HighlightPaintingStyle( + document, style_, node_, kPseudoIdHighlight, text_style, + paint_info_, highlight_marker.GetHighlightName()); + + if (final_text_style.current_color == Color::kTransparent) + break; + + text_painter_.Paint(paint_start_offset, paint_end_offset, + paint_end_offset - paint_start_offset, + final_text_style, kInvalidDOMNodeId); + + } break; + default: NOTREACHED(); break; diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.h index d01ec5ff291..69185992a54 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_highlight_painter.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_HIGHLIGHT_PAINTER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_HIGHLIGHT_PAINTER_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/editing/markers/document_marker.h" @@ -98,13 +99,13 @@ class CORE_EXPORT NGHighlightPainter { const NGFragmentItem& fragment_item, const PhysicalOffset& box_origin, const ComputedStyle& style, - absl::optional<SelectionPaintState>, + SelectionPaintState*, bool is_printing); enum Phase { kBackground, kForeground }; void Paint(Phase phase); - absl::optional<SelectionPaintState>& Selection() { return selection_; } + SelectionPaintState* Selection() { return selection_; } absl::optional<AppliedTextDecoration> SelectionDecoration() { return selection_ ? selection_->GetSelectionStyle().selection_text_decoration @@ -118,7 +119,7 @@ class CORE_EXPORT NGHighlightPainter { const NGFragmentItem& fragment_item_; const PhysicalOffset& box_origin_; const ComputedStyle& style_; - absl::optional<SelectionPaintState> selection_; + SelectionPaintState* selection_; const LayoutObject* layout_object_; Node* node_; const DocumentMarkerVector markers_; diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc index acddffe60bd..e08bf386fb9 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc @@ -12,6 +12,7 @@ #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_phase.h" +#include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.h" #include "third_party/blink/renderer/core/style/nine_piece_image.h" #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" @@ -50,10 +51,15 @@ void NGInlineBoxFragmentPainter::Paint(const PaintInfo& paint_info, const PhysicalOffset& paint_offset) { ScopedDisplayItemFragment display_item_fragment( paint_info.context, inline_box_item_.FragmentId()); + const LayoutObject& layout_object = *inline_box_fragment_.GetLayoutObject(); + absl::optional<ScopedSVGPaintState> svg_paint_state; + if (layout_object.IsSVGInline()) + svg_paint_state.emplace(layout_object, paint_info); const PhysicalOffset adjusted_paint_offset = paint_offset + inline_box_item_.OffsetInContainerFragment(); - if (paint_info.phase == PaintPhase::kForeground) + if (paint_info.phase == PaintPhase::kForeground && + !layout_object.IsSVGInline()) PaintBackgroundBorderShadow(paint_info, adjusted_paint_offset); const bool suppress_box_decoration_background = true; @@ -68,7 +74,8 @@ void NGInlineBoxFragmentPainterBase::PaintBackgroundBorderShadow( const PaintInfo& paint_info, const PhysicalOffset& paint_offset) { DCHECK(paint_info.phase == PaintPhase::kForeground); - if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible) + if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible || + inline_box_fragment_.IsOpaque()) return; // You can use p::first-line to specify a background. If so, the direct child diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h index 1a0fefb2448..a3a79d8f6a7 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h @@ -6,6 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_INLINE_BOX_FRAGMENT_PAINTER_H_ #include "base/dcheck_is_on.h" +#include "third_party/blink/renderer/core/layout/layout_object_inlines.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" #include "third_party/blink/renderer/core/paint/inline_box_painter_base.h" diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_mathml_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_mathml_painter.cc index d764b2f2567..fbffad0c60b 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_mathml_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_mathml_painter.cc @@ -79,6 +79,17 @@ void NGMathMLPainter::PaintOperator(const PaintInfo& info, auto padding = box_fragment_.Padding(); physical_offset.left += borders.left + padding.left; physical_offset.top += borders.top + padding.top; + + // TODO(http://crbug.com/1124301): NGMathOperatorLayoutAlgorithm::Layout + // passes the operator's inline size but this does not match the width of the + // box fragment, which relies on the min-max sizes instead. Shift the paint + // offset to work around that issue, splitting the size error symmetrically. + DCHECK(box_fragment_.Style().IsHorizontalWritingMode()); + physical_offset.left += + (box_fragment_.Size().width - borders.HorizontalSum() - + padding.HorizontalSum() - parameters.operator_inline_size) / + 2; + PaintStretchyOrLargeOperator(info, paint_offset + physical_offset); } diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_table_painters.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_table_painters.cc index e44355ff1e8..53bca621ece 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_table_painters.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_table_painters.cc @@ -13,11 +13,11 @@ #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column.h" #include "third_party/blink/renderer/core/layout/ng/table/ng_table_borders.h" #include "third_party/blink/renderer/core/paint/background_image_geometry.h" +#include "third_party/blink/renderer/core/paint/box_border_painter.h" #include "third_party/blink/renderer/core/paint/box_decoration_data.h" #include "third_party/blink/renderer/core/paint/box_model_object_painter.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h" -#include "third_party/blink/renderer/core/paint/object_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" @@ -53,6 +53,16 @@ class NGTableCollapsedEdge { InitCachedProps(); } + NGTableCollapsedEdge(const NGTableCollapsedEdge& edge) + : NGTableCollapsedEdge(edge, 0) {} + + NGTableCollapsedEdge& operator=(const NGTableCollapsedEdge& edge) { + edge_index_ = edge.edge_index_; + border_width_ = edge.border_width_; + border_style_ = edge.border_style_; + return *this; + } + bool Exists() const { return edge_index_ != UINT_MAX; } bool CanPaint() const { @@ -206,13 +216,6 @@ class NGTableCollapsedEdge { return !(*this == rhs); } - NGTableCollapsedEdge& operator=(const NGTableCollapsedEdge& edge) { - edge_index_ = edge.edge_index_; - border_width_ = edge.border_width_; - border_style_ = edge.border_style_; - return *this; - } - private: void InitCachedProps() { if (edge_index_ == UINT_MAX) { @@ -420,7 +423,6 @@ void NGTablePainter::PaintBoxDecorationBackground( void NGTablePainter::PaintCollapsedBorders(const PaintInfo& paint_info, const PhysicalOffset& paint_offset, const IntRect& visual_rect) { - DCHECK_EQ(paint_info.phase, PaintPhase::kForeground); const NGTableBorders* collapsed_borders = fragment_.TableCollapsedBorders(); if (!collapsed_borders) return; @@ -525,7 +527,7 @@ void NGTablePainter::PaintCollapsedBorders(const PaintInfo& paint_info, } else { box_side = edge.IsInlineAxis() ? BoxSide::kLeft : BoxSide::kTop; } - ObjectPainter::DrawBoxSide( + BoxBorderPainter::DrawBoxSide( paint_info.context, PixelSnappedIntRect(physical_border_rect), box_side, edge.BorderColor(), edge.BorderStyle()); } diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.cc new file mode 100644 index 00000000000..82a618e8a1e --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.cc @@ -0,0 +1,127 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h" + +#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h" +#include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h" +#include "third_party/blink/renderer/core/paint/paint_info.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/graphics/graphics_context.h" +#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" + +namespace blink { + +NGTextCombinePainter::NGTextCombinePainter(GraphicsContext& context, + const ComputedStyle& style, + const PhysicalRect& text_frame_rect) + : TextPainterBase(context, + style.GetFont(), + text_frame_rect.offset, + text_frame_rect, + /* horizontal */ false), + style_(style) {} + +NGTextCombinePainter::~NGTextCombinePainter() = default; + +void NGTextCombinePainter::Paint(const PaintInfo& paint_info, + const PhysicalOffset& paint_offset, + const LayoutNGTextCombine& text_combine) { + if (paint_info.phase == PaintPhase::kBlockBackground || + paint_info.phase == PaintPhase::kForcedColorsModeBackplate || + paint_info.phase == PaintPhase::kFloat || + paint_info.phase == PaintPhase::kSelfBlockBackgroundOnly || + paint_info.phase == PaintPhase::kDescendantBlockBackgroundsOnly || + paint_info.phase == PaintPhase::kSelfOutlineOnly) { + // Note: We should not paint text decoration and emphasis markr in above + // paint phases. Otherwise, text decoration and emphasis mark are painted + // multiple time and anti-aliasing is broken. + // See virtual/text-antialias/emphasis-combined-text.html + return; + } + + // Here |paint_info.phases| is one of following: + // PaintPhase::kSelectionDragImage + // PaintPhase::kTextClip + // PaintPhase::kForeground + // PaintPhase::kOutline + // These values come from |NGBoxFragmentPainter::PaintAllPhasesAtomically()|. + + const ComputedStyle& style = text_combine.Parent()->StyleRef(); + const bool has_text_decoration = + style.TextDecorationsInEffect() != TextDecoration::kNone; + const bool has_emphasis_mark = + style.GetTextEmphasisMark() != TextEmphasisMark::kNone; + DCHECK(has_text_decoration | has_emphasis_mark); + + const PhysicalRect& text_frame_rect = + text_combine.ComputeTextFrameRect(paint_offset); + + // To match the logical direction + GraphicsContextStateSaver state_saver(paint_info.context); + paint_info.context.ConcatCTM( + TextPainterBase::Rotation(text_frame_rect, style.GetWritingMode())); + + NGTextCombinePainter text_painter(paint_info.context, style, text_frame_rect); + const TextPaintStyle text_style = TextPainterBase::TextPaintingStyle( + text_combine.GetDocument(), style, paint_info); + + if (has_emphasis_mark) { + text_painter.PaintEmphasisMark(text_style, + text_combine.Parent()->StyleRef().GetFont()); + } + + if (has_text_decoration) + text_painter.PaintDecorations(paint_info, text_style); +} + +// static +bool NGTextCombinePainter::ShouldPaint( + const LayoutNGTextCombine& text_combine) { + const auto& style = text_combine.Parent()->StyleRef(); + return style.TextDecorationsInEffect() != TextDecoration::kNone || + style.GetTextEmphasisMark() != TextEmphasisMark::kNone; +} + +void NGTextCombinePainter::ClipDecorationsStripe(float upper, + float stripe_width, + float dilation) { + // Nothing to do. +} + +void NGTextCombinePainter::PaintDecorations(const PaintInfo& paint_info, + const TextPaintStyle& text_style) { + // Setup arguments for painting text decorations + const absl::optional<AppliedTextDecoration> selection_text_decoration; + const ComputedStyle* const decorating_box_style = nullptr; + TextDecorationInfo decoration_info( + text_frame_rect_.offset, text_frame_rect_.size.width, + style_.GetFontBaseline(), style_, selection_text_decoration, + decorating_box_style); + + const NGTextDecorationOffset decoration_offset(style_, style_, nullptr); + const auto& applied_text_decorations = style_.AppliedTextDecorations(); + + // Paint text decorations except line through + bool has_line_through_decoration = false; + PaintDecorationsExceptLineThrough(decoration_offset, decoration_info, + paint_info, applied_text_decorations, + text_style, &has_line_through_decoration); + if (!has_line_through_decoration) + return; + + // Paint line through + PaintDecorationsOnlyLineThrough(decoration_info, paint_info, + applied_text_decorations, text_style); +} + +void NGTextCombinePainter::PaintEmphasisMark(const TextPaintStyle& text_style, + const Font& emphasis_mark_font) { + DCHECK_NE(style_.GetTextEmphasisMark(), TextEmphasisMark::kNone); + SetEmphasisMark(style_.TextEmphasisMarkString(), + style_.GetTextEmphasisPosition()); + PaintEmphasisMarkForCombinedText(text_style, emphasis_mark_font); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h new file mode 100644 index 00000000000..479fae900eb --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_combine_painter.h @@ -0,0 +1,46 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_TEXT_COMBINE_PAINTER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_TEXT_COMBINE_PAINTER_H_ + +#include "third_party/blink/renderer/core/paint/text_painter_base.h" +#include "third_party/blink/renderer/platform/graphics/dom_node_id.h" + +namespace blink { + +class ComputedStyle; +class LayoutNGTextCombine; + +// The painter for painting text decorations and emphasis marks for +// LayoutNGTextCombine. +class NGTextCombinePainter final : public TextPainterBase { + public: + NGTextCombinePainter(GraphicsContext& context, + const ComputedStyle& style, + const PhysicalRect& text_frame_rect); + ~NGTextCombinePainter(); + + static void Paint(const PaintInfo& paint_info, + const PhysicalOffset& paint_offset, + const LayoutNGTextCombine& text_combine); + + static bool ShouldPaint(const LayoutNGTextCombine& text_combine); + + private: + void ClipDecorationsStripe(float upper, + float stripe_width, + float dilation) override; + + void PaintDecorations(const PaintInfo& paint_info, + const TextPaintStyle& text_style); + void PaintEmphasisMark(const TextPaintStyle& text_style, + const Font& emphasis_mark_font); + + const ComputedStyle& style_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_TEXT_COMBINE_PAINTER_H_ diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc index 00f418a60e3..ed7e78071b1 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc @@ -13,7 +13,9 @@ #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/layout/geometry/logical_rect.h" #include "third_party/blink/renderer/core/layout/layout_ruby_run.h" +#include "third_party/blink/renderer/core/layout/layout_ruby_text.h" #include "third_party/blink/renderer/core/layout/list_marker.h" +#include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" @@ -56,7 +58,7 @@ inline PhysicalRect ComputeBoxRect(const NGInlineCursor& cursor, const PhysicalOffset& paint_offset, const PhysicalOffset& parent_offset) { PhysicalRect box_rect; - if (const auto* svg_data = cursor.CurrentItem()->SVGFragmentData()) + if (const auto* svg_data = cursor.CurrentItem()->SvgFragmentData()) box_rect = PhysicalRect::FastAndLossyFromFloatRect(svg_data->rect); else box_rect = cursor.CurrentItem()->RectInContainerFragment(); @@ -89,6 +91,9 @@ bool ShouldPaintEmphasisMark(const ComputedStyle& style, const LayoutObject& layout_object) { if (style.GetTextEmphasisMark() == TextEmphasisMark::kNone) return false; + // Note: We set text-emphasis-style:none for combined text and we paint + // emphasis mark at left/right side of |LayoutNGTextCombine|. + DCHECK(!IsA<LayoutNGTextCombine>(layout_object.Parent())); const LayoutObject* containing_block = layout_object.ContainingBlock(); if (!containing_block || !containing_block->IsRubyBase()) return true; @@ -144,16 +149,32 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, const LayoutObject* layout_object = text_item.GetLayoutObject(); const Document& document = layout_object->GetDocument(); const bool is_printing = document.Printing(); + // Don't paint selections when rendering a mask, clip-path (as a mask), + // pattern or feImage (element reference.) + const bool is_rendering_resource = paint_info.IsRenderingResourceSubtree(); + const auto* const text_combine = + DynamicTo<LayoutNGTextCombine>(layout_object->Parent()); +#if DCHECK_IS_ON() + if (UNLIKELY(text_combine)) + LayoutNGTextCombine::AssertStyleIsValid(style); +#endif // Determine whether or not we're selected. - absl::optional<NGHighlightPainter::SelectionPaintState> selection; - if (UNLIKELY(!is_printing && paint_info.phase != PaintPhase::kTextClip && + NGHighlightPainter::SelectionPaintState* selection = nullptr; + absl::optional<NGHighlightPainter::SelectionPaintState> + selection_for_bounds_recording; + if (UNLIKELY(!is_printing && !is_rendering_resource && + paint_info.phase != PaintPhase::kTextClip && layout_object->IsSelected())) { const NGInlineCursor& root_inline_cursor = InlineCursorForBlockFlow(cursor_, &inline_cursor_for_block_flow_); - selection.emplace(root_inline_cursor); - if (!selection->Status().HasValidRange()) - selection.reset(); + + // Empty selections might be the boundary of the document selection, and + // thus need to get recorded. We only need to paint the selection if it + // has a valid range. + selection_for_bounds_recording.emplace(root_inline_cursor); + if (selection_for_bounds_recording->Status().HasValidRange()) + selection = &selection_for_bounds_recording.value(); } if (!selection) { // When only painting the selection drag image, don't bother to paint if @@ -167,54 +188,64 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, } PhysicalRect box_rect = ComputeBoxRect(cursor_, paint_offset, parent_offset_); + if (UNLIKELY(text_combine)) { + box_rect.offset.left = + text_combine->AdjustTextLeftForPaint(box_rect.offset.left); + } + IntRect visual_rect; - const LayoutSVGInlineText* svg_inline_text = nullptr; + const auto* const svg_inline_text = + DynamicTo<LayoutSVGInlineText>(layout_object); float scaling_factor = 1.0f; - if (text_item.Type() == NGFragmentItem::kSVGText) { - svg_inline_text = To<LayoutSVGInlineText>(layout_object); + if (UNLIKELY(svg_inline_text)) { + DCHECK_EQ(text_item.Type(), NGFragmentItem::kSvgText); scaling_factor = svg_inline_text->ScalingFactor(); DCHECK_NE(scaling_factor, 0.0f); visual_rect = EnclosingIntRect( svg_inline_text->Parent()->VisualRectInLocalSVGCoordinates()); } else { + DCHECK_NE(text_item.Type(), NGFragmentItem::kSvgText); PhysicalRect ink_overflow = text_item.SelfInkOverflow(); ink_overflow.Move(box_rect.offset); visual_rect = EnclosingIntRect(ink_overflow); } - // The text clip phase already has a DrawingRecorder. Text clips are initiated - // only in BoxPainterBase::PaintFillLayer, which is already within a - // DrawingRecorder. - absl::optional<DrawingRecorder> recorder; - const auto& display_item_client = - AsDisplayItemClient(cursor_, selection.has_value()); - // Ensure the selection bounds are recorded on the paint chunk regardless of - // whether the diplay item that contains the actual selection painting is + // whether the display item that contains the actual selection painting is // reused. absl::optional<SelectionBoundsRecorder> selection_recorder; - if (UNLIKELY(selection && paint_info.phase == PaintPhase::kForeground && - !is_printing)) { + if (UNLIKELY(selection_for_bounds_recording && + paint_info.phase == PaintPhase::kForeground && !is_printing)) { if (SelectionBoundsRecorder::ShouldRecordSelection( cursor_.Current().GetLayoutObject()->GetFrame()->Selection(), - selection->State())) { + selection_for_bounds_recording->State())) { PhysicalRect selection_rect = - selection->ComputeSelectionRect(box_rect.offset); - selection_recorder.emplace(selection->State(), selection_rect, - paint_info.context.GetPaintController(), - cursor_.Current().ResolvedDirection(), - style.GetWritingMode(), - *cursor_.Current().GetLayoutObject()); + selection_for_bounds_recording->ComputeSelectionRect(box_rect.offset); + selection_recorder.emplace( + selection_for_bounds_recording->State(), selection_rect, + paint_info.context.GetPaintController(), + cursor_.Current().ResolvedDirection(), style.GetWritingMode(), + *cursor_.Current().GetLayoutObject()); } } + // This is declared after selection_recorder so that this will be destructed + // before selection_recorder to ensure the selection is painted before + // selection_recorder records the selection bounds. + absl::optional<DrawingRecorder> recorder; + const auto& display_item_client = + AsDisplayItemClient(cursor_, selection != nullptr); + // Text clips are initiated only in BoxPainterBase::PaintFillLayer, which is + // already within a DrawingRecorder. if (paint_info.phase != PaintPhase::kTextClip) { - if (DrawingRecorder::UseCachedDrawingIfPossible( - paint_info.context, display_item_client, paint_info.phase)) { - return; + if (LIKELY(!paint_info.context.InDrawingRecorder())) { + if (DrawingRecorder::UseCachedDrawingIfPossible( + paint_info.context, display_item_client, paint_info.phase)) { + return; + } + recorder.emplace(paint_info.context, display_item_client, + paint_info.phase, visual_rect); } - recorder.emplace(paint_info.context, display_item_client, paint_info.phase, - visual_rect); } if (UNLIKELY(text_item.IsSymbolMarker())) { @@ -238,50 +269,62 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, // Determine text colors. Node* node = layout_object->GetNode(); - DCHECK(!svg_inline_text || - (!IsA<SVGElement>(node) && IsA<SVGElement>(node->parentNode()))); TextPaintStyle text_style = - svg_inline_text - ? TextPainterBase::SvgTextPaintingStyle( - document, SVGLengthContext(To<SVGElement>(node->parentNode())), - style, paint_info) - : TextPainterBase::TextPaintingStyle(document, style, paint_info); - // TODO(crbug.com/1179585): Support SVG Paint Servers (e.g. Gradient, Pattern) + TextPainterBase::TextPaintingStyle(document, style, paint_info); if (UNLIKELY(selection)) { selection->ComputeSelectionStyle(document, style, node, paint_info, text_style); } // Set our font. - const Font& font = - svg_inline_text ? svg_inline_text->ScaledFont() : style.GetFont(); + const Font& font = UNLIKELY(svg_inline_text) + ? svg_inline_text->ScaledFont() + : UNLIKELY(text_combine) + ? text_combine->UsesCompressedFont() + ? text_combine->CompressedFont() + : style.GetFont() + : style.GetFont(); const SimpleFontData* font_data = font.PrimaryFont(); DCHECK(font_data); const bool paint_marker_backgrounds = paint_info.phase != PaintPhase::kSelectionDragImage && paint_info.phase != PaintPhase::kTextClip && !is_printing; - absl::optional<GraphicsContextStateSaver> state_saver; + GraphicsContextStateSaver state_saver(context, /*save_and_restore=*/false); absl::optional<AffineTransform> rotation; const WritingMode writing_mode = style.GetWritingMode(); const bool is_horizontal = IsHorizontalWritingMode(writing_mode); - int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0; - PhysicalOffset text_origin(box_rect.offset.left, - box_rect.offset.top + ascent); - if (svg_inline_text && scaling_factor != 1.0f) { - state_saver.emplace(context); - context.Scale(1 / scaling_factor, 1 / scaling_factor); - } - if (text_item.HasSVGTransformForPaint()) { - if (!state_saver) - state_saver.emplace(context); - context.ConcatCTM(text_item.BuildSVGTransformForPaint()); - } + const int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0; + PhysicalOffset text_origin( + box_rect.offset.left, + UNLIKELY(text_combine) + ? text_combine->AdjustTextTopForPaint(box_rect.offset.top) + : box_rect.offset.top + ascent); + NGTextPainter text_painter(context, font, fragment_paint_info, visual_rect, text_origin, box_rect, is_horizontal); - NGHighlightPainter highlight_painter( - text_painter, paint_info, cursor_, *cursor_.CurrentItem(), - box_rect.offset, style, std::move(selection), is_printing); + NGHighlightPainter highlight_painter(text_painter, paint_info, cursor_, + *cursor_.CurrentItem(), box_rect.offset, + style, selection, is_printing); + + if (svg_inline_text) { + NGTextPainter::SvgTextPaintState& svg_state = text_painter.SetSvgState( + *svg_inline_text, style, paint_info.IsRenderingClipPathAsMaskImage()); + + if (scaling_factor != 1.0f) { + state_saver.SaveIfNeeded(); + context.Scale(1 / scaling_factor, 1 / scaling_factor); + svg_state.EnsureShaderTransform().Scale(scaling_factor); + } + if (text_item.HasSvgTransformForPaint()) { + state_saver.SaveIfNeeded(); + const auto fragment_transform = text_item.BuildSvgTransformForPaint(); + context.ConcatCTM(fragment_transform); + DCHECK(fragment_transform.IsInvertible()); + svg_state.EnsureShaderTransform().PreMultiply( + fragment_transform.Inverse()); + } + } // 1. Paint backgrounds for document markers that don’t participate in the CSS // highlight overlay system, such as composition highlights. They use physical @@ -289,21 +332,25 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, highlight_painter.Paint(NGHighlightPainter::kBackground); if (!is_horizontal) { - if (!state_saver) - state_saver.emplace(context); + state_saver.SaveIfNeeded(); // Because we rotate the GraphicsContext to match the logical direction, // transpose the |box_rect| to match to it. box_rect.size = PhysicalSize(box_rect.Height(), box_rect.Width()); - rotation.emplace(TextPainterBase::Rotation( - box_rect, writing_mode != WritingMode::kSidewaysLr - ? TextPainterBase::kClockwise - : TextPainterBase::kCounterclockwise)); + rotation.emplace(TextPainterBase::Rotation(box_rect, writing_mode)); context.ConcatCTM(*rotation); + if (NGTextPainter::SvgTextPaintState* state = text_painter.GetSvgState()) { + DCHECK(rotation->IsInvertible()); + state->EnsureShaderTransform().PreMultiply(rotation->Inverse()); + } } if (UNLIKELY(highlight_painter.Selection())) { PhysicalRect before_rotation = highlight_painter.Selection()->ComputeSelectionRect(box_rect.offset); + if (scaling_factor != 1.0f) { + before_rotation.offset.Scale(1 / scaling_factor); + before_rotation.size.Scale(1 / scaling_factor); + } // The selection rect is given in physical coordinates, so we need to map // them into our now-possibly-rotated space before calling any methods @@ -344,6 +391,7 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, &has_line_through_decoration); text_painter.Paint(start_offset, end_offset, length, text_style, node_id); if (has_line_through_decoration) { + DCHECK(!text_combine); text_painter.PaintDecorationsOnlyLineThrough( text_item, paint_info, style, text_style, box_rect, absl::nullopt); } @@ -375,6 +423,7 @@ void NGTextFragmentPainter::Paint(const PaintInfo& paint_info, text_style, node_id); if (has_line_through_decoration) { + DCHECK(!text_combine); text_painter.PaintDecorationsOnlyLineThrough( text_item, paint_info, style, text_style, box_rect, highlight_painter.SelectionDecoration()); diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h index 24047eed809..87af0ca8d3e 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_TEXT_FRAGMENT_PAINTER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_NG_NG_TEXT_FRAGMENT_PAINTER_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/geometry/layout_rect.h" diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc index 89650c6b601..69a63b78781 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc @@ -4,28 +4,114 @@ #include "third_party/blink/renderer/core/paint/ng/ng_text_painter.h" +#include "base/stl_util.h" #include "third_party/blink/renderer/core/css/css_property_names.h" #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h" #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h" +#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h" +#include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h" +#include "third_party/blink/renderer/core/layout/svg/svg_resources.h" #include "third_party/blink/renderer/core/paint/applied_decoration_painter.h" #include "third_party/blink/renderer/core/paint/box_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" +#include "third_party/blink/renderer/core/paint/svg_object_painter.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/core/style/shadow_list.h" +#include "third_party/blink/renderer/core/svg/svg_element.h" #include "third_party/blink/renderer/platform/fonts/font.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h" #include "third_party/blink/renderer/platform/graphics/graphics_context.h" #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" +#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h" #include "third_party/blink/renderer/platform/wtf/text/character_names.h" namespace blink { namespace { +class SelectionStyleScope { + STACK_ALLOCATED(); + + public: + SelectionStyleScope(LayoutObject&, + const ComputedStyle& style, + const ComputedStyle& selection_style); + SelectionStyleScope(const SelectionStyleScope&) = delete; + SelectionStyleScope& operator=(const SelectionStyleScope) = delete; + ~SelectionStyleScope(); + + private: + LayoutObject& layout_object_; + const ComputedStyle& selection_style_; + const bool styles_are_equal_; +}; + +SelectionStyleScope::SelectionStyleScope(LayoutObject& layout_object, + const ComputedStyle& style, + const ComputedStyle& selection_style) + : layout_object_(layout_object), + selection_style_(selection_style), + styles_are_equal_(style == selection_style) { + if (styles_are_equal_) + return; + DCHECK(!layout_object.IsSVGInlineText()); + auto& element = To<SVGElement>(*layout_object_.GetNode()); + SVGResources::UpdatePaints(element, nullptr, selection_style_); +} + +SelectionStyleScope::~SelectionStyleScope() { + if (styles_are_equal_) + return; + auto& element = To<SVGElement>(*layout_object_.GetNode()); + SVGResources::ClearPaints(element, &selection_style_); +} + +bool SetupPaintForSvgText(const LayoutSVGInlineText& svg_inline_text, + const GraphicsContext& context, + bool is_rendering_clip_path_as_mask_image, + const ComputedStyle& style, + const AffineTransform* shader_transform, + LayoutSVGResourceMode resource_mode, + PaintFlags& flags) { + const LayoutObject* layout_parent = svg_inline_text.Parent(); + if (!SVGObjectPainter(*layout_parent) + .PreparePaint(context, is_rendering_clip_path_as_mask_image, style, + resource_mode, flags, shader_transform)) { + return false; + } + + flags.setAntiAlias(true); + + if (style.TextShadow() && + // Text shadows are disabled when printing. http://crbug.com/258321 + !svg_inline_text.GetDocument().Printing()) { + flags.setLooper(TextPainterBase::CreateDrawLooper( + style.TextShadow(), DrawLooperBuilder::kShadowRespectsAlpha, + style.VisitedDependentColor(GetCSSPropertyColor()), + style.UsedColorScheme())); + } + + if (resource_mode == kApplyToStrokeMode) { + // The stroke geometry needs be generated based on the scaled font. + float stroke_scale_factor = + style.VectorEffect() != EVectorEffect::kNonScalingStroke + ? svg_inline_text.ScalingFactor() + : 1; + StrokeData stroke_data; + SVGLayoutSupport::ApplyStrokeStyleToStrokeData( + stroke_data, style, *layout_parent, stroke_scale_factor); + if (stroke_scale_factor != 1) + stroke_data.SetThickness(stroke_data.Thickness() * stroke_scale_factor); + stroke_data.SetupPaint(&flags); + } + + return true; +} + absl::optional<TextDecorationInfo> DecorationsForLayer( const NGFragmentItem& text_item, const PhysicalRect& decoration_rect, @@ -37,9 +123,9 @@ absl::optional<TextDecorationInfo> DecorationsForLayer( text_item.IsEllipsis()) { return absl::nullopt; } - return TextDecorationInfo(decoration_rect.offset, decoration_rect.offset, - decoration_rect.Width(), style.GetFontBaseline(), - style, selection_text_decoration, nullptr); + return TextDecorationInfo(decoration_rect.offset, decoration_rect.Width(), + style.GetFontBaseline(), style, + selection_text_decoration, nullptr); } } // namespace @@ -87,6 +173,11 @@ void NGTextPainter::PaintSelectedText(unsigned start_offset, // painting in most small text. snapped_selection_rect.Inflate(1); if (snapped_selection_rect.Contains(visual_rect_)) { + absl::optional<base::AutoReset<bool>> is_painting_selection_reset; + if (svg_text_paint_state_.has_value()) { + is_painting_selection_reset.emplace( + &svg_text_paint_state_->is_painting_selection_, true); + } Paint(start_offset, end_offset, length, selection_style, node_id); return; } @@ -109,6 +200,11 @@ void NGTextPainter::PaintSelectedText(unsigned start_offset, } // Then draw the glyphs inside the selection area, with the selection style. { + absl::optional<base::AutoReset<bool>> is_painting_selection_reset; + if (svg_text_paint_state_.has_value()) { + is_painting_selection_reset.emplace( + &svg_text_paint_state_->is_painting_selection_, true); + } GraphicsContextStateSaver state_saver(graphics_context_); graphics_context_.Clip(float_selection_rect); Paint(start_offset, end_offset, length, selection_style, node_id); @@ -136,86 +232,9 @@ void NGTextPainter::PaintDecorationsExceptLineThrough( const NGTextDecorationOffset decoration_offset(decoration_info->Style(), text_item.Style(), nullptr); - GraphicsContext& context = paint_info.context; - GraphicsContextStateSaver state_saver(context); - UpdateGraphicsContext(context, text_style, horizontal_, state_saver); - - if (has_combined_text_) - context.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); - - // text-underline-position may flip underline and overline. - ResolvedUnderlinePosition underline_position = - decoration_info->UnderlinePosition(); - bool flip_underline_and_overline = false; - if (underline_position == ResolvedUnderlinePosition::kOver) { - flip_underline_and_overline = true; - underline_position = ResolvedUnderlinePosition::kUnder; - } - - const Vector<AppliedTextDecoration>& decorations = - style.AppliedTextDecorations(); - for (size_t applied_decoration_index = 0; - applied_decoration_index < decorations.size(); - ++applied_decoration_index) { - const AppliedTextDecoration& decoration = - decorations[applied_decoration_index]; - TextDecoration lines = decoration.Lines(); - bool has_underline = EnumHasFlags(lines, TextDecoration::kUnderline); - bool has_overline = EnumHasFlags(lines, TextDecoration::kOverline); - if (flip_underline_and_overline) - std::swap(has_underline, has_overline); - - decoration_info->SetDecorationIndex(applied_decoration_index); - - float resolved_thickness = decoration_info->ResolvedThickness(); - context.SetStrokeThickness(resolved_thickness); - - if (has_underline && decoration_info->FontData()) { - // Don't apply text-underline-offset to overline. - Length line_offset = - flip_underline_and_overline ? Length() : decoration.UnderlineOffset(); - - const int paint_underline_offset = - decoration_offset.ComputeUnderlineOffset( - underline_position, decoration_info->Style().ComputedFontSize(), - decoration_info->FontData()->GetFontMetrics(), line_offset, - resolved_thickness); - decoration_info->SetPerLineData( - TextDecoration::kUnderline, paint_underline_offset, - TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness), 1); - PaintDecorationUnderOrOverLine(context, *decoration_info, - TextDecoration::kUnderline); - } - - if (has_overline && decoration_info->FontData()) { - // Don't apply text-underline-offset to overline. - Length line_offset = - flip_underline_and_overline ? decoration.UnderlineOffset() : Length(); - - FontVerticalPositionType position = - flip_underline_and_overline ? FontVerticalPositionType::TopOfEmHeight - : FontVerticalPositionType::TextTop; - const int paint_overline_offset = - decoration_offset.ComputeUnderlineOffsetForUnder( - line_offset, decoration_info->Style().ComputedFontSize(), - resolved_thickness, position); - decoration_info->SetPerLineData( - TextDecoration::kOverline, paint_overline_offset, - -TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness), - 1); - PaintDecorationUnderOrOverLine(context, *decoration_info, - TextDecoration::kOverline); - } - - // We could instead build a vector of the TextDecoration instances needing - // line-through but this is a rare case so better to avoid vector overhead. - *has_line_through_decoration |= - EnumHasFlags(lines, TextDecoration::kLineThrough); - } - - // Restore rotation as needed. - if (has_combined_text_) - context.ConcatCTM(Rotation(text_frame_rect_, kCounterclockwise)); + TextPainterBase::PaintDecorationsExceptLineThrough( + decoration_offset, *decoration_info, paint_info, + style.AppliedTextDecorations(), text_style, has_line_through_decoration); } // Based on legacy TextPainter. @@ -231,55 +250,8 @@ void NGTextPainter::PaintDecorationsOnlyLineThrough( DCHECK(decoration_info); - const NGTextDecorationOffset decoration_offset(decoration_info->Style(), - text_item.Style(), nullptr); - - GraphicsContext& context = paint_info.context; - GraphicsContextStateSaver state_saver(context); - UpdateGraphicsContext(context, text_style, horizontal_, state_saver); - - if (has_combined_text_) - context.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); - - const Vector<AppliedTextDecoration>& decorations = - style.AppliedTextDecorations(); - for (size_t applied_decoration_index = 0; - applied_decoration_index < decorations.size(); - ++applied_decoration_index) { - const AppliedTextDecoration& decoration = - decorations[applied_decoration_index]; - TextDecoration lines = decoration.Lines(); - if (EnumHasFlags(lines, TextDecoration::kLineThrough)) { - decoration_info->SetDecorationIndex(applied_decoration_index); - - float resolved_thickness = decoration_info->ResolvedThickness(); - context.SetStrokeThickness(resolved_thickness); - - // For increased line thickness, the line-through decoration needs to grow - // in both directions from its origin, subtract half the thickness to keep - // it centered at the same origin. - const float line_through_offset = - 2 * decoration_info->Baseline() / 3 - resolved_thickness / 2; - // Floor double_offset in order to avoid double-line gap to appear - // of different size depending on position where the double line - // is drawn because of rounding downstream in - // GraphicsContext::DrawLineForText. - decoration_info->SetPerLineData( - TextDecoration::kLineThrough, line_through_offset, - floorf(TextDecorationInfo::DoubleOffsetFromThickness( - resolved_thickness)), - 0); - AppliedDecorationPainter decoration_painter(context, *decoration_info, - TextDecoration::kLineThrough); - // No skip: ink for line-through, - // compare https://github.com/w3c/csswg-drafts/issues/711 - decoration_painter.Paint(); - } - } - - // Restore rotation as needed. - if (has_combined_text_) - context.ConcatCTM(Rotation(text_frame_rect_, kCounterclockwise)); + TextPainterBase::PaintDecorationsOnlyLineThrough( + *decoration_info, paint_info, style.AppliedTextDecorations(), text_style); } template <NGTextPainter::PaintInternalStep step> @@ -299,8 +271,12 @@ void NGTextPainter::PaintInternalFragment( FloatPoint(text_origin_) + IntSize(0, emphasis_mark_offset_)); } else { DCHECK(step == kPaintText); - graphics_context_.DrawText(font_, fragment_paint_info_, - FloatPoint(text_origin_), node_id); + if (svg_text_paint_state_.has_value()) { + PaintSvgTextFragment(node_id); + } else { + graphics_context_.DrawText(font_, fragment_paint_info_, + FloatPoint(text_origin_), node_id); + } // TODO(npm): Check that there are non-whitespace characters. See // crbug.com/788444. graphics_context_.GetPaintController().SetTextPainted(); @@ -347,6 +323,113 @@ void NGTextPainter::ClipDecorationsStripe(float upper, DecorationsStripeIntercepts(upper, stripe_width, dilation, text_intercepts); } -void NGTextPainter::PaintEmphasisMarkForCombinedText() {} +void NGTextPainter::PaintSvgTextFragment(DOMNodeId node_id) { + const NGTextPainter::SvgTextPaintState& state = *svg_text_paint_state_; + absl::optional<SelectionStyleScope> selection_style_scope; + bool has_fill = state.Style().HasFill(); + bool has_visible_stroke = state.Style().HasVisibleStroke(); + const ComputedStyle* style_to_paint = &state.Style(); + if (state.IsPaintingSelection()) { + LayoutObject* layout_parent = state.InlineText().Parent(); + style_to_paint = + layout_parent->GetCachedPseudoElementStyle(kPseudoIdSelection); + if (style_to_paint) { + if (!has_fill) + has_fill = style_to_paint->HasFill(); + if (!has_visible_stroke) + has_visible_stroke = style_to_paint->HasVisibleStroke(); + } else { + style_to_paint = &state.Style(); + } + + selection_style_scope.emplace(*layout_parent, state.Style(), + *style_to_paint); + } + + if (state.IsRenderingClipPathAsMaskImage()) { + has_fill = true; + has_visible_stroke = false; + } + + for (int i = 0; i < 3; i++) { + absl::optional<LayoutSVGResourceMode> resource_mode; + + switch (state.Style().PaintOrderType(i)) { + case PT_FILL: + if (has_fill) + resource_mode = kApplyToFillMode; + break; + case PT_STROKE: + if (has_visible_stroke) + resource_mode = kApplyToStrokeMode; + break; + case PT_MARKERS: + // Markers don't apply to text + break; + default: + NOTREACHED(); + break; + } + + if (resource_mode) { + PaintFlags flags; + if (SetupPaintForSvgText(state.InlineText(), graphics_context_, + state.IsRenderingClipPathAsMaskImage(), + *style_to_paint, state.GetShaderTransform(), + *resource_mode, flags)) { + graphics_context_.DrawText(font_, fragment_paint_info_, + FloatPoint(text_origin_), flags, node_id); + } + } + } +} + +NGTextPainter::SvgTextPaintState& NGTextPainter::SetSvgState( + const LayoutSVGInlineText& svg_inline_text, + const ComputedStyle& style, + bool is_rendering_clip_path_as_mask_image) { + return svg_text_paint_state_.emplace(svg_inline_text, style, + is_rendering_clip_path_as_mask_image); +} + +NGTextPainter::SvgTextPaintState* NGTextPainter::GetSvgState() { + return base::OptionalOrNullptr(svg_text_paint_state_); +} + +NGTextPainter::SvgTextPaintState::SvgTextPaintState( + const LayoutSVGInlineText& layout_svg_inline_text, + const ComputedStyle& style, + bool is_rendering_clip_path_as_mask_image) + : layout_svg_inline_text_(layout_svg_inline_text), + style_(style), + is_rendering_clip_path_as_mask_image_( + is_rendering_clip_path_as_mask_image) {} + +const LayoutSVGInlineText& NGTextPainter::SvgTextPaintState::InlineText() + const { + return layout_svg_inline_text_; +} + +const ComputedStyle& NGTextPainter::SvgTextPaintState::Style() const { + return style_; +} + +bool NGTextPainter::SvgTextPaintState::IsPaintingSelection() const { + return is_painting_selection_; +} + +bool NGTextPainter::SvgTextPaintState::IsRenderingClipPathAsMaskImage() const { + return is_rendering_clip_path_as_mask_image_; +} + +AffineTransform& NGTextPainter::SvgTextPaintState::EnsureShaderTransform() { + return shader_transform_ ? shader_transform_.value() + : shader_transform_.emplace(); +} + +const AffineTransform* NGTextPainter::SvgTextPaintState::GetShaderTransform() + const { + return base::OptionalOrNullptr(shader_transform_); +} } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.h b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.h index 42e8bc6ad2a..26b4bb67899 100644 --- a/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/ng/ng_text_painter.h @@ -12,6 +12,7 @@ namespace blink { +class LayoutSVGInlineText; class NGFragmentItem; struct NGTextFragmentPaintInfo; @@ -24,6 +25,29 @@ class CORE_EXPORT NGTextPainter : public TextPainterBase { STACK_ALLOCATED(); public: + class SvgTextPaintState final { + public: + SvgTextPaintState(const LayoutSVGInlineText&, + const ComputedStyle&, + bool is_rendering_clip_path_as_mask_image); + + const LayoutSVGInlineText& InlineText() const; + const ComputedStyle& Style() const; + bool IsPaintingSelection() const; + bool IsRenderingClipPathAsMaskImage() const; + + AffineTransform& EnsureShaderTransform(); + const AffineTransform* GetShaderTransform() const; + + private: + const LayoutSVGInlineText& layout_svg_inline_text_; + const ComputedStyle& style_; + absl::optional<AffineTransform> shader_transform_; + bool is_painting_selection_ = false; + bool is_rendering_clip_path_as_mask_image_ = false; + friend class NGTextPainter; + }; + NGTextPainter(GraphicsContext& context, const Font& font, const NGTextFragmentPaintInfo& fragment_paint_info, @@ -58,7 +82,6 @@ class CORE_EXPORT NGTextPainter : public TextPainterBase { const PhysicalRect& selection_rect, DOMNodeId node_id); - // Based on legacy TextPainter. void PaintDecorationsExceptLineThrough( const NGFragmentItem& text_item, const PaintInfo& paint_info, @@ -68,7 +91,6 @@ class CORE_EXPORT NGTextPainter : public TextPainterBase { const absl::optional<AppliedTextDecoration>& selection_decoration, bool* has_line_through_decoration); - // Based on legacy TextPainter. void PaintDecorationsOnlyLineThrough( const NGFragmentItem& text_item, const PaintInfo& paint_info, @@ -77,6 +99,11 @@ class CORE_EXPORT NGTextPainter : public TextPainterBase { const PhysicalRect& decoration_rect, const absl::optional<AppliedTextDecoration>& selection_decoration); + SvgTextPaintState& SetSvgState(const LayoutSVGInlineText&, + const ComputedStyle&, + bool is_rendering_clip_path_as_mask_image); + SvgTextPaintState* GetSvgState(); + private: template <PaintInternalStep step> void PaintInternalFragment(unsigned from, unsigned to, DOMNodeId node_id); @@ -87,10 +114,11 @@ class CORE_EXPORT NGTextPainter : public TextPainterBase { unsigned truncation_point, DOMNodeId node_id); - void PaintEmphasisMarkForCombinedText(); + void PaintSvgTextFragment(DOMNodeId node_id); NGTextFragmentPaintInfo fragment_paint_info_; const IntRect& visual_rect_; + absl::optional<SvgTextPaintState> svg_text_paint_state_; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/nine_piece_image_painter.cc b/chromium/third_party/blink/renderer/core/paint/nine_piece_image_painter.cc index 292c087dcb2..d6637d60483 100644 --- a/chromium/third_party/blink/renderer/core/paint/nine_piece_image_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/nine_piece_image_painter.cc @@ -40,9 +40,7 @@ struct TileParameters { absl::optional<TileParameters> ComputeTileParameters( ENinePieceImageRule tile_rule, - float dst_pos, float dst_extent, - float src_pos, float src_extent, float in_scale_factor) { switch (tile_rule) { @@ -50,14 +48,13 @@ absl::optional<TileParameters> ComputeTileParameters( float repetitions = std::max(1.0f, roundf(dst_extent / (src_extent * in_scale_factor))); float scale_factor = dst_extent / (src_extent * repetitions); - return TileParameters{scale_factor, src_pos * scale_factor, 0}; + return TileParameters{scale_factor, 0, 0}; } case kRepeatImageRule: { float scaled_tile_extent = src_extent * in_scale_factor; // We want to construct the phase such that the pattern is centered (when // stretch is not set for a particular rule). - float phase = src_pos * in_scale_factor; - phase -= (dst_extent - scaled_tile_extent) / 2; + float phase = (dst_extent - scaled_tile_extent) / 2; return TileParameters{in_scale_factor, phase, 0}; } case kSpaceImageRule: { @@ -65,10 +62,10 @@ absl::optional<TileParameters> ComputeTileParameters( CalculateSpaceNeeded(dst_extent, src_extent); if (!spacing) return absl::nullopt; - return TileParameters{1, src_pos - *spacing, *spacing}; + return TileParameters{1, *spacing, *spacing}; } case kStretchImageRule: - return TileParameters{in_scale_factor, src_pos * in_scale_factor, 0}; + return TileParameters{in_scale_factor, 0, 0}; default: NOTREACHED(); } @@ -124,35 +121,41 @@ void PaintPieces(GraphicsContext& context, // Since there is no way for the developer to specify decode behavior, // use kSync by default. context.DrawImage(image, Image::kSyncDecode, draw_info.destination, - &draw_info.source, style.HasFilterInducingProperty()); + &draw_info.source, style.DisableForceDark()); continue; } // TODO(cavalcantii): see crbug.com/662513. absl::optional<TileParameters> h_tile = ComputeTileParameters( - draw_info.tile_rule.horizontal, draw_info.destination.X(), - draw_info.destination.Width(), draw_info.source.X(), + draw_info.tile_rule.horizontal, draw_info.destination.Width(), draw_info.source.Width(), draw_info.tile_scale.Width()); absl::optional<TileParameters> v_tile = ComputeTileParameters( - draw_info.tile_rule.vertical, draw_info.destination.Y(), - draw_info.destination.Height(), draw_info.source.Y(), + draw_info.tile_rule.vertical, draw_info.destination.Height(), draw_info.source.Height(), draw_info.tile_scale.Height()); if (!h_tile || !v_tile) continue; - FloatSize tile_scale_factor(h_tile->scale_factor, v_tile->scale_factor); - FloatPoint tile_phase(draw_info.destination.X() - h_tile->phase, - draw_info.destination.Y() - v_tile->phase); - FloatSize tile_spacing(h_tile->spacing, v_tile->spacing); - // TODO(cavalcantii): see crbug.com/662507. absl::optional<ScopedInterpolationQuality> interpolation_quality_override; if (draw_info.tile_rule.horizontal == kRoundImageRule || draw_info.tile_rule.vertical == kRoundImageRule) interpolation_quality_override.emplace(context, kInterpolationMedium); - context.DrawImageTiled(image, draw_info.destination, draw_info.source, - tile_scale_factor, tile_phase, tile_spacing); + ImageTilingInfo tiling_info; + tiling_info.image_rect = draw_info.source; + tiling_info.scale = FloatSize(h_tile->scale_factor, v_tile->scale_factor); + // The phase defines the origin of the whole image - not the image + // rect (see ImageTilingInfo) - so we need to adjust it to account + // for that. + FloatPoint tile_origin_in_dest_space = draw_info.source.Location(); + tile_origin_in_dest_space.Scale(tiling_info.scale.Width(), + tiling_info.scale.Height()); + tiling_info.phase = + draw_info.destination.Location() + + (FloatPoint(h_tile->phase, v_tile->phase) - tile_origin_in_dest_space); + tiling_info.spacing = FloatSize(h_tile->spacing, v_tile->spacing); + + context.DrawImageTiled(image, draw_info.destination, tiling_info); } } @@ -189,9 +192,8 @@ bool NinePieceImagePainter::Paint(GraphicsContext& graphics_context, // image with either "native" size (raster images) or size scaled by effective // zoom. const FloatSize default_object_size(border_image_rect.size); - FloatSize image_size = - style_image->ImageSize(document, style.EffectiveZoom(), - default_object_size, kRespectImageOrientation); + FloatSize image_size = style_image->ImageSize( + style.EffectiveZoom(), default_object_size, kRespectImageOrientation); scoped_refptr<Image> image = style_image->GetImage(observer, document, style, image_size); if (!image) @@ -201,7 +203,7 @@ bool NinePieceImagePainter::Paint(GraphicsContext& graphics_context, // yield the size in CSS pixels. This is the unit/scale we expect the // 'border-image-slice' values to be in. FloatSize unzoomed_image_size = style_image->ImageSize( - document, 1, default_object_size.ScaledBy(1 / style.EffectiveZoom()), + 1, default_object_size.ScaledBy(1 / style.EffectiveZoom()), kRespectImageOrientation); DEVTOOLS_TIMELINE_TRACE_EVENT_WITH_CATEGORIES( diff --git a/chromium/third_party/blink/renderer/core/paint/object_paint_invalidator.h b/chromium/third_party/blink/renderer/core/paint/object_paint_invalidator.h index c4afc37d1ab..bc53051bc14 100644 --- a/chromium/third_party/blink/renderer/core/paint/object_paint_invalidator.h +++ b/chromium/third_party/blink/renderer/core/paint/object_paint_invalidator.h @@ -7,7 +7,6 @@ #include "base/auto_reset.h" #include "base/dcheck_is_on.h" -#include "base/macros.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h" #include "third_party/blink/renderer/platform/graphics/paint_invalidation_reason.h" diff --git a/chromium/third_party/blink/renderer/core/paint/object_paint_properties.h b/chromium/third_party/blink/renderer/core/paint/object_paint_properties.h index 7e0d9f6d0b1..a41e8898545 100644 --- a/chromium/third_party/blink/renderer/core/paint/object_paint_properties.h +++ b/chromium/third_party/blink/renderer/core/paint/object_paint_properties.h @@ -225,7 +225,8 @@ class CORE_EXPORT ObjectPaintProperties { public: #if DCHECK_IS_ON() - // Used by FindPropertiesNeedingUpdate.h for verifying state doesn't change. + // Used by find_properties_needing_update.h for verifying state doesn't + // change. void SetImmutable() const { is_immutable_ = true; } bool IsImmutable() const { return is_immutable_; } void SetMutable() const { is_immutable_ = false; } diff --git a/chromium/third_party/blink/renderer/core/paint/object_painter.cc b/chromium/third_party/blink/renderer/core/paint/object_painter.cc index a59fa4d19f7..51893f575e7 100644 --- a/chromium/third_party/blink/renderer/core/paint/object_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/object_painter.cc @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/layout/layout_inline.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_theme.h" +#include "third_party/blink/renderer/core/paint/outline_painter.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/style/border_edge.h" #include "third_party/blink/renderer/core/style/computed_style.h" @@ -50,7 +51,8 @@ void ObjectPainter::PaintOutline(const PaintInfo& paint_info, visual_rect.Inflate(style_to_use.OutlineOutsetExtent()); DrawingRecorder recorder(paint_info.context, layout_object_, paint_info.phase, visual_rect); - PaintOutlineRects(paint_info, outline_rects, style_to_use); + OutlinePainter::PaintOutlineRects(paint_info.context, outline_rects, + style_to_use); } void ObjectPainter::PaintInlineChildrenOutlines(const PaintInfo& paint_info) { diff --git a/chromium/third_party/blink/renderer/core/paint/object_painter.h b/chromium/third_party/blink/renderer/core/paint/object_painter.h index 25a0c8b3ccb..5d001b57a13 100644 --- a/chromium/third_party/blink/renderer/core/paint/object_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/object_painter.h @@ -5,7 +5,6 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINTER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINTER_H_ -#include "third_party/blink/renderer/core/paint/object_painter_base.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" @@ -15,7 +14,7 @@ class LayoutObject; struct PaintInfo; struct PhysicalOffset; -class ObjectPainter : public ObjectPainterBase { +class ObjectPainter { STACK_ALLOCATED(); public: diff --git a/chromium/third_party/blink/renderer/core/paint/object_painter_base.cc b/chromium/third_party/blink/renderer/core/paint/object_painter_base.cc deleted file mode 100644 index 1029ef5e606..00000000000 --- a/chromium/third_party/blink/renderer/core/paint/object_painter_base.cc +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/paint/object_painter_base.h" - -#include "third_party/abseil-cpp/absl/types/optional.h" -#include "third_party/blink/renderer/core/paint/box_border_painter.h" -#include "third_party/blink/renderer/core/paint/paint_info.h" -#include "third_party/blink/renderer/core/style/border_edge.h" -#include "third_party/blink/renderer/core/style/computed_style.h" -#include "third_party/blink/renderer/platform/graphics/color.h" -#include "third_party/blink/renderer/platform/graphics/graphics_context.h" -#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" -#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" -#include "ui/base/ui_base_features.h" -#include "ui/native_theme/native_theme.h" - -namespace blink { - -namespace { - -struct OutlineEdgeInfo { - int x1; - int y1; - int x2; - int y2; - BoxSide side; -}; - -// Adjust length of edges if needed. Returns the width of the joint. -int AdjustJoint(int outline_width, - OutlineEdgeInfo& edge1, - OutlineEdgeInfo& edge2) { - // A clockwise joint: - // - needs no adjustment of edge length because our edges are along the - // clockwise outer edge of the outline; - // - needs a positive adjacent joint width (required by - // ObjectPainterBase::DrawLineForBoxSide). A counterclockwise joint: - needs - // to increase the edge length to include the joint; - needs a negative - // adjacent joint width (required by ObjectPainterBase::DrawLineForBoxSide). - switch (edge1.side) { - case BoxSide::kTop: - switch (edge2.side) { - case BoxSide::kRight: // Clockwise - return outline_width; - case BoxSide::kLeft: // Counterclockwise - edge1.x2 += outline_width; - edge2.y2 += outline_width; - return -outline_width; - default: // Same side or no joint. - return 0; - } - case BoxSide::kRight: - switch (edge2.side) { - case BoxSide::kBottom: // Clockwise - return outline_width; - case BoxSide::kTop: // Counterclockwise - edge1.y2 += outline_width; - edge2.x1 -= outline_width; - return -outline_width; - default: // Same side or no joint. - return 0; - } - case BoxSide::kBottom: - switch (edge2.side) { - case BoxSide::kLeft: // Clockwise - return outline_width; - case BoxSide::kRight: // Counterclockwise - edge1.x1 -= outline_width; - edge2.y1 -= outline_width; - return -outline_width; - default: // Same side or no joint. - return 0; - } - case BoxSide::kLeft: - switch (edge2.side) { - case BoxSide::kTop: // Clockwise - return outline_width; - case BoxSide::kBottom: // Counterclockwise - edge1.y1 -= outline_width; - edge2.x2 += outline_width; - return -outline_width; - default: // Same side or no joint. - return 0; - } - default: - NOTREACHED(); - return 0; - } -} - -void ApplyOutlineOffset(IntRect& rect, int offset) { - // A negative outline-offset should not cause the rendered outline shape to - // become smaller than twice the computed value of the outline-width, in each - // direction separately. See: https://drafts.csswg.org/css-ui/#outline-offset - rect.InflateX(std::max(offset, -rect.Width() / 2)); - rect.InflateY(std::max(offset, -rect.Height() / 2)); -} - -void PaintComplexOutline(GraphicsContext& graphics_context, - const Vector<IntRect> rects, - const ComputedStyle& style, - const Color& color) { - DCHECK(!style.OutlineStyleIsAuto()); - - // Construct a clockwise path along the outer edge of the outline. - SkRegion region; - uint16_t width = style.OutlineWidthInt(); - int offset = style.OutlineOffsetInt(); - for (auto& r : rects) { - IntRect rect = r; - ApplyOutlineOffset(rect, offset); - rect.Inflate(width); - region.op(rect, SkRegion::kUnion_Op); - } - SkPath path; - if (!region.getBoundaryPath(&path)) - return; - - Vector<OutlineEdgeInfo, 4> edges; - - SkPath::RawIter iter(path); - SkPoint points[4], first_point, last_point; - wtf_size_t count = 0; - for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb; - verb = iter.next(points)) { - // Keep track of the first and last point of each contour (started with - // kMove_Verb) so we can add the closing-line on kClose_Verb. - if (verb == SkPath::kMove_Verb) { - first_point = points[0]; - last_point = first_point; // this gets reset after each line, but we - // initialize it here - } else if (verb == SkPath::kClose_Verb) { - // create an artificial line to close the contour - verb = SkPath::kLine_Verb; - points[0] = last_point; - points[1] = first_point; - } - if (verb != SkPath::kLine_Verb) - continue; - last_point = points[1]; - - edges.Grow(++count); - OutlineEdgeInfo& edge = edges.back(); - edge.x1 = SkScalarTruncToInt(points[0].x()); - edge.y1 = SkScalarTruncToInt(points[0].y()); - edge.x2 = SkScalarTruncToInt(points[1].x()); - edge.y2 = SkScalarTruncToInt(points[1].y()); - if (edge.x1 == edge.x2) { - if (edge.y1 < edge.y2) { - edge.x1 -= width; - edge.side = BoxSide::kRight; - } else { - std::swap(edge.y1, edge.y2); - edge.x2 += width; - edge.side = BoxSide::kLeft; - } - } else { - DCHECK(edge.y1 == edge.y2); - if (edge.x1 < edge.x2) { - edge.y2 += width; - edge.side = BoxSide::kTop; - } else { - std::swap(edge.x1, edge.x2); - edge.y1 -= width; - edge.side = BoxSide::kBottom; - } - } - } - - if (!count) - return; - - Color outline_color = color; - bool use_transparency_layer = color.HasAlpha(); - if (use_transparency_layer) { - graphics_context.BeginLayer(static_cast<float>(color.Alpha()) / 255); - outline_color = - Color(outline_color.Red(), outline_color.Green(), outline_color.Blue()); - } - - DCHECK(count >= 4 && edges.size() == count); - int first_adjacent_width = AdjustJoint(width, edges.back(), edges.front()); - - // The width of the angled part of starting and ending joint of the current - // edge. - int adjacent_width_start = first_adjacent_width; - int adjacent_width_end; - for (wtf_size_t i = 0; i < count; ++i) { - OutlineEdgeInfo& edge = edges[i]; - adjacent_width_end = i == count - 1 - ? first_adjacent_width - : AdjustJoint(width, edge, edges[i + 1]); - int adjacent_width1 = adjacent_width_start; - int adjacent_width2 = adjacent_width_end; - if (edge.side == BoxSide::kLeft || edge.side == BoxSide::kBottom) - std::swap(adjacent_width1, adjacent_width2); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, edge.x1, edge.y1, edge.x2, edge.y2, edge.side, - outline_color, style.OutlineStyle(), adjacent_width1, adjacent_width2, - false); - adjacent_width_start = adjacent_width_end; - } - - if (use_transparency_layer) - graphics_context.EndLayer(); -} - -void PaintSingleRectangleOutline(const PaintInfo& paint_info, - const IntRect& rect, - const ComputedStyle& style, - const Color& color) { - DCHECK(!style.OutlineStyleIsAuto()); - - IntRect offset_rect = rect; - ApplyOutlineOffset(offset_rect, style.OutlineOffsetInt()); - - PhysicalRect inner(offset_rect); - PhysicalRect outer(inner); - outer.Inflate(LayoutUnit(style.OutlineWidthInt())); - const BorderEdge common_edge_info(style.OutlineWidthInt(), color, - style.OutlineStyle()); - BoxBorderPainter(style, outer, inner, common_edge_info) - .PaintBorder(paint_info, outer); -} - -void FillQuad(GraphicsContext& context, - const FloatPoint quad[], - const Color& color, - bool antialias) { - SkPathBuilder path; - path.moveTo(FloatPointToSkPoint(quad[0])); - path.lineTo(FloatPointToSkPoint(quad[1])); - path.lineTo(FloatPointToSkPoint(quad[2])); - path.lineTo(FloatPointToSkPoint(quad[3])); - PaintFlags flags(context.FillFlags()); - flags.setAntiAlias(antialias); - flags.setColor(color.Rgb()); - - context.DrawPath(path.detach(), flags); -} - -void DrawDashedOrDottedBoxSide(GraphicsContext& graphics_context, - int x1, - int y1, - int x2, - int y2, - BoxSide side, - Color color, - int thickness, - EBorderStyle style, - bool antialias) { - DCHECK_GT(thickness, 0); - - GraphicsContextStateSaver state_saver(graphics_context); - graphics_context.SetShouldAntialias(antialias); - graphics_context.SetStrokeColor(color); - graphics_context.SetStrokeThickness(thickness); - graphics_context.SetStrokeStyle( - style == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke); - - switch (side) { - case BoxSide::kBottom: - case BoxSide::kTop: { - int mid_y = y1 + thickness / 2; - graphics_context.DrawLine(IntPoint(x1, mid_y), IntPoint(x2, mid_y)); - break; - } - case BoxSide::kRight: - case BoxSide::kLeft: { - int mid_x = x1 + thickness / 2; - graphics_context.DrawLine(IntPoint(mid_x, y1), IntPoint(mid_x, y2)); - break; - } - } -} - -void DrawDoubleBoxSide(GraphicsContext& graphics_context, - int x1, - int y1, - int x2, - int y2, - int length, - BoxSide side, - Color color, - float thickness, - int adjacent_width1, - int adjacent_width2, - bool antialias) { - int third_of_thickness = (thickness + 1) / 3; - DCHECK_GT(third_of_thickness, 0); - - if (!adjacent_width1 && !adjacent_width2) { - StrokeStyle old_stroke_style = graphics_context.GetStrokeStyle(); - graphics_context.SetStrokeStyle(kNoStroke); - graphics_context.SetFillColor(color); - - bool was_antialiased = graphics_context.ShouldAntialias(); - graphics_context.SetShouldAntialias(antialias); - - switch (side) { - case BoxSide::kTop: - case BoxSide::kBottom: - graphics_context.DrawRect(IntRect(x1, y1, length, third_of_thickness)); - graphics_context.DrawRect( - IntRect(x1, y2 - third_of_thickness, length, third_of_thickness)); - break; - case BoxSide::kLeft: - case BoxSide::kRight: - graphics_context.DrawRect(IntRect(x1, y1, third_of_thickness, length)); - graphics_context.DrawRect( - IntRect(x2 - third_of_thickness, y1, third_of_thickness, length)); - break; - } - - graphics_context.SetShouldAntialias(was_antialiased); - graphics_context.SetStrokeStyle(old_stroke_style); - return; - } - - int adjacent1_big_third = - ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 3; - int adjacent2_big_third = - ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 3; - - switch (side) { - case BoxSide::kTop: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), - y1, x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), - y1 + third_of_thickness, side, color, EBorderStyle::kSolid, - adjacent1_big_third, adjacent2_big_third, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), - y2 - third_of_thickness, - x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), y2, side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - break; - case BoxSide::kLeft: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1, - y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), - x1 + third_of_thickness, - y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x2 - third_of_thickness, - y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), x2, - y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - break; - case BoxSide::kBottom: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), y1, - x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), - y1 + third_of_thickness, side, color, EBorderStyle::kSolid, - adjacent1_big_third, adjacent2_big_third, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), - y2 - third_of_thickness, - x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), y2, side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - break; - case BoxSide::kRight: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1, y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), - x1 + third_of_thickness, - y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x2 - third_of_thickness, - y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), x2, - y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side, color, - EBorderStyle::kSolid, adjacent1_big_third, adjacent2_big_third, - antialias); - break; - default: - break; - } -} - -void DrawRidgeOrGrooveBoxSide(GraphicsContext& graphics_context, - int x1, - int y1, - int x2, - int y2, - BoxSide side, - Color color, - EBorderStyle style, - int adjacent_width1, - int adjacent_width2, - bool antialias) { - EBorderStyle s1; - EBorderStyle s2; - if (style == EBorderStyle::kGroove) { - s1 = EBorderStyle::kInset; - s2 = EBorderStyle::kOutset; - } else { - s1 = EBorderStyle::kOutset; - s2 = EBorderStyle::kInset; - } - - int adjacent1_big_half = - ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 2; - int adjacent2_big_half = - ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 2; - - switch (side) { - case BoxSide::kTop: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max(-adjacent_width1, 0) / 2, y1, - x2 - std::max(-adjacent_width2, 0) / 2, (y1 + y2 + 1) / 2, side, - color, s1, adjacent1_big_half, adjacent2_big_half, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max(adjacent_width1 + 1, 0) / 2, - (y1 + y2 + 1) / 2, x2 - std::max(adjacent_width2 + 1, 0) / 2, y2, - side, color, s2, adjacent_width1 / 2, adjacent_width2 / 2, antialias); - break; - case BoxSide::kLeft: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1, y1 + std::max(-adjacent_width1, 0) / 2, - (x1 + x2 + 1) / 2, y2 - std::max(-adjacent_width2, 0) / 2, side, - color, s1, adjacent1_big_half, adjacent2_big_half, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, (x1 + x2 + 1) / 2, - y1 + std::max(adjacent_width1 + 1, 0) / 2, x2, - y2 - std::max(adjacent_width2 + 1, 0) / 2, side, color, s2, - adjacent_width1 / 2, adjacent_width2 / 2, antialias); - break; - case BoxSide::kBottom: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max(adjacent_width1, 0) / 2, y1, - x2 - std::max(adjacent_width2, 0) / 2, (y1 + y2 + 1) / 2, side, color, - s2, adjacent1_big_half, adjacent2_big_half, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1 + std::max(-adjacent_width1 + 1, 0) / 2, - (y1 + y2 + 1) / 2, x2 - std::max(-adjacent_width2 + 1, 0) / 2, y2, - side, color, s1, adjacent_width1 / 2, adjacent_width2 / 2, antialias); - break; - case BoxSide::kRight: - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, x1, y1 + std::max(adjacent_width1, 0) / 2, - (x1 + x2 + 1) / 2, y2 - std::max(adjacent_width2, 0) / 2, side, color, - s2, adjacent1_big_half, adjacent2_big_half, antialias); - ObjectPainterBase::DrawLineForBoxSide( - graphics_context, (x1 + x2 + 1) / 2, - y1 + std::max(-adjacent_width1 + 1, 0) / 2, x2, - y2 - std::max(-adjacent_width2 + 1, 0) / 2, side, color, s1, - adjacent_width1 / 2, adjacent_width2 / 2, antialias); - break; - } -} - -void DrawSolidBoxSide(GraphicsContext& graphics_context, - int x1, - int y1, - int x2, - int y2, - BoxSide side, - Color color, - int adjacent_width1, - int adjacent_width2, - bool antialias) { - DCHECK_GE(x2, x1); - DCHECK_GE(y2, y1); - - if (!adjacent_width1 && !adjacent_width2) { - // Tweak antialiasing to match the behavior of fillQuad(); - // this matters for rects in transformed contexts. - bool was_antialiased = graphics_context.ShouldAntialias(); - if (antialias != was_antialiased) - graphics_context.SetShouldAntialias(antialias); - graphics_context.FillRect(IntRect(x1, y1, x2 - x1, y2 - y1), color); - if (antialias != was_antialiased) - graphics_context.SetShouldAntialias(was_antialiased); - return; - } - - FloatPoint quad[4]; - switch (side) { - case BoxSide::kTop: - quad[0] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y1); - quad[1] = FloatPoint(x1 + std::max(adjacent_width1, 0), y2); - quad[2] = FloatPoint(x2 - std::max(adjacent_width2, 0), y2); - quad[3] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y1); - break; - case BoxSide::kBottom: - quad[0] = FloatPoint(x1 + std::max(adjacent_width1, 0), y1); - quad[1] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y2); - quad[2] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y2); - quad[3] = FloatPoint(x2 - std::max(adjacent_width2, 0), y1); - break; - case BoxSide::kLeft: - quad[0] = FloatPoint(x1, y1 + std::max(-adjacent_width1, 0)); - quad[1] = FloatPoint(x1, y2 - std::max(-adjacent_width2, 0)); - quad[2] = FloatPoint(x2, y2 - std::max(adjacent_width2, 0)); - quad[3] = FloatPoint(x2, y1 + std::max(adjacent_width1, 0)); - break; - case BoxSide::kRight: - quad[0] = FloatPoint(x1, y1 + std::max(adjacent_width1, 0)); - quad[1] = FloatPoint(x1, y2 - std::max(adjacent_width2, 0)); - quad[2] = FloatPoint(x2, y2 - std::max(-adjacent_width2, 0)); - quad[3] = FloatPoint(x2, y1 + std::max(-adjacent_width1, 0)); - break; - } - - FillQuad(graphics_context, quad, color, antialias); -} - -float GetFocusRingBorderRadius(const ComputedStyle& style) { - // Default style is border-radius equal to outline width. - float border_radius = style.GetOutlineStrokeWidthForFocusRing(); - - if (::features::IsFormControlsRefreshEnabled() && !style.HasAuthorBorder() && - style.HasEffectiveAppearance()) { - // For the elements that have not been styled and that have an appearance, - // the focus ring should use the same border radius as the one used for - // drawing the element. - absl::optional<ui::NativeTheme::Part> part; - switch (style.EffectiveAppearance()) { - case kCheckboxPart: - part = ui::NativeTheme::kCheckbox; - break; - case kRadioPart: - part = ui::NativeTheme::kRadio; - break; - case kPushButtonPart: - case kSquareButtonPart: - case kButtonPart: - part = ui::NativeTheme::kPushButton; - break; - case kTextFieldPart: - case kTextAreaPart: - case kSearchFieldPart: - part = ui::NativeTheme::kTextField; - break; - default: - break; - } - if (part) { - border_radius = - ui::NativeTheme::GetInstanceForWeb()->GetBorderRadiusForPart( - part.value(), style.Width().GetFloatValue(), - style.Height().GetFloatValue()); - - // Form controls send to NativeTheme have zoom applied. But the focus ring - // outline does not. Apply zoom to checkbox focus ring. - return (style.EffectiveAppearance() == kCheckboxPart) - ? border_radius * style.EffectiveZoom() - : border_radius; - } - } - - return border_radius; -} - -} // anonymous namespace - -void ObjectPainterBase::PaintOutlineRects( - const PaintInfo& paint_info, - const Vector<PhysicalRect>& outline_rects, - const ComputedStyle& style) { - Vector<IntRect> pixel_snapped_outline_rects; - for (auto& r : outline_rects) - pixel_snapped_outline_rects.push_back(PixelSnappedIntRect(r)); - - Color color = style.VisitedDependentColor(GetCSSPropertyOutlineColor()); - if (style.OutlineStyleIsAuto()) { - // Logic in draw focus ring is dependent on whether the border is large - // enough to have an inset outline. Use the smallest border edge for that - // test. - float min_border_width = - std::min(std::min(style.BorderTopWidth(), style.BorderBottomWidth()), - std::min(style.BorderLeftWidth(), style.BorderRightWidth())); - float border_radius = GetFocusRingBorderRadius(style); - paint_info.context.DrawFocusRing( - pixel_snapped_outline_rects, style.GetOutlineStrokeWidthForFocusRing(), - style.OutlineOffsetInt(), border_radius, min_border_width, color, - style.UsedColorScheme()); - return; - } - - IntRect united_outline_rect = UnionRect(pixel_snapped_outline_rects); - if (united_outline_rect == pixel_snapped_outline_rects[0]) { - PaintSingleRectangleOutline(paint_info, united_outline_rect, style, color); - return; - } - PaintComplexOutline(paint_info.context, pixel_snapped_outline_rects, style, - color); -} - -void ObjectPainterBase::DrawLineForBoxSide(GraphicsContext& graphics_context, - float x1, - float y1, - float x2, - float y2, - BoxSide side, - Color color, - EBorderStyle style, - int adjacent_width1, - int adjacent_width2, - bool antialias) { - float thickness; - float length; - if (side == BoxSide::kTop || side == BoxSide::kBottom) { - thickness = y2 - y1; - length = x2 - x1; - } else { - thickness = x2 - x1; - length = y2 - y1; - } - - // We would like this check to be an ASSERT as we don't want to draw empty - // borders. However nothing guarantees that the following recursive calls to - // ObjectPainterBase::DrawLineForBoxSide will have positive thickness and - // length. - if (length <= 0 || thickness <= 0) - return; - - if (style == EBorderStyle::kDouble && thickness < 3) - style = EBorderStyle::kSolid; - - switch (style) { - case EBorderStyle::kNone: - case EBorderStyle::kHidden: - return; - case EBorderStyle::kDotted: - case EBorderStyle::kDashed: - DrawDashedOrDottedBoxSide(graphics_context, x1, y1, x2, y2, side, color, - thickness, style, antialias); - break; - case EBorderStyle::kDouble: - DrawDoubleBoxSide(graphics_context, x1, y1, x2, y2, length, side, color, - thickness, adjacent_width1, adjacent_width2, antialias); - break; - case EBorderStyle::kRidge: - case EBorderStyle::kGroove: - DrawRidgeOrGrooveBoxSide(graphics_context, x1, y1, x2, y2, side, color, - style, adjacent_width1, adjacent_width2, - antialias); - break; - case EBorderStyle::kInset: - // FIXME: Maybe we should lighten the colors on one side like Firefox. - // https://bugs.webkit.org/show_bug.cgi?id=58608 - if (side == BoxSide::kTop || side == BoxSide::kLeft) - color = color.Dark(); - FALLTHROUGH; - case EBorderStyle::kOutset: - if (style == EBorderStyle::kOutset && - (side == BoxSide::kBottom || side == BoxSide::kRight)) - color = color.Dark(); - FALLTHROUGH; - case EBorderStyle::kSolid: - DrawSolidBoxSide(graphics_context, x1, y1, x2, y2, side, color, - adjacent_width1, adjacent_width2, antialias); - break; - } -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/object_painter_base.h b/chromium/third_party/blink/renderer/core/paint/object_painter_base.h deleted file mode 100644 index 4f83f196202..00000000000 --- a/chromium/third_party/blink/renderer/core/paint/object_painter_base.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINTER_BASE_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINTER_BASE_H_ - -#include "third_party/blink/renderer/core/style/computed_style_constants.h" -#include "third_party/blink/renderer/platform/geometry/int_rect.h" -#include "third_party/blink/renderer/platform/graphics/color.h" -#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" -#include "third_party/blink/renderer/platform/wtf/vector.h" - -namespace blink { - -class ComputedStyle; -class GraphicsContext; -struct PaintInfo; -struct PhysicalRect; - -// Base class for object painting. Has no dependencies on the layout tree and -// thus provides functionality and definitions that can be shared between both -// legacy layout and LayoutNG. -class ObjectPainterBase { - STACK_ALLOCATED(); - - public: - static void DrawBoxSide(GraphicsContext& context, - const IntRect& snapped_edge_rect, - BoxSide side, - Color color, - EBorderStyle style) { - DrawLineForBoxSide(context, snapped_edge_rect.X(), snapped_edge_rect.Y(), - snapped_edge_rect.MaxX(), snapped_edge_rect.MaxY(), side, - color, style, 0, 0, true); - } - - // TODO(wangxianzhu): The float parameters are truncated to int in the - // function, which implicitly snaps to whole pixels incorrectly. We should - // always use the above function. For now the only outside caller is - // BoxBorderPainter::PaintOneBorderSide(). - static void DrawLineForBoxSide(GraphicsContext&, - float x1, - float y1, - float x2, - float y2, - BoxSide, - Color, - EBorderStyle, - int adjacent_edge_width1, - int adjacent_edge_width2, - bool antialias = false); - - protected: - ObjectPainterBase() = default; - void PaintOutlineRects(const PaintInfo&, - const Vector<PhysicalRect>&, - const ComputedStyle&); -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINTER_BASE_H_ diff --git a/chromium/third_party/blink/renderer/core/paint/outline_painter.cc b/chromium/third_party/blink/renderer/core/paint/outline_painter.cc new file mode 100644 index 00000000000..bb1a6d18914 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/outline_painter.cc @@ -0,0 +1,724 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/paint/outline_painter.h" + +#include "build/build_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" +#include "third_party/blink/renderer/core/paint/box_border_painter.h" +#include "third_party/blink/renderer/core/paint/rounded_border_geometry.h" +#include "third_party/blink/renderer/core/style/border_edge.h" +#include "third_party/blink/renderer/core/style/computed_style.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/graphics/color.h" +#include "third_party/blink/renderer/platform/graphics/graphics_context.h" +#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" +#include "third_party/blink/renderer/platform/graphics/path.h" +#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" +#include "ui/native_theme/native_theme.h" + +namespace blink { + +namespace { + +struct OutlineEdgeInfo { + int x1; + int y1; + int x2; + int y2; + BoxSide side; +}; + +// Adjust length of edges if needed. Returns the width of the joint. +int AdjustJoint(int outline_width, + OutlineEdgeInfo& edge1, + OutlineEdgeInfo& edge2) { + // A clockwise joint: + // - needs no adjustment of edge length because our edges are along the + // clockwise outer edge of the outline; + // - needs a positive adjacent joint width (required by + // BoxBorderPainter::DrawLineForBoxSide). + // A counterclockwise joint: + // - needs to increase the edge length to include the joint; + // - needs a negative adjacent joint width (required by + // BoxBorderPainter::DrawLineForBoxSide). + switch (edge1.side) { + case BoxSide::kTop: + switch (edge2.side) { + case BoxSide::kRight: // Clockwise + return outline_width; + case BoxSide::kLeft: // Counterclockwise + edge1.x2 += outline_width; + edge2.y2 += outline_width; + return -outline_width; + default: // Same side or no joint. + return 0; + } + case BoxSide::kRight: + switch (edge2.side) { + case BoxSide::kBottom: // Clockwise + return outline_width; + case BoxSide::kTop: // Counterclockwise + edge1.y2 += outline_width; + edge2.x1 -= outline_width; + return -outline_width; + default: // Same side or no joint. + return 0; + } + case BoxSide::kBottom: + switch (edge2.side) { + case BoxSide::kLeft: // Clockwise + return outline_width; + case BoxSide::kRight: // Counterclockwise + edge1.x1 -= outline_width; + edge2.y1 -= outline_width; + return -outline_width; + default: // Same side or no joint. + return 0; + } + case BoxSide::kLeft: + switch (edge2.side) { + case BoxSide::kTop: // Clockwise + return outline_width; + case BoxSide::kBottom: // Counterclockwise + edge1.y1 -= outline_width; + edge2.x2 += outline_width; + return -outline_width; + default: // Same side or no joint. + return 0; + } + default: + NOTREACHED(); + return 0; + } +} + +// A negative outline-offset should not cause the rendered outline shape to +// become smaller than twice the computed value of the outline-width, in each +// direction separately. See: https://drafts.csswg.org/css-ui/#outline-offset +int AdjustedOutlineOffsetX(const IntRect& rect, int offset) { + return std::max(offset, -rect.Width() / 2); +} +int AdjustedOutlineOffsetY(const IntRect& rect, int offset) { + return std::max(offset, -rect.Height() / 2); +} + +// Construct a clockwise path along the outer edge of the region covered by +// |rects| expanded by |outline_offset| (which can be negative and clamped by +// the rect size) and |additional_outset| (which should be non-negative). +bool ComputeRightAnglePath(SkPath& path, + const Vector<IntRect>& rects, + int outline_offset, + int additional_outset) { + DCHECK_GE(additional_outset, 0); + SkRegion region; + for (auto& r : rects) { + IntRect rect = r; + rect.InflateX(AdjustedOutlineOffsetX(rect, outline_offset)); + rect.InflateY(AdjustedOutlineOffsetY(rect, outline_offset)); + rect.Inflate(additional_outset); + region.op(rect, SkRegion::kUnion_Op); + } + return region.getBoundaryPath(&path); +} + +struct Line { + SkPoint start; + SkPoint end; +}; + +// Merge line2 into line1 if they are in the same straight line. +bool MergeLineIfPossible(Line& line1, const Line& line2) { + DCHECK(line1.end == line2.start); + if ((line1.start.x() == line1.end.x() && line1.start.x() == line2.end.x()) || + (line1.start.y() == line1.end.y() && line1.start.y() == line2.end.y())) { + line1.end = line2.end; + return true; + } + return false; +} + +// Iterate a right angle |path| by running |contour_action| on each contour. +// The path contains one or more contours each of which is like (kMove_Verb, +// kLine_Verb, ..., kClose_Verb). Each line must be either horizontal or +// vertical. Each pair of adjacent lines (including the last and the first) +// should either create a right angle or be in the same straight line. +template <typename Action> +void IterateRightAnglePath(const SkPath& path, const Action& contour_action) { + SkPath::Iter iter(path, /*forceClose*/ true); + SkPoint points[4]; + Vector<Line> lines; + for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb; + verb = iter.next(points)) { + switch (verb) { + case SkPath::kMove_Verb: + DCHECK(lines.IsEmpty()); + break; + case SkPath::kLine_Verb: { + Line new_line{points[0], points[1]}; + if (lines.IsEmpty() || !MergeLineIfPossible(lines.back(), new_line)) { + lines.push_back(new_line); + DCHECK(lines.size() == 1 || + lines.back().start == lines[lines.size() - 2].end); + } + break; + } + case SkPath::kClose_Verb: { + if (lines.size() >= 4u) { + if (MergeLineIfPossible(lines.back(), lines.front())) { + lines.front() = lines.back(); + lines.pop_back(); + } + DCHECK(lines.front().start == lines.back().end); + DCHECK_GE(lines.size(), 4u); + contour_action(lines); + } + lines.clear(); + break; + } + default: + NOTREACHED(); + } + } +} + +void PaintComplexRightAngleOutlineContour(GraphicsContext& context, + const Vector<Line>& lines, + const ComputedStyle& style, + Color color) { + int width = style.OutlineWidthInt(); + Vector<OutlineEdgeInfo> edges; + edges.ReserveInitialCapacity(lines.size()); + for (auto& line : lines) { + auto& edge = edges.emplace_back(); + edge.x1 = SkScalarTruncToInt(line.start.x()); + edge.y1 = SkScalarTruncToInt(line.start.y()); + edge.x2 = SkScalarTruncToInt(line.end.x()); + edge.y2 = SkScalarTruncToInt(line.end.y()); + if (edge.x1 == edge.x2) { + if (edge.y1 < edge.y2) { + edge.x1 -= width; + edge.side = BoxSide::kRight; + } else { + std::swap(edge.y1, edge.y2); + edge.x2 += width; + edge.side = BoxSide::kLeft; + } + } else { + DCHECK(edge.y1 == edge.y2); + if (edge.x1 < edge.x2) { + edge.y2 += width; + edge.side = BoxSide::kTop; + } else { + std::swap(edge.x1, edge.x2); + edge.y1 -= width; + edge.side = BoxSide::kBottom; + } + } + } + + int first_adjacent_width = AdjustJoint(width, edges.back(), edges.front()); + // The width of the angled part of starting and ending joint of the current + // edge. + int adjacent_width_start = first_adjacent_width; + int adjacent_width_end; + for (wtf_size_t i = 0; i < edges.size(); ++i) { + OutlineEdgeInfo& edge = edges[i]; + adjacent_width_end = i == edges.size() - 1 + ? first_adjacent_width + : AdjustJoint(width, edge, edges[i + 1]); + int adjacent_width1 = adjacent_width_start; + int adjacent_width2 = adjacent_width_end; + if (edge.side == BoxSide::kLeft || edge.side == BoxSide::kBottom) + std::swap(adjacent_width1, adjacent_width2); + BoxBorderPainter::DrawLineForBoxSide( + context, edge.x1, edge.y1, edge.x2, edge.y2, edge.side, color, + style.OutlineStyle(), adjacent_width1, adjacent_width2, + /*antialias*/ false); + adjacent_width_start = adjacent_width_end; + } +} + +void PaintComplexRightAngleOutline(GraphicsContext& context, + const Vector<IntRect>& rects, + const ComputedStyle& style) { + DCHECK(!style.OutlineStyleIsAuto()); + + SkPath path; + if (!ComputeRightAnglePath(path, rects, style.OutlineOffsetInt(), + style.OutlineWidthInt())) { + return; + } + + Color color = style.VisitedDependentColor(GetCSSPropertyOutlineColor()); + bool use_transparency_layer = color.HasAlpha(); + if (use_transparency_layer) { + context.BeginLayer(static_cast<float>(color.Alpha()) / 255); + color.SetRGB(color.Red(), color.Green(), color.Blue()); + } + + IterateRightAnglePath(path, [&](const Vector<Line>& lines) { + PaintComplexRightAngleOutlineContour(context, lines, style, color); + }); + + if (use_transparency_layer) + context.EndLayer(); +} + +// Given 3 points defining a right angle corner, returns |p2| shifted to make +// the containing path shrink by |inset|. +SkPoint ShrinkCorner(const SkPoint& p1, + const SkPoint& p2, + const SkPoint& p3, + int inset) { + if (p1.x() == p2.x()) { + if (p1.y() < p2.y()) { + return p2.x() < p3.x() ? p2 + SkVector::Make(-inset, inset) + : p2 + SkVector::Make(-inset, -inset); + } + return p2.x() < p3.x() ? p2 + SkVector::Make(inset, inset) + : p2 + SkVector::Make(inset, -inset); + } + if (p1.x() < p2.x()) { + return p2.y() < p3.y() ? p2 + SkVector::Make(-inset, inset) + : p2 + SkVector::Make(inset, inset); + } + return p2.y() < p3.y() ? p2 + SkVector::Make(-inset, -inset) + : p2 + SkVector::Make(inset, -inset); +} + +void ShrinkRightAnglePath(SkPath& path, int inset) { + SkPath input; + std::swap(input, path); + IterateRightAnglePath(input, [&path, inset](const Vector<Line>& lines) { + for (wtf_size_t i = 0; i < lines.size(); i++) { + const SkPoint& prev_point = + lines[i == 0 ? lines.size() - 1 : i - 1].start; + SkPoint new_point = + ShrinkCorner(prev_point, lines[i].start, lines[i].end, inset); + if (i == 0) { + path.moveTo(new_point); + } else { + path.lineTo(new_point); + } + } + path.close(); + }); +} + +FloatRoundedRect::Radii ComputeCornerRadii( + const ComputedStyle& style, + const PhysicalRect& reference_border_rect, + float offset) { + return RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + style, reference_border_rect, + LayoutRectOutsets(offset, offset, offset, offset)) + .GetRadii(); +} + +// Given 3 points defining a right angle corner, returns the corresponding +// corner in |convex_radii| or |concave_radii|. +FloatSize GetRadiiCorner(const FloatRoundedRect::Radii& convex_radii, + const FloatRoundedRect::Radii& concave_radii, + const SkPoint& p1, + const SkPoint& p2, + const SkPoint& p3) { + if (p1.x() == p2.x()) { + if (p1.y() == p2.y() || p2.x() == p3.x()) + return FloatSize(); + DCHECK_EQ(p2.y(), p3.y()); + if (p1.y() < p2.y()) { + return p2.x() < p3.x() ? concave_radii.BottomLeft() + : convex_radii.BottomRight(); + } + return p2.x() < p3.x() ? convex_radii.TopLeft() : concave_radii.TopRight(); + } + DCHECK_EQ(p1.y(), p2.y()); + if (p2.x() != p3.x() || p2.y() == p3.y()) + return FloatSize(); + if (p1.x() < p2.x()) { + return p2.y() < p3.y() ? convex_radii.TopRight() + : concave_radii.BottomRight(); + } + return p2.y() < p3.y() ? concave_radii.TopLeft() : convex_radii.BottomLeft(); +} + +// Shorten |line| between rounded corners. +void AdjustLineBetweenCorners(Line& line, + const FloatRoundedRect::Radii& convex_radii, + const FloatRoundedRect::Radii& concave_radii, + const SkPoint& prev_point, + const SkPoint& next_point) { + FloatSize corner1 = GetRadiiCorner(convex_radii, concave_radii, prev_point, + line.start, line.end); + FloatSize corner2 = GetRadiiCorner(convex_radii, concave_radii, line.start, + line.end, next_point); + if (line.start.x() == line.end.x()) { + // |line| is vertical, and adjacent lines are horizontal. + float height = std::abs(line.end.y() - line.start.y()); + float corner1_height = corner1.Height(); + float corner2_height = corner2.Height(); + if (corner1_height + corner2_height > height) { + // Scale down the corner heights to make the corners fit in |height|. + float scale = height / (corner1_height + corner2_height); + corner1_height = floorf(corner1_height * scale); + corner2_height = floorf(corner2_height * scale); + } + if (line.start.y() < line.end.y()) { + line.start.offset(0, corner1_height); + line.end.offset(0, -corner2_height); + } else { + line.start.offset(0, -corner1_height); + line.end.offset(0, corner2_height); + } + } else { + // |line| is horizontal, and adjacent lines are vertical. + float width = std::abs(line.end.x() - line.start.x()); + float corner1_width = corner1.Width(); + float corner2_width = corner2.Width(); + if (corner1_width + corner2_width > width) { + // Scale down the corner widths to make the corners fit in |width|. + float scale = width / (corner1_width + corner2_width); + corner1_width = floorf(corner1_width * scale); + corner2_width = floorf(corner2_width * scale); + } + if (line.start.x() < line.end.x()) { + line.start.offset(corner1_width, 0); + line.end.offset(-corner2_width, 0); + } else { + line.start.offset(-corner1_width, 0); + line.end.offset(corner2_width, 0); + } + } +} + +// Create a rounded path from a right angle |path| by +// - inserting arc segments for corners; +// - adjusting length of the lines. +void AddCornerRadiiToPath(SkPath& path, + const FloatRoundedRect::Radii& convex_radii, + const FloatRoundedRect::Radii& concave_radii) { + SkPath input; + input.swap(path); + IterateRightAnglePath(input, [&](const Vector<Line>& lines) { + auto new_lines = lines; + for (wtf_size_t i = 0; i < lines.size(); i++) { + const SkPoint& prev_point = + lines[i == 0 ? lines.size() - 1 : i - 1].start; + const SkPoint& next_point = lines[i == lines.size() - 1 ? 0 : i + 1].end; + AdjustLineBetweenCorners(new_lines[i], convex_radii, concave_radii, + prev_point, next_point); + } + // Generate the new contour into |path|. + DCHECK_EQ(lines.size(), new_lines.size()); + path.moveTo(new_lines[0].start); + for (wtf_size_t i = 0; i < new_lines.size(); i++) { + const Line& line = new_lines[i]; + if (line.end != line.start) + path.lineTo(line.end); + const Line& next_line = new_lines[i == lines.size() - 1 ? 0 : i + 1]; + if (line.end != next_line.start) { + constexpr float kCornerConicWeight = 0.707106781187; // 1/sqrt(2) + // This produces a 90 degree arc from line.end towards lines[i].end + // to next_line.start. + path.conicTo(lines[i].end, next_line.start, kCornerConicWeight); + } + } + path.close(); + }); +} + +class ComplexRoundedOutlinePainter { + public: + ComplexRoundedOutlinePainter(GraphicsContext& context, + const Vector<IntRect>& rects, + const PhysicalRect& reference_border_rect, + const ComputedStyle& style) + : context_(context), + rects_(rects), + reference_border_rect_(reference_border_rect), + style_(style), + outline_style_(style.OutlineStyle()), + offset_(style.OutlineOffsetInt()), + width_(style.OutlineWidthInt()), + color_(style.VisitedDependentColor(GetCSSPropertyOutlineColor())) { + DCHECK(!style.OutlineStyleIsAuto()); + if (width_ <= 2 && outline_style_ == EBorderStyle::kDouble) { + outline_style_ = EBorderStyle::kSolid; + } else if (width_ == 1 && (outline_style_ == EBorderStyle::kRidge || + outline_style_ == EBorderStyle::kGroove)) { + outline_style_ = EBorderStyle::kSolid; + Color dark = color_.Dark(); + color_ = Color((color_.Red() + dark.Red()) / 2, + (color_.Green() + dark.Green()) / 2, + (color_.Blue() + dark.Blue()) / 2, color_.Alpha()); + } + } + + bool Paint() { + if (width_ == 0) + return true; + + if (!ComputeRightAnglePath(right_angle_outer_path_, rects_, offset_, + width_)) { + return true; + } + + SkPath outer_path = right_angle_outer_path_; + SkPath inner_path = right_angle_outer_path_; + ShrinkRightAnglePath(inner_path, width_); + auto inner_radii = ComputeRadii(0); + auto outer_radii = ComputeRadii(width_); + AddCornerRadiiToPath(outer_path, outer_radii, inner_radii); + AddCornerRadiiToPath(inner_path, inner_radii, outer_radii); + + GraphicsContextStateSaver saver(context_); + context_.ClipPath(outer_path, kAntiAliased); + context_.ClipOut(inner_path); + context_.SetFillColor(color_); + + switch (outline_style_) { + case EBorderStyle::kSolid: + context_.FillRect(outer_path.getBounds()); + break; + case EBorderStyle::kDouble: + PaintDoubleOutline(); + break; + case EBorderStyle::kDotted: + case EBorderStyle::kDashed: + PaintDottedOrDashedOutline(); + break; + default: + // TODO(wangxianzhu): Draw kRidge, kGroove, kInset, kOutset by calling + // BoxBorderPainter::DrawBoxSideFromPath() for each segment of the path. + return false; + } + return true; + } + + private: + void PaintDoubleOutline() { + SkPath inner_third_path = right_angle_outer_path_; + SkPath outer_third_path = right_angle_outer_path_; + int stroke_width = std::round(width_ / 3.0); + ShrinkRightAnglePath(inner_third_path, width_ - stroke_width); + ShrinkRightAnglePath(outer_third_path, stroke_width); + auto inner_third_radii = ComputeRadii(stroke_width); + auto outer_third_radii = ComputeRadii(width_ - stroke_width); + AddCornerRadiiToPath(inner_third_path, inner_third_radii, + outer_third_radii); + AddCornerRadiiToPath(outer_third_path, outer_third_radii, + inner_third_radii); + { + GraphicsContextStateSaver saver(context_); + context_.ClipOut(outer_third_path); + context_.FillRect(right_angle_outer_path_.getBounds()); + } + context_.FillPath(inner_third_path); + } + + void PaintDottedOrDashedOutline() { + SkPath center_path = right_angle_outer_path_; + int center_outset = width_ / 2; + ShrinkRightAnglePath(center_path, width_ - center_outset); + auto center_radii = ComputeRadii(center_outset); + AddCornerRadiiToPath(center_path, center_radii, center_radii); + context_.SetStrokeColor(color_); + auto stroke_style = + outline_style_ == EBorderStyle::kDashed ? kDashedStroke : kDottedStroke; + context_.SetStrokeStyle(stroke_style); + if (StrokeData::StrokeIsDashed(width_, stroke_style)) { + // Draw wider to fill the clip area between inner_path_ and outer_path_, + // to get smoother edges, and even stroke thickness when the outline is + // thin. + context_.SetStrokeThickness(width_ + 2); + } else { + context_.SetStrokeThickness(width_); + context_.SetLineCap(kRoundCap); + } + context_.StrokePath(center_path, Path(center_path).length(), width_); + } + + FloatRoundedRect::Radii ComputeRadii(int outset) const { + return ComputeCornerRadii(style_, reference_border_rect_, offset_ + outset); + } + + GraphicsContext& context_; + const Vector<IntRect>& rects_; + const PhysicalRect& reference_border_rect_; + const ComputedStyle& style_; + EBorderStyle outline_style_; + int offset_; + int width_; + Color color_; + SkPath right_angle_outer_path_; +}; + +float DefaultFocusRingCornerRadius(const ComputedStyle& style) { + // Default style is corner radius equal to outline width. + return style.FocusRingStrokeWidth(); +} + +FloatRoundedRect::Radii GetFocusRingCornerRadii( + const ComputedStyle& style, + const PhysicalRect& reference_border_rect) { + if (style.HasBorderRadius() && + (!style.HasEffectiveAppearance() || style.HasAuthorBorderRadius())) { + auto radii = ComputeCornerRadii(style, reference_border_rect, + style.OutlineOffsetInt()); + radii.SetMinimumRadius(DefaultFocusRingCornerRadius(style)); + return radii; + } + + if (!style.HasAuthorBorder() && style.HasEffectiveAppearance()) { + // For the elements that have not been styled and that have an appearance, + // the focus ring should use the same border radius as the one used for + // drawing the element. + absl::optional<ui::NativeTheme::Part> part; + switch (style.EffectiveAppearance()) { + case kCheckboxPart: + part = ui::NativeTheme::kCheckbox; + break; + case kRadioPart: + part = ui::NativeTheme::kRadio; + break; + case kPushButtonPart: + case kSquareButtonPart: + case kButtonPart: + part = ui::NativeTheme::kPushButton; + break; + case kTextFieldPart: + case kTextAreaPart: + case kSearchFieldPart: + part = ui::NativeTheme::kTextField; + break; + default: + break; + } + if (part) { + float corner_radius = + ui::NativeTheme::GetInstanceForWeb()->GetBorderRadiusForPart( + part.value(), style.Width().GetFloatValue(), + style.Height().GetFloatValue()); + corner_radius = + ui::NativeTheme::GetInstanceForWeb()->AdjustBorderRadiusByZoom( + part.value(), corner_radius, style.EffectiveZoom()); + return FloatRoundedRect::Radii(corner_radius); + } + } + + return FloatRoundedRect::Radii(DefaultFocusRingCornerRadius(style)); +} + +void PaintSingleFocusRing(GraphicsContext& context, + const Vector<IntRect>& rects, + float width, + int offset, + const FloatRoundedRect::Radii& corner_radii, + const Color& color) { + DCHECK(!rects.IsEmpty()); + SkPath path; + if (!ComputeRightAnglePath(path, rects, offset, 0)) + return; + + SkRect rect; + if (path.isRect(&rect)) { + context.DrawFocusRingRect(FloatRoundedRect(rect, corner_radii), color, + width); + return; + } + + absl::optional<float> corner_radius = corner_radii.UniformRadius(); + if (corner_radius.has_value()) { + context.DrawFocusRingPath(path, color, width, *corner_radius); + return; + } + + // Bake non-uniform radii into the path, and draw the path with 0 corner + // radius as the path already has rounded corners. + AddCornerRadiiToPath(path, corner_radii, corner_radii); + context.DrawFocusRingPath(path, color, width, 0); +} + +void PaintFocusRing(GraphicsContext& context, + const Vector<IntRect>& rects, + const ComputedStyle& style, + const FloatRoundedRect::Radii& corner_radii) { + Color inner_color = style.VisitedDependentColor(GetCSSPropertyOutlineColor()); +#if !defined(OS_MAC) + if (style.DarkColorScheme()) + inner_color = Color::kWhite; +#endif + + const float outer_ring_width = style.FocusRingOuterStrokeWidth(); + const float inner_ring_width = style.FocusRingInnerStrokeWidth(); + const int offset = style.FocusRingOffset(); + Color outer_color = + style.DarkColorScheme() ? Color(0x10, 0x10, 0x10) : Color::kWhite; + PaintSingleFocusRing(context, rects, outer_ring_width, + offset + std::ceil(inner_ring_width), corner_radii, + outer_color); + // Draw the inner ring using |outer_ring_width| (which should be wider than + // the additional offset of the outer ring) over the outer ring to ensure no + // gaps or AA artifacts. + DCHECK_GE(outer_ring_width, std::ceil(inner_ring_width)); + PaintSingleFocusRing(context, rects, outer_ring_width, offset, corner_radii, + inner_color); +} + +} // anonymous namespace + +void OutlinePainter::PaintOutlineRects( + GraphicsContext& context, + const Vector<PhysicalRect>& outline_rects, + const ComputedStyle& style) { + Vector<IntRect> pixel_snapped_outline_rects; + for (auto& r : outline_rects) { + IntRect pixel_snapped_rect = PixelSnappedIntRect(r); + // Keep empty rect for normal outline, but not for focus rings. + if (!pixel_snapped_rect.IsEmpty() || !style.OutlineStyleIsAuto()) + pixel_snapped_outline_rects.push_back(pixel_snapped_rect); + } + if (pixel_snapped_outline_rects.IsEmpty()) + return; + + if (style.OutlineStyleIsAuto()) { + auto corner_radii = GetFocusRingCornerRadii(style, outline_rects[0]); + PaintFocusRing(context, pixel_snapped_outline_rects, style, corner_radii); + return; + } + + IntRect united_outline_rect = UnionRect(pixel_snapped_outline_rects); + if (united_outline_rect == pixel_snapped_outline_rects[0]) { + BoxBorderPainter::PaintSingleRectOutline( + context, style, outline_rects[0], + AdjustedOutlineOffsetX(united_outline_rect, style.OutlineOffsetInt()), + AdjustedOutlineOffsetY(united_outline_rect, style.OutlineOffsetInt())); + return; + } + + if (style.HasBorderRadius() && + ComplexRoundedOutlinePainter(context, pixel_snapped_outline_rects, + outline_rects[0], style) + .Paint()) { + return; + } + + PaintComplexRightAngleOutline(context, pixel_snapped_outline_rects, style); +} + +void OutlinePainter::PaintFocusRingPath(GraphicsContext& context, + const Path& focus_ring_path, + const ComputedStyle& style) { + // TODO(crbug/251206): Implement outline-offset and double focus rings like + // right angle focus rings, which requires SkPathOps to support expanding and + // shrinking generic paths. + context.DrawFocusRingPath( + focus_ring_path.GetSkPath(), + style.VisitedDependentColor(GetCSSPropertyOutlineColor()), + style.FocusRingStrokeWidth(), DefaultFocusRingCornerRadius(style)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/outline_painter.h b/chromium/third_party/blink/renderer/core/paint/outline_painter.h new file mode 100644 index 00000000000..68e76bb9ac5 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/outline_painter.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OUTLINE_PAINTER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OUTLINE_PAINTER_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class ComputedStyle; +class GraphicsContext; +class Path; +struct PhysicalRect; + +class OutlinePainter { + STATIC_ONLY(OutlinePainter); + + public: + static void PaintOutlineRects(GraphicsContext&, + const Vector<PhysicalRect>&, + const ComputedStyle&); + + static void PaintFocusRingPath(GraphicsContext&, + const Path&, + const ComputedStyle&); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OUTLINE_PAINTER_H_ diff --git a/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc index d21438ff2c5..694579235e8 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc @@ -195,7 +195,7 @@ TEST_P(PaintAndRasterInvalidationTest, SubpixelChange) { IntRect(0, 0, 50, 100), PaintInvalidationReason::kGeometry}, RasterInvalidationInfo{object, object->DebugName(), - IntRect(0, 0, 101, 71), + IntRect(0, 0, 101, 70), PaintInvalidationReason::kGeometry})); GetDocument().View()->SetTracksRasterInvalidations(false); @@ -208,7 +208,7 @@ TEST_P(PaintAndRasterInvalidationTest, SubpixelChange) { IntRect(0, 0, 50, 100), PaintInvalidationReason::kGeometry}, RasterInvalidationInfo{object, object->DebugName(), - IntRect(0, 0, 101, 71), + IntRect(0, 0, 101, 70), PaintInvalidationReason::kGeometry})); GetDocument().View()->SetTracksRasterInvalidations(false); } @@ -230,7 +230,7 @@ TEST_P(PaintAndRasterInvalidationTest, SubpixelVisualRectChangeWithTransform) { IntRect(0, 0, 100, 200), PaintInvalidationReason::kGeometry}, RasterInvalidationInfo{object, object->DebugName(), - IntRect(0, 0, 202, 142), + IntRect(0, 0, 202, 140), PaintInvalidationReason::kGeometry})); GetDocument().View()->SetTracksRasterInvalidations(false); @@ -243,7 +243,7 @@ TEST_P(PaintAndRasterInvalidationTest, SubpixelVisualRectChangeWithTransform) { IntRect(0, 0, 100, 200), PaintInvalidationReason::kGeometry}, RasterInvalidationInfo{object, object->DebugName(), - IntRect(0, 0, 202, 142), + IntRect(0, 0, 202, 140), PaintInvalidationReason::kGeometry})); GetDocument().View()->SetTracksRasterInvalidations(false); } @@ -269,7 +269,7 @@ TEST_P(PaintAndRasterInvalidationTest, SubpixelWithinPixelsChange) { UpdateAllLifecyclePhasesForTest(); EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(), UnorderedElementsAre(RasterInvalidationInfo{ - object, object->DebugName(), IntRect(0, 0, 50, 100), + object, object->DebugName(), IntRect(0, 1, 50, 99), PaintInvalidationReason::kGeometry})); GetDocument().View()->SetTracksRasterInvalidations(false); } @@ -951,6 +951,52 @@ TEST_P(PaintAndRasterInvalidationTest, ScrollingInvalidatesStickyOffset) { EXPECT_EQ(PhysicalOffset(), inner->FirstFragment().PaintOffset()); } +TEST_P(PaintAndRasterInvalidationTest, NoDamageDueToFloatingPointError) { + SetBodyInnerHTML(R"HTML( + <style> + #canvas { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + will-change: transform; + transform-origin: top left; + } + .initial { transform: translateX(0px) scale(1.8); } + .updated { transform: translateX(47.22222222222222px) scale(1.8); } + #tile { + position: absolute; + will-change: transform; + transform-origin: top left; + transform: scale(0.55555555555556); + } + #tileInner { + transform-origin: top left; + transform: scale(1.8); + width: 200px; + height: 200px; + background: lightblue; + } + </style> + <div id="canvas" class="initial"> + <div id="tile"> + <div id="tileInner"></div> + </div> + </div> + )HTML"); + + GetDocument().View()->SetTracksRasterInvalidations(true); + + auto* canvas = GetDocument().getElementById("canvas"); + canvas->setAttribute(html_names::kClassAttr, "updated"); + GetDocument().View()->SetPaintArtifactCompositorNeedsUpdate(); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_FALSE(GetRasterInvalidationTracking(1)->HasInvalidations()); + GetDocument().View()->SetTracksRasterInvalidations(false); +} + TEST_P(PaintAndRasterInvalidationTest, ResizeElementWhichHasNonCustomResizer) { SetBodyInnerHTML(R"HTML( <!DOCTYPE html> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h b/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h index d1da3abf39a..a6d5c358146 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h @@ -19,7 +19,7 @@ class PaintAndRasterInvalidationTest : public PaintControllerPaintTest { MakeGarbageCollected<SingleChildLocalFrameClient>()) {} protected: - ContentLayerClientImpl* GetContentLayerClient(size_t index = 0) const { + ContentLayerClientImpl* GetContentLayerClient(wtf_size_t index = 0) const { DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); const auto& clients = GetDocument() .View() @@ -29,7 +29,7 @@ class PaintAndRasterInvalidationTest : public PaintControllerPaintTest { } const RasterInvalidationTracking* GetRasterInvalidationTracking( - size_t index = 0) const { + wtf_size_t index = 0) const { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { if (auto* client = GetContentLayerClient(index)) return client->GetRasterInvalidator().GetTracking(); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc index e176942d118..0c543f7cc54 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc @@ -128,10 +128,10 @@ TEST_P(PaintControllerPaintTestForCAP, FrameScrollingContents) { <div id='div4' style='top: 9000px; left: 9000px'></div> )HTML"); - const auto& div1 = *GetLayoutObjectByElementId("div1"); - const auto& div2 = *GetLayoutObjectByElementId("div2"); - const auto& div3 = *GetLayoutObjectByElementId("div3"); - const auto& div4 = *GetLayoutObjectByElementId("div4"); + const auto& div1 = To<LayoutBox>(*GetLayoutObjectByElementId("div1")); + const auto& div2 = To<LayoutBox>(*GetLayoutObjectByElementId("div2")); + const auto& div3 = To<LayoutBox>(*GetLayoutObjectByElementId("div3")); + const auto& div4 = To<LayoutBox>(*GetLayoutObjectByElementId("div4")); EXPECT_THAT(ContentDisplayItems(), ElementsAre(VIEW_SCROLLING_BACKGROUND_DISPLAY_ITEM, @@ -147,8 +147,18 @@ TEST_P(PaintControllerPaintTestForCAP, FrameScrollingContents) { PaintChunk::Id(GetLayoutView(), DisplayItem::kScrollHitTest), GetLayoutView().FirstFragment().LocalBorderBoxProperties(), &view_scroll_hit_test, IntRect(0, 0, 800, 600))); - EXPECT_THAT(ContentPaintChunks(), - ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(3, nullptr))); + auto contents_properties = + GetLayoutView().FirstFragment().ContentsProperties(); + EXPECT_THAT( + ContentPaintChunks(), + ElementsAre( + VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, + IsPaintChunk(1, 2, + PaintChunk::Id(*div1.Layer(), DisplayItem::kLayerChunk), + contents_properties), + IsPaintChunk(2, 3, + PaintChunk::Id(*div2.Layer(), DisplayItem::kLayerChunk), + contents_properties))); GetDocument().View()->LayoutViewport()->SetScrollOffset( ScrollOffset(5000, 5000), mojom::blink::ScrollType::kProgrammatic); @@ -165,8 +175,20 @@ TEST_P(PaintControllerPaintTestForCAP, FrameScrollingContents) { PaintChunk::Id(GetLayoutView(), DisplayItem::kScrollHitTest), GetLayoutView().FirstFragment().LocalBorderBoxProperties(), &view_scroll_hit_test, IntRect(0, 0, 800, 600))); - EXPECT_THAT(ContentPaintChunks(), - ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK(4, nullptr))); + EXPECT_THAT( + ContentPaintChunks(), + ElementsAre( + VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, + // html and div1 are out of the cull rect. + IsPaintChunk(1, 2, + PaintChunk::Id(*div2.Layer(), DisplayItem::kLayerChunk), + contents_properties), + IsPaintChunk(2, 3, + PaintChunk::Id(*div3.Layer(), DisplayItem::kLayerChunk), + contents_properties), + IsPaintChunk(3, 4, + PaintChunk::Id(*div4.Layer(), DisplayItem::kLayerChunk), + contents_properties))); } TEST_P(PaintControllerPaintTestForCAP, BlockScrollingNonLayeredContents) { diff --git a/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.h b/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.h index d5ede23a64a..3471dfd69f8 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_controller_paint_test.h @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/layout/layout_view.h" +#include "third_party/blink/renderer/core/paint/cull_rect_updater.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" @@ -49,6 +50,10 @@ class PaintControllerPaintTestBase : public RenderingTest { void UpdateAllLifecyclePhasesExceptPaint() { GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( DocumentUpdateReason::kTest); + // Run CullRectUpdater to ease testing of cull rects and repaint flags of + // PaintLayers on cull rect change. + if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) + CullRectUpdater(*GetLayoutView().Layer()).Update(); } void PaintContents(const IntRect& interest_rect) { @@ -111,31 +116,6 @@ class PaintControllerPaintTestBase : public RenderingTest { return PaintChunkSubset(RootPaintController().GetPaintArtifactShared(), begin_index, end_index); } - - class CachedItemAndSubsequenceCounter { - public: - CachedItemAndSubsequenceCounter() - : reset_uma_reporting_(&PaintController::disable_uma_reporting_, true) { - Reset(); - } - void Reset() { - old_num_cached_items_ = PaintController::sum_num_cached_items_; - old_num_cached_subsequences_ = - PaintController::sum_num_cached_subsequences_; - } - size_t NumNewCachedItems() const { - return PaintController::sum_num_cached_items_ - old_num_cached_items_; - } - size_t NumNewCachedSubsequences() const { - return PaintController::sum_num_cached_subsequences_ - - old_num_cached_subsequences_; - } - - private: - base::AutoReset<bool> reset_uma_reporting_; - size_t old_num_cached_items_; - size_t old_num_cached_subsequences_; - }; }; class PaintControllerPaintTest : public PaintTestConfigurations, diff --git a/chromium/third_party/blink/renderer/core/paint/paint_info.h b/chromium/third_party/blink/renderer/core/paint/paint_info.h index 37f149818af..971354f09b8 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_info.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_info.h @@ -28,8 +28,8 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_INFO_H_ #include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/layout/layout_object.h" -#include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" +#include "third_party/blink/renderer/core/layout/layout_box.h" +#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" // TODO(jchaffraix): Once we unify PaintBehavior and PaintLayerFlags, we should // move PaintLayerFlags to PaintPhase and rename it. Thus removing the need for // this #include @@ -59,14 +59,11 @@ struct CORE_EXPORT PaintInfo { PaintPhase phase, GlobalPaintFlags global_paint_flags, PaintLayerFlags paint_flags, - const LayoutBoxModelObject* paint_container = nullptr, - LayoutUnit fragment_logical_top_in_flow_thread = LayoutUnit()) + const LayoutBoxModelObject* paint_container = nullptr) : context(context), phase(phase), cull_rect_(cull_rect), paint_container_(paint_container), - fragment_logical_top_in_flow_thread_( - fragment_logical_top_in_flow_thread), paint_flags_(paint_flags), global_paint_flags_(global_paint_flags) {} @@ -76,8 +73,7 @@ struct CORE_EXPORT PaintInfo { phase(copy_other_fields_from.phase), cull_rect_(copy_other_fields_from.cull_rect_), paint_container_(copy_other_fields_from.paint_container_), - fragment_logical_top_in_flow_thread_( - copy_other_fields_from.fragment_logical_top_in_flow_thread_), + fragment_id_(copy_other_fields_from.fragment_id_), paint_flags_(copy_other_fields_from.paint_flags_), global_paint_flags_(copy_other_fields_from.global_paint_flags_) { // We should never pass is_painting_scrolling_background_ other PaintInfo. @@ -152,11 +148,16 @@ struct CORE_EXPORT PaintInfo { // Returns the fragment of the current painting object matching the current // layer fragment. - const FragmentData* FragmentToPaint(const LayoutObject& object) const { + const FragmentData* LegacyFragmentToPaint(const LayoutObject& object) const { + if (fragment_id_ == WTF::kNotFound) { + // We haven't been set up for legacy block fragmentation, so the object + // better not be fragmented, then. + DCHECK(!object.FirstFragment().NextFragment()); + return &object.FirstFragment(); + } for (const auto* fragment = &object.FirstFragment(); fragment; fragment = fragment->NextFragment()) { - if (fragment->LogicalTopInFlowThread() == - fragment_logical_top_in_flow_thread_) + if (fragment->FragmentID() == fragment_id_) return fragment; } // No fragment of the current painting object matches the layer fragment, @@ -164,20 +165,44 @@ struct CORE_EXPORT PaintInfo { return nullptr; } - // Returns the FragmentData of the specified physical fragment. If fragment - // traversal is supported, it will map directly to the right FragmentData. - // Otherwise we'll fall back to matching against the current + const FragmentData* FragmentToPaint(const LayoutObject& object) const { + if (const auto* box = DynamicTo<LayoutBox>(&object)) { + // We're are looking up FragmentData via LayoutObject, even though the + // object has NG fragments. This happens with objects that don't support + // fragment traversal, such as replaced content. We cannot use legacy- + // based lookup in such cases, as we might not have set a fragment ID to + // match against. Since we got here, though, it has to mean that we should + // paint the one and only fragment. + if (box->PhysicalFragmentCount()) { + // TODO(mstensho): We should DCHECK that box->PhysicalFragmentCount() is + // exactly 1 here (i.e. that the object is monolithic), but we are not + // ready for that yet, as there's code that enters legacy paint + // functions when we're traversing the fragment tree. See + // e.g. NGBoxFragmentPainter::RecordScrollHitTestData(), and how it does + // the job by invoking BoxPainter, which has no concept of + // fragments. One of the tests that would fail with such a DCHECK here + // is: + // virtual/layout_ng_block_frag/fast/multicol/overflow-across-columns.html + return &box->FirstFragment(); + } + } + return LegacyFragmentToPaint(object); + } + + // Returns the FragmentData of the specified physical fragment. If we're + // performing fragment traversal, it will map directly to the right + // FragmentData. Otherwise we'll fall back to matching against the current // PaintLayerFragment. const FragmentData* FragmentToPaint( const NGPhysicalFragment& fragment) const { - if (fragment.CanTraverse()) + if (fragment_id_ == WTF::kNotFound) return fragment.GetFragmentData(); - return FragmentToPaint(*fragment.GetLayoutObject()); + return LegacyFragmentToPaint(*fragment.GetLayoutObject()); } - void SetFragmentLogicalTopInFlowThread(LayoutUnit fragment_logical_top) { - fragment_logical_top_in_flow_thread_ = fragment_logical_top; - } + wtf_size_t FragmentID() const { return fragment_id_; } + void SetFragmentID(wtf_size_t id) { fragment_id_ = id; } + void SetIsInFragmentTraversal() { fragment_id_ = WTF::kNotFound; } bool IsPaintingScrollingBackground() const { DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); @@ -206,9 +231,13 @@ struct CORE_EXPORT PaintInfo { // The box model object that originates the current painting. const LayoutBoxModelObject* paint_container_; - // The logical top of the current fragment of the self-painting PaintLayer - // which initiated the current painting, in the containing flow thread. - LayoutUnit fragment_logical_top_in_flow_thread_; + // The ID of the fragment that we're currently painting. + // + // This is always used in legacy block fragmentation. In NG block + // fragmentation, it's only used when painting self-painting non-atomic + // inlines (because we currently have no way of mapping from + // NGPhysicalFragment to FragmentData in such cases). + wtf_size_t fragment_id_ = WTF::kNotFound; PaintLayerFlags paint_flags_; const GlobalPaintFlags global_paint_flags_; diff --git a/chromium/third_party/blink/renderer/core/paint/paint_invalidator.cc b/chromium/third_party/blink/renderer/core/paint/paint_invalidator.cc index ea066d96c00..648f2c0bb20 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_invalidator.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_invalidator.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/paint/paint_invalidator.h" +#include "base/trace_event/trace_event.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" @@ -17,7 +18,6 @@ #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h" #include "third_party/blink/renderer/core/page/link_highlight.h" @@ -32,14 +32,12 @@ namespace blink { void PaintInvalidator::UpdatePaintingLayer(const LayoutObject& object, - PaintInvalidatorContext& context, - bool is_ng_painting) { + PaintInvalidatorContext& context) { if (object.HasLayer() && To<LayoutBoxModelObject>(object).HasSelfPaintingLayer()) { context.painting_layer = To<LayoutBoxModelObject>(object).Layer(); - } else if (!is_ng_painting && - (object.IsColumnSpanAll() || - object.IsFloatingWithNonContainingBlockParent())) { + } else if (object.IsColumnSpanAll() || + object.IsFloatingWithNonContainingBlockParent()) { // See |LayoutObject::PaintingLayer| for the special-cases of floating under // inline and multicolumn. // Post LayoutNG the |LayoutObject::IsFloatingWithNonContainingBlockParent| @@ -68,8 +66,7 @@ void PaintInvalidator::UpdatePaintingLayer(const LayoutObject& object, void PaintInvalidator::UpdateDirectlyCompositedContainer( const LayoutObject& object, - PaintInvalidatorContext& context, - bool is_ng_painting) { + PaintInvalidatorContext& context) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; @@ -88,9 +85,8 @@ void PaintInvalidator::UpdateDirectlyCompositedContainer( context.directly_composited_container_for_stacked_contents = context.directly_composited_container = &object.DirectlyCompositableContainer(); - } else if (!is_ng_painting && - (object.IsColumnSpanAll() || - object.IsFloatingWithNonContainingBlockParent())) { + } else if (object.IsColumnSpanAll() || + object.IsFloatingWithNonContainingBlockParent()) { // In these cases, the object may belong to an ancestor of the current // paint invalidation container, in paint order. // Post LayoutNG the |LayoutObject::IsFloatingWithNonContainingBlockParent| @@ -141,11 +137,6 @@ void PaintInvalidator::UpdateDirectlyCompositedContainer( context.subtree_flags = 0; } } - - DCHECK_EQ(context.directly_composited_container, - object.DirectlyCompositableContainer()) - << object; - DCHECK_EQ(context.painting_layer, object.PaintingLayer()) << object; } void PaintInvalidator::UpdateFromTreeBuilderContext( @@ -227,6 +218,7 @@ void PaintInvalidator::UpdateLayoutShiftTracking( adjusted_old_paint_offset, tree_builder_context.translation_2d_to_layout_shift_root_delta, tree_builder_context.current.scroll_offset_to_layout_shift_root_delta, + tree_builder_context.current.pending_scroll_anchor_adjustment, new_paint_offset, logical_height); return; } @@ -288,6 +280,7 @@ void PaintInvalidator::UpdateLayoutShiftTracking( box, property_tree_state, old_rect, new_rect, adjusted_old_paint_offset, tree_builder_context.translation_2d_to_layout_shift_root_delta, tree_builder_context.current.scroll_offset_to_layout_shift_root_delta, + tree_builder_context.current.pending_scroll_anchor_adjustment, new_paint_offset); } } @@ -309,9 +302,29 @@ bool PaintInvalidator::InvalidatePaint( object.GetMutableForPainting().EnsureIsReadyForPaintInvalidation(); - UpdatePaintingLayer(object, context, /* is_ng_painting */ !!pre_paint_info); - UpdateDirectlyCompositedContainer(object, context, - /* is_ng_painting */ !!pre_paint_info); + UpdatePaintingLayer(object, context); + UpdateDirectlyCompositedContainer(object, context); + +#if DCHECK_IS_ON() + // Assert that the container state in the invalidation context is consistent + // with what the LayoutObject tree says. We cannot do this if we're fragment- + // traversing an "orphaned" object (an object that has a fragment inside a + // fragmentainer, even though not all its ancestor objects have it; this may + // happen to OOFs, and also to floats, if they are inside a non-atomic + // inline). In such cases we'll just have to live with the inconsitency, which + // means that we'll lose any paint effects from such "missing" ancestors. + if (!pre_paint_info || !pre_paint_info->is_inside_orphaned_object) { + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + DCHECK_EQ(context.directly_composited_container, + object.DirectlyCompositableContainer()) + << object; + } + DCHECK_EQ(context.painting_layer, object.PaintingLayer()) << object; + } +#endif // DCHECK_IS_ON() + + if (AXObjectCache* cache = object.GetDocument().ExistingAXObjectCache()) + cache->InvalidateBoundingBox(&object); if (!object.ShouldCheckForPaintInvalidation() && !context.NeedsSubtreeWalk()) return false; @@ -337,7 +350,7 @@ bool PaintInvalidator::InvalidatePaint( } if (pre_paint_info) { - FragmentData& fragment_data = pre_paint_info->fragment_data; + FragmentData& fragment_data = *pre_paint_info->fragment_data; context.fragment_data = &fragment_data; if (tree_builder_context) { @@ -368,10 +381,6 @@ bool PaintInvalidator::InvalidatePaint( UpdateFromTreeBuilderContext(fragment_tree_builder_context, context); UpdateLayoutShiftTracking(object, fragment_tree_builder_context, context); - - if (auto* mf_checker = - object.GetFrameView()->GetMobileFriendlinessChecker()) - mf_checker->NotifyInvalidatePaint(object); } else { context.old_paint_offset = fragment_data->PaintOffset(); } @@ -388,8 +397,12 @@ bool PaintInvalidator::InvalidatePaint( reason == PaintInvalidationReason::kJustCreated)) pending_delayed_paint_invalidations_.push_back(&object); - if (AXObjectCache* cache = object.GetDocument().ExistingAXObjectCache()) - cache->InvalidateBoundingBox(&object); + if (auto* mf_checker = + object.GetFrameView()->GetMobileFriendlinessChecker()) { + if (tree_builder_context && + (!pre_paint_info || pre_paint_info->is_last_for_node)) + mf_checker->NotifyInvalidatePaint(object); + } return reason != PaintInvalidationReason::kNone; } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_invalidator.h b/chromium/third_party/blink/renderer/core/paint/paint_invalidator.h index 245e629d4ec..7c6d9747747 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_invalidator.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_invalidator.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_INVALIDATOR_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_INVALIDATOR_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_shift_tracker.h" #include "third_party/blink/renderer/core/paint/paint_property_tree_builder.h" @@ -105,11 +106,10 @@ class PaintInvalidator final { friend struct PaintInvalidatorContext; ALWAYS_INLINE void UpdatePaintingLayer(const LayoutObject&, - PaintInvalidatorContext&, - bool is_ng_painting); - ALWAYS_INLINE void UpdateDirectlyCompositedContainer(const LayoutObject&, - PaintInvalidatorContext&, - bool is_ng_painting); + PaintInvalidatorContext&); + ALWAYS_INLINE void UpdateDirectlyCompositedContainer( + const LayoutObject&, + PaintInvalidatorContext&); ALWAYS_INLINE void UpdateFromTreeBuilderContext( const PaintPropertyTreeBuilderFragmentContext&, PaintInvalidatorContext&); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer.cc index 4eec5f301b4..f7a02c22431 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer.cc @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> @@ -48,6 +48,7 @@ #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" @@ -85,6 +86,7 @@ #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" @@ -195,7 +197,7 @@ PaintLayer::PaintLayer(LayoutBoxModelObject& layout_object) needs_cull_rect_update_(false), forces_children_cull_rect_update_(false), descendant_needs_cull_rect_update_(false), - previous_paint_result_(kFullyPainted), + previous_paint_result_(kMayBeClippedByCullRect), needs_paint_phase_descendant_outlines_(false), needs_paint_phase_float_(false), has_non_isolated_descendant_with_blend_mode_(false), @@ -206,6 +208,7 @@ PaintLayer::PaintLayer(LayoutBoxModelObject& layout_object) self_painting_status_changed_(false), filter_on_effect_node_dirty_(false), backdrop_filter_on_effect_node_dirty_(false), + has_filter_that_moves_pixels_(false), is_under_svg_hidden_container_(false), descendant_has_direct_or_scrolling_compositing_reason_(false), needs_compositing_reasons_update_( @@ -969,10 +972,8 @@ PaintLayer* PaintLayer::ContainingLayer(const PaintLayer* ancestor, // 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. - // A rendered legend needs to find the containing layer through its containing - // block to skip anonymous fieldset content box. if ((!Parent() || Parent()->GetLayoutObject().IsLayoutBlock()) && - !layout_object.IsColumnSpanAll() && !layout_object.IsRenderedLegend()) + !layout_object.IsColumnSpanAll()) return Parent(); return SlowContainingLayer(ancestor, skipped_ancestor, &layout_object); @@ -1135,6 +1136,21 @@ PaintLayer* PaintLayer::EnclosingDirectlyCompositableLayer( return nullptr; } +const PaintLayer* PaintLayer::EnclosingCompositedScrollingLayerUnderPagination( + IncludeSelfOrNot include_self_or_not) const { + DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); + const auto* start_layer = + include_self_or_not == kIncludeSelf ? this : CompositingContainer(); + for (const auto* curr = start_layer; curr && curr->EnclosingPaginationLayer(); + curr = curr->CompositingContainer()) { + if (const auto* scrollable_area = curr->GetScrollableArea()) { + if (scrollable_area->NeedsCompositedScrolling()) + return curr; + } + } + return nullptr; +} + void PaintLayer::SetNeedsCompositingInputsUpdate(bool mark_ancestor_flags) { SetNeedsCompositingInputsUpdateInternal(); @@ -1469,8 +1485,17 @@ void PaintLayer::RemoveOnlyThisLayerAfterStyleChange( if (!parent_) return; - if (old_style && GetLayoutObject().IsStacked(*old_style)) - DirtyStackingContextZOrderLists(); + if (old_style) { + if (GetLayoutObject().IsStacked(*old_style)) + DirtyStackingContextZOrderLists(); + + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && + PaintLayerPainter::PaintedOutputInvisible(*old_style)) { + // This layer is removed because opacity becomes 1. Do the same as + // StyleDidChange() on change of PaintedOutputInvisible(). + GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation(); + } + } bool did_set_paint_invalidation = false; if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { @@ -1675,10 +1700,12 @@ bool PaintLayer::RequiresScrollableArea() const { // PaintLayerScrollableArea. if (GetLayoutBox()->CanResize()) return true; - // With custom scrollbars, we need a PaintLayerScrollableArea - // so we can calculate the size of scrollbar gutters. - if (GetLayoutObject().StyleRef().IsScrollbarGutterForce() && - GetLayoutObject().StyleRef().HasPseudoElementStyle(kPseudoIdScrollbar)) { + // 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; @@ -1711,26 +1738,18 @@ bool PaintLayer::HasOverflowControls() const { GetLayoutObject().StyleRef().HasResize()); } -void PaintLayer::AppendSingleFragmentIgnoringPagination( +void PaintLayer::AppendSingleFragmentIgnoringPaginationForHitTesting( PaintLayerFragments& fragments, - const PaintLayer* root_layer, - const CullRect* cull_rect, - OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior, - ShouldRespectOverflowClipType respect_overflow_clip, - const PhysicalOffset* offset_from_root, - const PhysicalOffset& sub_pixel_accumulation) const { + ShouldRespectOverflowClipType respect_overflow_clip) const { PaintLayerFragment fragment; - ClipRectsContext clip_rects_context( - root_layer, &root_layer->GetLayoutObject().FirstFragment(), - overlay_scrollbar_clip_behavior, respect_overflow_clip, - sub_pixel_accumulation); - Clipper(GeometryMapperOption::kUseGeometryMapper) - .CalculateRects(clip_rects_context, &GetLayoutObject().FirstFragment(), - cull_rect, fragment.layer_bounds, - fragment.background_rect, fragment.foreground_rect, - offset_from_root); - fragment.root_fragment_data = &root_layer->GetLayoutObject().FirstFragment(); 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. @@ -1745,8 +1764,12 @@ bool PaintLayer::ShouldFragmentCompositedBounds( const PaintLayer* compositing_layer) const { if (!EnclosingPaginationLayer()) return false; - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return true; + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + // We should not fragment composited scrolling layers and descendants, which + // is not only to render the scroller correctly, but also to prevent + // multiple cc::Layers with the same scrolling element id. + return !EnclosingCompositedScrollingLayerUnderPagination(kIncludeSelf); + } if (Transform() && !PaintsWithDirectReasonIntoOwnBacking(kGlobalPaintNormalPhase)) return true; @@ -1763,7 +1786,7 @@ bool PaintLayer::ShouldFragmentCompositedBounds( void PaintLayer::CollectFragments( PaintLayerFragments& fragments, const PaintLayer* root_layer, - const CullRect* cull_rect, + const CullRect* painting_cull_rect, OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior, ShouldRespectOverflowClipType respect_overflow_clip, const PhysicalOffset* offset_from_root, @@ -1794,58 +1817,63 @@ void PaintLayer::CollectFragments( wtf_size_t physical_fragment_idx = 0u; for (auto* fragment_data = &first_fragment_data; fragment_data; fragment_data = fragment_data->NextFragment(), physical_fragment_idx++) { - 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; + // 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->FragmentID() == fragment_data->FragmentID()) + break; + } + } else { + root_fragment_data = &first_root_fragment_data; } - } 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; - } + 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 (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 = cull_rect->Rect(); - first_root_fragment_data.MapRectToFragment(*root_fragment_data, rect); - fragment_cull_rect.emplace(rect); - } + 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(); + 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.root_fragment_data = root_fragment_data; fragment.fragment_data = fragment_data; if (GetLayoutObject().CanTraversePhysicalFragments()) { @@ -1974,6 +2002,16 @@ HitTestingTransformState PaintLayer::CreateLocalTransformState( 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); @@ -2203,11 +2241,9 @@ PaintLayer* PaintLayer::HitTestLayer(PaintLayer* root_layer, if (recursion_data.intersects_location) { layer_fragments.emplace(); if (applied_transform) { - DCHECK(root_layer == this); - PhysicalOffset ignored; - AppendSingleFragmentIgnoringPagination( - *layer_fragments, root_layer, nullptr, - kExcludeOverlayScrollbarSizeForHitTesting, clip_behavior, &ignored); + DCHECK_EQ(root_layer, this); + AppendSingleFragmentIgnoringPaginationForHitTesting(*layer_fragments, + clip_behavior); } else { CollectFragments(*layer_fragments, root_layer, nullptr, kExcludeOverlayScrollbarSizeForHitTesting, @@ -2872,19 +2908,11 @@ void PaintLayer::ExpandRectForSelfPaintingDescendants( 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; - } - } + // If the layer is known to clip the whole subtree, then we don't need to + // expand for children. Not checking kIncludeAncestorClips because the clip of + // the current layer is always applied. + if (KnownToClipSubtree()) + return; PaintLayerPaintOrderIterator iterator(*this, kAllChildren); while (PaintLayer* child_layer = iterator.Next()) { @@ -2901,6 +2929,22 @@ void PaintLayer::ExpandRectForSelfPaintingDescendants( } } +bool PaintLayer::KnownToClipSubtree() const { + if (const auto* box = GetLayoutBox()) { + if (!box->ShouldClipOverflowAlongBothAxis()) + return false; + if (HasNonContainedAbsolutePositionDescendant()) + return false; + if (HasFixedPositionDescendant() && !box->CanContainFixedPositionObjects()) + return false; + // The root frame's clip is special at least in Android WebView. + if (is_root_layer_ && box->GetFrame()->IsLocalRoot()) + return false; + return true; + } + return false; +} + PhysicalRect PaintLayer::BoundingBoxForCompositing() const { return BoundingBoxForCompositingInternal( *this, nullptr, @@ -3131,8 +3175,8 @@ bool PaintLayer::SupportsSubsequenceCaching() const { if (EnclosingPaginationLayer()) return false; - // SVG paints atomically. - if (GetLayoutObject().IsSVGRoot()) + // 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 @@ -3141,8 +3185,8 @@ bool PaintLayer::SupportsSubsequenceCaching() const { if (GetLayoutObject().IsDocumentElement()) return false; - // Create subsequence for only stacking contexts whose painting are atomic. - return GetLayoutObject().IsStackingContext(); + // Create subsequence for only stacked objects whose paintings are atomic. + return GetLayoutObject().IsStacked(); } ScrollingCoordinator* PaintLayer::GetScrollingCoordinator() { @@ -3157,6 +3201,7 @@ bool PaintLayer::CompositesWithTransform() const { 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. @@ -3201,6 +3246,7 @@ bool PaintLayer::BackgroundIsKnownToBeOpaqueInRect( 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. @@ -3228,8 +3274,7 @@ bool PaintLayer::ShouldBeSelfPaintingLayer() const { return GetLayoutObject().LayerTypeRequired() == kNormalPaintLayer || (scrollable_area_ && scrollable_area_->HasOverlayOverflowControls()) || ScrollsOverflow() || - (RuntimeEnabledFeatures::CompositeSVGEnabled() && - GetLayoutObject().IsSVGRoot() && + (GetLayoutObject().IsSVGRoot() && To<LayoutSVGRoot>(GetLayoutObject()) .HasDescendantCompositingReasons()); } @@ -3420,6 +3465,8 @@ void PaintLayer::StyleDidChange(StyleDifference diff, const ComputedStyle* old_style) { UpdateScrollableArea(); + has_filter_that_moves_pixels_ = ComputeHasFilterThatMovesPixels(); + if (AttemptDirectCompositingUpdate(diff, old_style)) { if (diff.HasDifference()) GetLayoutObject().SetNeedsPaintPropertyUpdate(); @@ -3483,40 +3530,39 @@ void PaintLayer::StyleDidChange(StyleDifference diff, UpdateBackdropFilters(old_style, new_style); UpdateClipPath(old_style, new_style); - if (!SelfNeedsRepaint()) { - 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. - SetNeedsRepaint(); - } else if (old_style) { - bool new_painted_output_invisible = - PaintLayerPainter::PaintedOutputInvisible(new_style); - if (PaintLayerPainter::PaintedOutputInvisible(*old_style) != - new_painted_output_invisible) { - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // Though CompositeAfterPaint ignores PaintedOutputInvisible during - // paint, we still force repaint to ensure FCP/LCP will be reported. - // See crbug.com/1184903. Only SetNeedsRepaint() won't work because - // we won't repaint the display items which are already in the old - // painted result. TODO(crbug.com/1104218): Optimize this. - if (!new_painted_output_invisible) - GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation(); - } else { - // Change of PaintedOutputInvisible() will affect existence of paint - // chunks, so needs repaint. - SetNeedsRepaint(); - } + 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(); } } } } -LayoutSize PaintLayer::PixelSnappedScrolledContentOffset() const { +IntPoint PaintLayer::PixelSnappedScrolledContentOffset() const { if (GetLayoutObject().IsScrollContainer()) return GetLayoutBox()->PixelSnappedScrolledContentOffset(); - return LayoutSize(); + return IntPoint(); } PaintLayerClipper PaintLayer::Clipper( @@ -3666,7 +3712,7 @@ PhysicalRect PaintLayer::MapRectForFilter(const PhysicalRect& rect) const { return PhysicalRect::EnclosingRect(MapRectForFilter(FloatRect(rect))); } -bool PaintLayer::HasFilterThatMovesPixels() const { +bool PaintLayer::ComputeHasFilterThatMovesPixels() const { if (!HasFilterInducingProperty()) return false; const ComputedStyle& style = GetLayoutObject().StyleRef(); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer.h b/chromium/third_party/blink/renderer/core/paint/paint_layer.h index 9ec703a5b60..92145820440 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer.h @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> @@ -313,7 +313,7 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { // testing, intersection observer). Most other cases should use the unsnapped // offset from LayoutBox (for layout) or the source offset from the // ScrollableArea. - LayoutSize PixelSnappedScrolledContentOffset() const; + IntPoint PixelSnappedScrolledContentOffset() const; // FIXME: size() should DCHECK(!needs_position_update_) as well, but that // fails in some tests, for example, fast/repaint/clipped-relative.html. @@ -408,6 +408,10 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { PaintLayer* EnclosingDirectlyCompositableLayer(IncludeSelfOrNot) const; + // For CompositeAfterPaint, but not for LayoutNGBlockFragmentation. + const PaintLayer* EnclosingCompositedScrollingLayerUnderPagination( + IncludeSelfOrNot) const; + // https://crbug.com/751768, this function can return nullptr sometimes. // Always check the result before using it, don't just DCHECK. PaintLayer* EnclosingLayerForPaintInvalidationCrossingFrameBoundaries() const; @@ -619,6 +623,7 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { PhysicalOffset&); bool PaintsWithTransparency(GlobalPaintFlags global_paint_flags) const { + DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); return IsTransparent() && !PaintsIntoOwnBacking(global_paint_flags); } @@ -681,7 +686,9 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { // Calls the above, rounding outwards. PhysicalRect MapRectForFilter(const PhysicalRect&) const; - bool HasFilterThatMovesPixels() const; + bool HasFilterThatMovesPixels() const { + return has_filter_that_moves_pixels_; + } PaintLayerResourceInfo* ResourceInfo() const { return rare_data_ ? rare_data_->resource_info.Get() : nullptr; @@ -990,19 +997,10 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { void DidUpdateScrollsOverflow(); - void AppendSingleFragmentIgnoringPagination( - PaintLayerFragments&, - const PaintLayer* root_layer, - const CullRect* cull_rect, - OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize, - ShouldRespectOverflowClipType = kRespectOverflowClip, - const PhysicalOffset* offset_from_root = nullptr, - const PhysicalOffset& sub_pixel_accumulation = PhysicalOffset()) const; - void CollectFragments( PaintLayerFragments&, const PaintLayer* root_layer, - const CullRect* cull_rect, + const CullRect* painting_cull_rect, OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize, ShouldRespectOverflowClipType = kRespectOverflowClip, const PhysicalOffset* offset_from_root = nullptr, @@ -1162,6 +1160,8 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { needs_paint_offset_translation_for_compositing_ = b; } + bool KnownToClipSubtree() const; + private: PhysicalRect LocalBoundingBoxForCompositingOverlapTest() const; bool PaintsWithDirectReasonIntoOwnBacking(GlobalPaintFlags) const; @@ -1185,6 +1185,10 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { void UpdateHasSelfPaintingLayerDescendant() const; + void AppendSingleFragmentIgnoringPaginationForHitTesting( + PaintLayerFragments&, + ShouldRespectOverflowClipType) const; + struct HitTestRecursionData { const PhysicalRect& rect; // Whether location.Intersects(rect) returns true. @@ -1333,6 +1337,8 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { needs_reorder_overlay_overflow_controls_ = b; } + bool ComputeHasFilterThatMovesPixels() const; + // Self-painting layer is an optimization where we avoid the heavy Layer // painting machinery for a Layer allocated only to handle the overflow clip // case. @@ -1404,6 +1410,9 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { unsigned filter_on_effect_node_dirty_ : 1; unsigned backdrop_filter_on_effect_node_dirty_ : 1; + // Caches |ComputeHasFilterThatMovesPixels()|, updated on style changes. + unsigned has_filter_that_moves_pixels_ : 1; + // True if the current subtree is underneath a LayoutSVGHiddenContainer // ancestor. unsigned is_under_svg_hidden_container_ : 1; diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.cc index 85d08c83b45..92e3f133bfe 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.cc @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.h index 680c0b5400b..707bc99c901 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_clipper.h @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_fragment.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_fragment.h index 9ffa07ab179..ab911889fa2 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_fragment.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_fragment.h @@ -81,6 +81,7 @@ struct PaintLayerFragment { // Defines the coordinate space of the above rects: // root_fragment_data->LocalBorderBoxProperties().Transform() + // root_fragment_data.PaintOffset(). + // It's for legacy cull rect calculation (pre-CompositeAfterPaint) only. const FragmentData* root_fragment_data = nullptr; // The corresponding FragmentData of this structure. diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.cc index 34ef71b4929..b263886d8a2 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.cc @@ -25,6 +25,7 @@ #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" #include "third_party/blink/renderer/platform/graphics/paint/scoped_display_item_fragment.h" +#include "third_party/blink/renderer/platform/graphics/paint/scoped_effectively_invisible.h" #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_hint.h" #include "third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" @@ -59,6 +60,9 @@ bool PaintLayerPainter::PaintedOutputInvisible(const ComputedStyle& style) { if (style.HasWillChangeOpacityHint()) return false; + if (style.HasCurrentOpacityAnimation()) + return false; + // 0.0004f < 1/2048. With 10-bit color channels (only available on the // newest Macs; otherwise it's 8-bit), we see that an alpha of 1/2048 or // less leads to a color output of less than 0.5 in all channels, hence @@ -87,17 +91,27 @@ PaintResult PaintLayerPainter::Paint( // In CompositeAfterPaint we simplify this optimization by painting even when // effectively invisible but skipping the painted content during layerization // in PaintArtifactCompositor. - if (paint_layer_.PaintsWithTransparency( - painting_info.GetGlobalPaintFlags())) { - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && - PaintedOutputInvisible(paint_layer_.GetLayoutObject().StyleRef())) - return kFullyPainted; - - paint_flags |= kPaintLayerHaveTransparency; + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && + paint_layer_.PaintsWithTransparency( + painting_info.GetGlobalPaintFlags()) && + PaintedOutputInvisible(paint_layer_.GetLayoutObject().StyleRef())) { + return kFullyPainted; } - // If the transform can't be inverted, then don't paint anything. - if (paint_layer_.PaintsWithTransform(painting_info.GetGlobalPaintFlags()) && + // If the transform can't be inverted, don't paint anything. We still need + // to paint with CompositeAfterPaint if there are animations to ensure the + // animation can be setup to run on the compositor. + bool paint_non_invertible_transforms = false; + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + const auto* properties = + paint_layer_.GetLayoutObject().FirstFragment().PaintProperties(); + if (properties && properties->Transform() && + properties->Transform()->HasActiveTransformAnimation()) { + paint_non_invertible_transforms = true; + } + } + if (!paint_non_invertible_transforms && + paint_layer_.PaintsWithTransform(painting_info.GetGlobalPaintFlags()) && !paint_layer_.RenderableTransform(painting_info.GetGlobalPaintFlags()) .IsInvertible()) { return kFullyPainted; @@ -211,15 +225,37 @@ bool PaintLayerPainter::ShouldUseInfiniteCullRectInternal( if (const auto* properties = paint_layer_.GetLayoutObject().FirstFragment().PaintProperties()) { + // Cull rect mapping doesn't work under perspective in some cases. + // See http://crbug.com/887558 for details. if (properties->Perspective()) return true; if (for_cull_rect_update) { - // A CSS transform can also have perspective like - // "transform: perspective(100px) rotateY(45deg)". if (const auto* transform = properties->Transform()) { + // A CSS transform can also have perspective like + // "transform: perspective(100px) rotateY(45deg)". In these cases, we + // also want to skip cull rect mapping. See http://crbug.com/887558 for + // details. if (!transform->IsIdentityOr2DTranslation() && - transform->Matrix().HasPerspective()) + transform->Matrix().HasPerspective()) { + return true; + } + + // Ensure content under animating transforms is not culled out, even if + // the initial matrix is non-invertible. + if (transform->HasActiveTransformAnimation() && + !transform->IsIdentityOr2DTranslation() && + !transform->Matrix().IsInvertible()) { + return true; + } + + // As an optimization, skip cull rect updating for non-composited + // transforms which have already been painted. This is because the cull + // rect update, which needs to do complex mapping of the cull rect, can + // be more expensive than over-painting. + if (!transform->HasDirectCompositingReasons() && + paint_layer_.PreviousPaintResult() == kFullyPainted) { return true; + } } } } @@ -277,35 +313,7 @@ void PaintLayerPainter::AdjustForPaintProperties( return; if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - auto& cull_rect = painting_info.cull_rect; - // CullRect::ApplyTransforms() requires the cull rect in the source - // transform space. Convert cull_rect from the root layer's local space. - // - // TODO(paint-dev): Become block fragmentation aware. Just using the first - // fragment seems wrong. - cull_rect.MoveBy(RoundedIntPoint(first_root_fragment.PaintOffset())); - absl::optional<CullRect> old_cull_rect; - if (!paint_layer_.SelfOrDescendantNeedsRepaint() && - !RuntimeEnabledFeatures::CullRectUpdateEnabled()) { - old_cull_rect = paint_layer_.PreviousCullRect(); - // Convert old_cull_rect into the layer's transform space. - old_cull_rect->MoveBy(RoundedIntPoint(first_fragment.PaintOffset())); - } - if (paint_flags & kPaintLayerPaintingOverflowContents) { - // Use PostScrollTranslation as the source transform to avoid clipping - // of the scrolling contents in CullRect::ApplyTransforms(). - source_transform = &first_root_fragment.PostScrollTranslation(); - // Map cull_rect into scrolling contents space (i.e. source_transform). - if (const auto* properties = first_root_fragment.PaintProperties()) { - if (const auto* scroll_translation = properties->ScrollTranslation()) - cull_rect.Move(-scroll_translation->Translation2D()); - } - } - cull_rect.ApplyTransforms(source_transform->Unalias(), - destination_transform.Unalias(), old_cull_rect); - // Convert cull_rect from the layer's transform space to the layer's local - // space. - cull_rect.MoveBy(-RoundedIntPoint(first_fragment.PaintOffset())); + DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled()); } else if (!painting_info.cull_rect.IsInfinite()) { auto rect = painting_info.cull_rect.Rect(); const FragmentData& primary_stitching_fragment = @@ -432,21 +440,6 @@ PaintResult PaintLayerPainter::PaintLayerContents( !paint_layer_.IsUnderSVGHiddenContainer() && is_self_painting_layer && !is_painting_overlay_overflow_controls; - bool should_create_subsequence = - should_paint_content && - ShouldCreateSubsequence(paint_layer_, context, painting_info); - - absl::optional<SubsequenceRecorder> subsequence_recorder; - if (should_create_subsequence) { - if (!ShouldRepaintSubsequence(paint_layer_, painting_info) && - SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, - paint_layer_)) { - return paint_layer_.PreviousPaintResult(); - } - DCHECK(paint_layer_.SupportsSubsequenceCaching()); - subsequence_recorder.emplace(context, paint_layer_); - } - // TODO(paint-dev): Become block fragmentation aware. Unconditionally using // the first fragment doesn't seem right. PhysicalOffset offset_from_root = object.FirstFragment().PaintOffset(); @@ -458,17 +451,36 @@ PaintResult PaintLayerPainter::PaintLayerContents( if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { if (object.FirstFragment().NextFragment()) { result = kMayBeClippedByCullRect; - } else if (!object.FirstFragment().GetCullRect().Rect().Contains( - visual_rect)) { - result = kMayBeClippedByCullRect; - } else if (const auto* box = DynamicTo<LayoutBox>(object)) { - PhysicalRect contents_visual_rect = - box->PhysicalContentsVisualOverflowRect(); - contents_visual_rect.Move(object.FirstFragment().PaintOffset()); - if (!PhysicalRect(object.FirstFragment().GetContentsCullRect().Rect()) - .Contains(contents_visual_rect)) { + } else { + IntRect cull_rect = object.FirstFragment().GetCullRect().Rect(); + bool cull_rect_intersects_self = cull_rect.Intersects(visual_rect); + if (!cull_rect.Contains(visual_rect)) result = kMayBeClippedByCullRect; + + bool cull_rect_intersects_contents = true; + if (const auto* box = DynamicTo<LayoutBox>(object)) { + PhysicalRect contents_visual_rect = + box->PhysicalContentsVisualOverflowRect(); + contents_visual_rect.Move(object.FirstFragment().PaintOffset()); + PhysicalRect contents_cull_rect( + object.FirstFragment().GetContentsCullRect().Rect()); + cull_rect_intersects_contents = + contents_cull_rect.Intersects(contents_visual_rect); + if (!contents_cull_rect.Contains(contents_visual_rect)) + result = kMayBeClippedByCullRect; + } else { + cull_rect_intersects_contents = cull_rect_intersects_self; } + + if (!cull_rect_intersects_self && !cull_rect_intersects_contents) { + if (!is_painting_overflow_contents && + paint_layer_.KnownToClipSubtree()) { + paint_layer_.SetPreviousPaintResult(kMayBeClippedByCullRect); + return kMayBeClippedByCullRect; + } + should_paint_content = false; + } + // The above doesn't consider clips on non-self-painting contents. // Will update in ScopedBoxContentsPaintState. } @@ -485,26 +497,45 @@ PaintResult PaintLayerPainter::PaintLayerContents( if (should_paint_content || should_paint_self_outline || is_painting_overlay_overflow_controls) { - // Collect the fragments. This will compute the clip rectangles and paint - // offsets for each layer fragment. + // Collect the fragments. If CullRectUpdate is enabled, this will just + // create a light-weight adapter from FragmentData to PaintLayerFragment + // and we'll remove the adapter in the future. Otherwise this will compute + // the clip rectangles and paint offsets for each layer fragment. paint_layer_.CollectFragments( layer_fragments, local_painting_info.root_layer, &local_painting_info.cull_rect, kIgnoreOverlayScrollbarSize, respect_overflow_clip, &offset_from_root, local_painting_info.sub_pixel_accumulation); - // PaintLayer::CollectFragments depends on the paint dirty rect in - // complicated ways. For now, always assume a partially painted output - // for fragmented content. - if (layer_fragments.size() > 1) - result = kMayBeClippedByCullRect; - - if (should_paint_content) { - should_paint_content = AtLeastOneFragmentIntersectsDamageRect( - layer_fragments, local_painting_info, paint_flags, offset_from_root); - if (!should_paint_content) + if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) { + // PaintLayer::CollectFragments depends on the paint dirty rect in + // complicated ways. For now, always assume a partially painted output + // for fragmented content. + if (layer_fragments.size() > 1) result = kMayBeClippedByCullRect; + + if (should_paint_content) { + should_paint_content = AtLeastOneFragmentIntersectsDamageRect( + layer_fragments, local_painting_info, paint_flags, + offset_from_root); + if (!should_paint_content) + result = kMayBeClippedByCullRect; + } + } + } + + bool should_create_subsequence = + should_paint_content && + ShouldCreateSubsequence(paint_layer_, context, painting_info); + absl::optional<SubsequenceRecorder> subsequence_recorder; + if (should_create_subsequence) { + if (!ShouldRepaintSubsequence(paint_layer_, painting_info) && + SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, + paint_layer_)) { + return paint_layer_.PreviousPaintResult(); } + DCHECK(paint_layer_.SupportsSubsequenceCaching()); + subsequence_recorder.emplace(context, paint_layer_); } bool is_painting_root_layer = (&paint_layer_) == painting_info.root_layer; @@ -524,6 +555,11 @@ PaintResult PaintLayerPainter::PaintLayerContents( !is_painting_overlay_overflow_controls; bool is_video = IsA<LayoutVideo>(object); + absl::optional<ScopedEffectivelyInvisible> effectively_invisible; + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && + PaintedOutputInvisible(object.StyleRef())) + effectively_invisible.emplace(context.GetPaintController()); + absl::optional<ScopedPaintChunkHint> paint_chunk_hint; if (should_paint_content) { paint_chunk_hint.emplace(context.GetPaintController(), @@ -623,6 +659,8 @@ bool PaintLayerPainter::AtLeastOneFragmentIntersectsDamageRect( const PaintLayerPaintingInfo& local_painting_info, PaintLayerFlags local_paint_flags, const PhysicalOffset& offset_from_root) { + DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled()); + if (&paint_layer_ == local_painting_info.root_layer && (local_paint_flags & kPaintLayerPaintingOverflowContents)) return true; @@ -736,18 +774,19 @@ void PaintLayerPainter::PaintFragmentWithPhase( context.GetPaintController(), chunk_properties, paint_layer_, DisplayItem::PaintPhaseToDrawingType(phase)); - PaintInfo paint_info( - context, cull_rect, phase, painting_info.GetGlobalPaintFlags(), - paint_flags, &painting_info.root_layer->GetLayoutObject(), - fragment.fragment_data ? fragment.fragment_data->LogicalTopInFlowThread() - : LayoutUnit()); + PaintInfo paint_info(context, cull_rect, phase, + painting_info.GetGlobalPaintFlags(), paint_flags, + &painting_info.root_layer->GetLayoutObject()); if (paint_layer_.GetLayoutObject().ChildPaintBlockedByDisplayLock()) paint_info.SetDescendantPaintingBlocked(true); - if (fragment.physical_fragment) + if (fragment.physical_fragment) { NGBoxFragmentPainter(*fragment.physical_fragment).Paint(paint_info); - else + } else { + if (fragment.fragment_data) + paint_info.SetFragmentID(fragment.fragment_data->FragmentID()); paint_layer_.GetLayoutObject().Paint(paint_info); + } } static CullRect LegacyCullRect(const PaintLayerFragment& fragment, diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.h index 27ad0e60a6e..c8e3343a6b5 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter.h @@ -35,7 +35,7 @@ class CORE_EXPORT PaintLayerPainter { void Paint(GraphicsContext&, const CullRect&, const GlobalPaintFlags = kGlobalPaintNormalPhase, - PaintLayerFlags = 0); + PaintLayerFlags = kPaintLayerNoFlag); // Paint() assumes that the caller will clip to the bounds of the painting // dirty if necessary. PaintResult Paint(GraphicsContext&, diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc index 4fadc94b899..9728634781a 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" #include "third_party/blink/renderer/platform/graphics/graphics_context.h" +#include "third_party/blink/renderer/platform/testing/find_cc_layer.h" #include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" @@ -22,23 +23,6 @@ class PaintLayerPainterTest : public PaintControllerPaintTest { USING_FAST_MALLOC(PaintLayerPainterTest); public: - void ExpectPaintedOutputInvisibleAndPaintsWithTransparency( - const char* element_name, - bool expected_invisible, - bool expected_paints_with_transparency) { - // The optimization to skip painting for effectively-invisible content is - // limited to pre-CAP. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - - PaintLayer* target_layer = GetPaintLayerByElementId(element_name); - bool invisible = PaintLayerPainter::PaintedOutputInvisible( - target_layer->GetLayoutObject().StyleRef()); - EXPECT_EQ(expected_invisible, invisible); - EXPECT_EQ(expected_paints_with_transparency, - target_layer->PaintsWithTransparency(kGlobalPaintNormalPhase)); - } - PaintController& MainGraphicsLayerPaintController() { return GetLayoutView() .Layer() @@ -82,8 +66,10 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithBackgrounds) { auto* filler2 = GetLayoutObjectByElementId("filler2"); auto* container1_layer = To<LayoutBoxModelObject>(container1)->Layer(); + auto* content1_layer = To<LayoutBoxModelObject>(content1)->Layer(); auto* filler1_layer = To<LayoutBoxModelObject>(filler1)->Layer(); auto* container2_layer = To<LayoutBoxModelObject>(container2)->Layer(); + auto* content2_layer = To<LayoutBoxModelObject>(content2)->Layer(); auto* filler2_layer = To<LayoutBoxModelObject>(filler2)->Layer(); auto chunk_state = GetLayoutView().FirstFragment().ContentsProperties(); @@ -106,27 +92,36 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithBackgrounds) { // Check that new paint chunks were forced for the layers. auto chunks = ContentPaintChunks(); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*container1_layer, chunks.begin() + 1, 1); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler1_layer, chunks.begin() + 2, 1); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*container2_layer, chunks.begin() + 3, 1); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler2_layer, chunks.begin() + 4, 1); + auto chunk_it = chunks.begin(); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*container1_layer, chunk_it + 1, 2); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*content1_layer, chunk_it + 2, 1); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler1_layer, chunk_it + 3, 1); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*container2_layer, chunk_it + 4, 2); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*content2_layer, chunk_it + 5, 1); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler2_layer, chunk_it + 6, 1); EXPECT_THAT( chunks, ElementsAre( VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, IsPaintChunk( - 1, 3, + 1, 2, PaintChunk::Id(*container1_layer, DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 0, 200, 200)), IsPaintChunk( + 2, 3, PaintChunk::Id(*content1_layer, DisplayItem::kLayerChunk), + chunk_state, nullptr, IntRect(0, 0, 100, 100)), + IsPaintChunk( 3, 4, PaintChunk::Id(*filler1_layer, DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 200, 20, 20)), IsPaintChunk( - 4, 6, + 4, 5, PaintChunk::Id(*container2_layer, DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 220, 200, 200)), IsPaintChunk( + 5, 6, PaintChunk::Id(*content2_layer, DisplayItem::kLayerChunk), + chunk_state, nullptr, IntRect(0, 220, 100, 100)), + IsPaintChunk( 6, 7, PaintChunk::Id(*filler2_layer, DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 420, 20, 20)))); }; @@ -137,10 +132,10 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithBackgrounds) { ->setAttribute(html_names::kStyleAttr, "position: absolute; width: 100px; height: 100px; " "background-color: green"); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); - EXPECT_EQ(6u, counter.NumNewCachedItems()); - EXPECT_EQ(3u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(6u, counter.num_cached_items); + EXPECT_EQ(4u, counter.num_cached_subsequences); // We should still have the paint chunks forced by the cached subsequences. check_results(); @@ -181,9 +176,10 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) { auto* filler_layer = To<LayoutBoxModelObject>(filler)->Layer(); auto chunks = ContentPaintChunks(); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 4); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 1); - EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 4, 1); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1); auto container_properties = container->FirstFragment().LocalBorderBoxProperties(); @@ -207,6 +203,10 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) { PaintChunk::Id(*content_layer, DisplayItem::kLayerChunk), content_properties, nullptr, IntRect(0, 0, 200, 100)), IsPaintChunk( + 1, 1, + PaintChunk::Id(*inner_content_layer, DisplayItem::kLayerChunk), + content_properties, nullptr, IntRect(0, 0, 100, 100)), + IsPaintChunk( 1, 1, PaintChunk::Id(*filler_layer, DisplayItem::kLayerChunk), content_properties, nullptr, IntRect(0, 100, 300, 300)))); @@ -214,8 +214,12 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) { ->setAttribute(html_names::kStyleAttr, "position: absolute; width: 100px; height: 100px; " "top: 100px; background-color: green"); + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ(1u, counter.num_cached_items); // view background. + EXPECT_EQ(1u, counter.num_cached_subsequences); // filler layer. + EXPECT_THAT( ContentDisplayItems(), ElementsAre(VIEW_SCROLLING_BACKGROUND_DISPLAY_ITEM, @@ -225,6 +229,7 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) { chunks = ContentPaintChunks(); EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5); EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2); + EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1); EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1); EXPECT_THAT( @@ -250,12 +255,6 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) { } TEST_P(PaintLayerPainterTest, CachedSubsequenceOnCullRectChange) { - // This test doesn't work in CompositeAfterPaint mode because we always paint - // from the local root frame view, and we always expand cull rect for - // scrolling. - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; - SetBodyInnerHTML(R"HTML( <div id='container1' style='position: relative; z-index: 1; width: 200px; height: 200px; background-color: blue'> @@ -310,15 +309,15 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceOnCullRectChange) { IsSameId(&content3, kBackgroundType))); UpdateAllLifecyclePhasesExceptPaint(); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; PaintContents(IntRect(0, 100, 300, 1000)); // Container1 becomes partly in the interest rect, but uses cached subsequence // because it was fully painted before; // Container2's intersection with the interest rect changes; // Content2b is out of the interest rect and outputs nothing; // Container3 becomes out of the interest rect and outputs nothing. - EXPECT_EQ(5u, counter.NumNewCachedItems()); - EXPECT_EQ(1u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(5u, counter.num_cached_items); + EXPECT_EQ(2u, counter.num_cached_subsequences); EXPECT_THAT(ContentDisplayItems(), ElementsAre(VIEW_SCROLLING_BACKGROUND_DISPLAY_ITEM, @@ -356,12 +355,12 @@ TEST_P(PaintLayerPainterTest, SetBodyInnerHTML(R"HTML( <div id='container1' style='position: relative; z-index: 1; width: 200px; height: 200px; background-color: blue'> - <div id='content1' style='position: absolute; width: 100px; + <div id='content1' style='overflow: hidden; width: 100px; height: 100px; background-color: red'></div> </div> <div id='container2' style='position: relative; z-index: 1; width: 200px; height: 200px; background-color: blue'> - <div id='content2' style='position: absolute; width: 100px; + <div id='content2' style='overflow: hidden; width: 100px; height: 100px; background-color: green'></div> </div> )HTML"); @@ -390,10 +389,9 @@ TEST_P(PaintLayerPainterTest, "position: absolute; width: 100px; height: 100px; " "background-color: green"); UpdateAllLifecyclePhasesExceptPaint(); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; PaintContents(IntRect(0, 0, 50, 300)); - EXPECT_EQ(4u, counter.NumNewCachedItems()); - EXPECT_EQ(1u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(4u, counter.num_cached_items); EXPECT_THAT(ContentDisplayItems(), ElementsAre(VIEW_SCROLLING_BACKGROUND_DISPLAY_ITEM, @@ -450,10 +448,10 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceRetainsPreviousPaintResult) { "display: block"); UpdateAllLifecyclePhasesExceptPaint(); EXPECT_FALSE(target_layer->SelfNeedsRepaint()); - CachedItemAndSubsequenceCounter counter; + PaintController::CounterForTesting counter; UpdateAllLifecyclePhasesForTest(); - EXPECT_EQ(2u, counter.NumNewCachedItems()); - EXPECT_EQ(1u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(2u, counter.num_cached_items); + EXPECT_EQ(1u, counter.num_cached_subsequences); // |target| is still partially painted. EXPECT_EQ(kMayBeClippedByCullRect, target_layer->PreviousPaintResult()); @@ -494,8 +492,8 @@ TEST_P(PaintLayerPainterTest, CachedSubsequenceRetainsPreviousPaintResult) { counter.Reset(); UpdateAllLifecyclePhasesForTest(); - EXPECT_EQ(2u, counter.NumNewCachedItems()); - EXPECT_EQ(0u, counter.NumNewCachedSubsequences()); + EXPECT_EQ(2u, counter.num_cached_items); + EXPECT_EQ(0u, counter.num_cached_subsequences); // |target| is still partially painted. EXPECT_EQ(kMayBeClippedByCullRect, target_layer->PreviousPaintResult()); @@ -527,12 +525,13 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithBackgrounds) { div { background: blue } </style> <div id='container1' style='position: relative; height: 150px; z-index: 1'> - <div id='content1a' style='position: relative; height: 100px'></div> - <div id='content1b' style='position: relative; height: 100px'></div> + <div id='content1a' style='overflow: hidden; height: 100px'></div> + <div id='content1b' style='overflow: hidden; height: 100px'></div> </div> <div id='container2' style='position: relative; z-index: 1'> - <div id='content2a' style='position: relative; height: 100px'></div> - <div id='content2b' style='position: relative; z-index: -1; height: 100px'></div> + <div id='content2a' style='overflow: hidden; height: 100px'></div> + <div id='content2b' + style='position: relative; z-index: -1; height: 100px'></div> </div> )HTML"); @@ -559,14 +558,9 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithBackgrounds) { VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, // Includes |container1| and |content1a|. IsPaintChunk( - 1, 3, + 1, 4, PaintChunk::Id(*container1->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 0, 800, 150)), - // Includes |content1b| which overflows |container1|. - IsPaintChunk( - 3, 4, - PaintChunk::Id(*content1b->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 100, 800, 100)), + chunk_state, nullptr, IntRect(0, 0, 800, 200)), IsPaintChunk( 4, 5, PaintChunk::Id(*container2->Layer(), DisplayItem::kLayerChunk), @@ -575,10 +569,10 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithBackgrounds) { 5, 6, PaintChunk::Id(*content2b->Layer(), DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 250, 800, 100)), - IsPaintChunk( - 6, 7, - PaintChunk::Id(*content2a->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 150, 800, 100)))); + IsPaintChunk(6, 7, + PaintChunk::Id(*container2->Layer(), + DisplayItem::kLayerChunkForeground), + chunk_state, nullptr, IntRect(0, 150, 800, 100)))); } TEST_P(PaintLayerPainterTest, HintedPaintChunksWithoutBackgrounds) { @@ -588,20 +582,18 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithoutBackgrounds) { SetBodyInnerHTML(R"HTML( <style>body { margin: 0 }</style> <div id='container1' style='position: relative; height: 150px; z-index: 1'> - <div id='content1a' style='position: relative; height: 100px'></div> - <div id='content1b' style='position: relative; height: 100px'></div> + <div id='content1a' style='overflow: hidden; height: 100px'></div> + <div id='content1b' style='overflow: hidden; height: 100px'></div> </div> <div id='container2' style='position: relative; z-index: 1'> - <div id='content2a' style='position: relative; height: 100px'></div> + <div id='content2a' style='overflow: hidden; height: 100px'></div> <div id='content2b' style='position: relative; z-index: -1; height: 100px'></div> </div> )HTML"); auto* container1 = GetLayoutBoxByElementId("container1"); - auto* content1b = GetLayoutBoxByElementId("content1b"); auto* container2 = GetLayoutBoxByElementId("container2"); - auto* content2a = GetLayoutBoxByElementId("content2a"); auto* content2b = GetLayoutBoxByElementId("content2b"); auto chunk_state = GetLayoutView().FirstFragment().ContentsProperties(); @@ -615,11 +607,7 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithoutBackgrounds) { IsPaintChunk( 1, 1, PaintChunk::Id(*container1->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 0, 800, 150)), - IsPaintChunk( - 1, 1, - PaintChunk::Id(*content1b->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 100, 800, 100)), + chunk_state, nullptr, IntRect(0, 0, 800, 200)), IsPaintChunk( 1, 1, PaintChunk::Id(*container2->Layer(), DisplayItem::kLayerChunk), @@ -628,10 +616,10 @@ TEST_P(PaintLayerPainterTest, HintedPaintChunksWithoutBackgrounds) { 1, 1, PaintChunk::Id(*content2b->Layer(), DisplayItem::kLayerChunk), chunk_state, nullptr, IntRect(0, 250, 800, 100)), - IsPaintChunk( - 1, 1, - PaintChunk::Id(*content2a->Layer(), DisplayItem::kLayerChunk), - chunk_state, nullptr, IntRect(0, 150, 800, 100)))); + IsPaintChunk(1, 1, + PaintChunk::Id(*container2->Layer(), + DisplayItem::kLayerChunkForeground), + chunk_state, nullptr, IntRect(0, 150, 800, 100)))); } TEST_P(PaintLayerPainterTest, PaintPhaseOutline) { @@ -891,149 +879,6 @@ TEST_P(PaintLayerPainterTest, PaintPhasesUpdateOnBecomingNonSelfPainting) { EXPECT_TRUE(html_layer.NeedsPaintPhaseDescendantOutlines()); } -TEST_P(PaintLayerPainterTest, DontPaintWithTinyOpacity) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.0001'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, true); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndWillChangeOpacity) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.0001; " - " will-change: opacity'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndBackdropFilter) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.0001;" - " backdrop-filter: blur(2px);'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, - DoPaintWithTinyOpacityAndBackdropFilterAndWillChangeOpacity) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.0001;" - " backdrop-filter: blur(2px); will-change: opacity'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithCompositedTinyOpacity) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.0001;" - " will-change: transform'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithNonTinyOpacity) { - SetBodyInnerHTML( - "<div id='target' style='background: blue; opacity: 0.1'></div>"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, true); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div { - width: 100px; - height: 100px; - animation-name: example; - animation-duration: 4s; - } - @keyframes example { - from { opacity: 0.0;} - to { opacity: 1.0;} - } - </style> - <div id='target'></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithTransformAnimationZeroOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div#target { - animation-name: example; - animation-duration: 4s; - opacity: 0.0; - } - @keyframes example { - from { transform: translate(0px, 0px); } - to { transform: translate(3em, 0px); } - } - </style> - <div id='target'>x</div></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false); -} - -TEST_P(PaintLayerPainterTest, - DoPaintWithTransformAnimationZeroOpacityWillChangeOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div#target { - animation-name: example; - animation-duration: 4s; - opacity: 0.0; - will-change: opacity; - } - @keyframes example { - from { transform: translate(0px, 0px); } - to { transform: translate(3em, 0px); } - } - </style> - <div id='target'>x</div></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div { - width: 100px; - height: 100px; - will-change: opacity; - } - </style> - <div id='target'></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div { - width: 100px; - height: 100px; - opacity: 0; - will-change: opacity; - } - </style> - <div id='target'></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - -TEST_P(PaintLayerPainterTest, - DoPaintWithNoContentAndZeroOpacityAndWillChangeOpacity) { - SetBodyInnerHTML(R"HTML( - <style> - div { - width: 100px; - height: 100px; - opacity: 0; - will-change: opacity; - } - </style> - <div id='target'></div> - )HTML"); - ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false); -} - using PaintLayerPainterTestCAP = PaintLayerPainterTest; INSTANTIATE_CAP_TEST_SUITE_P(PaintLayerPainterTestCAP); @@ -1214,6 +1059,33 @@ TEST_P(PaintLayerPainterTestCAP, ScaledAndRotatedCullRect) { GetCullRect(*GetPaintLayerByElementId("target")).Rect()); } +// This is a testcase for https://crbug.com/1227907 where repeated cull rect +// updates are expensive on the motionmark microbenchmark. +TEST_P(PaintLayerPainterTestCAP, OptimizeNonCompositedTransformUpdate) { + SetBodyInnerHTML(R"HTML( + <style> + #target { + width: 50px; + height: 50px; + background: green; + transform: translate(-8px, -8px); + } + </style> + <div id='target'></div> + )HTML"); + + // The cull rect should be correctly calculated on first paint. + EXPECT_EQ(IntRect(0, 0, 800, 600), + GetCullRect(*GetPaintLayerByElementId("target")).Rect()); + + // On subsequent paints, fall back to an infinite cull rect. + GetDocument().getElementById("target")->setAttribute( + html_names::kStyleAttr, "transform: rotate(10deg);"); + UpdateAllLifecyclePhasesForTest(); + EXPECT_EQ(CullRect::Infinite().Rect(), + GetCullRect(*GetPaintLayerByElementId("target")).Rect()); +} + TEST_P(PaintLayerPainterTestCAP, 3DRotated90DegreesCullRect) { GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true); SetBodyInnerHTML(R"HTML( @@ -1381,4 +1253,138 @@ TEST_P(PaintLayerPainterTestCAP, ClippedBigLayer) { GetCullRect(*GetPaintLayerByElementId("target")).Rect()); } +class PaintLayerPainterPaintedOutputInvisibleTest + : public PaintLayerPainterTest { + protected: + void RunTest() { + SetBodyInnerHTML(R"HTML( + <div id="parent"> + <div id="target"> + <div id="child"></div> + </div> + </div> + <style> + #parent { + width: 10px; + height: 10px; + will-change: transform; + } + #target { + width: 100px; + height: 100px; + opacity: 0.0001; + } + #child { + width: 200px; + height: 50px; + opacity: 0.9; + } + )HTML" + additional_style_ + + "</style>"); + + auto* parent = GetLayoutObjectByElementId("parent"); + auto* parent_layer = To<LayoutBox>(parent)->Layer(); + auto* target = GetLayoutObjectByElementId("target"); + auto* target_layer = To<LayoutBox>(target)->Layer(); + auto* child = GetLayoutObjectByElementId("child"); + auto* child_layer = To<LayoutBox>(child)->Layer(); + + EXPECT_EQ(expected_invisible_, + PaintLayerPainter::PaintedOutputInvisible( + target_layer->GetLayoutObject().StyleRef())); + + auto* cc_layer = + CcLayersByDOMElementId(GetDocument().View()->RootCcLayer(), + expected_composited_ ? "target" : "parent")[0]; + ASSERT_TRUE(cc_layer); + EXPECT_EQ(gfx::Size(200, 100), cc_layer->bounds()); + + auto chunks = ContentPaintChunks(); + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + EXPECT_THAT( + chunks, + ElementsAre( + VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, + IsPaintChunk( + 1, 1, PaintChunk::Id(*parent_layer, DisplayItem::kLayerChunk), + parent->FirstFragment().LocalBorderBoxProperties(), nullptr, + IntRect(0, 0, 10, 10)), + IsPaintChunk( + 1, 1, PaintChunk::Id(*target_layer, DisplayItem::kLayerChunk), + target->FirstFragment().LocalBorderBoxProperties(), nullptr, + IntRect(0, 0, 100, 100)), + IsPaintChunk( + 1, 1, PaintChunk::Id(*child_layer, DisplayItem::kLayerChunk), + child->FirstFragment().LocalBorderBoxProperties(), nullptr, + IntRect(0, 0, 200, 50)))); + EXPECT_FALSE((chunks.begin() + 1)->effectively_invisible); + EXPECT_EQ(expected_invisible_, + (chunks.begin() + 2)->effectively_invisible); + EXPECT_EQ(expected_invisible_, + (chunks.begin() + 3)->effectively_invisible); + } else { + EXPECT_EQ(expected_paints_with_transparency_, + target_layer->PaintsWithTransparency(kGlobalPaintNormalPhase)); + } + } + + String additional_style_; + bool expected_composited_ = false; + bool expected_invisible_ = true; + bool expected_paints_with_transparency_ = true; +}; + +INSTANTIATE_PAINT_TEST_SUITE_P(PaintLayerPainterPaintedOutputInvisibleTest); + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, TinyOpacity) { + expected_composited_ = false; + expected_invisible_ = true; + expected_paints_with_transparency_ = true; + RunTest(); +} + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, + TinyOpacityAndWillChangeOpacity) { + additional_style_ = "#target { will-change: opacity; }"; + expected_composited_ = true; + expected_invisible_ = false; + expected_paints_with_transparency_ = false; + RunTest(); +} + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, + TinyOpacityAndBackdropFilter) { + additional_style_ = "#target { backdrop-filter: blur(2px); }"; + expected_composited_ = true; + expected_invisible_ = false; + expected_paints_with_transparency_ = false; + RunTest(); +} + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, + TinyOpacityAndWillChangeTransform) { + additional_style_ = "#target { will-change: transform; }"; + expected_composited_ = true; + expected_invisible_ = true; + expected_paints_with_transparency_ = false; + RunTest(); +} + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, NonTinyOpacity) { + additional_style_ = "#target { opacity: 0.5; }"; + expected_composited_ = false; + expected_invisible_ = false; + expected_paints_with_transparency_ = true; + RunTest(); +} + +TEST_P(PaintLayerPainterPaintedOutputInvisibleTest, + NonTinyOpacityAndWillChangeOpacity) { + additional_style_ = "#target { opacity: 1; will-change: opacity; }"; + expected_composited_ = true; + expected_invisible_ = false; + expected_paints_with_transparency_ = false; + RunTest(); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_painting_info.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_painting_info.h index d5d86d38692..b0bb26c4a57 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_painting_info.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_painting_info.h @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> @@ -63,7 +63,6 @@ class PaintLayer; enum PaintLayerFlag { kPaintLayerNoFlag = 0, - kPaintLayerHaveTransparency = 1, kPaintLayerPaintingOverlayOverflowControls = 1 << 3, kPaintLayerPaintingCompositingBackgroundPhase = 1 << 4, kPaintLayerPaintingCompositingForegroundPhase = 1 << 5, @@ -135,8 +134,6 @@ inline String PaintLayerFlagsToDebugString(PaintLayerFlags flags) { append("kPaintLayerPaintingCompositingDecorationPhase"); } - if (flags & kPaintLayerHaveTransparency) - append("kPaintLayerHaveTransparency"); if (flags & kPaintLayerPaintingOverlayOverflowControls) append("kPaintLayerPaintingOverlayOverflowControls"); if (flags & kPaintLayerPaintingCompositingScrollingPhase) diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc index c838509b144..072c1af3382 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@gmail.com> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> @@ -76,6 +76,7 @@ #include "third_party/blink/renderer/core/html/forms/text_control_element.h" #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" #include "third_party/blink/renderer/core/input/event_handler.h" +#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/layout/custom_scrollbar.h" #include "third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" @@ -94,6 +95,7 @@ #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.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/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_layer_fragment.h" #include "third_party/blink/renderer/core/scroll/scroll_alignment.h" @@ -179,8 +181,8 @@ PaintLayerScrollableArea* PaintLayerScrollableArea::FromNode(const Node& node) { return box ? box->GetScrollableArea() : nullptr; } -void PaintLayerScrollableArea::DidScroll(const FloatPoint& position) { - ScrollableArea::DidScroll(position); +void PaintLayerScrollableArea::DidCompositorScroll(const FloatPoint& position) { + ScrollableArea::DidCompositorScroll(position); // This should be alive if it receives composited scroll callbacks. CHECK(!HasBeenDisposed()); } @@ -646,16 +648,30 @@ void PaintLayerScrollableArea::InvalidatePaintForScrollOffsetChange() { bool background_paint_in_scrolling_contents = background_paint_location & kBackgroundPaintInScrollingContents; - // Both local attachment background painted in graphics layer and normal - // attachment background painted in scrolling contents require paint - // invalidation. Fixed attachment background has been dealt with in + // Invalidate background on scroll if needed. + // Fixed attachment background has been dealt with in // frame_view->InvalidateBackgroundAttachmentFixedDescendantsOnScroll(). - auto background_layers = box->StyleRef().BackgroundLayers(); - if ((background_layers.AnyLayerHasLocalAttachmentImage() && - background_paint_in_graphics_layer) || - (background_layers.AnyLayerHasDefaultAttachmentImage() && - background_paint_in_scrolling_contents)) + const auto& background_layers = box->StyleRef().BackgroundLayers(); + if (background_layers.AnyLayerHasLocalAttachmentImage() && + background_paint_in_graphics_layer) { + // Local-attachment background image scrolls, so needs invalidation if it + // paints in non-scrolling space. box->SetBackgroundNeedsFullPaintInvalidation(); + } else if (background_layers.AnyLayerHasDefaultAttachmentImage() && + background_paint_in_scrolling_contents) { + // Normal attachment background image doesn't scroll, so needs + // invalidation if it paints in scrolling contents. + box->SetBackgroundNeedsFullPaintInvalidation(); + } else if (background_layers.AnyLayerHasLocalAttachment() && + background_layers.AnyLayerUsesContentBox() && + background_paint_in_graphics_layer && + (box->PaddingLeft() || box->PaddingTop() || + box->PaddingRight() || box->PaddingBottom())) { + // Local attachment content box background needs invalidation if there is + // padding because the content area can change on scroll (e.g. the top + // padding can disappear when the box scrolls to the bottom). + box->SetBackgroundNeedsFullPaintInvalidation(); + } } // If any scrolling content might have been clipped by a cull rect, then @@ -2669,6 +2685,12 @@ bool PaintLayerScrollableArea::ComputeNeedsCompositedScrollingInternal( return needs_composited_scrolling; } +bool PaintLayerScrollableArea::UsesCompositedScrolling() const { + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) + return GetLayoutBox()->UsesCompositedScrolling(); + return ScrollableArea::UsesCompositedScrolling(); +} + void PaintLayerScrollableArea::UpdateNeedsCompositedScrolling( bool force_prefer_compositing_to_lcd_text) { bool new_needs_composited_scrolling = @@ -2785,9 +2807,7 @@ Scrollbar* PaintLayerScrollableArea::ScrollbarManager::CreateScrollbar( ScrollableArea(), orientation, To<Element>(style_source.GetNode())); } else { Element* style_source_element = nullptr; - if (::features::IsFormControlsRefreshEnabled()) { - style_source_element = DynamicTo<Element>(style_source.GetNode()); - } + style_source_element = DynamicTo<Element>(style_source.GetNode()); scrollbar = MakeGarbageCollected<Scrollbar>(ScrollableArea(), orientation, style_source_element); } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h index 81e80996e04..d794536ad08 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h @@ -5,7 +5,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> @@ -278,7 +278,7 @@ class CORE_EXPORT PaintLayerScrollableArea final // only a helper. cc::Layer* LayerForScrolling() const override; - void DidScroll(const FloatPoint&) override; + void DidCompositorScroll(const FloatPoint&) override; // GraphicsLayers for the scrolling components. // Any function can return nullptr if they are not accelerated. @@ -459,6 +459,11 @@ class CORE_EXPORT PaintLayerScrollableArea final // Rectangle encompassing the scroll corner and resizer rect. IntRect ScrollCornerAndResizerRect() const; + // The difference between this function and NeedsCompositedScrolling() is + // that this function returns the composited scrolling status based on paint + // properties which are updated based on the latter. + bool UsesCompositedScrolling() const override; + void UpdateNeedsCompositedScrolling( bool force_prefer_compositing_to_lcd_text); bool NeedsCompositedScrolling() const { return needs_composited_scrolling_; } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc index 78187982a9d..bac1b5d58ad 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "build/build_config.h" +#include "cc/layers/picture_layer.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/renderer/core/animation/scroll_timeline.h" @@ -17,6 +18,7 @@ #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/scroll/scroll_types.h" #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" +#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" using testing::_; @@ -921,7 +923,7 @@ TEST_P(PaintLayerScrollableAreaTest, } TEST_P(PaintLayerScrollableAreaTest, - ScrollWithFixedDoesNotNeedCompositingInputsUpdate) { + ScrollWithFixedDoesNotNeedCompositingUpdate) { SetBodyInnerHTML(R"HTML( <style> * { @@ -951,6 +953,9 @@ TEST_P(PaintLayerScrollableAreaTest, scrollable_area->SetScrollOffset(ScrollOffset(0, 1), mojom::blink::ScrollType::kProgrammatic); EXPECT_FALSE(scrollable_area->Layer()->NeedsCompositingInputsUpdate()); + UpdateAllLifecyclePhasesExceptPaint(); + EXPECT_FALSE( + GetDocument().View()->GetPaintArtifactCompositor()->NeedsUpdate()); UpdateAllLifecyclePhasesForTest(); EXPECT_EQ(FloatSize(0, 1), scrollable_area->GetScrollOffset()); } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.cc index 30ab95fbbb3..5ae07789067 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.cc @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h b/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h index ff04f7c7ae9..9544b3f6374 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h @@ -6,7 +6,7 @@ * * Other contributors: * Robert O'Callahan <roc+@cs.cmu.edu> - * David Baron <dbaron@fas.harvard.edu> + * David Baron <dbaron@dbaron.org> * Christian Biesinger <cbiesinger@web.de> * Randall Jesup <rjesup@wgate.com> * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_layer_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_layer_test.cc index 5bf75df25e1..398368e4ff3 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_layer_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_layer_test.cc @@ -212,7 +212,7 @@ TEST_P(PaintLayerTest, CompositedScrollingNoNeedsRepaint) { EXPECT_EQ(PhysicalOffset(0, 0), content_layer->LocationWithoutPositionOffset()); EXPECT_EQ( - LayoutSize(1000, 1000), + IntPoint(1000, 1000), content_layer->ContainingLayer()->PixelSnappedScrolledContentOffset()); EXPECT_FALSE(content_layer->SelfNeedsRepaint()); EXPECT_FALSE(scroll_layer->SelfNeedsRepaint()); @@ -249,7 +249,7 @@ TEST_P(PaintLayerTest, NonCompositedScrollingNeedsRepaint) { EXPECT_EQ(PhysicalOffset(0, 0), content_layer->LocationWithoutPositionOffset()); EXPECT_EQ( - LayoutSize(1000, 1000), + IntPoint(1000, 1000), content_layer->ContainingLayer()->PixelSnappedScrolledContentOffset()); if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { @@ -1074,44 +1074,35 @@ TEST_P(PaintLayerTest, SubsequenceCachingStackedLayers) { PaintLayer* grandchild1 = GetPaintLayerByElementId("grandchild1"); PaintLayer* grandchild2 = GetPaintLayerByElementId("grandchild2"); - EXPECT_FALSE(parent->SupportsSubsequenceCaching()); - EXPECT_FALSE(child1->SupportsSubsequenceCaching()); - EXPECT_TRUE(child2->SupportsSubsequenceCaching()); - EXPECT_FALSE(grandchild1->SupportsSubsequenceCaching()); - EXPECT_FALSE(grandchild2->SupportsSubsequenceCaching()); - - GetDocument() - .getElementById("grandchild1") - ->setAttribute(html_names::kStyleAttr, "isolation: isolate"); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_FALSE(parent->SupportsSubsequenceCaching()); - EXPECT_FALSE(child1->SupportsSubsequenceCaching()); + EXPECT_TRUE(parent->SupportsSubsequenceCaching()); + EXPECT_TRUE(child1->SupportsSubsequenceCaching()); EXPECT_TRUE(child2->SupportsSubsequenceCaching()); EXPECT_TRUE(grandchild1->SupportsSubsequenceCaching()); - EXPECT_FALSE(grandchild2->SupportsSubsequenceCaching()); + EXPECT_TRUE(grandchild2->SupportsSubsequenceCaching()); } -TEST_P(PaintLayerTest, SubsequenceCachingSVGRoot) { +TEST_P(PaintLayerTest, SubsequenceCachingSVG) { SetBodyInnerHTML(R"HTML( - <div id='parent' style='position: relative'> - <svg id='svgroot' style='position: relative'></svg> - </div> + <svg id='svgroot'> + <foreignObject id='foreignObject'/> + </svg> )HTML"); PaintLayer* svgroot = GetPaintLayerByElementId("svgroot"); + PaintLayer* foreign_object = GetPaintLayerByElementId("foreignObject"); EXPECT_TRUE(svgroot->SupportsSubsequenceCaching()); + EXPECT_TRUE(foreign_object->SupportsSubsequenceCaching()); } TEST_P(PaintLayerTest, SubsequenceCachingMuticol) { SetBodyInnerHTML(R"HTML( <div style='columns: 2'> - <svg id='svgroot' style='position: relative'></svg> + <div id='target' style='position: relative'></div> </div> )HTML"); - PaintLayer* svgroot = GetPaintLayerByElementId("svgroot"); - EXPECT_FALSE(svgroot->SupportsSubsequenceCaching()); + PaintLayer* target = GetPaintLayerByElementId("target"); + EXPECT_FALSE(target->SupportsSubsequenceCaching()); } TEST_P(PaintLayerTest, NegativeZIndexChangeToPositive) { @@ -1635,7 +1626,7 @@ TEST_P(PaintLayerTest, FloatLayerUnderInlineLayerScrolled) { EXPECT_EQ(PhysicalOffset(0, 0), span->LocationWithoutPositionOffset()); EXPECT_EQ(PhysicalOffset(0, 0), span->GetLayoutObject().OffsetForInFlowPosition()); - EXPECT_EQ(LayoutSize(0, 400), + EXPECT_EQ(IntPoint(0, 400), span->ContainingLayer()->PixelSnappedScrolledContentOffset()); EXPECT_EQ(PhysicalOffset(150, 150), floating->LocationWithoutPositionOffset()); @@ -1649,12 +1640,12 @@ TEST_P(PaintLayerTest, FloatLayerUnderInlineLayerScrolled) { EXPECT_EQ(PhysicalOffset(0, 0), span->LocationWithoutPositionOffset()); EXPECT_EQ(PhysicalOffset(100, 100), span->GetLayoutObject().OffsetForInFlowPosition()); - EXPECT_EQ(LayoutSize(0, 400), + EXPECT_EQ(IntPoint(0, 400), span->ContainingLayer()->PixelSnappedScrolledContentOffset()); EXPECT_EQ(PhysicalOffset(0, 0), floating->LocationWithoutPositionOffset()); EXPECT_EQ(PhysicalOffset(50, 50), floating->GetLayoutObject().OffsetForInFlowPosition()); - EXPECT_EQ(LayoutSize(0, 400), + EXPECT_EQ(IntPoint(0, 400), floating->ContainingLayer()->PixelSnappedScrolledContentOffset()); EXPECT_EQ(PhysicalOffset(-50, -50), floating->VisualOffsetFromAncestor(span)); @@ -1963,7 +1954,7 @@ TEST_P(PaintLayerTest, ColumnSpanLayerUnderExtraLayerScrolled) { EXPECT_EQ(PhysicalOffset(50, 50), spanner->GetLayoutObject().OffsetForInFlowPosition()); - EXPECT_EQ(LayoutSize(200, 0), + EXPECT_EQ(IntPoint(200, 0), spanner->ContainingLayer()->PixelSnappedScrolledContentOffset()); EXPECT_EQ(PhysicalOffset(0, 0), extra_layer->LocationWithoutPositionOffset()); EXPECT_EQ(PhysicalOffset(100, 100), @@ -2518,6 +2509,10 @@ TEST_P(PaintLayerTest, HitTestOverlayResizer) { } TEST_P(PaintLayerTest, BackgroundIsKnownToBeOpaqueInRectChildren) { + // This test doesn't apply in CompositeAfterPaint. + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) + return; + SetBodyInnerHTML(R"HTML( <style> div { @@ -2772,8 +2767,6 @@ TEST_P(PaintLayerTest, HasNonEmptyChildLayoutObjectsZeroSizeOverflowVisible) { EXPECT_TRUE(layer->HasNonEmptyChildLayoutObjects()); } -enum { kCompositingOptimizations = 1 << 0 }; - class PaintLayerOverlapTest : public RenderingTest { public: PaintLayerOverlapTest() diff --git a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc index 0d45e6fcda3..fb7fcfa2e15 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc @@ -28,7 +28,7 @@ #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h" +#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h" @@ -111,6 +111,7 @@ PaintPropertyTreeBuilderContext::PaintPropertyTreeBuilderContext() was_main_thread_scrolling(false) {} PaintPropertyChangeType VisualViewportPaintPropertyTreeBuilder::Update( + LocalFrameView& main_frame_view, VisualViewport& visual_viewport, PaintPropertyTreeBuilderContext& full_context) { if (full_context.fragments.IsEmpty()) @@ -121,6 +122,11 @@ PaintPropertyChangeType VisualViewportPaintPropertyTreeBuilder::Update( auto property_changed = visual_viewport.UpdatePaintPropertyNodesIfNeeded(context); + if (const EffectPaintPropertyNode* overscroll_elasticity_effect_node = + visual_viewport.GetOverscrollElasticityEffectNode()) { + context.current_effect = overscroll_elasticity_effect_node; + } + context.current.transform = visual_viewport.GetScrollTranslationNode(); context.absolute_position.transform = visual_viewport.GetScrollTranslationNode(); @@ -135,6 +141,10 @@ PaintPropertyChangeType VisualViewportPaintPropertyTreeBuilder::Update( // removed). Not a big deal for performance because this is rare. full_context.force_subtree_update_reasons |= PaintPropertyTreeBuilderContext::kSubtreeUpdateIsolationPiercing; + // The main frame's paint chunks (e.g. scrollbars) may reference paint + // properties of the visual viewport. + if (auto* layout_view = main_frame_view.GetLayoutView()) + layout_view->Layer()->SetNeedsRepaint(); } #if DCHECK_IS_ON() @@ -250,6 +260,38 @@ class FragmentPaintPropertyTreeBuilder { bool IsInNGFragmentTraversal() const { return pre_paint_info_; } + void SwitchToOOFContext( + PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext& + oof_context) const { + context_.current = oof_context; + + // If we're not block-fragmented, or if we're traversing the fragment tree + // to an orphaned object, simply setting a new context is all we have to do. + if (!oof_context.is_in_block_fragmentation || + (pre_paint_info_ && pre_paint_info_->is_inside_orphaned_object)) + return; + + // Inside NG block fragmentation we have to perform an offset adjustment. + // An OOF fragment that is contained by something inside a fragmentainer + // will be a direct child of the fragmentainer, rather than a child of its + // actual containing block. We therefore need to adjust the offset to make + // us relative to the fragmentainer before applying the offset of the OOF. + PhysicalOffset delta = + oof_context.paint_offset - context_.fragmentainer_paint_offset; + // So, we did store |fragmentainer_paint_offset| when entering the + // fragmentainer, but the offset may have been reset by + // UpdateForPaintOffsetTranslation() since we entered it, which we'll need + // to compensate for now. + delta += context_.adjustment_for_oof_in_fragmentainer; + context_.current.paint_offset -= delta; + } + + void ResetPaintOffset(PhysicalOffset new_offset = PhysicalOffset()) { + context_.adjustment_for_oof_in_fragmentainer += + context_.current.paint_offset - new_offset; + context_.current.paint_offset = new_offset; + } + void OnUpdate(PaintPropertyChangeType change) { property_changed_ = std::max(property_changed_, change); } @@ -499,7 +541,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( // compositing code. if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && NeedsIsolationNodes(object_)) { - context_.current.paint_offset = PhysicalOffset(); + ResetPaintOffset(); context_.current.directly_composited_container_paint_offset_subpixel_delta = PhysicalOffset(); return; @@ -514,9 +556,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( PhysicalOffset subpixel_accumulation; if (RequiresFragmentStitching()) { const LayoutBox& box = To<LayoutBox>(object_); - const NGFragmentChildIterator& iterator = pre_paint_info_->iterator; - const NGPhysicalBoxFragment& fragment = *iterator->BoxFragment(); - const NGBlockBreakToken* incoming_break_token = iterator->BlockBreakToken(); + const NGPhysicalBoxFragment& fragment = pre_paint_info_->box_fragment; // Calculate this fragment's rectangle relatively to the enclosing stitched // layout object, with help from the break token. @@ -528,7 +568,8 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( WritingModeConverter converter(box.StyleRef().GetWritingDirection(), PhysicalSize(box.Size())); LogicalOffset logical_offset; - if (incoming_break_token) + if (const NGBlockBreakToken* incoming_break_token = + FindPreviousBreakToken(fragment)) logical_offset.block_offset = incoming_break_token->ConsumedBlockSize(); LogicalRect logical_rect(logical_offset, converter.ToLogical(fragment.Size())); @@ -540,7 +581,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( // offset in order to get to the right offset for each fragment. new_paint_offset = converter.ToPhysical(logical_rect).offset; - if (&pre_paint_info_->fragment_data == &object_.FirstFragment()) { + if (pre_paint_info_->fragment_data == &object_.FirstFragment()) { // Make the translation relatively to the top/left corner of the box. In // vertical-rl writing mode, the first fragment is not the leftmost one. PhysicalOffset topleft = context_.current.paint_offset - new_paint_offset; @@ -571,7 +612,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( // If the object has a non-translation transform, discard the fractional // paint offset which can't be transformed by the transform. if (!CanPropagateSubpixelAccumulation()) { - context_.current.paint_offset = new_paint_offset; + ResetPaintOffset(new_paint_offset); context_.current .directly_composited_container_paint_offset_subpixel_delta = PhysicalOffset(); @@ -579,7 +620,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation( } } - context_.current.paint_offset = new_paint_offset + subpixel_accumulation; + ResetPaintOffset(new_paint_offset + subpixel_accumulation); bool can_be_directly_composited = RuntimeEnabledFeatures::CompositeAfterPaintEnabled() @@ -616,6 +657,14 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffsetTranslation( state.direct_compositing_reasons = full_context_.direct_compositing_reasons & CompositingReason::kDirectReasonsForPaintOffsetTranslationProperty; + if (state.direct_compositing_reasons & CompositingReason::kFixedPosition && + object_.View()->FirstFragment().PaintProperties()->Scroll()) { + state.scroll_translation_for_fixed = object_.View() + ->FirstFragment() + .PaintProperties() + ->ScrollTranslation(); + } + if (IsA<LayoutView>(object_)) { DCHECK(object_.GetFrame()); state.flags.is_frame_paint_offset_translation = true; @@ -761,7 +810,7 @@ static CompositingReasons CompositingReasonsForTransformProperty() { reasons |= CompositingReason::kWillChangeFilter; reasons |= CompositingReason::kWillChangeBackdropFilter; - if (RuntimeEnabledFeatures::TransformInteropEnabled()) + if (RuntimeEnabledFeatures::BackfaceVisibilityInteropEnabled()) reasons |= CompositingReason::kBackfaceInvisibility3DAncestor; return reasons; @@ -842,19 +891,16 @@ void FragmentPaintPropertyTreeBuilder::UpdateTransformForSVGChild( // TODO(pdr): There is additional logic in // FragmentPaintPropertyTreeBuilder::UpdateTransform that likely needs to - // be included here, such as setting animation_is_axis_aligned, which - // may be the only important difference remaining. - if (RuntimeEnabledFeatures::CompositeSVGEnabled()) { - state.direct_compositing_reasons = - direct_compositing_reasons & - CompositingReasonsForTransformProperty(); - state.flags.flattens_inherited_transform = - context_.current.should_flatten_inherited_transform; - state.rendering_context_id = context_.current.rendering_context_id; - state.flags.is_for_svg_child = true; - state.compositor_element_id = GetCompositorElementId( - CompositorElementIdNamespace::kPrimaryTransform); - } + // be included here, such as setting animation_is_axis_aligned, which may + // be the only important difference remaining. + state.direct_compositing_reasons = + direct_compositing_reasons & CompositingReasonsForTransformProperty(); + state.flags.flattens_inherited_transform = + context_.current.should_flatten_inherited_transform; + state.rendering_context_id = context_.current.rendering_context_id; + state.flags.is_for_svg_child = true; + state.compositor_element_id = GetCompositorElementId( + CompositorElementIdNamespace::kPrimaryTransform); TransformPaintPropertyNode::AnimationState animation_state; animation_state.is_running_animation_on_compositor = @@ -900,8 +946,11 @@ static FloatPoint3D TransformOrigin(const ComputedStyle& style, style.TransformOriginZ()); } -static bool NeedsTransform(const LayoutObject& object, - CompositingReasons direct_compositing_reasons) { +} // namespace + +bool PaintPropertyTreeBuilder::NeedsTransform( + const LayoutObject& object, + CompositingReasons direct_compositing_reasons) { if (object.IsText()) return false; @@ -920,6 +969,8 @@ static bool NeedsTransform(const LayoutObject& object, return false; } +namespace { + static bool UpdateBoxSizeAndCheckActiveAnimationAxisAlignment( const LayoutBox& object, CompositingReasons compositing_reasons) { @@ -949,7 +1000,8 @@ void FragmentPaintPropertyTreeBuilder::UpdateTransform() { // direct compositing reason. The latter is required because this is the // only way to represent compositing both an element and its stacking // descendants. - if (NeedsTransform(object_, full_context_.direct_compositing_reasons)) { + if (PaintPropertyTreeBuilder::NeedsTransform( + object_, full_context_.direct_compositing_reasons)) { TransformPaintPropertyNode::State state; if (object_.IsBox()) { @@ -957,10 +1009,11 @@ void FragmentPaintPropertyTreeBuilder::UpdateTransform() { // Each individual fragment should have its own transform origin, based // on the fragment size. We'll do that, unless the fragments aren't to // be stitched together. - PhysicalSize size( - !pre_paint_info_ || RequiresFragmentStitching() - ? PhysicalSize(box.Size()) - : pre_paint_info_->iterator->BoxFragment()->Size()); + PhysicalSize size; + if (!pre_paint_info_ || RequiresFragmentStitching()) + size = PhysicalSize(box.Size()); + else + size = pre_paint_info_->box_fragment.Size(); TransformationMatrix matrix; style.ApplyTransform( matrix, size.ToLayoutSize(), ComputedStyle::kExcludeTransformOrigin, @@ -1068,7 +1121,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateTransform() { transform->Translation2D(); } } else if (RuntimeEnabledFeatures::TransformInteropEnabled() && - !object_.IsAnonymous()) { + object_.IsForElement()) { // With kTransformInterop enabled, 3D rendering contexts follow the // DOM ancestor chain, so flattening should apply regardless of // presence of transform. @@ -1084,9 +1137,10 @@ static bool MayNeedClipPathClip(const LayoutObject& object) { (object.HasLayer() || object.IsSVGChild()); } -static bool NeedsClipPathClip(const LayoutObject& object) { +static bool NeedsClipPathClip(const LayoutObject& object, + const FragmentData& fragment_data) { // We should have already updated the clip path cache when this is called. - if (object.FirstFragment().ClipPathPath()) { + if (fragment_data.ClipPathPath()) { DCHECK(MayNeedClipPathClip(object)); return true; } @@ -1285,6 +1339,17 @@ void FragmentPaintPropertyTreeBuilder::UpdateEffect() { full_context_.direct_compositing_reasons & CompositingReasonsForEffectProperty(); + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + // If an effect node exists, add an additional direct compositing reason + // for 3d transforms to ensure it is composited. + CompositingReasons additional_transform_compositing_trigger = + CompositingReason::k3DTransform | + CompositingReason::kTrivial3DTransform; + state.direct_compositing_reasons |= + (full_context_.direct_compositing_reasons & + additional_transform_compositing_trigger); + } + if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { state.direct_compositing_reasons |= full_context_.direct_compositing_reasons & @@ -1534,6 +1599,18 @@ void FragmentPaintPropertyTreeBuilder::UpdateFilter() { state.direct_compositing_reasons = full_context_.direct_compositing_reasons & CompositingReasonsForFilterProperty(); + + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + // If an effect node exists, add an additional direct compositing reason + // for 3d transforms to ensure it is composited. + CompositingReasons additional_transform_compositing_trigger = + CompositingReason::k3DTransform | + CompositingReason::kTrivial3DTransform; + state.direct_compositing_reasons |= + (full_context_.direct_compositing_reasons & + additional_transform_compositing_trigger); + } + state.compositor_element_id = GetCompositorElementId(CompositorElementIdNamespace::kEffectFilter); @@ -1623,7 +1700,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateCssClip() { void FragmentPaintPropertyTreeBuilder::UpdateClipPathClip() { if (NeedsPaintPropertyUpdate()) { - if (!NeedsClipPathClip(object_)) { + if (!NeedsClipPathClip(object_, fragment_data_)) { OnClearClip(properties_->ClearClipPathClip()); } else { ClipPaintPropertyNode::State state( @@ -1831,13 +1908,14 @@ void FragmentPaintPropertyTreeBuilder::UpdateOverflowClip() { LayoutReplaced::PreSnappedRectForPersistentSizing(content_rect); } // LayoutReplaced clips the foreground by rounded content box. - auto clip_rect = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( - replaced.StyleRef(), content_rect, - LayoutRectOutsets( - -(replaced.PaddingTop() + replaced.BorderTop()), - -(replaced.PaddingRight() + replaced.BorderRight()), - -(replaced.PaddingBottom() + replaced.BorderBottom()), - -(replaced.PaddingLeft() + replaced.BorderLeft()))); + auto clip_rect = + RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( + replaced.StyleRef(), content_rect, + LayoutRectOutsets( + -(replaced.PaddingTop() + replaced.BorderTop()), + -(replaced.PaddingRight() + replaced.BorderRight()), + -(replaced.PaddingBottom() + replaced.BorderBottom()), + -(replaced.PaddingLeft() + replaced.BorderLeft()))); state.SetClipRect(clip_rect, clip_rect); if (replaced.IsLayoutEmbeddedContent()) { // Embedded objects are always sized to fit the content rect, but they @@ -1852,9 +1930,9 @@ void FragmentPaintPropertyTreeBuilder::UpdateOverflowClip() { } else if (object_.IsBox()) { PhysicalRect clip_rect; if (pre_paint_info_) { - const NGFragmentChildIterator& iterator = pre_paint_info_->iterator; - clip_rect = iterator->BoxFragment()->OverflowClipRect( - context_.current.paint_offset, iterator->BlockBreakToken()); + clip_rect = pre_paint_info_->box_fragment.OverflowClipRect( + context_.current.paint_offset, + FindPreviousBreakToken(pre_paint_info_->box_fragment)); } else { clip_rect = To<LayoutBox>(object_).OverflowClipRect( context_.current.paint_offset); @@ -1909,7 +1987,7 @@ void FragmentPaintPropertyTreeBuilder::UpdatePerspective() { // most transform nodes do. TransformPaintPropertyNode::State state{ TransformPaintPropertyNode::TransformAndOrigin( - TransformationMatrix().ApplyPerspective(style.Perspective()), + TransformationMatrix().ApplyPerspective(style.UsedPerspective()), PerspectiveOrigin(To<LayoutBox>(object_)) + FloatSize(context_.current.paint_offset))}; state.flags.flattens_inherited_transform = @@ -2117,6 +2195,11 @@ void FragmentPaintPropertyTreeBuilder::UpdateScrollAndScrollTranslation() { FloatPoint scroll_position = FloatPoint(box.ScrollOrigin()) + box.GetScrollableArea()->GetScrollOffset(); TransformPaintPropertyNode::State state{-ToFloatSize(scroll_position)}; + if (!box.GetScrollableArea()->PendingScrollAnchorAdjustment().IsZero()) { + context_.current.pending_scroll_anchor_adjustment += + box.GetScrollableArea()->PendingScrollAnchorAdjustment(); + box.GetScrollableArea()->ClearPendingScrollAnchorAdjustment(); + } state.flags.flattens_inherited_transform = context_.current.should_flatten_inherited_transform; state.rendering_context_id = context_.current.rendering_context_id; @@ -2166,11 +2249,20 @@ void FragmentPaintPropertyTreeBuilder::UpdateScrollAndScrollTranslation() { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && scroll_translation->Translation2D() != old_scroll_offset) { - // Scrolling can change overlap relationship. + // Scrolling can change overlap relationship for sticky positioned or + // elements fixed to an overflow: hidden view that programmatically + // scrolls via script. In this case the fixed transform doesn't have + // enough information to perform the expansion - there is no scroll node + // to describe the bounds of the scrollable content. auto* frame_view = object_.GetFrameView(); - if (frame_view->HasViewportConstrainedObjects()) { - // TODO(crbug.com/1099379): Implement better fixed/sticky overlap - // testing. + if (frame_view->HasStickyViewportConstrainedObject()) { + // TODO(crbug.com/1117658): Implement better sticky overlap testing. + frame_view->SetPaintArtifactCompositorNeedsUpdate(); + } else if (frame_view->HasViewportConstrainedObjects() && + !frame_view->GetLayoutView() + ->FirstFragment() + .PaintProperties() + ->Scroll()) { frame_view->SetPaintArtifactCompositorNeedsUpdate(); } else if (!object_.IsStackingContext() && // TODO(wangxianzhu): for accuracy, this should be something @@ -2422,15 +2514,6 @@ static PhysicalOffset PaintOffsetInPaginationContainer( } void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { - if (IsInNGFragmentTraversal()) { - // Text and non-atomic inlines are special, in that they share one - // FragmentData per fragmentainer, so their paint offset is kept at their - // container. For all other objects, include the offset now. - if (object_.IsBox()) - context_.current.paint_offset += pre_paint_info_->iterator->Link().offset; - return; - } - // Paint offsets for fragmented content are computed from scratch. const auto* enclosing_pagination_layer = full_context_.painting_layer->EnclosingPaginationLayer(); @@ -2485,14 +2568,17 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { return; } - if (object_.IsFloating() && !object_.IsInLayoutNGInlineFormattingContext()) - context_.current.paint_offset = context_.paint_offset_for_float; + if (!pre_paint_info_) { + if (object_.IsFloating() && !object_.IsInLayoutNGInlineFormattingContext()) + context_.current.paint_offset = context_.paint_offset_for_float; - // Multicolumn spanners are painted starting at the multicolumn container (but - // still inherit properties in layout-tree order) so reset the paint offset. - if (object_.IsColumnSpanAll()) { - context_.current.paint_offset = - object_.Container()->FirstFragment().PaintOffset(); + // Multicolumn spanners are painted starting at the multicolumn container + // (but still inherit properties in layout-tree order) so reset the paint + // offset. + if (object_.IsColumnSpanAll()) { + context_.current.paint_offset = + object_.Container()->FirstFragment().PaintOffset(); + } } if (object_.IsBoxModelObject()) { @@ -2505,8 +2591,12 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { box_model_object.OffsetForInFlowPosition(); break; case EPosition::kAbsolute: { - DCHECK_EQ(full_context_.container_for_absolute_position, - box_model_object.Container()); +#if DCHECK_IS_ON() + if (!pre_paint_info_ || !pre_paint_info_->is_inside_orphaned_object) { + DCHECK_EQ(full_context_.container_for_absolute_position, + box_model_object.Container()); + } +#endif if (RuntimeEnabledFeatures::TransformInteropEnabled()) { // FIXME(dbaron): When the TransformInteropEnabled flag is removed // because it's always enabled, we should move these variables from @@ -2517,7 +2607,7 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { context_.absolute_position.rendering_context_id = context_.current.rendering_context_id; } - context_.current = context_.absolute_position; + SwitchToOOFContext(context_.absolute_position); // Absolutely positioned content in an inline should be positioned // relative to the inline. @@ -2534,8 +2624,12 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { case EPosition::kSticky: break; case EPosition::kFixed: { - DCHECK_EQ(full_context_.container_for_fixed_position, - box_model_object.Container()); +#if DCHECK_IS_ON() + if (!pre_paint_info_ || !pre_paint_info_->is_inside_orphaned_object) { + DCHECK_EQ(full_context_.container_for_fixed_position, + box_model_object.Container()); + } +#endif if (RuntimeEnabledFeatures::TransformInteropEnabled()) { // FIXME(dbaron): When the TransformInteropEnabled flag is removed // because it's always enabled, we should move these variables from @@ -2546,7 +2640,7 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { context_.fixed_position.rendering_context_id = context_.current.rendering_context_id; } - context_.current = context_.fixed_position; + SwitchToOOFContext(context_.fixed_position); // Fixed-position elements that are fixed to the viewport have a // transform above the scroll of the LayoutView. Child content is // relative to that transform, and hence the fixed-position element. @@ -2568,21 +2662,32 @@ void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() { } } - if (object_.IsBox()) { - // TODO(pdr): Several calls in this function walk back up the tree to - // calculate containers (e.g., physicalLocation, offsetForInFlowPosition*). - // The containing block and other containers can be stored on - // PaintPropertyTreeBuilderFragmentContext instead of recomputing them. - context_.current.paint_offset += To<LayoutBox>(object_).PhysicalLocation(); + if (const auto* box = DynamicTo<LayoutBox>(&object_)) { + if (pre_paint_info_) { + context_.current.paint_offset += pre_paint_info_->paint_offset; - // This is a weird quirk that table cells paint as children of table rows, - // but their location have the row's location baked-in. - // Similar adjustment is done in LayoutTableCell::offsetFromContainer(). - if (object_.IsTableCellLegacy()) { - LayoutObject* parent_row = object_.Parent(); - DCHECK(parent_row && parent_row->IsTableRow()); - context_.current.paint_offset -= - To<LayoutBox>(parent_row)->PhysicalLocation(); + // Determine whether we're inside block fragmentation or not. OOF + // descendants need special treatment inside block fragmentation. + context_.current.is_in_block_fragmentation = + pre_paint_info_->fragmentainer_idx != WTF::kNotFound && + box->GetNGPaginationBreakability() != LayoutBox::kForbidBreaks; + } else { + // TODO(pdr): Several calls in this function walk back up the tree to + // calculate containers (e.g., physicalLocation, + // offsetForInFlowPosition*). The containing block and other containers + // can be stored on PaintPropertyTreeBuilderFragmentContext instead of + // recomputing them. + context_.current.paint_offset += box->PhysicalLocation(); + + // This is a weird quirk that table cells paint as children of table rows, + // but their location have the row's location baked-in. + // Similar adjustment is done in LayoutTableCell::offsetFromContainer(). + if (object_.IsTableCellLegacy()) { + LayoutObject* parent_row = object_.Parent(); + DCHECK(parent_row && parent_row->IsTableRow()); + context_.current.paint_offset -= + To<LayoutBox>(parent_row)->PhysicalLocation(); + } } } @@ -2742,8 +2847,11 @@ static bool IsLayoutShiftRoot(const LayoutObject& object, void FragmentPaintPropertyTreeBuilder::UpdateForSelf() { #if DCHECK_IS_ON() - FindPaintOffsetNeedingUpdateScope check_paint_offset( - object_, fragment_data_, full_context_.is_actually_needed); + absl::optional<FindPaintOffsetNeedingUpdateScope> check_paint_offset; + if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) { + check_paint_offset.emplace(object_, fragment_data_, + full_context_.is_actually_needed); + } #endif // This is not in FindObjectPropertiesNeedingUpdateScope because paint offset @@ -2757,8 +2865,12 @@ void FragmentPaintPropertyTreeBuilder::UpdateForSelf() { if (properties_) { { #if DCHECK_IS_ON() - FindPropertiesNeedingUpdateScope check_fragment_clip( - object_, fragment_data_, full_context_.force_subtree_update_reasons); + absl::optional<FindPropertiesNeedingUpdateScope> check_fragment_clip; + if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) { + bool force_subtree_update = full_context_.force_subtree_update_reasons; + check_fragment_clip.emplace(object_, fragment_data_, + force_subtree_update); + } #endif UpdateFragmentClip(); } @@ -2768,8 +2880,12 @@ void FragmentPaintPropertyTreeBuilder::UpdateForSelf() { } #if DCHECK_IS_ON() - FindPropertiesNeedingUpdateScope check_paint_properties( - object_, fragment_data_, full_context_.force_subtree_update_reasons); + absl::optional<FindPropertiesNeedingUpdateScope> check_paint_properties; + if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) { + bool force_subtree_update = full_context_.force_subtree_update_reasons; + check_paint_properties.emplace(object_, fragment_data_, + force_subtree_update); + } #endif if (properties_) { @@ -2781,7 +2897,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateForSelf() { UpdateFilter(); UpdateOverflowControlsClip(); } else if (RuntimeEnabledFeatures::TransformInteropEnabled() && - !object_.IsAnonymous()) { + object_.IsForElement()) { // With kTransformInterop enabled, 3D rendering contexts follow the // DOM ancestor chain, so flattening should apply regardless of // presence of transform. @@ -2806,13 +2922,19 @@ void FragmentPaintPropertyTreeBuilder::UpdateForSelf() { void FragmentPaintPropertyTreeBuilder::UpdateForChildren() { #if DCHECK_IS_ON() - // Paint offset should not change during this function. + // Will be used though a reference by check_paint_offset, so it's declared + // here to out-live check_paint_offset. It's false because paint offset + // should not change during this function. const bool needs_paint_offset_update = false; - FindPaintOffsetNeedingUpdateScope check_paint_offset( - object_, fragment_data_, needs_paint_offset_update); - - FindPropertiesNeedingUpdateScope check_paint_properties( - object_, fragment_data_, full_context_.force_subtree_update_reasons); + absl::optional<FindPaintOffsetNeedingUpdateScope> check_paint_offset; + absl::optional<FindPropertiesNeedingUpdateScope> check_paint_properties; + if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) { + check_paint_offset.emplace(object_, fragment_data_, + needs_paint_offset_update); + bool force_subtree_update = full_context_.force_subtree_update_reasons; + check_paint_properties.emplace(object_, fragment_data_, + force_subtree_update); + } #endif if (properties_) { @@ -2838,8 +2960,10 @@ void FragmentPaintPropertyTreeBuilder::UpdateForChildren() { context_.translation_2d_to_layout_shift_root_delta = FloatSize(); // Don't reset scroll_offset_to_layout_shift_root_delta if this object has // scroll translation because we need to propagate the delta to descendants. - if (!properties_ || !properties_->ScrollTranslation()) + if (!properties_ || !properties_->ScrollTranslation()) { context_.current.scroll_offset_to_layout_shift_root_delta = FloatSize(); + context_.current.pending_scroll_anchor_adjustment = FloatSize(); + } } #if DCHECK_IS_ON() @@ -2910,7 +3034,7 @@ void PaintPropertyTreeBuilder::InitFragmentPaintPropertiesForNG( context_.fragments.push_back(PaintPropertyTreeBuilderFragmentContext()); else context_.fragments.resize(1); - InitFragmentPaintProperties(pre_paint_info_->fragment_data, + InitFragmentPaintProperties(*pre_paint_info_->fragment_data, needs_paint_properties, context_.fragments[0]); } @@ -2931,12 +3055,15 @@ void PaintPropertyTreeBuilder::InitSingleFragmentFromParent( // Column-span:all skips pagination container in the tree hierarchy, so it // should also skip any fragment clip created by the skipped pagination - // container. We also need to skip fragment clip if the object is a paint - // invalidation container which doesn't allow fragmentation. - bool skip_fragment_clip_for_composited_layer = - !RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && - object_.CanBeCompositedForDirectReasons() && - To<LayoutBoxModelObject>(object_).Layer()->EnclosingPaginationLayer(); + // container. We also need to skip fragment clip if the layer doesn't allow + // fragmentation. + bool skip_fragment_clip_for_composited_layer = false; + if (object_.HasLayer()) { + const auto* layer = To<LayoutBoxModelObject>(object_).Layer(); + skip_fragment_clip_for_composited_layer = + layer->EnclosingPaginationLayer() && + !layer->ShouldFragmentCompositedBounds(); + } if (!skip_fragment_clip_for_composited_layer && !object_.IsColumnSpanAll()) return; @@ -2979,8 +3106,6 @@ void PaintPropertyTreeBuilder::InitSingleFragmentFromParent( void PaintPropertyTreeBuilder::UpdateCompositedLayerPaginationOffset() { DCHECK(!IsInNGFragmentTraversal()); - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return; const auto* enclosing_pagination_layer = context_.painting_layer->EnclosingPaginationLayer(); @@ -2994,13 +3119,28 @@ void PaintPropertyTreeBuilder::UpdateCompositedLayerPaginationOffset() { DCHECK(!context_.painting_layer->ShouldFragmentCompositedBounds()); FragmentData& first_fragment = object_.GetMutableForPainting().FirstFragment(); - bool can_be_directly_composited = object_.CanBeCompositedForDirectReasons(); - const auto* parent_directly_composited_layer = - context_.painting_layer->EnclosingDirectlyCompositableLayer( - can_be_directly_composited ? kExcludeSelf : kIncludeSelf); - if (can_be_directly_composited && - (!parent_directly_composited_layer || - !parent_directly_composited_layer->EnclosingPaginationLayer())) { + bool may_use_self_pagination_offset = false; + const PaintLayer* parent_pagination_offset_layer = nullptr; + if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { + parent_pagination_offset_layer = + context_.painting_layer + ->EnclosingCompositedScrollingLayerUnderPagination(kIncludeSelf); + if (parent_pagination_offset_layer->GetLayoutObject() == object_) { + may_use_self_pagination_offset = true; + parent_pagination_offset_layer = + parent_pagination_offset_layer + ->EnclosingCompositedScrollingLayerUnderPagination(kExcludeSelf); + } + } else { + may_use_self_pagination_offset = object_.CanBeCompositedForDirectReasons(); + parent_pagination_offset_layer = + context_.painting_layer->EnclosingDirectlyCompositableLayer( + may_use_self_pagination_offset ? kExcludeSelf : kIncludeSelf); + } + + if (may_use_self_pagination_offset && + (!parent_pagination_offset_layer || + !parent_pagination_offset_layer->EnclosingPaginationLayer())) { // |object_| establishes the top level composited layer under the // pagination layer. FragmentainerIterator iterator( @@ -3013,10 +3153,10 @@ void PaintPropertyTreeBuilder::UpdateCompositedLayerPaginationOffset() { first_fragment.SetLogicalTopInFlowThread( iterator.FragmentainerLogicalTopInFlowThread()); } - } else if (parent_directly_composited_layer) { + } else if (parent_pagination_offset_layer) { // All objects under the composited layer use the same pagination offset. const auto& fragment = - parent_directly_composited_layer->GetLayoutObject().FirstFragment(); + parent_pagination_offset_layer->GetLayoutObject().FirstFragment(); first_fragment.SetLegacyPaginationOffset(fragment.LegacyPaginationOffset()); first_fragment.SetLogicalTopInFlowThread(fragment.LogicalTopInFlowThread()); } @@ -3762,7 +3902,7 @@ PaintPropertyChangeType PaintPropertyTreeBuilder::UpdateForSelf() { DCHECK_EQ(context_.fragments.size(), 1u); FragmentPaintPropertyTreeBuilder builder(object_, pre_paint_info_, context_, context_.fragments[0], - pre_paint_info_->fragment_data); + *pre_paint_info_->fragment_data); builder.UpdateForSelf(); property_changed = std::max(property_changed, builder.PropertyChanged()); } else { @@ -3813,7 +3953,7 @@ PaintPropertyChangeType PaintPropertyTreeBuilder::UpdateForChildren() { FragmentData* fragment_data; if (pre_paint_info_) { DCHECK_EQ(context_.fragments.size(), 1u); - fragment_data = &pre_paint_info_->fragment_data; + fragment_data = pre_paint_info_->fragment_data; DCHECK(fragment_data); } else { fragment_data = &object_.GetMutableForPainting().FirstFragment(); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.h index a8d5224beb2..9ef282663dd 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder.h @@ -21,7 +21,7 @@ class FragmentData; class LayoutObject; class LayoutNGTableSectionInterface; class LocalFrameView; -class NGFragmentChildIterator; +class NGPhysicalBoxFragment; class PaintLayer; class VisualViewport; @@ -96,6 +96,8 @@ struct PaintPropertyTreeBuilderFragmentContext { // object has changed. bool layout_shift_root_changed = false; + bool is_in_block_fragmentation = false; + // Rendering context for 3D sorting. See // TransformPaintPropertyNode::renderingContextId. unsigned rendering_context_id = 0; @@ -110,6 +112,8 @@ struct PaintPropertyTreeBuilderFragmentContext { // reference a scroll offset transform, scroll nodes should be updated if // the transform tree changes. const ScrollPaintPropertyNode* scroll = nullptr; + + FloatSize pending_scroll_anchor_adjustment; }; ContainingBlockContext current; @@ -121,8 +125,6 @@ struct PaintPropertyTreeBuilderFragmentContext { // containing block of corresponding positioned descendants. Overflow clips // are also inherited by containing block tree instead of DOM tree, thus they // are included in the additional context too. - // - // Note that these contexts are not used in LayoutNGFragmentTraversal. ContainingBlockContext absolute_position; ContainingBlockContext fixed_position; @@ -154,6 +156,13 @@ struct PaintPropertyTreeBuilderFragmentContext { PhysicalOffset old_paint_offset; + // Paint offset at the current innermost fragmentainer. + PhysicalOffset fragmentainer_paint_offset; + + // Amount of adjustment done by UpdateForPaintOffsetTranslation() since we + // entered the innermost fragmentainer. + PhysicalOffset adjustment_for_oof_in_fragmentainer; + // An additional offset that applies to the current fragment, but is detected // *before* the ContainingBlockContext is updated for it. Once the // ContainingBlockContext is set, this value should be added to @@ -175,7 +184,6 @@ struct PaintPropertyTreeBuilderContext final { Vector<PaintPropertyTreeBuilderFragmentContext, 1> fragments; - // TODO(mstensho): Stop using these in LayoutNGFragmentTraversal. const LayoutObject* container_for_absolute_position = nullptr; const LayoutObject* container_for_fixed_position = nullptr; @@ -252,7 +260,8 @@ class VisualViewportPaintPropertyTreeBuilder { // Update the paint properties for the visual viewport and ensure the context // is up to date. Returns the maximum paint property change type for any of // the viewport nodes. - static PaintPropertyChangeType Update(VisualViewport&, + static PaintPropertyChangeType Update(LocalFrameView& main_frame_view, + VisualViewport&, PaintPropertyTreeBuilderContext&); }; @@ -260,12 +269,41 @@ struct NGPrePaintInfo { STACK_ALLOCATED(); public: - NGPrePaintInfo(const NGFragmentChildIterator& iterator, - FragmentData& fragment_data) - : iterator(iterator), fragment_data(fragment_data) {} - - const NGFragmentChildIterator& iterator; - FragmentData& fragment_data; + NGPrePaintInfo(const NGPhysicalBoxFragment& box_fragment, + PhysicalOffset paint_offset, + wtf_size_t fragmentainer_idx, + bool is_first_for_node, + bool is_last_for_node, + bool is_inside_orphaned_object, + bool is_inside_fragment_child) + : box_fragment(box_fragment), + paint_offset(paint_offset), + fragmentainer_idx(fragmentainer_idx), + is_first_for_node(is_first_for_node), + is_last_for_node(is_last_for_node), + is_inside_orphaned_object(is_inside_orphaned_object), + is_inside_fragment_child(is_inside_fragment_child) {} + + // The fragment for the LayoutObject currently being processed, or, in the + // case of text and non-atomic inlines: the fragment of the containing block. + const NGPhysicalBoxFragment& box_fragment; + + FragmentData* fragment_data = nullptr; + PhysicalOffset paint_offset; + wtf_size_t fragmentainer_idx; + bool is_first_for_node; + bool is_last_for_node; + + // True if we're fragment-traversing an object (OOF or float) directly, + // instead of walking the layout object tree. In this case, the property / + // invalidation context chains will be missing ancestors between the + // fragmentainer and the OOF / float. + bool is_inside_orphaned_object; + + // True if |box_fragment| is the containing block of the LayoutObject + // currently being processed. Otherwise, |box_fragment| is a fragment for the + // LayoutObject itself. + bool is_inside_fragment_child; }; // Creates paint property tree nodes for non-local effects in the layout tree. @@ -295,6 +333,9 @@ class PaintPropertyTreeBuilder { // Returns whether any paint property of the object has changed. PaintPropertyChangeType UpdateForChildren(); + static bool NeedsTransform(const LayoutObject& object, + CompositingReasons direct_compositing_reasons); + private: ALWAYS_INLINE void InitFragmentPaintProperties( FragmentData&, diff --git a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc index f8028de1716..0c159000b5b 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc @@ -125,7 +125,13 @@ void PaintPropertyTreeBuilderTest::SetUp() { #define CHECK_EXACT_VISUAL_RECT(expected, source_object, ancestor) \ CHECK_VISUAL_RECT(expected, source_object, ancestor, 0) -INSTANTIATE_PAINT_TEST_SUITE_P(PaintPropertyTreeBuilderTest); +INSTANTIATE_TEST_SUITE_P(All, + PaintPropertyTreeBuilderTest, + ::testing::Values(0, + kCompositeAfterPaint, + kUnderInvalidationChecking, + kCompositeAfterPaint | + kUnderInvalidationChecking)); TEST_P(PaintPropertyTreeBuilderTest, FixedPosition) { LoadTestData("fixed-position.html"); @@ -833,8 +839,9 @@ TEST_P(PaintPropertyTreeBuilderTest, WillChangeContents) { TEST_P(PaintPropertyTreeBuilderTest, BackfaceVisibilityWithPseudoStacking3DChildren) { - ScopedTransformInteropForTest enabled(true); - // TODO(chrishtr): implement for CAP. This entails computing + ScopedTransformInteropForTest ti_enabled(true); + ScopedBackfaceVisibilityInteropForTest bfi_enabled(true); + // TODO(chrishtr, dbaron): implement for CAP. This entails computing // has_backface_invisible_ancestor_in_same_3d_context in the pre-paint tree // walk. if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) @@ -4318,6 +4325,12 @@ TEST_P(PaintPropertyTreeBuilderTest, SpanFragmentsLimitedToSize) { TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetUnderMulticolumnScrollFixedPos) { + // Raster under-invalidation will fail to allocate bitmap when checking a huge + // layer created without LayoutNGBlockFragmentation. + if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() && + !RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) + return; + SetBodyInnerHTML(R"HTML( <div id=fixed style='position: fixed; columns: 2'> <div style='width: 50px; height: 20px; background: lightblue'></div> diff --git a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_printer.cc b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_printer.cc index f3b66da3b0a..0570c2d62e1 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_printer.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_printer.cc @@ -113,7 +113,9 @@ class PropertyTreePrinterTraits<EffectPaintPropertyNodeOrAlias> { public: static void AddVisualViewportProperties( const VisualViewport& visual_viewport, - PropertyTreePrinter<EffectPaintPropertyNodeOrAlias>& printer) {} + PropertyTreePrinter<EffectPaintPropertyNodeOrAlias>& printer) { + printer.AddNode(visual_viewport.GetOverscrollElasticityEffectNode()); + } static void AddObjectPaintProperties( const ObjectPaintProperties& properties, @@ -165,6 +167,10 @@ namespace paint_property_tree_printer { void UpdateDebugNames(const VisualViewport& viewport) { if (auto* device_emulation_node = viewport.GetDeviceEmulationTransformNode()) device_emulation_node->SetDebugName("Device Emulation Node"); + if (auto* overscroll_effect_node = + viewport.GetOverscrollElasticityEffectNode()) { + overscroll_effect_node->SetDebugName("Overscroll Elasticity Effect Node"); + } if (auto* overscroll_node = viewport.GetOverscrollElasticityTransformNode()) overscroll_node->SetDebugName("Overscroll Elasticity Node"); viewport.GetPageScaleNode()->SetDebugName("VisualViewport Scale Node"); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc index 279953b0614..b989f2f8287 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc @@ -18,7 +18,13 @@ namespace blink { // Tests covering incremental updates of paint property trees. class PaintPropertyTreeUpdateTest : public PaintPropertyTreeBuilderTest {}; -INSTANTIATE_PAINT_TEST_SUITE_P(PaintPropertyTreeUpdateTest); +INSTANTIATE_TEST_SUITE_P(All, + PaintPropertyTreeUpdateTest, + ::testing::Values(0, + kCompositeAfterPaint, + kUnderInvalidationChecking, + kCompositeAfterPaint | + kUnderInvalidationChecking)); TEST_P(PaintPropertyTreeUpdateTest, ThreadedScrollingDisabledMainThreadScrollReason) { @@ -826,7 +832,7 @@ TEST_P(PaintPropertyTreeUpdateTest, SetBodyInnerHTML(R"HTML( <style> ::-webkit-scrollbar {width: 20px; height: 20px} - body {height: 10000px; width: 10000px; margin: 0;} + body {height: 2000px; width: 2000px; margin: 0;} </style> )HTML"); @@ -840,7 +846,7 @@ TEST_P(PaintPropertyTreeUpdateTest, TEST_P(PaintPropertyTreeUpdateTest, ViewportAddRemoveDeviceEmulationNode) { SetBodyInnerHTML( - "<style>body {height: 10000px; width: 10000px; margin: 0;}</style>"); + "<style>body {height: 2000px; width: 2000px; margin: 0;}</style>"); auto& visual_viewport = GetDocument().GetPage()->GetVisualViewport(); EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode()); @@ -858,8 +864,10 @@ TEST_P(PaintPropertyTreeUpdateTest, ViewportAddRemoveDeviceEmulationNode) { EXPECT_EQ(&TransformPaintPropertyNode::Root(), &scrollbar_layer->GetPropertyTreeState().Transform()); } else { - // TODO(wangxianzhu): Test for CompositeAfterPaint. - EXPECT_FALSE(scrollbar_layer); + auto& chunk = *(ContentPaintChunks().begin() + 1); + EXPECT_EQ(DisplayItem::kScrollbarHorizontal, chunk.id.type); + EXPECT_EQ(&TransformPaintPropertyNode::Root(), + &chunk.properties.Transform()); } // These emulate WebViewImpl::SetDeviceEmulationTransform(). @@ -874,8 +882,10 @@ TEST_P(PaintPropertyTreeUpdateTest, ViewportAddRemoveDeviceEmulationNode) { EXPECT_EQ(visual_viewport.GetDeviceEmulationTransformNode(), &scrollbar_layer->GetPropertyTreeState().Transform()); } else { - // TODO(wangxianzhu): Test for CompositeAfterPaint. - EXPECT_FALSE(scrollbar_layer); + auto& chunk = *(ContentPaintChunks().begin() + 1); + EXPECT_EQ(DisplayItem::kScrollbarHorizontal, chunk.id.type); + EXPECT_EQ(visual_viewport.GetDeviceEmulationTransformNode(), + &chunk.properties.Transform()); } // These emulate WebViewImpl::SetDeviceEmulationTransform(). @@ -889,8 +899,10 @@ TEST_P(PaintPropertyTreeUpdateTest, ViewportAddRemoveDeviceEmulationNode) { EXPECT_EQ(&TransformPaintPropertyNode::Root(), &scrollbar_layer->GetPropertyTreeState().Transform()); } else { - // TODO(wangxianzhu): Test for CompositeAfterPaint. - EXPECT_FALSE(scrollbar_layer); + auto& chunk = *(ContentPaintChunks().begin() + 1); + EXPECT_EQ(DisplayItem::kScrollbarHorizontal, chunk.id.type); + EXPECT_EQ(&TransformPaintPropertyNode::Root(), + &chunk.properties.Transform()); } } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_timing.cc b/chromium/third_party/blink/renderer/core/paint/paint_timing.cc index 5b92ab258b1..44da167084b 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_timing.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_timing.cc @@ -23,6 +23,7 @@ #include "third_party/blink/renderer/core/probe/core_probes.h" #include "third_party/blink/renderer/core/timing/dom_window_performance.h" #include "third_party/blink/renderer/core/timing/window_performance.h" +#include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h" #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" @@ -49,7 +50,7 @@ class RecodingTimeAfterBackForwardCacheRestoreFrameCallback public: RecodingTimeAfterBackForwardCacheRestoreFrameCallback( PaintTiming* paint_timing, - size_t record_index) + wtf_size_t record_index) : paint_timing_(paint_timing), record_index_(record_index) {} ~RecodingTimeAfterBackForwardCacheRestoreFrameCallback() override = default; @@ -81,7 +82,7 @@ class RecodingTimeAfterBackForwardCacheRestoreFrameCallback private: Member<PaintTiming> paint_timing_; - const size_t record_index_; + const wtf_size_t record_index_; size_t count_ = 0; }; @@ -104,6 +105,7 @@ void PaintTiming::MarkFirstPaint() { // markFirstPaint(). if (!first_paint_.is_null()) return; + DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0); SetFirstPaint(clock_->NowTicks()); } @@ -114,12 +116,15 @@ void PaintTiming::MarkFirstContentfulPaint() { // markFirstContentfulPaint(). if (!first_contentful_paint_.is_null()) return; + if (IgnorePaintTimingScope::IgnoreDepth() > 0) + return; SetFirstContentfulPaint(clock_->NowTicks()); } void PaintTiming::MarkFirstImagePaint() { if (!first_image_paint_.is_null()) return; + DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0); first_image_paint_ = clock_->NowTicks(); SetFirstContentfulPaint(first_image_paint_); RegisterNotifyPresentationTime(PaintEvent::kFirstImagePaint); @@ -176,6 +181,8 @@ void PaintTiming::SetFirstMeaningfulPaint( void PaintTiming::NotifyPaint(bool is_first_paint, bool text_painted, bool image_painted) { + if (IgnorePaintTimingScope::IgnoreDepth() > 0) + return; if (is_first_paint) MarkFirstPaint(); if (text_painted) @@ -223,13 +230,7 @@ void PaintTiming::SetFirstPaint(base::TimeTicks stamp) { if (!first_paint_.is_null()) return; - LocalFrame* frame = GetFrame(); - if (frame && frame->GetDocument()) { - Document* document = frame->GetDocument(); - document->MarkFirstPaint(); - if (frame->IsMainFrame()) - document->Fetcher()->MarkFirstPaint(); - } + DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0); first_paint_ = stamp; RegisterNotifyPresentationTime(PaintEvent::kFirstPaint); @@ -238,6 +239,7 @@ void PaintTiming::SetFirstPaint(base::TimeTicks stamp) { void PaintTiming::SetFirstContentfulPaint(base::TimeTicks stamp) { if (!first_contentful_paint_.is_null()) return; + DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0); SetFirstPaint(stamp); first_contentful_paint_ = stamp; RegisterNotifyPresentationTime(PaintEvent::kFirstContentfulPaint); @@ -248,9 +250,6 @@ void PaintTiming::SetFirstContentfulPaint(base::TimeTicks stamp) { return; frame->View()->OnFirstContentfulPaint(); - if (frame->GetDocument() && frame->GetDocument()->Fetcher()) - frame->GetDocument()->Fetcher()->MarkFirstContentfulPaint(); - if (frame->GetFrameScheduler()) frame->GetFrameScheduler()->OnFirstContentfulPaintInMainFrame(); @@ -266,7 +265,7 @@ void PaintTiming::RegisterNotifyPresentationTime(PaintEvent event) { void PaintTiming:: RegisterNotifyFirstPaintAfterBackForwardCacheRestorePresentationTime( - size_t index) { + wtf_size_t index) { RegisterNotifyPresentationTime(CrossThreadBindOnce( &PaintTiming:: ReportFirstPaintAfterBackForwardCacheRestorePresentationTime, @@ -299,7 +298,6 @@ void PaintTiming::ReportPresentationTime(PaintEvent event, // // TODO(crbug.com/738235): Consider not reporting any timestamp when failing // for reasons other than kDidNotSwapSwapFails. - ReportSwapResultHistogram(result); switch (event) { case PaintEvent::kFirstPaint: SetFirstPaintPresentation(timestamp); @@ -319,11 +317,10 @@ void PaintTiming::ReportPresentationTime(PaintEvent event, } void PaintTiming::ReportFirstPaintAfterBackForwardCacheRestorePresentationTime( - size_t index, + wtf_size_t index, WebSwapResult result, base::TimeTicks timestamp) { DCHECK(IsMainThread()); - ReportSwapResultHistogram(result); SetFirstPaintAfterBackForwardCacheRestorePresentation(timestamp, index); } @@ -382,7 +379,7 @@ void PaintTiming::SetFirstImagePaintPresentation(base::TimeTicks stamp) { void PaintTiming::SetFirstPaintAfterBackForwardCacheRestorePresentation( base::TimeTicks stamp, - size_t index) { + wtf_size_t index) { // The elements are allocated when the page is restored from the cache. DCHECK_GE(first_paints_after_back_forward_cache_restore_presentation_.size(), index); @@ -393,7 +390,7 @@ void PaintTiming::SetFirstPaintAfterBackForwardCacheRestorePresentation( } void PaintTiming::SetRequestAnimationFrameAfterBackForwardCacheRestore( - size_t index, + wtf_size_t index, size_t count) { auto now = clock_->NowTicks(); @@ -407,15 +404,10 @@ void PaintTiming::SetRequestAnimationFrameAfterBackForwardCacheRestore( current_rafs[count] = now; } -void PaintTiming::ReportSwapResultHistogram(WebSwapResult result) { - UMA_HISTOGRAM_ENUMERATION("PageLoad.Internal.Renderer.PaintTiming.SwapResult", - result); -} - void PaintTiming::OnRestoredFromBackForwardCache() { // Allocate the last element with 0, which indicates that the first paint // after this navigation doesn't happen yet. - size_t index = + wtf_size_t index = first_paints_after_back_forward_cache_restore_presentation_.size(); DCHECK_EQ(index, request_animation_frames_after_back_forward_cache_restore_.size()); diff --git a/chromium/third_party/blink/renderer/core/paint/paint_timing.h b/chromium/third_party/blink/renderer/core/paint/paint_timing.h index d0dbdf06ace..7a44f1d77ca 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_timing.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_timing.h @@ -146,12 +146,10 @@ class CORE_EXPORT PaintTiming final : public GarbageCollected<PaintTiming>, WebSwapResult, base::TimeTicks timestamp); void ReportFirstPaintAfterBackForwardCacheRestorePresentationTime( - size_t index, + wtf_size_t index, WebSwapResult, base::TimeTicks timestamp); - void ReportSwapResultHistogram(WebSwapResult); - // The caller owns the |clock| which must outlive the PaintTiming. void SetTickClockForTesting(const base::TickClock* clock); @@ -191,13 +189,13 @@ class CORE_EXPORT PaintTiming final : public GarbageCollected<PaintTiming>, // index to avoid confusing the data from different navigations. void SetFirstPaintAfterBackForwardCacheRestorePresentation( base::TimeTicks stamp, - size_t index); - void SetRequestAnimationFrameAfterBackForwardCacheRestore(size_t index, + wtf_size_t index); + void SetRequestAnimationFrameAfterBackForwardCacheRestore(wtf_size_t index, size_t count); void RegisterNotifyPresentationTime(PaintEvent); void RegisterNotifyFirstPaintAfterBackForwardCacheRestorePresentationTime( - size_t index); + wtf_size_t index); base::TimeTicks FirstPaintRendered() const { return first_paint_; } diff --git a/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.cc b/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.cc index 85e53c825b4..9a795855075 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.cc +++ b/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.cc @@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_view.h" diff --git a/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.h b/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.h index 0c8698caf2d..9b8bd4c7c66 100644 --- a/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.h +++ b/chromium/third_party/blink/renderer/core/paint/paint_timing_detector.h @@ -13,6 +13,7 @@ #include "third_party/blink/renderer/core/scroll/scroll_types.h" #include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h" #include "third_party/blink/renderer/platform/heap/member.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc index 08626f312fb..9806adced82 100644 --- a/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc +++ b/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h" #include "base/auto_reset.h" +#include "base/stl_util.h" #include "cc/base/features.h" #include "third_party/blink/renderer/core/dom/document_lifecycle.h" #include "third_party/blink/renderer/core/frame/event_handler_registry.h" @@ -15,20 +16,19 @@ #include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/layout/layout_box_model_object.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" -#include "third_party/blink/renderer/core/layout/layout_fieldset.h" -#include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h" +#include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h" #include "third_party/blink/renderer/core/layout/layout_shift_tracker.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" +#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" -#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h" #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/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/cull_rect_updater.h" +#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" @@ -36,114 +36,6 @@ namespace blink { -namespace { - -// Locate or/and set up the current FragmentData object. This may involve -// creating it, or resetting an existing one. If |allow_reset| is set, we're -// allowed to clear old FragmentData objects. -NGPrePaintInfo SetupFragmentData(const NGFragmentChildIterator& iterator, - bool allow_reset) { - // TODO(crbug.com/1043787): What's here is mostly gross, and we need to come - // up with something better. The way FragmentData works (and is stored) - // vs. the way NGPhysicalFragment works is less than ideal. - // - // There's essentially a 1:1 correspondence between a block-level - // NGPhysicalBoxFragment and FragmentData, but there's no direct link between - // them, so we have to do some work. In the future we might want to make - // FragmentData part of NGPhysicalBoxFragment objects, to simplify this, and - // to get rid of O(n^2) performance complexity (where n is the number of - // fragments generated by a node). Note that this performance complexity also - // exists in the legacy engine. - // - // For inline-level nodes, it gets a bit more complicated. There's one - // FragmentData object per fragmentainer said node occurs in. The offset and - // invalidation rectangle in each FragmentData will be a union of all the - // fragments generated by the node (one per line box, typically) in that - // fragmentainer. This also matches how we do it in legacy layout. It's - // considered too expensive to have one FragmentData object per line for each - // text node or non-atomic inline. - DCHECK(iterator->GetLayoutObject()); - const LayoutObject& object = *iterator->GetLayoutObject(); - FragmentData* fragment_data = &object.GetMutableForPainting().FirstFragment(); - const auto* incoming_break_token = iterator->BlockBreakToken(); - const NGPhysicalBoxFragment* box_fragment = iterator->BoxFragment(); - - // The need for paint properties is the same across all fragments, so if the - // first FragmentData needs it, so do all the others. - bool needs_paint_properties = fragment_data->PaintProperties(); - - if (const NGFragmentItem* fragment_item = iterator->FragmentItem()) { - // We're in an inline formatting context. The consumed block-size stored in - // the incoming break token will be stored in FragmentData objects to - // identify each portion for a given fragmentainer. - LayoutUnit consumed_block_size; - if (incoming_break_token) - consumed_block_size = incoming_break_token->ConsumedBlockSize(); - if (fragment_item->IsFirstForNode()) { - // This is the first fragment generated for the node (i.e. we're on the - // first line and first fragmentainer (column) that this node occurs - // in). Now is our chance to reset everything (the number or size of - // fragments may have changed since last time). All the other fragments - // will be visited in due course. - if (allow_reset && !object.IsBox()) { - // For text and non-atomic inlines, we now remove additional - // FragmentData objects, and reset the visual rect. The visual rect will - // be set and expanded, as we visit each individual fragment. - fragment_data->ClearNextFragment(); - } - fragment_data->SetLogicalTopInFlowThread(consumed_block_size); - } else { - // This is not the first fragment. Now see if we can find a FragmentData - // with the right consumed block-size (or flow thread logical top). If - // not, we'll have to create one now. - while (consumed_block_size > fragment_data->LogicalTopInFlowThread()) { - FragmentData* next_fragment_data = fragment_data->NextFragment(); - if (!next_fragment_data) { - fragment_data = &fragment_data->EnsureNextFragment(); - fragment_data->SetLogicalTopInFlowThread(consumed_block_size); - break; - } - fragment_data = next_fragment_data; - } - DCHECK_EQ(fragment_data->LogicalTopInFlowThread(), consumed_block_size); - } - } else { - // The fragment is block-level. - if (IsResumingLayout(incoming_break_token)) { - // This isn't the first fragment for the node. We now need to walk past - // all preceding fragments to figure out which FragmentData to return (or - // create, if it doesn't already exist). - const auto& layout_box = To<LayoutBox>(object); - for (wtf_size_t idx = 0;; idx++) { - DCHECK_LT(idx, layout_box.PhysicalFragmentCount()); - if (layout_box.GetPhysicalFragment(idx) == box_fragment) - break; - FragmentData* next = fragment_data->NextFragment(); - if (!next) { - DCHECK_EQ(layout_box.GetPhysicalFragment(idx + 1), box_fragment); - fragment_data = &fragment_data->EnsureNextFragment(); - break; - } - fragment_data = next; - } - fragment_data->SetLogicalTopInFlowThread( - incoming_break_token->ConsumedBlockSize()); - } - if (!box_fragment->BreakToken()) { - // We have reached the end. There may be more data entries that were - // needed in the previous layout, but not any more. Clear them. - fragment_data->ClearNextFragment(); - } - } - - if (needs_paint_properties) - fragment_data->EnsurePaintProperties(); - - return NGPrePaintInfo(iterator, *fragment_data); -} - -} // anonymous namespace - static void SetNeedsCompositingLayerPropertyUpdate(const LayoutObject& object) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; @@ -192,7 +84,7 @@ void PrePaintTreeWalk::WalkTree(LocalFrameView& root_frame_view) { if (root_frame_view.GetFrame().IsMainFrame()) { auto property_changed = VisualViewportPaintPropertyTreeBuilder::Update( - root_frame_view.GetPage()->GetVisualViewport(), + root_frame_view, root_frame_view.GetPage()->GetVisualViewport(), *context.tree_builder_context); if (property_changed > @@ -206,14 +98,10 @@ void PrePaintTreeWalk::WalkTree(LocalFrameView& root_frame_view) { Walk(root_frame_view, context); paint_invalidator_.ProcessPendingDelayedPaintInvalidations(); - if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { - if (auto* layout_view = root_frame_view.GetLayoutView()) - CullRectUpdater(*layout_view->Layer()).Update(); - } - #if DCHECK_IS_ON() if (needs_tree_builder_context_update) { - if (VLOG_IS_ON(2) && root_frame_view.GetLayoutView()) { + if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() && VLOG_IS_ON(2) && + root_frame_view.GetLayoutView()) { VLOG(2) << "PrePaintTreeWalk::Walk(root_frame_view=" << &root_frame_view << ")\nPaintLayer tree:"; showLayerTree(root_frame_view.GetLayoutView()->Layer()); @@ -257,6 +145,12 @@ void PrePaintTreeWalk::Walk(LocalFrameView& frame_view, PrePaintTreeWalkContext context(parent_context, needs_tree_builder_context_update); + // Block fragmentation doesn't cross frame boundaries. + context.current_fragmentainer = {}; + context.absolute_positioned_container = {}; + context.fixed_positioned_container = {}; + context.oof_container_candidate_fragment = nullptr; + // ancestor_scroll_container_paint_layer does not cross frame boundaries. context.ancestor_scroll_container_paint_layer = nullptr; if (context.tree_builder_context) { @@ -273,14 +167,14 @@ void PrePaintTreeWalk::Walk(LocalFrameView& frame_view, << ")\nLayout tree:"; showLayoutTree(view); VLOG(3) << "Fragment tree:"; - NGPhysicalFragment::ShowFragmentTree(*view); + ShowFragmentTree(*view); } #endif is_wheel_event_regions_enabled_ = base::FeatureList::IsEnabled(::features::kWheelEventRegions); - Walk(*view, context, /* iterator */ nullptr); + Walk(*view, context, /* pre_paint_info */ nullptr); #if DCHECK_IS_ON() view->AssertSubtreeClearedPaintInvalidationFlags(); #endif @@ -428,8 +322,14 @@ bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate( } return frame_view.GetLayoutView() && - (ObjectRequiresTreeBuilderContext(*frame_view.GetLayoutView()) || - ContextRequiresChildTreeBuilderContext(context)); + NeedsTreeBuilderContextUpdate(*frame_view.GetLayoutView(), context); +} + +bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate( + const LayoutObject& object, + const PrePaintTreeWalkContext& parent_context) { + return ContextRequiresChildTreeBuilderContext(parent_context) || + ObjectRequiresTreeBuilderContext(object); } bool PrePaintTreeWalk::ObjectRequiresPrePaint(const LayoutObject& object) { @@ -548,18 +448,108 @@ void PrePaintTreeWalk::UpdatePaintInvalidationContainer( } } +NGPrePaintInfo PrePaintTreeWalk::CreatePrePaintInfo( + const NGLink& child, + const PrePaintTreeWalkContext& context) { + const auto& fragment = *To<NGPhysicalBoxFragment>(child.fragment); + return NGPrePaintInfo(fragment, child.offset, + context.current_fragmentainer.fragmentainer_idx, + fragment.IsFirstForNode(), !fragment.BreakToken(), + context.is_inside_orphaned_object, + /* is_inside_fragment_child */ false); +} + +FragmentData* PrePaintTreeWalk::GetOrCreateFragmentData( + const LayoutObject& object, + const PrePaintTreeWalkContext& context, + const NGPrePaintInfo& pre_paint_info) { + // If |allow_update| is set, we're allowed to add, remove and modify + // FragmentData objects. Otherwise they will be left alone. + bool allow_update = context.NeedsTreeBuilderContext(); + + FragmentData* fragment_data = &object.GetMutableForPainting().FirstFragment(); + + // The need for paint properties is the same across all fragments, so if the + // first FragmentData needs it, so do all the others. + bool needs_paint_properties = fragment_data->PaintProperties(); + + wtf_size_t fragment_id = pre_paint_info.fragmentainer_idx; + // TODO(mstensho): For now we need to treat unfragmented as ID 0. It doesn't + // really matter for LayoutNG, but legacy + // PaintPropertyTreeBuilder::ContextForFragment() may take a walk up the tree + // and end up querying this (LayoutNG) object, and + // FragmentData::LogicalTopInFlowThread() will DCHECK that the value is 0 + // unless it has been explicitly set by legacy code (which won't happen, since + // it's an NG object). + if (fragment_id == WTF::kNotFound) + fragment_id = 0; + + if (pre_paint_info.is_first_for_node) { + if (allow_update) + fragment_data->ClearNextFragment(); + else + DCHECK_EQ(fragment_data->FragmentID(), fragment_id); + } else { + FragmentData* last_fragment = nullptr; + do { + if (fragment_data->FragmentID() >= fragment_id) + break; + last_fragment = fragment_data; + fragment_data = fragment_data->NextFragment(); + } while (fragment_data); + if (fragment_data) { + if (pre_paint_info.is_last_for_node) { + // We have reached the end. There may be more data entries that were + // needed in the previous layout, but not any more. Clear them. + if (allow_update) + fragment_data->ClearNextFragment(); + else + DCHECK(!fragment_data->NextFragment()); + } else if (fragment_data->FragmentID() != fragment_id) { + // There are entries for fragmentainers after this one, but none for + // this one. Remove the fragment tail. + DCHECK(allow_update); + DCHECK_GT(fragment_data->FragmentID(), fragment_id); + fragment_data->ClearNextFragment(); + } + } else { + DCHECK(allow_update); + fragment_data = &last_fragment->EnsureNextFragment(); + } + } + + if (allow_update) { + fragment_data->SetFragmentID(fragment_id); + + if (needs_paint_properties) + fragment_data->EnsurePaintProperties(); + } else { + DCHECK_EQ(fragment_data->FragmentID(), fragment_id); + DCHECK(!needs_paint_properties || fragment_data->PaintProperties()); + } + + return fragment_data; +} + void PrePaintTreeWalk::WalkInternal(const LayoutObject& object, PrePaintTreeWalkContext& context, - const NGFragmentChildIterator* iterator) { + NGPrePaintInfo* pre_paint_info) { PaintInvalidatorContext& paint_invalidator_context = context.paint_invalidator_context; - absl::optional<NGPrePaintInfo> pre_paint_info_storage; - NGPrePaintInfo* pre_paint_info = nullptr; - if (iterator) { - bool allow_reset = context.NeedsTreeBuilderContext(); - pre_paint_info_storage.emplace(SetupFragmentData(*iterator, allow_reset)); - pre_paint_info = &pre_paint_info_storage.value(); + if (pre_paint_info) { + DCHECK(!pre_paint_info->fragment_data); + // Find, update or create a FragmentData object to match the current block + // fragment. + // + // TODO(mstensho): If this is collapsed text or a culled inline, we might + // not have any work to do (we could just return early here), as there'll be + // no need for paint property updates or invalidation. However, this is a + // bit tricky to determine, because of things like LinkHighlight, which + // might set paint properties on a culled inline. + pre_paint_info->fragment_data = + GetOrCreateFragmentData(object, context, *pre_paint_info); + DCHECK(pre_paint_info->fragment_data); } // This must happen before updatePropertiesForSelf, because the latter reads @@ -677,6 +667,70 @@ void PrePaintTreeWalk::WalkInternal(const LayoutObject& object, } } +bool PrePaintTreeWalk::CollectMissableChildren( + PrePaintTreeWalkContext& context, + const NGPhysicalBoxFragment& parent) { + bool has_missable_children = false; + for (const NGLink& child : parent.Children()) { + if ((child->IsOutOfFlowPositioned() && + (context.current_fragmentainer.fragment || + child->IsFixedPositioned())) || + (child->IsFloating() && parent.IsInlineFormattingContext() && + context.current_fragmentainer.fragment)) { + // We'll add resumed floats (or floats that couldn't fit a fragment in the + // fragmentainer where it was discovered) that have escaped their inline + // formatting context. + // + // We'll also add all out-of-flow positioned fragments inside a + // fragmentation context. If a fragment is fixed-positioned, we even need + // to add those that aren't inside a fragmentation context, because they + // may have an ancestor LayoutObject inside one, and one of those + // ancestors may be out-of-flow positioned, which may be missed, in which + // case we'll miss this fixed-positioned one as well (since we don't enter + // descendant OOFs when walking missed children) (example: fixedpos inside + // missed abspos in relpos in multicol). + pending_missables_.insert(child.fragment); + has_missable_children = true; + } + } + return has_missable_children; +} + +void PrePaintTreeWalk::WalkMissedChildren(const NGPhysicalBoxFragment& fragment, + PrePaintTreeWalkContext& context) { + if (pending_missables_.IsEmpty()) + return; + + for (const NGLink& child : fragment.Children()) { + if (!child->IsOutOfFlowPositioned() && !child->IsFloating()) + continue; + if (!pending_missables_.Contains(child.fragment)) + continue; + const LayoutObject& descendant_object = *child->GetLayoutObject(); + PrePaintTreeWalkContext descendant_context( + context, NeedsTreeBuilderContextUpdate(descendant_object, context)); + if (child->IsOutOfFlowPositioned() && + descendant_context.tree_builder_context) { + PaintPropertyTreeBuilderFragmentContext& fragment_context = + descendant_context.tree_builder_context->fragments[0]; + // Reset the relevant OOF context to this fragmentainer, since this is its + // containing block, as far as the NG fragment structure is concerned. + // Note that when walking a missed child OOF fragment, we'll also + // forcefully miss any OOF descendant nodes, which is why we only set the + // context for the OOF type we're dealing with here. + if (child->IsFixedPositioned()) + fragment_context.fixed_position = fragment_context.current; + else + fragment_context.absolute_position = fragment_context.current; + } + descendant_context.is_inside_orphaned_object = true; + + NGPrePaintInfo pre_paint_info = + CreatePrePaintInfo(child, descendant_context); + Walk(descendant_object, descendant_context, &pre_paint_info); + } +} + LocalFrameView* FindWebViewPluginContentFrameView( const LayoutEmbeddedContent& embedded_content) { for (Frame* frame = embedded_content.GetFrame()->Tree().FirstChild(); frame; @@ -688,248 +742,454 @@ LocalFrameView* FindWebViewPluginContentFrameView( return nullptr; } -void PrePaintTreeWalk::WalkNGChildren(const LayoutObject* parent, - PrePaintTreeWalkContext& parent_context, - NGFragmentChildIterator* iterator) { +void PrePaintTreeWalk::WalkFragmentationContextRootChildren( + const LayoutObject& object, + const NGPhysicalBoxFragment& fragment, + PrePaintTreeWalkContext& context) { + // The actual children are inside the flow thread child of |object|. + const auto* flow_thread = + To<LayoutBlockFlow>(&object)->MultiColumnFlowThread(); + const LayoutObject& actual_parent = flow_thread ? *flow_thread : object; + FragmentData* fragmentainer_fragment_data = nullptr; #if DCHECK_IS_ON() const LayoutObject* fragmentainer_owner_box = nullptr; #endif - for (; !iterator->IsAtEnd(); iterator->Advance()) { - const LayoutObject* object = (*iterator)->GetLayoutObject(); - if (const auto* fragment_item = (*iterator)->FragmentItem()) { - // Line boxes are not interesting. They have no paint effects. Descend - // directly into children. - if (fragment_item->Type() == NGFragmentItem::kLine) { - WalkChildren(/* parent */ nullptr, parent_context, iterator); - continue; - } - } else if (!object) { - const NGPhysicalBoxFragment* box_fragment = (*iterator)->BoxFragment(); - if (UNLIKELY(box_fragment->IsLayoutObjectDestroyedOrMoved())) - continue; - // Check |box_fragment| and the |LayoutBox| that produced it are in sync. - // |OwnerLayoutBox()| has a few DCHECKs for this purpose. - DCHECK(box_fragment->OwnerLayoutBox()); + DCHECK(fragment.IsFragmentationContextRoot()); - // A fragmentainer doesn't paint anything itself. Just include its offset - // and descend into children. - DCHECK((*iterator)->BoxFragment()->IsFragmentainerBox()); - if (UNLIKELY(!parent_context.tree_builder_context)) { - WalkChildren(/* parent */ nullptr, parent_context, iterator); + const auto outer_fragmentainer = context.current_fragmentainer; + absl::optional<wtf_size_t> inner_fragmentainer_idx; + + for (NGLink child : fragment.Children()) { + const auto* box_fragment = To<NGPhysicalBoxFragment>(child.fragment); + if (UNLIKELY(box_fragment->IsLayoutObjectDestroyedOrMoved())) + continue; + + if (box_fragment->GetLayoutObject()) { + // OOFs contained by a multicol container will be visited during object + // tree traversal. + if (box_fragment->IsOutOfFlowPositioned()) continue; - } - PaintPropertyTreeBuilderContext& tree_builder_context = - *parent_context.tree_builder_context; - PaintPropertyTreeBuilderFragmentContext& context = - tree_builder_context.fragments[0]; - PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext* - containing_block_context = &context.current; - const PhysicalOffset offset = (*iterator)->Link().offset; - containing_block_context->paint_offset += offset; - const PhysicalOffset paint_offset = - containing_block_context->paint_offset; - - // Create corresponding |FragmentData|. Hit-testing needs - // |FragmentData.PaintOffset|. - if (fragmentainer_fragment_data) { - DCHECK(!box_fragment->IsFirstForNode()); + // We'll walk all other non-fragmentainer children directly now, entering + // them from the fragment tree, rather than from the LayoutObject tree. + // One consequence of this is that paint effects on any ancestors between + // a column spanner and its multicol container will not be applied on the + // spanner. This is fixable, but it would require non-trivial amounts of + // special-code for such a special case. If anyone complains, we can + // revisit this decision. + if (box_fragment->IsColumnSpanAll()) + context.current_fragmentainer = outer_fragmentainer; + + NGPrePaintInfo pre_paint_info = CreatePrePaintInfo(child, context); + Walk(*box_fragment->GetLayoutObject(), context, &pre_paint_info); + continue; + } + + // Check |box_fragment| and the |LayoutBox| that produced it are in sync. + // |OwnerLayoutBox()| has a few DCHECKs for this purpose. + DCHECK(box_fragment->OwnerLayoutBox()); + + // A fragmentainer doesn't paint anything itself. Just include its offset + // and descend into children. + DCHECK(box_fragment->IsFragmentainerBox()); + + // Always keep track of the current innermost fragmentainer we're handling, + // as they may serve as containing blocks for OOF descendants. + context.current_fragmentainer.fragment = box_fragment; + + // Set up |inner_fragmentainer_idx| lazily, as it's O(n) (n == number of + // multicol container fragments). + if (!inner_fragmentainer_idx) + inner_fragmentainer_idx = PreviousInnerFragmentainerIndex(fragment); + context.current_fragmentainer.fragmentainer_idx = *inner_fragmentainer_idx; + + if (UNLIKELY(!context.tree_builder_context)) { + WalkChildren(actual_parent, box_fragment, context); + continue; + } + + PaintPropertyTreeBuilderContext& tree_builder_context = + *context.tree_builder_context; + PaintPropertyTreeBuilderFragmentContext& fragment_context = + tree_builder_context.fragments[0]; + PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext* + containing_block_context = &fragment_context.current; + containing_block_context->paint_offset += child.offset; + + const PhysicalOffset paint_offset = containing_block_context->paint_offset; + // Keep track of the paint offset at the fragmentainer, and also reset the + // offset adjustment tracker. This is needed when entering OOF + // descendants. OOFs have the nearest fragmentainer as their containing + // block, so when entering them during LayoutObject tree traversal, we have + // to compensate for this. + fragment_context.fragmentainer_paint_offset = paint_offset; + fragment_context.adjustment_for_oof_in_fragmentainer = PhysicalOffset(); + + // Create corresponding |FragmentData|. Hit-testing needs + // |FragmentData.PaintOffset|. + if (fragmentainer_fragment_data) { + DCHECK(!box_fragment->IsFirstForNode()); #if DCHECK_IS_ON() - DCHECK_EQ(fragmentainer_owner_box, box_fragment->OwnerLayoutBox()); + DCHECK_EQ(fragmentainer_owner_box, box_fragment->OwnerLayoutBox()); #endif - fragmentainer_fragment_data = - &fragmentainer_fragment_data->EnsureNextFragment(); - } else { - const LayoutBox* owner_box = box_fragment->OwnerLayoutBox(); + fragmentainer_fragment_data = + &fragmentainer_fragment_data->EnsureNextFragment(); + } else { + const LayoutBox* owner_box = box_fragment->OwnerLayoutBox(); #if DCHECK_IS_ON() - DCHECK(!fragmentainer_owner_box); - fragmentainer_owner_box = owner_box; + DCHECK(!fragmentainer_owner_box); + fragmentainer_owner_box = owner_box; #endif + fragmentainer_fragment_data = + &owner_box->GetMutableForPainting().FirstFragment(); + if (box_fragment->IsFirstForNode()) { + fragmentainer_fragment_data->ClearNextFragment(); + } else { + // |box_fragment| is nested in another fragmentainer, and that it is + // the first one in this loop, but not the first one for the + // |LayoutObject|. Append a new |FragmentData| to the last one. fragmentainer_fragment_data = - &owner_box->GetMutableForPainting().FirstFragment(); - if (box_fragment->IsFirstForNode()) { - fragmentainer_fragment_data->ClearNextFragment(); - } else { - // |box_fragment| is nested in another fragmentainer, and that it is - // the first one in this loop, but not the first one for the - // |LayoutObject|. Append a new |FragmentData| to the last one. - fragmentainer_fragment_data = - &fragmentainer_fragment_data->LastFragment().EnsureNextFragment(); - } + &fragmentainer_fragment_data->LastFragment().EnsureNextFragment(); } - fragmentainer_fragment_data->SetPaintOffset(paint_offset); - - WalkChildren(/* parent */ nullptr, parent_context, iterator); - containing_block_context->paint_offset -= offset; - continue; } - Walk(*object, parent_context, iterator); + fragmentainer_fragment_data->SetPaintOffset(paint_offset); + + WalkChildren(actual_parent, box_fragment, context); + + containing_block_context->paint_offset -= child.offset; + (*inner_fragmentainer_idx)++; } - const LayoutBlockFlow* parent_block = DynamicTo<LayoutBlockFlow>(parent); - if (!parent_block || !parent_block->MultiColumnFlowThread()) + if (!flow_thread) return; // Multicol containers only contain special legacy children invisible to // LayoutNG, so we need to clean them manually. - for (const LayoutObject* child = parent->SlowFirstChild(); child; + if (fragment.BreakToken()) + return; // Wait until we've reached the end. + for (const LayoutObject* child = object.SlowFirstChild(); child; child = child->NextSibling()) { DCHECK(child->IsLayoutFlowThread() || child->IsLayoutMultiColumnSet() || child->IsLayoutMultiColumnSpannerPlaceholder()); child->GetMutableForPainting().ClearPaintFlags(); } -} -void PrePaintTreeWalk::WalkLegacyChildren(const LayoutObject& object, - PrePaintTreeWalkContext& context) { - if (const auto* layout_box = DynamicTo<LayoutBox>(&object)) { - if (layout_box->CanTraversePhysicalFragments()) { - // Enter NG child fragment traversal. We'll stay in this mode for all - // descendants that support fragment traversal. We'll re-enter - // LayoutObject traversal for descendants that don't support it. This only - // works correctly if we're not block-fragmented, though, so DCHECK for - // that. - DCHECK_EQ(layout_box->PhysicalFragmentCount(), 1u); - const NGPhysicalBoxFragment& fragment = - To<NGPhysicalBoxFragment>(*layout_box->GetPhysicalFragment(0)); - DCHECK(!fragment.BreakToken()); - NGFragmentChildIterator child_iterator(fragment); - WalkNGChildren(&object, context, &child_iterator); - return; + // If we missed any nested fixpos elements during fragment traversal, that + // means that their containing block lives outside the fragmentation context + // root. Walk these missed fixepos elements now. + if (!pending_fixedpos_missables_.IsEmpty()) { + for (const auto* fixedpos : pending_fixedpos_missables_) { + DCHECK(!walked_fixedpos_.Contains(fixedpos)); + Walk(*fixedpos, context, /* pre_paint_info */ nullptr); } } +} - if (UNLIKELY(object.IsFieldsetIncludingNG())) { - // Handle the rendered legend of the fieldset right away. It may not be a - // direct child in the layout object tree (there may be an anonymous - // fieldset content wrapper in-between, and even a flow thread), but it is - // to be treated as such (similarly to out-of-flow positioned elements in a - // way). - if (const LayoutBox* legend = - LayoutFieldset::FindInFlowLegend(To<LayoutBlock>(object))) - Walk(*legend, context, /* iterator */ nullptr); - } +void PrePaintTreeWalk::WalkLayoutObjectChildren( + const LayoutObject& parent_object, + const NGPhysicalBoxFragment* parent_fragment, + PrePaintTreeWalkContext& context) { + absl::optional<NGInlineCursor> inline_cursor; + for (const LayoutObject* child = parent_object.SlowFirstChild(); child; + // Stay on the |child| while iterating fragments of |child|. + child = inline_cursor ? child : child->NextSibling()) { + if (!parent_fragment) { + // If we haven't found a fragment tree to accompany us in our walk, + // perform a pure LayoutObject tree walk. This is needed for legacy block + // fragmentation, and it also works fine if there's no block fragmentation + // involved at all (in such cases we can either to do this, or perform the + // NGPhysicalBoxFragment-accompanied walk that we do further down). + + if (child->IsLayoutMultiColumnSpannerPlaceholder()) { + child->GetMutableForPainting().ClearPaintFlags(); + continue; + } - for (const LayoutObject* child = object.SlowFirstChild(); child; - child = child->NextSibling()) { - if (child->IsLayoutMultiColumnSpannerPlaceholder()) { - child->GetMutableForPainting().ClearPaintFlags(); + Walk(*child, context, /* pre_paint_info */ nullptr); continue; } - // Skip out-of-flow positioned children lest they be walked twice. If |this| - // is an NG object, but it still walks its children the legacy way (this may - // happen to table-cells; see LayoutObject::CanTraversePhysicalFragments()), - // we're either going to handle it in the code below after the loop - if - // |this| is actually the containing block. Otherwise it will be handled by - // some ancestor - either in the same code below (if it's a legacy-walking - // object) or via regular child fragment traversal. If we walk it here as - // well, we'd end up walking it twice. This is both bad for performance, and - // also correctness, as fragment items are sensitive to how they're walked - // (see SetupFragmentData()). - if (UNLIKELY(RuntimeEnabledFeatures::LayoutNGFragmentTraversalEnabled() && - child->IsOutOfFlowPositioned() && object.IsLayoutNGObject())) - continue; - // The rendered legend was handled above, before processing the children of - // the fieldset. So skip it when found during normal child traversal. - if (UNLIKELY(child->IsRenderedLegend())) + // If we're in the middle of walking a missed OOF, don't enter nested OOFs + // (but miss those as well, and handle them via fragment traversal). + if (context.is_inside_orphaned_object && child->IsOutOfFlowPositioned()) { + if (child->IsFixedPositioned() && !walked_fixedpos_.Contains(child)) + pending_fixedpos_missables_.insert(child); continue; + } - Walk(*child, context, /* iterator */ nullptr); - } + // Perform an NGPhysicalBoxFragment-accompanied walk of the child + // LayoutObject tree. + // + // We'll map each child LayoutObject to a corresponding + // NGPhysicalBoxFragment. For text and non-atomic inlines this will be the + // fragment of their containing block, while for all other objects, it will + // be a fragment generated by the object itself. Even when we have LayoutNG + // fragments, we'll try to do the pre-paint walk it in LayoutObject tree + // order. This will ensure that paint properties are applied correctly (the + // LayoutNG fragment tree follows the containing block structure closely, + // but for paint effects, it's actually the LayoutObject / DOM tree + // structure that matters, e.g. when there's a relpos with a child with + // opacity, which has an absolutely positioned child, the absolutely + // positioned child should be affected by opacity, even if the object that + // establishes the opacity layer isn't in the containing block + // chain). Furthermore, culled inlines have no fragments, but they still + // need to be visited, since the invalidation code marks them for pre-paint. + const NGPhysicalBoxFragment* box_fragment = nullptr; + wtf_size_t fragmentainer_idx = + context.current_fragmentainer.fragmentainer_idx; + PhysicalOffset paint_offset; + const auto* child_box = DynamicTo<LayoutBox>(child); + bool is_first_for_node = true; + bool is_last_for_node = true; + bool is_inside_fragment_child = false; + + if (!inline_cursor && parent_fragment->HasItems() && + child->HasInlineFragments()) { + // Limit the search to descendants of |parent_fragment|. + inline_cursor.emplace(*parent_fragment); + inline_cursor->MoveTo(*child); + // Searching fragments of |child| may not find any because they may be in + // other fragmentainers than |parent_fragment|. + } + if (inline_cursor) { + for (; inline_cursor->Current(); + inline_cursor->MoveToNextForSameLayoutObject()) { + // Check if the search is limited to descendants of |parent_fragment|. + DCHECK_EQ(&inline_cursor->ContainerFragment(), parent_fragment); + const NGFragmentItem& item = *inline_cursor->Current().Item(); + DCHECK_EQ(item.GetLayoutObject(), child); + + is_last_for_node = item.IsLastForNode(); + if (box_fragment) { + if (is_last_for_node) + break; + continue; + } - if (!RuntimeEnabledFeatures::LayoutNGFragmentTraversalEnabled()) - return; + paint_offset = item.OffsetInContainerFragment(); + is_first_for_node = item.IsFirstForNode(); - const LayoutBlock* block = DynamicTo<LayoutBlock>(&object); - if (!block) - return; - const auto* positioned_objects = block->PositionedObjects(); - if (!positioned_objects) - return; + if (item.BoxFragment() && !item.BoxFragment()->IsInlineBox()) { + box_fragment = item.BoxFragment(); + is_last_for_node = !box_fragment->BreakToken(); + break; + } else { + // Inlines will pass their containing block fragment (and its incoming + // break token). + box_fragment = parent_fragment; + is_inside_fragment_child = true; + } - // If we have performed NG fragment traversal in any part of the subtree, we - // may have missed certain out-of-flow positioned objects. LayoutNG fragments - // are always children of their containing block, while the structure of the - // LayoutObject tree corresponds more closely to that of the DOM tree. - // - // Example: if we assume that flexbox isn't natively supported in LayoutNG: - // - // <div id="flex" style="display:flex; position:relative;"> - // <div id="flexitem"> - // <div id="abs" style="position:absolute;"></div> - // <div id="regular"></div> - // - // If we let |object| be #flex, it will be handled by legacy LayoutObject - // traversal, while #flexitem, on the other hand, can traverse its NG child - // fragments. However, #regular will be the only child fragment of #flexitem, - // since the containing block for #abs is #flex. So we'd miss it, unless we - // walk it now. - for (const LayoutBox* box : *positioned_objects) { - // It's important that objects that have already been walked be left alone. - // Otherwise, we might calculate the wrong offsets (and overwrite the - // correct ones) in case of out-of-flow positioned objects whose containing - // block is a relatively positioned non-atomic inline (such objects will - // already have been properly walked, since we don't switch engines within - // an inline formatting context). Put differently, the code here will only - // do the right thing if |object| is truly the containing block of the - // positioned objects in its list (which isn't the case if the containing - // block is a non-atomic inline). - if (!ObjectRequiresPrePaint(*box) && - !ObjectRequiresTreeBuilderContext(*box)) - continue; - DCHECK_EQ(box->Container(), &object); - Walk(*box, context, /* iterator */ nullptr); + if (is_last_for_node) + break; + + // Keep looking for the end. We need to know whether this is the last + // time we're going to visit this object. + } + if (is_last_for_node || !inline_cursor->Current()) { + // If all fragments are done, move to the next sibling of |child|. + inline_cursor.reset(); + } else { + inline_cursor->MoveToNextForSameLayoutObject(); + } + if (!box_fragment) + continue; + } else if (child->IsInline() && !child_box) { + // Deal with inline-level objects not searched for above. + // + // Needed for fragment-less objects that have children. This is the case + // for culled inlines. We're going to have to enter them for every + // fragment in the parent. + // + // The child is inline-level even if the parent fragment doesn't establish + // an inline formatting context. This may happen if there's only collapsed + // text, or if we had to insert a break in a block before we got to any + // inline content. Make sure that we visit such objects once. + + // Inlines will pass their containing block fragment (and its incoming + // break token). + box_fragment = parent_fragment; + is_inside_fragment_child = true; + + is_first_for_node = parent_fragment->IsFirstForNode(); + is_last_for_node = !parent_fragment->BreakToken(); + } else if (child_box && child_box->PhysicalFragmentCount()) { + // Figure out which fragment the child may be found inside. The fragment + // tree follows the structure of containing blocks closely, while here + // we're walking the LayoutObject tree (which follows the structure of the + // flat DOM tree, more or less). This means that for out-of-flow + // positioned objects, the fragment of the parent LayoutObject might not + // be the right place to search. + const NGPhysicalBoxFragment* search_fragment = parent_fragment; + if (child_box->IsOutOfFlowPositioned()) { + if (child_box->IsFixedPositioned()) { + search_fragment = context.fixed_positioned_container.fragment; + fragmentainer_idx = + context.fixed_positioned_container.fragmentainer_idx; + } else { + search_fragment = context.absolute_positioned_container.fragment; + fragmentainer_idx = + context.absolute_positioned_container.fragmentainer_idx; + } + if (!search_fragment) { + // Only walk unfragmented legacy-contained OOFs once. + if (!parent_fragment->IsFirstForNode()) + continue; + } + } + + if (search_fragment) { + // See if we can find a fragment for the child. + for (NGLink link : search_fragment->Children()) { + if (link->GetLayoutObject() != child) + continue; + // We found it! + box_fragment = To<NGPhysicalBoxFragment>(link.get()); + paint_offset = link.offset; + is_first_for_node = box_fragment->IsFirstForNode(); + is_last_for_node = !box_fragment->BreakToken(); + break; + } + // If we didn't find a fragment for the child, it means that the child + // doesn't occur inside the fragmentainer that we're currently handling. + if (!box_fragment) + continue; + } + } + + if (box_fragment) { + NGPrePaintInfo pre_paint_info( + *box_fragment, paint_offset, fragmentainer_idx, is_first_for_node, + is_last_for_node, context.is_inside_orphaned_object, + is_inside_fragment_child); + Walk(*child, context, &pre_paint_info); + } else { + Walk(*child, context, /* pre_paint_info */ nullptr); + } } } -void PrePaintTreeWalk::WalkChildren(const LayoutObject* object, +void PrePaintTreeWalk::WalkChildren(const LayoutObject& object, + const NGPhysicalBoxFragment* fragment, PrePaintTreeWalkContext& context, - const NGFragmentChildIterator* iterator) { - DCHECK(iterator || object); + bool is_inside_fragment_child) { + const LayoutBox* box = DynamicTo<LayoutBox>(&object); + if (box) { + if (fragment) { + if (!box->IsLayoutFlowThread() && (!box->CanTraversePhysicalFragments() || + !box->PhysicalFragmentCount())) { + // Leave LayoutNGBoxFragment-accompanied child LayoutObject traversal, + // since this object doesn't support that (or has no fragments (happens + // for table columns)). We need to switch back to legacy LayoutObject + // traversal for its children. We're then also assuming that we're + // either not block-fragmenting, or that this is monolithic content. We + // may re-enter LayoutNGBoxFragment-accompanied traversal if we get to a + // descendant that supports that. + DCHECK( + !box->FlowThreadContainingBlock() || + (box->GetNGPaginationBreakability() == LayoutBox::kForbidBreaks)); + + fragment = nullptr; + context.oof_container_candidate_fragment = nullptr; + } + } else if (box->PhysicalFragmentCount()) { + // Enter LayoutNGBoxFragment-accompanied child LayoutObject traversal if + // we're at an NG fragmentation context root. While we in theory *could* + // enter this mode for any object that has a traversable fragment, without + // affecting correctness, we're better off with plain LayoutObject + // traversal when possible, as fragment-accompanied traversal has O(n^2) + // performance complexity (where n is the number of siblings). + // + // We'll stay in this mode for all descendants that support fragment + // traversal. We'll re-enter legacy traversal for descendants that don't + // support it. This only works correctly as long as there's no block + // fragmentation in the ancestry, though, so DCHECK for that. + DCHECK_EQ(box->PhysicalFragmentCount(), 1u); + const auto* first_fragment = + To<NGPhysicalBoxFragment>(box->GetPhysicalFragment(0)); + DCHECK(!first_fragment->BreakToken()); + if (first_fragment->IsFragmentationContextRoot() && + box->CanTraversePhysicalFragments()) + fragment = first_fragment; + } - if (!iterator) { - // We're not doing LayoutNG fragment traversal of this object. - WalkLegacyChildren(*object, context); - return; + // Inline-contained OOFs are placed in the containing block of the + // containing inline in NG, not an anonymous block that's part of a + // continuation, if any. We need to know where these might be stored, so + // that we eventually search the right ancestor fragment for them. + if (fragment && !box->IsAnonymousBlock()) + context.oof_container_candidate_fragment = fragment; } - // If we are performing LayoutNG fragment traversal, but this object doesn't - // support that, we need to switch back to legacy LayoutObject traversal for - // its children. We're then also assuming that we're either not - // block-fragmenting, or that this is monolithic content. We may re-enter - // LayoutNG fragment traversal if we get to a descendant that supports that. - if (object && !object->CanTraversePhysicalFragments()) { - DCHECK(!object->FlowThreadContainingBlock() || - (object->IsBox() && - To<LayoutBox>(object)->GetNGPaginationBreakability() == - LayoutBox::kForbidBreaks)); - WalkLegacyChildren(*object, context); - return; + // Keep track of fragments that act as containers for OOFs, so that we can + // search their children when looking for an OOF further down in the tree. + if (object.CanContainAbsolutePositionObjects()) { + if (context.current_fragmentainer.fragment && box && + box->GetNGPaginationBreakability() == LayoutBox::kForbidBreaks) { + // If we're in a fragmentation context, the parent fragment of OOFs is the + // fragmentainer, unless the object is monolithic, in which case nothing + // inside the object participates in the current block fragmentation + // context. This means that this object (and not the nearest + // fragmentainer) acts as a containing block for OOF descendants, + context.current_fragmentainer = {}; + } + // The OOF containing block structure is special under block fragmentation: + // A fragmentable OOF is always a direct child of a fragmentainer. + const auto* container_fragment = context.current_fragmentainer.fragment; + if (!container_fragment) + container_fragment = context.oof_container_candidate_fragment; + context.absolute_positioned_container = { + container_fragment, context.current_fragmentainer.fragmentainer_idx}; + if (object.CanContainFixedPositionObjects()) { + context.fixed_positioned_container = { + container_fragment, context.current_fragmentainer.fragmentainer_idx}; + } } - // Traverse child NG fragments. - NGFragmentChildIterator child_iterator(iterator->Descend()); - WalkNGChildren(object, context, &child_iterator); + if (fragment) { + bool has_missable_children = false; + // If we are at a block fragment, collect any missable children. + DCHECK(!is_inside_fragment_child || !object.IsBox()); + if (!is_inside_fragment_child) + has_missable_children = CollectMissableChildren(context, *fragment); + + // We'll always walk the LayoutObject tree when possible, but if this is a + // fragmentation context root (such as a multicol container), we need to + // enter each fragmentainer child and then walk all the LayoutObject + // children. + if (fragment->IsFragmentationContextRoot()) + WalkFragmentationContextRootChildren(object, *fragment, context); + else + WalkLayoutObjectChildren(object, fragment, context); + + if (has_missable_children) + WalkMissedChildren(*fragment, context); + } else { + WalkLayoutObjectChildren(object, fragment, context); + } } void PrePaintTreeWalk::Walk(const LayoutObject& object, const PrePaintTreeWalkContext& parent_context, - const NGFragmentChildIterator* iterator) { + NGPrePaintInfo* pre_paint_info) { const NGPhysicalBoxFragment* physical_fragment = nullptr; - bool is_last_fragment = true; - if (iterator) { - physical_fragment = (*iterator)->BoxFragment(); - if (const auto* fragment_item = (*iterator)->FragmentItem()) - is_last_fragment = fragment_item->IsLastForNode(); - else if (physical_fragment) - is_last_fragment = !physical_fragment->BreakToken(); + bool is_inside_fragment_child = false; + if (pre_paint_info) { + physical_fragment = &pre_paint_info->box_fragment; + if (physical_fragment && (physical_fragment->IsOutOfFlowPositioned() || + physical_fragment->IsFloating())) { + pending_missables_.erase(physical_fragment); + if (object.IsFixedPositioned()) { + pending_fixedpos_missables_.erase(&object); + walked_fixedpos_.insert(&object); + } + } + is_inside_fragment_child = pre_paint_info->is_inside_fragment_child; } bool needs_tree_builder_context_update = - ContextRequiresChildTreeBuilderContext(parent_context) || - ObjectRequiresTreeBuilderContext(object); + NeedsTreeBuilderContextUpdate(object, parent_context); #if DCHECK_IS_ON() CheckTreeBuilderContextState(object, parent_context); @@ -951,7 +1211,7 @@ void PrePaintTreeWalk::Walk(const LayoutObject& object, context.tree_builder_context->clip_changed = false; } - WalkInternal(object, context, iterator); + WalkInternal(object, context, pre_paint_info); bool child_walk_blocked = object.ChildPrePaintBlockedByDisplayLock(); // If we need a subtree walk due to context flags, we need to store that @@ -971,7 +1231,7 @@ void PrePaintTreeWalk::Walk(const LayoutObject& object, } if (!child_walk_blocked) { - WalkChildren(&object, context, iterator); + WalkChildren(object, physical_fragment, context, is_inside_fragment_child); if (const auto* layout_embedded_content = DynamicTo<LayoutEmbeddedContent>(object)) { @@ -1000,7 +1260,7 @@ void PrePaintTreeWalk::Walk(const LayoutObject& object, } } } - if (is_last_fragment) + if (!pre_paint_info || pre_paint_info->is_last_for_node) object.GetMutableForPainting().ClearPaintFlags(); } diff --git a/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h b/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h index 42ae9c2d19d..7971ae5340a 100644 --- a/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h +++ b/chromium/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h @@ -6,15 +6,19 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PRE_PAINT_TREE_WALK_H_ #include "base/dcheck_is_on.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/paint/paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_property_tree_builder.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" namespace blink { class LayoutObject; class LocalFrameView; -class NGFragmentChildIterator; +struct NGLink; +class NGPhysicalBoxFragment; +class NGPhysicalFragment; // This class walks the whole layout tree, beginning from the root // LocalFrameView, across frame boundaries. Helper classes are called for each @@ -30,56 +34,24 @@ class CORE_EXPORT PrePaintTreeWalk final { static bool ObjectRequiresPrePaint(const LayoutObject&); static bool ObjectRequiresTreeBuilderContext(const LayoutObject&); - struct PrePaintTreeWalkContext { - STACK_ALLOCATED(); + struct ContainingFragment { + const NGPhysicalBoxFragment* fragment = nullptr; + wtf_size_t fragmentainer_idx = WTF::kNotFound; + }; - public: - PrePaintTreeWalkContext() { - tree_builder_context.emplace(); - } - PrePaintTreeWalkContext(const PrePaintTreeWalkContext& parent_context, - bool needs_tree_builder_context) - : paint_invalidator_context(parent_context.paint_invalidator_context), - ancestor_scroll_container_paint_layer( - parent_context.ancestor_scroll_container_paint_layer), - inside_blocking_touch_event_handler( - parent_context.inside_blocking_touch_event_handler), - effective_allowed_touch_action_changed( - parent_context.effective_allowed_touch_action_changed), - inside_blocking_wheel_event_handler( - parent_context.inside_blocking_wheel_event_handler), - blocking_wheel_event_handler_changed( - parent_context.blocking_wheel_event_handler_changed), - clip_changed(parent_context.clip_changed), - paint_invalidation_container( - parent_context.paint_invalidation_container), - paint_invalidation_container_for_stacked_contents( - parent_context - .paint_invalidation_container_for_stacked_contents) { - if (needs_tree_builder_context || DCHECK_IS_ON()) { - DCHECK(parent_context.tree_builder_context); - tree_builder_context.emplace(*parent_context.tree_builder_context); - } -#if DCHECK_IS_ON() - if (needs_tree_builder_context) - DCHECK(parent_context.tree_builder_context->is_actually_needed); - tree_builder_context->is_actually_needed = needs_tree_builder_context; -#endif - } + // This provides a default base copy constructor for PrePaintTreeWalkContext. + // It contains all fields except for tree_builder_context which needs special + // treatment in the copy constructor. + struct PrePaintTreeWalkContextBase { + STACK_ALLOCATED(); - absl::optional<PaintPropertyTreeBuilderContext> tree_builder_context; + protected: + PrePaintTreeWalkContextBase() = default; + PrePaintTreeWalkContextBase(const PrePaintTreeWalkContextBase&) = default; + public: PaintInvalidatorContext paint_invalidator_context; - bool NeedsTreeBuilderContext() const { -#if DCHECK_IS_ON() - DCHECK(tree_builder_context); - return tree_builder_context->is_actually_needed; -#else - return tree_builder_context.has_value(); -#endif - } - // The ancestor in the PaintLayer tree which is a scroll container. Note // that it is tree ancestor, not containing block or stacking ancestor. PaintLayer* ancestor_scroll_container_paint_layer = nullptr; @@ -104,9 +76,60 @@ class CORE_EXPORT PrePaintTreeWalk final { // enabled. bool clip_changed = false; + // True if we're fragment-traversing an object whose fragment wasn't found + // and walked when walking the layout object tree. This may happen for + // out-of-flow positioned and floated fragments inside block fragmentation, + // when an ancestor object doesn't have a fragment representation in a + // fragmentainer even if the OOF / float is there. + bool is_inside_orphaned_object = false; + const LayoutBoxModelObject* paint_invalidation_container = nullptr; const LayoutBoxModelObject* paint_invalidation_container_for_stacked_contents = nullptr; + + ContainingFragment current_fragmentainer; + ContainingFragment absolute_positioned_container; + ContainingFragment fixed_positioned_container; + + // When walking down the tree and discovering containers for OOFs, not every + // such container has the fragment actually containing OOF descendants; they + // may instead be inside a fragment generated by a parent (this happens for + // inline continuations, for instance). So keep track of the innermost valid + // container fragment for OOFs, and set + // absolute_positioned_container_fragment and + // fixed_positioned_container_fragment to this one as appropriate. + const NGPhysicalBoxFragment* oof_container_candidate_fragment = nullptr; + }; + + struct PrePaintTreeWalkContext : public PrePaintTreeWalkContextBase { + PrePaintTreeWalkContext() { tree_builder_context.emplace(); } + PrePaintTreeWalkContext(const PrePaintTreeWalkContext& parent_context, + bool needs_tree_builder_context) + : PrePaintTreeWalkContextBase(parent_context) { + if (needs_tree_builder_context || DCHECK_IS_ON()) { + DCHECK(parent_context.tree_builder_context); + tree_builder_context.emplace(*parent_context.tree_builder_context); + } +#if DCHECK_IS_ON() + if (needs_tree_builder_context) + DCHECK(parent_context.tree_builder_context->is_actually_needed); + tree_builder_context->is_actually_needed = needs_tree_builder_context; +#endif + } + + PrePaintTreeWalkContext(const PrePaintTreeWalkContext&) = delete; + PrePaintTreeWalkContext& operator=(const PrePaintTreeWalkContext&) = delete; + + bool NeedsTreeBuilderContext() const { +#if DCHECK_IS_ON() + DCHECK(tree_builder_context); + return tree_builder_context->is_actually_needed; +#else + return tree_builder_context.has_value(); +#endif + } + + absl::optional<PaintPropertyTreeBuilderContext> tree_builder_context; }; static bool ContextRequiresChildPrePaint(const PrePaintTreeWalkContext&); @@ -118,6 +141,19 @@ class CORE_EXPORT PrePaintTreeWalk final { const PrePaintTreeWalkContext&); #endif + // Upon entering a child LayoutObject, create an NGPrePaintInfo, and populate + // everything except its FragmentData. We need to get a bit further inside the + // child (WalkInternal()) before we can set up FragmentData (if we get there + // at all). + NGPrePaintInfo CreatePrePaintInfo(const NGLink& child, + const PrePaintTreeWalkContext& context); + + // Locate and/or set up a FragmentData object for the current object / + // physical fragment. + FragmentData* GetOrCreateFragmentData(const LayoutObject&, + const PrePaintTreeWalkContext&, + const NGPrePaintInfo&); + void Walk(LocalFrameView&, const PrePaintTreeWalkContext& parent_context); // This is to minimize stack frame usage during recursion. Modern compilers @@ -127,20 +163,44 @@ class CORE_EXPORT PrePaintTreeWalk final { // See https://crbug.com/781301 . NOINLINE void WalkInternal(const LayoutObject&, PrePaintTreeWalkContext&, - const NGFragmentChildIterator*); - void WalkNGChildren(const LayoutObject* parent, - PrePaintTreeWalkContext& parent_context, - NGFragmentChildIterator*); - void WalkLegacyChildren(const LayoutObject&, PrePaintTreeWalkContext&); - void WalkChildren(const LayoutObject*, + NGPrePaintInfo*); + + // Add any "missable" children to a list. Missable children are children that + // we might not find during LayoutObject traversal. This happens when an + // ancestor LayoutObject (of the missable child) has no fragment inside a + // given fragmentainer, e.g. when there's an OOF fragment, but its containing + // block has no fragment inside that fragmentainer. Later, during the child + // walk, when a missable child is actually walked, it's removed from the + // list. + // + // Returns true if there are any missable children inside the fragment, false + // otherwise. + bool CollectMissableChildren(PrePaintTreeWalkContext&, + const NGPhysicalBoxFragment&); + + // Walk any missed children (i.e. those collected by CollectMissableChildren() + // and not walked by Walk()) after child object traversal. + void WalkMissedChildren(const NGPhysicalBoxFragment&, + PrePaintTreeWalkContext&); + + void WalkFragmentationContextRootChildren(const LayoutObject&, + const NGPhysicalBoxFragment&, + PrePaintTreeWalkContext&); + void WalkLayoutObjectChildren(const LayoutObject&, + const NGPhysicalBoxFragment*, + PrePaintTreeWalkContext&); + void WalkChildren(const LayoutObject&, + const NGPhysicalBoxFragment*, PrePaintTreeWalkContext&, - const NGFragmentChildIterator*); + bool is_inside_fragment_child = false); void Walk(const LayoutObject&, const PrePaintTreeWalkContext& parent_context, - const NGFragmentChildIterator*); + NGPrePaintInfo*); bool NeedsTreeBuilderContextUpdate(const LocalFrameView&, const PrePaintTreeWalkContext&); + bool NeedsTreeBuilderContextUpdate(const LayoutObject&, + const PrePaintTreeWalkContext&); void UpdateAuxiliaryObjectProperties(const LayoutObject&, PrePaintTreeWalkContext&); // Updates |LayoutObject::InsideBlockingTouchEventHandler|. Also ensures @@ -163,6 +223,21 @@ class CORE_EXPORT PrePaintTreeWalk final { PaintInvalidator paint_invalidator_; + // List of fragments that may be missed during LayoutObject walking. See + // CollectMissableChildren() and WalkMissedChildren(). + HashSet<const NGPhysicalFragment*> pending_missables_; + + // List of fixedpos objects that may be missed during fragment traversal. This + // can happen if a fixedpos is nested in another OOF inside a multicol, and + // the OOF parent is a pending missable (see |pending_missables_|). If that + // fixedpos' containing block is located outside of the multicol, we can would + // miss it during normal fragment traversal. + HashSet<const LayoutObject*> pending_fixedpos_missables_; + + // List of fixedpos objects that have already been walked. This helps to avoid + // re-walking any fixedpos objects handled by |pending_fixedpos_missables_|. + HashSet<const LayoutObject*> walked_fixedpos_; + // TODO(https://crbug.com/841364): Remove is_wheel_event_regions_enabled // argument once kWheelEventRegions feature flag is removed. bool is_wheel_event_regions_enabled_ = false; diff --git a/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.cc b/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.cc index 32d127242e4..db66edbb0ef 100644 --- a/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.cc +++ b/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.cc @@ -112,39 +112,46 @@ FloatRoundedRect RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( int bottom_width = sides_to_include.bottom ? style.BorderBottomWidth().Floor() : 0; - return PixelSnappedRoundedInnerBorder( + return PixelSnappedRoundedBorderWithOutsets( style, border_rect, LayoutRectOutsets(-top_width, -right_width, -bottom_width, -left_width), sides_to_include); } -FloatRoundedRect RoundedBorderGeometry::PixelSnappedRoundedInnerBorder( +FloatRoundedRect RoundedBorderGeometry::PixelSnappedRoundedBorderWithOutsets( const ComputedStyle& style, const PhysicalRect& border_rect, - const LayoutRectOutsets& insets, + const LayoutRectOutsets& outsets, PhysicalBoxSides sides_to_include) { - PhysicalRect inner_rect = border_rect; - inner_rect.Expand(insets); - inner_rect.size.ClampNegativeToZero(); + PhysicalRect rect_with_outsets = border_rect; + rect_with_outsets.Expand(outsets); + rect_with_outsets.size.ClampNegativeToZero(); // The standard LayoutRect::PixelSnappedIntRect() method will not // let small sizes snap to zero, but that has the side effect here of // preventing an inner border for a very thin element from snapping to // zero size as occurs when a unit width border is applied to a sub-pixel // sized element. So round without forcing non-near-zero sizes to one. - FloatRoundedRect rounded_rect(IntRect( - RoundedIntPoint(inner_rect.offset), - IntSize( - SnapSizeToPixelAllowingZero(inner_rect.Width(), inner_rect.X()), - SnapSizeToPixelAllowingZero(inner_rect.Height(), inner_rect.Y())))); + FloatRoundedRect rounded_rect( + IntRect(RoundedIntPoint(rect_with_outsets.offset), + IntSize(SnapSizeToPixelAllowingZero(rect_with_outsets.Width(), + rect_with_outsets.X()), + SnapSizeToPixelAllowingZero(rect_with_outsets.Height(), + rect_with_outsets.Y())))); if (style.HasBorderRadius()) { FloatRoundedRect::Radii radii = PixelSnappedRoundedBorder(style, border_rect, sides_to_include) .GetRadii(); - // Insets use negative values. - radii.Shrink(-insets.Top().ToFloat(), -insets.Bottom().ToFloat(), - -insets.Left().ToFloat(), -insets.Right().ToFloat()); + if (outsets.Top() <= 0 && outsets.Bottom() <= 0 && outsets.Left() <= 0 && + outsets.Right() <= 0) { + radii.Shrink(-outsets.Top().ToFloat(), -outsets.Bottom().ToFloat(), + -outsets.Left().ToFloat(), -outsets.Right().ToFloat()); + } else { + // radii.Expand() will DCHECK if all values are >= 0. + radii.Expand(outsets.Top().ToFloat(), outsets.Bottom().ToFloat(), + outsets.Left().ToFloat(), outsets.Right().ToFloat()); + } ExcludeSides(sides_to_include, &radii); rounded_rect.SetRadii(radii); } diff --git a/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.h b/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.h index 4881daeb366..5e9c4dfed5e 100644 --- a/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.h +++ b/chromium/third_party/blink/renderer/core/paint/rounded_border_geometry.h @@ -36,10 +36,12 @@ class CORE_EXPORT RoundedBorderGeometry { const PhysicalRect& border_rect, PhysicalBoxSides edges_to_include = PhysicalBoxSides()); - static FloatRoundedRect PixelSnappedRoundedInnerBorder( + // Values in |outsets| must be either all >= 0 to expand from |border_rect|, + // or all <= 0 to shrink from |border_rect|. + static FloatRoundedRect PixelSnappedRoundedBorderWithOutsets( const ComputedStyle&, const PhysicalRect& border_rect, - const LayoutRectOutsets& insets, + const LayoutRectOutsets& outsets_from_border, PhysicalBoxSides edges_to_include = PhysicalBoxSides()); }; diff --git a/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.cc b/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.cc index 3e302b138f0..db29fae1c85 100644 --- a/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.cc +++ b/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.cc @@ -55,25 +55,25 @@ void ScopedBoxContentsPaintState::AdjustForBoxContents(const LayoutBox& box) { fragment_to_paint_->ContentsProperties(), box, input_paint_info_.DisplayItemTypeForClipping()); - // Then adjust paint offset and cull rect for scroll translation. const auto* properties = fragment_to_paint_->PaintProperties(); - if (!properties) - return; - const auto* scroll_translation = properties->ScrollTranslation(); - if (!scroll_translation) - return; + const auto* scroll_translation = + properties ? properties->ScrollTranslation() : nullptr; // See comments for ScrollTranslation in object_paint_properties.h // for the reason of adding ScrollOrigin(). The paint offset will // be used only for the scrolling contents that are not painted through // descendant objects' Paint() method, e.g. inline boxes. - paint_offset_ += PhysicalOffset(box.ScrollOrigin()); + if (scroll_translation) + paint_offset_ += PhysicalOffset(box.ScrollOrigin()); if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { + // We calculated cull rects for PaintLayers only. + if (!box.HasLayer()) + return; adjusted_paint_info_.emplace(input_paint_info_); adjusted_paint_info_->SetCullRect( fragment_to_paint_->GetContentsCullRect()); - if (box.HasLayer() && box.Layer()->PreviousPaintResult() == kFullyPainted) { + if (box.Layer()->PreviousPaintResult() == kFullyPainted) { PhysicalRect contents_visual_rect = box.PhysicalContentsVisualOverflowRect(); contents_visual_rect.Move(fragment_to_paint_->PaintOffset()); @@ -94,8 +94,10 @@ void ScopedBoxContentsPaintState::AdjustForBoxContents(const LayoutBox& box) { if (IsA<LayoutView>(box) && input_paint_info_.GetCullRect().IsInfinite()) return; - adjusted_paint_info_.emplace(input_paint_info_); - adjusted_paint_info_->TransformCullRect(*scroll_translation); + if (scroll_translation) { + adjusted_paint_info_.emplace(input_paint_info_); + adjusted_paint_info_->TransformCullRect(*scroll_translation); + } } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.h b/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.h index e2a0f807a38..8a1d5f85777 100644 --- a/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.h +++ b/chromium/third_party/blink/renderer/core/paint/scoped_paint_state.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCOPED_PAINT_STATE_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCOPED_PAINT_STATE_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" #include "third_party/blink/renderer/core/paint/paint_info.h" @@ -45,7 +46,9 @@ class ScopedPaintState { return; } const auto* properties = fragment_to_paint_->PaintProperties(); - if (properties && properties->PaintOffsetTranslation()) { + if (!properties) + return; + if (properties->PaintOffsetTranslation()) { AdjustForPaintOffsetTranslation(object, *properties->PaintOffsetTranslation()); } diff --git a/chromium/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h b/chromium/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h index 750aed68085..6cc9ccc4014 100644 --- a/chromium/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h +++ b/chromium/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h @@ -26,6 +26,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCOPED_SVG_PAINT_STATE_H_ #include "base/dcheck_is_on.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/paint/object_paint_properties.h" #include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h" diff --git a/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter.cc b/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter.cc index e6fc87badf9..089a702ffac 100644 --- a/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/page/chrome_client.h" @@ -225,6 +226,20 @@ void ScrollableAreaPainter::PaintScrollbar(GraphicsContext& context, if (!cull_rect.Intersects(visual_rect)) return; + const auto* properties = + GetScrollableArea().GetLayoutBox()->FirstFragment().PaintProperties(); + DCHECK(properties); + auto type = scrollbar.Orientation() == kHorizontalScrollbar + ? DisplayItem::kScrollbarHorizontal + : DisplayItem::kScrollbarVertical; + absl::optional<ScopedPaintChunkProperties> chunk_properties; + if (const auto* effect = scrollbar.Orientation() == kHorizontalScrollbar + ? properties->HorizontalScrollbarEffect() + : properties->VerticalScrollbarEffect()) { + chunk_properties.emplace(context.GetPaintController(), *effect, scrollbar, + type); + } + if (scrollbar.IsCustomScrollbar()) { scrollbar.Paint(context, paint_offset); @@ -240,19 +255,12 @@ void ScrollableAreaPainter::PaintScrollbar(GraphicsContext& context, return; } - auto type = scrollbar.Orientation() == kHorizontalScrollbar - ? DisplayItem::kScrollbarHorizontal - : DisplayItem::kScrollbarVertical; if (context.GetPaintController().UseCachedItemIfPossible(scrollbar, type)) return; const TransformPaintPropertyNode* scroll_translation = nullptr; - if (scrollable_area_->ShouldDirectlyCompositeScrollbar(scrollbar)) { - auto* properties = - GetScrollableArea().GetLayoutBox()->FirstFragment().PaintProperties(); - DCHECK(properties); + if (scrollable_area_->ShouldDirectlyCompositeScrollbar(scrollbar)) scroll_translation = properties->ScrollTranslation(); - } auto delegate = base::MakeRefCounted<ScrollbarLayerDelegate>( scrollbar, context.DeviceScaleFactor()); ScrollbarDisplayItem::Record(context, scrollbar, type, delegate, visual_rect, diff --git a/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter_test.cc new file mode 100644 index 00000000000..c438826ab37 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/scrollable_area_painter_test.cc @@ -0,0 +1,54 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" + +using testing::_; +using testing::ElementsAre; + +namespace blink { + +using ScrollableAreaPainterTest = PaintControllerPaintTest; + +INSTANTIATE_CAP_TEST_SUITE_P(ScrollableAreaPainterTest); + +TEST_P(ScrollableAreaPainterTest, OverlayScrollbars) { + SetBodyInnerHTML(R"HTML( + <div id="target" style="overflow: scroll; width: 50px; height: 50px"> + <div style="width: 200px; height: 200px"></div> + </div> + )HTML"); + + ASSERT_TRUE( + GetDocument().GetPage()->GetScrollbarTheme().UsesOverlayScrollbars()); + const auto* target = To<LayoutBox>(GetLayoutObjectByElementId("target")); + const auto* properties = + GetLayoutObjectByElementId("target")->FirstFragment().PaintProperties(); + ASSERT_TRUE(properties); + ASSERT_TRUE(properties->HorizontalScrollbarEffect()); + ASSERT_TRUE(properties->VerticalScrollbarEffect()); + + PaintChunk::Id horizontal_id( + *target->GetScrollableArea()->HorizontalScrollbar(), + DisplayItem::kScrollbarHorizontal); + auto horizontal_state = target->FirstFragment().LocalBorderBoxProperties(); + horizontal_state.SetEffect(*properties->HorizontalScrollbarEffect()); + + PaintChunk::Id vertical_id(*target->GetScrollableArea()->VerticalScrollbar(), + DisplayItem::kScrollbarVertical); + auto vertical_state = target->FirstFragment().LocalBorderBoxProperties(); + vertical_state.SetEffect(*properties->VerticalScrollbarEffect()); + + EXPECT_THAT(ContentPaintChunks(), + ElementsAre(VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON, _, _, _, + IsPaintChunk(1, 2, horizontal_id, horizontal_state), + IsPaintChunk(2, 3, vertical_id, vertical_state))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc b/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc index 58c5409858b..f9266ca880b 100644 --- a/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc +++ b/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder.cc @@ -3,12 +3,15 @@ // found in the LICENSE file. #include "third_party/blink/renderer/core/paint/selection_bounds_recorder.h" + #include "third_party/blink/renderer/core/editing/frame_selection.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/html/forms/text_control_element.h" #include "third_party/blink/renderer/core/layout/api/selection_state.h" #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" #include "third_party/blink/renderer/core/layout/layout_box.h" -#include "third_party/blink/renderer/core/layout/layout_view.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" +#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" namespace blink { @@ -164,6 +167,16 @@ bool SelectionBoundsRecorder::ShouldRecordSelection( if (!frame_selection.IsHandleVisible() || frame_selection.IsHidden()) return false; + // If the currently focused frame is not the one in which selection + // lives, don't paint the selection bounds. Note this is subtly different + // from whether the frame has focus (i.e. `FrameSelection::SelectionHasFocus`) + // which is false if the hosting window is not focused. + LocalFrame* local_frame = frame_selection.GetFrame(); + LocalFrame* focused_frame = + local_frame->GetPage()->GetFocusController().FocusedFrame(); + if (local_frame != focused_frame) + return false; + if (state == SelectionState::kInside || state == SelectionState::kNone) return false; @@ -189,7 +202,7 @@ bool SelectionBoundsRecorder::IsVisible(const LayoutObject& rect_layout_object, return true; const PhysicalOffset sample_point = GetSamplePointForVisibility( - edge_start, edge_end, rect_layout_object.View()->ZoomFactor()); + edge_start, edge_end, rect_layout_object.GetFrame()->PageZoomFactor()); auto* const text_control_object = To<LayoutBox>(layout_object); const PhysicalOffset position_in_input = diff --git a/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder_test.cc b/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder_test.cc new file mode 100644 index 00000000000..2f14c05d1d6 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/paint/selection_bounds_recorder_test.cc @@ -0,0 +1,206 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/core/editing/testing/selection_sample.h" +#include "third_party/blink/renderer/core/page/focus_controller.h" +#include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" + +using testing::ElementsAre; + +namespace blink { + +class SelectionBoundsRecorderTest : public PaintControllerPaintTestBase, + public testing::WithParamInterface<bool>, + public ScopedLayoutNGForTest, + public ScopedCompositeAfterPaintForTest { + public: + SelectionBoundsRecorderTest() + : ScopedLayoutNGForTest(GetParam()), + ScopedCompositeAfterPaintForTest(true) {} +}; + +struct SelectionBoundsRecorderTestPassToString { + std::string operator()(const testing::TestParamInfo<bool> b) const { + return b.param ? "LayoutNG" : "LegacyLayout"; + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + SelectionBoundsRecorderTest, + ::testing::Bool(), + SelectionBoundsRecorderTestPassToString()); + +TEST_P(SelectionBoundsRecorderTest, SelectAll) { + SetBodyInnerHTML("<span>A<br>B<br>C</span>"); + + LocalFrame* local_frame = GetDocument().GetFrame(); + local_frame->Selection().SetHandleVisibleForTesting(); + local_frame->GetPage()->GetFocusController().SetFocusedFrame(local_frame); + local_frame->Selection().SelectAll(); + UpdateAllLifecyclePhasesForTest(); + + auto chunks = ContentPaintChunks(); + EXPECT_EQ(chunks.size(), 1u); + EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value()); + EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value()); + PaintedSelectionBound start = + chunks.begin()->layer_selection_data->start.value(); + EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); + EXPECT_EQ(start.edge_start, IntPoint(8, 8)); + EXPECT_EQ(start.edge_end, IntPoint(8, 9)); + + PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value(); + EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); + EXPECT_EQ(end.edge_start, IntPoint(9, 10)); + EXPECT_EQ(end.edge_end, IntPoint(9, 11)); +} + +TEST_P(SelectionBoundsRecorderTest, SelectMultiline) { + LocalFrame* local_frame = GetDocument().GetFrame(); + local_frame->Selection().SetSelectionAndEndTyping( + SelectionSample::SetSelectionText( + GetDocument().body(), + "<div style='white-space:pre'>f^oo\nbar\nb|az</div>")); + local_frame->Selection().SetHandleVisibleForTesting(); + local_frame->GetPage()->GetFocusController().SetFocusedFrame(local_frame); + UpdateAllLifecyclePhasesForTest(); + + auto chunks = ContentPaintChunks(); + EXPECT_EQ(chunks.size(), 1u); + EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value()); + EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value()); + PaintedSelectionBound start = + chunks.begin()->layer_selection_data->start.value(); + EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); + EXPECT_EQ(start.edge_start, IntPoint(8, 8)); + EXPECT_EQ(start.edge_end, IntPoint(8, 9)); + + PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value(); + EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); + EXPECT_EQ(end.edge_start, IntPoint(9, 10)); + EXPECT_EQ(end.edge_end, IntPoint(9, 11)); +} + +TEST_P(SelectionBoundsRecorderTest, SelectMultilineEmptyStartEnd) { + LocalFrame* local_frame = GetDocument().GetFrame(); + LoadAhem(*local_frame); + local_frame->Selection().SetSelectionAndEndTyping( + SelectionSample::SetSelectionText(GetDocument().body(), + R"HTML( + <style> + body { margin: 0; } + * { font: 10px/1 Ahem; } + </style> + <div>foo^<br>bar<br>|baz</div> + )HTML")); + local_frame->Selection().SetHandleVisibleForTesting(); + local_frame->GetPage()->GetFocusController().SetFocusedFrame(local_frame); + UpdateAllLifecyclePhasesForTest(); + + auto chunks = ContentPaintChunks(); + EXPECT_EQ(chunks.size(), 1u); + EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value()); + EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value()); + PaintedSelectionBound start = + chunks.begin()->layer_selection_data->start.value(); + EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); + EXPECT_EQ(start.edge_start, IntPoint(30, 0)); + EXPECT_EQ(start.edge_end, IntPoint(30, 10)); + + PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value(); + EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); + EXPECT_EQ(end.edge_start, IntPoint(0, 20)); + EXPECT_EQ(end.edge_end, IntPoint(0, 30)); +} + +TEST_P(SelectionBoundsRecorderTest, InvalidationForEmptyBounds) { + LocalFrame* local_frame = GetDocument().GetFrame(); + LoadAhem(*local_frame); + + // Set a selection that has empty start and end in separate paint chunks. + // We'll move these empty endpoints into the middle div and make sure + // everything is invalidated/re-painted/recorded correctly. + local_frame->Selection().SetSelectionAndEndTyping( + SelectionSample::SetSelectionText(GetDocument().body(), + R"HTML( + <style> + body { margin: 0; } + div { will-change: transform; } + * { font: 10px/1 Ahem; } + </style> + <div>foo^</div><div id=target>bar</div><div>|baz</div> + )HTML")); + local_frame->Selection().SetHandleVisibleForTesting(); + local_frame->GetPage()->GetFocusController().SetFocusedFrame(local_frame); + UpdateAllLifecyclePhasesForTest(); + + auto chunks = ContentPaintChunks(); + EXPECT_EQ(chunks.size(), 4u); + + PaintChunkSubset::Iterator chunk_iterator = chunks.begin(); + // Skip the root chunk to get to the first div. + ++chunk_iterator; + EXPECT_TRUE(chunk_iterator->layer_selection_data->start.has_value()); + PaintedSelectionBound start = + chunk_iterator->layer_selection_data->start.value(); + EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); + EXPECT_EQ(start.edge_start, IntPoint(30, 0)); + EXPECT_EQ(start.edge_end, IntPoint(30, 10)); + + // Skip the middle div as well to get to the third div where the end of the + // selection is. + ++chunk_iterator; + ++chunk_iterator; + + EXPECT_TRUE(chunk_iterator->layer_selection_data->end.has_value()); + PaintedSelectionBound end = chunk_iterator->layer_selection_data->end.value(); + EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); + // Coordinates are chunk-relative, so they should start at 0 y coordinate. + EXPECT_EQ(end.edge_start, IntPoint(0, 0)); + EXPECT_EQ(end.edge_end, IntPoint(0, 10)); + + // Move the selection around the start and end of the second div. + local_frame->Selection().SetSelectionAndEndTyping( + SelectionInDOMTree::Builder() + .Collapse(Position(GetElementById("target")->firstChild(), 0)) + .Extend(Position(GetElementById("target")->firstChild(), 3)) + .Build()); + + // Ensure the handle will be visible for the next paint (previous call to + // SetSelectionAndEndTyping will clear the bit). + local_frame->Selection().SetHandleVisibleForTesting(); + + UpdateAllLifecyclePhasesForTest(); + + chunks = ContentPaintChunks(); + chunk_iterator = chunks.begin(); + EXPECT_EQ(chunks.size(), 4u); + + // Skip the root chunk to get to the first div, which should no longer have + // a recorded value. + ++chunk_iterator; + EXPECT_FALSE(chunk_iterator->layer_selection_data); + + // Validate start/end in second div. + ++chunk_iterator; + EXPECT_TRUE(chunk_iterator->layer_selection_data->start.has_value()); + EXPECT_TRUE(chunk_iterator->layer_selection_data->end.has_value()); + start = chunk_iterator->layer_selection_data->start.value(); + EXPECT_EQ(start.type, gfx::SelectionBound::LEFT); + EXPECT_EQ(start.edge_start, IntPoint(0, 0)); + EXPECT_EQ(start.edge_end, IntPoint(0, 10)); + + end = chunk_iterator->layer_selection_data->end.value(); + EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT); + EXPECT_EQ(end.edge_start, IntPoint(30, 0)); + EXPECT_EQ(end.edge_end, IntPoint(30, 10)); + + // Third div's chunk should no longer have an end value. + ++chunk_iterator; + EXPECT_FALSE(chunk_iterator->layer_selection_data); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/svg_container_painter_test.cc b/chromium/third_party/blink/renderer/core/paint/svg_container_painter_test.cc index 80d94a1b198..fbe8b7ad23e 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_container_painter_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_container_painter_test.cc @@ -23,7 +23,6 @@ using SVGContainerPainterTest = PaintControllerPaintTest; INSTANTIATE_PAINT_TEST_SUITE_P(SVGContainerPainterTest); TEST_P(SVGContainerPainterTest, FilterPaintProperties) { - ScopedCompositeSVGForTest enable_feature(true); SetBodyInnerHTML(R"HTML( <style> #container, #before, #after { will-change: transform; } diff --git a/chromium/third_party/blink/renderer/core/paint/svg_image_painter.cc b/chromium/third_party/blink/renderer/core/paint/svg_image_painter.cc index 0f1f5c9970d..944feb5e496 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_image_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_image_painter.cc @@ -87,10 +87,9 @@ void SVGImagePainter::PaintForeground(const PaintInfo& paint_info) { layout_svg_image_.StyleRef().GetInterpolationQuality()); Image::ImageDecodingMode decode_mode = image_element->GetDecodingModeForPainting(image->paint_image_id()); - paint_info.context.DrawImage( - image.get(), decode_mode, dest_rect, &src_rect, - layout_svg_image_.StyleRef().HasFilterInducingProperty(), - SkBlendMode::kSrcOver, respect_orientation); + paint_info.context.DrawImage(image.get(), decode_mode, dest_rect, &src_rect, + layout_svg_image_.StyleRef().DisableForceDark(), + SkBlendMode::kSrcOver, respect_orientation); ImageResourceContent* image_content = image_resource.CachedImage(); if (image_content->IsLoaded()) { diff --git a/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc b/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc index 430e7873c4c..c4ec5e29300 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc @@ -5,6 +5,8 @@ #include "third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h" #include <memory> + +#include "base/stl_util.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/editing/editor.h" #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" @@ -379,9 +381,12 @@ void SVGInlineTextBoxPainter::PaintDecoration(const PaintInfo& paint_info, if (decoration_style.HasFill()) { PaintFlags fill_flags; if (!SVGObjectPainter(*decoration_layout_object) - .PreparePaint(paint_info, decoration_style, kApplyToFillMode, - fill_flags)) + .PreparePaint(paint_info.context, + paint_info.IsRenderingClipPathAsMaskImage(), + decoration_style, kApplyToFillMode, + fill_flags)) { break; + } fill_flags.setAntiAlias(true); paint_info.context.DrawPath(path.GetSkPath(), fill_flags); } @@ -390,9 +395,12 @@ void SVGInlineTextBoxPainter::PaintDecoration(const PaintInfo& paint_info, if (decoration_style.HasVisibleStroke()) { PaintFlags stroke_flags; if (!SVGObjectPainter(*decoration_layout_object) - .PreparePaint(paint_info, decoration_style, - kApplyToStrokeMode, stroke_flags)) + .PreparePaint(paint_info.context, + paint_info.IsRenderingClipPathAsMaskImage(), + decoration_style, kApplyToStrokeMode, + stroke_flags)) { break; + } stroke_flags.setAntiAlias(true); float stroke_scale_factor = decoration_style.VectorEffect() == EVectorEffect::kNonScalingStroke @@ -441,9 +449,13 @@ bool SVGInlineTextBoxPainter::SetupTextPaint( } if (!SVGObjectPainter(ParentInlineLayoutObject()) - .PreparePaint(paint_info, style, resource_mode, flags, - base::OptionalOrNullptr(paint_server_transform))) + .PreparePaint(paint_info.context, + paint_info.IsRenderingClipPathAsMaskImage(), style, + resource_mode, flags, + base::OptionalOrNullptr(paint_server_transform))) { return false; + } + flags.setAntiAlias(true); if (style.TextShadow() && @@ -677,11 +689,7 @@ void SVGInlineTextBoxPainter::PaintTextMarkerForeground( return; Color text_color = LayoutTheme::GetTheme().PlatformTextSearchColor( - marker.IsActiveMatch(), - svg_inline_text_box_.GetLineLayoutItem() - .GetDocument() - .InForcedColorsMode(), - style.UsedColorScheme()); + marker.IsActiveMatch(), style.UsedColorScheme()); PaintFlags fill_flags; fill_flags.setColor(text_color.Rgb()); @@ -724,11 +732,7 @@ void SVGInlineTextBoxPainter::PaintTextMarkerBackground( return; Color color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor( - marker.IsActiveMatch(), - svg_inline_text_box_.GetLineLayoutItem() - .GetDocument() - .InForcedColorsMode(), - style.UsedColorScheme()); + marker.IsActiveMatch(), style.UsedColorScheme()); for (const SVGTextFragmentWithRange& text_match_info : text_match_info_list) { const SVGTextFragment& fragment = text_match_info.fragment; diff --git a/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h b/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h index 6870ae22f33..865ffad3cdc 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_INLINE_TEXT_BOX_PAINTER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_INLINE_TEXT_BOX_PAINTER_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/paint/svg_object_painter.h" #include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" diff --git a/chromium/third_party/blink/renderer/core/paint/svg_mask_painter.cc b/chromium/third_party/blink/renderer/core/paint/svg_mask_painter.cc index 72f23f43e38..893109d449b 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_mask_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_mask_painter.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/paint/svg_mask_painter.h" +#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h" #include "third_party/blink/renderer/core/layout/svg/svg_resources.h" #include "third_party/blink/renderer/core/paint/object_paint_properties.h" @@ -46,7 +47,9 @@ void SVGMaskPainter::Paint(GraphicsContext& context, auto* masker = GetSVGResourceAsType<LayoutSVGResourceMasker>( *client, style.MaskerResource()); DCHECK(masker); - SECURITY_DCHECK(!masker->NeedsLayout()); + if (DisplayLockUtilities::LockedAncestorPreventingLayout(*masker)) + return; + SECURITY_DCHECK(!masker->SelfNeedsLayout()); masker->ClearInvalidationMask(); FloatRect reference_box = SVGResources::ReferenceBoxForEffects(layout_object); diff --git a/chromium/third_party/blink/renderer/core/paint/svg_object_painter.cc b/chromium/third_party/blink/renderer/core/paint/svg_object_painter.cc index acd0c2d1623..f675b25f63d 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_object_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_object_painter.cc @@ -14,7 +14,8 @@ namespace blink { namespace { -void CopyStateFromGraphicsContext(GraphicsContext& context, PaintFlags& flags) { +void CopyStateFromGraphicsContext(const GraphicsContext& context, + PaintFlags& flags) { // TODO(fs): The color filter can be set when generating a picture for a mask // due to color-interpolation. We could also just apply the // color-interpolation property from the the shape itself (which could mean @@ -31,7 +32,7 @@ void CopyStateFromGraphicsContext(GraphicsContext& context, PaintFlags& flags) { } // namespace void SVGObjectPainter::PaintResourceSubtree(GraphicsContext& context) { - DCHECK(!layout_object_.NeedsLayout()); + DCHECK(!layout_object_.SelfNeedsLayout()); PaintInfo info(context, CullRect::Infinite(), PaintPhase::kForeground, kGlobalPaintNormalPhase | kGlobalPaintFlattenCompositingLayers, @@ -57,12 +58,13 @@ bool SVGObjectPainter::ApplyPaintResource( } bool SVGObjectPainter::PreparePaint( - const PaintInfo& paint_info, + const GraphicsContext& context, + bool is_rendering_clip_path_as_mask_image, const ComputedStyle& style, LayoutSVGResourceMode resource_mode, PaintFlags& flags, const AffineTransform* additional_paint_server_transform) { - if (paint_info.IsRenderingClipPathAsMaskImage()) { + if (is_rendering_clip_path_as_mask_image) { if (resource_mode == kApplyToStrokeMode) return false; flags.setColor(SK_ColorBLACK); @@ -78,7 +80,7 @@ bool SVGObjectPainter::PreparePaint( if (paint.HasUrl()) { if (ApplyPaintResource(paint, additional_paint_server_transform, flags)) { flags.setColor(ScaleAlpha(SK_ColorBLACK, alpha)); - CopyStateFromGraphicsContext(paint_info.context, flags); + CopyStateFromGraphicsContext(context, flags); return true; } } @@ -88,7 +90,7 @@ bool SVGObjectPainter::PreparePaint( const Color color = style.VisitedDependentColor(property); flags.setColor(ScaleAlpha(color.Rgb(), alpha)); flags.setShader(nullptr); - CopyStateFromGraphicsContext(paint_info.context, flags); + CopyStateFromGraphicsContext(context, flags); return true; } return false; diff --git a/chromium/third_party/blink/renderer/core/paint/svg_object_painter.h b/chromium/third_party/blink/renderer/core/paint/svg_object_painter.h index 2be9fa3f5c2..a45b590e5be 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_object_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/svg_object_painter.h @@ -11,7 +11,6 @@ namespace blink { -struct PaintInfo; class AffineTransform; class ComputedStyle; class GraphicsContext; @@ -34,7 +33,8 @@ class SVGObjectPainter { // object. Returns true if successful, and the caller can continue to paint // using |paint_flags|. bool PreparePaint( - const PaintInfo&, + const GraphicsContext& context, + bool is_rendering_clip_path_as_mask_image, const ComputedStyle&, LayoutSVGResourceMode, PaintFlags& paint_flags, diff --git a/chromium/third_party/blink/renderer/core/paint/svg_shape_painter.cc b/chromium/third_party/blink/renderer/core/paint/svg_shape_painter.cc index fc56de03b3d..252cb9ae20d 100644 --- a/chromium/third_party/blink/renderer/core/paint/svg_shape_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/svg_shape_painter.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/paint/svg_shape_painter.h" +#include "base/stl_util.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_shape.h" #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h" @@ -74,9 +75,11 @@ void SVGShapePainter::Paint(const PaintInfo& paint_info) { case PT_FILL: { PaintFlags fill_flags; if (!SVGObjectPainter(layout_svg_shape_) - .PreparePaint(paint_info, style, kApplyToFillMode, - fill_flags)) + .PreparePaint(paint_info.context, + paint_info.IsRenderingClipPathAsMaskImage(), + style, kApplyToFillMode, fill_flags)) { break; + } fill_flags.setAntiAlias(should_anti_alias); FillShape(paint_info.context, fill_flags, FillRuleFromStyle(paint_info, style)); @@ -99,9 +102,12 @@ void SVGShapePainter::Paint(const PaintInfo& paint_info) { PaintFlags stroke_flags; if (!SVGObjectPainter(layout_svg_shape_) .PreparePaint( - paint_info, style, kApplyToStrokeMode, stroke_flags, - base::OptionalOrNullptr(non_scaling_transform))) + paint_info.context, + paint_info.IsRenderingClipPathAsMaskImage(), style, + kApplyToStrokeMode, stroke_flags, + base::OptionalOrNullptr(non_scaling_transform))) { break; + } stroke_flags.setAntiAlias(should_anti_alias); StrokeData stroke_data; diff --git a/chromium/third_party/blink/renderer/core/paint/table_paint_invalidator.cc b/chromium/third_party/blink/renderer/core/paint/table_paint_invalidator.cc index fb7125dee16..00889fb2825 100644 --- a/chromium/third_party/blink/renderer/core/paint/table_paint_invalidator.cc +++ b/chromium/third_party/blink/renderer/core/paint/table_paint_invalidator.cc @@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/layout/layout_table_row.h" #include "third_party/blink/renderer/core/layout/layout_table_section.h" #include "third_party/blink/renderer/core/paint/box_paint_invalidator.h" +#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_invalidator.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/core/paint/table_section_painter.cc b/chromium/third_party/blink/renderer/core/paint/table_section_painter.cc index 9c099775574..886e3ccdf21 100644 --- a/chromium/third_party/blink/renderer/core/paint/table_section_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/table_section_painter.cc @@ -48,8 +48,7 @@ void TableSectionPainter::Paint(const PaintInfo& paint_info) { for (const auto* fragment = &layout_table_section_.FirstFragment(); fragment; fragment = fragment->NextFragment()) { PaintInfo fragment_paint_info = paint_info; - fragment_paint_info.SetFragmentLogicalTopInFlowThread( - fragment->LogicalTopInFlowThread()); + fragment_paint_info.SetFragmentID(fragment->FragmentID()); ScopedDisplayItemFragment scoped_display_item_fragment( fragment_paint_info.context, fragment_index++); PaintSection(fragment_paint_info); @@ -106,11 +105,13 @@ void TableSectionPainter::PaintCollapsedBorders(const PaintInfo& paint_info) { return; } + unsigned fragment_index = 0; for (const auto* fragment = &layout_table_section_.FirstFragment(); fragment; fragment = fragment->NextFragment()) { PaintInfo fragment_paint_info = paint_info; - fragment_paint_info.SetFragmentLogicalTopInFlowThread( - fragment->LogicalTopInFlowThread()); + fragment_paint_info.SetFragmentID(fragment->FragmentID()); + ScopedDisplayItemFragment scoped_display_item_fragment( + fragment_paint_info.context, fragment_index++); PaintCollapsedSectionBorders(fragment_paint_info); } } diff --git a/chromium/third_party/blink/renderer/core/paint/text_decoration_info.cc b/chromium/third_party/blink/renderer/core/paint/text_decoration_info.cc index 3409e759e1b..31983132cb7 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_decoration_info.cc +++ b/chromium/third_party/blink/renderer/core/paint/text_decoration_info.cc @@ -28,7 +28,7 @@ static ResolvedUnderlinePosition ResolveUnderlinePosition( if (style.TextUnderlinePosition() & kTextUnderlinePositionFromFont) return ResolvedUnderlinePosition::kNearAlphabeticBaselineFromFont; return ResolvedUnderlinePosition::kNearAlphabeticBaselineAuto; - case kIdeographicBaseline: + case kCentralBaseline: { // Compute language-appropriate default underline position. // https://drafts.csswg.org/css-text-decor-3/#default-stylesheet UScriptCode script = style.GetFontDescription().GetScript(); @@ -42,6 +42,10 @@ static ResolvedUnderlinePosition ResolveUnderlinePosition( return ResolvedUnderlinePosition::kOver; } return ResolvedUnderlinePosition::kUnder; + } + default: + NOTREACHED(); + break; } NOTREACHED(); return ResolvedUnderlinePosition::kNearAlphabeticBaselineAuto; @@ -154,7 +158,6 @@ static int TextDecorationToLineDataIndex(TextDecoration line) { } // anonymous namespace TextDecorationInfo::TextDecorationInfo( - const PhysicalOffset& box_origin, PhysicalOffset local_origin, LayoutUnit width, FontBaseline baseline_type, diff --git a/chromium/third_party/blink/renderer/core/paint/text_decoration_info.h b/chromium/third_party/blink/renderer/core/paint/text_decoration_info.h index 1973942f40b..be9d2e63831 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_decoration_info.h +++ b/chromium/third_party/blink/renderer/core/paint/text_decoration_info.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_DECORATION_INFO_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_DECORATION_INFO_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" #include "third_party/blink/renderer/core/paint/text_paint_style.h" @@ -37,7 +38,6 @@ class CORE_EXPORT TextDecorationInfo { public: TextDecorationInfo( - const PhysicalOffset& box_origin, PhysicalOffset local_origin, LayoutUnit width, FontBaseline baseline_type, diff --git a/chromium/third_party/blink/renderer/core/paint/text_paint_style.h b/chromium/third_party/blink/renderer/core/paint/text_paint_style.h index 427f81b1f55..33fb74c26af 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_paint_style.h +++ b/chromium/third_party/blink/renderer/core/paint/text_paint_style.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_PAINT_STYLE_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_PAINT_STYLE_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/style/applied_text_decoration.h" diff --git a/chromium/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc b/chromium/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc index 63fc7b0ed8b..14a8233ba68 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc +++ b/chromium/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc @@ -85,7 +85,8 @@ class TextPaintTimingDetectorTest : public testing::Test { wtf_size_t CountRankingSetSize() { DCHECK(GetTextPaintTimingDetector()); - return GetLargestTextPaintManager()->size_ordered_set_.size(); + return static_cast<wtf_size_t>( + GetLargestTextPaintManager()->size_ordered_set_.size()); } wtf_size_t CountInvisibleTexts() { @@ -326,10 +327,10 @@ TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_TraceEvent_Candidate) { EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId node_id; EXPECT_TRUE(arg_dict->GetInteger("DOMNodeId", &node_id)); EXPECT_GT(node_id, 0); @@ -395,10 +396,10 @@ TEST_F(TextPaintTimingDetectorTest, EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId node_id; EXPECT_TRUE(arg_dict->GetInteger("DOMNodeId", &node_id)); EXPECT_GT(node_id, 0); @@ -463,10 +464,10 @@ TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_TraceEvent_NoCandidate) { EXPECT_TRUE(events[0]->HasArg("frame")); EXPECT_TRUE(events[0]->HasArg("data")); - std::unique_ptr<base::Value> arg; + base::Value arg; EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); base::DictionaryValue* arg_dict; - EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + EXPECT_TRUE(arg.GetAsDictionary(&arg_dict)); DOMNodeId candidate_index; EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index)); EXPECT_EQ(candidate_index, 2); diff --git a/chromium/third_party/blink/renderer/core/paint/text_painter.cc b/chromium/third_party/blink/renderer/core/paint/text_painter.cc index f8379c71dd4..77e220b71b1 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/text_painter.cc @@ -44,12 +44,15 @@ void TextPainter::Paint(unsigned start_offset, } if (!emphasis_mark_.IsEmpty()) { - if (text_style.emphasis_mark_color != text_style.fill_color) - graphics_context_.SetFillColor(text_style.emphasis_mark_color); - if (combined_text_) { - PaintEmphasisMarkForCombinedText(); + graphics_context_.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); + PaintEmphasisMarkForCombinedText(text_style, + combined_text_->OriginalFont()); + graphics_context_.ConcatCTM( + Rotation(text_frame_rect_, kCounterclockwise)); } else { + if (text_style.emphasis_mark_color != text_style.fill_color) + graphics_context_.SetFillColor(text_style.emphasis_mark_color); PaintInternal<kPaintEmphasisMark>(start_offset, end_offset, length, node_id); } @@ -67,7 +70,7 @@ void TextPainter::PaintDecorationsExceptLineThrough( GraphicsContextStateSaver state_saver(context); UpdateGraphicsContext(context, text_style, horizontal_, state_saver); - if (has_combined_text_) + if (combined_text_) context.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); // text-underline-position may flip underline and overline. @@ -79,7 +82,7 @@ void TextPainter::PaintDecorationsExceptLineThrough( underline_position = ResolvedUnderlinePosition::kUnder; } - for (size_t applied_decoration_index = 0; + for (wtf_size_t applied_decoration_index = 0; applied_decoration_index < decorations.size(); ++applied_decoration_index) { const AppliedTextDecoration& decoration = @@ -139,7 +142,7 @@ void TextPainter::PaintDecorationsExceptLineThrough( } // Restore rotation as needed. - if (has_combined_text_) + if (combined_text_) context.ConcatCTM(Rotation(text_frame_rect_, kCounterclockwise)); } @@ -152,10 +155,10 @@ void TextPainter::PaintDecorationsOnlyLineThrough( GraphicsContextStateSaver state_saver(context); UpdateGraphicsContext(context, text_style, horizontal_, state_saver); - if (has_combined_text_) + if (combined_text_) context.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); - for (size_t applied_decoration_index = 0; + for (wtf_size_t applied_decoration_index = 0; applied_decoration_index < decorations.size(); ++applied_decoration_index) { const AppliedTextDecoration& decoration = @@ -190,7 +193,7 @@ void TextPainter::PaintDecorationsOnlyLineThrough( } // Restore rotation as needed. - if (has_combined_text_) + if (combined_text_) context.ConcatCTM(Rotation(text_frame_rect_, kCounterclockwise)); } @@ -253,24 +256,4 @@ void TextPainter::ClipDecorationsStripe(float upper, DecorationsStripeIntercepts(upper, stripe_width, dilation, text_intercepts); } -void TextPainter::PaintEmphasisMarkForCombinedText() { - const SimpleFontData* font_data = font_.PrimaryFont(); - DCHECK(font_data); - if (!font_data) - return; - - DCHECK(combined_text_); - TextRun placeholder_text_run(&kIdeographicFullStopCharacter, 1); - FloatPoint emphasis_mark_text_origin( - text_frame_rect_.X().ToFloat(), text_frame_rect_.Y().ToFloat() + - font_data->GetFontMetrics().Ascent() + - emphasis_mark_offset_); - TextRunPaintInfo text_run_paint_info(placeholder_text_run); - graphics_context_.ConcatCTM(Rotation(text_frame_rect_, kClockwise)); - graphics_context_.DrawEmphasisMarks(combined_text_->OriginalFont(), - text_run_paint_info, emphasis_mark_, - emphasis_mark_text_origin); - graphics_context_.ConcatCTM(Rotation(text_frame_rect_, kCounterclockwise)); -} - } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/text_painter.h b/chromium/third_party/blink/renderer/core/paint/text_painter.h index 423743444e0..232c67d496b 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_painter.h +++ b/chromium/third_party/blink/renderer/core/paint/text_painter.h @@ -32,13 +32,11 @@ class CORE_EXPORT TextPainter : public TextPainterBase { text_origin, text_frame_rect, horizontal), - run_(run), - combined_text_(nullptr) {} + run_(run) {} ~TextPainter() = default; void SetCombinedText(LayoutTextCombine* combined_text) { combined_text_ = combined_text; - has_combined_text_ = combined_text_ ? true : false; } void ClipDecorationsStripe(float upper, @@ -74,10 +72,8 @@ class CORE_EXPORT TextPainter : public TextPainterBase { unsigned truncation_point, DOMNodeId node_id); - void PaintEmphasisMarkForCombinedText(); - const TextRun& run_; - LayoutTextCombine* combined_text_; + LayoutTextCombine* combined_text_ = nullptr; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/text_painter_base.cc b/chromium/third_party/blink/renderer/core/paint/text_painter_base.cc index 74463be4405..18107374fc3 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_painter_base.cc +++ b/chromium/third_party/blink/renderer/core/paint/text_painter_base.cc @@ -13,8 +13,8 @@ #include "third_party/blink/renderer/core/paint/text_decoration_info.h" #include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/core/style/shadow_list.h" -#include "third_party/blink/renderer/core/svg/svg_length_context.h" #include "third_party/blink/renderer/platform/fonts/font.h" +#include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h" #include "third_party/blink/renderer/platform/geometry/length_functions.h" #include "third_party/blink/renderer/platform/graphics/graphics_context.h" #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" @@ -42,10 +42,7 @@ TextPainterBase::TextPainterBase(GraphicsContext& context, font_(font), text_origin_(text_origin), text_frame_rect_(text_frame_rect), - horizontal_(horizontal), - has_combined_text_(false), - emphasis_mark_offset_(0), - ellipsis_offset_(0) {} + horizontal_(horizontal) {} TextPainterBase::~TextPainterBase() = default; @@ -74,34 +71,6 @@ void TextPainterBase::SetEmphasisMark(const AtomicString& emphasis_mark, } // static -void TextPainterBase::AdjustTextStyleForClip(TextPaintStyle& text_style) { - // When we use the text as a clip, we only care about the alpha, thus we - // make all the colors black. - text_style.current_color = Color::kBlack; - text_style.fill_color = Color::kBlack; - text_style.stroke_color = Color::kBlack; - text_style.emphasis_mark_color = Color::kBlack; - text_style.shadow = nullptr; -} - -// static -void TextPainterBase::AdjustTextStyleForPrint(const Document& document, - const ComputedStyle& style, - TextPaintStyle& text_style) { - // Adjust text color when printing with a white background. - bool force_background_to_white = - BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(document, - style); - if (force_background_to_white) { - text_style.fill_color = TextColorForWhiteBackground(text_style.fill_color); - text_style.stroke_color = - TextColorForWhiteBackground(text_style.stroke_color); - text_style.emphasis_mark_color = - TextColorForWhiteBackground(text_style.emphasis_mark_color); - } -} - -// static void TextPainterBase::UpdateGraphicsContext( GraphicsContext& context, const TextPaintStyle& text_style, @@ -188,7 +157,13 @@ TextPaintStyle TextPainterBase::TextPaintingStyle(const Document& document, text_style.color_scheme = style.UsedColorScheme(); if (paint_info.phase == PaintPhase::kTextClip) { - AdjustTextStyleForClip(text_style); + // When we use the text as a clip, we only care about the alpha, thus we + // make all the colors black. + text_style.current_color = Color::kBlack; + text_style.fill_color = Color::kBlack; + text_style.stroke_color = Color::kBlack; + text_style.emphasis_mark_color = Color::kBlack; + text_style.shadow = nullptr; } else { text_style.current_color = style.VisitedDependentColor(GetCSSPropertyColor()); @@ -200,54 +175,18 @@ TextPaintStyle TextPainterBase::TextPaintingStyle(const Document& document, style.VisitedDependentColor(GetCSSPropertyWebkitTextEmphasisColor()); text_style.shadow = style.TextShadow(); - AdjustTextStyleForPrint(document, style, text_style); - } - - return text_style; -} - -// static -TextPaintStyle TextPainterBase::SvgTextPaintingStyle( - const Document& document, - const SVGLengthContext& length_context, - const ComputedStyle& style, - const PaintInfo& paint_info) { - TextPaintStyle text_style; - text_style.stroke_width = - style.HasStroke() ? length_context.ValueForLength(style.StrokeWidth()) - : 0; - text_style.color_scheme = style.UsedColorScheme(); - - if (paint_info.phase == PaintPhase::kTextClip) { - AdjustTextStyleForClip(text_style); - } else { - text_style.current_color = - style.VisitedDependentColor(GetCSSPropertyColor()); - - const SVGPaint fill_paint = style.FillPaint(); - if (fill_paint.IsNone()) { - text_style.fill_color = Color::kTransparent; - } else if (fill_paint.HasColor()) { - const Color color = style.VisitedDependentColor(GetCSSPropertyFill()); - const float alpha = style.FillOpacity(); - text_style.fill_color = ScaleAlpha(color.Rgb(), alpha); - } else { - text_style.fill_color = Color::kBlack; + // Adjust text color when printing with a white background. + bool force_background_to_white = + BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(document, + style); + if (force_background_to_white) { + text_style.fill_color = + TextColorForWhiteBackground(text_style.fill_color); + text_style.stroke_color = + TextColorForWhiteBackground(text_style.stroke_color); + text_style.emphasis_mark_color = + TextColorForWhiteBackground(text_style.emphasis_mark_color); } - - if (style.StrokePaint().HasColor()) { - const Color color = style.VisitedDependentColor(GetCSSPropertyStroke()); - const float alpha = style.StrokeOpacity(); - text_style.stroke_color = ScaleAlpha(color.Rgb(), alpha); - } else { - text_style.stroke_color = Color::kTransparent; - } - - text_style.emphasis_mark_color = - style.VisitedDependentColor(GetCSSPropertyWebkitTextEmphasisColor()); - text_style.shadow = style.TextShadow(); - - AdjustTextStyleForPrint(document, style, text_style); } return text_style; @@ -289,6 +228,130 @@ void TextPainterBase::DecorationsStripeIntercepts( } } +void TextPainterBase::PaintDecorationsExceptLineThrough( + const TextDecorationOffsetBase& decoration_offset, + TextDecorationInfo& decoration_info, + const PaintInfo& paint_info, + const Vector<AppliedTextDecoration>& decorations, + const TextPaintStyle& text_style, + bool* has_line_through_decoration) { + GraphicsContext& context = paint_info.context; + GraphicsContextStateSaver state_saver(context); + UpdateGraphicsContext(context, text_style, horizontal_, state_saver); + + // text-underline-position may flip underline and overline. + ResolvedUnderlinePosition underline_position = + decoration_info.UnderlinePosition(); + bool flip_underline_and_overline = false; + if (underline_position == ResolvedUnderlinePosition::kOver) { + flip_underline_and_overline = true; + underline_position = ResolvedUnderlinePosition::kUnder; + } + + for (wtf_size_t applied_decoration_index = 0; + applied_decoration_index < decorations.size(); + ++applied_decoration_index) { + const AppliedTextDecoration& decoration = + decorations[applied_decoration_index]; + TextDecoration lines = decoration.Lines(); + bool has_underline = EnumHasFlags(lines, TextDecoration::kUnderline); + bool has_overline = EnumHasFlags(lines, TextDecoration::kOverline); + if (flip_underline_and_overline) + std::swap(has_underline, has_overline); + + decoration_info.SetDecorationIndex(applied_decoration_index); + + float resolved_thickness = decoration_info.ResolvedThickness(); + context.SetStrokeThickness(resolved_thickness); + + if (has_underline && decoration_info.FontData()) { + // Don't apply text-underline-offset to overline. + Length line_offset = + flip_underline_and_overline ? Length() : decoration.UnderlineOffset(); + + const int paint_underline_offset = + decoration_offset.ComputeUnderlineOffset( + underline_position, decoration_info.Style().ComputedFontSize(), + decoration_info.FontData()->GetFontMetrics(), line_offset, + resolved_thickness); + decoration_info.SetPerLineData( + TextDecoration::kUnderline, paint_underline_offset, + TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness), 1); + PaintDecorationUnderOrOverLine(context, decoration_info, + TextDecoration::kUnderline); + } + + if (has_overline && decoration_info.FontData()) { + // Don't apply text-underline-offset to overline. + Length line_offset = + flip_underline_and_overline ? decoration.UnderlineOffset() : Length(); + + FontVerticalPositionType position = + flip_underline_and_overline ? FontVerticalPositionType::TopOfEmHeight + : FontVerticalPositionType::TextTop; + const int paint_overline_offset = + decoration_offset.ComputeUnderlineOffsetForUnder( + line_offset, decoration_info.Style().ComputedFontSize(), + resolved_thickness, position); + decoration_info.SetPerLineData( + TextDecoration::kOverline, paint_overline_offset, + -TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness), + 1); + PaintDecorationUnderOrOverLine(context, decoration_info, + TextDecoration::kOverline); + } + + // We could instead build a vector of the TextDecoration instances needing + // line-through but this is a rare case so better to avoid vector overhead. + *has_line_through_decoration |= + EnumHasFlags(lines, TextDecoration::kLineThrough); + } +} + +void TextPainterBase::PaintDecorationsOnlyLineThrough( + TextDecorationInfo& decoration_info, + const PaintInfo& paint_info, + const Vector<AppliedTextDecoration>& decorations, + const TextPaintStyle& text_style) { + GraphicsContext& context = paint_info.context; + GraphicsContextStateSaver state_saver(context); + UpdateGraphicsContext(context, text_style, horizontal_, state_saver); + + for (wtf_size_t applied_decoration_index = 0; + applied_decoration_index < decorations.size(); + ++applied_decoration_index) { + const AppliedTextDecoration& decoration = + decorations[applied_decoration_index]; + TextDecoration lines = decoration.Lines(); + if (EnumHasFlags(lines, TextDecoration::kLineThrough)) { + decoration_info.SetDecorationIndex(applied_decoration_index); + + float resolved_thickness = decoration_info.ResolvedThickness(); + context.SetStrokeThickness(resolved_thickness); + + // For increased line thickness, the line-through decoration needs to grow + // in both directions from its origin, subtract half the thickness to keep + // it centered at the same origin. + const float line_through_offset = + 2 * decoration_info.Baseline() / 3 - resolved_thickness / 2; + // Floor double_offset in order to avoid double-line gap to appear + // of different size depending on position where the double line + // is drawn because of rounding downstream in + // GraphicsContext::DrawLineForText. + decoration_info.SetPerLineData( + TextDecoration::kLineThrough, line_through_offset, + floorf(TextDecorationInfo::DoubleOffsetFromThickness( + resolved_thickness)), + 0); + AppliedDecorationPainter decoration_painter(context, decoration_info, + TextDecoration::kLineThrough); + // No skip: ink for line-through, + // compare https://github.com/w3c/csswg-drafts/issues/711 + decoration_painter.Paint(); + } + } +} + void TextPainterBase::PaintDecorationUnderOrOverLine( GraphicsContext& context, TextDecorationInfo& decoration_info, @@ -308,4 +371,30 @@ void TextPainterBase::PaintDecorationUnderOrOverLine( decoration_painter.Paint(); } +void TextPainterBase::PaintEmphasisMarkForCombinedText( + const TextPaintStyle& text_style, + const Font& emphasis_mark_font) { + DCHECK(emphasis_mark_font.GetFontDescription().IsVerticalBaseline()); + DCHECK(emphasis_mark_); + const SimpleFontData* const font_data = font_.PrimaryFont(); + DCHECK(font_data); + if (!font_data) + return; + + if (text_style.emphasis_mark_color != text_style.fill_color) { + // See virtual/text-antialias/emphasis-combined-text.html + graphics_context_.SetFillColor(text_style.emphasis_mark_color); + } + + const auto font_ascent = font_data->GetFontMetrics().Ascent(); + const TextRun placeholder_text_run(&kIdeographicFullStopCharacter, 1); + const FloatPoint emphasis_mark_text_origin( + text_frame_rect_.X().ToFloat(), + text_frame_rect_.Y().ToFloat() + font_ascent + emphasis_mark_offset_); + const TextRunPaintInfo text_run_paint_info(placeholder_text_run); + graphics_context_.DrawEmphasisMarks(emphasis_mark_font, text_run_paint_info, + emphasis_mark_, + emphasis_mark_text_origin); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/text_painter_base.h b/chromium/third_party/blink/renderer/core/paint/text_painter_base.h index 35271337b3f..bba7e4bb175 100644 --- a/chromium/third_party/blink/renderer/core/paint/text_painter_base.h +++ b/chromium/third_party/blink/renderer/core/paint/text_painter_base.h @@ -25,7 +25,7 @@ class Document; class GraphicsContext; class GraphicsContextStateSaver; class Node; -class SVGLengthContext; +class TextDecorationOffsetBase; struct PaintInfo; // Base class for text painting. Has no dependencies on the layout tree and thus @@ -71,10 +71,6 @@ class CORE_EXPORT TextPainterBase { static TextPaintStyle TextPaintingStyle(const Document&, const ComputedStyle&, const PaintInfo&); - static TextPaintStyle SvgTextPaintingStyle(const Document&, - const SVGLengthContext&, - const ComputedStyle&, - const PaintInfo&); static TextPaintStyle SelectionPaintingStyle( const Document&, const ComputedStyle&, @@ -85,12 +81,9 @@ class CORE_EXPORT TextPainterBase { enum RotationDirection { kCounterclockwise, kClockwise }; static AffineTransform Rotation(const PhysicalRect& box_rect, RotationDirection); + static AffineTransform Rotation(const PhysicalRect& box_rect, WritingMode); protected: - static void AdjustTextStyleForClip(TextPaintStyle&); - static void AdjustTextStyleForPrint(const Document&, - const ComputedStyle&, - TextPaintStyle&); void UpdateGraphicsContext(const TextPaintStyle& style, GraphicsContextStateSaver& saver) { UpdateGraphicsContext(graphics_context_, style, horizontal_, saver); @@ -101,17 +94,42 @@ class CORE_EXPORT TextPainterBase { float dilation, const Vector<Font::TextIntercept>& text_intercepts); + // We have two functions to paint text decoations, because we should paint + // text and decorations in following order: + // 1. Paint text decorations except line through + // 2. Paint text + // 3. Paint line throguh + void PaintDecorationsExceptLineThrough(const TextDecorationOffsetBase&, + TextDecorationInfo&, + const PaintInfo&, + const Vector<AppliedTextDecoration>&, + const TextPaintStyle& text_style, + bool* has_line_through_decoration); + void PaintDecorationsOnlyLineThrough(TextDecorationInfo&, + const PaintInfo&, + const Vector<AppliedTextDecoration>&, + const TextPaintStyle&); + + // Paints emphasis mark as for ideographic full stop character. Callers of + // this function should rotate canvas to paint emphasis mark at left/right + // side instead of top/bottom side. + // |emphasis_mark_font| is used for painting emphasis mark because |font_| + // may be compressed font (width variants). + // TODO(yosin): Once legacy inline layout gone, we should move this function + // to |NGTextCombinePainter|. + void PaintEmphasisMarkForCombinedText(const TextPaintStyle& text_style, + const Font& emphasis_mark_font); + enum PaintInternalStep { kPaintText, kPaintEmphasisMark }; GraphicsContext& graphics_context_; const Font& font_; - PhysicalOffset text_origin_; - PhysicalRect text_frame_rect_; - bool horizontal_; - bool has_combined_text_; + const PhysicalOffset text_origin_; + const PhysicalRect text_frame_rect_; AtomicString emphasis_mark_; - int emphasis_mark_offset_; - int ellipsis_offset_; + int emphasis_mark_offset_ = 0; + int ellipsis_offset_ = 0; + const bool horizontal_; }; inline AffineTransform TextPainterBase::Rotation( @@ -139,6 +157,13 @@ inline AffineTransform TextPainterBase::Rotation( box_rect.X() + box_rect.Bottom()); } +inline AffineTransform TextPainterBase::Rotation(const PhysicalRect& box_rect, + WritingMode writing_mode) { + return Rotation(box_rect, writing_mode != WritingMode::kSidewaysLr + ? TextPainterBase::kClockwise + : TextPainterBase::kCounterclockwise); +} + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_PAINTER_BASE_H_ diff --git a/chromium/third_party/blink/renderer/core/paint/theme_painter.cc b/chromium/third_party/blink/renderer/core/paint/theme_painter.cc index f228a00a533..9ef1c2346e8 100644 --- a/chromium/third_party/blink/renderer/core/paint/theme_painter.cc +++ b/chromium/third_party/blink/renderer/core/paint/theme_painter.cc @@ -23,6 +23,7 @@ #include "build/build_config.h" #include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/html/forms/html_data_list_element.h" #include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h" @@ -171,15 +172,9 @@ bool ThemePainter::Paint(const LayoutObject& o, case kMenulistButtonPart: return true; case kTextFieldPart: - if (!features::IsFormControlsRefreshEnabled()) { - return true; - } CountAppearanceTextFieldPart(element); return PaintTextField(element, style, paint_info, r); case kTextAreaPart: - if (!features::IsFormControlsRefreshEnabled()) { - return true; - } COUNT_APPEARANCE(doc, TextArea); return PaintTextArea(element, style, paint_info, r); case kSearchFieldPart: { @@ -211,17 +206,8 @@ bool ThemePainter::PaintBorderOnly(const Node* node, // Call the appropriate paint method based off the appearance value. switch (style.EffectiveAppearance()) { case kTextFieldPart: - if (features::IsFormControlsRefreshEnabled()) { - return false; - } - CountAppearanceTextFieldPart(element); - return PaintTextField(element, style, paint_info, r); case kTextAreaPart: - if (features::IsFormControlsRefreshEnabled()) { - return false; - } - COUNT_APPEARANCE(element.GetDocument(), TextArea); - return PaintTextArea(element, style, paint_info, r); + return false; case kMenulistButtonPart: case kSearchFieldPart: case kListboxPart: @@ -249,8 +235,6 @@ bool ThemePainter::PaintBorderOnly(const Node* node, // appearance values. return false; } - - return false; } bool ThemePainter::PaintDecorations(const Node* node, diff --git a/chromium/third_party/blink/renderer/core/paint/theme_painter_default.cc b/chromium/third_party/blink/renderer/core/paint/theme_painter_default.cc index 8832e863a1a..ed10b07cd5d 100644 --- a/chromium/third_party/blink/renderer/core/paint/theme_painter_default.cc +++ b/chromium/third_party/blink/renderer/core/paint/theme_painter_default.cc @@ -27,6 +27,7 @@ #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/web_theme_engine.h" #include "third_party/blink/public/resources/grit/blink_image_resources.h" +#include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/slider_thumb_element.h" @@ -187,10 +188,14 @@ bool ThemePainterDefault::PaintCheckbox(const Element& element, GraphicsContextStateSaver state_saver(paint_info.context, false); IntRect unzoomed_rect = ApplyZoomToRect(rect, paint_info, state_saver, zoom_level); + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartCheckbox, GetWebThemeState(element), - gfx::Rect(unzoomed_rect), &extra_params, style.UsedColorScheme(), + gfx::Rect(unzoomed_rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -209,13 +214,15 @@ bool ThemePainterDefault::PaintRadio(const Element& element, extra_params.button.zoom = zoom_level; GraphicsContextStateSaver state_saver(paint_info.context, false); IntRect unzoomed_rect = - features::IsFormControlsRefreshEnabled() - ? ApplyZoomToRect(rect, paint_info, state_saver, zoom_level) - : rect; + ApplyZoomToRect(rect, paint_info, state_saver, zoom_level); + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartRadio, GetWebThemeState(element), - gfx::Rect(unzoomed_rect), &extra_params, style.UsedColorScheme(), + gfx::Rect(unzoomed_rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -235,10 +242,14 @@ bool ThemePainterDefault::PaintButton(const Element& element, extra_params.button.background_color = style.VisitedDependentColor(GetCSSPropertyBackgroundColor()).Rgb(); } + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); + Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartButton, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, style.UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -251,15 +262,6 @@ bool ThemePainterDefault::PaintTextField(const Element& element, if (style.HasBorderRadius() || style.HasBackgroundImage()) return true; - // Don't use the theme painter if dark mode is enabled. It has a separate - // graphics pipeline that doesn't go through GraphicsContext and so does not - // currently know how to handle Dark Mode, causing elements to be rendered - // incorrectly (e.g. https://crbug.com/937872). - // TODO(gilmanmh): Implement a more permanent solution that allows use of - // native dark themes. - if (paint_info.context.IsDarkModeEnabled()) - return true; - ControlPart part = style.EffectiveAppearance(); WebThemeEngine::ExtraParams extra_params; @@ -276,10 +278,14 @@ bool ThemePainterDefault::PaintTextField(const Element& element, extra_params.text_field.auto_complete_active = DynamicTo<HTMLFormControlElement>(element)->IsAutofilled(); + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); + Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartTextField, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, style.UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -312,10 +318,14 @@ bool ThemePainterDefault::PaintMenuList(const Element& element, SetupMenuListArrow(document, style, rect, extra_params); cc::PaintCanvas* canvas = i.context.Canvas(); + bool enable_force_dark = + i.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); + Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartMenuList, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, style.UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -332,10 +342,14 @@ bool ThemePainterDefault::PaintMenuListButton(const Element& element, SetupMenuListArrow(document, style, rect, extra_params); cc::PaintCanvas* canvas = paint_info.context.Canvas(); + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); + Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartMenuList, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, style.UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -353,8 +367,7 @@ void ThemePainterDefault::SetupMenuListArrow( theme_.ClampedMenuListArrowPaddingSize(document.GetFrame(), style); float arrow_scale_factor = arrow_box_width / theme_.MenuListArrowWidthInDIP(); // TODO(tkent): This should be 7.0 to match scroll bar buttons. - float arrow_size = (features::IsFormControlsRefreshEnabled() ? 8.0 : 6.0) * - arrow_scale_factor; + float arrow_size = 8.0 * arrow_scale_factor; // Put the arrow at the center of paddingForArrow area. // |arrowX| is the left position for Aura theme engine. extra_params.menu_list.arrow_x = @@ -362,6 +375,8 @@ void ThemePainterDefault::SetupMenuListArrow( ? left + (arrow_box_width - arrow_size) / 2 : right - (arrow_box_width + arrow_size) / 2; extra_params.menu_list.arrow_size = arrow_size; + // TODO: (https://crbug.com/1227305)This color still does not support forced + // dark mode extra_params.menu_list.arrow_color = style.VisitedDependentColor(GetCSSPropertyColor()).Rgb(); } @@ -383,14 +398,6 @@ bool ThemePainterDefault::PaintSliderTrack(const Element& element, extra_params.slider.zoom = zoom_level; GraphicsContextStateSaver state_saver(i.context, false); IntRect unzoomed_rect = rect; - if (zoom_level != 1 && !features::IsFormControlsRefreshEnabled()) { - state_saver.Save(); - unzoomed_rect.SetWidth(unzoomed_rect.Width() / zoom_level); - unzoomed_rect.SetHeight(unzoomed_rect.Height() / zoom_level); - i.context.Translate(unzoomed_rect.X(), unzoomed_rect.Y()); - i.context.Scale(zoom_level, zoom_level); - i.context.Translate(-unzoomed_rect.X(), -unzoomed_rect.Y()); - } auto* input = DynamicTo<HTMLInputElement>(element); extra_params.slider.thumb_x = 0; @@ -405,29 +412,22 @@ bool ThemePainterDefault::PaintSliderTrack(const Element& element, LayoutBox* input_box = input->GetLayoutBox(); if (thumb) { IntRect thumb_rect = PixelSnappedIntRect(thumb->FrameRect()); - if (features::IsFormControlsRefreshEnabled()) { - extra_params.slider.thumb_x = thumb_rect.X() + - input_box->PaddingLeft().ToInt() + - input_box->BorderLeft().ToInt(); - extra_params.slider.thumb_y = thumb_rect.Y() + - input_box->PaddingTop().ToInt() + - input_box->BorderTop().ToInt(); - } else { - extra_params.slider.thumb_x = - (thumb_rect.X() + input_box->PaddingLeft().ToInt() + - input_box->BorderLeft().ToInt()) / - zoom_level; - extra_params.slider.thumb_y = - (thumb_rect.Y() + input_box->PaddingTop().ToInt() + - input_box->BorderTop().ToInt()) / - zoom_level; - } + extra_params.slider.thumb_x = thumb_rect.X() + + input_box->PaddingLeft().ToInt() + + input_box->BorderLeft().ToInt(); + extra_params.slider.thumb_y = thumb_rect.Y() + + input_box->PaddingTop().ToInt() + + input_box->BorderTop().ToInt(); } } + bool enable_force_dark = + i.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = o.StyleRef().UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartSliderTrack, GetWebThemeState(element), - gfx::Rect(unzoomed_rect), &extra_params, o.StyleRef().UsedColorScheme(), + gfx::Rect(unzoomed_rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -446,14 +446,6 @@ bool ThemePainterDefault::PaintSliderThumb(const Element& element, extra_params.slider.zoom = zoom_level; GraphicsContextStateSaver state_saver(paint_info.context, false); IntRect unzoomed_rect = rect; - if (zoom_level != 1 && !features::IsFormControlsRefreshEnabled()) { - state_saver.Save(); - unzoomed_rect.SetWidth(unzoomed_rect.Width() / zoom_level); - unzoomed_rect.SetHeight(unzoomed_rect.Height() / zoom_level); - paint_info.context.Translate(unzoomed_rect.X(), unzoomed_rect.Y()); - paint_info.context.Scale(zoom_level, zoom_level); - paint_info.context.Translate(-unzoomed_rect.X(), -unzoomed_rect.Y()); - } // The element passed in is inside the user agent shadowdom of the input // element, so we have to access the parent input element in order to get the @@ -464,11 +456,14 @@ bool ThemePainterDefault::PaintSliderThumb(const Element& element, // SliderThumbElement absl::optional<SkColor> accent_color = GetAccentColor(*slider_element->HostInput()->EnsureComputedStyle()); + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartSliderThumb, GetWebThemeState(element), - gfx::Rect(unzoomed_rect), &extra_params, style.UsedColorScheme(), - accent_color); + gfx::Rect(unzoomed_rect), &extra_params, color_scheme, accent_color); return false; } @@ -491,11 +486,14 @@ bool ThemePainterDefault::PaintInnerSpinButton(const Element& element, extra_params.inner_spin.spin_up = spin_up; extra_params.inner_spin.read_only = read_only; + bool enable_force_dark = + paint_info.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = style.UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartInnerSpinButton, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, style.UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -520,10 +518,14 @@ bool ThemePainterDefault::PaintProgressBar(const Element& element, DirectionFlippingScope scope(o, i, rect); cc::PaintCanvas* canvas = i.context.Canvas(); + bool enable_force_dark = + i.context.IsDarkModeEnabled() && !style.DisableForceDark(); + mojom::blink::ColorScheme color_scheme = o.StyleRef().UsedColorScheme(); + AdjustColorScheme(color_scheme, enable_force_dark); + Platform::Current()->ThemeEngine()->Paint( canvas, WebThemeEngine::kPartProgressBar, GetWebThemeState(element), - gfx::Rect(rect), &extra_params, o.StyleRef().UsedColorScheme(), - GetAccentColor(style)); + gfx::Rect(rect), &extra_params, color_scheme, GetAccentColor(style)); return false; } @@ -641,4 +643,11 @@ IntRect ThemePainterDefault::ApplyZoomToRect( return unzoomed_rect; } +void ThemePainterDefault::AdjustColorScheme(mojom::ColorScheme& color_scheme, + bool enable_force_dark) { + if (color_scheme == mojom::blink::ColorScheme::kLight && enable_force_dark) { + color_scheme = mojom::blink::ColorScheme::kDark; + } +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/paint/theme_painter_default.h b/chromium/third_party/blink/renderer/core/paint/theme_painter_default.h index 965c7a92b87..377fd89e263 100644 --- a/chromium/third_party/blink/renderer/core/paint/theme_painter_default.h +++ b/chromium/third_party/blink/renderer/core/paint/theme_painter_default.h @@ -111,6 +111,8 @@ class ThemePainterDefault final : public ThemePainter { GraphicsContextStateSaver&, float zoom_level); + void AdjustColorScheme(mojom::ColorScheme&, bool); + // ThemePaintDefault is a part object of m_theme. LayoutThemeDefault& theme_; }; |