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/scrollable_area_painter.h"
6 
7 #include "third_party/blink/renderer/core/layout/layout_view.h"
8 #include "third_party/blink/renderer/core/page/chrome_client.h"
9 #include "third_party/blink/renderer/core/page/page.h"
10 #include "third_party/blink/renderer/core/paint/custom_scrollbar_theme.h"
11 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
12 #include "third_party/blink/renderer/core/paint/paint_info.h"
13 #include "third_party/blink/renderer/core/paint/paint_layer.h"
14 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
15 #include "third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h"
16 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
17 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
18 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
19 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
20 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
21 #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h"
22 #include "third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.h"
23 
24 namespace blink {
25 
PaintResizer(GraphicsContext & context,const IntPoint & paint_offset,const CullRect & cull_rect)26 void ScrollableAreaPainter::PaintResizer(GraphicsContext& context,
27                                          const IntPoint& paint_offset,
28                                          const CullRect& cull_rect) {
29   if (!GetScrollableArea().GetLayoutBox()->StyleRef().HasResize())
30     return;
31 
32   IntRect abs_rect = GetScrollableArea().ResizerCornerRect(kResizerForPointer);
33   if (abs_rect.IsEmpty())
34     return;
35   abs_rect.MoveBy(paint_offset);
36 
37   const auto& client = DisplayItemClientForCorner();
38   if (const auto* resizer = GetScrollableArea().Resizer()) {
39     if (!cull_rect.Intersects(abs_rect))
40       return;
41     CustomScrollbarTheme::PaintIntoRect(*resizer, context,
42                                         PhysicalOffset(paint_offset),
43                                         PhysicalRect(abs_rect));
44     return;
45   }
46 
47   if (DrawingRecorder::UseCachedDrawingIfPossible(context, client,
48                                                   DisplayItem::kResizer))
49     return;
50 
51   DrawingRecorder recorder(context, client, DisplayItem::kResizer);
52 
53   DrawPlatformResizerImage(context, abs_rect);
54 
55   // Draw a frame around the resizer (1px grey line) if there are any scrollbars
56   // present.  Clipping will exclude the right and bottom edges of this frame.
57   if (GetScrollableArea().HasNonOverlayOverflowControls()) {
58     GraphicsContextStateSaver state_saver(context);
59     context.Clip(abs_rect);
60     IntRect larger_corner = abs_rect;
61     larger_corner.SetSize(
62         IntSize(larger_corner.Width() + 1, larger_corner.Height() + 1));
63     context.SetStrokeColor(Color(217, 217, 217));
64     context.SetStrokeThickness(1.0f);
65     context.SetFillColor(Color::kTransparent);
66     context.DrawRect(larger_corner);
67   }
68 }
69 
RecordResizerScrollHitTestData(GraphicsContext & context,const PhysicalOffset & paint_offset)70 void ScrollableAreaPainter::RecordResizerScrollHitTestData(
71     GraphicsContext& context,
72     const PhysicalOffset& paint_offset) {
73   if (!GetScrollableArea().GetLayoutBox()->CanResize())
74     return;
75 
76   IntRect touch_rect = scrollable_area_->ResizerCornerRect(kResizerForTouch);
77   touch_rect.MoveBy(RoundedIntPoint(paint_offset));
78   context.GetPaintController().RecordScrollHitTestData(
79       DisplayItemClientForCorner(), DisplayItem::kResizerScrollHitTest, nullptr,
80       touch_rect);
81 }
82 
DrawPlatformResizerImage(GraphicsContext & context,const IntRect & resizer_corner_rect)83 void ScrollableAreaPainter::DrawPlatformResizerImage(
84     GraphicsContext& context,
85     const IntRect& resizer_corner_rect) {
86   IntPoint points[4];
87   bool on_left = false;
88   if (GetScrollableArea()
89           .GetLayoutBox()
90           ->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
91     on_left = true;
92     points[0].SetX(resizer_corner_rect.X() + 1);
93     points[1].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() -
94                    resizer_corner_rect.Width() / 2);
95     points[2].SetX(points[0].X());
96     points[3].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() -
97                    resizer_corner_rect.Width() * 3 / 4);
98   } else {
99     points[0].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() - 1);
100     points[1].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() / 2);
101     points[2].SetX(points[0].X());
102     points[3].SetX(resizer_corner_rect.X() +
103                    resizer_corner_rect.Width() * 3 / 4);
104   }
105   points[0].SetY(resizer_corner_rect.Y() + resizer_corner_rect.Height() / 2);
106   points[1].SetY(resizer_corner_rect.Y() + resizer_corner_rect.Height() - 1);
107   points[2].SetY(resizer_corner_rect.Y() +
108                  resizer_corner_rect.Height() * 3 / 4);
109   points[3].SetY(points[1].Y());
110 
111   PaintFlags paint_flags;
112   paint_flags.setStyle(PaintFlags::kStroke_Style);
113   paint_flags.setStrokeWidth(1);
114 
115   SkPath line_path;
116 
117   // Draw a dark line, to ensure contrast against a light background
118   line_path.moveTo(points[0].X(), points[0].Y());
119   line_path.lineTo(points[1].X(), points[1].Y());
120   line_path.moveTo(points[2].X(), points[2].Y());
121   line_path.lineTo(points[3].X(), points[3].Y());
122   paint_flags.setColor(SkColorSetARGB(153, 0, 0, 0));
123   context.DrawPath(line_path, paint_flags);
124 
125   // Draw a light line one pixel below the light line,
126   // to ensure contrast against a dark background
127   line_path.rewind();
128   line_path.moveTo(points[0].X(), points[0].Y() + 1);
129   line_path.lineTo(points[1].X() + (on_left ? -1 : 1), points[1].Y());
130   line_path.moveTo(points[2].X(), points[2].Y() + 1);
131   line_path.lineTo(points[3].X() + (on_left ? -1 : 1), points[3].Y());
132   paint_flags.setColor(SkColorSetARGB(153, 255, 255, 255));
133   context.DrawPath(line_path, paint_flags);
134 }
135 
PaintOverflowControls(const PaintInfo & paint_info,const IntPoint & paint_offset)136 void ScrollableAreaPainter::PaintOverflowControls(
137     const PaintInfo& paint_info,
138     const IntPoint& paint_offset) {
139   // Don't do anything if we have no overflow.
140   const auto& box = *GetScrollableArea().GetLayoutBox();
141   if (!box.HasOverflowClip() ||
142       box.StyleRef().Visibility() != EVisibility::kVisible)
143     return;
144 
145   // Overlay overflow controls are painted in the dedicated paint phase, and
146   // normal overflow controls are painted in the background paint phase.
147   if (GetScrollableArea().HasOverlayOverflowControls()) {
148     if (paint_info.phase != PaintPhase::kOverlayOverflowControls)
149       return;
150   } else if (!ShouldPaintSelfBlockBackground(paint_info.phase)) {
151     return;
152   }
153 
154   GraphicsContext& context = paint_info.context;
155   const auto* fragment = paint_info.FragmentToPaint(box);
156   if (!fragment)
157     return;
158 
159   base::Optional<ScopedPaintChunkProperties> scoped_paint_chunk_properties;
160   const auto* properties = fragment->PaintProperties();
161   // TODO(crbug.com/849278): Remove either the DCHECK or the if condition
162   // when we figure out in what cases that the box doesn't have properties.
163   DCHECK(properties);
164   if (properties) {
165     if (const auto* clip = properties->OverflowControlsClip()) {
166       scoped_paint_chunk_properties.emplace(context.GetPaintController(), *clip,
167                                             box,
168                                             DisplayItem::kOverflowControls);
169     }
170   }
171 
172   if (GetScrollableArea().HorizontalScrollbar() &&
173       !GetScrollableArea().GraphicsLayerForHorizontalScrollbar()) {
174     PaintScrollbar(context, *GetScrollableArea().HorizontalScrollbar(),
175                    paint_info.GetCullRect(), paint_offset);
176   }
177   if (GetScrollableArea().VerticalScrollbar() &&
178       !GetScrollableArea().GraphicsLayerForVerticalScrollbar()) {
179     PaintScrollbar(context, *GetScrollableArea().VerticalScrollbar(),
180                    paint_info.GetCullRect(), paint_offset);
181   }
182 
183   if (!GetScrollableArea().GraphicsLayerForScrollCorner()) {
184     // We fill our scroll corner with white if we have a scrollbar that doesn't
185     // run all the way up to the edge of the box.
186     PaintScrollCorner(context, paint_offset, paint_info.GetCullRect());
187 
188     // Paint our resizer last, since it sits on top of the scroll corner.
189     PaintResizer(context, paint_offset, paint_info.GetCullRect());
190   }
191 }
192 
PaintScrollbar(GraphicsContext & context,Scrollbar & scrollbar,const CullRect & cull_rect,const IntPoint & paint_offset)193 void ScrollableAreaPainter::PaintScrollbar(GraphicsContext& context,
194                                            Scrollbar& scrollbar,
195                                            const CullRect& cull_rect,
196                                            const IntPoint& paint_offset) {
197   // TODO(crbug.com/1020913): We should not round paint_offset but should
198   // consider subpixel accumulation when painting scrollbars.
199   IntRect rect = scrollbar.FrameRect();
200   rect.MoveBy(paint_offset);
201   if (!cull_rect.Intersects(rect))
202     return;
203 
204   if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
205       scrollbar.IsCustomScrollbar()) {
206     scrollbar.Paint(context, paint_offset);
207     return;
208   }
209 
210   auto type = scrollbar.Orientation() == kHorizontalScrollbar
211                   ? DisplayItem::kScrollbarHorizontal
212                   : DisplayItem::kScrollbarVertical;
213   if (context.GetPaintController().UseCachedItemIfPossible(scrollbar, type))
214     return;
215 
216   const TransformPaintPropertyNode* scroll_translation = nullptr;
217   // Use ScrollTranslation only if the scrollbar is scrollable, to prevent
218   // non-scrollable scrollbars from being unnecessarily composited.
219   if (scrollbar.Maximum()) {
220     auto* properties =
221         GetScrollableArea().GetLayoutBox()->FirstFragment().PaintProperties();
222     DCHECK(properties);
223     scroll_translation = properties->ScrollTranslation();
224   }
225   auto delegate = base::MakeRefCounted<ScrollbarLayerDelegate>(
226       scrollbar, context.DeviceScaleFactor());
227   ScrollbarDisplayItem::Record(context, scrollbar, type, delegate, rect,
228                                scroll_translation, scrollbar.GetElementId());
229 }
230 
PaintScrollCorner(GraphicsContext & context,const IntPoint & paint_offset,const CullRect & cull_rect)231 void ScrollableAreaPainter::PaintScrollCorner(GraphicsContext& context,
232                                               const IntPoint& paint_offset,
233                                               const CullRect& cull_rect) {
234   IntRect abs_rect = GetScrollableArea().ScrollCornerRect();
235   if (abs_rect.IsEmpty())
236     return;
237   abs_rect.MoveBy(paint_offset);
238 
239   if (const auto* scroll_corner = GetScrollableArea().ScrollCorner()) {
240     if (!cull_rect.Intersects(abs_rect))
241       return;
242     CustomScrollbarTheme::PaintIntoRect(*scroll_corner, context,
243                                         PhysicalOffset(paint_offset),
244                                         PhysicalRect(abs_rect));
245     return;
246   }
247 
248   // We don't want to paint opaque if we have overlay scrollbars, since we need
249   // to see what is behind it.
250   if (GetScrollableArea().HasOverlayScrollbars())
251     return;
252 
253   ScrollbarTheme* theme = nullptr;
254 
255   if (GetScrollableArea().HorizontalScrollbar()) {
256     theme = &GetScrollableArea().HorizontalScrollbar()->GetTheme();
257   } else if (GetScrollableArea().VerticalScrollbar()) {
258     theme = &GetScrollableArea().VerticalScrollbar()->GetTheme();
259   } else {
260     NOTREACHED();
261   }
262 
263   const auto& client = DisplayItemClientForCorner();
264   theme->PaintScrollCorner(context, GetScrollableArea().VerticalScrollbar(),
265                            client, abs_rect,
266                            GetScrollableArea().UsedColorScheme());
267 }
268 
GetScrollableArea() const269 PaintLayerScrollableArea& ScrollableAreaPainter::GetScrollableArea() const {
270   return *scrollable_area_;
271 }
272 
DisplayItemClientForCorner() const273 const DisplayItemClient& ScrollableAreaPainter::DisplayItemClientForCorner()
274     const {
275   if (const auto* graphics_layer =
276           GetScrollableArea().GraphicsLayerForScrollCorner())
277     return *graphics_layer;
278   return GetScrollableArea().GetScrollCornerDisplayItemClient();
279 }
280 
281 }  // namespace blink
282