blob: 3d1b6045f37c1c71fc39fec2de89edbbedd61902 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.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/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.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/overscroll_controller.h"
#include "third_party/blink/renderer/core/page/scrolling/viewport_scroll_callback.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
namespace blink {
namespace {
ScrollableArea* GetScrollableArea(Node* node) {
if (!node || !node->GetLayoutObject() ||
!node->GetLayoutObject()->IsBoxModelObject())
return nullptr;
return To<LayoutBoxModelObject>(node->GetLayoutObject())->GetScrollableArea();
}
} // namespace
TopDocumentRootScrollerController::TopDocumentRootScrollerController(Page& page)
: page_(&page) {}
void TopDocumentRootScrollerController::Trace(Visitor* visitor) const {
visitor->Trace(viewport_apply_scroll_);
visitor->Trace(global_root_scroller_);
visitor->Trace(page_);
}
void TopDocumentRootScrollerController::DidChangeRootScroller() {
Node* target = FindGlobalRootScroller();
UpdateGlobalRootScroller(target);
}
void TopDocumentRootScrollerController::DidResizeViewport() {
if (!GlobalRootScroller() || !GlobalRootScroller()->GetDocument().IsActive())
return;
if (!GlobalRootScroller()->GetLayoutObject())
return;
auto* layout_object =
To<LayoutBoxModelObject>(GlobalRootScroller()->GetLayoutObject());
// Top controls can resize the viewport without invalidating compositing or
// paint so we need to do that manually here.
if (layout_object->HasLayer()) {
layout_object->Layer()->SetNeedsCompositingInputsUpdate();
layout_object->Layer()->UpdateSelfPaintingLayer();
}
layout_object->SetNeedsPaintPropertyUpdate();
}
ScrollableArea* TopDocumentRootScrollerController::RootScrollerArea() const {
return GetScrollableArea(GlobalRootScroller());
}
IntSize TopDocumentRootScrollerController::RootScrollerVisibleArea() const {
if (!TopDocument() || !TopDocument()->View())
return IntSize();
float minimum_page_scale =
page_->GetPageScaleConstraintsSet().FinalConstraints().minimum_scale;
int browser_controls_adjustment =
ceilf(page_->GetVisualViewport().BrowserControlsAdjustment() /
minimum_page_scale);
return TopDocument()
->View()
->LayoutViewport()
->VisibleContentRect(kExcludeScrollbars)
.Size() +
IntSize(0, browser_controls_adjustment);
}
Node* TopDocumentRootScrollerController::FindGlobalRootScroller() {
if (!TopDocument())
return nullptr;
Node* root_scroller =
&TopDocument()->GetRootScrollerController().EffectiveRootScroller();
while (auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(root_scroller)) {
Document* iframe_document = frame_owner->contentDocument();
if (!iframe_document)
return root_scroller;
root_scroller =
&iframe_document->GetRootScrollerController().EffectiveRootScroller();
}
return root_scroller;
}
void SetNeedsCompositingUpdateOnAncestors(Node* node) {
if (!node || !node->GetDocument().IsActive())
return;
ScrollableArea* area = GetScrollableArea(node);
if (!area || !area->Layer())
return;
Frame* frame = area->Layer()->GetLayoutObject().GetFrame();
for (; frame; frame = frame->Tree().Parent()) {
auto* local_frame = DynamicTo<LocalFrame>(frame);
if (!local_frame)
continue;
LayoutView* layout_view = local_frame->View()->GetLayoutView();
PaintLayer* frame_root_layer = layout_view->Layer();
DCHECK(frame_root_layer);
frame_root_layer->SetNeedsCompositingInputsUpdate();
}
}
void TopDocumentRootScrollerController::UpdateGlobalRootScroller(
Node* new_global_root_scroller) {
if (!viewport_apply_scroll_)
return;
// Note, the layout object can be replaced during a rebuild. In that case,
// re-run process even if the element itself is the same.
if (new_global_root_scroller == global_root_scroller_ &&
global_root_scroller_->GetLayoutObject()->IsGlobalRootScroller())
return;
ScrollableArea* target_scroller = GetScrollableArea(new_global_root_scroller);
if (!target_scroller)
return;
if (global_root_scroller_)
global_root_scroller_->RemoveApplyScroll();
// Use disable-native-scroll since the ViewportScrollCallback needs to
// apply scroll actions both before (BrowserControls) and after (overscroll)
// scrolling the element so it will apply scroll to the element itself.
new_global_root_scroller->SetApplyScroll(viewport_apply_scroll_);
Node* old_root_scroller = global_root_scroller_;
global_root_scroller_ = new_global_root_scroller;
// Ideally, scroll customization would pass the current element to scroll to
// the apply scroll callback but this doesn't happen today so we set it
// through a back door here. This is also needed by the
// ViewportScrollCallback to swap the new global root scroller into the
// layout viewport in RootFrameViewport.
viewport_apply_scroll_->SetScroller(target_scroller);
SetNeedsCompositingUpdateOnAncestors(old_root_scroller);
SetNeedsCompositingUpdateOnAncestors(new_global_root_scroller);
UpdateCachedBits(old_root_scroller, new_global_root_scroller);
if (ScrollableArea* area = GetScrollableArea(old_root_scroller)) {
if (old_root_scroller->GetDocument().IsActive())
area->DidChangeGlobalRootScroller();
}
target_scroller->DidChangeGlobalRootScroller();
}
void TopDocumentRootScrollerController::UpdateCachedBits(Node* old_global,
Node* new_global) {
if (old_global) {
if (LayoutObject* object = old_global->GetLayoutObject())
object->SetIsGlobalRootScroller(false);
}
if (new_global) {
if (LayoutObject* object = new_global->GetLayoutObject())
object->SetIsGlobalRootScroller(true);
}
}
Document* TopDocumentRootScrollerController::TopDocument() const {
if (!page_)
return nullptr;
auto* main_local_frame = DynamicTo<LocalFrame>(page_->MainFrame());
return main_local_frame ? main_local_frame->GetDocument() : nullptr;
}
void TopDocumentRootScrollerController::DidDisposeScrollableArea(
ScrollableArea& area) {
if (!TopDocument() || !TopDocument()->View())
return;
// If the document is tearing down, we may no longer have a layoutViewport to
// fallback to.
if (TopDocument()->Lifecycle().GetState() >= DocumentLifecycle::kStopping)
return;
LocalFrameView* frame_view = TopDocument()->View();
RootFrameViewport* rfv = frame_view->GetRootFrameViewport();
if (rfv && &area == &rfv->LayoutViewport()) {
DCHECK(frame_view->LayoutViewport());
rfv->SetLayoutViewport(*frame_view->LayoutViewport());
}
}
void TopDocumentRootScrollerController::InitializeViewportScrollCallback(
RootFrameViewport& root_frame_viewport,
Document& main_document) {
DCHECK(page_);
viewport_apply_scroll_ = MakeGarbageCollected<ViewportScrollCallback>(
&page_->GetBrowserControls(), &page_->GetOverscrollController(),
root_frame_viewport);
// Initialize global_root_scroller_ to the default; the main document node.
// We can't yet reliably compute this because the frame we're loading may not
// be swapped into the main frame yet so TopDocument returns nullptr.
UpdateGlobalRootScroller(&main_document);
}
bool TopDocumentRootScrollerController::IsViewportScrollCallback(
const ScrollStateCallback* callback) const {
if (!callback)
return false;
return callback == viewport_apply_scroll_.Get();
}
Node* TopDocumentRootScrollerController::GlobalRootScroller() const {
return global_root_scroller_.Get();
}
} // namespace blink