blob: 42228126b34354524138d8387478430b98df81af [file] [log] [blame]
/*
* 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/FrameView.h"
#include "core/frame/LocalFrame.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.
static const TimeDelta kAutoscrollDelay = TimeDelta::FromSecondsD(0.2);
AutoscrollController* AutoscrollController::Create(Page& page) {
return new AutoscrollController(page);
}
AutoscrollController::AutoscrollController(Page& page)
: page_(&page),
autoscroll_layout_object_(nullptr),
pressed_layout_object_(nullptr),
autoscroll_type_(kNoAutoscroll),
did_latch_for_middle_click_autoscroll_(false) {}
DEFINE_TRACE(AutoscrollController) {
visitor->Trace(page_);
}
bool AutoscrollController::AutoscrollInProgress() const {
return autoscroll_type_ == kAutoscrollForSelection;
}
bool AutoscrollController::AutoscrollInProgress(
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;
StartAutoscroll();
}
void AutoscrollController::StopAutoscroll() {
if (pressed_layout_object_) {
pressed_layout_object_->StopAutoscroll();
pressed_layout_object_ = nullptr;
}
LayoutBox* scrollable = autoscroll_layout_object_;
autoscroll_layout_object_ = nullptr;
if (!scrollable)
return;
if (RuntimeEnabledFeatures::middleClickAutoscrollEnabled() &&
MiddleClickAutoscrollInProgress()) {
if (FrameView* view = scrollable->GetFrame()->View()) {
view->SetCursor(PointerCursor());
}
}
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_;
if (RuntimeEnabledFeatures::middleClickAutoscrollEnabled()) {
HitTestResult hit_test =
layout_object->GetFrame()->GetEventHandler().HitTestResultAtPoint(
middle_click_autoscroll_start_pos_,
HitTestRequest::kReadOnly | HitTestRequest::kActive);
if (Node* node_at_point = hit_test.InnerNode())
layout_object = node_at_point->GetLayoutObject();
}
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_type_ != kNoAutoscroll && !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(page_->MainFrame(), UseCounter::kDragAndDropScrollStart);
StartAutoscroll();
} else if (autoscroll_layout_object_ != scrollable) {
drag_and_drop_autoscroll_start_time_ = event_time;
autoscroll_layout_object_ = scrollable;
}
}
void AutoscrollController::HandleMouseReleaseForMiddleClickAutoscroll(
LocalFrame* frame,
const WebMouseEvent& mouse_event) {
DCHECK(RuntimeEnabledFeatures::middleClickAutoscrollEnabled());
if (!frame->IsMainFrame())
return;
switch (autoscroll_type_) {
case kAutoscrollForMiddleClick:
if (mouse_event.button == WebPointerProperties::Button::kMiddle)
autoscroll_type_ = kAutoscrollForMiddleClickCanStop;
break;
case kAutoscrollForMiddleClickCanStop:
StopAutoscroll();
break;
case kAutoscrollForDragAndDrop:
case kAutoscrollForSelection:
case kNoAutoscroll:
// Nothing to do.
break;
}
}
bool AutoscrollController::MiddleClickAutoscrollInProgress() const {
return autoscroll_type_ == kAutoscrollForMiddleClickCanStop ||
autoscroll_type_ == kAutoscrollForMiddleClick;
}
void AutoscrollController::StartMiddleClickAutoscroll(
LayoutBox* scrollable,
const IntPoint& last_known_mouse_position) {
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;
autoscroll_layout_object_ = scrollable;
middle_click_autoscroll_start_pos_ = last_known_mouse_position;
did_latch_for_middle_click_autoscroll_ = false;
UseCounter::Count(page_->MainFrame(),
UseCounter::kMiddleClickAutoscrollStart);
StartAutoscroll();
}
static inline int AdjustedScrollDelta(int beginning_delta) {
// This implemention matches Firefox's.
// http://mxr.mozilla.org/firefox/source/toolkit/content/widgets/browser.xml#856.
const int kSpeedReducer = 12;
int adjusted_delta = beginning_delta / kSpeedReducer;
if (adjusted_delta > 1) {
adjusted_delta =
static_cast<int>(adjusted_delta *
sqrt(static_cast<double>(adjusted_delta))) -
1;
} else if (adjusted_delta < -1) {
adjusted_delta =
static_cast<int>(adjusted_delta *
sqrt(static_cast<double>(-adjusted_delta))) +
1;
}
return adjusted_delta;
}
static inline IntSize AdjustedScrollDelta(const IntSize& delta) {
return IntSize(AdjustedScrollDelta(delta.Width()),
AdjustedScrollDelta(delta.Height()));
}
FloatSize AutoscrollController::CalculateAutoscrollDelta() {
LocalFrame* frame = autoscroll_layout_object_->GetFrame();
if (!frame)
return FloatSize();
IntPoint last_known_mouse_position =
frame->GetEventHandler().LastKnownMousePosition();
// We need to check if the last known mouse position is out of the window.
// When the mouse is out of the window, the position is incoherent
static IntPoint previous_mouse_position;
if (last_known_mouse_position.X() < 0 || last_known_mouse_position.Y() < 0)
last_known_mouse_position = previous_mouse_position;
else
previous_mouse_position = last_known_mouse_position;
IntSize delta =
last_known_mouse_position - middle_click_autoscroll_start_pos_;
// at the center we let the space for the icon.
if (abs(delta.Width()) <= kNoMiddleClickAutoscrollRadius)
delta.SetWidth(0);
if (abs(delta.Height()) <= kNoMiddleClickAutoscrollRadius)
delta.SetHeight(0);
return FloatSize(AdjustedScrollDelta(delta));
}
void AutoscrollController::Animate(double) {
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:
if ((TimeTicks::Now() - 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();
autoscroll_layout_object_->Autoscroll(selection_point);
break;
case kNoAutoscroll:
break;
case kAutoscrollForMiddleClickCanStop:
case kAutoscrollForMiddleClick:
DCHECK(RuntimeEnabledFeatures::middleClickAutoscrollEnabled());
if (!MiddleClickAutoscrollInProgress()) {
StopAutoscroll();
return;
}
if (FrameView* view = autoscroll_layout_object_->GetFrame()->View())
UpdateMiddleClickAutoscrollState(
view, event_handler.LastKnownMousePosition());
FloatSize delta = CalculateAutoscrollDelta();
if (delta.IsZero())
break;
ScrollResult result =
autoscroll_layout_object_->Scroll(kScrollByPixel, delta);
LayoutObject* layout_object = autoscroll_layout_object_;
while (!did_latch_for_middle_click_autoscroll_ && !result.DidScroll()) {
if (layout_object->GetNode() &&
layout_object->GetNode()->IsDocumentNode()) {
Element* owner = ToDocument(layout_object->GetNode())->LocalOwner();
layout_object = owner ? owner->GetLayoutObject() : nullptr;
} else {
layout_object = layout_object->Parent();
}
if (!layout_object) {
break;
}
if (layout_object && layout_object->IsBox() &&
ToLayoutBox(layout_object)->CanBeScrolledAndHasScrollableArea())
result = ToLayoutBox(layout_object)->Scroll(kScrollByPixel, delta);
}
if (result.DidScroll()) {
did_latch_for_middle_click_autoscroll_ = true;
autoscroll_layout_object_ = ToLayoutBox(layout_object);
}
break;
}
if (autoscroll_type_ != kNoAutoscroll && autoscroll_layout_object_) {
page_->GetChromeClient().ScheduleAnimation(
autoscroll_layout_object_->GetFrame()->View());
}
}
void AutoscrollController::StartAutoscroll() {
page_->GetChromeClient().ScheduleAnimation(
autoscroll_layout_object_->GetFrame()->View());
}
void AutoscrollController::UpdateMiddleClickAutoscrollState(
FrameView* view,
const IntPoint& last_known_mouse_position) {
DCHECK(RuntimeEnabledFeatures::middleClickAutoscrollEnabled());
// 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 = middle_click_autoscroll_start_pos_.X() <
(last_known_mouse_position.X() - kNoMiddleClickAutoscrollRadius);
bool west = middle_click_autoscroll_start_pos_.X() >
(last_known_mouse_position.X() + kNoMiddleClickAutoscrollRadius);
bool north = middle_click_autoscroll_start_pos_.Y() >
(last_known_mouse_position.Y() + kNoMiddleClickAutoscrollRadius);
bool south = middle_click_autoscroll_start_pos_.Y() <
(last_known_mouse_position.Y() - kNoMiddleClickAutoscrollRadius);
if (autoscroll_type_ == kAutoscrollForMiddleClick &&
(east || west || north || south))
autoscroll_type_ = kAutoscrollForMiddleClickCanStop;
if (north) {
if (east)
view->SetCursor(NorthEastPanningCursor());
else if (west)
view->SetCursor(NorthWestPanningCursor());
else
view->SetCursor(NorthPanningCursor());
} else if (south) {
if (east)
view->SetCursor(SouthEastPanningCursor());
else if (west)
view->SetCursor(SouthWestPanningCursor());
else
view->SetCursor(SouthPanningCursor());
} else if (east) {
view->SetCursor(EastPanningCursor());
} else if (west) {
view->SetCursor(WestPanningCursor());
} else {
view->SetCursor(MiddlePanningCursor());
}
}
} // namespace blink