// Copyright 2013 The Chromium Authors. All rights reserved. // 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 #include "base/auto_reset.h" #include "cc/base/math_util.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/layers/painted_scrollbar_layer_impl.h" #include "cc/paint/paint_flags.h" #include "cc/paint/skia_paint_canvas.h" #include "cc/resources/ui_resource_bitmap.h" #include "cc/trees/draw_property_utils.h" #include "cc/trees/layer_tree_host.h" #include "cc/trees/layer_tree_impl.h" #include "skia/ext/platform_canvas.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkSize.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/skia_util.h" namespace { static constexpr int kMaxScrollbarDimension = 8192; }; namespace cc { std::unique_ptr PaintedScrollbarLayer::CreateLayerImpl( LayerTreeImpl* tree_impl) { return PaintedScrollbarLayerImpl::Create( tree_impl, id(), scrollbar_->Orientation(), scrollbar_->IsLeftSideVerticalScrollbar(), scrollbar_->IsOverlay()); } scoped_refptr PaintedScrollbarLayer::Create( std::unique_ptr scrollbar, ElementId scroll_element_id) { return base::WrapRefCounted( new PaintedScrollbarLayer(std::move(scrollbar), scroll_element_id)); } PaintedScrollbarLayer::PaintedScrollbarLayer( std::unique_ptr scrollbar, ElementId scroll_element_id) : scrollbar_(std::move(scrollbar)), scroll_element_id_(scroll_element_id), internal_contents_scale_(1.f), thumb_thickness_(scrollbar_->ThumbThickness()), thumb_length_(scrollbar_->ThumbLength()), is_overlay_(scrollbar_->IsOverlay()), has_thumb_(scrollbar_->HasThumb()), thumb_opacity_(scrollbar_->ThumbOpacity()) { if (!scrollbar_->IsOverlay()) AddMainThreadScrollingReasons( MainThreadScrollingReason::kScrollbarScrolling); } PaintedScrollbarLayer::~PaintedScrollbarLayer() {} void PaintedScrollbarLayer::SetScrollElementId(ElementId element_id) { if (element_id == scroll_element_id_) return; scroll_element_id_ = element_id; SetNeedsFullTreeSync(); } bool PaintedScrollbarLayer::OpacityCanAnimateOnImplThread() const { return scrollbar_->IsOverlay(); } void PaintedScrollbarLayer::PushPropertiesTo(LayerImpl* layer) { Layer::PushPropertiesTo(layer); PaintedScrollbarLayerImpl* scrollbar_layer = static_cast(layer); scrollbar_layer->SetScrollElementId(scroll_element_id_); scrollbar_layer->set_internal_contents_scale_and_bounds( internal_contents_scale_, internal_content_bounds_); scrollbar_layer->SetThumbThickness(thumb_thickness_); scrollbar_layer->SetThumbLength(thumb_length_); if (scrollbar_->Orientation() == HORIZONTAL) { scrollbar_layer->SetTrackStart( track_rect_.x() - location_.x()); scrollbar_layer->SetTrackLength(track_rect_.width()); } else { scrollbar_layer->SetTrackStart( track_rect_.y() - location_.y()); scrollbar_layer->SetTrackLength(track_rect_.height()); } if (track_resource_.get()) scrollbar_layer->set_track_ui_resource_id(track_resource_->id()); else scrollbar_layer->set_track_ui_resource_id(0); if (thumb_resource_.get()) scrollbar_layer->set_thumb_ui_resource_id(thumb_resource_->id()); else scrollbar_layer->set_thumb_ui_resource_id(0); scrollbar_layer->set_thumb_opacity(thumb_opacity_); scrollbar_layer->set_is_overlay_scrollbar(is_overlay_); } ScrollbarLayerInterface* PaintedScrollbarLayer::ToScrollbarLayer() { return this; } 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_resource_ = nullptr; thumb_resource_ = nullptr; } Layer::SetLayerTreeHost(host); } gfx::Rect PaintedScrollbarLayer::ScrollbarLayerRectToContentRect( const gfx::Rect& layer_rect) const { // Don't intersect with the bounds as in LayerRectToContentRect() because // layer_rect here might be in coordinates of the containing layer. gfx::Rect expanded_rect = gfx::ScaleToEnclosingRectSafe( layer_rect, internal_contents_scale_, internal_contents_scale_); // We should never return a rect bigger than the content bounds. gfx::Size clamped_size = expanded_rect.size(); clamped_size.SetToMin(internal_content_bounds_); expanded_rect.set_size(clamped_size); return expanded_rect; } gfx::Rect PaintedScrollbarLayer::OriginThumbRect() const { gfx::Size thumb_size; if (scrollbar_->Orientation() == HORIZONTAL) { thumb_size = gfx::Size(scrollbar_->ThumbLength(), scrollbar_->ThumbThickness()); } else { thumb_size = gfx::Size(scrollbar_->ThumbThickness(), scrollbar_->ThumbLength()); } return gfx::Rect(thumb_size); } void PaintedScrollbarLayer::UpdateThumbAndTrackGeometry() { UpdateProperty(scrollbar_->TrackRect(), &track_rect_); UpdateProperty(scrollbar_->Location(), &location_); UpdateProperty(scrollbar_->IsOverlay(), &is_overlay_); UpdateProperty(scrollbar_->HasThumb(), &has_thumb_); if (has_thumb_) { UpdateProperty(scrollbar_->ThumbThickness(), &thumb_thickness_); UpdateProperty(scrollbar_->ThumbLength(), &thumb_length_); } else { UpdateProperty(0, &thumb_thickness_); UpdateProperty(0, &thumb_length_); } } void PaintedScrollbarLayer::UpdateInternalContentScale() { float scale = layer_tree_host()->device_scale_factor(); if (layer_tree_host() ->GetSettings() .layer_transforms_should_scale_layer_contents) { gfx::Transform transform; transform = draw_property_utils::ScreenSpaceTransform( this, layer_tree_host()->property_trees()->transform_tree); gfx::Vector2dF transform_scales = MathUtil::ComputeTransform2dScaleComponents(transform, scale); scale = std::max(transform_scales.x(), transform_scales.y()); } bool changed = false; changed |= UpdateProperty(scale, &internal_contents_scale_); changed |= UpdateProperty(gfx::ScaleToCeiledSize(bounds(), internal_contents_scale_), &internal_content_bounds_); if (changed) { // If the content scale or bounds change, repaint. SetNeedsDisplay(); } } bool PaintedScrollbarLayer::Update() { { base::AutoReset ignore_set_needs_commit(&ignore_set_needs_commit_, true); Layer::Update(); UpdateInternalContentScale(); } UpdateThumbAndTrackGeometry(); gfx::Rect track_layer_rect = gfx::Rect(location_, bounds()); gfx::Rect scaled_track_rect = ScrollbarLayerRectToContentRect( track_layer_rect); bool updated = false; if (scaled_track_rect.IsEmpty()) { if (track_resource_) { track_resource_ = nullptr; thumb_resource_ = nullptr; SetNeedsPushProperties(); updated = true; } return updated; } if (!has_thumb_ && thumb_resource_) { thumb_resource_ = nullptr; SetNeedsPushProperties(); updated = true; } if (update_rect().IsEmpty() && track_resource_) return updated; if (!track_resource_ || scrollbar_->NeedsPaintPart(TRACK)) { track_resource_ = ScopedUIResource::Create( layer_tree_host()->GetUIResourceManager(), RasterizeScrollbarPart(track_layer_rect, scaled_track_rect, TRACK)); } gfx::Rect thumb_layer_rect = OriginThumbRect(); gfx::Rect scaled_thumb_rect = ScrollbarLayerRectToContentRect(thumb_layer_rect); if (has_thumb_ && !scaled_thumb_rect.IsEmpty()) { if (!thumb_resource_ || scrollbar_->NeedsPaintPart(THUMB) || scaled_thumb_rect.size() != thumb_resource_->GetBitmap(0, false).GetSize()) { thumb_resource_ = ScopedUIResource::Create( layer_tree_host()->GetUIResourceManager(), RasterizeScrollbarPart(thumb_layer_rect, scaled_thumb_rect, THUMB)); } thumb_opacity_ = scrollbar_->ThumbOpacity(); } // UI resources changed so push properties is needed. SetNeedsPushProperties(); updated = true; return updated; } UIResourceBitmap PaintedScrollbarLayer::RasterizeScrollbarPart( const gfx::Rect& layer_rect, const gfx::Rect& requested_content_rect, ScrollbarPart part) { DCHECK(!requested_content_rect.size().IsEmpty()); DCHECK(!layer_rect.size().IsEmpty()); gfx::Rect content_rect = requested_content_rect; // Pages can end up requesting arbitrarily large scrollbars. Prevent this // from crashing due to OOM and try something smaller. SkBitmap skbitmap; if (!skbitmap.tryAllocN32Pixels(content_rect.width(), content_rect.height())) { content_rect.Intersect( gfx::Rect(requested_content_rect.x(), requested_content_rect.y(), kMaxScrollbarDimension, kMaxScrollbarDimension)); skbitmap.allocN32Pixels(content_rect.width(), content_rect.height()); } SkiaPaintCanvas canvas(skbitmap); canvas.clear(SK_ColorTRANSPARENT); float scale_x = content_rect.width() / static_cast(layer_rect.width()); float scale_y = content_rect.height() / static_cast(layer_rect.height()); canvas.scale(SkFloatToScalar(scale_x), SkFloatToScalar(scale_y)); // TODO(pdr): Scrollbars are painted with an offset (see Scrollbar::PaintPart) // and the canvas is translated so that scrollbars are drawn at the origin. // Refactor this code to not use an offset at all so Scrollbar::PaintPart // paints at the origin and no translation is needed below. canvas.translate(-layer_rect.x(), -layer_rect.y()); scrollbar_->PaintPart(&canvas, part, layer_rect); // Make sure that the pixels are no longer mutable to unavoid unnecessary // allocation and copying. skbitmap.setImmutable(); return UIResourceBitmap(skbitmap); } } // namespace cc