1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/paint/box_paint_invalidator.h"
6
7 #include "third_party/blink/renderer/core/frame/settings.h"
8 #include "third_party/blink/renderer/core/layout/layout_view.h"
9 #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
10 #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
11 #include "third_party/blink/renderer/core/paint/paint_invalidator.h"
12 #include "third_party/blink/renderer/core/paint/paint_layer.h"
13 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
14 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
15
16 namespace blink {
17
HasEffectiveBackground()18 bool BoxPaintInvalidator::HasEffectiveBackground() {
19 // The view can paint background not from the style.
20 if (IsA<LayoutView>(box_))
21 return true;
22 return box_.StyleRef().HasBackground() && !box_.BackgroundTransfersToView();
23 }
24
25 // |width| is of the positioning area.
ShouldFullyInvalidateFillLayersOnWidthChange(const FillLayer & layer)26 static bool ShouldFullyInvalidateFillLayersOnWidthChange(
27 const FillLayer& layer) {
28 // Nobody will use multiple layers without wanting fancy positioning.
29 if (layer.Next())
30 return true;
31
32 // The layer properties checked below apply only when there is a valid image.
33 const StyleImage* image = layer.GetImage();
34 if (!image || !image->CanRender())
35 return false;
36
37 if (layer.RepeatX() != EFillRepeat::kRepeatFill &&
38 layer.RepeatX() != EFillRepeat::kNoRepeatFill)
39 return true;
40
41 // TODO(alancutter): Make this work correctly for calc lengths.
42 if (layer.PositionX().IsPercentOrCalc() && !layer.PositionX().IsZero())
43 return true;
44
45 if (layer.BackgroundXOrigin() != BackgroundEdgeOrigin::kLeft)
46 return true;
47
48 EFillSizeType size_type = layer.SizeType();
49
50 if (size_type == EFillSizeType::kContain ||
51 size_type == EFillSizeType::kCover)
52 return true;
53
54 DCHECK_EQ(size_type, EFillSizeType::kSizeLength);
55
56 // TODO(alancutter): Make this work correctly for calc lengths.
57 const Length& width = layer.SizeLength().Width();
58 if (width.IsPercentOrCalc() && !width.IsZero())
59 return true;
60
61 if (width.IsAuto() && !image->HasIntrinsicSize())
62 return true;
63
64 return false;
65 }
66
67 // |height| is of the positioning area.
ShouldFullyInvalidateFillLayersOnHeightChange(const FillLayer & layer)68 static bool ShouldFullyInvalidateFillLayersOnHeightChange(
69 const FillLayer& layer) {
70 // Nobody will use multiple layers without wanting fancy positioning.
71 if (layer.Next())
72 return true;
73
74 // The layer properties checked below apply only when there is a valid image.
75 const StyleImage* image = layer.GetImage();
76 if (!image || !image->CanRender())
77 return false;
78
79 if (layer.RepeatY() != EFillRepeat::kRepeatFill &&
80 layer.RepeatY() != EFillRepeat::kNoRepeatFill)
81 return true;
82
83 // TODO(alancutter): Make this work correctly for calc lengths.
84 if (layer.PositionY().IsPercentOrCalc() && !layer.PositionY().IsZero())
85 return true;
86
87 if (layer.BackgroundYOrigin() != BackgroundEdgeOrigin::kTop)
88 return true;
89
90 EFillSizeType size_type = layer.SizeType();
91
92 if (size_type == EFillSizeType::kContain ||
93 size_type == EFillSizeType::kCover)
94 return true;
95
96 DCHECK_EQ(size_type, EFillSizeType::kSizeLength);
97
98 // TODO(alancutter): Make this work correctly for calc lengths.
99 const Length& height = layer.SizeLength().Height();
100 if (height.IsPercentOrCalc() && !height.IsZero())
101 return true;
102
103 if (height.IsAuto() && !image->HasIntrinsicSize())
104 return true;
105
106 return false;
107 }
108
109 // old_size and new_size are the old and new sizes of the positioning area.
ShouldFullyInvalidateFillLayersOnSizeChange(const FillLayer & layer,const PhysicalSize & old_size,const PhysicalSize & new_size)110 bool ShouldFullyInvalidateFillLayersOnSizeChange(const FillLayer& layer,
111 const PhysicalSize& old_size,
112 const PhysicalSize& new_size) {
113 return (old_size.width != new_size.width &&
114 ShouldFullyInvalidateFillLayersOnWidthChange(layer)) ||
115 (old_size.height != new_size.height &&
116 ShouldFullyInvalidateFillLayersOnHeightChange(layer));
117 }
118
ComputePaintInvalidationReason()119 PaintInvalidationReason BoxPaintInvalidator::ComputePaintInvalidationReason() {
120 PaintInvalidationReason reason =
121 ObjectPaintInvalidatorWithContext(box_, context_)
122 .ComputePaintInvalidationReason();
123
124 if (reason != PaintInvalidationReason::kIncremental)
125 return reason;
126
127 const ComputedStyle& style = box_.StyleRef();
128
129 if (style.MaskLayers().AnyLayerUsesContentBox() &&
130 box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
131 return PaintInvalidationReason::kGeometry;
132
133 if (box_.PreviousSize() == box_.Size() &&
134 context_.old_visual_rect == context_.fragment_data->VisualRect())
135 return PaintInvalidationReason::kNone;
136
137 // If either border box changed or bounds changed, and old or new border box
138 // doesn't equal old or new bounds, incremental invalidation is not
139 // applicable. This captures the following cases:
140 // - pixel snapping, or not snapping e.g. for some visual overflowing effects,
141 // - scale, rotate, skew etc. transforms,
142 // - visual (ink) overflows.
143 if (PhysicalRect(context_.old_visual_rect) !=
144 PhysicalRect(context_.old_paint_offset, box_.PreviousSize()) ||
145 PhysicalRect(context_.fragment_data->VisualRect()) !=
146 PhysicalRect(context_.fragment_data->PaintOffset(), box_.Size())) {
147 return PaintInvalidationReason::kGeometry;
148 }
149
150 DCHECK_NE(box_.PreviousSize(), box_.Size());
151
152 // Incremental invalidation is not applicable if there is border in the
153 // direction of border box size change because we don't know the border
154 // width when issuing incremental raster invalidations.
155 if (box_.BorderRight() || box_.BorderBottom())
156 return PaintInvalidationReason::kGeometry;
157
158 if (style.HasVisualOverflowingEffect() || style.HasEffectiveAppearance() ||
159 style.HasFilterInducingProperty() || style.HasMask() || style.ClipPath())
160 return PaintInvalidationReason::kGeometry;
161
162 if (style.HasBorderRadius() || style.CanRenderBorderImage())
163 return PaintInvalidationReason::kGeometry;
164
165 // Needs to repaint frame boundaries.
166 if (box_.IsFrameSet())
167 return PaintInvalidationReason::kGeometry;
168
169 // Needs to repaint column rules.
170 if (box_.IsLayoutMultiColumnSet())
171 return PaintInvalidationReason::kGeometry;
172
173 // Background invalidation has been done during InvalidateBackground(), so
174 // we don't need to check background in this function.
175
176 return PaintInvalidationReason::kIncremental;
177 }
178
BackgroundGeometryDependsOnLayoutOverflowRect()179 bool BoxPaintInvalidator::BackgroundGeometryDependsOnLayoutOverflowRect() {
180 return HasEffectiveBackground() &&
181 box_.StyleRef().BackgroundLayers().AnyLayerHasLocalAttachmentImage();
182 }
183
BackgroundPaintsOntoScrollingContentsLayer()184 bool BoxPaintInvalidator::BackgroundPaintsOntoScrollingContentsLayer() {
185 if (!HasEffectiveBackground())
186 return false;
187 return box_.GetBackgroundPaintLocation() &
188 kBackgroundPaintInScrollingContents;
189 }
190
BackgroundPaintsOntoMainGraphicsLayer()191 bool BoxPaintInvalidator::BackgroundPaintsOntoMainGraphicsLayer() {
192 if (!HasEffectiveBackground())
193 return false;
194 return box_.GetBackgroundPaintLocation() & kBackgroundPaintInGraphicsLayer;
195 }
196
ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(const PhysicalRect & old_layout_overflow,const PhysicalRect & new_layout_overflow)197 bool BoxPaintInvalidator::ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
198 const PhysicalRect& old_layout_overflow,
199 const PhysicalRect& new_layout_overflow) {
200 if (new_layout_overflow == old_layout_overflow)
201 return false;
202
203 if (!BackgroundGeometryDependsOnLayoutOverflowRect())
204 return false;
205
206 // The background should invalidate on most location changes.
207 if (new_layout_overflow.offset != old_layout_overflow.offset)
208 return true;
209
210 return ShouldFullyInvalidateFillLayersOnSizeChange(
211 box_.StyleRef().BackgroundLayers(), old_layout_overflow.size,
212 new_layout_overflow.size);
213 }
214
215 BoxPaintInvalidator::BackgroundInvalidationType
ComputeViewBackgroundInvalidation()216 BoxPaintInvalidator::ComputeViewBackgroundInvalidation() {
217 DCHECK(IsA<LayoutView>(box_));
218
219 const auto& layout_view = To<LayoutView>(box_);
220 auto new_background_rect = layout_view.BackgroundRect();
221 auto old_background_rect = layout_view.PreviousBackgroundRect();
222 layout_view.SetPreviousBackgroundRect(new_background_rect);
223
224 // BackgroundRect is the positioning area of all fixed attachment backgrounds,
225 // including the LayoutView's and descendants'.
226 bool background_location_changed =
227 new_background_rect.offset != old_background_rect.offset;
228 bool background_size_changed =
229 new_background_rect.size != old_background_rect.size;
230 if (background_location_changed || background_size_changed) {
231 for (auto* object :
232 layout_view.GetFrameView()->BackgroundAttachmentFixedObjects())
233 object->SetBackgroundNeedsFullPaintInvalidation();
234 }
235
236 if (background_location_changed ||
237 layout_view.BackgroundNeedsFullPaintInvalidation())
238 return BackgroundInvalidationType::kFull;
239
240 if (Element* document_element = box_.GetDocument().documentElement()) {
241 if (document_element) {
242 if (const auto* document_element_object =
243 document_element->GetLayoutObject()) {
244 // LayoutView's non-fixed-attachment background is positioned in the
245 // document element and needs to invalidate if the size changes.
246 // See: https://drafts.csswg.org/css-backgrounds-3/#root-background.
247 if (BackgroundGeometryDependsOnLayoutOverflowRect()) {
248 if (document_element_object->IsBox()) {
249 const auto* document_background_box =
250 ToLayoutBox(document_element_object);
251 if (ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
252 document_background_box
253 ->PreviousPhysicalLayoutOverflowRect(),
254 document_background_box->PhysicalLayoutOverflowRect())) {
255 return BackgroundInvalidationType::kFull;
256 }
257 }
258 }
259
260 // The document background paints with a transform but nevertheless
261 // extended onto an infinite canvas. In cases where it has a transform
262 // we cna't apply incremental invalidation, because the visual rect is
263 // no longer axis-aligned to the LayoutView.
264 if (document_element_object->StyleRef().HasTransform()) {
265 return BackgroundInvalidationType::kFull;
266 }
267 }
268 }
269 }
270
271 return background_size_changed ? BackgroundInvalidationType::kIncremental
272 : BackgroundInvalidationType::kNone;
273 }
274
275 BoxPaintInvalidator::BackgroundInvalidationType
ComputeBackgroundInvalidation(bool & should_invalidate_all_layers)276 BoxPaintInvalidator::ComputeBackgroundInvalidation(
277 bool& should_invalidate_all_layers) {
278 // If background changed, we may paint the background on different graphics
279 // layer, so we need to fully invalidate the background on all layers.
280 if (box_.BackgroundNeedsFullPaintInvalidation()) {
281 should_invalidate_all_layers = true;
282 return BackgroundInvalidationType::kFull;
283 }
284
285 if (!HasEffectiveBackground())
286 return BackgroundInvalidationType::kNone;
287
288 const auto& background_layers = box_.StyleRef().BackgroundLayers();
289 if (background_layers.AnyLayerHasDefaultAttachmentImage() &&
290 ShouldFullyInvalidateFillLayersOnSizeChange(
291 background_layers, PhysicalSizeToBeNoop(box_.PreviousSize()),
292 PhysicalSizeToBeNoop(box_.Size())))
293 return BackgroundInvalidationType::kFull;
294
295 if (background_layers.AnyLayerUsesContentBox() &&
296 box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
297 return BackgroundInvalidationType::kFull;
298
299 bool layout_overflow_change_causes_invalidation =
300 (BackgroundGeometryDependsOnLayoutOverflowRect() ||
301 BackgroundPaintsOntoScrollingContentsLayer());
302
303 if (!layout_overflow_change_causes_invalidation)
304 return BackgroundInvalidationType::kNone;
305
306 const auto& old_layout_overflow = box_.PreviousPhysicalLayoutOverflowRect();
307 auto new_layout_overflow = box_.PhysicalLayoutOverflowRect();
308 if (ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
309 old_layout_overflow, new_layout_overflow))
310 return BackgroundInvalidationType::kFull;
311
312 if (new_layout_overflow != old_layout_overflow) {
313 // Do incremental invalidation if possible.
314 if (old_layout_overflow.offset == new_layout_overflow.offset)
315 return BackgroundInvalidationType::kIncremental;
316 return BackgroundInvalidationType::kFull;
317 }
318 return BackgroundInvalidationType::kNone;
319 }
320
InvalidateBackground()321 void BoxPaintInvalidator::InvalidateBackground() {
322 bool should_invalidate_all_layers = false;
323 auto background_invalidation_type =
324 ComputeBackgroundInvalidation(should_invalidate_all_layers);
325 if (IsA<LayoutView>(box_)) {
326 background_invalidation_type = std::max(
327 background_invalidation_type, ComputeViewBackgroundInvalidation());
328 }
329
330 if (box_.GetScrollableArea()) {
331 if (should_invalidate_all_layers ||
332 (BackgroundPaintsOntoScrollingContentsLayer() &&
333 background_invalidation_type != BackgroundInvalidationType::kNone)) {
334 auto reason =
335 background_invalidation_type == BackgroundInvalidationType::kFull
336 ? PaintInvalidationReason::kBackground
337 : PaintInvalidationReason::kIncremental;
338 context_.painting_layer->SetNeedsRepaint();
339 ObjectPaintInvalidator(box_).InvalidateDisplayItemClient(
340 box_.GetScrollableArea()->GetScrollingBackgroundDisplayItemClient(),
341 reason);
342 }
343 }
344
345 if (should_invalidate_all_layers ||
346 (BackgroundPaintsOntoMainGraphicsLayer() &&
347 background_invalidation_type == BackgroundInvalidationType::kFull)) {
348 box_.GetMutableForPainting()
349 .SetShouldDoFullPaintInvalidationWithoutGeometryChange(
350 PaintInvalidationReason::kBackground);
351 }
352 }
353
InvalidatePaint()354 void BoxPaintInvalidator::InvalidatePaint() {
355 InvalidateBackground();
356
357 ObjectPaintInvalidatorWithContext(box_, context_)
358 .InvalidatePaintWithComputedReason(ComputePaintInvalidationReason());
359
360 if (PaintLayerScrollableArea* area = box_.GetScrollableArea())
361 area->InvalidatePaintOfScrollControlsIfNeeded(context_);
362
363 // This is for the next invalidatePaintIfNeeded so must be at the end.
364 SavePreviousBoxGeometriesIfNeeded();
365 }
366
367 bool BoxPaintInvalidator::
NeedsToSavePreviousContentBoxRectOrLayoutOverflowRect()368 NeedsToSavePreviousContentBoxRectOrLayoutOverflowRect() {
369 // The LayoutView depends on the document element's layout overflow rect (see:
370 // ComputeViewBackgroundInvalidation) and needs to invalidate before the
371 // document element invalidates. There are few document elements so the
372 // previous layout overflow rect is always saved, rather than duplicating the
373 // logic save-if-needed logic for this special case.
374 if (box_.IsDocumentElement())
375 return true;
376
377 // Replaced elements are clipped to the content box thus we need to check
378 // for its size.
379 if (box_.IsLayoutReplaced())
380 return true;
381
382 // Don't save old box geometries if the paint rect is empty because we'll
383 // fully invalidate once the paint rect becomes non-empty.
384 if (context_.fragment_data->VisualRect().IsEmpty())
385 return false;
386
387 const ComputedStyle& style = box_.StyleRef();
388
389 // Background and mask layers can depend on other boxes than border box. See
390 // crbug.com/490533
391 if ((style.BackgroundLayers().AnyLayerUsesContentBox() ||
392 style.MaskLayers().AnyLayerUsesContentBox()) &&
393 box_.ContentSize() != box_.Size())
394 return true;
395 if ((BackgroundGeometryDependsOnLayoutOverflowRect() ||
396 BackgroundPaintsOntoScrollingContentsLayer()) &&
397 box_.LayoutOverflowRect() != box_.BorderBoxRect())
398 return true;
399
400 return false;
401 }
402
SavePreviousBoxGeometriesIfNeeded()403 void BoxPaintInvalidator::SavePreviousBoxGeometriesIfNeeded() {
404 box_.GetMutableForPainting().SavePreviousSize();
405
406 if (NeedsToSavePreviousContentBoxRectOrLayoutOverflowRect()) {
407 box_.GetMutableForPainting()
408 .SavePreviousContentBoxRectAndLayoutOverflowRect();
409 } else {
410 box_.GetMutableForPainting()
411 .ClearPreviousContentBoxRectAndLayoutOverflowRect();
412 }
413 }
414
415 } // namespace blink
416