blob: 11d7207adcf6002b02940c9dc392fd0109007eff [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include <memory>
#include "base/metrics/histogram_functions.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/layers/solid_color_scrollbar_layer.h"
#include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/page_scale_constraints.h"
#include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
#include "third_party/blink/renderer/core/frame/root_frame_viewport.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h"
#include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
#include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_builder.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
#include "third_party/blink/renderer/core/scroll/scrollbar.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mobile.h"
#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
#include "third_party/blink/renderer/platform/geometry/double_rect.h"
#include "third_party/blink/renderer/platform/geometry/float_size.h"
#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
#include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h"
#include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
namespace blink {
VisualViewport::VisualViewport(Page& owner)
: ScrollableArea(owner.GetAgentGroupScheduler().CompositorTaskRunner()),
page_(&owner),
parent_property_tree_state_(PropertyTreeState::Uninitialized()),
scale_(1),
is_pinch_gesture_active_(false),
browser_controls_adjustment_(0),
max_page_scale_(-1),
track_pinch_zoom_stats_for_page_(false),
needs_paint_property_update_(true) {
UniqueObjectId unique_id = NewUniqueObjectId();
page_scale_element_id_ = CompositorElementIdFromUniqueObjectId(
unique_id, CompositorElementIdNamespace::kPrimary);
scroll_element_id_ = CompositorElementIdFromUniqueObjectId(
unique_id, CompositorElementIdNamespace::kScroll);
Reset();
}
TransformPaintPropertyNode* VisualViewport::GetDeviceEmulationTransformNode()
const {
return device_emulation_transform_node_.get();
}
TransformPaintPropertyNode*
VisualViewport::GetOverscrollElasticityTransformNode() const {
return overscroll_elasticity_transform_node_.get();
}
TransformPaintPropertyNode* VisualViewport::GetPageScaleNode() const {
return page_scale_node_.get();
}
TransformPaintPropertyNode* VisualViewport::GetScrollTranslationNode() const {
return scroll_translation_node_.get();
}
ScrollPaintPropertyNode* VisualViewport::GetScrollNode() const {
return scroll_node_.get();
}
PaintPropertyChangeType VisualViewport::UpdatePaintPropertyNodesIfNeeded(
PaintPropertyTreeBuilderFragmentContext& context) {
PaintPropertyChangeType change = PaintPropertyChangeType::kUnchanged;
if (!scroll_layer_)
CreateLayers();
if (!needs_paint_property_update_)
return change;
needs_paint_property_update_ = false;
auto* transform_parent = context.current.transform;
auto* scroll_parent = context.current.scroll;
auto* clip_parent = context.current.clip;
auto* effect_parent = context.current_effect;
DCHECK(transform_parent);
DCHECK(scroll_parent);
DCHECK(clip_parent);
DCHECK(effect_parent);
{
const auto& device_emulation_transform =
GetChromeClient()->GetDeviceEmulationTransform();
if (!device_emulation_transform.IsIdentity()) {
TransformPaintPropertyNode::State state{device_emulation_transform};
state.flags.in_subtree_of_page_scale = false;
if (!device_emulation_transform_node_) {
device_emulation_transform_node_ = TransformPaintPropertyNode::Create(
*transform_parent, std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
change = std::max(change, device_emulation_transform_node_->Update(
*transform_parent, std::move(state)));
}
transform_parent = device_emulation_transform_node_.get();
} else if (device_emulation_transform_node_) {
device_emulation_transform_node_ = nullptr;
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
}
}
{
DCHECK(!transform_parent->Unalias().IsInSubtreeOfPageScale());
TransformPaintPropertyNode::State state;
state.flags.in_subtree_of_page_scale = false;
// TODO(crbug.com/877794) Should create overscroll elasticity transform node
// based on settings.
if (!overscroll_elasticity_transform_node_) {
overscroll_elasticity_transform_node_ =
TransformPaintPropertyNode::Create(*transform_parent,
std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
change = std::max(change, overscroll_elasticity_transform_node_->Update(
*transform_parent, std::move(state)));
}
}
{
TransformPaintPropertyNode::State state;
if (scale_ != 1.f)
state.transform_and_origin = {TransformationMatrix().Scale(scale_)};
state.flags.in_subtree_of_page_scale = false;
state.direct_compositing_reasons = CompositingReason::kViewport;
state.compositor_element_id = page_scale_element_id_;
if (!page_scale_node_) {
page_scale_node_ = TransformPaintPropertyNode::Create(
*overscroll_elasticity_transform_node_.get(), std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
auto effective_change_type = page_scale_node_->Update(
*overscroll_elasticity_transform_node_.get(), std::move(state));
// As an optimization, attempt to directly update the compositor
// scale translation node and return kChangedOnlyCompositedValues which
// avoids an expensive PaintArtifactCompositor update.
if (effective_change_type ==
PaintPropertyChangeType::kChangedOnlySimpleValues) {
if (auto* paint_artifact_compositor = GetPaintArtifactCompositor()) {
bool updated =
paint_artifact_compositor->DirectlyUpdatePageScaleTransform(
*page_scale_node_);
if (updated) {
effective_change_type =
PaintPropertyChangeType::kChangedOnlyCompositedValues;
page_scale_node_->CompositorSimpleValuesUpdated();
}
}
}
change = std::max(change, effective_change_type);
}
}
{
ScrollPaintPropertyNode::State state;
state.container_rect = IntRect(IntPoint(), size_);
state.contents_size = ContentsSize();
state.user_scrollable_horizontal =
UserInputScrollable(kHorizontalScrollbar);
state.user_scrollable_vertical = UserInputScrollable(kVerticalScrollbar);
state.max_scroll_offset_affected_by_page_scale = true;
state.compositor_element_id = GetScrollElementId();
if (LocalMainFrame() && LocalMainFrame()->GetDocument()) {
Document* document = LocalMainFrame()->GetDocument();
bool uses_default_root_scroller =
&document->GetRootScrollerController().EffectiveRootScroller() ==
document;
// All position: fixed elements will chain scrolling directly up to the
// visual viewport's scroll node. In the case of a default root scroller
// (i.e. the LayoutView), we actually want to scroll the "full viewport".
// i.e. scrolling from the position: fixed element should cause the page
// to scroll. This is not the case when we have a different root
// scroller. We set |prevent_viewport_scrolling_from_inner| so the
// compositor can know to use the correct chaining behavior. This would be
// better fixed by setting the correct scroll_tree_index in PAC::Update on
// the fixed layer but that's a larger change. See
// https://crbug.com/977954 for details.
state.prevent_viewport_scrolling_from_inner = !uses_default_root_scroller;
}
if (LocalMainFrame() &&
!LocalMainFrame()->GetSettings()->GetThreadedScrollingEnabled()) {
state.main_thread_scrolling_reasons =
cc::MainThreadScrollingReason::kThreadedScrollingDisabled;
}
if (!scroll_node_) {
scroll_node_ =
ScrollPaintPropertyNode::Create(*scroll_parent, std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
change = std::max(change,
scroll_node_->Update(*scroll_parent, std::move(state)));
}
}
{
ScrollOffset scroll_position = GetScrollOffset();
TransformPaintPropertyNode::State state{
FloatSize(-scroll_position.Width(), -scroll_position.Height())};
state.scroll = scroll_node_;
state.direct_compositing_reasons = CompositingReason::kViewport;
if (!scroll_translation_node_) {
scroll_translation_node_ = TransformPaintPropertyNode::Create(
*page_scale_node_, std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
auto effective_change_type =
scroll_translation_node_->Update(*page_scale_node_, std::move(state));
// As an optimization, attempt to directly update the compositor
// translation node and return kChangedOnlyCompositedValues which avoids
// an expensive PaintArtifactCompositor update.
if (effective_change_type ==
PaintPropertyChangeType::kChangedOnlySimpleValues) {
if (auto* paint_artifact_compositor = GetPaintArtifactCompositor()) {
bool updated =
paint_artifact_compositor->DirectlyUpdateScrollOffsetTransform(
*scroll_translation_node_);
if (updated) {
effective_change_type =
PaintPropertyChangeType::kChangedOnlyCompositedValues;
scroll_translation_node_->CompositorSimpleValuesUpdated();
}
}
}
}
}
if (scrollbar_layer_horizontal_) {
EffectPaintPropertyNode::State state;
state.local_transform_space = transform_parent;
state.direct_compositing_reasons =
CompositingReason::kActiveOpacityAnimation;
state.has_active_opacity_animation = true;
state.compositor_element_id =
GetScrollbarElementId(ScrollbarOrientation::kHorizontalScrollbar);
if (!horizontal_scrollbar_effect_node_) {
horizontal_scrollbar_effect_node_ =
EffectPaintPropertyNode::Create(*effect_parent, std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
change = std::max(change, horizontal_scrollbar_effect_node_->Update(
*effect_parent, std::move(state)));
}
}
if (scrollbar_layer_vertical_) {
EffectPaintPropertyNode::State state;
state.local_transform_space = transform_parent;
state.direct_compositing_reasons =
CompositingReason::kActiveOpacityAnimation;
state.has_active_opacity_animation = true;
state.compositor_element_id =
GetScrollbarElementId(ScrollbarOrientation::kVerticalScrollbar);
if (!vertical_scrollbar_effect_node_) {
vertical_scrollbar_effect_node_ =
EffectPaintPropertyNode::Create(*effect_parent, std::move(state));
change = PaintPropertyChangeType::kNodeAddedOrRemoved;
} else {
change = std::max(change, vertical_scrollbar_effect_node_->Update(
*effect_parent, std::move(state)));
}
}
parent_property_tree_state_ =
PropertyTreeStateOrAlias(*transform_parent, *clip_parent, *effect_parent);
if (change == PaintPropertyChangeType::kNodeAddedOrRemoved &&
LocalMainFrame()) {
LocalMainFrame()->View()->SetVisualViewportNeedsRepaint();
}
return change;
}
VisualViewport::~VisualViewport() {
SendUMAMetrics();
}
void VisualViewport::Trace(Visitor* visitor) const {
visitor->Trace(page_);
ScrollableArea::Trace(visitor);
}
void VisualViewport::UpdateStyleAndLayout(DocumentUpdateReason reason) const {
if (!LocalMainFrame())
return;
if (Document* document = LocalMainFrame()->GetDocument())
document->UpdateStyleAndLayout(reason);
}
void VisualViewport::EnqueueScrollEvent() {
if (Document* document = LocalMainFrame()->GetDocument())
document->EnqueueVisualViewportScrollEvent();
}
void VisualViewport::EnqueueResizeEvent() {
if (Document* document = LocalMainFrame()->GetDocument())
document->EnqueueVisualViewportResizeEvent();
}
void VisualViewport::SetSize(const IntSize& size) {
if (size_ == size)
return;
TRACE_EVENT2("blink", "VisualViewport::setSize", "width", size.Width(),
"height", size.Height());
size_ = size;
needs_paint_property_update_ = true;
TRACE_EVENT_INSTANT1("loading", "viewport", TRACE_EVENT_SCOPE_THREAD, "data",
ViewportToTracedValue());
if (!LocalMainFrame())
return;
// Need to re-compute sizes for the overlay scrollbars.
if (scrollbar_layer_horizontal_) {
DCHECK(scrollbar_layer_vertical_);
UpdateScrollbarLayer(kHorizontalScrollbar);
UpdateScrollbarLayer(kVerticalScrollbar);
LocalMainFrame()->View()->SetVisualViewportNeedsRepaint();
}
EnqueueResizeEvent();
}
void VisualViewport::Reset() {
SetScaleAndLocation(1, is_pinch_gesture_active_, FloatPoint());
}
void VisualViewport::MainFrameDidChangeSize() {
TRACE_EVENT0("blink", "VisualViewport::mainFrameDidChangeSize");
// In unit tests we may not have initialized the layer tree.
if (scroll_layer_)
scroll_layer_->SetBounds(gfx::Size(ContentsSize()));
needs_paint_property_update_ = true;
ClampToBoundaries();
}
FloatRect VisualViewport::VisibleRect(
IncludeScrollbarsInRect scrollbar_inclusion) const {
FloatSize visible_size(size_);
if (scrollbar_inclusion == kExcludeScrollbars)
visible_size = FloatSize(ExcludeScrollbars(size_));
visible_size.Expand(0, browser_controls_adjustment_);
visible_size.Scale(1 / scale_);
return FloatRect(FloatPoint(GetScrollOffset()), visible_size);
}
FloatPoint VisualViewport::ViewportCSSPixelsToRootFrame(
const FloatPoint& point) const {
// Note, this is in CSS Pixels so we don't apply scale.
FloatPoint point_in_root_frame = point;
point_in_root_frame.Move(GetScrollOffset());
return point_in_root_frame;
}
void VisualViewport::SetLocation(const FloatPoint& new_location) {
SetScaleAndLocation(scale_, is_pinch_gesture_active_, new_location);
}
void VisualViewport::Move(const ScrollOffset& delta) {
SetLocation(FloatPoint(offset_ + delta));
}
void VisualViewport::SetScale(float scale) {
SetScaleAndLocation(scale, is_pinch_gesture_active_, FloatPoint(offset_));
}
double VisualViewport::OffsetLeft() const {
if (!LocalMainFrame())
return 0;
UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
return VisibleRect().X() / LocalMainFrame()->PageZoomFactor();
}
double VisualViewport::OffsetTop() const {
if (!LocalMainFrame())
return 0;
UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
return VisibleRect().Y() / LocalMainFrame()->PageZoomFactor();
}
double VisualViewport::Width() const {
UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
return VisibleWidthCSSPx();
}
double VisualViewport::Height() const {
UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
return VisibleHeightCSSPx();
}
double VisualViewport::ScaleForVisualViewport() const {
return Scale();
}
void VisualViewport::SetScaleAndLocation(float scale,
bool is_pinch_gesture_active,
const FloatPoint& location) {
if (DidSetScaleOrLocation(scale, is_pinch_gesture_active, location)) {
NotifyRootFrameViewport();
Document* document = LocalMainFrame()->GetDocument();
if (AXObjectCache* cache = document->ExistingAXObjectCache()) {
cache->HandleScaleAndLocationChanged(document);
}
}
}
double VisualViewport::VisibleWidthCSSPx() const {
if (!LocalMainFrame())
return 0;
float zoom = LocalMainFrame()->PageZoomFactor();
float width_css_px = VisibleRect().Width() / zoom;
return width_css_px;
}
double VisualViewport::VisibleHeightCSSPx() const {
if (!LocalMainFrame())
return 0;
float zoom = LocalMainFrame()->PageZoomFactor();
float height_css_px = VisibleRect().Height() / zoom;
return height_css_px;
}
bool VisualViewport::DidSetScaleOrLocation(float scale,
bool is_pinch_gesture_active,
const FloatPoint& location) {
if (!LocalMainFrame()) {
is_pinch_gesture_active_ = is_pinch_gesture_active;
// The VisualViewport for a remote mainframe must always be 1.0 or else
// event targeting will fail.
DCHECK(scale == 1.f);
scale_ = scale;
offset_ = ScrollOffset();
return false;
}
bool values_changed = false;
bool notify_page_scale_factor_changed =
is_pinch_gesture_active_ != is_pinch_gesture_active;
is_pinch_gesture_active_ = is_pinch_gesture_active;
if (!std::isnan(scale) && !std::isinf(scale)) {
float clamped_scale = GetPage()
.GetPageScaleConstraintsSet()
.FinalConstraints()
.ClampToConstraints(scale);
if (clamped_scale != scale_) {
scale_ = clamped_scale;
values_changed = true;
notify_page_scale_factor_changed = true;
EnqueueResizeEvent();
}
}
if (notify_page_scale_factor_changed)
GetPage().GetChromeClient().PageScaleFactorChanged();
ScrollOffset clamped_offset = ClampScrollOffset(ToScrollOffset(location));
// TODO(bokan): If the offset is invalid, we might end up in an infinite
// recursion as we reenter this function on clamping. It would be cleaner to
// avoid reentrancy but for now just prevent the stack overflow.
// crbug.com/702771.
if (std::isnan(clamped_offset.Width()) ||
std::isnan(clamped_offset.Height()) ||
std::isinf(clamped_offset.Width()) || std::isinf(clamped_offset.Height()))
return false;
if (clamped_offset != offset_) {
offset_ = clamped_offset;
GetScrollAnimator().SetCurrentOffset(offset_);
// SVG runs with accelerated compositing disabled so no
// ScrollingCoordinator.
if (auto* coordinator = GetPage().GetScrollingCoordinator()) {
if (scroll_layer_)
coordinator->UpdateCompositorScrollOffset(*LocalMainFrame(), *this);
}
EnqueueScrollEvent();
LocalMainFrame()->View()->DidChangeScrollOffset();
values_changed = true;
}
if (!values_changed)
return false;
probe::DidChangeViewport(LocalMainFrame());
LocalMainFrame()->Loader().SaveScrollState();
ClampToBoundaries();
needs_paint_property_update_ = true;
if (notify_page_scale_factor_changed) {
TRACE_EVENT_INSTANT1("loading", "viewport", TRACE_EVENT_SCOPE_THREAD,
"data", ViewportToTracedValue());
}
return true;
}
void VisualViewport::CreateLayers() {
if (scroll_layer_)
return;
if (!GetPage().GetSettings().GetAcceleratedCompositingEnabled())
return;
DCHECK(!scrollbar_layer_horizontal_);
DCHECK(!scrollbar_layer_vertical_);
needs_paint_property_update_ = true;
// TODO(crbug.com/1015625): Avoid scroll_layer_.
scroll_layer_ = cc::Layer::Create();
scroll_layer_->SetScrollable(gfx::Size(size_));
scroll_layer_->SetBounds(gfx::Size(ContentsSize()));
scroll_layer_->SetElementId(GetScrollElementId());
InitializeScrollbars();
if (LocalMainFrame()) {
ScrollingCoordinator* coordinator = GetPage().GetScrollingCoordinator();
DCHECK(coordinator);
coordinator->UpdateCompositorScrollOffset(*LocalMainFrame(), *this);
}
}
void VisualViewport::InitializeScrollbars() {
// Do nothing if we haven't created the layer tree yet.
if (!scroll_layer_)
return;
needs_paint_property_update_ = true;
scrollbar_layer_horizontal_ = nullptr;
scrollbar_layer_vertical_ = nullptr;
if (VisualViewportSuppliesScrollbars() &&
!GetPage().GetSettings().GetHideScrollbars()) {
UpdateScrollbarLayer(kHorizontalScrollbar);
UpdateScrollbarLayer(kVerticalScrollbar);
}
// Ensure existing LocalFrameView scrollbars are removed if the visual
// viewport scrollbars are now supplied, or created if the visual viewport no
// longer supplies scrollbars.
LocalFrame* frame = LocalMainFrame();
if (frame && frame->View())
frame->View()->VisualViewportScrollbarsChanged();
}
int VisualViewport::ScrollbarThickness() const {
return ScrollbarThemeOverlayMobile::GetInstance().ScrollbarThickness(
ScaleFromDIP());
}
void VisualViewport::UpdateScrollbarLayer(ScrollbarOrientation orientation) {
bool is_horizontal = orientation == kHorizontalScrollbar;
scoped_refptr<cc::ScrollbarLayerBase>& scrollbar_layer =
is_horizontal ? scrollbar_layer_horizontal_ : scrollbar_layer_vertical_;
if (!scrollbar_layer) {
auto& theme = ScrollbarThemeOverlayMobile::GetInstance();
float scale = ScaleFromDIP();
int thumb_thickness = theme.ThumbThickness(scale);
int scrollbar_margin = theme.ScrollbarMargin(scale);
cc::ScrollbarOrientation cc_orientation =
orientation == kHorizontalScrollbar
? cc::ScrollbarOrientation::HORIZONTAL
: cc::ScrollbarOrientation::VERTICAL;
scrollbar_layer = cc::SolidColorScrollbarLayer::Create(
cc_orientation, thumb_thickness, scrollbar_margin,
/*is_left_side_vertical_scrollbar*/ false);
scrollbar_layer->SetElementId(GetScrollbarElementId(orientation));
scrollbar_layer->SetScrollElementId(scroll_layer_->element_id());
scrollbar_layer->SetIsDrawable(true);
}
scrollbar_layer->SetBounds(
orientation == kHorizontalScrollbar
? gfx::Size(size_.Width() - ScrollbarThickness(),
ScrollbarThickness())
: gfx::Size(ScrollbarThickness(),
size_.Height() - ScrollbarThickness()));
}
bool VisualViewport::VisualViewportSuppliesScrollbars() const {
return GetPage().GetSettings().GetViewportEnabled();
}
const Document* VisualViewport::GetDocument() const {
return LocalMainFrame() ? LocalMainFrame()->GetDocument() : nullptr;
}
CompositorElementId VisualViewport::GetScrollElementId() const {
return scroll_element_id_;
}
bool VisualViewport::ScrollAnimatorEnabled() const {
return GetPage().GetSettings().GetScrollAnimatorEnabled();
}
ChromeClient* VisualViewport::GetChromeClient() const {
return &GetPage().GetChromeClient();
}
SmoothScrollSequencer* VisualViewport::GetSmoothScrollSequencer() const {
if (!LocalMainFrame())
return nullptr;
return &LocalMainFrame()->GetSmoothScrollSequencer();
}
void VisualViewport::SetScrollOffset(
const ScrollOffset& offset,
mojom::blink::ScrollType scroll_type,
mojom::blink::ScrollBehavior scroll_behavior,
ScrollCallback on_finish) {
// We clamp the offset here, because the ScrollAnimator may otherwise be
// set to a non-clamped offset by ScrollableArea::setScrollOffset,
// which may lead to incorrect scrolling behavior in RootFrameViewport down
// the line.
// TODO(eseckler): Solve this instead by ensuring that ScrollableArea and
// ScrollAnimator are kept in sync. This requires that ScrollableArea always
// stores fractional offsets and that truncation happens elsewhere, see
// crbug.com/626315.
ScrollOffset new_scroll_offset = ClampScrollOffset(offset);
ScrollableArea::SetScrollOffset(new_scroll_offset, scroll_type,
scroll_behavior, std::move(on_finish));
}
void VisualViewport::SetScrollOffset(
const ScrollOffset& offset,
mojom::blink::ScrollType scroll_type,
mojom::blink::ScrollBehavior scroll_behavior) {
SetScrollOffset(offset, scroll_type, scroll_behavior, ScrollCallback());
}
PhysicalRect VisualViewport::ScrollIntoView(
const PhysicalRect& rect_in_absolute,
const mojom::blink::ScrollIntoViewParamsPtr& params) {
PhysicalRect scroll_snapport_rect = VisibleScrollSnapportRect();
ScrollOffset new_scroll_offset =
ClampScrollOffset(ScrollAlignment::GetScrollOffsetToExpose(
scroll_snapport_rect, rect_in_absolute, *params->align_x.get(),
*params->align_y.get(), GetScrollOffset()));
if (new_scroll_offset != GetScrollOffset()) {
if (params->is_for_scroll_sequence) {
DCHECK(params->type == mojom::blink::ScrollType::kProgrammatic ||
params->type == mojom::blink::ScrollType::kUser);
if (SmoothScrollSequencer* sequencer = GetSmoothScrollSequencer()) {
sequencer->QueueAnimation(this, new_scroll_offset, params->behavior);
}
} else {
SetScrollOffset(new_scroll_offset, params->type, params->behavior,
ScrollCallback());
}
}
return rect_in_absolute;
}
int VisualViewport::ScrollSize(ScrollbarOrientation orientation) const {
IntSize scroll_dimensions =
MaximumScrollOffsetInt() - MinimumScrollOffsetInt();
return (orientation == kHorizontalScrollbar) ? scroll_dimensions.Width()
: scroll_dimensions.Height();
}
IntSize VisualViewport::MinimumScrollOffsetInt() const {
return IntSize();
}
IntSize VisualViewport::MaximumScrollOffsetInt() const {
return FlooredIntSize(MaximumScrollOffset());
}
ScrollOffset VisualViewport::MaximumScrollOffset() const {
if (!LocalMainFrame())
return ScrollOffset();
// TODO(bokan): We probably shouldn't be storing the bounds in a float.
// crbug.com/470718.
FloatSize frame_view_size(ContentsSize());
if (browser_controls_adjustment_) {
float min_scale =
GetPage().GetPageScaleConstraintsSet().FinalConstraints().minimum_scale;
frame_view_size.Expand(0, browser_controls_adjustment_ / min_scale);
}
frame_view_size.Scale(scale_);
frame_view_size = FloatSize(FlooredIntSize(frame_view_size));
FloatSize viewport_size(size_);
viewport_size.Expand(0, ceilf(browser_controls_adjustment_));
FloatSize max_position = frame_view_size - viewport_size;
max_position.Scale(1 / scale_);
return ScrollOffset(max_position);
}
IntPoint VisualViewport::ClampDocumentOffsetAtScale(const IntPoint& offset,
float scale) {
if (!LocalMainFrame() || !LocalMainFrame()->View())
return IntPoint();
LocalFrameView* view = LocalMainFrame()->View();
FloatSize scaled_size(ExcludeScrollbars(size_));
scaled_size.Scale(1 / scale);
IntSize visual_viewport_max =
FlooredIntSize(FloatSize(ContentsSize()) - scaled_size);
IntSize max =
view->LayoutViewport()->MaximumScrollOffsetInt() + visual_viewport_max;
IntSize min =
view->LayoutViewport()
->MinimumScrollOffsetInt(); // VisualViewportMin should be (0, 0)
IntSize clamped = ToIntSize(offset);
clamped = clamped.ShrunkTo(max);
clamped = clamped.ExpandedTo(min);
return IntPoint(clamped);
}
void VisualViewport::SetBrowserControlsAdjustment(float adjustment) {
if (browser_controls_adjustment_ == adjustment)
return;
browser_controls_adjustment_ = adjustment;
EnqueueResizeEvent();
}
float VisualViewport::BrowserControlsAdjustment() const {
return browser_controls_adjustment_;
}
bool VisualViewport::UserInputScrollable(ScrollbarOrientation) const {
// If there is a non-root fullscreen element, prevent the viewport from
// scrolling.
Document* main_document =
LocalMainFrame() ? LocalMainFrame()->GetDocument() : nullptr;
if (main_document) {
Element* fullscreen_element =
Fullscreen::FullscreenElementFrom(*main_document);
if (fullscreen_element)
return false;
}
return true;
}
IntSize VisualViewport::ContentsSize() const {
LocalFrame* frame = LocalMainFrame();
if (!frame || !frame->View())
return IntSize();
return frame->View()->Size();
}
IntRect VisualViewport::VisibleContentRect(
IncludeScrollbarsInRect scrollbar_inclusion) const {
return EnclosingIntRect(VisibleRect(scrollbar_inclusion));
}
scoped_refptr<base::SingleThreadTaskRunner> VisualViewport::GetTimerTaskRunner()
const {
return LocalMainFrame()->GetTaskRunner(TaskType::kInternalDefault);
}
mojom::blink::ColorScheme VisualViewport::UsedColorScheme() const {
if (LocalFrame* main_frame = LocalMainFrame()) {
if (Document* main_document = main_frame->GetDocument())
return main_document->GetLayoutView()->StyleRef().UsedColorScheme();
}
return ComputedStyle::InitialStyle().UsedColorScheme();
}
void VisualViewport::UpdateScrollOffset(const ScrollOffset& position,
mojom::blink::ScrollType scroll_type) {
if (!DidSetScaleOrLocation(scale_, is_pinch_gesture_active_,
FloatPoint(position))) {
return;
}
if (IsExplicitScrollType(scroll_type)) {
NotifyRootFrameViewport();
if (scroll_type != mojom::blink::ScrollType::kCompositor && scroll_layer_)
scroll_layer_->ShowScrollbars();
}
}
cc::Layer* VisualViewport::LayerForScrolling() const {
return scroll_layer_.get();
}
cc::Layer* VisualViewport::LayerForHorizontalScrollbar() const {
return scrollbar_layer_horizontal_.get();
}
cc::Layer* VisualViewport::LayerForVerticalScrollbar() const {
return scrollbar_layer_vertical_.get();
}
RootFrameViewport* VisualViewport::GetRootFrameViewport() const {
if (!LocalMainFrame() || !LocalMainFrame()->View())
return nullptr;
return LocalMainFrame()->View()->GetRootFrameViewport();
}
LocalFrame* VisualViewport::LocalMainFrame() const {
return GetPage().MainFrame() && GetPage().MainFrame()->IsLocalFrame()
? GetPage().DeprecatedLocalMainFrame()
: nullptr;
}
IntSize VisualViewport::ExcludeScrollbars(const IntSize& size) const {
IntSize excluded_size = size;
if (RootFrameViewport* root_frame_viewport = GetRootFrameViewport()) {
excluded_size.Expand(-root_frame_viewport->VerticalScrollbarWidth(),
-root_frame_viewport->HorizontalScrollbarHeight());
}
return excluded_size;
}
bool VisualViewport::ScheduleAnimation() {
GetPage().GetChromeClient().ScheduleAnimation(LocalMainFrame()->View());
return true;
}
void VisualViewport::ClampToBoundaries() {
SetLocation(FloatPoint(offset_));
}
FloatRect VisualViewport::ViewportToRootFrame(
const FloatRect& rect_in_viewport) const {
FloatRect rect_in_root_frame = rect_in_viewport;
rect_in_root_frame.Scale(1 / Scale());
rect_in_root_frame.Move(GetScrollOffset());
return rect_in_root_frame;
}
IntRect VisualViewport::ViewportToRootFrame(
const IntRect& rect_in_viewport) const {
// FIXME: How to snap to pixels?
return EnclosingIntRect(ViewportToRootFrame(FloatRect(rect_in_viewport)));
}
FloatRect VisualViewport::RootFrameToViewport(
const FloatRect& rect_in_root_frame) const {
FloatRect rect_in_viewport = rect_in_root_frame;
rect_in_viewport.Move(-GetScrollOffset());
rect_in_viewport.Scale(Scale());
return rect_in_viewport;
}
IntRect VisualViewport::RootFrameToViewport(
const IntRect& rect_in_root_frame) const {
// FIXME: How to snap to pixels?
return EnclosingIntRect(RootFrameToViewport(FloatRect(rect_in_root_frame)));
}
FloatPoint VisualViewport::ViewportToRootFrame(
const FloatPoint& point_in_viewport) const {
FloatPoint point_in_root_frame = point_in_viewport;
point_in_root_frame.Scale(1 / Scale(), 1 / Scale());
point_in_root_frame.Move(GetScrollOffset());
return point_in_root_frame;
}
FloatPoint VisualViewport::RootFrameToViewport(
const FloatPoint& point_in_root_frame) const {
FloatPoint point_in_viewport = point_in_root_frame;
point_in_viewport.Move(-GetScrollOffset());
point_in_viewport.Scale(Scale(), Scale());
return point_in_viewport;
}
IntPoint VisualViewport::ViewportToRootFrame(
const IntPoint& point_in_viewport) const {
// FIXME: How to snap to pixels?
return FlooredIntPoint(
FloatPoint(ViewportToRootFrame(FloatPoint(point_in_viewport))));
}
IntPoint VisualViewport::RootFrameToViewport(
const IntPoint& point_in_root_frame) const {
// FIXME: How to snap to pixels?
return FlooredIntPoint(
FloatPoint(RootFrameToViewport(FloatPoint(point_in_root_frame))));
}
void VisualViewport::StartTrackingPinchStats() {
if (!LocalMainFrame())
return;
Document* document = LocalMainFrame()->GetDocument();
if (!document)
return;
if (!document->Url().ProtocolIsInHTTPFamily())
return;
track_pinch_zoom_stats_for_page_ = !ShouldDisableDesktopWorkarounds();
}
void VisualViewport::UserDidChangeScale() {
if (!track_pinch_zoom_stats_for_page_)
return;
max_page_scale_ = std::max(max_page_scale_, scale_);
}
void VisualViewport::SendUMAMetrics() {
if (track_pinch_zoom_stats_for_page_) {
bool did_scale = max_page_scale_ > 0;
base::UmaHistogramBoolean("Viewport.DidScalePage", did_scale);
if (did_scale) {
int zoom_percentage = floor(max_page_scale_ * 100);
// Note: while defined as an exact linear histogram with 21 buckets here,
// the UMA itself is tagged as an enumeration (PageScaleFactor) in
// histograms.xml to make it easy to identify the buckets...
int bucket = floor(zoom_percentage / 25.f);
base::UmaHistogramExactLinear("Viewport.MaxPageScale", bucket, 21);
}
}
max_page_scale_ = -1;
track_pinch_zoom_stats_for_page_ = false;
}
bool VisualViewport::ShouldDisableDesktopWorkarounds() const {
if (!LocalMainFrame() || !LocalMainFrame()->View())
return false;
if (!LocalMainFrame()->GetSettings()->GetViewportEnabled())
return false;
// A document is considered adapted to small screen UAs if one of these holds:
// 1. The author specified viewport has a constrained width that is equal to
// the initial viewport width.
// 2. The author has disabled viewport zoom.
const PageScaleConstraints& constraints =
GetPage().GetPageScaleConstraintsSet().PageDefinedConstraints();
return LocalMainFrame()->View()->GetLayoutSize().Width() == size_.Width() ||
(constraints.minimum_scale == constraints.maximum_scale &&
constraints.minimum_scale != -1);
}
cc::AnimationHost* VisualViewport::GetCompositorAnimationHost() const {
DCHECK(GetPage().MainFrame()->IsLocalFrame());
ScrollingCoordinator* c = GetPage().GetScrollingCoordinator();
return c ? c->GetCompositorAnimationHost() : nullptr;
}
CompositorAnimationTimeline* VisualViewport::GetCompositorAnimationTimeline()
const {
DCHECK(GetPage().MainFrame()->IsLocalFrame());
ScrollingCoordinator* c = GetPage().GetScrollingCoordinator();
return c ? c->GetCompositorAnimationTimeline() : nullptr;
}
void VisualViewport::NotifyRootFrameViewport() const {
if (!GetRootFrameViewport())
return;
GetRootFrameViewport()->DidUpdateVisualViewport();
}
ScrollbarTheme& VisualViewport::GetPageScrollbarTheme() const {
return GetPage().GetScrollbarTheme();
}
PaintArtifactCompositor* VisualViewport::GetPaintArtifactCompositor() const {
if (LocalMainFrame() && LocalMainFrame()->View())
return LocalMainFrame()->View()->GetPaintArtifactCompositor();
return nullptr;
}
std::unique_ptr<TracedValue> VisualViewport::ViewportToTracedValue() const {
auto value = std::make_unique<TracedValue>();
IntRect viewport = VisibleContentRect();
value->SetInteger("x", clampTo<int>(roundf(viewport.X())));
value->SetInteger("y", clampTo<int>(roundf(viewport.Y())));
value->SetInteger("width", clampTo<int>(roundf(viewport.Width())));
value->SetInteger("height", clampTo<int>(roundf(viewport.Height())));
value->SetString("frameID",
IdentifiersFactory::FrameId(GetPage().MainFrame()));
return value;
}
void VisualViewport::DisposeImpl() {
scroll_layer_.reset();
scrollbar_layer_horizontal_.reset();
scrollbar_layer_vertical_.reset();
device_emulation_transform_node_.reset();
overscroll_elasticity_transform_node_.reset();
page_scale_node_.reset();
scroll_translation_node_.reset();
scroll_node_.reset();
horizontal_scrollbar_effect_node_.reset();
vertical_scrollbar_effect_node_.reset();
}
void VisualViewport::Paint(GraphicsContext& context) const {
// TODO(crbug.com/1015625): Avoid scroll_layer_.
if (scroll_layer_) {
auto state = parent_property_tree_state_;
state.SetTransform(*scroll_translation_node_);
DEFINE_STATIC_LOCAL(LiteralDebugNameClient, debug_name_client,
("Inner Viewport Scroll Layer"));
RecordForeignLayer(context, debug_name_client,
DisplayItem::kForeignLayerViewportScroll, scroll_layer_,
IntPoint(), &state);
}
if (scrollbar_layer_horizontal_) {
auto state = parent_property_tree_state_;
state.SetEffect(*horizontal_scrollbar_effect_node_);
DEFINE_STATIC_LOCAL(LiteralDebugNameClient, debug_name_client,
("Inner Viewport Horizontal Scrollbar"));
RecordForeignLayer(
context, debug_name_client, DisplayItem::kForeignLayerViewportScrollbar,
scrollbar_layer_horizontal_,
IntPoint(0, size_.Height() - ScrollbarThickness()), &state);
}
if (scrollbar_layer_vertical_) {
auto state = parent_property_tree_state_;
state.SetEffect(*vertical_scrollbar_effect_node_);
DEFINE_STATIC_LOCAL(LiteralDebugNameClient, debug_name_client,
("Inner Viewport Vertical Scrollbar"));
RecordForeignLayer(
context, debug_name_client, DisplayItem::kForeignLayerViewportScrollbar,
scrollbar_layer_vertical_,
IntPoint(size_.Width() - ScrollbarThickness(), 0), &state);
}
}
} // namespace blink