1 // Copyright 2014 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_painter_base.h"
6
7 #include "base/optional.h"
8 #include "third_party/blink/renderer/core/css/native_paint_image_generator.h"
9 #include "third_party/blink/renderer/core/dom/document.h"
10 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
11 #include "third_party/blink/renderer/core/frame/settings.h"
12 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
13 #include "third_party/blink/renderer/core/layout/layout_progress.h"
14 #include "third_party/blink/renderer/core/paint/background_image_geometry.h"
15 #include "third_party/blink/renderer/core/paint/box_border_painter.h"
16 #include "third_party/blink/renderer/core/paint/image_element_timing.h"
17 #include "third_party/blink/renderer/core/paint/nine_piece_image_painter.h"
18 #include "third_party/blink/renderer/core/paint/paint_info.h"
19 #include "third_party/blink/renderer/core/paint/paint_layer.h"
20 #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
21 #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h"
22 #include "third_party/blink/renderer/core/paint/rounded_inner_rect_clipper.h"
23 #include "third_party/blink/renderer/core/style/border_edge.h"
24 #include "third_party/blink/renderer/core/style/computed_style.h"
25 #include "third_party/blink/renderer/core/style/shadow_list.h"
26 #include "third_party/blink/renderer/core/style/style_fetched_image.h"
27 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
28 #include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
29 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
30 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
31 #include "third_party/blink/renderer/platform/graphics/scoped_interpolation_quality.h"
32 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
33
34 namespace blink {
35
PaintFillLayers(const PaintInfo & paint_info,const Color & c,const FillLayer & fill_layer,const PhysicalRect & rect,BackgroundImageGeometry & geometry,BackgroundBleedAvoidance bleed)36 void BoxPainterBase::PaintFillLayers(const PaintInfo& paint_info,
37 const Color& c,
38 const FillLayer& fill_layer,
39 const PhysicalRect& rect,
40 BackgroundImageGeometry& geometry,
41 BackgroundBleedAvoidance bleed) {
42 FillLayerOcclusionOutputList reversed_paint_list;
43 bool should_draw_background_in_separate_buffer =
44 CalculateFillLayerOcclusionCulling(reversed_paint_list, fill_layer);
45
46 // TODO(trchen): We can optimize out isolation group if we have a
47 // non-transparent background color and the bottom layer encloses all other
48 // layers.
49 GraphicsContext& context = paint_info.context;
50 if (should_draw_background_in_separate_buffer)
51 context.BeginLayer();
52
53 for (auto it = reversed_paint_list.rbegin(); it != reversed_paint_list.rend();
54 ++it) {
55 PaintFillLayer(paint_info, c, **it, rect, bleed, geometry);
56 }
57
58 if (should_draw_background_in_separate_buffer)
59 context.EndLayer();
60 }
61
PaintNormalBoxShadow(const PaintInfo & info,const PhysicalRect & paint_rect,const ComputedStyle & style,PhysicalBoxSides sides_to_include,bool background_is_skipped)62 void BoxPainterBase::PaintNormalBoxShadow(const PaintInfo& info,
63 const PhysicalRect& paint_rect,
64 const ComputedStyle& style,
65 PhysicalBoxSides sides_to_include,
66 bool background_is_skipped) {
67 if (!style.BoxShadow())
68 return;
69 GraphicsContext& context = info.context;
70
71 FloatRoundedRect border = RoundedBorderGeometry::PixelSnappedRoundedBorder(
72 style, paint_rect, sides_to_include);
73
74 bool has_border_radius = style.HasBorderRadius();
75 bool has_opaque_background =
76 !background_is_skipped &&
77 style.VisitedDependentColor(GetCSSPropertyBackgroundColor()).Alpha() ==
78 255;
79
80 GraphicsContextStateSaver state_saver(context, false);
81
82 const ShadowList* shadow_list = style.BoxShadow();
83 for (wtf_size_t i = shadow_list->Shadows().size(); i--;) {
84 const ShadowData& shadow = shadow_list->Shadows()[i];
85 if (shadow.Style() != ShadowStyle::kNormal)
86 continue;
87
88 FloatSize shadow_offset(shadow.X(), shadow.Y());
89 float shadow_blur = shadow.Blur();
90 float shadow_spread = shadow.Spread();
91
92 if (shadow_offset.IsZero() && !shadow_blur && !shadow_spread)
93 continue;
94
95 const Color& shadow_color = shadow.GetColor().Resolve(
96 style.VisitedDependentColor(GetCSSPropertyColor()),
97 style.UsedColorScheme());
98
99 FloatRect fill_rect = border.Rect();
100 fill_rect.Inflate(shadow_spread);
101 if (fill_rect.IsEmpty())
102 continue;
103
104 // Save the state and clip, if not already done.
105 // The clip does not depend on any shadow-specific properties.
106 if (!state_saver.Saved()) {
107 state_saver.Save();
108 if (has_border_radius) {
109 FloatRoundedRect rect_to_clip_out = border;
110
111 // If the box is opaque, it is unnecessary to clip it out. However,
112 // doing so saves time when painting the shadow. On the other hand, it
113 // introduces subpixel gaps along the corners. Those are avoided by
114 // insetting the clipping path by one CSS pixel.
115 if (has_opaque_background)
116 rect_to_clip_out.InflateWithRadii(-1);
117
118 if (!rect_to_clip_out.IsEmpty())
119 context.ClipOutRoundedRect(rect_to_clip_out);
120 } else {
121 // This IntRect is correct even with fractional shadows, because it is
122 // used for the rectangle of the box itself, which is always
123 // pixel-aligned.
124 FloatRect rect_to_clip_out = border.Rect();
125
126 // If the box is opaque, it is unnecessary to clip it out. However,
127 // doing so saves time when painting the shadow. On the other hand, it
128 // introduces subpixel gaps along the edges if they are not
129 // pixel-aligned. Those are avoided by insetting the clipping path by
130 // one CSS pixel.
131 if (has_opaque_background)
132 rect_to_clip_out.Inflate(-1);
133
134 if (!rect_to_clip_out.IsEmpty())
135 context.ClipOut(rect_to_clip_out);
136 }
137 }
138
139 // Draw only the shadow.
140 context.SetShadow(shadow_offset, shadow_blur, shadow_color,
141 DrawLooperBuilder::kShadowRespectsTransforms,
142 DrawLooperBuilder::kShadowIgnoresAlpha, kDrawShadowOnly);
143
144 if (has_border_radius) {
145 FloatRoundedRect rounded_fill_rect = border;
146 rounded_fill_rect.Inflate(shadow_spread);
147
148 if (shadow_spread >= 0)
149 rounded_fill_rect.ExpandRadii(shadow_spread);
150 else
151 rounded_fill_rect.ShrinkRadii(-shadow_spread);
152 if (!rounded_fill_rect.IsRenderable())
153 rounded_fill_rect.AdjustRadii();
154 rounded_fill_rect.ConstrainRadii();
155 context.FillRoundedRect(rounded_fill_rect, Color::kBlack);
156 } else {
157 context.FillRect(fill_rect, Color::kBlack);
158 }
159 }
160 }
161
PaintInsetBoxShadowWithBorderRect(const PaintInfo & info,const PhysicalRect & border_rect,const ComputedStyle & style,PhysicalBoxSides sides_to_include)162 void BoxPainterBase::PaintInsetBoxShadowWithBorderRect(
163 const PaintInfo& info,
164 const PhysicalRect& border_rect,
165 const ComputedStyle& style,
166 PhysicalBoxSides sides_to_include) {
167 if (!style.BoxShadow())
168 return;
169 auto bounds = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(
170 style, border_rect, sides_to_include);
171 PaintInsetBoxShadow(info, bounds, style, sides_to_include);
172 }
173
PaintInsetBoxShadowWithInnerRect(const PaintInfo & info,const PhysicalRect & inner_rect,const ComputedStyle & style)174 void BoxPainterBase::PaintInsetBoxShadowWithInnerRect(
175 const PaintInfo& info,
176 const PhysicalRect& inner_rect,
177 const ComputedStyle& style) {
178 if (!style.BoxShadow())
179 return;
180 auto bounds = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(
181 style, inner_rect, LayoutRectOutsets());
182 PaintInsetBoxShadow(info, bounds, style);
183 }
184
PaintInsetBoxShadow(const PaintInfo & info,const FloatRoundedRect & bounds,const ComputedStyle & style,PhysicalBoxSides sides_to_include)185 void BoxPainterBase::PaintInsetBoxShadow(const PaintInfo& info,
186 const FloatRoundedRect& bounds,
187 const ComputedStyle& style,
188 PhysicalBoxSides sides_to_include) {
189 GraphicsContext& context = info.context;
190 GraphicsContextStateSaver state_saver(context, false);
191
192 const ShadowList* shadow_list = style.BoxShadow();
193 for (wtf_size_t i = shadow_list->Shadows().size(); i--;) {
194 const ShadowData& shadow = shadow_list->Shadows()[i];
195 if (shadow.Style() != ShadowStyle::kInset)
196 continue;
197
198 FloatSize shadow_offset(shadow.X(), shadow.Y());
199 float shadow_blur = shadow.Blur();
200 float shadow_spread = shadow.Spread();
201
202 if (shadow_offset.IsZero() && !shadow_blur && !shadow_spread)
203 continue;
204
205 const Color& shadow_color = shadow.GetColor().Resolve(
206 style.VisitedDependentColor(GetCSSPropertyColor()),
207 style.UsedColorScheme());
208
209 // The inset shadow case.
210 GraphicsContext::Edges clipped_edges = GraphicsContext::kNoEdge;
211 if (!sides_to_include.top)
212 clipped_edges |= GraphicsContext::kTopEdge;
213 if (!sides_to_include.right)
214 clipped_edges |= GraphicsContext::kRightEdge;
215 if (!sides_to_include.bottom)
216 clipped_edges |= GraphicsContext::kBottomEdge;
217 if (!sides_to_include.left)
218 clipped_edges |= GraphicsContext::kLeftEdge;
219 context.DrawInnerShadow(bounds, shadow_color, shadow_offset, shadow_blur,
220 shadow_spread, clipped_edges);
221 }
222 }
223
ShouldForceWhiteBackgroundForPrintEconomy(const Document & document,const ComputedStyle & style)224 bool BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(
225 const Document& document,
226 const ComputedStyle& style) {
227 return document.Printing() &&
228 style.PrintColorAdjust() == EPrintColorAdjust::kEconomy &&
229 (!document.GetSettings() ||
230 !document.GetSettings()->GetShouldPrintBackgrounds());
231 }
232
CalculateFillLayerOcclusionCulling(FillLayerOcclusionOutputList & reversed_paint_list,const FillLayer & fill_layer)233 bool BoxPainterBase::CalculateFillLayerOcclusionCulling(
234 FillLayerOcclusionOutputList& reversed_paint_list,
235 const FillLayer& fill_layer) {
236 bool is_non_associative = false;
237 for (auto* current_layer = &fill_layer; current_layer;
238 current_layer = current_layer->Next()) {
239 reversed_paint_list.push_back(current_layer);
240 // Stop traversal when an opaque layer is encountered.
241 // FIXME : It would be possible for the following occlusion culling test to
242 // be more aggressive on layers with no repeat by testing whether the image
243 // covers the layout rect. Testing that here would imply duplicating a lot
244 // of calculations that are currently done in
245 // LayoutBoxModelObject::paintFillLayer. A more efficient solution might be
246 // to move the layer recursion into paintFillLayer, or to compute the layer
247 // geometry here and pass it down.
248
249 // TODO(trchen): Need to check compositing mode as well.
250 if (current_layer->GetBlendMode() != BlendMode::kNormal)
251 is_non_associative = true;
252
253 // TODO(trchen): A fill layer cannot paint if the calculated tile size is
254 // empty. This occlusion check can be wrong.
255 if (current_layer->ClipOccludesNextLayers() &&
256 current_layer->ImageOccludesNextLayers(*document_, style_)) {
257 if (current_layer->Clip() == EFillBox::kBorder)
258 is_non_associative = false;
259 break;
260 }
261 }
262 return is_non_associative;
263 }
264
FillLayerInfo(const Document & doc,const ComputedStyle & style,bool is_scroll_container,Color bg_color,const FillLayer & layer,BackgroundBleedAvoidance bleed_avoidance,RespectImageOrientationEnum respect_image_orientation,PhysicalBoxSides sides_to_include,bool is_inline,bool is_painting_scrolling_background)265 BoxPainterBase::FillLayerInfo::FillLayerInfo(
266 const Document& doc,
267 const ComputedStyle& style,
268 bool is_scroll_container,
269 Color bg_color,
270 const FillLayer& layer,
271 BackgroundBleedAvoidance bleed_avoidance,
272 RespectImageOrientationEnum respect_image_orientation,
273 PhysicalBoxSides sides_to_include,
274 bool is_inline,
275 bool is_painting_scrolling_background)
276 : image(layer.GetImage()),
277 color(bg_color),
278 respect_image_orientation(respect_image_orientation),
279 sides_to_include(sides_to_include),
280 is_bottom_layer(!layer.Next()),
281 is_border_fill(layer.Clip() == EFillBox::kBorder),
282 is_clipped_with_local_scrolling(is_scroll_container &&
283 layer.Attachment() ==
284 EFillAttachment::kLocal) {
285 // When printing backgrounds is disabled or using economy mode,
286 // change existing background colors and images to a solid white background.
287 // If there's no bg color or image, leave it untouched to avoid affecting
288 // transparency. We don't try to avoid loading the background images,
289 // because this style flag is only set when printing, and at that point
290 // we've already loaded the background images anyway. (To avoid loading the
291 // background images we'd have to do this check when applying styles rather
292 // than while layout.)
293 if (BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(doc, style)) {
294 // Note that we can't reuse this variable below because the bgColor might
295 // be changed.
296 bool should_paint_background_color = is_bottom_layer && color.Alpha();
297 if (image || should_paint_background_color) {
298 color = Color::kWhite;
299 image = nullptr;
300 }
301 }
302
303 // Background images are not allowed at the inline level in forced colors
304 // mode when forced-color-adjust is auto. This ensures that the inline images
305 // are not painted on top of the forced colors mode backplate.
306 if (doc.InForcedColorsMode() && is_inline &&
307 style.ForcedColorAdjust() != EForcedColorAdjust::kNone)
308 image = nullptr;
309
310 const bool has_rounded_border =
311 style.HasBorderRadius() && !sides_to_include.IsEmpty();
312 // BorderFillBox radius clipping is taken care of by
313 // BackgroundBleedClip{Only,Layer}
314 is_rounded_fill =
315 has_rounded_border && !is_painting_scrolling_background &&
316 !(is_border_fill && BleedAvoidanceIsClipping(bleed_avoidance));
317
318 should_paint_image = image && image->CanRender();
319 should_paint_color =
320 is_bottom_layer && color.Alpha() &&
321 (!should_paint_image || !layer.ImageOccludesNextLayers(doc, style));
322 should_paint_color_with_paint_worklet_image =
323 should_paint_color &&
324 RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled();
325 }
326
327 namespace {
328
329 // Given the |size| that the whole image should draw at, and the input phase
330 // requested by the content, and the space between repeated tiles, return a
331 // rectangle with |size| and a location that respects the phase but is no more
332 // than one size + space in magnitude. In practice, this means that if there is
333 // no repeating the returned rect would contain the destination_offset
334 // location. The destination_offset passed here must exactly match the location
335 // of the subset in a following call to ComputeSubsetForBackground.
ComputePhaseForBackground(const FloatPoint & destination_offset,const FloatSize & size,const FloatPoint & phase,const FloatSize & spacing)336 FloatRect ComputePhaseForBackground(const FloatPoint& destination_offset,
337 const FloatSize& size,
338 const FloatPoint& phase,
339 const FloatSize& spacing) {
340 const FloatSize step_per_tile(size + spacing);
341 return FloatRect(
342 FloatPoint(
343 destination_offset.X() + fmodf(-phase.X(), step_per_tile.Width()),
344 destination_offset.Y() + fmodf(-phase.Y(), step_per_tile.Height())),
345 size);
346 }
347
348 // Compute the image subset, in intrinsic image coordinates, that gets mapped
349 // onto the |subset|, when the whole image would be drawn with phase and size
350 // given by |phase_and_size|. Assumes |phase_and_size| contains |subset|. The
351 // location of the requested subset should be the painting snapped location, or
352 // whatever was used as a destination_offset in ComputePhaseForBackground.
353 //
354 // It is used to undo the offset added in ComputePhaseForBackground. The size
355 // of requested subset should be the unsnapped size so that the computed
356 // scale and location in the source image can be correctly determined.
ComputeSubsetForBackground(const FloatRect & phase_and_size,const FloatRect & subset,const FloatSize & intrinsic_size)357 FloatRect ComputeSubsetForBackground(const FloatRect& phase_and_size,
358 const FloatRect& subset,
359 const FloatSize& intrinsic_size) {
360 // TODO(schenney): Re-enable this after determining why it fails for
361 // CAP, and maybe other cases.
362 // DCHECK(phase_and_size.Contains(subset));
363
364 const FloatSize scale(phase_and_size.Width() / intrinsic_size.Width(),
365 phase_and_size.Height() / intrinsic_size.Height());
366 return FloatRect((subset.X() - phase_and_size.X()) / scale.Width(),
367 (subset.Y() - phase_and_size.Y()) / scale.Height(),
368 subset.Width() / scale.Width(),
369 subset.Height() / scale.Height());
370 }
371
SnapSourceRectIfNearIntegral(const FloatRect src_rect)372 FloatRect SnapSourceRectIfNearIntegral(const FloatRect src_rect) {
373 // Round to avoid filtering pulling in neighboring pixels, for the
374 // common case of sprite maps, but only if we're close to an integral size.
375 // "Close" in this context means we will allow floating point inaccuracy,
376 // when converted to layout units, to be at most one LayoutUnit::Epsilon and
377 // still snap.
378 if (std::abs(std::round(src_rect.X()) - src_rect.X()) <=
379 LayoutUnit::Epsilon() &&
380 std::abs(std::round(src_rect.Y()) - src_rect.Y()) <=
381 LayoutUnit::Epsilon() &&
382 std::abs(std::round(src_rect.MaxX()) - src_rect.MaxX()) <=
383 LayoutUnit::Epsilon() &&
384 std::abs(std::round(src_rect.MaxY()) - src_rect.MaxY()) <=
385 LayoutUnit::Epsilon()) {
386 return FloatRect(RoundedIntRect(src_rect));
387 }
388 return src_rect;
389 }
390
391 // The unsnapped_subset_size should be the target painting area implied by the
392 // content, without any snapping applied. It is necessary to correctly
393 // compute the subset of the source image to paint into the destination.
394 // The snapped_paint_rect should be the target destination for painting into.
395 // The phase is never snapped.
396 // The tile_size is the total image size. The mapping from this size
397 // to the unsnapped_dest_rect size defines the scaling of the image for
398 // sprite computation.
DrawTiledBackground(GraphicsContext & context,Image * image,const FloatSize & unsnapped_subset_size,const FloatRect & snapped_paint_rect,const FloatPoint & phase,const FloatSize & tile_size,SkBlendMode op,const FloatSize & repeat_spacing,bool has_filter_property,RespectImageOrientationEnum respect_orientation)399 void DrawTiledBackground(GraphicsContext& context,
400 Image* image,
401 const FloatSize& unsnapped_subset_size,
402 const FloatRect& snapped_paint_rect,
403 const FloatPoint& phase,
404 const FloatSize& tile_size,
405 SkBlendMode op,
406 const FloatSize& repeat_spacing,
407 bool has_filter_property,
408 RespectImageOrientationEnum respect_orientation) {
409 DCHECK(!tile_size.IsEmpty());
410
411 // Use the intrinsic size of the image if it has one, otherwise force the
412 // generated image to be the tile size.
413 FloatSize intrinsic_tile_size(image->Size());
414 // image-resolution information is baked into the given parameters, but we
415 // need oriented size. That requires explicitly applying orientation here.
416 if (respect_orientation &&
417 image->CurrentFrameOrientation().UsesWidthAsHeight()) {
418 intrinsic_tile_size = intrinsic_tile_size.TransposedSize();
419 }
420
421 FloatSize scale(1, 1);
422 if (!image->HasIntrinsicSize() ||
423 // TODO(crbug.com/1042783): This is not checking for real empty image
424 // (for which we have checked and skipped the whole FillLayer), but for
425 // that a subpixel image size is rounded to empty, to avoid infinite tile
426 // scale that would be calculated in the |else| part.
427 // We should probably support subpixel size here.
428 intrinsic_tile_size.IsEmpty()) {
429 intrinsic_tile_size = tile_size;
430 } else {
431 scale = FloatSize(tile_size.Width() / intrinsic_tile_size.Width(),
432 tile_size.Height() / intrinsic_tile_size.Height());
433 }
434
435 const FloatRect one_tile_rect = ComputePhaseForBackground(
436 snapped_paint_rect.Location(), tile_size, phase, repeat_spacing);
437
438 // Check and see if a single draw of the image can cover the entire area we
439 // are supposed to tile. The dest_rect_for_subset must use the same
440 // location that was used in ComputePhaseForBackground and the unsnapped
441 // destination rect in order to correctly evaluate the subset size and
442 // location in the presence of border snapping and zoom.
443 FloatRect dest_rect_for_subset(snapped_paint_rect.Location(),
444 unsnapped_subset_size);
445 if (one_tile_rect.Contains(dest_rect_for_subset)) {
446 FloatRect visible_src_rect = ComputeSubsetForBackground(
447 one_tile_rect, dest_rect_for_subset, intrinsic_tile_size);
448 visible_src_rect = SnapSourceRectIfNearIntegral(visible_src_rect);
449
450 // When respecting image orientation, the drawing code expects the source
451 // rect to be in the unrotated image space, but we have computed it here in
452 // the rotated space in order to position and size the background. Undo the
453 // src rect rotation if necessary.
454 if (respect_orientation && !image->HasDefaultOrientation()) {
455 visible_src_rect = image->CorrectSrcRectForImageOrientation(
456 intrinsic_tile_size, visible_src_rect);
457 }
458
459 context.DrawImage(image, Image::kSyncDecode, snapped_paint_rect,
460 &visible_src_rect, has_filter_property, op,
461 respect_orientation);
462 return;
463 }
464
465 // At this point we have decided to tile the image to fill the dest rect.
466 // Note that this tile rect uses the image's pre-scaled size.
467 FloatRect tile_rect(FloatPoint(), intrinsic_tile_size);
468
469 // Farther down the pipeline we will use the scaled tile size to determine
470 // which dimensions to clamp or repeat in. We do not want to repeat when the
471 // tile size rounds to match the dest in a given dimension, to avoid having
472 // a single row or column repeated when the developer almost certainly
473 // intended the image to not repeat (this generally occurs under zoom).
474 //
475 // So detect when we do not want to repeat and set the scale to round the
476 // values in that dimension.
477 if (fabs(tile_size.Width() - snapped_paint_rect.Width()) <= 0.5) {
478 scale.SetWidth(snapped_paint_rect.Width() / intrinsic_tile_size.Width());
479 }
480 if (fabs(tile_size.Height() - snapped_paint_rect.Height()) <= 0.5) {
481 scale.SetHeight(snapped_paint_rect.Height() / intrinsic_tile_size.Height());
482 }
483
484 // This call takes the unscaled image, applies the given scale, and paints
485 // it into the snapped_dest_rect using phase from one_tile_rect and the
486 // given repeat spacing. Note the phase is already scaled.
487 context.DrawImageTiled(image, snapped_paint_rect, tile_rect, scale,
488 one_tile_rect.Location(), repeat_spacing, op,
489 respect_orientation);
490 }
491
FillRectWithPaintWorklet(const BoxPainterBase::FillLayerInfo & info,Node * node,const FloatRoundedRect & dest_rect,GraphicsContext & context)492 void FillRectWithPaintWorklet(const BoxPainterBase::FillLayerInfo& info,
493 Node* node,
494 const FloatRoundedRect& dest_rect,
495 GraphicsContext& context) {
496 FloatRect src_rect = dest_rect.Rect();
497 std::unique_ptr<NativePaintImageGenerator> generator =
498 NativePaintImageGenerator::Create();
499 scoped_refptr<Image> paint_worklet_image =
500 generator->Paint(src_rect.Size(), SkColor(info.color));
501 context.DrawImageRRect(
502 paint_worklet_image.get(), Image::kSyncDecode, dest_rect, src_rect,
503 node && node->ComputedStyleRef().HasFilterInducingProperty(),
504 SkBlendMode::kSrcOver, info.respect_image_orientation);
505 }
506
PaintFastBottomLayer(Node * node,const PaintInfo & paint_info,const BoxPainterBase::FillLayerInfo & info,const PhysicalRect & rect,const FloatRoundedRect & border_rect,BackgroundImageGeometry & geometry,Image * image,SkBlendMode composite_op)507 inline bool PaintFastBottomLayer(Node* node,
508 const PaintInfo& paint_info,
509 const BoxPainterBase::FillLayerInfo& info,
510 const PhysicalRect& rect,
511 const FloatRoundedRect& border_rect,
512 BackgroundImageGeometry& geometry,
513 Image* image,
514 SkBlendMode composite_op) {
515 // Painting a background image from an ancestor onto a cell is a complex case.
516 if (geometry.CellUsingContainerBackground())
517 return false;
518 // Complex cases not handled on the fast path.
519 if (!info.is_bottom_layer || !info.is_border_fill)
520 return false;
521
522 // Transparent layer, nothing to paint.
523 if (!info.should_paint_color && !info.should_paint_image)
524 return true;
525
526 // Compute the destination rect for painting the color here because we may
527 // need it for computing the image painting rect for optimization.
528 GraphicsContext& context = paint_info.context;
529 FloatRoundedRect color_border =
530 info.is_rounded_fill ? border_rect
531 : FloatRoundedRect(PixelSnappedIntRect(rect));
532 // When the layer has an image, figure out whether it is covered by a single
533 // tile. The border for painting images may not be the same as the color due
534 // to optimizations for the image painting destination that avoid painting
535 // under the border.
536 FloatRect image_tile;
537 FloatRoundedRect image_border;
538 if (info.should_paint_image) {
539 // Avoid image shaders when printing (poorly supported in PDF).
540 if (info.is_rounded_fill && paint_info.IsPrinting())
541 return false;
542
543 // Compute the dest rect we will be using for images.
544 image_border =
545 info.is_rounded_fill
546 ? color_border
547 : FloatRoundedRect(FloatRect(geometry.SnappedDestRect()));
548
549 if (!image_border.Rect().IsEmpty()) {
550 // We cannot optimize if the tile is too small.
551 if (geometry.TileSize().width < image_border.Rect().Width() ||
552 geometry.TileSize().height < image_border.Rect().Height())
553 return false;
554
555 // Phase calculation uses the actual painted location, given by the
556 // border-snapped destination rect.
557 image_tile = ComputePhaseForBackground(
558 FloatPoint(geometry.SnappedDestRect().offset),
559 FloatSize(geometry.TileSize()), geometry.Phase(),
560 FloatSize(geometry.SpaceSize()));
561
562 // Force the image tile to LayoutUnit precision, which is the precision
563 // it was calculated in. This avoids bleeding due to values very close to
564 // integers.
565 // The test images/sprite-no-bleed.html fails on two of the sub-cases
566 // due to this rounding still not being enough to make the Contains check
567 // pass. The best way to fix this would be to remove the paint rect offset
568 // from the tile computation, because we effectively add it in
569 // ComputePhaseForBackground then remove it in ComputeSubsetForBackground.
570 image_tile =
571 FloatRect(PhysicalRect::FastAndLossyFromFloatRect(image_tile));
572 // We cannot optimize if the tile is misaligned.
573 if (!image_tile.Contains(image_border.Rect()))
574 return false;
575 }
576 }
577
578 // At this point we're committed to the fast path: the destination (r)rect
579 // fits within a single tile, and we can paint it using direct draw(R)Rect()
580 // calls.
581 base::Optional<RoundedInnerRectClipper> clipper;
582 if (info.is_rounded_fill && !color_border.IsRenderable()) {
583 // When the rrect is not renderable, we resort to clipping.
584 // RoundedInnerRectClipper handles this case via discrete, corner-wise
585 // clipping.
586 clipper.emplace(context, rect, color_border);
587 color_border.SetRadii(FloatRoundedRect::Radii());
588 image_border.SetRadii(FloatRoundedRect::Radii());
589 }
590
591 // Paint the color if needed.
592 if (info.should_paint_color) {
593 if (info.should_paint_color_with_paint_worklet_image) {
594 FillRectWithPaintWorklet(info, node, color_border, context);
595 } else {
596 context.FillRoundedRect(color_border, info.color);
597 }
598 }
599
600 // Paint the image if needed.
601 if (!info.should_paint_image || !image || image_tile.IsEmpty())
602 return true;
603
604 // Generated images will be created at the desired tile size, so assume their
605 // intrinsic size is the requested tile size.
606 bool has_intrinsic_size = image->HasIntrinsicSize();
607 const FloatSize intrinsic_tile_size =
608 !has_intrinsic_size
609 ? image_tile.Size()
610 : FloatSize(image->Size(info.respect_image_orientation));
611
612 // Subset computation needs the same location as was used with
613 // ComputePhaseForBackground above, but needs the unsnapped destination
614 // size to correctly calculate sprite subsets in the presence of zoom. But if
615 // this is a generated image sized according to the tile size (which is a
616 // snapped value), use the snapped dest rect instead.
617 FloatRect dest_rect_for_subset(
618 FloatPoint(geometry.SnappedDestRect().offset),
619 !has_intrinsic_size ? FloatSize(geometry.SnappedDestRect().size)
620 : FloatSize(geometry.UnsnappedDestRect().size));
621 // Content providers almost always choose source pixels at integer locations,
622 // so snap to integers. This is particuarly important for sprite maps.
623 // Calculation up to this point, in LayoutUnits, can lead to small variations
624 // from integer size, so it is safe to round without introducing major issues.
625 const FloatRect unrounded_subset = ComputeSubsetForBackground(
626 image_tile, dest_rect_for_subset, intrinsic_tile_size);
627 FloatRect src_rect = SnapSourceRectIfNearIntegral(unrounded_subset);
628
629 // If we have snapped the image size to 0, revert the rounding.
630 if (src_rect.IsEmpty())
631 src_rect = unrounded_subset;
632
633 // When respecting image orientation, the drawing code expects the source rect
634 // to be in the unrotated image space, but we have computed it here in the
635 // rotated space in order to position and size the background. Undo the src
636 // rect rotation if necessaary.
637 if (info.respect_image_orientation && !image->HasDefaultOrientation()) {
638 src_rect =
639 image->CorrectSrcRectForImageOrientation(intrinsic_tile_size, src_rect);
640 }
641
642 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
643 "data",
644 inspector_paint_image_event::Data(
645 node, *info.image, FloatRect(image->Rect()),
646 FloatRect(image_border.Rect())));
647
648 // Since there is no way for the developer to specify decode behavior, use
649 // kSync by default.
650 context.DrawImageRRect(
651 image, Image::kSyncDecode, image_border, src_rect,
652 node && node->ComputedStyleRef().HasFilterInducingProperty(),
653 composite_op, info.respect_image_orientation);
654
655 if (info.image && info.image->IsImageResource()) {
656 PaintTimingDetector::NotifyBackgroundImagePaint(
657 node, image, To<StyleFetchedImage>(info.image),
658 paint_info.context.GetPaintController().CurrentPaintChunkProperties(),
659 RoundedIntRect(image_border.Rect()));
660 }
661 if (node && info.image && info.image->IsImageResource()) {
662 LocalDOMWindow* window = node->GetDocument().domWindow();
663 DCHECK(window);
664 ImageElementTiming::From(*window).NotifyBackgroundImagePainted(
665 node, To<StyleFetchedImage>(info.image),
666 context.GetPaintController().CurrentPaintChunkProperties(),
667 RoundedIntRect(image_border.Rect()));
668 }
669 return true;
670 }
671
672 // Inset the background rect by a "safe" amount: 1/2 border-width for opaque
673 // border styles, 1/6 border-width for double borders.
BackgroundRoundedRectAdjustedForBleedAvoidance(const ComputedStyle & style,const PhysicalRect & border_rect,bool object_has_multiple_boxes,PhysicalBoxSides sides_to_include,FloatRoundedRect background_rounded_rect)674 FloatRoundedRect BackgroundRoundedRectAdjustedForBleedAvoidance(
675 const ComputedStyle& style,
676 const PhysicalRect& border_rect,
677 bool object_has_multiple_boxes,
678 PhysicalBoxSides sides_to_include,
679 FloatRoundedRect background_rounded_rect) {
680 // TODO(fmalita): we should be able to fold these parameters into
681 // BoxBorderInfo or BoxDecorationData and avoid calling getBorderEdgeInfo
682 // redundantly here.
683 BorderEdge edges[4];
684 style.GetBorderEdgeInfo(edges, sides_to_include);
685
686 // Use the most conservative inset to avoid mixed-style corner issues.
687 float fractional_inset = 1.0f / 2;
688 for (auto& edge : edges) {
689 if (edge.BorderStyle() == EBorderStyle::kDouble) {
690 fractional_inset = 1.0f / 6;
691 break;
692 }
693 }
694
695 FloatRectOutsets insets(
696 -fractional_inset *
697 edges[static_cast<unsigned>(BoxSide::kTop)].UsedWidth(),
698 -fractional_inset *
699 edges[static_cast<unsigned>(BoxSide::kRight)].UsedWidth(),
700 -fractional_inset *
701 edges[static_cast<unsigned>(BoxSide::kBottom)].UsedWidth(),
702 -fractional_inset *
703 edges[static_cast<unsigned>(BoxSide::kLeft)].UsedWidth());
704
705 FloatRect inset_rect(background_rounded_rect.Rect());
706 inset_rect.Expand(insets);
707 FloatRoundedRect::Radii inset_radii(background_rounded_rect.GetRadii());
708 inset_radii.Shrink(-insets.Top(), -insets.Bottom(), -insets.Left(),
709 -insets.Right());
710 return FloatRoundedRect(inset_rect, inset_radii);
711 }
712
RoundedBorderRectForClip(const ComputedStyle & style,const BoxPainterBase::FillLayerInfo & info,const FillLayer & bg_layer,const PhysicalRect & rect,bool object_has_multiple_boxes,const PhysicalSize & flow_box_size,BackgroundBleedAvoidance bleed_avoidance,LayoutRectOutsets border_padding_insets)713 FloatRoundedRect RoundedBorderRectForClip(
714 const ComputedStyle& style,
715 const BoxPainterBase::FillLayerInfo& info,
716 const FillLayer& bg_layer,
717 const PhysicalRect& rect,
718 bool object_has_multiple_boxes,
719 const PhysicalSize& flow_box_size,
720 BackgroundBleedAvoidance bleed_avoidance,
721 LayoutRectOutsets border_padding_insets) {
722 if (!info.is_rounded_fill)
723 return FloatRoundedRect();
724
725 FloatRoundedRect border = RoundedBorderGeometry::PixelSnappedRoundedBorder(
726 style, rect, info.sides_to_include);
727 if (object_has_multiple_boxes) {
728 FloatRoundedRect segment_border =
729 RoundedBorderGeometry::PixelSnappedRoundedBorder(
730 style,
731 PhysicalRect(PhysicalOffset(),
732 PhysicalSize(FlooredIntSize(flow_box_size))),
733 info.sides_to_include);
734 border.SetRadii(segment_border.GetRadii());
735 }
736
737 if (info.is_border_fill &&
738 bleed_avoidance == kBackgroundBleedShrinkBackground) {
739 border = BackgroundRoundedRectAdjustedForBleedAvoidance(
740 style, rect, object_has_multiple_boxes, info.sides_to_include, border);
741 }
742
743 // Clip to the padding or content boxes as necessary.
744 // Use FastAndLossyFromFloatRect because we know it has been pixel snapped.
745 PhysicalRect border_rect =
746 PhysicalRect::FastAndLossyFromFloatRect(border.Rect());
747 if (bg_layer.Clip() == EFillBox::kContent) {
748 border = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(
749 style, border_rect, border_padding_insets, info.sides_to_include);
750 } else if (bg_layer.Clip() == EFillBox::kPadding) {
751 border = RoundedBorderGeometry::PixelSnappedRoundedInnerBorder(
752 style, border_rect, info.sides_to_include);
753 }
754 return border;
755 }
756
PaintFillLayerBackground(GraphicsContext & context,const BoxPainterBase::FillLayerInfo & info,Node * node,Image * image,SkBlendMode composite_op,const BackgroundImageGeometry & geometry,const PhysicalRect & scrolled_paint_rect)757 void PaintFillLayerBackground(GraphicsContext& context,
758 const BoxPainterBase::FillLayerInfo& info,
759 Node* node,
760 Image* image,
761 SkBlendMode composite_op,
762 const BackgroundImageGeometry& geometry,
763 const PhysicalRect& scrolled_paint_rect) {
764 // Paint the color first underneath all images, culled if background image
765 // occludes it.
766 // TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the
767 // culling test by verifying whether the background image covers the entire
768 // painting area.
769 if (info.is_bottom_layer && info.color.Alpha() && info.should_paint_color) {
770 IntRect background_rect(PixelSnappedIntRect(scrolled_paint_rect));
771 if (info.should_paint_color_with_paint_worklet_image) {
772 FillRectWithPaintWorklet(info, node, FloatRoundedRect(background_rect),
773 context);
774 } else {
775 context.FillRect(background_rect, info.color);
776 }
777 }
778
779 // No progressive loading of the background image.
780 // NOTE: This method can be called with no image in situations when a bad
781 // resource locator is given such as "//:0", so still check for image.
782 if (info.should_paint_image && !geometry.SnappedDestRect().IsEmpty() &&
783 !geometry.TileSize().IsEmpty() && image) {
784 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
785 "data",
786 inspector_paint_image_event::Data(
787 node, *info.image, FloatRect(image->Rect()),
788 FloatRect(scrolled_paint_rect)));
789 DrawTiledBackground(
790 context, image, FloatSize(geometry.UnsnappedDestRect().size),
791 FloatRect(geometry.SnappedDestRect()), geometry.Phase(),
792 FloatSize(geometry.TileSize()), composite_op,
793 FloatSize(geometry.SpaceSize()),
794 node && node->ComputedStyleRef().HasFilterInducingProperty(),
795 info.respect_image_orientation);
796 if (info.image && info.image->IsImageResource()) {
797 PaintTimingDetector::NotifyBackgroundImagePaint(
798 node, image, To<StyleFetchedImage>(info.image),
799 context.GetPaintController().CurrentPaintChunkProperties(),
800 EnclosingIntRect(geometry.SnappedDestRect()));
801 }
802 if (node && info.image && info.image->IsImageResource()) {
803 LocalDOMWindow* window = node->GetDocument().domWindow();
804 DCHECK(window);
805 ImageElementTiming::From(*window).NotifyBackgroundImagePainted(
806 node, To<StyleFetchedImage>(info.image),
807 context.GetPaintController().CurrentPaintChunkProperties(),
808 EnclosingIntRect(geometry.SnappedDestRect()));
809 }
810 }
811 }
812
AdjustOutsetsForEdgeInclusion(const LayoutRectOutsets outsets,const BoxPainterBase::FillLayerInfo & info)813 LayoutRectOutsets AdjustOutsetsForEdgeInclusion(
814 const LayoutRectOutsets outsets,
815 const BoxPainterBase::FillLayerInfo& info) {
816 LayoutRectOutsets adjusted = outsets;
817 if (!info.sides_to_include.top)
818 adjusted.SetTop(LayoutUnit());
819 if (!info.sides_to_include.right)
820 adjusted.SetRight(LayoutUnit());
821 if (!info.sides_to_include.bottom)
822 adjusted.SetBottom(LayoutUnit());
823 if (!info.sides_to_include.left)
824 adjusted.SetLeft(LayoutUnit());
825 return adjusted;
826 }
827
ShouldApplyBlendOperation(const BoxPainterBase::FillLayerInfo & info,const FillLayer & layer)828 bool ShouldApplyBlendOperation(const BoxPainterBase::FillLayerInfo& info,
829 const FillLayer& layer) {
830 // For a mask layer, don't use the operator if this is the bottom layer.
831 return !info.is_bottom_layer || layer.GetType() != EFillLayerType::kMask;
832 }
833
834 } // anonymous namespace
835
AdjustedBorderOutsets(const FillLayerInfo & info) const836 LayoutRectOutsets BoxPainterBase::AdjustedBorderOutsets(
837 const FillLayerInfo& info) const {
838 return AdjustOutsetsForEdgeInclusion(ComputeBorders(), info);
839 }
840
PaintFillLayer(const PaintInfo & paint_info,const Color & color,const FillLayer & bg_layer,const PhysicalRect & rect,BackgroundBleedAvoidance bleed_avoidance,BackgroundImageGeometry & geometry,bool object_has_multiple_boxes,const PhysicalSize & flow_box_size)841 void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info,
842 const Color& color,
843 const FillLayer& bg_layer,
844 const PhysicalRect& rect,
845 BackgroundBleedAvoidance bleed_avoidance,
846 BackgroundImageGeometry& geometry,
847 bool object_has_multiple_boxes,
848 const PhysicalSize& flow_box_size) {
849 GraphicsContext& context = paint_info.context;
850 if (rect.IsEmpty())
851 return;
852
853 const FillLayerInfo info =
854 GetFillLayerInfo(color, bg_layer, bleed_avoidance,
855 IsPaintingScrollingBackground(paint_info));
856 // If we're not actually going to paint anything, abort early.
857 if (!info.should_paint_image && !info.should_paint_color)
858 return;
859
860 GraphicsContextStateSaver clip_with_scrolling_state_saver(
861 context, info.is_clipped_with_local_scrolling);
862 auto scrolled_paint_rect =
863 AdjustRectForScrolledContent(paint_info, info, rect);
864 const auto did_adjust_paint_rect = scrolled_paint_rect != rect;
865
866 scoped_refptr<Image> image;
867 SkBlendMode composite_op = SkBlendMode::kSrcOver;
868 base::Optional<ScopedInterpolationQuality> interpolation_quality_context;
869 if (info.should_paint_image) {
870 geometry.Calculate(paint_info.PaintContainer(), paint_info.phase,
871 paint_info.GetGlobalPaintFlags(), bg_layer,
872 scrolled_paint_rect);
873 image = info.image->GetImage(
874 geometry.ImageClient(), geometry.ImageDocument(), geometry.ImageStyle(),
875 FloatSize(geometry.TileSize()));
876 interpolation_quality_context.emplace(context,
877 geometry.ImageInterpolationQuality());
878
879 if (ShouldApplyBlendOperation(info, bg_layer)) {
880 composite_op = WebCoreCompositeToSkiaComposite(bg_layer.Composite(),
881 bg_layer.GetBlendMode());
882 }
883 }
884
885 LayoutRectOutsets border = ComputeBorders();
886 LayoutRectOutsets padding = ComputePadding();
887 LayoutRectOutsets border_padding_insets = -(border + padding);
888 FloatRoundedRect border_rect = RoundedBorderRectForClip(
889 style_, info, bg_layer, rect, object_has_multiple_boxes, flow_box_size,
890 bleed_avoidance, border_padding_insets);
891
892 // Fast path for drawing simple color backgrounds. Do not use the fast
893 // path with images if the dest rect has been adjusted for scrolling
894 // backgrounds because correcting the dest rect for scrolling reduces the
895 // accuracy of the destination rects. Also disable the fast path for images
896 // if we are shrinking the background for bleed avoidance, because this
897 // adjusts the border rects in a way that breaks the optimization.
898 bool disable_fast_path =
899 info.should_paint_image &&
900 (bleed_avoidance == kBackgroundBleedShrinkBackground ||
901 did_adjust_paint_rect);
902 if (!disable_fast_path &&
903 PaintFastBottomLayer(node_, paint_info, info, rect, border_rect, geometry,
904 image.get(), composite_op)) {
905 return;
906 }
907
908 base::Optional<RoundedInnerRectClipper> clip_to_border;
909 if (info.is_rounded_fill)
910 clip_to_border.emplace(context, rect, border_rect);
911
912 if (bg_layer.Clip() == EFillBox::kText) {
913 PaintFillLayerTextFillBox(context, info, image.get(), composite_op,
914 geometry, rect, scrolled_paint_rect,
915 object_has_multiple_boxes);
916 return;
917 }
918
919 GraphicsContextStateSaver background_clip_state_saver(context, false);
920 switch (bg_layer.Clip()) {
921 case EFillBox::kPadding:
922 case EFillBox::kContent: {
923 if (info.is_rounded_fill)
924 break;
925
926 // Clip to the padding or content boxes as necessary.
927 PhysicalRect clip_rect = scrolled_paint_rect;
928 clip_rect.Contract(AdjustOutsetsForEdgeInclusion(border, info));
929 if (bg_layer.Clip() == EFillBox::kContent)
930 clip_rect.Contract(AdjustOutsetsForEdgeInclusion(padding, info));
931 background_clip_state_saver.Save();
932 // TODO(chrishtr): this should be pixel-snapped.
933 context.Clip(FloatRect(clip_rect));
934 break;
935 }
936 case EFillBox::kBorder:
937 break;
938 case EFillBox::kText: // fall through
939 default:
940 NOTREACHED();
941 break;
942 }
943
944 PaintFillLayerBackground(context, info, node_, image.get(), composite_op,
945 geometry, scrolled_paint_rect);
946 }
947
PaintFillLayerTextFillBox(GraphicsContext & context,const BoxPainterBase::FillLayerInfo & info,Image * image,SkBlendMode composite_op,const BackgroundImageGeometry & geometry,const PhysicalRect & rect,const PhysicalRect & scrolled_paint_rect,bool object_has_multiple_boxes)948 void BoxPainterBase::PaintFillLayerTextFillBox(
949 GraphicsContext& context,
950 const BoxPainterBase::FillLayerInfo& info,
951 Image* image,
952 SkBlendMode composite_op,
953 const BackgroundImageGeometry& geometry,
954 const PhysicalRect& rect,
955 const PhysicalRect& scrolled_paint_rect,
956 bool object_has_multiple_boxes) {
957 // First figure out how big the mask has to be. It should be no bigger
958 // than what we need to actually render, so we should intersect the dirty
959 // rect with the border box of the background.
960 IntRect mask_rect = PixelSnappedIntRect(rect);
961
962 // We draw the background into a separate layer, to be later masked with
963 // yet another layer holding the text content.
964 GraphicsContextStateSaver background_clip_state_saver(context, false);
965 background_clip_state_saver.Save();
966 context.Clip(mask_rect);
967 context.BeginLayer(1, composite_op);
968
969 PaintFillLayerBackground(context, info, node_, image, SkBlendMode::kSrcOver,
970 geometry, scrolled_paint_rect);
971
972 // Create the text mask layer and draw the text into the mask. We do this by
973 // painting using a special paint phase that signals to InlineTextBoxes that
974 // they should just add their contents to the clip.
975 context.BeginLayer(1, SkBlendMode::kDstIn);
976
977 PaintTextClipMask(context, mask_rect, scrolled_paint_rect.offset,
978 object_has_multiple_boxes);
979
980 context.EndLayer(); // Text mask layer.
981 context.EndLayer(); // Background layer.
982 }
983
PaintBorder(const ImageResourceObserver & obj,const Document & document,Node * node,const PaintInfo & info,const PhysicalRect & rect,const ComputedStyle & style,BackgroundBleedAvoidance bleed_avoidance,PhysicalBoxSides sides_to_include)984 void BoxPainterBase::PaintBorder(const ImageResourceObserver& obj,
985 const Document& document,
986 Node* node,
987 const PaintInfo& info,
988 const PhysicalRect& rect,
989 const ComputedStyle& style,
990 BackgroundBleedAvoidance bleed_avoidance,
991 PhysicalBoxSides sides_to_include) {
992 // border-image is not affected by border-radius.
993 if (NinePieceImagePainter::Paint(info.context, obj, document, node, rect,
994 style, style.BorderImage())) {
995 return;
996 }
997
998 const BoxBorderPainter border_painter(rect, style, bleed_avoidance,
999 sides_to_include);
1000 border_painter.PaintBorder(info, rect);
1001 }
1002
PaintMaskImages(const PaintInfo & paint_info,const PhysicalRect & paint_rect,const ImageResourceObserver & obj,BackgroundImageGeometry & geometry,PhysicalBoxSides sides_to_include)1003 void BoxPainterBase::PaintMaskImages(const PaintInfo& paint_info,
1004 const PhysicalRect& paint_rect,
1005 const ImageResourceObserver& obj,
1006 BackgroundImageGeometry& geometry,
1007 PhysicalBoxSides sides_to_include) {
1008 if (!style_.HasMask() || style_.Visibility() != EVisibility::kVisible)
1009 return;
1010
1011 PaintFillLayers(paint_info, Color::kTransparent, style_.MaskLayers(),
1012 paint_rect, geometry);
1013 NinePieceImagePainter::Paint(paint_info.context, obj, *document_, node_,
1014 paint_rect, style_, style_.MaskBoxImage(),
1015 sides_to_include);
1016 }
1017
ShouldSkipPaintUnderInvalidationChecking(const LayoutBox & box)1018 bool BoxPainterBase::ShouldSkipPaintUnderInvalidationChecking(
1019 const LayoutBox& box) {
1020 DCHECK(RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled());
1021
1022 // Disable paint under-invalidation checking for cases that under-invalidation
1023 // is intensional and/or harmless.
1024
1025 // A box having delayed-invalidation may change before it's actually
1026 // invalidated. Note that we still report harmless under-invalidation of
1027 // non-delayed-invalidation animated background, which should be ignored.
1028 if (box.ShouldDelayFullPaintInvalidation())
1029 return true;
1030
1031 // We always paint a MediaSliderPart using the latest data (buffered ranges,
1032 // current time and duration) which may be different from the cached data.
1033 if (box.StyleRef().EffectiveAppearance() == kMediaSliderPart)
1034 return true;
1035
1036 // We paint an indeterminate progress based on the position calculated from
1037 // the animation progress. Harmless under-invalidatoin may happen during a
1038 // paint that is not scheduled for animation.
1039 if (box.IsProgress() && !To<LayoutProgress>(box).IsDeterminate())
1040 return true;
1041
1042 return false;
1043 }
1044
1045 } // namespace blink
1046