| /* |
| * 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/input/EventHandler.h" |
| |
| #include <memory> |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/HTMLNames.h" |
| #include "core/InputTypeNames.h" |
| #include "core/clipboard/DataTransfer.h" |
| #include "core/dom/DOMNodeIds.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/dom/TouchList.h" |
| #include "core/dom/UserGestureIndicator.h" |
| #include "core/dom/shadow/FlatTreeTraversal.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/Editor.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/SelectionController.h" |
| #include "core/events/EventPath.h" |
| #include "core/events/GestureEvent.h" |
| #include "core/events/KeyboardEvent.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/events/PointerEvent.h" |
| #include "core/events/TextEvent.h" |
| #include "core/events/TouchEvent.h" |
| #include "core/events/WheelEvent.h" |
| #include "core/frame/Deprecation.h" |
| #include "core/frame/EventHandlerRegistry.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/frame/VisualViewport.h" |
| #include "core/html/HTMLDialogElement.h" |
| #include "core/html/HTMLFrameElementBase.h" |
| #include "core/html/HTMLFrameSetElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/input/EventHandlingUtil.h" |
| #include "core/input/InputDeviceCapabilities.h" |
| #include "core/input/TouchActionUtil.h" |
| #include "core/layout/HitTestRequest.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutPart.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/resource/ImageResourceContent.h" |
| #include "core/page/AutoscrollController.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/DragState.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "core/page/TouchAdjustment.h" |
| #include "core/page/scrolling/ScrollState.h" |
| #include "core/paint/PaintLayer.h" |
| #include "core/style/ComputedStyle.h" |
| #include "core/style/CursorData.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/WindowsKeyboardCodes.h" |
| #include "platform/geometry/FloatPoint.h" |
| #include "platform/graphics/Image.h" |
| #include "platform/heap/Handle.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/scroll/ScrollAnimatorBase.h" |
| #include "platform/scroll/Scrollbar.h" |
| #include "platform/wtf/Assertions.h" |
| #include "platform/wtf/CurrentTime.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "platform/wtf/StdLibExtras.h" |
| #include "public/platform/WebInputEvent.h" |
| #include "public/platform/WebMouseWheelEvent.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // Refetch the event target node if it is removed or currently is the shadow |
| // node inside an <input> element. If a mouse event handler changes the input |
| // element type to one that has a FrameViewBase associated, we'd like to |
| // EventHandler::handleMousePressEvent to pass the event to the FrameViewBase |
| // and thus the event target node can't still be the shadow node. |
| bool ShouldRefetchEventTarget(const MouseEventWithHitTestResults& mev) { |
| Node* target_node = mev.InnerNode(); |
| if (!target_node || !target_node->parentNode()) |
| return true; |
| return target_node->IsShadowRoot() && |
| isHTMLInputElement(ToShadowRoot(target_node)->host()); |
| } |
| |
| bool ShouldShowIBeamForNode(const Node* node, const HitTestResult& result) { |
| if (!node) |
| return false; |
| |
| bool layout_object_selectable = false; |
| if (LayoutObject* layout_object = node->GetLayoutObject()) { |
| PaintLayer* layer = layout_object->EnclosingLayer(); |
| if (layer->GetScrollableArea() && |
| layer->GetScrollableArea()->IsPointInResizeControl( |
| result.RoundedPointInMainFrame(), kResizerForPointer)) { |
| return false; |
| } |
| |
| layout_object_selectable = |
| layout_object->IsText() && node->CanStartSelection(); |
| } |
| |
| return HasEditableStyle(*node) || layout_object_selectable; |
| } |
| |
| } // namespace |
| |
| using namespace HTMLNames; |
| |
| // The amount of time to wait for a cursor update on style and layout changes |
| // Set to 50Hz, no need to be faster than common screen refresh rate |
| static const double kCursorUpdateInterval = 0.02; |
| |
| static const int kMaximumCursorSize = 128; |
| |
| // It's pretty unlikely that a scale of less than one would ever be used. But |
| // all we really need to ensure here is that the scale isn't so small that |
| // integer overflow can occur when dividing cursor sizes (limited above) by the |
| // scale. |
| static const double kMinimumCursorScale = 0.001; |
| |
| // The minimum amount of time an element stays active after a ShowPress |
| // This is roughly 9 frames, which should be long enough to be noticeable. |
| static const TimeDelta kMinimumActiveInterval = TimeDelta::FromSecondsD(0.15); |
| |
| enum NoCursorChangeType { kNoCursorChange }; |
| |
| class OptionalCursor { |
| public: |
| OptionalCursor(NoCursorChangeType) : is_cursor_change_(false) {} |
| OptionalCursor(const Cursor& cursor) |
| : is_cursor_change_(true), cursor_(cursor) {} |
| |
| bool IsCursorChange() const { return is_cursor_change_; } |
| const Cursor& GetCursor() const { |
| DCHECK(is_cursor_change_); |
| return cursor_; |
| } |
| |
| private: |
| bool is_cursor_change_; |
| Cursor cursor_; |
| }; |
| |
| EventHandler::EventHandler(LocalFrame& frame) |
| : frame_(frame), |
| selection_controller_(SelectionController::Create(frame)), |
| hover_timer_(TaskRunnerHelper::Get(TaskType::kUserInteraction, &frame), |
| this, |
| &EventHandler::HoverTimerFired), |
| cursor_update_timer_( |
| TaskRunnerHelper::Get(TaskType::kUnspecedTimer, &frame), |
| this, |
| &EventHandler::CursorUpdateTimerFired), |
| event_handler_will_reset_capturing_mouse_events_node_(0), |
| should_only_fire_drag_over_event_(false), |
| scroll_manager_(new ScrollManager(frame)), |
| mouse_event_manager_(new MouseEventManager(frame, *scroll_manager_)), |
| keyboard_event_manager_( |
| new KeyboardEventManager(frame, *scroll_manager_)), |
| pointer_event_manager_( |
| new PointerEventManager(frame, *mouse_event_manager_)), |
| gesture_manager_(new GestureManager(frame, |
| *scroll_manager_, |
| *mouse_event_manager_, |
| *pointer_event_manager_, |
| *selection_controller_)), |
| active_interval_timer_( |
| TaskRunnerHelper::Get(TaskType::kUserInteraction, &frame), |
| this, |
| &EventHandler::ActiveIntervalTimerFired) {} |
| |
| DEFINE_TRACE(EventHandler) { |
| visitor->Trace(frame_); |
| visitor->Trace(selection_controller_); |
| visitor->Trace(capturing_mouse_events_node_); |
| visitor->Trace(last_mouse_move_event_subframe_); |
| visitor->Trace(last_scrollbar_under_mouse_); |
| visitor->Trace(drag_target_); |
| visitor->Trace(frame_set_being_resized_); |
| visitor->Trace(scroll_manager_); |
| visitor->Trace(mouse_event_manager_); |
| visitor->Trace(keyboard_event_manager_); |
| visitor->Trace(pointer_event_manager_); |
| visitor->Trace(gesture_manager_); |
| visitor->Trace(last_deferred_tap_element_); |
| } |
| |
| void EventHandler::Clear() { |
| hover_timer_.Stop(); |
| cursor_update_timer_.Stop(); |
| active_interval_timer_.Stop(); |
| last_mouse_move_event_subframe_ = nullptr; |
| last_scrollbar_under_mouse_ = nullptr; |
| frame_set_being_resized_ = nullptr; |
| drag_target_ = nullptr; |
| should_only_fire_drag_over_event_ = false; |
| last_mouse_down_user_gesture_token_.Clear(); |
| capturing_mouse_events_node_ = nullptr; |
| pointer_event_manager_->Clear(); |
| scroll_manager_->Clear(); |
| gesture_manager_->Clear(); |
| mouse_event_manager_->Clear(); |
| last_deferred_tap_element_ = nullptr; |
| event_handler_will_reset_capturing_mouse_events_node_ = false; |
| } |
| |
| void EventHandler::UpdateSelectionForMouseDrag() { |
| mouse_event_manager_->UpdateSelectionForMouseDrag(); |
| } |
| |
| void EventHandler::StartMiddleClickAutoscroll(LayoutObject* layout_object) { |
| DCHECK(RuntimeEnabledFeatures::middleClickAutoscrollEnabled()); |
| if (!layout_object->IsBox()) |
| return; |
| AutoscrollController* controller = scroll_manager_->GetAutoscrollController(); |
| if (!controller) |
| return; |
| controller->StartMiddleClickAutoscroll( |
| ToLayoutBox(layout_object), |
| mouse_event_manager_->LastKnownMousePosition()); |
| mouse_event_manager_->InvalidateClick(); |
| } |
| |
| HitTestResult EventHandler::HitTestResultAtPoint( |
| const LayoutPoint& point, |
| HitTestRequest::HitTestRequestType hit_type, |
| const LayoutSize& padding) { |
| TRACE_EVENT0("blink", "EventHandler::hitTestResultAtPoint"); |
| |
| DCHECK((hit_type & HitTestRequest::kListBased) || padding.IsEmpty()); |
| |
| // We always send hitTestResultAtPoint to the main frame if we have one, |
| // otherwise we might hit areas that are obscured by higher frames. |
| if (frame_->GetPage()) { |
| LocalFrame& main_frame = frame_->LocalFrameRoot(); |
| if (frame_ != &main_frame) { |
| LocalFrameView* frame_view = frame_->View(); |
| LocalFrameView* main_view = main_frame.View(); |
| if (frame_view && main_view) { |
| IntPoint main_frame_point = main_view->RootFrameToContents( |
| frame_view->ContentsToRootFrame(RoundedIntPoint(point))); |
| return main_frame.GetEventHandler().HitTestResultAtPoint( |
| main_frame_point, hit_type, padding); |
| } |
| } |
| } |
| |
| // hitTestResultAtPoint is specifically used to hitTest into all frames, thus |
| // it always allows child frame content. |
| HitTestRequest request(hit_type | HitTestRequest::kAllowChildFrameContent); |
| HitTestResult result(request, point, padding.Height().ToUnsigned(), |
| padding.Width().ToUnsigned(), |
| padding.Height().ToUnsigned(), |
| padding.Width().ToUnsigned()); |
| |
| // LayoutView::hitTest causes a layout, and we don't want to hit that until |
| // the first layout because until then, there is nothing shown on the screen - |
| // the user can't have intentionally clicked on something belonging to this |
| // page. Furthermore, mousemove events before the first layout should not |
| // lead to a premature layout() happening, which could show a flash of white. |
| // See also the similar code in Document::performMouseEventHitTest. |
| if (frame_->ContentLayoutItem().IsNull() || !frame_->View() || |
| !frame_->View()->DidFirstLayout()) |
| return result; |
| |
| frame_->ContentLayoutItem().HitTest(result); |
| if (!request.ReadOnly()) |
| frame_->GetDocument()->UpdateHoverActiveState(request, |
| result.InnerElement()); |
| |
| return result; |
| } |
| |
| void EventHandler::StopAutoscroll() { |
| scroll_manager_->StopAutoscroll(); |
| } |
| |
| // TODO(bokan): This should be merged with logicalScroll assuming |
| // defaultSpaceEventHandler's chaining scroll can be done crossing frames. |
| bool EventHandler::BubblingScroll(ScrollDirection direction, |
| ScrollGranularity granularity, |
| Node* starting_node) { |
| return scroll_manager_->BubblingScroll( |
| direction, granularity, starting_node, |
| mouse_event_manager_->MousePressNode()); |
| } |
| |
| IntPoint EventHandler::LastKnownMousePosition() const { |
| return mouse_event_manager_->LastKnownMousePosition(); |
| } |
| |
| IntPoint EventHandler::DragDataTransferLocationForTesting() { |
| if (mouse_event_manager_->GetDragState().drag_data_transfer_) |
| return mouse_event_manager_->GetDragState() |
| .drag_data_transfer_->DragLocation(); |
| |
| return IntPoint(); |
| } |
| |
| static LocalFrame* SubframeForTargetNode(Node* node) { |
| if (!node) |
| return nullptr; |
| |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| if (!layout_object || !layout_object->IsLayoutPart()) |
| return nullptr; |
| |
| LocalFrameView* frame_view = ToLayoutPart(layout_object)->ChildFrameView(); |
| if (!frame_view) |
| return nullptr; |
| |
| return &frame_view->GetFrame(); |
| } |
| |
| static LocalFrame* SubframeForHitTestResult( |
| const MouseEventWithHitTestResults& hit_test_result) { |
| if (!hit_test_result.IsOverFrameViewBase()) |
| return nullptr; |
| return SubframeForTargetNode(hit_test_result.InnerNode()); |
| } |
| |
| static bool IsSubmitImage(Node* node) { |
| return isHTMLInputElement(node) && |
| toHTMLInputElement(node)->type() == InputTypeNames::image; |
| } |
| |
| bool EventHandler::UseHandCursor(Node* node, bool is_over_link) { |
| if (!node) |
| return false; |
| |
| return ((is_over_link || IsSubmitImage(node)) && !HasEditableStyle(*node)); |
| } |
| |
| void EventHandler::CursorUpdateTimerFired(TimerBase*) { |
| DCHECK(frame_); |
| DCHECK(frame_->GetDocument()); |
| |
| UpdateCursor(); |
| } |
| |
| void EventHandler::UpdateCursor() { |
| TRACE_EVENT0("input", "EventHandler::updateCursor"); |
| |
| // We must do a cross-frame hit test because the frame that triggered the |
| // cursor update could be occluded by a different frame. |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| |
| LocalFrameView* view = frame_->View(); |
| if (!view || !view->ShouldSetCursor()) |
| return; |
| |
| LayoutViewItem layout_view_item = view->GetLayoutViewItem(); |
| if (layout_view_item.IsNull()) |
| return; |
| |
| frame_->GetDocument()->UpdateStyleAndLayout(); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | |
| HitTestRequest::kAllowChildFrameContent); |
| HitTestResult result(request, |
| view->RootFrameToContents( |
| mouse_event_manager_->LastKnownMousePosition())); |
| layout_view_item.HitTest(result); |
| |
| if (LocalFrame* frame = result.InnerNodeFrame()) { |
| OptionalCursor optional_cursor = |
| frame->GetEventHandler().SelectCursor(result); |
| if (optional_cursor.IsCursorChange()) { |
| view->SetCursor(optional_cursor.GetCursor()); |
| } |
| } |
| } |
| |
| OptionalCursor EventHandler::SelectCursor(const HitTestResult& result) { |
| if (scroll_manager_->InResizeMode()) |
| return kNoCursorChange; |
| |
| Page* page = frame_->GetPage(); |
| if (!page) |
| return kNoCursorChange; |
| if (scroll_manager_->MiddleClickAutoscrollInProgress()) |
| return kNoCursorChange; |
| |
| Node* node = result.InnerPossiblyPseudoNode(); |
| if (!node) |
| return SelectAutoCursor(result, node, IBeamCursor()); |
| |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| const ComputedStyle* style = layout_object ? layout_object->Style() : nullptr; |
| |
| if (layout_object) { |
| Cursor override_cursor; |
| switch (layout_object->GetCursor(RoundedIntPoint(result.LocalPoint()), |
| override_cursor)) { |
| case kSetCursorBasedOnStyle: |
| break; |
| case kSetCursor: |
| return override_cursor; |
| case kDoNotSetCursor: |
| return kNoCursorChange; |
| } |
| } |
| |
| if (style && style->Cursors()) { |
| const CursorList* cursors = style->Cursors(); |
| for (unsigned i = 0; i < cursors->size(); ++i) { |
| StyleImage* style_image = (*cursors)[i].GetImage(); |
| if (!style_image) |
| continue; |
| ImageResourceContent* cached_image = style_image->CachedImage(); |
| if (!cached_image) |
| continue; |
| float scale = style_image->ImageScaleFactor(); |
| bool hot_spot_specified = (*cursors)[i].HotSpotSpecified(); |
| // Get hotspot and convert from logical pixels to physical pixels. |
| IntPoint hot_spot = (*cursors)[i].HotSpot(); |
| hot_spot.Scale(scale, scale); |
| IntSize size = cached_image->GetImage()->Size(); |
| if (cached_image->ErrorOccurred()) |
| continue; |
| // Limit the size of cursors (in UI pixels) so that they cannot be |
| // used to cover UI elements in chrome. |
| size.Scale(1 / scale); |
| if (size.Width() > kMaximumCursorSize || |
| size.Height() > kMaximumCursorSize) |
| continue; |
| |
| Image* image = cached_image->GetImage(); |
| // Ensure no overflow possible in calculations above. |
| if (scale < kMinimumCursorScale) |
| continue; |
| return Cursor(image, hot_spot_specified, hot_spot, scale); |
| } |
| } |
| |
| switch (style ? style->Cursor() : ECursor::kAuto) { |
| case ECursor::kAuto: { |
| bool horizontal_text = !style || style->IsHorizontalWritingMode(); |
| const Cursor& i_beam = |
| horizontal_text ? IBeamCursor() : VerticalTextCursor(); |
| return SelectAutoCursor(result, node, i_beam); |
| } |
| case ECursor::kCrosshair: |
| return CrossCursor(); |
| case ECursor::kPointer: |
| return HandCursor(); |
| case ECursor::kMove: |
| return MoveCursor(); |
| case ECursor::kAllScroll: |
| return MoveCursor(); |
| case ECursor::kEResize: |
| return EastResizeCursor(); |
| case ECursor::kWResize: |
| return WestResizeCursor(); |
| case ECursor::kNResize: |
| return NorthResizeCursor(); |
| case ECursor::kSResize: |
| return SouthResizeCursor(); |
| case ECursor::kNeResize: |
| return NorthEastResizeCursor(); |
| case ECursor::kSwResize: |
| return SouthWestResizeCursor(); |
| case ECursor::kNwResize: |
| return NorthWestResizeCursor(); |
| case ECursor::kSeResize: |
| return SouthEastResizeCursor(); |
| case ECursor::kNsResize: |
| return NorthSouthResizeCursor(); |
| case ECursor::kEwResize: |
| return EastWestResizeCursor(); |
| case ECursor::kNeswResize: |
| return NorthEastSouthWestResizeCursor(); |
| case ECursor::kNwseResize: |
| return NorthWestSouthEastResizeCursor(); |
| case ECursor::kColResize: |
| return ColumnResizeCursor(); |
| case ECursor::kRowResize: |
| return RowResizeCursor(); |
| case ECursor::kText: |
| return IBeamCursor(); |
| case ECursor::kWait: |
| return WaitCursor(); |
| case ECursor::kHelp: |
| return HelpCursor(); |
| case ECursor::kVerticalText: |
| return VerticalTextCursor(); |
| case ECursor::kCell: |
| return CellCursor(); |
| case ECursor::kContextMenu: |
| return ContextMenuCursor(); |
| case ECursor::kProgress: |
| return ProgressCursor(); |
| case ECursor::kNoDrop: |
| return NoDropCursor(); |
| case ECursor::kAlias: |
| return AliasCursor(); |
| case ECursor::kCopy: |
| return CopyCursor(); |
| case ECursor::kNone: |
| return NoneCursor(); |
| case ECursor::kNotAllowed: |
| return NotAllowedCursor(); |
| case ECursor::kDefault: |
| return PointerCursor(); |
| case ECursor::kZoomIn: |
| return ZoomInCursor(); |
| case ECursor::kZoomOut: |
| return ZoomOutCursor(); |
| case ECursor::kWebkitGrab: |
| return GrabCursor(); |
| case ECursor::kWebkitGrabbing: |
| return GrabbingCursor(); |
| } |
| return PointerCursor(); |
| } |
| |
| OptionalCursor EventHandler::SelectAutoCursor(const HitTestResult& result, |
| Node* node, |
| const Cursor& i_beam) { |
| if (result.GetScrollbar()) { |
| return PointerCursor(); |
| } |
| |
| const bool is_over_link = |
| !GetSelectionController().MouseDownMayStartSelect() && |
| result.IsOverLink(); |
| if (UseHandCursor(node, is_over_link)) |
| return HandCursor(); |
| |
| // During selection, use an I-beam no matter what we're over. |
| // If a drag may be starting or we're capturing mouse events for a particular |
| // node, don't treat this as a selection. Note calling |
| // ComputeVisibleSelectionInDOMTreeDeprecated may update layout. |
| if (mouse_event_manager_->MousePressed() && |
| GetSelectionController().MouseDownMayStartSelect() && |
| !mouse_event_manager_->MouseDownMayStartDrag() && |
| !frame_->Selection() |
| .ComputeVisibleSelectionInDOMTreeDeprecated() |
| .IsNone() && |
| !capturing_mouse_events_node_) { |
| return i_beam; |
| } |
| |
| if (ShouldShowIBeamForNode(node, result)) |
| return i_beam; |
| return PointerCursor(); |
| } |
| |
| WebInputEventResult EventHandler::HandleMousePressEvent( |
| const WebMouseEvent& mouse_event) { |
| TRACE_EVENT0("blink", "EventHandler::handleMousePressEvent"); |
| |
| // For 4th/5th button in the mouse since Chrome does not yet send |
| // button value to Blink but in some cases it does send the event. |
| // This check is needed to suppress such an event (crbug.com/574959) |
| if (mouse_event.button == WebPointerProperties::Button::kNoButton) |
| return WebInputEventResult::kHandledSuppressed; |
| |
| if (event_handler_will_reset_capturing_mouse_events_node_) |
| capturing_mouse_events_node_ = nullptr; |
| mouse_event_manager_->HandleMousePressEventUpdateStates(mouse_event); |
| if (!frame_->View()) |
| return WebInputEventResult::kNotHandled; |
| |
| HitTestRequest request(HitTestRequest::kActive); |
| // Save the document point we generate in case the window coordinate is |
| // invalidated by what happens when we dispatch the event. |
| LayoutPoint document_point = frame_->View()->RootFrameToContents( |
| FlooredIntPoint(mouse_event.PositionInRootFrame())); |
| MouseEventWithHitTestResults mev = |
| frame_->GetDocument()->PerformMouseEventHitTest(request, document_point, |
| mouse_event); |
| |
| if (!mev.InnerNode()) { |
| mouse_event_manager_->InvalidateClick(); |
| return WebInputEventResult::kNotHandled; |
| } |
| |
| mouse_event_manager_->SetMousePressNode(mev.InnerNode()); |
| frame_->GetDocument()->SetSequentialFocusNavigationStartingPoint( |
| mev.InnerNode()); |
| |
| LocalFrame* subframe = SubframeForHitTestResult(mev); |
| if (subframe) { |
| WebInputEventResult result = PassMousePressEventToSubframe(mev, subframe); |
| // Start capturing future events for this frame. We only do this if we |
| // didn't clear the m_mousePressed flag, which may happen if an AppKit |
| // FrameViewBase entered a modal event loop. The capturing should be done |
| // only when the result indicates it has been handled. See crbug.com/269917 |
| mouse_event_manager_->SetCapturesDragging( |
| subframe->GetEventHandler().mouse_event_manager_->CapturesDragging()); |
| if (mouse_event_manager_->MousePressed() && |
| mouse_event_manager_->CapturesDragging()) { |
| capturing_mouse_events_node_ = mev.InnerNode(); |
| event_handler_will_reset_capturing_mouse_events_node_ = true; |
| } |
| mouse_event_manager_->InvalidateClick(); |
| return result; |
| } |
| |
| UserGestureIndicator gesture_indicator( |
| UserGestureToken::Create(frame_->GetDocument())); |
| frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .last_mouse_down_user_gesture_token_ = |
| UserGestureIndicator::CurrentToken(); |
| |
| if (RuntimeEnabledFeatures::middleClickAutoscrollEnabled()) { |
| // We store whether middle click autoscroll is in progress before calling |
| // stopAutoscroll() because it will set m_autoscrollType to NoAutoscroll on |
| // return. |
| bool is_middle_click_autoscroll_in_progress = |
| scroll_manager_->MiddleClickAutoscrollInProgress(); |
| scroll_manager_->StopAutoscroll(); |
| if (is_middle_click_autoscroll_in_progress) { |
| // We invalidate the click when exiting middle click auto scroll so that |
| // we don't inadvertently navigate away from the current page (e.g. the |
| // click was on a hyperlink). See <rdar://problem/6095023>. |
| mouse_event_manager_->InvalidateClick(); |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| } |
| |
| mouse_event_manager_->SetClickCount(mouse_event.click_count); |
| mouse_event_manager_->SetClickElement(mev.InnerElement()); |
| |
| if (!mouse_event.FromTouch()) |
| frame_->Selection().SetCaretBlinkingSuspended(true); |
| |
| WebInputEventResult event_result = UpdatePointerTargetAndDispatchEvents( |
| EventTypeNames::mousedown, mev.InnerNode(), mev.CanvasRegionId(), |
| mev.Event(), Vector<WebMouseEvent>()); |
| |
| if (event_result == WebInputEventResult::kNotHandled && frame_->View()) { |
| LocalFrameView* view = frame_->View(); |
| PaintLayer* layer = |
| mev.InnerNode()->GetLayoutObject() |
| ? mev.InnerNode()->GetLayoutObject()->EnclosingLayer() |
| : nullptr; |
| IntPoint p = view->RootFrameToContents( |
| FlooredIntPoint(mouse_event.PositionInRootFrame())); |
| if (layer && layer->GetScrollableArea() && |
| layer->GetScrollableArea()->IsPointInResizeControl( |
| p, kResizerForPointer)) { |
| scroll_manager_->SetResizeScrollableArea(layer, p); |
| return WebInputEventResult::kHandledSystem; |
| } |
| } |
| |
| // m_selectionInitiationState is initialized after dispatching mousedown |
| // event in order not to keep the selection by DOM APIs because we can't |
| // give the user the chance to handle the selection by user action like |
| // dragging if we keep the selection in case of mousedown. FireFox also has |
| // the same behavior and it's more compatible with other browsers. |
| GetSelectionController().InitializeSelectionState(); |
| HitTestResult hit_test_result = EventHandlingUtil::HitTestResultInFrame( |
| frame_, document_point, HitTestRequest::kReadOnly); |
| InputDeviceCapabilities* source_capabilities = |
| frame_->GetDocument() |
| ->domWindow() |
| ->GetInputDeviceCapabilities() |
| ->FiresTouchEvents(mouse_event.FromTouch()); |
| |
| if (event_result == WebInputEventResult::kNotHandled) { |
| event_result = mouse_event_manager_->HandleMouseFocus(hit_test_result, |
| source_capabilities); |
| } |
| mouse_event_manager_->SetCapturesDragging( |
| event_result == WebInputEventResult::kNotHandled || mev.GetScrollbar()); |
| |
| // If the hit testing originally determined the event was in a scrollbar, |
| // refetch the MouseEventWithHitTestResults in case the scrollbar |
| // FrameViewBase was destroyed when the mouse event was handled. |
| if (mev.GetScrollbar()) { |
| const bool was_last_scroll_bar = |
| mev.GetScrollbar() == last_scrollbar_under_mouse_.Get(); |
| HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive); |
| mev = frame_->GetDocument()->PerformMouseEventHitTest( |
| request, document_point, mouse_event); |
| if (was_last_scroll_bar && |
| mev.GetScrollbar() != last_scrollbar_under_mouse_.Get()) |
| last_scrollbar_under_mouse_ = nullptr; |
| } |
| |
| if (event_result != WebInputEventResult::kNotHandled) { |
| // Scrollbars should get events anyway, even disabled controls might be |
| // scrollable. |
| PassMousePressEventToScrollbar(mev); |
| } else { |
| if (ShouldRefetchEventTarget(mev)) { |
| HitTestRequest request(HitTestRequest::kReadOnly | |
| HitTestRequest::kActive); |
| mev = frame_->GetDocument()->PerformMouseEventHitTest( |
| request, document_point, mouse_event); |
| } |
| |
| if (PassMousePressEventToScrollbar(mev)) |
| event_result = WebInputEventResult::kHandledSystem; |
| else |
| event_result = mouse_event_manager_->HandleMousePressEvent(mev); |
| } |
| |
| if (mev.GetHitTestResult().InnerNode() && |
| mouse_event.button == WebPointerProperties::Button::kLeft) { |
| DCHECK_EQ(WebInputEvent::kMouseDown, mouse_event.GetType()); |
| HitTestResult result = mev.GetHitTestResult(); |
| result.SetToShadowHostIfInRestrictedShadowRoot(); |
| frame_->GetChromeClient().OnMouseDown(*result.InnerNode()); |
| } |
| |
| return event_result; |
| } |
| |
| WebInputEventResult EventHandler::HandleMouseMoveEvent( |
| const WebMouseEvent& event, |
| const Vector<WebMouseEvent>& coalesced_events) { |
| TRACE_EVENT0("blink", "EventHandler::handleMouseMoveEvent"); |
| |
| HitTestResult hovered_node = HitTestResult(); |
| WebInputEventResult result = |
| HandleMouseMoveOrLeaveEvent(event, coalesced_events, &hovered_node); |
| |
| Page* page = frame_->GetPage(); |
| if (!page) |
| return result; |
| |
| if (PaintLayer* layer = |
| EventHandlingUtil::LayerForNode(hovered_node.InnerNode())) { |
| if (ScrollableArea* layer_scrollable_area = |
| EventHandlingUtil::AssociatedScrollableArea(layer)) |
| layer_scrollable_area->MouseMovedInContentArea(); |
| } |
| |
| if (LocalFrameView* frame_view = frame_->View()) |
| frame_view->MouseMovedInContentArea(); |
| |
| hovered_node.SetToShadowHostIfInRestrictedShadowRoot(); |
| page->GetChromeClient().MouseDidMoveOverElement(*frame_, hovered_node); |
| |
| return result; |
| } |
| |
| void EventHandler::HandleMouseLeaveEvent(const WebMouseEvent& event) { |
| TRACE_EVENT0("blink", "EventHandler::handleMouseLeaveEvent"); |
| |
| Page* page = frame_->GetPage(); |
| if (page) |
| page->GetChromeClient().ClearToolTip(*frame_); |
| HandleMouseMoveOrLeaveEvent(event, Vector<WebMouseEvent>(), 0, false, true); |
| } |
| |
| WebInputEventResult EventHandler::HandleMouseMoveOrLeaveEvent( |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events, |
| HitTestResult* hovered_node, |
| bool only_update_scrollbars, |
| bool force_leave) { |
| DCHECK(frame_); |
| DCHECK(frame_->View()); |
| |
| mouse_event_manager_->SetLastKnownMousePosition(mouse_event); |
| |
| hover_timer_.Stop(); |
| cursor_update_timer_.Stop(); |
| |
| mouse_event_manager_->CancelFakeMouseMoveEvent(); |
| mouse_event_manager_->HandleSvgPanIfNeeded(false); |
| |
| if (frame_set_being_resized_) { |
| return UpdatePointerTargetAndDispatchEvents( |
| EventTypeNames::mousemove, frame_set_being_resized_.Get(), String(), |
| mouse_event, coalesced_events); |
| } |
| |
| // Send events right to a scrollbar if the mouse is pressed. |
| if (last_scrollbar_under_mouse_ && mouse_event_manager_->MousePressed()) { |
| last_scrollbar_under_mouse_->MouseMoved(mouse_event); |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| // Mouse events simulated from touch should not hit-test again. |
| DCHECK(!mouse_event.FromTouch()); |
| |
| HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kMove; |
| if (mouse_event_manager_->MousePressed()) { |
| hit_type |= HitTestRequest::kActive; |
| } else if (only_update_scrollbars) { |
| // Mouse events should be treated as "read-only" if we're updating only |
| // scrollbars. This means that :hover and :active freeze in the state they |
| // were in, rather than updating for nodes the mouse moves while the window |
| // is not key (which will be the case if onlyUpdateScrollbars is true). |
| hit_type |= HitTestRequest::kReadOnly; |
| } |
| |
| // Treat any mouse move events as readonly if the user is currently touching |
| // the screen. |
| if (pointer_event_manager_->IsAnyTouchActive() && !force_leave) |
| hit_type |= HitTestRequest::kActive | HitTestRequest::kReadOnly; |
| HitTestRequest request(hit_type); |
| MouseEventWithHitTestResults mev = MouseEventWithHitTestResults( |
| mouse_event, HitTestResult(request, LayoutPoint())); |
| |
| // We don't want to do a hit-test in forceLeave scenarios because there might |
| // actually be some other frame above this one at the specified co-ordinate. |
| // So we must force the hit-test to fail, while still clearing hover/active |
| // state. |
| if (force_leave) { |
| frame_->GetDocument()->UpdateHoverActiveState(request, nullptr); |
| } else { |
| mev = EventHandlingUtil::PerformMouseEventHitTest(frame_, request, |
| mouse_event); |
| } |
| |
| if (hovered_node) |
| *hovered_node = mev.GetHitTestResult(); |
| |
| Scrollbar* scrollbar = nullptr; |
| |
| if (scroll_manager_->InResizeMode()) { |
| scroll_manager_->Resize(mev.Event()); |
| } else { |
| if (!scrollbar) |
| scrollbar = mev.GetScrollbar(); |
| |
| UpdateLastScrollbarUnderMouse(scrollbar, |
| !mouse_event_manager_->MousePressed()); |
| if (only_update_scrollbars) |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| |
| WebInputEventResult event_result = WebInputEventResult::kNotHandled; |
| LocalFrame* new_subframe = |
| capturing_mouse_events_node_.Get() |
| ? SubframeForTargetNode(capturing_mouse_events_node_.Get()) |
| : SubframeForHitTestResult(mev); |
| |
| // We want mouseouts to happen first, from the inside out. First send a move |
| // event to the last subframe so that it will fire mouseouts. |
| if (last_mouse_move_event_subframe_ && |
| last_mouse_move_event_subframe_->Tree().IsDescendantOf(frame_) && |
| last_mouse_move_event_subframe_ != new_subframe) |
| last_mouse_move_event_subframe_->GetEventHandler().HandleMouseLeaveEvent( |
| mev.Event()); |
| |
| if (new_subframe) { |
| // Update over/out state before passing the event to the subframe. |
| pointer_event_manager_->SendMouseAndPointerBoundaryEvents( |
| UpdateMouseEventTargetNode(mev.InnerNode()), mev.CanvasRegionId(), |
| mev.Event()); |
| |
| // Event dispatch in sendMouseAndPointerBoundaryEvents may have caused the |
| // subframe of the target node to be detached from its LocalFrameView, in |
| // which case the event should not be passed. |
| if (new_subframe->View()) { |
| event_result = PassMouseMoveEventToSubframe(mev, coalesced_events, |
| new_subframe, hovered_node); |
| } |
| } else { |
| if (scrollbar && !mouse_event_manager_->MousePressed()) { |
| // Handle hover effects on platforms that support visual feedback on |
| // scrollbar hovering. |
| scrollbar->MouseMoved(mev.Event()); |
| } |
| if (LocalFrameView* view = frame_->View()) { |
| OptionalCursor optional_cursor = SelectCursor(mev.GetHitTestResult()); |
| if (optional_cursor.IsCursorChange()) { |
| view->SetCursor(optional_cursor.GetCursor()); |
| } |
| } |
| } |
| |
| last_mouse_move_event_subframe_ = new_subframe; |
| |
| if (event_result != WebInputEventResult::kNotHandled) |
| return event_result; |
| |
| event_result = UpdatePointerTargetAndDispatchEvents( |
| EventTypeNames::mousemove, mev.InnerNode(), mev.CanvasRegionId(), |
| mev.Event(), coalesced_events); |
| if (event_result != WebInputEventResult::kNotHandled) |
| return event_result; |
| |
| return mouse_event_manager_->HandleMouseDraggedEvent(mev); |
| } |
| |
| WebInputEventResult EventHandler::HandleMouseReleaseEvent( |
| const WebMouseEvent& mouse_event) { |
| TRACE_EVENT0("blink", "EventHandler::handleMouseReleaseEvent"); |
| |
| // For 4th/5th button in the mouse since Chrome does not yet send |
| // button value to Blink but in some cases it does send the event. |
| // This check is needed to suppress such an event (crbug.com/574959) |
| if (mouse_event.button == WebPointerProperties::Button::kNoButton) |
| return WebInputEventResult::kHandledSuppressed; |
| |
| if (!mouse_event.FromTouch()) |
| frame_->Selection().SetCaretBlinkingSuspended(false); |
| |
| if (RuntimeEnabledFeatures::middleClickAutoscrollEnabled()) { |
| if (Page* page = frame_->GetPage()) |
| page->GetAutoscrollController() |
| .HandleMouseReleaseForMiddleClickAutoscroll(frame_, mouse_event); |
| } |
| |
| mouse_event_manager_->SetMousePressed(false); |
| mouse_event_manager_->SetLastKnownMousePosition(mouse_event); |
| mouse_event_manager_->HandleSvgPanIfNeeded(true); |
| |
| if (frame_set_being_resized_) { |
| return mouse_event_manager_->SetMousePositionAndDispatchMouseEvent( |
| UpdateMouseEventTargetNode(frame_set_being_resized_.Get()), String(), |
| EventTypeNames::mouseup, mouse_event); |
| } |
| |
| if (last_scrollbar_under_mouse_) { |
| mouse_event_manager_->InvalidateClick(); |
| last_scrollbar_under_mouse_->MouseUp(mouse_event); |
| return UpdatePointerTargetAndDispatchEvents( |
| EventTypeNames::mouseup, mouse_event_manager_->GetNodeUnderMouse(), |
| String(), mouse_event, Vector<WebMouseEvent>()); |
| } |
| |
| // Mouse events simulated from touch should not hit-test again. |
| DCHECK(!mouse_event.FromTouch()); |
| |
| HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kRelease; |
| HitTestRequest request(hit_type); |
| MouseEventWithHitTestResults mev = |
| EventHandlingUtil::PerformMouseEventHitTest(frame_, request, mouse_event); |
| Element* mouse_release_target = mev.InnerElement(); |
| LocalFrame* subframe = |
| capturing_mouse_events_node_.Get() |
| ? SubframeForTargetNode(capturing_mouse_events_node_.Get()) |
| : SubframeForHitTestResult(mev); |
| if (event_handler_will_reset_capturing_mouse_events_node_) |
| capturing_mouse_events_node_ = nullptr; |
| if (subframe) |
| return PassMouseReleaseEventToSubframe(mev, subframe); |
| |
| // Mouse events will be associated with the Document where mousedown |
| // occurred. If, e.g., there is a mousedown, then a drag to a different |
| // Document and mouseup there, the mouseup's gesture will be associated with |
| // the mousedown's Document. It's not absolutely certain that this is the |
| // correct behavior. |
| std::unique_ptr<UserGestureIndicator> gesture_indicator; |
| if (frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .last_mouse_down_user_gesture_token_) { |
| gesture_indicator = WTF::WrapUnique(new UserGestureIndicator( |
| std::move(frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .last_mouse_down_user_gesture_token_))); |
| } else { |
| gesture_indicator = WTF::WrapUnique(new UserGestureIndicator( |
| UserGestureToken::Create(frame_->GetDocument()))); |
| } |
| |
| WebInputEventResult event_result = UpdatePointerTargetAndDispatchEvents( |
| EventTypeNames::mouseup, mev.InnerNode(), mev.CanvasRegionId(), |
| mev.Event(), Vector<WebMouseEvent>()); |
| |
| WebInputEventResult click_event_result = |
| mouse_release_target ? mouse_event_manager_->DispatchMouseClickIfNeeded( |
| mev, *mouse_release_target) |
| : WebInputEventResult::kNotHandled; |
| |
| scroll_manager_->ClearResizeScrollableArea(false); |
| |
| if (event_result == WebInputEventResult::kNotHandled) |
| event_result = mouse_event_manager_->HandleMouseReleaseEvent(mev); |
| |
| mouse_event_manager_->HandleMouseReleaseEventUpdateStates(); |
| |
| return EventHandlingUtil::MergeEventResult(click_event_result, event_result); |
| } |
| |
| static bool TargetIsFrame(Node* target, LocalFrame*& frame) { |
| if (!IsHTMLFrameElementBase(target)) |
| return false; |
| |
| // Cross-process drag and drop is not yet supported. |
| if (ToHTMLFrameElementBase(target)->ContentFrame() && |
| !ToHTMLFrameElementBase(target)->ContentFrame()->IsLocalFrame()) |
| return false; |
| |
| frame = ToLocalFrame(ToHTMLFrameElementBase(target)->ContentFrame()); |
| return true; |
| } |
| |
| WebInputEventResult EventHandler::UpdateDragAndDrop( |
| const WebMouseEvent& event, |
| DataTransfer* data_transfer) { |
| WebInputEventResult event_result = WebInputEventResult::kNotHandled; |
| |
| if (!frame_->View()) |
| return event_result; |
| |
| HitTestRequest request(HitTestRequest::kReadOnly); |
| MouseEventWithHitTestResults mev = |
| EventHandlingUtil::PerformMouseEventHitTest(frame_, request, event); |
| |
| // Drag events should never go to text nodes (following IE, and proper |
| // mouseover/out dispatch) |
| Node* new_target = mev.InnerNode(); |
| if (new_target && new_target->IsTextNode()) |
| new_target = FlatTreeTraversal::Parent(*new_target); |
| |
| if (AutoscrollController* controller = |
| scroll_manager_->GetAutoscrollController()) { |
| controller->UpdateDragAndDrop( |
| new_target, FlooredIntPoint(event.PositionInRootFrame()), |
| TimeTicks::FromSeconds(event.TimeStampSeconds())); |
| } |
| |
| if (drag_target_ != new_target) { |
| // FIXME: this ordering was explicitly chosen to match WinIE. However, |
| // it is sometimes incorrect when dragging within subframes, as seen with |
| // LayoutTests/fast/events/drag-in-frames.html. |
| // |
| // Moreover, this ordering conforms to section 7.9.4 of the HTML 5 spec. |
| // <http://dev.w3.org/html5/spec/Overview.html#drag-and-drop-processing-model>. |
| LocalFrame* target_frame; |
| if (TargetIsFrame(new_target, target_frame)) { |
| if (target_frame) |
| event_result = target_frame->GetEventHandler().UpdateDragAndDrop( |
| event, data_transfer); |
| } else if (new_target) { |
| // As per section 7.9.4 of the HTML 5 spec., we must always fire a drag |
| // event before firing a dragenter, dragleave, or dragover event. |
| if (mouse_event_manager_->GetDragState().drag_src_) { |
| // For now we don't care if event handler cancels default behavior, |
| // since there is none. |
| mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event); |
| } |
| event_result = mouse_event_manager_->DispatchDragEvent( |
| EventTypeNames::dragenter, new_target, drag_target_, event, |
| data_transfer); |
| } |
| |
| if (TargetIsFrame(drag_target_.Get(), target_frame)) { |
| if (target_frame) |
| event_result = target_frame->GetEventHandler().UpdateDragAndDrop( |
| event, data_transfer); |
| } else if (drag_target_) { |
| mouse_event_manager_->DispatchDragEvent(EventTypeNames::dragleave, |
| drag_target_.Get(), new_target, |
| event, data_transfer); |
| } |
| |
| if (new_target) { |
| // We do not explicitly call m_mouseEventManager->dispatchDragEvent here |
| // because it could ultimately result in the appearance that two dragover |
| // events fired. So, we mark that we should only fire a dragover event on |
| // the next call to this function. |
| should_only_fire_drag_over_event_ = true; |
| } |
| } else { |
| LocalFrame* target_frame; |
| if (TargetIsFrame(new_target, target_frame)) { |
| if (target_frame) |
| event_result = target_frame->GetEventHandler().UpdateDragAndDrop( |
| event, data_transfer); |
| } else if (new_target) { |
| // Note, when dealing with sub-frames, we may need to fire only a dragover |
| // event as a drag event may have been fired earlier. |
| if (!should_only_fire_drag_over_event_ && |
| mouse_event_manager_->GetDragState().drag_src_) { |
| // For now we don't care if event handler cancels default behavior, |
| // since there is none. |
| mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event); |
| } |
| event_result = mouse_event_manager_->DispatchDragEvent( |
| EventTypeNames::dragover, new_target, nullptr, event, data_transfer); |
| should_only_fire_drag_over_event_ = false; |
| } |
| } |
| drag_target_ = new_target; |
| |
| return event_result; |
| } |
| |
| void EventHandler::CancelDragAndDrop(const WebMouseEvent& event, |
| DataTransfer* data_transfer) { |
| LocalFrame* target_frame; |
| if (TargetIsFrame(drag_target_.Get(), target_frame)) { |
| if (target_frame) |
| target_frame->GetEventHandler().CancelDragAndDrop(event, data_transfer); |
| } else if (drag_target_.Get()) { |
| if (mouse_event_manager_->GetDragState().drag_src_) |
| mouse_event_manager_->DispatchDragSrcEvent(EventTypeNames::drag, event); |
| mouse_event_manager_->DispatchDragEvent(EventTypeNames::dragleave, |
| drag_target_.Get(), nullptr, event, |
| data_transfer); |
| } |
| ClearDragState(); |
| } |
| |
| WebInputEventResult EventHandler::PerformDragAndDrop( |
| const WebMouseEvent& event, |
| DataTransfer* data_transfer) { |
| LocalFrame* target_frame; |
| WebInputEventResult result = WebInputEventResult::kNotHandled; |
| if (TargetIsFrame(drag_target_.Get(), target_frame)) { |
| if (target_frame) |
| result = target_frame->GetEventHandler().PerformDragAndDrop( |
| event, data_transfer); |
| } else if (drag_target_.Get()) { |
| result = mouse_event_manager_->DispatchDragEvent( |
| EventTypeNames::drop, drag_target_.Get(), nullptr, event, |
| data_transfer); |
| } |
| ClearDragState(); |
| return result; |
| } |
| |
| void EventHandler::ClearDragState() { |
| scroll_manager_->StopAutoscroll(); |
| drag_target_ = nullptr; |
| capturing_mouse_events_node_ = nullptr; |
| should_only_fire_drag_over_event_ = false; |
| } |
| |
| void EventHandler::SetCapturingMouseEventsNode(Node* n) { |
| capturing_mouse_events_node_ = n; |
| event_handler_will_reset_capturing_mouse_events_node_ = false; |
| } |
| |
| Node* EventHandler::UpdateMouseEventTargetNode(Node* target_node) { |
| Node* new_node_under_mouse = target_node; |
| |
| if (capturing_mouse_events_node_) { |
| new_node_under_mouse = capturing_mouse_events_node_.Get(); |
| } else { |
| // If the target node is a text node, dispatch on the parent node - |
| // rdar://4196646 |
| if (new_node_under_mouse && new_node_under_mouse->IsTextNode()) |
| new_node_under_mouse = FlatTreeTraversal::Parent(*new_node_under_mouse); |
| } |
| return new_node_under_mouse; |
| } |
| |
| bool EventHandler::IsTouchPointerIdActiveOnFrame(int pointer_id, |
| LocalFrame* frame) const { |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| return pointer_event_manager_->IsTouchPointerIdActiveOnFrame(pointer_id, |
| frame); |
| } |
| |
| bool EventHandler::RootFrameTouchPointerActiveInCurrentFrame( |
| int pointer_id) const { |
| return frame_ != &frame_->LocalFrameRoot() && |
| frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .IsTouchPointerIdActiveOnFrame(pointer_id, frame_); |
| } |
| |
| bool EventHandler::IsPointerEventActive(int pointer_id) { |
| return pointer_event_manager_->IsActive(pointer_id) || |
| RootFrameTouchPointerActiveInCurrentFrame(pointer_id); |
| } |
| |
| void EventHandler::SetPointerCapture(int pointer_id, EventTarget* target) { |
| // TODO(crbug.com/591387): This functionality should be per page not per |
| // frame. |
| if (RootFrameTouchPointerActiveInCurrentFrame(pointer_id)) { |
| frame_->LocalFrameRoot().GetEventHandler().SetPointerCapture(pointer_id, |
| target); |
| } else { |
| pointer_event_manager_->SetPointerCapture(pointer_id, target); |
| } |
| } |
| |
| void EventHandler::ReleasePointerCapture(int pointer_id, EventTarget* target) { |
| if (RootFrameTouchPointerActiveInCurrentFrame(pointer_id)) { |
| frame_->LocalFrameRoot().GetEventHandler().ReleasePointerCapture(pointer_id, |
| target); |
| } else { |
| pointer_event_manager_->ReleasePointerCapture(pointer_id, target); |
| } |
| } |
| |
| void EventHandler::ReleaseMousePointerCapture() { |
| pointer_event_manager_->ReleaseMousePointerCapture(); |
| } |
| |
| bool EventHandler::HasPointerCapture(int pointer_id, |
| const EventTarget* target) const { |
| if (RootFrameTouchPointerActiveInCurrentFrame(pointer_id)) { |
| return frame_->LocalFrameRoot().GetEventHandler().HasPointerCapture( |
| pointer_id, target); |
| } else { |
| return pointer_event_manager_->HasPointerCapture(pointer_id, target); |
| } |
| } |
| |
| bool EventHandler::HasProcessedPointerCapture(int pointer_id, |
| const EventTarget* target) const { |
| return pointer_event_manager_->HasProcessedPointerCapture(pointer_id, target); |
| } |
| |
| void EventHandler::ProcessPendingPointerCaptureForPointerLock( |
| const WebMouseEvent& mouse_event) { |
| pointer_event_manager_->ProcessPendingPointerCaptureForPointerLock( |
| mouse_event); |
| } |
| |
| void EventHandler::ElementRemoved(EventTarget* target) { |
| pointer_event_manager_->ElementRemoved(target); |
| } |
| |
| WebInputEventResult EventHandler::UpdatePointerTargetAndDispatchEvents( |
| const AtomicString& mouse_event_type, |
| Node* target_node, |
| const String& canvas_region_id, |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events) { |
| DCHECK(mouse_event_type == EventTypeNames::mousedown || |
| mouse_event_type == EventTypeNames::mousemove || |
| mouse_event_type == EventTypeNames::mouseup); |
| |
| const auto& event_result = pointer_event_manager_->SendMousePointerEvent( |
| UpdateMouseEventTargetNode(target_node), canvas_region_id, |
| mouse_event_type, mouse_event, coalesced_events); |
| return event_result; |
| } |
| |
| WebInputEventResult EventHandler::HandleWheelEvent( |
| const WebMouseWheelEvent& event) { |
| #if OS(MACOSX) |
| // Filter Mac OS specific phases, usually with a zero-delta. |
| // https://crbug.com/553732 |
| // TODO(chongz): EventSender sends events with |
| // |WebMouseWheelEvent::PhaseNone|, |
| // but it shouldn't. |
| const int kWheelEventPhaseNoEventMask = WebMouseWheelEvent::kPhaseEnded | |
| WebMouseWheelEvent::kPhaseCancelled | |
| WebMouseWheelEvent::kPhaseMayBegin; |
| if ((event.phase & kWheelEventPhaseNoEventMask) || |
| (event.momentum_phase & kWheelEventPhaseNoEventMask)) |
| return WebInputEventResult::kNotHandled; |
| #endif |
| Document* doc = frame_->GetDocument(); |
| |
| if (doc->GetLayoutViewItem().IsNull()) |
| return WebInputEventResult::kNotHandled; |
| |
| LocalFrameView* view = frame_->View(); |
| if (!view) |
| return WebInputEventResult::kNotHandled; |
| |
| LayoutPoint v_point = |
| view->RootFrameToContents(FlooredIntPoint(event.PositionInRootFrame())); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly); |
| HitTestResult result(request, v_point); |
| doc->GetLayoutViewItem().HitTest(result); |
| |
| Node* node = result.InnerNode(); |
| // Wheel events should not dispatch to text nodes. |
| if (node && node->IsTextNode()) |
| node = FlatTreeTraversal::Parent(*node); |
| |
| // If we're over the frame scrollbar, scroll the document. |
| if (!node && result.GetScrollbar()) |
| node = doc->documentElement(); |
| |
| LocalFrame* subframe = SubframeForTargetNode(node); |
| if (subframe) { |
| WebInputEventResult result = |
| subframe->GetEventHandler().HandleWheelEvent(event); |
| if (result != WebInputEventResult::kNotHandled) |
| scroll_manager_->SetFrameWasScrolledByUser(); |
| return result; |
| } |
| |
| if (node) { |
| WheelEvent* dom_event = |
| WheelEvent::Create(event, node->GetDocument().domWindow()); |
| DispatchEventResult dom_event_result = node->DispatchEvent(dom_event); |
| if (dom_event_result != DispatchEventResult::kNotCanceled) |
| return EventHandlingUtil::ToWebInputEventResult(dom_event_result); |
| } |
| |
| return WebInputEventResult::kNotHandled; |
| } |
| |
| WebInputEventResult EventHandler::HandleGestureEvent( |
| const WebGestureEvent& gesture_event) { |
| // Propagation to inner frames is handled below this function. |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| DCHECK_NE(0, gesture_event.FrameScale()); |
| |
| // Scrolling-related gesture events invoke EventHandler recursively for each |
| // frame down the chain, doing a single-frame hit-test per frame. This matches |
| // handleWheelEvent. |
| // FIXME: Add a test that traverses this path, e.g. for devtools overlay. |
| if (gesture_event.IsScrollEvent()) |
| return HandleGestureScrollEvent(gesture_event); |
| |
| // Hit test across all frames and do touch adjustment as necessary for the |
| // event type. |
| GestureEventWithHitTestResults targeted_event = |
| TargetGestureEvent(gesture_event); |
| |
| return HandleGestureEvent(targeted_event); |
| } |
| |
| WebInputEventResult EventHandler::HandleGestureEvent( |
| const GestureEventWithHitTestResults& targeted_event) { |
| TRACE_EVENT0("input", "EventHandler::handleGestureEvent"); |
| if (!frame_->GetPage()) |
| return WebInputEventResult::kNotHandled; |
| |
| // Propagation to inner frames is handled below this function. |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| |
| // Non-scrolling related gesture events do a single cross-frame hit-test and |
| // jump directly to the inner most frame. This matches handleMousePressEvent |
| // etc. |
| DCHECK(!targeted_event.Event().IsScrollEvent()); |
| |
| // Update mouseout/leave/over/enter events before jumping directly to the |
| // inner most frame. |
| if (targeted_event.Event().GetType() == WebInputEvent::kGestureTap) |
| UpdateGestureTargetNodeForMouseEvent(targeted_event); |
| |
| // Route to the correct frame. |
| if (LocalFrame* inner_frame = |
| targeted_event.GetHitTestResult().InnerNodeFrame()) |
| return inner_frame->GetEventHandler().HandleGestureEventInFrame( |
| targeted_event); |
| |
| // No hit test result, handle in root instance. Perhaps we should just return |
| // false instead? |
| return gesture_manager_->HandleGestureEventInFrame(targeted_event); |
| } |
| |
| WebInputEventResult EventHandler::HandleGestureEventInFrame( |
| const GestureEventWithHitTestResults& targeted_event) { |
| return gesture_manager_->HandleGestureEventInFrame(targeted_event); |
| } |
| |
| WebInputEventResult EventHandler::HandleGestureScrollEvent( |
| const WebGestureEvent& gesture_event) { |
| TRACE_EVENT0("input", "EventHandler::handleGestureScrollEvent"); |
| if (!frame_->GetPage()) |
| return WebInputEventResult::kNotHandled; |
| |
| return scroll_manager_->HandleGestureScrollEvent(gesture_event); |
| } |
| |
| WebInputEventResult EventHandler::HandleGestureScrollEnd( |
| const WebGestureEvent& gesture_event) { |
| if (!frame_->GetPage()) |
| return WebInputEventResult::kNotHandled; |
| return scroll_manager_->HandleGestureScrollEnd(gesture_event); |
| } |
| |
| void EventHandler::SetMouseDownMayStartAutoscroll() { |
| mouse_event_manager_->SetMouseDownMayStartAutoscroll(); |
| } |
| |
| bool EventHandler::IsScrollbarHandlingGestures() const { |
| return scroll_manager_->IsScrollbarHandlingGestures(); |
| } |
| |
| bool EventHandler::ShouldApplyTouchAdjustment( |
| const WebGestureEvent& event) const { |
| if (frame_->GetSettings() && |
| !frame_->GetSettings()->GetTouchAdjustmentEnabled()) |
| return false; |
| return !event.TapAreaInRootFrame().IsEmpty(); |
| } |
| |
| bool EventHandler::BestClickableNodeForHitTestResult( |
| const HitTestResult& result, |
| IntPoint& target_point, |
| Node*& target_node) { |
| // FIXME: Unify this with the other best* functions which are very similar. |
| |
| TRACE_EVENT0("input", "EventHandler::bestClickableNodeForHitTestResult"); |
| DCHECK(result.IsRectBasedTest()); |
| |
| // If the touch is over a scrollbar, don't adjust the touch point since touch |
| // adjustment only takes into account DOM nodes so a touch over a scrollbar |
| // will be adjusted towards nearby nodes. This leads to things like textarea |
| // scrollbars being untouchable. |
| if (result.GetScrollbar()) { |
| target_node = 0; |
| return false; |
| } |
| |
| IntPoint touch_center = |
| frame_->View()->ContentsToRootFrame(result.RoundedPointInMainFrame()); |
| IntRect touch_rect = frame_->View()->ContentsToRootFrame( |
| result.GetHitTestLocation().BoundingBox()); |
| |
| HeapVector<Member<Node>, 11> nodes; |
| CopyToVector(result.ListBasedTestResult(), nodes); |
| |
| // FIXME: the explicit Vector conversion copies into a temporary and is |
| // wasteful. |
| return FindBestClickableCandidate(target_node, target_point, touch_center, |
| touch_rect, |
| HeapVector<Member<Node>>(nodes)); |
| } |
| |
| bool EventHandler::BestContextMenuNodeForHitTestResult( |
| const HitTestResult& result, |
| IntPoint& target_point, |
| Node*& target_node) { |
| DCHECK(result.IsRectBasedTest()); |
| IntPoint touch_center = |
| frame_->View()->ContentsToRootFrame(result.RoundedPointInMainFrame()); |
| IntRect touch_rect = frame_->View()->ContentsToRootFrame( |
| result.GetHitTestLocation().BoundingBox()); |
| HeapVector<Member<Node>, 11> nodes; |
| CopyToVector(result.ListBasedTestResult(), nodes); |
| |
| // FIXME: the explicit Vector conversion copies into a temporary and is |
| // wasteful. |
| return FindBestContextMenuCandidate(target_node, target_point, touch_center, |
| touch_rect, |
| HeapVector<Member<Node>>(nodes)); |
| } |
| |
| bool EventHandler::BestZoomableAreaForTouchPoint(const IntPoint& touch_center, |
| const IntSize& touch_radius, |
| IntRect& target_area, |
| Node*& target_node) { |
| if (touch_radius.IsEmpty()) |
| return false; |
| |
| IntPoint hit_test_point = frame_->View()->RootFrameToContents(touch_center); |
| |
| HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kReadOnly | |
| HitTestRequest::kActive | |
| HitTestRequest::kListBased; |
| HitTestResult result = |
| HitTestResultAtPoint(hit_test_point, hit_type, LayoutSize(touch_radius)); |
| |
| IntRect touch_rect(touch_center - touch_radius, touch_radius + touch_radius); |
| HeapVector<Member<Node>, 11> nodes; |
| CopyToVector(result.ListBasedTestResult(), nodes); |
| |
| // FIXME: the explicit Vector conversion copies into a temporary and is |
| // wasteful. |
| return FindBestZoomableArea(target_node, target_area, touch_center, |
| touch_rect, HeapVector<Member<Node>>(nodes)); |
| } |
| |
| // Update the hover and active state across all frames for this gesture. |
| // This logic is different than the mouse case because mice send MouseLeave |
| // events to frames as they're exited. With gestures, a single event |
| // conceptually both 'leaves' whatever frame currently had hover and enters a |
| // new frame |
| void EventHandler::UpdateGestureHoverActiveState(const HitTestRequest& request, |
| Element* inner_element) { |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| |
| HeapVector<Member<LocalFrame>> new_hover_frame_chain; |
| LocalFrame* new_hover_frame_in_document = |
| inner_element ? inner_element->GetDocument().GetFrame() : nullptr; |
| // Insert the ancestors of the frame having the new hovered element to the |
| // frame chain. The frame chain doesn't include the main frame to avoid the |
| // redundant work that cleans the hover state because the hover state for the |
| // main frame is updated by calling Document::updateHoverActiveState. |
| while (new_hover_frame_in_document && new_hover_frame_in_document != frame_) { |
| new_hover_frame_chain.push_back(new_hover_frame_in_document); |
| Frame* parent_frame = new_hover_frame_in_document->Tree().Parent(); |
| new_hover_frame_in_document = parent_frame && parent_frame->IsLocalFrame() |
| ? ToLocalFrame(parent_frame) |
| : nullptr; |
| } |
| |
| Element* old_hover_element_in_cur_doc = frame_->GetDocument()->HoverElement(); |
| Element* new_innermost_hover_element = inner_element; |
| |
| if (new_innermost_hover_element != old_hover_element_in_cur_doc) { |
| size_t index_frame_chain = new_hover_frame_chain.size(); |
| |
| // Clear the hover state on any frames which are no longer in the frame |
| // chain of the hovered element. |
| while (old_hover_element_in_cur_doc && |
| old_hover_element_in_cur_doc->IsFrameOwnerElement()) { |
| LocalFrame* new_hover_frame = nullptr; |
| // If we can't get the frame from the new hover frame chain, |
| // the newHoverFrame will be null and the old hover state will be cleared. |
| if (index_frame_chain > 0) |
| new_hover_frame = new_hover_frame_chain[--index_frame_chain]; |
| |
| HTMLFrameOwnerElement* owner = |
| ToHTMLFrameOwnerElement(old_hover_element_in_cur_doc); |
| if (!owner->ContentFrame() || !owner->ContentFrame()->IsLocalFrame()) |
| break; |
| |
| LocalFrame* old_hover_frame = ToLocalFrame(owner->ContentFrame()); |
| Document* doc = old_hover_frame->GetDocument(); |
| if (!doc) |
| break; |
| |
| old_hover_element_in_cur_doc = doc->HoverElement(); |
| // If the old hovered frame is different from the new hovered frame. |
| // we should clear the old hovered element from the old hovered frame. |
| if (new_hover_frame != old_hover_frame) |
| doc->UpdateHoverActiveState(request, nullptr); |
| } |
| } |
| |
| // Recursively set the new active/hover states on every frame in the chain of |
| // innerElement. |
| frame_->GetDocument()->UpdateHoverActiveState(request, inner_element); |
| } |
| |
| // Update the mouseover/mouseenter/mouseout/mouseleave events across all frames |
| // for this gesture, before passing the targeted gesture event directly to a hit |
| // frame. |
| void EventHandler::UpdateGestureTargetNodeForMouseEvent( |
| const GestureEventWithHitTestResults& targeted_event) { |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| |
| // Behaviour of this function is as follows: |
| // - Create the chain of all entered frames. |
| // - Compare the last frame chain under the gesture to newly entered frame |
| // chain from the main frame one by one. |
| // - If the last frame doesn't match with the entered frame, then create the |
| // chain of exited frames from the last frame chain. |
| // - Dispatch mouseout/mouseleave events of the exited frames from the inside |
| // out. |
| // - Dispatch mouseover/mouseenter events of the entered frames into the |
| // inside. |
| |
| // Insert the ancestors of the frame having the new target node to the entered |
| // frame chain. |
| HeapVector<Member<LocalFrame>, 2> entered_frame_chain; |
| LocalFrame* entered_frame_in_document = |
| targeted_event.GetHitTestResult().InnerNodeFrame(); |
| while (entered_frame_in_document) { |
| entered_frame_chain.push_back(entered_frame_in_document); |
| Frame* parent_frame = entered_frame_in_document->Tree().Parent(); |
| entered_frame_in_document = parent_frame && parent_frame->IsLocalFrame() |
| ? ToLocalFrame(parent_frame) |
| : nullptr; |
| } |
| |
| size_t index_entered_frame_chain = entered_frame_chain.size(); |
| LocalFrame* exited_frame_in_document = frame_; |
| HeapVector<Member<LocalFrame>, 2> exited_frame_chain; |
| // Insert the frame from the disagreement between last frames and entered |
| // frames. |
| while (exited_frame_in_document) { |
| Node* last_node_under_tap = exited_frame_in_document->GetEventHandler() |
| .mouse_event_manager_->GetNodeUnderMouse(); |
| if (!last_node_under_tap) |
| break; |
| |
| LocalFrame* next_exited_frame_in_document = nullptr; |
| if (last_node_under_tap->IsFrameOwnerElement()) { |
| HTMLFrameOwnerElement* owner = |
| ToHTMLFrameOwnerElement(last_node_under_tap); |
| if (owner->ContentFrame() && owner->ContentFrame()->IsLocalFrame()) |
| next_exited_frame_in_document = ToLocalFrame(owner->ContentFrame()); |
| } |
| |
| if (exited_frame_chain.size() > 0) { |
| exited_frame_chain.push_back(exited_frame_in_document); |
| } else { |
| LocalFrame* last_entered_frame_in_document = |
| index_entered_frame_chain |
| ? entered_frame_chain[index_entered_frame_chain - 1] |
| : nullptr; |
| if (exited_frame_in_document != last_entered_frame_in_document) |
| exited_frame_chain.push_back(exited_frame_in_document); |
| else if (next_exited_frame_in_document && index_entered_frame_chain) |
| --index_entered_frame_chain; |
| } |
| exited_frame_in_document = next_exited_frame_in_document; |
| } |
| |
| const WebGestureEvent& gesture_event = targeted_event.Event(); |
| unsigned modifiers = gesture_event.GetModifiers(); |
| WebMouseEvent fake_mouse_move( |
| WebInputEvent::kMouseMove, gesture_event, |
| WebPointerProperties::Button::kNoButton, |
| /* clickCount */ 0, |
| modifiers | WebInputEvent::Modifiers::kIsCompatibilityEventForTouch, |
| gesture_event.TimeStampSeconds()); |
| |
| // Update the mouseout/mouseleave event |
| size_t index_exited_frame_chain = exited_frame_chain.size(); |
| while (index_exited_frame_chain) { |
| LocalFrame* leave_frame = exited_frame_chain[--index_exited_frame_chain]; |
| leave_frame->GetEventHandler().mouse_event_manager_->SetNodeUnderMouse( |
| UpdateMouseEventTargetNode(nullptr), String(), fake_mouse_move); |
| } |
| |
| // update the mouseover/mouseenter event |
| while (index_entered_frame_chain) { |
| Frame* parent_frame = |
| entered_frame_chain[--index_entered_frame_chain]->Tree().Parent(); |
| if (parent_frame && parent_frame->IsLocalFrame()) { |
| ToLocalFrame(parent_frame) |
| ->GetEventHandler() |
| .mouse_event_manager_->SetNodeUnderMouse( |
| UpdateMouseEventTargetNode(ToHTMLFrameOwnerElement( |
| entered_frame_chain[index_entered_frame_chain]->Owner())), |
| String(), fake_mouse_move); |
| } |
| } |
| } |
| |
| GestureEventWithHitTestResults EventHandler::TargetGestureEvent( |
| const WebGestureEvent& gesture_event, |
| bool read_only) { |
| TRACE_EVENT0("input", "EventHandler::targetGestureEvent"); |
| |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| // Scrolling events get hit tested per frame (like wheel events do). |
| DCHECK(!gesture_event.IsScrollEvent()); |
| |
| HitTestRequest::HitTestRequestType hit_type = |
| gesture_manager_->GetHitTypeForGestureType(gesture_event.GetType()); |
| TimeDelta active_interval; |
| bool should_keep_active_for_min_interval = false; |
| if (read_only) { |
| hit_type |= HitTestRequest::kReadOnly; |
| } else if (gesture_event.GetType() == WebInputEvent::kGestureTap && |
| gesture_manager_->GetLastShowPressTimestamp()) { |
| // If the Tap is received very shortly after ShowPress, we want to |
| // delay clearing of the active state so that it's visible to the user |
| // for at least a couple of frames. |
| active_interval = TimeTicks::Now() - |
| gesture_manager_->GetLastShowPressTimestamp().value(); |
| should_keep_active_for_min_interval = |
| active_interval < kMinimumActiveInterval; |
| if (should_keep_active_for_min_interval) |
| hit_type |= HitTestRequest::kReadOnly; |
| } |
| |
| GestureEventWithHitTestResults event_with_hit_test_results = |
| HitTestResultForGestureEvent(gesture_event, hit_type); |
| // Now apply hover/active state to the final target. |
| HitTestRequest request(hit_type | HitTestRequest::kAllowChildFrameContent); |
| if (!request.ReadOnly()) |
| UpdateGestureHoverActiveState( |
| request, event_with_hit_test_results.GetHitTestResult().InnerElement()); |
| |
| if (should_keep_active_for_min_interval) { |
| last_deferred_tap_element_ = |
| event_with_hit_test_results.GetHitTestResult().InnerElement(); |
| // TODO(https://crbug.com/668758): Use a normal BeginFrame update for this. |
| active_interval_timer_.StartOneShot( |
| (kMinimumActiveInterval - active_interval).InSecondsF(), |
| BLINK_FROM_HERE); |
| } |
| |
| return event_with_hit_test_results; |
| } |
| |
| GestureEventWithHitTestResults EventHandler::HitTestResultForGestureEvent( |
| const WebGestureEvent& gesture_event, |
| HitTestRequest::HitTestRequestType hit_type) { |
| // Perform the rect-based hit-test (or point-based if adjustment is disabled). |
| // Note that we don't yet apply hover/active state here because we need to |
| // resolve touch adjustment first so that we apply hover/active it to the |
| // final adjusted node. |
| IntPoint hit_test_point = frame_->View()->RootFrameToContents( |
| FlooredIntPoint(gesture_event.PositionInRootFrame())); |
| LayoutSize padding; |
| if (ShouldApplyTouchAdjustment(gesture_event)) { |
| padding = LayoutSize(gesture_event.TapAreaInRootFrame()); |
| if (!padding.IsEmpty()) { |
| padding.Scale(1.f / 2); |
| hit_type |= HitTestRequest::kListBased; |
| } |
| } |
| HitTestResult hit_test_result = HitTestResultAtPoint( |
| hit_test_point, hit_type | HitTestRequest::kReadOnly, padding); |
| |
| // Adjust the location of the gesture to the most likely nearby node, as |
| // appropriate for the type of event. |
| WebGestureEvent adjusted_event = gesture_event; |
| ApplyTouchAdjustment(&adjusted_event, &hit_test_result); |
| |
| // Do a new hit-test at the (adjusted) gesture co-ordinates. This is necessary |
| // because rect-based hit testing and touch adjustment sometimes return a |
| // different node than what a point-based hit test would return for the same |
| // point. |
| // FIXME: Fix touch adjustment to avoid the need for a redundant hit test. |
| // http://crbug.com/398914 |
| if (ShouldApplyTouchAdjustment(gesture_event)) { |
| LocalFrame* hit_frame = hit_test_result.InnerNodeFrame(); |
| if (!hit_frame) |
| hit_frame = frame_; |
| hit_test_result = EventHandlingUtil::HitTestResultInFrame( |
| hit_frame, |
| hit_frame->View()->RootFrameToContents( |
| FlooredIntPoint(adjusted_event.PositionInRootFrame())), |
| (hit_type | HitTestRequest::kReadOnly) & ~HitTestRequest::kListBased); |
| } |
| |
| // If we did a rect-based hit test it must be resolved to the best single node |
| // by now to ensure consumers don't accidentally use one of the other |
| // candidates. |
| DCHECK(!hit_test_result.IsRectBasedTest()); |
| |
| return GestureEventWithHitTestResults(adjusted_event, hit_test_result); |
| } |
| |
| void EventHandler::ApplyTouchAdjustment(WebGestureEvent* gesture_event, |
| HitTestResult* hit_test_result) { |
| if (!ShouldApplyTouchAdjustment(*gesture_event)) |
| return; |
| |
| Node* adjusted_node = nullptr; |
| IntPoint adjusted_point = |
| FlooredIntPoint(gesture_event->PositionInRootFrame()); |
| bool adjusted = false; |
| switch (gesture_event->GetType()) { |
| case WebInputEvent::kGestureTap: |
| case WebInputEvent::kGestureTapUnconfirmed: |
| case WebInputEvent::kGestureTapDown: |
| case WebInputEvent::kGestureShowPress: |
| adjusted = BestClickableNodeForHitTestResult( |
| *hit_test_result, adjusted_point, adjusted_node); |
| break; |
| case WebInputEvent::kGestureLongPress: |
| case WebInputEvent::kGestureLongTap: |
| case WebInputEvent::kGestureTwoFingerTap: |
| adjusted = BestContextMenuNodeForHitTestResult( |
| *hit_test_result, adjusted_point, adjusted_node); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Update the hit-test result to be a point-based result instead of a |
| // rect-based result. |
| // FIXME: We should do this even when no candidate matches the node filter. |
| // crbug.com/398914 |
| if (adjusted) { |
| hit_test_result->ResolveRectBasedTest( |
| adjusted_node, frame_->View()->RootFrameToContents(adjusted_point)); |
| gesture_event->ApplyTouchAdjustment( |
| WebFloatPoint(adjusted_point.X(), adjusted_point.Y())); |
| } |
| } |
| |
| WebInputEventResult EventHandler::SendContextMenuEvent( |
| const WebMouseEvent& event, |
| Node* override_target_node) { |
| LocalFrameView* v = frame_->View(); |
| if (!v) |
| return WebInputEventResult::kNotHandled; |
| |
| // Clear mouse press state to avoid initiating a drag while context menu is |
| // up. |
| mouse_event_manager_->SetMousePressed(false); |
| LayoutPoint position_in_contents = |
| v->RootFrameToContents(FlooredIntPoint(event.PositionInRootFrame())); |
| HitTestRequest request(HitTestRequest::kActive); |
| MouseEventWithHitTestResults mev = |
| frame_->GetDocument()->PerformMouseEventHitTest( |
| request, position_in_contents, event); |
| // Since |Document::performMouseEventHitTest()| modifies layout tree for |
| // setting hover element, we need to update layout tree for requirement of |
| // |SelectionController::sendContextMenuEvent()|. |
| frame_->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| GetSelectionController().SendContextMenuEvent(mev, position_in_contents); |
| |
| Node* target_node = |
| override_target_node ? override_target_node : mev.InnerNode(); |
| return mouse_event_manager_->DispatchMouseEvent( |
| UpdateMouseEventTargetNode(target_node), EventTypeNames::contextmenu, |
| event, mev.GetHitTestResult().CanvasRegionId(), 0); |
| } |
| |
| static bool ShouldShowContextMenuAtSelection(const FrameSelection& selection) { |
| const VisibleSelection& visible_selection = |
| selection.ComputeVisibleSelectionInDOMTreeDeprecated(); |
| if (!visible_selection.IsRange() && !visible_selection.RootEditableElement()) |
| return false; |
| return selection.SelectionHasFocus(); |
| } |
| |
| WebInputEventResult EventHandler::ShowNonLocatedContextMenu( |
| Element* override_target_element, |
| WebMenuSourceType source_type) { |
| LocalFrameView* view = frame_->View(); |
| if (!view) |
| return WebInputEventResult::kNotHandled; |
| |
| Document* doc = frame_->GetDocument(); |
| if (!doc) |
| return WebInputEventResult::kNotHandled; |
| |
| static const int kContextMenuMargin = 1; |
| |
| #if OS(WIN) |
| int right_aligned = ::GetSystemMetrics(SM_MENUDROPALIGNMENT); |
| #else |
| int right_aligned = 0; |
| #endif |
| IntPoint location_in_root_frame; |
| |
| Element* focused_element = |
| override_target_element ? override_target_element : doc->FocusedElement(); |
| FrameSelection& selection = frame_->Selection(); |
| VisualViewport& visual_viewport = frame_->GetPage()->GetVisualViewport(); |
| |
| if (!override_target_element && ShouldShowContextMenuAtSelection(selection)) { |
| // TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets |
| // needs to be audited. See http://crbug.com/590369 for more details. |
| doc->UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| IntRect first_rect = frame_->GetEditor().FirstRectForRange( |
| selection.ComputeVisibleSelectionInDOMTree() |
| .ToNormalizedEphemeralRange()); |
| |
| int x = right_aligned ? first_rect.MaxX() : first_rect.X(); |
| // In a multiline edit, firstRect.maxY() would end up on the next line, so |
| // -1. |
| int y = first_rect.MaxY() ? first_rect.MaxY() - 1 : 0; |
| location_in_root_frame = view->ContentsToRootFrame(IntPoint(x, y)); |
| } else if (focused_element) { |
| IntRect clipped_rect = focused_element->BoundsInViewport(); |
| location_in_root_frame = |
| visual_viewport.ViewportToRootFrame(clipped_rect.Center()); |
| } else { |
| location_in_root_frame = IntPoint( |
| right_aligned |
| ? visual_viewport.VisibleRect().MaxX() - kContextMenuMargin |
| : visual_viewport.GetScrollOffset().Width() + kContextMenuMargin, |
| visual_viewport.GetScrollOffset().Height() + kContextMenuMargin); |
| } |
| |
| frame_->View()->SetCursor(PointerCursor()); |
| IntPoint location_in_viewport = |
| visual_viewport.RootFrameToViewport(location_in_root_frame); |
| IntPoint global_position = |
| view->GetChromeClient() |
| ->ViewportToScreen(IntRect(location_in_viewport, IntSize()), |
| frame_->View()) |
| .Location(); |
| |
| Node* target_node = |
| override_target_element ? override_target_element : doc->FocusedElement(); |
| if (!target_node) |
| target_node = doc; |
| |
| // Use the focused node as the target for hover and active. |
| HitTestRequest request(HitTestRequest::kActive); |
| HitTestResult result(request, location_in_root_frame); |
| result.SetInnerNode(target_node); |
| doc->UpdateHoverActiveState(request, result.InnerElement()); |
| |
| // The contextmenu event is a mouse event even when invoked using the |
| // keyboard. This is required for web compatibility. |
| WebInputEvent::Type event_type = WebInputEvent::kMouseDown; |
| if (frame_->GetSettings() && |
| frame_->GetSettings()->GetShowContextMenuOnMouseUp()) |
| event_type = WebInputEvent::kMouseUp; |
| |
| WebInputEvent::Modifiers modifiers; |
| switch (source_type) { |
| case kMenuSourceTouch: |
| case kMenuSourceLongPress: |
| case kMenuSourceTouchHandle: |
| modifiers = WebInputEvent::kIsCompatibilityEventForTouch; |
| break; |
| default: |
| modifiers = WebInputEvent::kNoModifiers; |
| break; |
| } |
| |
| WebMouseEvent mouse_event( |
| event_type, |
| WebFloatPoint(location_in_root_frame.X(), location_in_root_frame.Y()), |
| WebFloatPoint(global_position.X(), global_position.Y()), |
| WebPointerProperties::Button::kNoButton, /* clickCount */ 0, modifiers, |
| TimeTicks::Now().InSeconds()); |
| |
| // TODO(dtapuska): Transition the mouseEvent to be created really in viewport |
| // coordinates instead of root frame coordinates. |
| mouse_event.SetFrameScale(1); |
| |
| return SendContextMenuEvent(mouse_event, override_target_element); |
| } |
| |
| void EventHandler::ScheduleHoverStateUpdate() { |
| // TODO(https://crbug.com/668758): Use a normal BeginFrame update for this. |
| if (!hover_timer_.IsActive() && |
| !mouse_event_manager_->IsMousePositionUnknown()) |
| hover_timer_.StartOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void EventHandler::ScheduleCursorUpdate() { |
| // We only want one timer for the page, rather than each frame having it's own |
| // timer competing which eachother (since there's only one mouse cursor). |
| DCHECK_EQ(frame_, &frame_->LocalFrameRoot()); |
| |
| // TODO(https://crbug.com/668758): Use a normal BeginFrame update for this. |
| if (!cursor_update_timer_.IsActive()) |
| cursor_update_timer_.StartOneShot(kCursorUpdateInterval, BLINK_FROM_HERE); |
| } |
| |
| bool EventHandler::CursorUpdatePending() { |
| return cursor_update_timer_.IsActive(); |
| } |
| |
| void EventHandler::DispatchFakeMouseMoveEventSoon() { |
| mouse_event_manager_->DispatchFakeMouseMoveEventSoon(); |
| } |
| |
| void EventHandler::DispatchFakeMouseMoveEventSoonInQuad(const FloatQuad& quad) { |
| mouse_event_manager_->DispatchFakeMouseMoveEventSoonInQuad(quad); |
| } |
| |
| void EventHandler::SetResizingFrameSet(HTMLFrameSetElement* frame_set) { |
| frame_set_being_resized_ = frame_set; |
| } |
| |
| void EventHandler::ResizeScrollableAreaDestroyed() { |
| scroll_manager_->ClearResizeScrollableArea(true); |
| } |
| |
| void EventHandler::HoverTimerFired(TimerBase*) { |
| TRACE_EVENT0("input", "EventHandler::hoverTimerFired"); |
| |
| DCHECK(frame_); |
| DCHECK(frame_->GetDocument()); |
| |
| if (LayoutViewItem layout_item = frame_->ContentLayoutItem()) { |
| if (LocalFrameView* view = frame_->View()) { |
| HitTestRequest request(HitTestRequest::kMove); |
| HitTestResult result(request, |
| view->RootFrameToContents( |
| mouse_event_manager_->LastKnownMousePosition())); |
| layout_item.HitTest(result); |
| frame_->GetDocument()->UpdateHoverActiveState(request, |
| result.InnerElement()); |
| } |
| } |
| } |
| |
| void EventHandler::ActiveIntervalTimerFired(TimerBase*) { |
| TRACE_EVENT0("input", "EventHandler::activeIntervalTimerFired"); |
| |
| if (frame_ && frame_->GetDocument() && last_deferred_tap_element_) { |
| // FIXME: Enable condition when http://crbug.com/226842 lands |
| // m_lastDeferredTapElement.get() == m_frame->document()->activeElement() |
| HitTestRequest request(HitTestRequest::kTouchEvent | |
| HitTestRequest::kRelease); |
| frame_->GetDocument()->UpdateHoverActiveState( |
| request, last_deferred_tap_element_.Get()); |
| } |
| last_deferred_tap_element_ = nullptr; |
| } |
| |
| void EventHandler::NotifyElementActivated() { |
| // Since another element has been set to active, stop current timer and clear |
| // reference. |
| active_interval_timer_.Stop(); |
| last_deferred_tap_element_ = nullptr; |
| } |
| |
| bool EventHandler::HandleAccessKey(const WebKeyboardEvent& evt) { |
| return keyboard_event_manager_->HandleAccessKey(evt); |
| } |
| |
| WebInputEventResult EventHandler::KeyEvent( |
| const WebKeyboardEvent& initial_key_event) { |
| return keyboard_event_manager_->KeyEvent(initial_key_event); |
| } |
| |
| void EventHandler::DefaultKeyboardEventHandler(KeyboardEvent* event) { |
| keyboard_event_manager_->DefaultKeyboardEventHandler( |
| event, mouse_event_manager_->MousePressNode()); |
| } |
| |
| void EventHandler::DragSourceEndedAt(const WebMouseEvent& event, |
| DragOperation operation) { |
| // Asides from routing the event to the correct frame, the hit test is also an |
| // opportunity for Layer to update the :hover and :active pseudoclasses. |
| HitTestRequest request(HitTestRequest::kRelease); |
| MouseEventWithHitTestResults mev = |
| EventHandlingUtil::PerformMouseEventHitTest(frame_, request, event); |
| |
| LocalFrame* target_frame; |
| if (TargetIsFrame(mev.InnerNode(), target_frame)) { |
| if (target_frame) { |
| target_frame->GetEventHandler().DragSourceEndedAt(event, operation); |
| return; |
| } |
| } |
| |
| mouse_event_manager_->DragSourceEndedAt(event, operation); |
| } |
| |
| void EventHandler::UpdateDragStateAfterEditDragIfNeeded( |
| Element* root_editable_element) { |
| // If inserting the dragged contents removed the drag source, we still want to |
| // fire dragend at the root editble element. |
| if (mouse_event_manager_->GetDragState().drag_src_ && |
| !mouse_event_manager_->GetDragState().drag_src_->isConnected()) |
| mouse_event_manager_->GetDragState().drag_src_ = root_editable_element; |
| } |
| |
| bool EventHandler::HandleTextInputEvent(const String& text, |
| Event* underlying_event, |
| TextEventInputType input_type) { |
| // Platforms should differentiate real commands like selectAll from text input |
| // in disguise (like insertNewline), and avoid dispatching text input events |
| // from keydown default handlers. |
| DCHECK(!underlying_event || !underlying_event->IsKeyboardEvent() || |
| ToKeyboardEvent(underlying_event)->type() == EventTypeNames::keypress); |
| |
| if (!frame_) |
| return false; |
| |
| EventTarget* target; |
| if (underlying_event) |
| target = underlying_event->target(); |
| else |
| target = EventTargetNodeForDocument(frame_->GetDocument()); |
| if (!target) |
| return false; |
| |
| TextEvent* event = TextEvent::Create(frame_->DomWindow(), text, input_type); |
| event->SetUnderlyingEvent(underlying_event); |
| |
| target->DispatchEvent(event); |
| return event->DefaultHandled() || event->defaultPrevented(); |
| } |
| |
| void EventHandler::DefaultTextInputEventHandler(TextEvent* event) { |
| if (frame_->GetEditor().HandleTextEvent(event)) |
| event->SetDefaultHandled(); |
| } |
| |
| void EventHandler::CapsLockStateMayHaveChanged() { |
| keyboard_event_manager_->CapsLockStateMayHaveChanged(); |
| } |
| |
| bool EventHandler::PassMousePressEventToScrollbar( |
| MouseEventWithHitTestResults& mev) { |
| Scrollbar* scrollbar = mev.GetScrollbar(); |
| UpdateLastScrollbarUnderMouse(scrollbar, true); |
| |
| if (!scrollbar || !scrollbar->Enabled()) |
| return false; |
| scroll_manager_->SetFrameWasScrolledByUser(); |
| scrollbar->MouseDown(mev.Event()); |
| return true; |
| } |
| |
| // If scrollbar (under mouse) is different from last, send a mouse exited. Set |
| // last to scrollbar if setLast is true; else set last to 0. |
| void EventHandler::UpdateLastScrollbarUnderMouse(Scrollbar* scrollbar, |
| bool set_last) { |
| if (last_scrollbar_under_mouse_ != scrollbar) { |
| // Send mouse exited to the old scrollbar. |
| if (last_scrollbar_under_mouse_) |
| last_scrollbar_under_mouse_->MouseExited(); |
| |
| // Send mouse entered if we're setting a new scrollbar. |
| if (scrollbar && set_last) |
| scrollbar->MouseEntered(); |
| |
| last_scrollbar_under_mouse_ = set_last ? scrollbar : nullptr; |
| } |
| } |
| |
| WebInputEventResult EventHandler::HandleTouchEvent( |
| const WebTouchEvent& event, |
| const Vector<WebTouchEvent>& coalesced_events) { |
| TRACE_EVENT0("blink", "EventHandler::handleTouchEvent"); |
| return pointer_event_manager_->HandleTouchEvents(event, coalesced_events); |
| } |
| |
| WebInputEventResult EventHandler::PassMousePressEventToSubframe( |
| MouseEventWithHitTestResults& mev, |
| LocalFrame* subframe) { |
| GetSelectionController().PassMousePressEventToSubframe(mev); |
| WebInputEventResult result = |
| subframe->GetEventHandler().HandleMousePressEvent(mev.Event()); |
| if (result != WebInputEventResult::kNotHandled) |
| return result; |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| WebInputEventResult EventHandler::PassMouseMoveEventToSubframe( |
| MouseEventWithHitTestResults& mev, |
| const Vector<WebMouseEvent>& coalesced_events, |
| LocalFrame* subframe, |
| HitTestResult* hovered_node) { |
| if (mouse_event_manager_->MouseDownMayStartDrag()) |
| return WebInputEventResult::kNotHandled; |
| WebInputEventResult result = |
| subframe->GetEventHandler().HandleMouseMoveOrLeaveEvent( |
| mev.Event(), coalesced_events, hovered_node); |
| if (result != WebInputEventResult::kNotHandled) |
| return result; |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| WebInputEventResult EventHandler::PassMouseReleaseEventToSubframe( |
| MouseEventWithHitTestResults& mev, |
| LocalFrame* subframe) { |
| WebInputEventResult result = |
| subframe->GetEventHandler().HandleMouseReleaseEvent(mev.Event()); |
| if (result != WebInputEventResult::kNotHandled) |
| return result; |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| } // namespace blink |