summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
blob: 53a09c5d9e44525f390e2c298072fc83ab8ded70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
// Copyright 2016 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/box_paint_invalidator.h"

#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.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.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"

namespace blink {

bool BoxPaintInvalidator::HasEffectiveBackground() {
  // The view can paint background not from the style.
  if (IsA<LayoutView>(box_))
    return true;
  return box_.StyleRef().HasBackground() && !box_.BackgroundTransfersToView();
}

// |width| is of the positioning area.
static bool ShouldFullyInvalidateFillLayersOnWidthChange(
    const FillLayer& layer) {
  // Nobody will use multiple layers without wanting fancy positioning.
  if (layer.Next())
    return true;

  // The layer properties checked below apply only when there is a valid image.
  const StyleImage* image = layer.GetImage();
  if (!image || !image->CanRender())
    return false;

  if (layer.RepeatX() != EFillRepeat::kRepeatFill &&
      layer.RepeatX() != EFillRepeat::kNoRepeatFill)
    return true;

  // TODO(alancutter): Make this work correctly for calc lengths.
  if (layer.PositionX().IsPercentOrCalc() && !layer.PositionX().IsZero())
    return true;

  if (layer.BackgroundXOrigin() != BackgroundEdgeOrigin::kLeft)
    return true;

  EFillSizeType size_type = layer.SizeType();

  if (size_type == EFillSizeType::kContain ||
      size_type == EFillSizeType::kCover)
    return true;

  DCHECK_EQ(size_type, EFillSizeType::kSizeLength);

  // TODO(alancutter): Make this work correctly for calc lengths.
  const Length& width = layer.SizeLength().Width();
  if (width.IsPercentOrCalc() && !width.IsZero())
    return true;

  if (width.IsAuto() && !image->HasIntrinsicSize())
    return true;

  return false;
}

// |height| is of the positioning area.
static bool ShouldFullyInvalidateFillLayersOnHeightChange(
    const FillLayer& layer) {
  // Nobody will use multiple layers without wanting fancy positioning.
  if (layer.Next())
    return true;

  // The layer properties checked below apply only when there is a valid image.
  const StyleImage* image = layer.GetImage();
  if (!image || !image->CanRender())
    return false;

  if (layer.RepeatY() != EFillRepeat::kRepeatFill &&
      layer.RepeatY() != EFillRepeat::kNoRepeatFill)
    return true;

  // TODO(alancutter): Make this work correctly for calc lengths.
  if (layer.PositionY().IsPercentOrCalc() && !layer.PositionY().IsZero())
    return true;

  if (layer.BackgroundYOrigin() != BackgroundEdgeOrigin::kTop)
    return true;

  EFillSizeType size_type = layer.SizeType();

  if (size_type == EFillSizeType::kContain ||
      size_type == EFillSizeType::kCover)
    return true;

  DCHECK_EQ(size_type, EFillSizeType::kSizeLength);

  // TODO(alancutter): Make this work correctly for calc lengths.
  const Length& height = layer.SizeLength().Height();
  if (height.IsPercentOrCalc() && !height.IsZero())
    return true;

  if (height.IsAuto() && !image->HasIntrinsicSize())
    return true;

  return false;
}

// old_size and new_size are the old and new sizes of the positioning area.
bool ShouldFullyInvalidateFillLayersOnSizeChange(const FillLayer& layer,
                                                 const PhysicalSize& old_size,
                                                 const PhysicalSize& new_size) {
  return (old_size.width != new_size.width &&
          ShouldFullyInvalidateFillLayersOnWidthChange(layer)) ||
         (old_size.height != new_size.height &&
          ShouldFullyInvalidateFillLayersOnHeightChange(layer));
}

PaintInvalidationReason BoxPaintInvalidator::ComputePaintInvalidationReason() {
  PaintInvalidationReason reason =
      ObjectPaintInvalidatorWithContext(box_, context_)
          .ComputePaintInvalidationReason();

  if (reason != PaintInvalidationReason::kIncremental)
    return reason;

  const ComputedStyle& style = box_.StyleRef();

  if (style.MaskLayers().AnyLayerUsesContentBox() &&
      box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
    return PaintInvalidationReason::kGeometry;

  if (box_.PreviousSize() == box_.Size() &&
      context_.old_visual_rect == context_.fragment_data->VisualRect())
    return PaintInvalidationReason::kNone;

  // If either border box changed or bounds changed, and old or new border box
  // doesn't equal old or new bounds, incremental invalidation is not
  // applicable. This captures the following cases:
  // - pixel snapping, or not snapping e.g. for some visual overflowing effects,
  // - scale, rotate, skew etc. transforms,
  // - visual (ink) overflows.
  if (PhysicalRect(context_.old_visual_rect) !=
          PhysicalRect(context_.old_paint_offset, box_.PreviousSize()) ||
      PhysicalRect(context_.fragment_data->VisualRect()) !=
          PhysicalRect(context_.fragment_data->PaintOffset(), box_.Size())) {
    return PaintInvalidationReason::kGeometry;
  }

  DCHECK_NE(box_.PreviousSize(), box_.Size());

  // Incremental invalidation is not applicable if there is border in the
  // direction of border box size change because we don't know the border
  // width when issuing incremental raster invalidations.
  if (box_.BorderRight() || box_.BorderBottom())
    return PaintInvalidationReason::kGeometry;

  if (style.HasVisualOverflowingEffect() || style.HasEffectiveAppearance() ||
      style.HasFilterInducingProperty() || style.HasMask() || style.ClipPath())
    return PaintInvalidationReason::kGeometry;

  if (style.HasBorderRadius() || style.CanRenderBorderImage())
    return PaintInvalidationReason::kGeometry;

  // Needs to repaint frame boundaries.
  if (box_.IsFrameSet())
    return PaintInvalidationReason::kGeometry;

  // Needs to repaint column rules.
  if (box_.IsLayoutMultiColumnSet())
    return PaintInvalidationReason::kGeometry;

  // Background invalidation has been done during InvalidateBackground(), so
  // we don't need to check background in this function.

  return PaintInvalidationReason::kIncremental;
}

bool BoxPaintInvalidator::BackgroundGeometryDependsOnLayoutOverflowRect() {
  return HasEffectiveBackground() &&
         box_.StyleRef().BackgroundLayers().AnyLayerHasLocalAttachmentImage();
}

bool BoxPaintInvalidator::BackgroundPaintsOntoScrollingContentsLayer() {
  if (!HasEffectiveBackground())
    return false;
  return box_.GetBackgroundPaintLocation() &
         kBackgroundPaintInScrollingContents;
}

bool BoxPaintInvalidator::BackgroundPaintsOntoMainGraphicsLayer() {
  if (!HasEffectiveBackground())
    return false;
  return box_.GetBackgroundPaintLocation() & kBackgroundPaintInGraphicsLayer;
}

bool BoxPaintInvalidator::ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
    const PhysicalRect& old_layout_overflow,
    const PhysicalRect& new_layout_overflow) {
  if (new_layout_overflow == old_layout_overflow)
    return false;

  if (!BackgroundGeometryDependsOnLayoutOverflowRect())
    return false;

  // The background should invalidate on most location changes.
  if (new_layout_overflow.offset != old_layout_overflow.offset)
    return true;

  return ShouldFullyInvalidateFillLayersOnSizeChange(
      box_.StyleRef().BackgroundLayers(), old_layout_overflow.size,
      new_layout_overflow.size);
}

BoxPaintInvalidator::BackgroundInvalidationType
BoxPaintInvalidator::ComputeViewBackgroundInvalidation() {
  DCHECK(IsA<LayoutView>(box_));

  const auto& layout_view = To<LayoutView>(box_);
  auto new_background_rect = layout_view.BackgroundRect();
  auto old_background_rect = layout_view.PreviousBackgroundRect();
  layout_view.SetPreviousBackgroundRect(new_background_rect);

  // BackgroundRect is the positioning area of all fixed attachment backgrounds,
  // including the LayoutView's and descendants'.
  bool background_location_changed =
      new_background_rect.offset != old_background_rect.offset;
  bool background_size_changed =
      new_background_rect.size != old_background_rect.size;
  if (background_location_changed || background_size_changed) {
    for (auto* object :
         layout_view.GetFrameView()->BackgroundAttachmentFixedObjects())
      object->SetBackgroundNeedsFullPaintInvalidation();
  }

  if (background_location_changed ||
      layout_view.BackgroundNeedsFullPaintInvalidation())
    return BackgroundInvalidationType::kFull;

  if (Element* document_element = box_.GetDocument().documentElement()) {
    if (document_element) {
      if (const auto* document_element_object =
              document_element->GetLayoutObject()) {
        // LayoutView's non-fixed-attachment background is positioned in the
        // document element and needs to invalidate if the size changes.
        // See: https://drafts.csswg.org/css-backgrounds-3/#root-background.
        if (BackgroundGeometryDependsOnLayoutOverflowRect()) {
          if (document_element_object->IsBox()) {
            const auto* document_background_box =
                ToLayoutBox(document_element_object);
            if (ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
                    document_background_box
                        ->PreviousPhysicalLayoutOverflowRect(),
                    document_background_box->PhysicalLayoutOverflowRect())) {
              return BackgroundInvalidationType::kFull;
            }
          }
        }

        // The document background paints with a transform but nevertheless
        // extended onto an infinite canvas. In cases where it has a transform
        // we cna't apply incremental invalidation, because the visual rect is
        // no longer axis-aligned to the LayoutView.
        if (document_element_object->StyleRef().HasTransform()) {
          return BackgroundInvalidationType::kFull;
        }
      }
    }
  }

  return background_size_changed ? BackgroundInvalidationType::kIncremental
                                 : BackgroundInvalidationType::kNone;
}

BoxPaintInvalidator::BackgroundInvalidationType
BoxPaintInvalidator::ComputeBackgroundInvalidation(
    bool& should_invalidate_all_layers) {
  // If background changed, we may paint the background on different graphics
  // layer, so we need to fully invalidate the background on all layers.
  if (box_.BackgroundNeedsFullPaintInvalidation()) {
    should_invalidate_all_layers = true;
    return BackgroundInvalidationType::kFull;
  }

  if (!HasEffectiveBackground())
    return BackgroundInvalidationType::kNone;

  const auto& background_layers = box_.StyleRef().BackgroundLayers();
  if (background_layers.AnyLayerHasDefaultAttachmentImage() &&
      ShouldFullyInvalidateFillLayersOnSizeChange(
          background_layers, PhysicalSizeToBeNoop(box_.PreviousSize()),
          PhysicalSizeToBeNoop(box_.Size())))
    return BackgroundInvalidationType::kFull;

  if (background_layers.AnyLayerUsesContentBox() &&
      box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
    return BackgroundInvalidationType::kFull;

  bool layout_overflow_change_causes_invalidation =
      (BackgroundGeometryDependsOnLayoutOverflowRect() ||
       BackgroundPaintsOntoScrollingContentsLayer());

  if (!layout_overflow_change_causes_invalidation)
    return BackgroundInvalidationType::kNone;

  const auto& old_layout_overflow = box_.PreviousPhysicalLayoutOverflowRect();
  auto new_layout_overflow = box_.PhysicalLayoutOverflowRect();
  if (ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
          old_layout_overflow, new_layout_overflow))
    return BackgroundInvalidationType::kFull;

  if (new_layout_overflow != old_layout_overflow) {
    // Do incremental invalidation if possible.
    if (old_layout_overflow.offset == new_layout_overflow.offset)
      return BackgroundInvalidationType::kIncremental;
    return BackgroundInvalidationType::kFull;
  }
  return BackgroundInvalidationType::kNone;
}

void BoxPaintInvalidator::InvalidateBackground() {
  bool should_invalidate_all_layers = false;
  auto background_invalidation_type =
      ComputeBackgroundInvalidation(should_invalidate_all_layers);
  if (IsA<LayoutView>(box_)) {
    background_invalidation_type = std::max(
        background_invalidation_type, ComputeViewBackgroundInvalidation());
  }

  if (box_.GetScrollableArea()) {
    if (should_invalidate_all_layers ||
        (BackgroundPaintsOntoScrollingContentsLayer() &&
         background_invalidation_type != BackgroundInvalidationType::kNone)) {
      auto reason =
          background_invalidation_type == BackgroundInvalidationType::kFull
              ? PaintInvalidationReason::kBackground
              : PaintInvalidationReason::kIncremental;
      context_.painting_layer->SetNeedsRepaint();
      ObjectPaintInvalidator(box_).InvalidateDisplayItemClient(
          box_.GetScrollableArea()->GetScrollingBackgroundDisplayItemClient(),
          reason);
    }
  }

  if (should_invalidate_all_layers ||
      (BackgroundPaintsOntoMainGraphicsLayer() &&
       background_invalidation_type == BackgroundInvalidationType::kFull)) {
    box_.GetMutableForPainting()
        .SetShouldDoFullPaintInvalidationWithoutGeometryChange(
            PaintInvalidationReason::kBackground);
  }
}

void BoxPaintInvalidator::InvalidatePaint() {
  InvalidateBackground();

  ObjectPaintInvalidatorWithContext(box_, context_)
      .InvalidatePaintWithComputedReason(ComputePaintInvalidationReason());

  if (PaintLayerScrollableArea* area = box_.GetScrollableArea())
    area->InvalidatePaintOfScrollControlsIfNeeded(context_);

  // This is for the next invalidatePaintIfNeeded so must be at the end.
  SavePreviousBoxGeometriesIfNeeded();
}

bool BoxPaintInvalidator::
    NeedsToSavePreviousContentBoxRectOrLayoutOverflowRect() {
  // The LayoutView depends on the document element's layout overflow rect (see:
  // ComputeViewBackgroundInvalidation) and needs to invalidate before the
  // document element invalidates. There are few document elements so the
  // previous layout overflow rect is always saved, rather than duplicating the
  // logic save-if-needed logic for this special case.
  if (box_.IsDocumentElement())
    return true;

  // Replaced elements are clipped to the content box thus we need to check
  // for its size.
  if (box_.IsLayoutReplaced())
    return true;

  // Don't save old box geometries if the paint rect is empty because we'll
  // fully invalidate once the paint rect becomes non-empty.
  if (context_.fragment_data->VisualRect().IsEmpty())
    return false;

  const ComputedStyle& style = box_.StyleRef();

  // Background and mask layers can depend on other boxes than border box. See
  // crbug.com/490533
  if ((style.BackgroundLayers().AnyLayerUsesContentBox() ||
       style.MaskLayers().AnyLayerUsesContentBox()) &&
      box_.ContentSize() != box_.Size())
    return true;
  if ((BackgroundGeometryDependsOnLayoutOverflowRect() ||
       BackgroundPaintsOntoScrollingContentsLayer()) &&
      box_.LayoutOverflowRect() != box_.BorderBoxRect())
    return true;

  return false;
}

void BoxPaintInvalidator::SavePreviousBoxGeometriesIfNeeded() {
  box_.GetMutableForPainting().SavePreviousSize();

  if (NeedsToSavePreviousContentBoxRectOrLayoutOverflowRect()) {
    box_.GetMutableForPainting()
        .SavePreviousContentBoxRectAndLayoutOverflowRect();
  } else {
    box_.GetMutableForPainting()
        .ClearPreviousContentBoxRectAndLayoutOverflowRect();
  }
}

}  // namespace blink