1 // Copyright 2013 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 "cc/layers/painted_scrollbar_layer.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/auto_reset.h"
12 #include "cc/layers/painted_scrollbar_layer_impl.h"
13 #include "cc/paint/skia_paint_canvas.h"
14 #include "cc/trees/draw_property_utils.h"
15 #include "cc/trees/layer_tree_host.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 
18 namespace cc {
19 
CreateLayerImpl(LayerTreeImpl * tree_impl)20 std::unique_ptr<LayerImpl> PaintedScrollbarLayer::CreateLayerImpl(
21     LayerTreeImpl* tree_impl) {
22   return PaintedScrollbarLayerImpl::Create(tree_impl, id(), orientation(),
23                                            is_left_side_vertical_scrollbar(),
24                                            is_overlay_);
25 }
26 
CreateOrReuse(scoped_refptr<Scrollbar> scrollbar,PaintedScrollbarLayer * existing_layer)27 scoped_refptr<PaintedScrollbarLayer> PaintedScrollbarLayer::CreateOrReuse(
28     scoped_refptr<Scrollbar> scrollbar,
29     PaintedScrollbarLayer* existing_layer) {
30   if (existing_layer && existing_layer->scrollbar_->IsSame(*scrollbar))
31     return existing_layer;
32   return Create(std::move(scrollbar));
33 }
34 
Create(scoped_refptr<Scrollbar> scrollbar)35 scoped_refptr<PaintedScrollbarLayer> PaintedScrollbarLayer::Create(
36     scoped_refptr<Scrollbar> scrollbar) {
37   return base::WrapRefCounted(new PaintedScrollbarLayer(std::move(scrollbar)));
38 }
39 
PaintedScrollbarLayer(scoped_refptr<Scrollbar> scrollbar)40 PaintedScrollbarLayer::PaintedScrollbarLayer(scoped_refptr<Scrollbar> scrollbar)
41     : ScrollbarLayerBase(scrollbar->Orientation(),
42                          scrollbar->IsLeftSideVerticalScrollbar()),
43       scrollbar_(std::move(scrollbar)),
44       internal_contents_scale_(1.f),
45       painted_opacity_(scrollbar_->Opacity()),
46       has_thumb_(scrollbar_->HasThumb()),
47       jump_on_track_click_(scrollbar_->JumpOnTrackClick()),
48       supports_drag_snap_back_(scrollbar_->SupportsDragSnapBack()),
49       is_overlay_(scrollbar_->IsOverlay()) {}
50 
51 PaintedScrollbarLayer::~PaintedScrollbarLayer() = default;
52 
OpacityCanAnimateOnImplThread() const53 bool PaintedScrollbarLayer::OpacityCanAnimateOnImplThread() const {
54   return is_overlay_;
55 }
56 
PushPropertiesTo(LayerImpl * layer)57 void PaintedScrollbarLayer::PushPropertiesTo(LayerImpl* layer) {
58   ScrollbarLayerBase::PushPropertiesTo(layer);
59 
60   PaintedScrollbarLayerImpl* scrollbar_layer =
61       static_cast<PaintedScrollbarLayerImpl*>(layer);
62 
63   scrollbar_layer->set_internal_contents_scale_and_bounds(
64       internal_contents_scale_, internal_content_bounds_);
65 
66   scrollbar_layer->SetJumpOnTrackClick(jump_on_track_click_);
67   scrollbar_layer->SetSupportsDragSnapBack(supports_drag_snap_back_);
68   scrollbar_layer->SetBackButtonRect(back_button_rect_);
69   scrollbar_layer->SetForwardButtonRect(forward_button_rect_);
70   scrollbar_layer->SetTrackRect(track_rect_);
71   if (orientation() == ScrollbarOrientation::HORIZONTAL) {
72     scrollbar_layer->SetThumbThickness(thumb_size_.height());
73     scrollbar_layer->SetThumbLength(thumb_size_.width());
74   } else {
75     scrollbar_layer->SetThumbThickness(thumb_size_.width());
76     scrollbar_layer->SetThumbLength(thumb_size_.height());
77   }
78 
79   if (track_resource_.get())
80     scrollbar_layer->set_track_ui_resource_id(track_resource_->id());
81   else
82     scrollbar_layer->set_track_ui_resource_id(0);
83   if (thumb_resource_.get())
84     scrollbar_layer->set_thumb_ui_resource_id(thumb_resource_->id());
85   else
86     scrollbar_layer->set_thumb_ui_resource_id(0);
87 
88   scrollbar_layer->set_scrollbar_painted_opacity(painted_opacity_);
89 
90   scrollbar_layer->set_is_overlay_scrollbar(is_overlay_);
91 }
92 
SetLayerTreeHost(LayerTreeHost * host)93 void PaintedScrollbarLayer::SetLayerTreeHost(LayerTreeHost* host) {
94   // When the LTH is set to null or has changed, then this layer should remove
95   // all of its associated resources.
96   if (!host || host != layer_tree_host()) {
97     track_resource_ = nullptr;
98     thumb_resource_ = nullptr;
99   }
100 
101   ScrollbarLayerBase::SetLayerTreeHost(host);
102 }
103 
LayerSizeToContentSize(const gfx::Size & layer_size) const104 gfx::Size PaintedScrollbarLayer::LayerSizeToContentSize(
105     const gfx::Size& layer_size) const {
106   gfx::Size content_size =
107       gfx::ScaleToCeiledSize(layer_size, internal_contents_scale_);
108   // We should never return a rect bigger than the content bounds.
109   content_size.SetToMin(internal_content_bounds_);
110   return content_size;
111 }
112 
UpdateThumbAndTrackGeometry()113 bool PaintedScrollbarLayer::UpdateThumbAndTrackGeometry() {
114   // These properties should never change.
115   DCHECK_EQ(supports_drag_snap_back_, scrollbar_->SupportsDragSnapBack());
116   DCHECK_EQ(is_left_side_vertical_scrollbar(),
117             scrollbar_->IsLeftSideVerticalScrollbar());
118   DCHECK_EQ(is_overlay_, scrollbar_->IsOverlay());
119   DCHECK_EQ(orientation(), scrollbar_->Orientation());
120 
121   bool updated = false;
122   updated |=
123       UpdateProperty(scrollbar_->JumpOnTrackClick(), &jump_on_track_click_);
124   updated |= UpdateProperty(scrollbar_->TrackRect(), &track_rect_);
125   updated |= UpdateProperty(scrollbar_->BackButtonRect(), &back_button_rect_);
126   updated |=
127       UpdateProperty(scrollbar_->ForwardButtonRect(), &forward_button_rect_);
128   updated |= UpdateProperty(scrollbar_->HasThumb(), &has_thumb_);
129   if (has_thumb_) {
130     // Ignore ThumbRect's location because the PaintedScrollbarLayerImpl will
131     // compute it from scroll offset.
132     updated |= UpdateProperty(scrollbar_->ThumbRect().size(), &thumb_size_);
133   } else {
134     updated |= UpdateProperty(gfx::Size(), &thumb_size_);
135   }
136   return updated;
137 }
138 
UpdateInternalContentScale()139 bool PaintedScrollbarLayer::UpdateInternalContentScale() {
140   gfx::Transform transform;
141   transform = draw_property_utils::ScreenSpaceTransform(
142       this, layer_tree_host()->property_trees()->transform_tree);
143 
144   gfx::Vector2dF transform_scales = MathUtil::ComputeTransform2dScaleComponents(
145       transform, layer_tree_host()->device_scale_factor());
146   float scale = std::max(transform_scales.x(), transform_scales.y());
147   // Clamp minimum scale to 1 to avoid too low scale during scale animation.
148   // TODO(crbug.com/1009291): Move rasterization of scrollbars to the impl side
149   // to better handle scale changes.
150   scale = std::max(1.0f, scale);
151 
152   bool updated = false;
153   updated |= UpdateProperty(scale, &internal_contents_scale_);
154   updated |=
155       UpdateProperty(gfx::ScaleToCeiledSize(bounds(), internal_contents_scale_),
156                      &internal_content_bounds_);
157   return updated;
158 }
159 
Update()160 bool PaintedScrollbarLayer::Update() {
161   bool updated = false;
162 
163   updated |= ScrollbarLayerBase::Update();
164   updated |= UpdateInternalContentScale();
165   updated |= UpdateThumbAndTrackGeometry();
166 
167   gfx::Size size = bounds();
168   gfx::Size scaled_size = internal_content_bounds_;
169 
170   if (scaled_size.IsEmpty()) {
171     if (track_resource_) {
172       track_resource_ = nullptr;
173       thumb_resource_ = nullptr;
174       SetNeedsPushProperties();
175       updated = true;
176     }
177     return updated;
178   }
179 
180   if (!has_thumb_ && thumb_resource_) {
181     thumb_resource_ = nullptr;
182     SetNeedsPushProperties();
183     updated = true;
184   }
185 
186   if (!track_resource_ ||
187       scrollbar_->NeedsRepaintPart(ScrollbarPart::TRACK_BUTTONS_TICKMARKS)) {
188     track_resource_ = ScopedUIResource::Create(
189         layer_tree_host()->GetUIResourceManager(),
190         RasterizeScrollbarPart(size, scaled_size,
191                                ScrollbarPart::TRACK_BUTTONS_TICKMARKS));
192     SetNeedsPushProperties();
193     updated = true;
194   }
195 
196   gfx::Size scaled_thumb_size = LayerSizeToContentSize(thumb_size_);
197   if (has_thumb_ && !scaled_thumb_size.IsEmpty()) {
198     if (!thumb_resource_ ||
199         scrollbar_->NeedsRepaintPart(ScrollbarPart::THUMB) ||
200         scaled_thumb_size != thumb_resource_->GetBitmap(0, false).GetSize()) {
201       thumb_resource_ = ScopedUIResource::Create(
202           layer_tree_host()->GetUIResourceManager(),
203           RasterizeScrollbarPart(thumb_size_, scaled_thumb_size,
204                                  ScrollbarPart::THUMB));
205       SetNeedsPushProperties();
206       updated = true;
207     }
208     updated |= UpdateProperty(scrollbar_->Opacity(), &painted_opacity_);
209   }
210 
211   return updated;
212 }
213 
RasterizeScrollbarPart(const gfx::Size & size,const gfx::Size & requested_content_size,ScrollbarPart part)214 UIResourceBitmap PaintedScrollbarLayer::RasterizeScrollbarPart(
215     const gfx::Size& size,
216     const gfx::Size& requested_content_size,
217     ScrollbarPart part) {
218   DCHECK(!requested_content_size.IsEmpty());
219   DCHECK(!size.IsEmpty());
220 
221   gfx::Size content_size = requested_content_size;
222 
223   // Pages can end up requesting arbitrarily large scrollbars.  Prevent this
224   // from crashing due to OOM and try something smaller.
225   SkBitmap skbitmap;
226   bool allocation_succeeded =
227       skbitmap.tryAllocN32Pixels(content_size.width(), content_size.height());
228   // Assuming 4bpp, caps at 4M.
229   constexpr int kMinScrollbarDimension = 1024;
230   int dimension = std::max(content_size.width(), content_size.height()) / 2;
231   while (!allocation_succeeded && dimension >= kMinScrollbarDimension) {
232     content_size.SetToMin(gfx::Size(dimension, dimension));
233     allocation_succeeded =
234         skbitmap.tryAllocN32Pixels(content_size.width(), content_size.height());
235     if (!allocation_succeeded)
236       dimension = dimension / 2;
237   }
238   CHECK(allocation_succeeded)
239       << "Failed to allocate memory for scrollbar at dimension : " << dimension;
240 
241   SkiaPaintCanvas canvas(skbitmap);
242   canvas.clear(SK_ColorTRANSPARENT);
243 
244   float scale_x = content_size.width() / static_cast<float>(size.width());
245   float scale_y = content_size.height() / static_cast<float>(size.height());
246   canvas.scale(SkFloatToScalar(scale_x), SkFloatToScalar(scale_y));
247 
248   scrollbar_->PaintPart(&canvas, part, gfx::Rect(size));
249   // Make sure that the pixels are no longer mutable to unavoid unnecessary
250   // allocation and copying.
251   skbitmap.setImmutable();
252 
253   return UIResourceBitmap(skbitmap);
254 }
255 
256 ScrollbarLayerBase::ScrollbarLayerType
GetScrollbarLayerType() const257 PaintedScrollbarLayer::GetScrollbarLayerType() const {
258   return kPainted;
259 }
260 
261 }  // namespace cc
262