| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights |
| * reserved. |
| * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies) |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 "core/page/AutoscrollController.h" |
| |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/input/EventHandler.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutBox.h" |
| #include "core/layout/LayoutListBox.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/Page.h" |
| #include "platform/wtf/Time.h" |
| |
| namespace blink { |
| |
| // Delay time in second for start autoscroll if pointer is in border edge of |
| // scrollable element. |
| constexpr TimeDelta kAutoscrollDelay = TimeDelta::FromSecondsD(0.2); |
| |
| static const int kNoMiddleClickAutoscrollRadius = 15; |
| |
| static const Cursor& MiddleClickAutoscrollCursor(const FloatSize& velocity) { |
| // At the original click location we draw a 4 arrowed icon. Over this icon |
| // there won't be any scroll, So don't change the cursor over this area. |
| bool east = velocity.Width() < 0; |
| bool west = velocity.Width() > 0; |
| bool north = velocity.Height() > 0; |
| bool south = velocity.Height() < 0; |
| |
| if (north) { |
| if (east) |
| return NorthEastPanningCursor(); |
| if (west) |
| return NorthWestPanningCursor(); |
| return NorthPanningCursor(); |
| } |
| if (south) { |
| if (east) |
| return SouthEastPanningCursor(); |
| if (west) |
| return SouthWestPanningCursor(); |
| return SouthPanningCursor(); |
| } |
| if (east) |
| return EastPanningCursor(); |
| if (west) |
| return WestPanningCursor(); |
| return MiddlePanningCursor(); |
| } |
| |
| AutoscrollController* AutoscrollController::Create(Page& page) { |
| return new AutoscrollController(page); |
| } |
| |
| AutoscrollController::AutoscrollController(Page& page) : page_(&page) {} |
| |
| void AutoscrollController::Trace(blink::Visitor* visitor) { |
| visitor->Trace(page_); |
| } |
| |
| bool AutoscrollController::SelectionAutoscrollInProgress() const { |
| return autoscroll_type_ == kAutoscrollForSelection; |
| } |
| |
| bool AutoscrollController::AutoscrollInProgress() const { |
| return autoscroll_layout_object_; |
| } |
| |
| bool AutoscrollController::AutoscrollInProgressFor( |
| const LayoutBox* layout_object) const { |
| return autoscroll_layout_object_ == layout_object; |
| } |
| |
| void AutoscrollController::StartAutoscrollForSelection( |
| LayoutObject* layout_object) { |
| // We don't want to trigger the autoscroll or the middleClickAutoscroll if |
| // it's already active. |
| if (autoscroll_type_ != kNoAutoscroll) |
| return; |
| LayoutBox* scrollable = LayoutBox::FindAutoscrollable(layout_object); |
| if (!scrollable) |
| scrollable = |
| layout_object->IsListBox() ? ToLayoutListBox(layout_object) : nullptr; |
| if (!scrollable) |
| return; |
| |
| pressed_layout_object_ = layout_object && layout_object->IsBox() |
| ? ToLayoutBox(layout_object) |
| : nullptr; |
| autoscroll_type_ = kAutoscrollForSelection; |
| autoscroll_layout_object_ = scrollable; |
| ScheduleMainThreadAnimation(); |
| } |
| |
| void AutoscrollController::StopAutoscroll() { |
| if (pressed_layout_object_) { |
| pressed_layout_object_->StopAutoscroll(); |
| pressed_layout_object_ = nullptr; |
| } |
| autoscroll_layout_object_ = nullptr; |
| autoscroll_type_ = kNoAutoscroll; |
| } |
| |
| void AutoscrollController::StopAutoscrollIfNeeded(LayoutObject* layout_object) { |
| if (pressed_layout_object_ == layout_object) |
| pressed_layout_object_ = nullptr; |
| |
| if (autoscroll_layout_object_ != layout_object) |
| return; |
| autoscroll_layout_object_ = nullptr; |
| autoscroll_type_ = kNoAutoscroll; |
| } |
| |
| void AutoscrollController::UpdateAutoscrollLayoutObject() { |
| if (!autoscroll_layout_object_) |
| return; |
| |
| LayoutObject* layout_object = autoscroll_layout_object_; |
| |
| while (layout_object && !(layout_object->IsBox() && |
| ToLayoutBox(layout_object)->CanAutoscroll())) |
| layout_object = layout_object->Parent(); |
| |
| autoscroll_layout_object_ = layout_object && layout_object->IsBox() |
| ? ToLayoutBox(layout_object) |
| : nullptr; |
| |
| if (!autoscroll_layout_object_) |
| autoscroll_type_ = kNoAutoscroll; |
| } |
| |
| void AutoscrollController::UpdateDragAndDrop(Node* drop_target_node, |
| const IntPoint& event_position, |
| TimeTicks event_time) { |
| if (!drop_target_node || !drop_target_node->GetLayoutObject()) { |
| StopAutoscroll(); |
| return; |
| } |
| |
| if (autoscroll_layout_object_ && |
| autoscroll_layout_object_->GetFrame() != |
| drop_target_node->GetLayoutObject()->GetFrame()) |
| return; |
| |
| drop_target_node->GetLayoutObject() |
| ->GetFrameView() |
| ->UpdateAllLifecyclePhasesExceptPaint(); |
| |
| LayoutBox* scrollable = |
| LayoutBox::FindAutoscrollable(drop_target_node->GetLayoutObject()); |
| if (!scrollable) { |
| StopAutoscroll(); |
| return; |
| } |
| |
| Page* page = |
| scrollable->GetFrame() ? scrollable->GetFrame()->GetPage() : nullptr; |
| if (!page) { |
| StopAutoscroll(); |
| return; |
| } |
| |
| IntSize offset = scrollable->CalculateAutoscrollDirection(event_position); |
| if (offset.IsZero()) { |
| StopAutoscroll(); |
| return; |
| } |
| |
| drag_and_drop_autoscroll_reference_position_ = event_position + offset; |
| |
| if (autoscroll_type_ == kNoAutoscroll) { |
| autoscroll_type_ = kAutoscrollForDragAndDrop; |
| autoscroll_layout_object_ = scrollable; |
| drag_and_drop_autoscroll_start_time_ = event_time; |
| UseCounter::Count(autoscroll_layout_object_->GetFrame(), |
| WebFeature::kDragAndDropScrollStart); |
| ScheduleMainThreadAnimation(); |
| } else if (autoscroll_layout_object_ != scrollable) { |
| drag_and_drop_autoscroll_start_time_ = event_time; |
| autoscroll_layout_object_ = scrollable; |
| } |
| } |
| |
| void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll( |
| LocalFrame* frame, |
| const FloatPoint& position_global, |
| bool is_middle_button) { |
| if (!MiddleClickAutoscrollInProgress()) |
| return; |
| |
| LocalFrameView* view = frame->View(); |
| if (!view) |
| return; |
| |
| FloatSize distance = |
| (position_global - middle_click_autoscroll_start_pos_global_) |
| .ScaledBy(1 / frame->DevicePixelRatio()); |
| |
| if (fabs(distance.Width()) <= kNoMiddleClickAutoscrollRadius) |
| distance.SetWidth(0); |
| if (fabs(distance.Height()) <= kNoMiddleClickAutoscrollRadius) |
| distance.SetHeight(0); |
| |
| const float kExponent = 2.2f; |
| const float kMultiplier = -0.000008f; |
| const int x_signum = (distance.Width() < 0) ? -1 : (distance.Width() > 0); |
| const int y_signum = (distance.Height() < 0) ? -1 : (distance.Height() > 0); |
| FloatSize velocity( |
| pow(fabs(distance.Width()), kExponent) * kMultiplier * x_signum, |
| pow(fabs(distance.Height()), kExponent) * kMultiplier * y_signum); |
| |
| if (velocity != last_velocity_) { |
| last_velocity_ = velocity; |
| if (middle_click_mode_ == kMiddleClickInitial) |
| middle_click_mode_ = kMiddleClickHolding; |
| page_->GetChromeClient().SetCursorOverridden(false); |
| view->SetCursor(MiddleClickAutoscrollCursor(velocity)); |
| page_->GetChromeClient().SetCursorOverridden(true); |
| page_->GetChromeClient().AutoscrollFling(velocity, frame); |
| } |
| } |
| |
| void AutoscrollController::HandleMouseReleaseForMiddleClickAutoscroll( |
| LocalFrame* frame, |
| bool is_middle_button) { |
| DCHECK(RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled()); |
| if (!MiddleClickAutoscrollInProgress()) |
| return; |
| |
| if (!frame->IsMainFrame()) |
| return; |
| |
| if (middle_click_mode_ == kMiddleClickInitial && is_middle_button) |
| middle_click_mode_ = kMiddleClickToggled; |
| else if (middle_click_mode_ == kMiddleClickHolding) |
| StopMiddleClickAutoscroll(frame); |
| } |
| |
| void AutoscrollController::StopMiddleClickAutoscroll(LocalFrame* frame) { |
| if (!MiddleClickAutoscrollInProgress()) |
| return; |
| |
| page_->GetChromeClient().AutoscrollEnd(frame); |
| autoscroll_type_ = kNoAutoscroll; |
| page_->GetChromeClient().SetCursorOverridden(false); |
| frame->LocalFrameRoot().GetEventHandler().ScheduleCursorUpdate(); |
| } |
| |
| bool AutoscrollController::MiddleClickAutoscrollInProgress() const { |
| return autoscroll_type_ == kAutoscrollForMiddleClick; |
| } |
| |
| void AutoscrollController::StartMiddleClickAutoscroll( |
| LocalFrame* frame, |
| const FloatPoint& position, |
| const FloatPoint& position_global) { |
| DCHECK(RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled()); |
| // We don't want to trigger the autoscroll or the middleClickAutoscroll if |
| // it's already active. |
| if (autoscroll_type_ != kNoAutoscroll) |
| return; |
| |
| autoscroll_type_ = kAutoscrollForMiddleClick; |
| middle_click_mode_ = kMiddleClickInitial; |
| middle_click_autoscroll_start_pos_global_ = position_global; |
| |
| UseCounter::Count(frame, WebFeature::kMiddleClickAutoscrollStart); |
| |
| last_velocity_ = FloatSize(); |
| |
| if (LocalFrameView* view = frame->View()) |
| view->SetCursor(MiddleClickAutoscrollCursor(last_velocity_)); |
| page_->GetChromeClient().SetCursorOverridden(true); |
| page_->GetChromeClient().AutoscrollStart( |
| position.ScaledBy(1 / frame->DevicePixelRatio()), frame); |
| } |
| |
| void AutoscrollController::Animate(double) { |
| // Middle-click autoscroll isn't handled on the main thread. |
| if (MiddleClickAutoscrollInProgress()) |
| return; |
| |
| if (!autoscroll_layout_object_ || !autoscroll_layout_object_->GetFrame()) { |
| StopAutoscroll(); |
| return; |
| } |
| |
| EventHandler& event_handler = |
| autoscroll_layout_object_->GetFrame()->GetEventHandler(); |
| IntSize offset = autoscroll_layout_object_->CalculateAutoscrollDirection( |
| event_handler.LastKnownMousePosition()); |
| IntPoint selection_point = event_handler.LastKnownMousePosition() + offset; |
| switch (autoscroll_type_) { |
| case kAutoscrollForDragAndDrop: |
| ScheduleMainThreadAnimation(); |
| if ((CurrentTimeTicks() - drag_and_drop_autoscroll_start_time_) > |
| kAutoscrollDelay) |
| autoscroll_layout_object_->Autoscroll( |
| drag_and_drop_autoscroll_reference_position_); |
| break; |
| case kAutoscrollForSelection: |
| if (!event_handler.MousePressed()) { |
| StopAutoscroll(); |
| return; |
| } |
| event_handler.UpdateSelectionForMouseDrag(); |
| |
| // UpdateSelectionForMouseDrag may call layout to cancel auto scroll |
| // animation. |
| if (autoscroll_type_ != kNoAutoscroll) { |
| DCHECK(autoscroll_layout_object_); |
| ScheduleMainThreadAnimation(); |
| autoscroll_layout_object_->Autoscroll(selection_point); |
| } |
| break; |
| case kNoAutoscroll: |
| case kAutoscrollForMiddleClick: |
| break; |
| } |
| } |
| |
| void AutoscrollController::ScheduleMainThreadAnimation() { |
| page_->GetChromeClient().ScheduleAnimation( |
| autoscroll_layout_object_->GetFrame()->View()); |
| } |
| |
| bool AutoscrollController::IsAutoscrolling() const { |
| return (autoscroll_type_ != kNoAutoscroll); |
| } |
| |
| } // namespace blink |