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