blob: 6c51d121550194f7e6372863825e3c34b8c73081 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/layers/painted_scrollbar_layer.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/paint/skia_paint_canvas.h"
#include "cc/trees/draw_property_utils.h"
#include "cc/trees/layer_tree_host.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/transform_util.h"
namespace cc {
std::unique_ptr<LayerImpl> PaintedScrollbarLayer::CreateLayerImpl(
LayerTreeImpl* tree_impl) const {
return PaintedScrollbarLayerImpl::Create(tree_impl, id(), orientation(),
is_left_side_vertical_scrollbar(),
is_overlay_);
}
scoped_refptr<PaintedScrollbarLayer> PaintedScrollbarLayer::CreateOrReuse(
scoped_refptr<Scrollbar> scrollbar,
PaintedScrollbarLayer* existing_layer) {
if (existing_layer &&
existing_layer->scrollbar_.Read(*existing_layer)->IsSame(*scrollbar))
return existing_layer;
return Create(std::move(scrollbar));
}
scoped_refptr<PaintedScrollbarLayer> PaintedScrollbarLayer::Create(
scoped_refptr<Scrollbar> scrollbar) {
return base::WrapRefCounted(new PaintedScrollbarLayer(std::move(scrollbar)));
}
PaintedScrollbarLayer::PaintedScrollbarLayer(scoped_refptr<Scrollbar> scrollbar)
: ScrollbarLayerBase(scrollbar->Orientation(),
scrollbar->IsLeftSideVerticalScrollbar()),
scrollbar_(std::move(scrollbar)),
internal_contents_scale_(1.f),
painted_opacity_(scrollbar_.Read(*this)->Opacity()),
has_thumb_(scrollbar_.Read(*this)->HasThumb()),
jump_on_track_click_(scrollbar_.Read(*this)->JumpOnTrackClick()),
supports_drag_snap_back_(scrollbar_.Read(*this)->SupportsDragSnapBack()),
is_overlay_(scrollbar_.Read(*this)->IsOverlay()),
is_web_test_(scrollbar_.Read(*this)->IsRunningWebTest()),
uses_nine_patch_track_and_buttons_(
scrollbar_.Read(*this)->UsesNinePatchTrackAndButtonsResource()),
uses_solid_color_thumb_(scrollbar_.Read(*this)->UsesSolidColorThumb()) {}
PaintedScrollbarLayer::~PaintedScrollbarLayer() = default;
bool PaintedScrollbarLayer::OpacityCanAnimateOnImplThread() const {
return is_overlay_;
}
void PaintedScrollbarLayer::PushDirtyPropertiesTo(
LayerImpl* layer,
uint8_t dirty_flag,
const CommitState& commit_state,
const ThreadUnsafeCommitState& unsafe_state) {
ScrollbarLayerBase::PushDirtyPropertiesTo(layer, dirty_flag, commit_state,
unsafe_state);
if (dirty_flag & kChangedGeneralProperty) {
PaintedScrollbarLayerImpl* scrollbar_layer =
static_cast<PaintedScrollbarLayerImpl*>(layer);
scrollbar_layer->set_internal_contents_scale_and_bounds(
internal_contents_scale_.Read(*this),
internal_content_bounds_.Read(*this));
scrollbar_layer->SetJumpOnTrackClick(jump_on_track_click_.Read(*this));
scrollbar_layer->SetSupportsDragSnapBack(supports_drag_snap_back_);
scrollbar_layer->SetBackButtonRect(back_button_rect_.Read(*this));
scrollbar_layer->SetForwardButtonRect(forward_button_rect_.Read(*this));
scrollbar_layer->SetTrackRect(track_rect_.Read(*this));
if (orientation() == ScrollbarOrientation::kHorizontal) {
scrollbar_layer->SetThumbThickness(thumb_size_.Read(*this).height());
scrollbar_layer->SetThumbLength(thumb_size_.Read(*this).width());
} else {
scrollbar_layer->SetThumbThickness(thumb_size_.Read(*this).width());
scrollbar_layer->SetThumbLength(thumb_size_.Read(*this).height());
}
if (track_and_buttons_resource_.Read(*this)) {
scrollbar_layer->set_track_and_buttons_ui_resource_id(
track_and_buttons_resource_.Read(*this)->id());
} else {
scrollbar_layer->set_track_and_buttons_ui_resource_id(0);
}
if (thumb_resource_.Read(*this)) {
scrollbar_layer->set_thumb_ui_resource_id(
thumb_resource_.Read(*this)->id());
} else {
scrollbar_layer->set_thumb_ui_resource_id(0);
}
scrollbar_layer->SetScrollbarPaintedOpacity(painted_opacity_.Read(*this));
scrollbar_layer->set_is_overlay_scrollbar(is_overlay_);
scrollbar_layer->set_is_web_test(is_web_test_);
if (thumb_color_.Read(*this).has_value()) {
scrollbar_layer->SetThumbColor(thumb_color_.Read(*this).value());
}
if (uses_nine_patch_track_and_buttons_ &&
track_and_buttons_resource_.Read(*this)) {
const auto iter = commit_state.ui_resource_sizes.find(
track_and_buttons_resource_.Read(*this)->id());
const gfx::Size image_bounds =
(iter == commit_state.ui_resource_sizes.end()) ? gfx::Size()
: iter->second;
scrollbar_layer->SetTrackAndButtonsImageBounds(image_bounds);
scrollbar_layer->SetTrackAndButtonsAperture(
track_and_buttons_aperture_.Read(*this));
} else {
scrollbar_layer->SetTrackAndButtonsImageBounds(gfx::Size());
scrollbar_layer->SetTrackAndButtonsAperture(gfx::Rect());
}
scrollbar_layer->set_uses_nine_patch_track_and_buttons(
uses_nine_patch_track_and_buttons_);
}
}
void PaintedScrollbarLayer::SetLayerTreeHost(LayerTreeHost* host) {
// When the LTH is set to null or has changed, then this layer should remove
// all of its associated resources.
if (!host || host != layer_tree_host()) {
track_and_buttons_resource_.Write(*this) = nullptr;
thumb_resource_.Write(*this) = nullptr;
}
ScrollbarLayerBase::SetLayerTreeHost(host);
}
gfx::Size PaintedScrollbarLayer::LayerSizeToContentSize(
const gfx::Size& layer_size) const {
gfx::Size content_size =
gfx::ScaleToCeiledSize(layer_size, internal_contents_scale_.Read(*this));
// We should never return a rect bigger than the content bounds.
content_size.SetToMin(internal_content_bounds_.Read(*this));
return content_size;
}
bool PaintedScrollbarLayer::UpdateGeometry() {
// These properties should never change.
DCHECK_EQ(supports_drag_snap_back_,
scrollbar_.Read(*this)->SupportsDragSnapBack());
DCHECK_EQ(is_left_side_vertical_scrollbar(),
scrollbar_.Read(*this)->IsLeftSideVerticalScrollbar());
DCHECK_EQ(is_overlay_, scrollbar_.Read(*this)->IsOverlay());
DCHECK_EQ(orientation(), scrollbar_.Read(*this)->Orientation());
bool updated = false;
const auto& scrollbar = scrollbar_.Read(*this);
updated |= UpdateProperty(scrollbar->JumpOnTrackClick(),
&jump_on_track_click_.Write(*this));
updated |= UpdateProperty(scrollbar->TrackRect(), &track_rect_.Write(*this));
updated |= UpdateProperty(scrollbar->BackButtonRect(),
&back_button_rect_.Write(*this));
updated |= UpdateProperty(scrollbar->ForwardButtonRect(),
&forward_button_rect_.Write(*this));
updated |= UpdateProperty(scrollbar->HasThumb(), &has_thumb_.Write(*this));
if (has_thumb_.Read(*this)) {
gfx::Rect thumb_rect = scrollbar->ThumbRect();
if (uses_solid_color_thumb_) {
thumb_rect.Inset(scrollbar->SolidColorThumbInsets());
}
// Ignore ThumbRect's location because the PaintedScrollbarLayerImpl will
// compute it from scroll offset.
updated |= UpdateProperty(thumb_rect.size(), &thumb_size_.Write(*this));
} else {
updated |= UpdateProperty(gfx::Size(), &thumb_size_.Write(*this));
}
return updated;
}
bool PaintedScrollbarLayer::UpdateInternalContentScale() {
gfx::Transform transform;
transform = draw_property_utils::ScreenSpaceTransform(
this, layer_tree_host()->property_trees()->transform_tree());
gfx::Vector2dF transform_scales = gfx::ComputeTransform2dScaleComponents(
transform, layer_tree_host()->device_scale_factor());
float scale = std::max(transform_scales.x(), transform_scales.y());
// Clamp minimum scale to 1 to avoid too low scale during scale animation.
// TODO(crbug.com/40100995): Move rasterization of scrollbars to the impl side
// to better handle scale changes.
scale = std::max(1.0f, scale);
bool updated = false;
updated |= UpdateProperty(scale, &internal_contents_scale_.Write(*this));
updated |= UpdateProperty(
gfx::ScaleToCeiledSize(bounds(), internal_contents_scale_.Read(*this)),
&internal_content_bounds_.Write(*this));
return updated;
}
bool PaintedScrollbarLayer::Update() {
bool updated = false;
updated |= ScrollbarLayerBase::Update();
const bool internal_content_scaled = UpdateInternalContentScale();
updated |= internal_content_scaled;
updated |= UpdateGeometry();
const bool tickmarks_status_changed =
SetHasFindInPageTickmarks(scrollbar_.Read(*this)->HasTickmarks());
updated |= tickmarks_status_changed;
if (internal_content_bounds_.Read(*this).IsEmpty()) {
if (track_and_buttons_resource_.Read(*this)) {
track_and_buttons_resource_.Write(*this) = nullptr;
thumb_resource_.Write(*this) = nullptr;
SetNeedsPushProperties();
updated = true;
}
return updated;
}
if (!has_thumb_.Read(*this) && thumb_resource_.Read(*this)) {
thumb_resource_.Write(*this) = nullptr;
SetNeedsPushProperties();
updated = true;
}
// Scaling content requires scrollbars to be repainted to give the arrows
// appropriate proportions and tickmarks changing status needs a repaint to
// avoid incorrectly stretching the smaller 9patch bitmap.
updated |= UpdateTrackAndButtonsIfNeeded(
uses_nine_patch_track_and_buttons_ &&
(internal_content_scaled || tickmarks_status_changed));
updated |= UpdateThumbIfNeeded();
return updated;
}
bool PaintedScrollbarLayer::UpdateTrackAndButtonsIfNeeded(
bool force_repaint_for_nine_patch) {
bool updated = false;
gfx::Size size = bounds();
gfx::Size scaled_size = internal_content_bounds_.Read(*this);
if (!track_and_buttons_resource_.Read(*this) ||
scrollbar_.Read(*this)->TrackAndButtonsNeedRepaint() ||
force_repaint_for_nine_patch) {
if (uses_nine_patch_track_and_buttons_ &&
// Can't use nine-patch track and buttons if tickmarks are present.
!scrollbar_.Read(*this)->HasTickmarks()) {
size = scrollbar_.Read(*this)->NinePatchTrackAndButtonsCanvasSize(
/*scale=*/1.f);
const float scale = internal_contents_scale_.Read(*this);
scaled_size =
scrollbar_.Read(*this)->NinePatchTrackAndButtonsCanvasSize(scale);
track_and_buttons_aperture_.Write(*this) =
scrollbar_.Read(*this)->NinePatchTrackAndButtonsAperture(scale);
}
track_and_buttons_resource_.Write(*this) = ScopedUIResource::Create(
layer_tree_host()->GetUIResourceManager(),
RasterizeScrollbarPart(size, scaled_size,
[this, size](PaintCanvas& canvas) {
scrollbar_.Write(*this)->PaintTrackAndButtons(
canvas, gfx::Rect(size));
}));
SetNeedsPushProperties();
updated = true;
}
return updated;
}
bool PaintedScrollbarLayer::UpdateThumbIfNeeded() {
bool updated = false;
// If the scrollbar uses solid color thumb, it sends the correct color for
// the thumb to the Impl class instead of generating a bitmap.
if (uses_solid_color_thumb_) {
if (scrollbar_.Read(*this)->ThumbNeedsRepaint() ||
!thumb_color_.Read(*this).has_value()) {
const SkColor4f thumb_color = scrollbar_.Read(*this)->ThumbColor();
if (!thumb_color_.Read(*this).has_value() ||
thumb_color != thumb_color_.Read(*this).value()) {
thumb_color_.Write(*this) = thumb_color;
SetNeedsPushProperties();
updated = true;
}
// Clear thumb needs repaint regardless of if the thumb's color changed.
scrollbar_.Write(*this)->ClearThumbNeedsRepaint();
}
return updated;
}
gfx::Size thumb_size = thumb_size_.Read(*this);
gfx::Size scaled_thumb_size = LayerSizeToContentSize(thumb_size);
if (has_thumb_.Read(*this) && !scaled_thumb_size.IsEmpty()) {
if (!thumb_resource_.Read(*this) ||
scrollbar_.Read(*this)->ThumbNeedsRepaint() ||
scaled_thumb_size !=
thumb_resource_.Write(*this)->GetBitmap(0, false).GetSize()) {
thumb_resource_.Write(*this) = ScopedUIResource::Create(
layer_tree_host()->GetUIResourceManager(),
RasterizeScrollbarPart(thumb_size, scaled_thumb_size,
[this, thumb_size](PaintCanvas& canvas) {
scrollbar_.Write(*this)->PaintThumb(
canvas, gfx::Rect(thumb_size));
}));
SetNeedsPushProperties();
updated = true;
}
updated |= UpdateProperty(scrollbar_.Read(*this)->Opacity(),
&painted_opacity_.Write(*this));
}
return updated;
}
UIResourceBitmap PaintedScrollbarLayer::RasterizeScrollbarPart(
const gfx::Size& size,
const gfx::Size& requested_content_size,
base::FunctionRef<void(PaintCanvas&)> paint_function) {
DCHECK(!requested_content_size.IsEmpty());
DCHECK(!size.IsEmpty());
gfx::Size content_size = requested_content_size;
// Pages can end up requesting arbitrarily large scrollbars. Prevent this
// from crashing due to OOM and try something smaller.
SkBitmap skbitmap;
bool allocation_succeeded =
skbitmap.tryAllocN32Pixels(content_size.width(), content_size.height());
// Assuming 4bpp, caps at 4M.
constexpr int kMinScrollbarDimension = 1024;
int dimension = std::max(content_size.width(), content_size.height()) / 2;
while (!allocation_succeeded && dimension >= kMinScrollbarDimension) {
content_size.SetToMin(gfx::Size(dimension, dimension));
allocation_succeeded =
skbitmap.tryAllocN32Pixels(content_size.width(), content_size.height());
if (!allocation_succeeded)
dimension = dimension / 2;
}
CHECK(allocation_succeeded)
<< "Failed to allocate memory for scrollbar at dimension : " << dimension;
SkiaPaintCanvas canvas(skbitmap);
canvas.clear(SkColors::kTransparent);
float scale_x = content_size.width() / static_cast<float>(size.width());
float scale_y = content_size.height() / static_cast<float>(size.height());
canvas.scale(SkFloatToScalar(scale_x), SkFloatToScalar(scale_y));
paint_function(canvas);
// Make sure that the pixels are no longer mutable to unavoid unnecessary
// allocation and copying.
skbitmap.setImmutable();
return UIResourceBitmap(skbitmap);
}
ScrollbarLayerBase::ScrollbarLayerType
PaintedScrollbarLayer::GetScrollbarLayerType() const {
return kPainted;
}
} // namespace cc