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