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