blob: fa7863d60c8b6ac870809fcbd2b7dd654b5bc5d7 [file] [log] [blame]
// Copyright 2015 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/frame/rotation_viewport_anchor.h"
#include "third_party/blink/renderer/core/dom/container_node.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/frame/local_frame.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/input/event_handler.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/platform/geometry/double_rect.h"
namespace blink {
namespace {
static const float kViewportAnchorRelativeEpsilon = 0.1f;
static const int kViewportToNodeMaxRelativeArea = 2;
Node* FindNonEmptyAnchorNode(const FloatPoint& absolute_point,
const IntRect& view_rect,
EventHandler& event_handler) {
IntPoint point = FlooredIntPoint(absolute_point);
HitTestLocation location(point);
Node* node =
event_handler
.HitTestResultAtLocation(
location, HitTestRequest::kReadOnly | HitTestRequest::kActive)
.InnerNode();
if (!node)
return nullptr;
// If the node bounding box is sufficiently large, make a single attempt to
// find a smaller node; the larger the node bounds, the greater the
// variability under resize.
IntSize node_size =
node->GetLayoutObject()
? node->GetLayoutObject()->AbsoluteBoundingBoxRect().Size()
: IntSize();
const int max_node_area =
view_rect.Width() * view_rect.Height() * kViewportToNodeMaxRelativeArea;
if (node_size.Width() * node_size.Height() > max_node_area) {
IntSize point_offset = view_rect.Size();
point_offset.Scale(kViewportAnchorRelativeEpsilon);
HitTestLocation alternative_location(point + point_offset);
node = event_handler
.HitTestResultAtLocation(
alternative_location,
HitTestRequest::kReadOnly | HitTestRequest::kActive)
.InnerNode();
}
while (node &&
(!node->GetLayoutObject() ||
node->GetLayoutObject()->AbsoluteBoundingBoxRect().IsEmpty())) {
node = node->parentNode();
}
return node;
}
void MoveToEncloseRect(IntRect& outer, const FloatRect& inner) {
IntPoint minimum_position =
CeiledIntPoint(inner.Location() + inner.Size() - FloatSize(outer.Size()));
IntPoint maximum_position = FlooredIntPoint(inner.Location());
IntPoint outer_origin = outer.Location();
outer_origin = outer_origin.ExpandedTo(minimum_position);
outer_origin = outer_origin.ShrunkTo(maximum_position);
outer.SetLocation(outer_origin);
}
void MoveIntoRect(FloatRect& inner, const IntRect& outer) {
FloatPoint minimum_position = FloatPoint(outer.Location());
FloatPoint maximum_position = minimum_position + outer.Size() - inner.Size();
// Adjust maximumPosition to the nearest lower integer because
// VisualViewport::maximumScrollPosition() does the same.
// The value of minumumPosition is already adjusted since it is
// constructed from an integer point.
maximum_position = FloatPoint(FlooredIntPoint(maximum_position));
FloatPoint inner_origin = inner.Location();
inner_origin = inner_origin.ExpandedTo(minimum_position);
inner_origin = inner_origin.ShrunkTo(maximum_position);
inner.SetLocation(inner_origin);
}
} // namespace
RotationViewportAnchor::RotationViewportAnchor(
LocalFrameView& root_frame_view,
VisualViewport& visual_viewport,
const FloatSize& anchor_in_inner_view_coords,
PageScaleConstraintsSet& page_scale_constraints_set)
: root_frame_view_(&root_frame_view),
visual_viewport_(&visual_viewport),
anchor_in_inner_view_coords_(anchor_in_inner_view_coords),
page_scale_constraints_set_(&page_scale_constraints_set) {
SetAnchor();
}
RotationViewportAnchor::~RotationViewportAnchor() {
RestoreToAnchor();
}
void RotationViewportAnchor::SetAnchor() {
RootFrameViewport* root_frame_viewport =
root_frame_view_->GetRootFrameViewport();
DCHECK(root_frame_viewport);
old_page_scale_factor_ = visual_viewport_->Scale();
old_minimum_page_scale_factor_ =
page_scale_constraints_set_->FinalConstraints().minimum_scale;
// Save the absolute location in case we won't find the anchor node, we'll
// fall back to that.
visual_viewport_in_document_ =
FloatPoint(root_frame_viewport->VisibleContentRect().Location());
anchor_node_.Clear();
anchor_node_bounds_ = LayoutRect();
anchor_in_node_coords_ = FloatSize();
normalized_visual_viewport_offset_ = FloatSize();
IntRect inner_view_rect = root_frame_viewport->VisibleContentRect();
// Preserve origins at the absolute screen origin.
if (inner_view_rect.Location() == IntPoint::Zero() ||
inner_view_rect.IsEmpty())
return;
IntRect outer_view_rect =
LayoutViewport().VisibleContentRect(kIncludeScrollbars);
// Normalize by the size of the outer rect
DCHECK(!outer_view_rect.IsEmpty());
normalized_visual_viewport_offset_ = visual_viewport_->GetScrollOffset();
normalized_visual_viewport_offset_.Scale(1.0 / outer_view_rect.Width(),
1.0 / outer_view_rect.Height());
// Note, we specifically use the unscaled visual viewport size here as the
// conversion into content-space below will apply the scale.
FloatPoint anchor_offset(visual_viewport_->Size());
anchor_offset.Scale(anchor_in_inner_view_coords_.Width(),
anchor_in_inner_view_coords_.Height());
// Note, we specifically convert to the rootFrameView contents here, rather
// than the layout viewport. That's because hit testing works from the
// LocalFrameView's absolute coordinates even if it's not the layout viewport.
const FloatPoint anchor_point_in_document =
root_frame_view_->RootFrameToDocument(
visual_viewport_->ViewportToRootFrame(anchor_offset));
Node* node = FindNonEmptyAnchorNode(
root_frame_view_->DocumentToFrame(anchor_point_in_document),
inner_view_rect, root_frame_view_->GetFrame().GetEventHandler());
if (!node || !node->GetLayoutObject())
return;
anchor_node_ = node;
anchor_node_bounds_ = root_frame_view_->FrameToDocument(
LayoutRect(node->GetLayoutObject()->AbsoluteBoundingBoxRect()));
anchor_in_node_coords_ =
anchor_point_in_document - FloatPoint(anchor_node_bounds_.Location());
anchor_in_node_coords_.Scale(1.f / anchor_node_bounds_.Width(),
1.f / anchor_node_bounds_.Height());
}
void RotationViewportAnchor::RestoreToAnchor() {
float new_page_scale_factor =
old_page_scale_factor_ / old_minimum_page_scale_factor_ *
page_scale_constraints_set_->FinalConstraints().minimum_scale;
new_page_scale_factor =
page_scale_constraints_set_->FinalConstraints().ClampToConstraints(
new_page_scale_factor);
FloatSize visual_viewport_size(visual_viewport_->Size());
visual_viewport_size.Scale(1 / new_page_scale_factor);
IntPoint main_frame_origin;
FloatPoint visual_viewport_origin;
ComputeOrigins(visual_viewport_size, main_frame_origin,
visual_viewport_origin);
LayoutViewport().SetScrollOffset(
ToScrollOffset(FloatPoint(main_frame_origin)), kProgrammaticScroll);
// Set scale before location, since location can be clamped on setting scale.
visual_viewport_->SetScale(new_page_scale_factor);
visual_viewport_->SetLocation(visual_viewport_origin);
}
ScrollableArea& RotationViewportAnchor::LayoutViewport() const {
RootFrameViewport* root_frame_viewport =
root_frame_view_->GetRootFrameViewport();
DCHECK(root_frame_viewport);
return root_frame_viewport->LayoutViewport();
}
void RotationViewportAnchor::ComputeOrigins(
const FloatSize& inner_size,
IntPoint& main_frame_offset,
FloatPoint& visual_viewport_offset) const {
IntSize outer_size = LayoutViewport().VisibleContentRect().Size();
// Compute the viewport origins in CSS pixels relative to the document.
FloatSize abs_visual_viewport_offset = normalized_visual_viewport_offset_;
abs_visual_viewport_offset.Scale(outer_size.Width(), outer_size.Height());
FloatPoint inner_origin = GetInnerOrigin(inner_size);
FloatPoint outer_origin = inner_origin - abs_visual_viewport_offset;
IntRect outer_rect = IntRect(FlooredIntPoint(outer_origin), outer_size);
FloatRect inner_rect = FloatRect(inner_origin, inner_size);
MoveToEncloseRect(outer_rect, inner_rect);
outer_rect.SetLocation(IntPoint(
LayoutViewport().ClampScrollOffset(ToIntSize(outer_rect.Location()))));
MoveIntoRect(inner_rect, outer_rect);
main_frame_offset = outer_rect.Location();
visual_viewport_offset =
FloatPoint(inner_rect.Location() - outer_rect.Location());
}
FloatPoint RotationViewportAnchor::GetInnerOrigin(
const FloatSize& inner_size) const {
if (!anchor_node_ || !anchor_node_->isConnected() ||
!anchor_node_->GetLayoutObject())
return visual_viewport_in_document_;
const LayoutRect current_node_bounds = root_frame_view_->FrameToDocument(
LayoutRect(anchor_node_->GetLayoutObject()->AbsoluteBoundingBoxRect()));
if (anchor_node_bounds_ == current_node_bounds)
return visual_viewport_in_document_;
RootFrameViewport* root_frame_viewport =
root_frame_view_->GetRootFrameViewport();
const LayoutRect current_node_bounds_in_layout_viewport =
root_frame_viewport->RootContentsToLayoutViewportContents(
*root_frame_view_.Get(), current_node_bounds);
// Compute the new anchor point relative to the node position
FloatSize anchor_offset_from_node(
current_node_bounds_in_layout_viewport.Size());
anchor_offset_from_node.Scale(anchor_in_node_coords_.Width(),
anchor_in_node_coords_.Height());
FloatPoint anchor_point =
FloatPoint(current_node_bounds_in_layout_viewport.Location()) +
anchor_offset_from_node;
// Compute the new origin point relative to the new anchor point
FloatSize anchor_offset_from_origin = inner_size;
anchor_offset_from_origin.Scale(anchor_in_inner_view_coords_.Width(),
anchor_in_inner_view_coords_.Height());
return anchor_point - anchor_offset_from_origin;
}
} // namespace blink