| /* |
| * 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/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/task/single_thread_task_runner.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/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.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_alignment.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/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" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/size_f.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| OverscrollType ComputeOverscrollType() { |
| if (!Platform::Current()->IsElasticOverscrollEnabled()) |
| return OverscrollType::kNone; |
| return OverscrollType::kTransform; |
| } |
| |
| } // anonymous namespace |
| |
| 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), |
| needs_paint_property_update_(true), |
| overscroll_type_(ComputeOverscrollType()) { |
| UniqueObjectId unique_id = NewUniqueObjectId(); |
| page_scale_element_id_ = CompositorElementIdFromUniqueObjectId( |
| unique_id, CompositorElementIdNamespace::kPrimary); |
| scroll_element_id_ = CompositorElementIdFromUniqueObjectId( |
| unique_id, CompositorElementIdNamespace::kScroll); |
| Reset(); |
| } |
| |
| const TransformPaintPropertyNode* |
| VisualViewport::GetDeviceEmulationTransformNode() const { |
| return device_emulation_transform_node_.get(); |
| } |
| |
| const TransformPaintPropertyNode* |
| VisualViewport::GetOverscrollElasticityTransformNode() const { |
| return overscroll_elasticity_transform_node_.get(); |
| } |
| |
| const TransformPaintPropertyNode* VisualViewport::GetPageScaleNode() const { |
| return page_scale_node_.get(); |
| } |
| |
| const TransformPaintPropertyNode* VisualViewport::GetScrollTranslationNode() |
| const { |
| return scroll_translation_node_.get(); |
| } |
| |
| const ScrollPaintPropertyNode* VisualViewport::GetScrollNode() const { |
| return scroll_node_.get(); |
| } |
| |
| const TransformPaintPropertyNode* |
| VisualViewport::TransformNodeForViewportScrollbars() const { |
| // Viewport scrollbars don't move with elastic overscroll or scale with |
| // page scale. |
| if (overscroll_elasticity_transform_node_) |
| return overscroll_elasticity_transform_node_->UnaliasedParent(); |
| if (page_scale_node_) |
| return page_scale_node_->UnaliasedParent(); |
| return nullptr; |
| } |
| |
| PaintPropertyChangeType VisualViewport::UpdatePaintPropertyNodesIfNeeded( |
| PaintPropertyTreeBuilderFragmentContext& context) { |
| DCHECK(IsActiveViewport()); |
| 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.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; |
| } |
| } |
| |
| if (overscroll_type_ == OverscrollType::kTransform) { |
| DCHECK(!transform_parent->Unalias().IsInSubtreeOfPageScale()); |
| |
| TransformPaintPropertyNode::State state; |
| state.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))); |
| } |
| } else { |
| DCHECK(!overscroll_elasticity_transform_node_); |
| } |
| |
| { |
| auto* parent = overscroll_elasticity_transform_node_ |
| ? overscroll_elasticity_transform_node_.get() |
| : transform_parent; |
| DCHECK(!parent->Unalias().IsInSubtreeOfPageScale()); |
| |
| TransformPaintPropertyNode::State state; |
| if (scale_ != 1.f) |
| state.transform_and_origin.matrix = gfx::Transform::MakeScale(scale_); |
| state.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(*parent, std::move(state)); |
| change = PaintPropertyChangeType::kNodeAddedOrRemoved; |
| } else { |
| auto effective_change_type = |
| page_scale_node_->Update(*parent, 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 = gfx::Rect(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 (IsActiveViewport()) { |
| if (const 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 (!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))); |
| } |
| } |
| |
| { |
| TransformPaintPropertyNode::State state{ |
| {gfx::Transform::MakeTranslation(-offset_)}}; |
| 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.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.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 && |
| IsActiveViewport()) { |
| DCHECK(LocalMainFrame().View()); |
| LocalMainFrame().View()->SetVisualViewportOrOverlayNeedsRepaint(); |
| } |
| |
| return change; |
| } |
| |
| VisualViewport::~VisualViewport() = default; |
| |
| void VisualViewport::Trace(Visitor* visitor) const { |
| visitor->Trace(page_); |
| ScrollableArea::Trace(visitor); |
| } |
| |
| void VisualViewport::EnqueueScrollEvent() { |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->EnqueueVisualViewportScrollEvent(); |
| } |
| |
| void VisualViewport::EnqueueResizeEvent() { |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->EnqueueVisualViewportResizeEvent(); |
| } |
| |
| void VisualViewport::SetSize(const gfx::Size& size) { |
| if (size_ == size) |
| return; |
| |
| TRACE_EVENT2("blink", "VisualViewport::setSize", "width", size.width(), |
| "height", size.height()); |
| size_ = size; |
| |
| TRACE_EVENT_INSTANT1("loading", "viewport", TRACE_EVENT_SCOPE_THREAD, "data", |
| ViewportToTracedValue()); |
| |
| if (!IsActiveViewport()) |
| return; |
| |
| needs_paint_property_update_ = true; |
| |
| // Need to re-compute sizes for the overlay scrollbars. |
| if (scrollbar_layer_horizontal_ && LocalMainFrame().View()) { |
| DCHECK(scrollbar_layer_vertical_); |
| UpdateScrollbarLayer(kHorizontalScrollbar); |
| UpdateScrollbarLayer(kVerticalScrollbar); |
| LocalMainFrame().View()->SetVisualViewportOrOverlayNeedsRepaint(); |
| } |
| |
| EnqueueResizeEvent(); |
| } |
| |
| void VisualViewport::Reset() { |
| SetScaleAndLocation(1, is_pinch_gesture_active_, gfx::PointF()); |
| } |
| |
| void VisualViewport::MainFrameDidChangeSize() { |
| if (!IsActiveViewport()) |
| return; |
| |
| TRACE_EVENT0("blink", "VisualViewport::mainFrameDidChangeSize"); |
| |
| // In unit tests we may not have initialized the layer tree. |
| if (scroll_layer_) |
| scroll_layer_->SetBounds(ContentsSize()); |
| |
| needs_paint_property_update_ = true; |
| ClampToBoundaries(); |
| } |
| |
| gfx::RectF VisualViewport::VisibleRect( |
| IncludeScrollbarsInRect scrollbar_inclusion) const { |
| if (!IsActiveViewport()) |
| return gfx::RectF(gfx::PointF(), gfx::SizeF(size_)); |
| |
| gfx::SizeF visible_size(size_); |
| |
| if (scrollbar_inclusion == kExcludeScrollbars) |
| visible_size = gfx::SizeF(ExcludeScrollbars(size_)); |
| |
| visible_size.Enlarge(0, browser_controls_adjustment_); |
| visible_size.Scale(1 / scale_); |
| |
| return gfx::RectF(ScrollPosition(), visible_size); |
| } |
| |
| gfx::PointF VisualViewport::ViewportCSSPixelsToRootFrame( |
| const gfx::PointF& point) const { |
| // Note, this is in CSS Pixels so we don't apply scale. |
| gfx::PointF point_in_root_frame = point; |
| point_in_root_frame += GetScrollOffset(); |
| return point_in_root_frame; |
| } |
| |
| void VisualViewport::SetLocation(const gfx::PointF& new_location) { |
| SetScaleAndLocation(scale_, is_pinch_gesture_active_, new_location); |
| } |
| |
| void VisualViewport::Move(const ScrollOffset& delta) { |
| SetLocation(gfx::PointAtOffsetFromOrigin(offset_ + delta)); |
| } |
| |
| void VisualViewport::SetScale(float scale) { |
| SetScaleAndLocation(scale, is_pinch_gesture_active_, |
| gfx::PointAtOffsetFromOrigin(offset_)); |
| } |
| |
| double VisualViewport::OffsetLeft() const { |
| // Offset{Left|Top} and Width|Height are used by the DOMVisualViewport to |
| // expose values to JS. We'll only ever ask the visual viewport for these |
| // values for the outermost main frame. All other cases are based on layout |
| // of subframes. |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| return VisibleRect().x() / LocalMainFrame().PageZoomFactor(); |
| } |
| |
| double VisualViewport::OffsetTop() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| return VisibleRect().y() / LocalMainFrame().PageZoomFactor(); |
| } |
| |
| double VisualViewport::Width() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| return VisibleWidthCSSPx(); |
| } |
| |
| double VisualViewport::Height() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* document = LocalMainFrame().GetDocument()) |
| document->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| return VisibleHeightCSSPx(); |
| } |
| |
| double VisualViewport::ScaleForVisualViewport() const { |
| return Scale(); |
| } |
| |
| void VisualViewport::SetScaleAndLocation(float scale, |
| bool is_pinch_gesture_active, |
| const gfx::PointF& location) { |
| if (DidSetScaleOrLocation(scale, is_pinch_gesture_active, location)) { |
| // In remote or nested main frame cases, the visual viewport is inert so it |
| // cannot be moved or scaled. This is enforced by setting page scale |
| // constraints. |
| DCHECK(IsActiveViewport()); |
| NotifyRootFrameViewport(); |
| } |
| } |
| |
| double VisualViewport::VisibleWidthCSSPx() const { |
| if (!IsActiveViewport()) |
| return VisibleRect().width(); |
| |
| float zoom = LocalMainFrame().PageZoomFactor(); |
| float width_css_px = VisibleRect().width() / zoom; |
| return width_css_px; |
| } |
| |
| double VisualViewport::VisibleHeightCSSPx() const { |
| if (!IsActiveViewport()) |
| return VisibleRect().height(); |
| |
| 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 gfx::PointF& location) { |
| if (!IsActiveViewport()) { |
| is_pinch_gesture_active_ = is_pinch_gesture_active; |
| // The VisualViewport in an embedded widget 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::isfinite(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(location.OffsetFromOrigin()); |
| |
| // 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::isfinite(clamped_offset.x()) || |
| !std::isfinite(clamped_offset.y())) { |
| return false; |
| } |
| |
| if (clamped_offset != offset_) { |
| DCHECK(LocalMainFrame().View()); |
| |
| 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() { |
| DCHECK(IsActiveViewport()); |
| |
| if (scroll_layer_) |
| return; |
| |
| if (!GetPage().GetSettings().GetAcceleratedCompositingEnabled()) |
| return; |
| |
| DCHECK(!scrollbar_layer_horizontal_); |
| DCHECK(!scrollbar_layer_vertical_); |
| |
| needs_paint_property_update_ = true; |
| |
| scroll_layer_ = cc::Layer::Create(); |
| scroll_layer_->SetBounds(ContentsSize()); |
| scroll_layer_->SetElementId(GetScrollElementId()); |
| |
| InitializeScrollbars(); |
| |
| if (IsActiveViewport()) { |
| ScrollingCoordinator* coordinator = GetPage().GetScrollingCoordinator(); |
| DCHECK(coordinator); |
| coordinator->UpdateCompositorScrollOffset(LocalMainFrame(), *this); |
| } |
| } |
| |
| void VisualViewport::InitializeScrollbars() { |
| DCHECK(IsActiveViewport()); |
| // 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. |
| if (IsActiveViewport()) { |
| if (LocalFrameView* frame_view = LocalMainFrame().View()) |
| frame_view->SetVisualViewportOrOverlayNeedsRepaint(); |
| } |
| } |
| |
| EScrollbarWidth VisualViewport::CSSScrollbarWidth() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* main_document = LocalMainFrame().GetDocument()) |
| return main_document->GetLayoutView()->StyleRef().ScrollbarWidth(); |
| |
| return EScrollbarWidth::kAuto; |
| } |
| |
| std::optional<blink::Color> VisualViewport::CSSScrollbarThumbColor() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* main_document = LocalMainFrame().GetDocument()) { |
| return main_document->GetLayoutView() |
| ->StyleRef() |
| .ScrollbarThumbColorResolved(); |
| } |
| |
| return std::nullopt; |
| } |
| |
| void VisualViewport::DropCompositorScrollDeltaNextCommit() { |
| if (auto* paint_artifact_compositor = GetPaintArtifactCompositor()) { |
| paint_artifact_compositor->DropCompositorScrollDeltaNextCommit( |
| scroll_element_id_); |
| } |
| } |
| |
| int VisualViewport::ScrollbarThickness() const { |
| DCHECK(IsActiveViewport()); |
| return ScrollbarThemeOverlayMobile::GetInstance().ScrollbarThickness( |
| ScaleFromDIP(), CSSScrollbarWidth()); |
| } |
| |
| void VisualViewport::UpdateScrollbarLayer(ScrollbarOrientation orientation) { |
| DCHECK(IsActiveViewport()); |
| bool is_horizontal = orientation == kHorizontalScrollbar; |
| scoped_refptr<cc::SolidColorScrollbarLayer>& 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, CSSScrollbarWidth()); |
| int scrollbar_margin = theme.ScrollbarMargin(scale, CSSScrollbarWidth()); |
| cc::ScrollbarOrientation cc_orientation = |
| orientation == kHorizontalScrollbar |
| ? cc::ScrollbarOrientation::kHorizontal |
| : cc::ScrollbarOrientation::kVertical; |
| 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())); |
| |
| UpdateScrollbarColor(*scrollbar_layer); |
| } |
| |
| bool VisualViewport::VisualViewportSuppliesScrollbars() const { |
| return IsActiveViewport() && GetPage().GetSettings().GetViewportEnabled(); |
| } |
| |
| const Document* VisualViewport::GetDocument() const { |
| return IsActiveViewport() ? 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 (!IsActiveViewport()) |
| return nullptr; |
| return LocalMainFrame().GetSmoothScrollSequencer(); |
| } |
| |
| bool 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); |
| return ScrollableArea::SetScrollOffset(new_scroll_offset, scroll_type, |
| scroll_behavior, std::move(on_finish)); |
| } |
| |
| bool VisualViewport::SetScrollOffset( |
| const ScrollOffset& offset, |
| mojom::blink::ScrollType scroll_type, |
| mojom::blink::ScrollBehavior scroll_behavior) { |
| return SetScrollOffset(offset, scroll_type, scroll_behavior, |
| ScrollCallback()); |
| } |
| |
| PhysicalRect VisualViewport::ScrollIntoView( |
| const PhysicalRect& rect_in_absolute, |
| const PhysicalBoxStrut& scroll_margin, |
| const mojom::blink::ScrollIntoViewParamsPtr& params) { |
| if (!IsActiveViewport()) |
| return rect_in_absolute; |
| |
| PhysicalRect scroll_snapport_rect = VisibleScrollSnapportRect(); |
| |
| ScrollOffset new_scroll_offset = |
| ClampScrollOffset(ScrollAlignment::GetScrollOffsetToExpose( |
| scroll_snapport_rect, rect_in_absolute, scroll_margin, |
| *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); |
| CHECK(GetSmoothScrollSequencer()); |
| GetSmoothScrollSequencer()->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 { |
| gfx::Vector2d scroll_dimensions = |
| MaximumScrollOffsetInt() - MinimumScrollOffsetInt(); |
| return (orientation == kHorizontalScrollbar) ? scroll_dimensions.x() |
| : scroll_dimensions.y(); |
| } |
| |
| gfx::Vector2d VisualViewport::MinimumScrollOffsetInt() const { |
| return gfx::Vector2d(); |
| } |
| |
| gfx::Vector2d VisualViewport::MaximumScrollOffsetInt() const { |
| return gfx::ToFlooredVector2d(MaximumScrollOffset()); |
| } |
| |
| ScrollOffset VisualViewport::MaximumScrollOffset() const { |
| return MaximumScrollOffsetAtScale(scale_); |
| } |
| |
| ScrollOffset VisualViewport::MaximumScrollOffsetAtScale(float scale) const { |
| if (!IsActiveViewport()) |
| return ScrollOffset(); |
| |
| // TODO(bokan): We probably shouldn't be storing the bounds in a float. |
| // crbug.com/470718. |
| gfx::SizeF frame_view_size(ContentsSize()); |
| |
| if (browser_controls_adjustment_) { |
| float min_scale = |
| GetPage().GetPageScaleConstraintsSet().FinalConstraints().minimum_scale; |
| frame_view_size.Enlarge(0, browser_controls_adjustment_ / min_scale); |
| } |
| |
| frame_view_size.Scale(scale); |
| frame_view_size = gfx::SizeF(ToFlooredSize(frame_view_size)); |
| |
| gfx::SizeF viewport_size(size_); |
| viewport_size.Enlarge(0, ceilf(browser_controls_adjustment_)); |
| |
| gfx::SizeF max_position = frame_view_size - viewport_size; |
| max_position.Scale(1 / scale); |
| return ScrollOffset(max_position.width(), max_position.height()); |
| } |
| |
| gfx::Point VisualViewport::ClampDocumentOffsetAtScale(const gfx::Point& offset, |
| float scale) { |
| DCHECK(IsActiveViewport()); |
| |
| LocalFrameView* view = LocalMainFrame().View(); |
| if (!view) |
| return gfx::Point(); |
| |
| gfx::SizeF scaled_size(ExcludeScrollbars(size_)); |
| scaled_size.Scale(1 / scale); |
| |
| gfx::Size visual_viewport_max = |
| gfx::ToFlooredSize(gfx::SizeF(ContentsSize()) - scaled_size); |
| gfx::Vector2d max = |
| view->LayoutViewport()->MaximumScrollOffsetInt() + |
| gfx::Vector2d(visual_viewport_max.width(), visual_viewport_max.height()); |
| gfx::Vector2d min = |
| view->LayoutViewport() |
| ->MinimumScrollOffsetInt(); // VisualViewportMin should be (0, 0) |
| |
| gfx::Point clamped = offset; |
| clamped.SetToMin(gfx::PointAtOffsetFromOrigin(max)); |
| clamped.SetToMax(gfx::PointAtOffsetFromOrigin(min)); |
| return clamped; |
| } |
| |
| void VisualViewport::SetBrowserControlsAdjustment(float adjustment) { |
| DCHECK(IsActiveViewport()); |
| DCHECK(LocalMainFrame().IsOutermostMainFrame()); |
| |
| if (browser_controls_adjustment_ == adjustment) |
| return; |
| |
| browser_controls_adjustment_ = adjustment; |
| EnqueueResizeEvent(); |
| } |
| |
| float VisualViewport::BrowserControlsAdjustment() const { |
| DCHECK(!browser_controls_adjustment_ || IsActiveViewport()); |
| return browser_controls_adjustment_; |
| } |
| |
| bool VisualViewport::UserInputScrollable(ScrollbarOrientation) const { |
| // User input scrollable is used to block scrolling from the visual viewport. |
| // If the viewport isn't active we don't have to do anything special. |
| if (!IsActiveViewport()) |
| return true; |
| |
| // If there is a non-root fullscreen element, prevent the viewport from |
| // scrolling. |
| if (Document* main_document = LocalMainFrame().GetDocument()) { |
| Element* fullscreen_element = |
| Fullscreen::FullscreenElementFrom(*main_document); |
| if (fullscreen_element) |
| return false; |
| } |
| return true; |
| } |
| |
| gfx::Size VisualViewport::ContentsSize() const { |
| if (!IsActiveViewport()) |
| return gfx::Size(); |
| |
| LocalFrameView* frame_view = LocalMainFrame().View(); |
| if (!frame_view) |
| return gfx::Size(); |
| |
| return frame_view->Size(); |
| } |
| |
| gfx::Rect VisualViewport::VisibleContentRect( |
| IncludeScrollbarsInRect scrollbar_inclusion) const { |
| return ToEnclosingRect(VisibleRect(scrollbar_inclusion)); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> VisualViewport::GetTimerTaskRunner() |
| const { |
| DCHECK(IsActiveViewport()); |
| return LocalMainFrame().GetTaskRunner(TaskType::kInternalDefault); |
| } |
| |
| mojom::blink::ColorScheme VisualViewport::UsedColorSchemeScrollbars() const { |
| DCHECK(IsActiveViewport()); |
| if (Document* main_document = LocalMainFrame().GetDocument()) |
| return main_document->GetLayoutView()->StyleRef().UsedColorScheme(); |
| |
| return mojom::blink::ColorScheme::kLight; |
| } |
| |
| void VisualViewport::UpdateScrollOffset(const ScrollOffset& position, |
| mojom::blink::ScrollType scroll_type) { |
| if (!DidSetScaleOrLocation(scale_, is_pinch_gesture_active_, |
| gfx::PointAtOffsetFromOrigin(position))) { |
| return; |
| } |
| if (IsExplicitScrollType(scroll_type)) |
| NotifyRootFrameViewport(); |
| } |
| |
| cc::Layer* VisualViewport::LayerForScrolling() const { |
| DCHECK(!scroll_layer_ || IsActiveViewport()); |
| return scroll_layer_.get(); |
| } |
| |
| cc::Layer* VisualViewport::LayerForHorizontalScrollbar() const { |
| DCHECK(!scrollbar_layer_horizontal_ || IsActiveViewport()); |
| return scrollbar_layer_horizontal_.get(); |
| } |
| |
| cc::Layer* VisualViewport::LayerForVerticalScrollbar() const { |
| DCHECK(!scrollbar_layer_vertical_ || IsActiveViewport()); |
| return scrollbar_layer_vertical_.get(); |
| } |
| |
| RootFrameViewport* VisualViewport::GetRootFrameViewport() const { |
| if (!IsActiveViewport()) |
| return nullptr; |
| |
| LocalFrameView* frame_view = LocalMainFrame().View(); |
| if (!frame_view) |
| return nullptr; |
| |
| return frame_view->GetRootFrameViewport(); |
| } |
| |
| bool VisualViewport::IsActiveViewport() const { |
| Frame* main_frame = GetPage().MainFrame(); |
| if (!main_frame) |
| return false; |
| |
| // If the main frame is remote, we're inside a remote subframe which |
| // shouldn't have an active visual viewport. |
| if (!main_frame->IsLocalFrame()) |
| return false; |
| |
| // Only the outermost main frame should have an active viewport. |
| return main_frame->IsOutermostMainFrame(); |
| } |
| |
| LocalFrame& VisualViewport::LocalMainFrame() const { |
| DCHECK(IsActiveViewport()); |
| return *To<LocalFrame>(GetPage().MainFrame()); |
| } |
| |
| gfx::Size VisualViewport::ExcludeScrollbars(const gfx::Size& size) const { |
| if (!IsActiveViewport()) |
| return size; |
| |
| gfx::Size excluded_size = size; |
| if (RootFrameViewport* root_frame_viewport = GetRootFrameViewport()) { |
| excluded_size.Enlarge(-root_frame_viewport->VerticalScrollbarWidth(), |
| -root_frame_viewport->HorizontalScrollbarHeight()); |
| } |
| return excluded_size; |
| } |
| |
| bool VisualViewport::ScheduleAnimation() { |
| DCHECK(IsActiveViewport()); |
| |
| LocalFrameView* frame_view = LocalMainFrame().View(); |
| DCHECK(frame_view); |
| GetPage().GetChromeClient().ScheduleAnimation(frame_view); |
| return true; |
| } |
| |
| void VisualViewport::ClampToBoundaries() { |
| SetLocation(gfx::PointAtOffsetFromOrigin(offset_)); |
| } |
| |
| gfx::RectF VisualViewport::ViewportToRootFrame( |
| const gfx::RectF& rect_in_viewport) const { |
| gfx::RectF rect_in_root_frame = rect_in_viewport; |
| rect_in_root_frame.Scale(1 / Scale()); |
| rect_in_root_frame.Offset(GetScrollOffset()); |
| return rect_in_root_frame; |
| } |
| |
| gfx::Rect VisualViewport::ViewportToRootFrame( |
| const gfx::Rect& rect_in_viewport) const { |
| // FIXME: How to snap to pixels? |
| return ToEnclosingRect(ViewportToRootFrame(gfx::RectF(rect_in_viewport))); |
| } |
| |
| gfx::RectF VisualViewport::RootFrameToViewport( |
| const gfx::RectF& rect_in_root_frame) const { |
| gfx::RectF rect_in_viewport = rect_in_root_frame; |
| rect_in_viewport.Offset(-GetScrollOffset()); |
| rect_in_viewport.Scale(Scale()); |
| return rect_in_viewport; |
| } |
| |
| gfx::Rect VisualViewport::RootFrameToViewport( |
| const gfx::Rect& rect_in_root_frame) const { |
| // FIXME: How to snap to pixels? |
| return ToEnclosingRect(RootFrameToViewport(gfx::RectF(rect_in_root_frame))); |
| } |
| |
| gfx::PointF VisualViewport::ViewportToRootFrame( |
| const gfx::PointF& point_in_viewport) const { |
| gfx::PointF point_in_root_frame = point_in_viewport; |
| point_in_root_frame.Scale(1 / Scale()); |
| point_in_root_frame += GetScrollOffset(); |
| return point_in_root_frame; |
| } |
| |
| gfx::PointF VisualViewport::RootFrameToViewport( |
| const gfx::PointF& point_in_root_frame) const { |
| gfx::PointF point_in_viewport = point_in_root_frame; |
| point_in_viewport -= GetScrollOffset(); |
| point_in_viewport.Scale(Scale()); |
| return point_in_viewport; |
| } |
| |
| gfx::Point VisualViewport::ViewportToRootFrame( |
| const gfx::Point& point_in_viewport) const { |
| // FIXME: How to snap to pixels? |
| return gfx::ToFlooredPoint( |
| ViewportToRootFrame(gfx::PointF(point_in_viewport))); |
| } |
| |
| gfx::Point VisualViewport::RootFrameToViewport( |
| const gfx::Point& point_in_root_frame) const { |
| // FIXME: How to snap to pixels? |
| return gfx::ToFlooredPoint( |
| RootFrameToViewport(gfx::PointF(point_in_root_frame))); |
| } |
| |
| bool VisualViewport::ShouldDisableDesktopWorkarounds() const { |
| DCHECK(IsActiveViewport()); |
| |
| LocalFrameView* frame_view = LocalMainFrame().View(); |
| if (!frame_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 frame_view->GetLayoutSize().width() == size_.width() || |
| (constraints.minimum_scale == constraints.maximum_scale && |
| constraints.minimum_scale != -1); |
| } |
| |
| cc::AnimationHost* VisualViewport::GetCompositorAnimationHost() const { |
| DCHECK(IsActiveViewport()); |
| DCHECK(GetChromeClient()); |
| return GetChromeClient()->GetCompositorAnimationHost(LocalMainFrame()); |
| } |
| |
| cc::AnimationTimeline* VisualViewport::GetCompositorAnimationTimeline() const { |
| DCHECK(IsActiveViewport()); |
| DCHECK(GetChromeClient()); |
| return GetChromeClient()->GetScrollAnimationTimeline(LocalMainFrame()); |
| } |
| |
| void VisualViewport::NotifyRootFrameViewport() const { |
| DCHECK(IsActiveViewport()); |
| |
| if (!GetRootFrameViewport()) |
| return; |
| |
| GetRootFrameViewport()->DidUpdateVisualViewport(); |
| } |
| |
| ScrollbarTheme& VisualViewport::GetPageScrollbarTheme() const { |
| return GetPage().GetScrollbarTheme(); |
| } |
| |
| PaintArtifactCompositor* VisualViewport::GetPaintArtifactCompositor() const { |
| DCHECK(IsActiveViewport()); |
| |
| LocalFrameView* frame_view = LocalMainFrame().View(); |
| if (!frame_view) |
| return nullptr; |
| |
| return frame_view->GetPaintArtifactCompositor(); |
| } |
| |
| std::unique_ptr<TracedValue> VisualViewport::ViewportToTracedValue() const { |
| auto value = std::make_unique<TracedValue>(); |
| gfx::Rect 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())); |
| value->SetBoolean("isActive", IsActiveViewport()); |
| 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 { |
| if (!IsActiveViewport()) |
| return; |
| |
| // TODO(crbug.com/1015625): Avoid scroll_layer_. |
| if (scroll_layer_) { |
| auto state = parent_property_tree_state_; |
| state.SetTransform(*scroll_translation_node_); |
| DEFINE_STATIC_DISPLAY_ITEM_CLIENT(client, "Inner Viewport Scroll Layer"); |
| RecordForeignLayer(context, *client, |
| DisplayItem::kForeignLayerViewportScroll, scroll_layer_, |
| gfx::Point(), &state); |
| } |
| |
| if (scrollbar_layer_horizontal_) { |
| auto state = parent_property_tree_state_; |
| state.SetEffect(*horizontal_scrollbar_effect_node_); |
| DEFINE_STATIC_DISPLAY_ITEM_CLIENT(client, |
| "Inner Viewport Horizontal Scrollbar"); |
| RecordForeignLayer( |
| context, *client, DisplayItem::kForeignLayerViewportScrollbar, |
| scrollbar_layer_horizontal_, |
| gfx::Point(0, size_.height() - ScrollbarThickness()), &state); |
| } |
| |
| if (scrollbar_layer_vertical_) { |
| auto state = parent_property_tree_state_; |
| state.SetEffect(*vertical_scrollbar_effect_node_); |
| DEFINE_STATIC_DISPLAY_ITEM_CLIENT(client, |
| "Inner Viewport Vertical Scrollbar"); |
| RecordForeignLayer( |
| context, *client, DisplayItem::kForeignLayerViewportScrollbar, |
| scrollbar_layer_vertical_, |
| gfx::Point(size_.width() - ScrollbarThickness(), 0), &state); |
| } |
| } |
| |
| void VisualViewport::UsedColorSchemeChanged() { |
| DCHECK(IsActiveViewport()); |
| // The scrollbar overlay color theme depends on the used color scheme. |
| RecalculateScrollbarOverlayColorTheme(); |
| } |
| |
| void VisualViewport::ScrollbarColorChanged() { |
| DCHECK(IsActiveViewport()); |
| if (scrollbar_layer_horizontal_) { |
| DCHECK(scrollbar_layer_vertical_); |
| UpdateScrollbarColor(*scrollbar_layer_horizontal_); |
| UpdateScrollbarColor(*scrollbar_layer_vertical_); |
| } |
| } |
| |
| void VisualViewport::UpdateScrollbarColor(cc::SolidColorScrollbarLayer& layer) { |
| auto& theme = ScrollbarThemeOverlayMobile::GetInstance(); |
| layer.SetColor(theme.GetSolidColor(CSSScrollbarThumbColor())); |
| } |
| |
| } // namespace blink |