| /* |
| * 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 "third_party/blink/renderer/core/input/event_handler.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "build/build_config.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_input_event.h" |
| #include "third_party/blink/public/platform/web_mouse_wheel_event.h" |
| #include "third_party/blink/renderer/core/clipboard/data_transfer.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/events/event_path.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/editing/editor.h" |
| #include "third_party/blink/renderer/core/editing/ephemeral_range.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/selection_controller.h" |
| #include "third_party/blink/renderer/core/events/gesture_event.h" |
| #include "third_party/blink/renderer/core/events/keyboard_event.h" |
| #include "third_party/blink/renderer/core/events/mouse_event.h" |
| #include "third_party/blink/renderer/core/events/pointer_event.h" |
| #include "third_party/blink/renderer/core/events/text_event.h" |
| #include "third_party/blink/renderer/core/events/touch_event.h" |
| #include "third_party/blink/renderer/core/frame/deprecation.h" |
| #include "third_party/blink/renderer/core/frame/event_handler_registry.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/html_dialog_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_element_base.h" |
| #include "third_party/blink/renderer/core/html/html_frame_set_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input/event_handling_util.h" |
| #include "third_party/blink/renderer/core/input/input_device_capabilities.h" |
| #include "third_party/blink/renderer/core/input/touch_action_util.h" |
| #include "third_party/blink/renderer/core/input/touch_list.h" |
| #include "third_party/blink/renderer/core/input_type_names.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_request.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_loader.h" |
| #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" |
| #include "third_party/blink/renderer/core/page/autoscroll_controller.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/drag_state.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/frame_tree.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scroll_state.h" |
| #include "third_party/blink/renderer/core/page/touch_adjustment.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/style/cursor_data.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/geometry/float_point.h" |
| #include "third_party/blink/renderer/platform/graphics/image.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h" |
| #include "third_party/blink/renderer/platform/scroll/scrollbar.h" |
| #include "third_party/blink/renderer/platform/windows_keyboard_codes.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/time.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 EmbeddedContentView associated, we'd like to |
| // EventHandler::handleMousePressEvent to pass the event to the |
| // EmbeddedContentView 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()); |
| } |
| |
| } // 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. |
| constexpr TimeDelta kMinimumActiveInterval = TimeDelta::FromSecondsD(0.15); |
| |
| EventHandler::EventHandler(LocalFrame& frame) |
| : frame_(frame), |
| selection_controller_(SelectionController::Create(frame)), |
| hover_timer_(frame.GetTaskRunner(TaskType::kUserInteraction), |
| this, |
| &EventHandler::HoverTimerFired), |
| cursor_update_timer_( |
| frame.GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &EventHandler::CursorUpdateTimerFired), |
| event_handler_will_reset_capturing_mouse_events_node_(0), |
| should_only_fire_drag_over_event_(false), |
| event_handler_registry_( |
| frame_->IsLocalRoot() |
| ? new EventHandlerRegistry(*frame_) |
| : &frame_->LocalFrameRoot().GetEventHandlerRegistry()), |
| scroll_manager_(new ScrollManager(frame)), |
| mouse_event_manager_(new MouseEventManager(frame, *scroll_manager_)), |
| mouse_wheel_event_manager_(new MouseWheelEventManager(frame)), |
| 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_(frame.GetTaskRunner(TaskType::kUserInteraction), |
| this, |
| &EventHandler::ActiveIntervalTimerFired) {} |
| |
| void EventHandler::Trace(blink::Visitor* visitor) { |
| 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(event_handler_registry_); |
| visitor->Trace(scroll_manager_); |
| visitor->Trace(mouse_event_manager_); |
| visitor->Trace(mouse_wheel_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_ = nullptr; |
| capturing_mouse_events_node_ = nullptr; |
| pointer_event_manager_->Clear(); |
| scroll_manager_->Clear(); |
| gesture_manager_->Clear(); |
| mouse_event_manager_->Clear(); |
| mouse_wheel_event_manager_->Clear(); |
| last_show_press_timestamp_.reset(); |
| last_deferred_tap_element_ = nullptr; |
| event_handler_will_reset_capturing_mouse_events_node_ = false; |
| should_use_touch_event_adjusted_point_ = false; |
| touch_adjustment_result_.unique_event_id = 0; |
| } |
| |
| 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( |
| layout_object->GetFrame(), |
| frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| mouse_event_manager_->LastKnownMousePosition()), |
| mouse_event_manager_->LastKnownMousePositionGlobal()); |
| mouse_event_manager_->InvalidateClick(); |
| } |
| |
| void EventHandler::PerformHitTest(HitTestResult& result, |
| bool no_lifecycle_update) const { |
| // 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. |
| // The check to LifecycleUpdatesActive() prevents hit testing to frames |
| // that have already had layout but are throttled to prevent painting |
| // because the current Document isn't ready to render yet. In that case |
| // the lifecycle update prompted by HitTest() would fail. |
| if (!frame_->ContentLayoutObject() || !frame_->View() || |
| !frame_->View()->DidFirstLayout() || |
| !frame_->View()->LifecycleUpdatesActive()) |
| return; |
| |
| if (no_lifecycle_update) { |
| frame_->ContentLayoutObject()->HitTestNoLifecycleUpdate(result); |
| } else { |
| frame_->ContentLayoutObject()->HitTest(result); |
| } |
| const HitTestRequest& request = result.GetHitTestRequest(); |
| if (!request.ReadOnly()) { |
| frame_->GetDocument()->UpdateHoverActiveState(request, |
| result.InnerElement()); |
| } |
| } |
| |
| HitTestResult EventHandler::HitTestResultAtPoint( |
| const LayoutPoint& point, |
| HitTestRequest::HitTestRequestType hit_type, |
| const LayoutObject* stop_node, |
| bool no_lifecycle_update) { |
| TRACE_EVENT0("blink", "EventHandler::hitTestResultAtPoint"); |
| |
| // 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) { |
| LayoutPoint main_content_point = main_view->ConvertFromRootFrame( |
| frame_view->ConvertToRootFrame(point)); |
| return main_frame.GetEventHandler().HitTestResultAtPoint( |
| main_content_point, hit_type, stop_node); |
| } |
| } |
| } |
| |
| // hitTestResultAtPoint is specifically used to hitTest into all frames, thus |
| // it always allows child frame content. |
| HitTestRequest request(hit_type | HitTestRequest::kAllowChildFrameContent, |
| stop_node); |
| HitTestResult result(request, point); |
| PerformHitTest(result, no_lifecycle_update); |
| return result; |
| } |
| |
| HitTestResult EventHandler::HitTestResultAtRect( |
| const LayoutRect& rect, |
| HitTestRequest::HitTestRequestType hit_type, |
| const LayoutObject* stop_node, |
| bool no_lifecycle_update) { |
| TRACE_EVENT0("blink", "EventHandler::hitTestResultAtRect"); |
| |
| DCHECK(hit_type & HitTestRequest::kListBased); |
| DCHECK(!rect.IsEmpty()); |
| |
| 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) { |
| LayoutPoint main_content_point = main_view->ConvertFromRootFrame( |
| frame_view->ConvertToRootFrame(rect.Location())); |
| return main_frame.GetEventHandler().HitTestResultAtRect( |
| LayoutRect(main_content_point, rect.Size()), hit_type, stop_node); |
| } |
| } |
| } |
| |
| // hitTestResultAtPoint is specifically used to hitTest into all frames, thus |
| // it always allows child frame content. |
| HitTestRequest request(hit_type | HitTestRequest::kAllowChildFrameContent, |
| stop_node); |
| HitTestResult result(request, rect); |
| PerformHitTest(result, no_lifecycle_update); |
| return result; |
| } |
| |
| void EventHandler::StopAutoscroll() { |
| scroll_manager_->StopMiddleClickAutoscroll(); |
| 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::LastKnownMousePositionInRootFrame() const { |
| return frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| 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 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; |
| |
| auto* layout_view = view->GetLayoutView(); |
| if (!layout_view) |
| return; |
| |
| frame_->GetDocument()->UpdateStyleAndLayout(); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | |
| HitTestRequest::kAllowChildFrameContent); |
| HitTestResult result( |
| request, |
| view->ViewportToFrame(mouse_event_manager_->LastKnownMousePosition())); |
| layout_view->HitTest(result); |
| |
| if (LocalFrame* frame = result.InnerNodeFrame()) { |
| EventHandler::OptionalCursor optional_cursor = |
| frame->GetEventHandler().SelectCursor(result); |
| if (optional_cursor.IsCursorChange()) { |
| view->SetCursor(optional_cursor.GetCursor()); |
| } |
| } |
| } |
| |
| bool EventHandler::ShouldShowResizeForNode(const Node* node, |
| const HitTestResult& result) { |
| if (LayoutObject* layout_object = node->GetLayoutObject()) { |
| PaintLayer* layer = layout_object->EnclosingLayer(); |
| if (layer->GetScrollableArea() && |
| layer->GetScrollableArea()->IsPointInResizeControl( |
| result.RoundedPointInMainFrame(), kResizerForPointer)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool EventHandler::IsSelectingLink(const HitTestResult& result) { |
| // 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. |
| const bool mouse_selection = |
| !capturing_mouse_events_node_ && |
| mouse_event_manager_->MousePressed() && |
| GetSelectionController().MouseDownMayStartSelect() && |
| !mouse_event_manager_->MouseDownMayStartDrag() && |
| !frame_->Selection() |
| .ComputeVisibleSelectionInDOMTreeDeprecated() |
| .IsNone(); |
| return mouse_selection && result.IsOverLink(); |
| } |
| |
| bool EventHandler::ShouldShowIBeamForNode(const Node* node, |
| const HitTestResult& result) { |
| if (!node) |
| return false; |
| |
| if (node->IsTextNode() && (node->CanStartSelection() || result.IsOverLink())) |
| return true; |
| |
| return HasEditableStyle(*node); |
| } |
| |
| EventHandler::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; |
| |
| if (result.GetScrollbar()) |
| return PointerCursor(); |
| |
| Node* node = result.InnerPossiblyPseudoNode(); |
| if (!node) |
| return SelectAutoCursor(result, node, IBeamCursor()); |
| |
| if (ShouldShowResizeForNode(node, result)) |
| return PointerCursor(); |
| |
| 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); |
| } |
| } |
| |
| bool horizontal_text = !style || style->IsHorizontalWritingMode(); |
| const Cursor& i_beam = horizontal_text ? IBeamCursor() : VerticalTextCursor(); |
| |
| switch (style ? style->Cursor() : ECursor::kAuto) { |
| case ECursor::kAuto: { |
| return SelectAutoCursor(result, node, i_beam); |
| } |
| case ECursor::kCrosshair: |
| return CrossCursor(); |
| case ECursor::kPointer: |
| return IsSelectingLink(result) ? i_beam : 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 i_beam; |
| 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::kGrab: |
| return GrabCursor(); |
| case ECursor::kGrabbing: |
| return GrabbingCursor(); |
| } |
| return PointerCursor(); |
| } |
| |
| EventHandler::OptionalCursor EventHandler::SelectAutoCursor( |
| const HitTestResult& result, |
| Node* node, |
| const Cursor& i_beam) { |
| if (ShouldShowIBeamForNode(node, result)) |
| return i_beam; |
| |
| return PointerCursor(); |
| } |
| |
| WebInputEventResult EventHandler::DispatchBufferedTouchEvents() { |
| return pointer_event_manager_->FlushEvents(); |
| } |
| |
| WebInputEventResult EventHandler::HandlePointerEvent( |
| const WebPointerEvent& web_pointer_event, |
| const Vector<WebPointerEvent>& coalesced_events) { |
| return pointer_event_manager_->HandlePointerEvent(web_pointer_event, |
| coalesced_events); |
| } |
| |
| 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()->ConvertFromRootFrame( |
| 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 = EventHandlingUtil::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 |
| // EmbeddedContentView 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; |
| } |
| |
| std::unique_ptr<UserGestureIndicator> gesture_indicator = |
| Frame::NotifyUserActivation(frame_); |
| 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_->StopMiddleClickAutoscroll(); |
| 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 = DispatchMousePointerEvent( |
| WebInputEvent::kPointerDown, mev.InnerNode(), mev.CanvasRegionId(), |
| mev.Event(), Vector<WebMouseEvent>()); |
| |
| // Disabled form controls still need to resize the scrollable area. |
| if ((event_result == WebInputEventResult::kNotHandled || |
| event_result == WebInputEventResult::kHandledSuppressed) && |
| frame_->View()) { |
| LocalFrameView* view = frame_->View(); |
| PaintLayer* layer = |
| mev.InnerNode()->GetLayoutObject() |
| ? mev.InnerNode()->GetLayoutObject()->EnclosingLayer() |
| : nullptr; |
| IntPoint p = view->ConvertFromRootFrame( |
| 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 |
| // EmbeddedContentView 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>(), nullptr, 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); |
| |
| // Mouse states need to be reset when mouse move with no button down. |
| // This is for popup/context_menu opened at mouse_down event and |
| // mouse_release is not handled in page. |
| // crbug.com/527582 |
| if (mouse_event.button == WebPointerProperties::Button::kNoButton && |
| !(mouse_event.GetModifiers() & |
| WebInputEvent::Modifiers::kRelativeMotionEvent)) { |
| mouse_event_manager_->ClearDragHeuristicState(); |
| if (event_handler_will_reset_capturing_mouse_events_node_) |
| capturing_mouse_events_node_ = nullptr; |
| } |
| |
| if (RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled()) { |
| if (Page* page = frame_->GetPage()) { |
| if (mouse_event.GetType() == WebInputEvent::kMouseLeave && |
| mouse_event.button != WebPointerProperties::Button::kMiddle) { |
| page->GetAutoscrollController().StopMiddleClickAutoscroll(frame_); |
| } else { |
| page->GetAutoscrollController().HandleMouseMoveForMiddleClickAutoscroll( |
| frame_, mouse_event_manager_->LastKnownMousePositionGlobal(), |
| mouse_event.button == WebPointerProperties::Button::kMiddle); |
| } |
| } |
| } |
| |
| if (frame_set_being_resized_) { |
| return DispatchMousePointerEvent(WebInputEvent::kPointerMove, |
| 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; |
| } |
| |
| 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() |
| ? EventHandlingUtil::SubframeForTargetNode( |
| capturing_mouse_events_node_.Get()) |
| : EventHandlingUtil::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()); |
| last_mouse_move_event_subframe_->GetEventHandler() |
| .mouse_event_manager_->SetLastMousePositionAsUnknown(); |
| } |
| |
| if (new_subframe) { |
| // Update over/out state before passing the event to the subframe. |
| pointer_event_manager_->SendMouseAndPointerBoundaryEvents( |
| EffectiveMouseEventTargetNode(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()) { |
| EventHandler::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 = DispatchMousePointerEvent( |
| WebInputEvent::kPointerMove, 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.button == WebPointerProperties::Button::kMiddle); |
| } |
| } |
| |
| mouse_event_manager_->ReleaseMousePress(); |
| mouse_event_manager_->SetLastKnownMousePosition(mouse_event); |
| mouse_event_manager_->HandleSvgPanIfNeeded(true); |
| |
| if (frame_set_being_resized_) { |
| return mouse_event_manager_->SetMousePositionAndDispatchMouseEvent( |
| EffectiveMouseEventTargetNode(frame_set_being_resized_.Get()), String(), |
| EventTypeNames::mouseup, mouse_event); |
| } |
| |
| // Only relase scrollbar when left button up. |
| if (mouse_event.button == WebPointerProperties::Button::kLeft && |
| last_scrollbar_under_mouse_) { |
| mouse_event_manager_->InvalidateClick(); |
| last_scrollbar_under_mouse_->MouseUp(mouse_event); |
| return DispatchMousePointerEvent( |
| WebInputEvent::kPointerUp, 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() |
| ? EventHandlingUtil::SubframeForTargetNode( |
| capturing_mouse_events_node_.Get()) |
| : EventHandlingUtil::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 = std::make_unique<UserGestureIndicator>( |
| std::move(frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .last_mouse_down_user_gesture_token_)); |
| } else { |
| gesture_indicator = Frame::NotifyUserActivation(frame_); |
| } |
| |
| WebInputEventResult event_result = DispatchMousePointerEvent( |
| WebInputEvent::kPointerUp, 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()), |
| event.TimeStamp()); |
| } |
| |
| 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::EffectiveMouseEventTargetNode(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); |
| if (target) |
| mouse_wheel_event_manager_->ElementRemoved(target->ToNode()); |
| } |
| |
| WebInputEventResult EventHandler::DispatchMousePointerEvent( |
| const WebInputEvent::Type event_type, |
| Node* target_node, |
| const String& canvas_region_id, |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events) { |
| const auto& event_result = pointer_event_manager_->SendMousePointerEvent( |
| EffectiveMouseEventTargetNode(target_node), canvas_region_id, event_type, |
| mouse_event, coalesced_events); |
| return event_result; |
| } |
| |
| WebInputEventResult EventHandler::HandleWheelEvent( |
| const WebMouseWheelEvent& event) { |
| return mouse_wheel_event_manager_->HandleWheelEvent(event); |
| } |
| |
| 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()); |
| |
| if (targeted_event.Event().GetType() == WebInputEvent::kGestureShowPress) |
| last_show_press_timestamp_ = CurrentTimeTicks(); |
| |
| // 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; |
| |
| if (event.primary_pointer_type == WebPointerProperties::PointerType::kPen) |
| return false; |
| |
| return !event.TapAreaInRootFrame().IsEmpty(); |
| } |
| |
| void EventHandler::CacheTouchAdjustmentResult(uint32_t id, |
| FloatPoint adjusted_point) { |
| touch_adjustment_result_.unique_event_id = id; |
| touch_adjustment_result_.adjusted_point = adjusted_point; |
| } |
| |
| bool EventHandler::GestureCorrespondsToAdjustedTouch( |
| const WebGestureEvent& event) { |
| if (!RuntimeEnabledFeatures::UnifiedTouchAdjustmentEnabled()) |
| return false; |
| // Gesture events start with a GestureTapDown. If GestureTapDown's unique id |
| // matches stored adjusted touchstart event id, then we can use the stored |
| // result for following gesture event. |
| if (event.GetType() == WebInputEvent::kGestureTapDown) { |
| should_use_touch_event_adjusted_point_ = |
| (event.unique_touch_event_id != 0 && |
| event.unique_touch_event_id == |
| touch_adjustment_result_.unique_event_id); |
| } |
| |
| // Check if the adjusted point is in the gesture event tap rect. |
| // If not, should not use this touch point in following events. |
| if (should_use_touch_event_adjusted_point_) { |
| FloatRect tap_rect(FloatPoint(event.PositionInRootFrame()) - |
| FloatSize(event.TapAreaInRootFrame()) * 0.5, |
| FloatSize(event.TapAreaInRootFrame())); |
| should_use_touch_event_adjusted_point_ = |
| tap_rect.Contains(touch_adjustment_result_.adjusted_point); |
| } |
| |
| return should_use_touch_event_adjusted_point_; |
| } |
| |
| 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 = nullptr; |
| return false; |
| } |
| |
| IntPoint touch_center = |
| frame_->View()->ConvertToRootFrame(result.RoundedPointInMainFrame()); |
| IntRect touch_rect = frame_->View()->ConvertToRootFrame( |
| result.GetHitTestLocation().EnclosingIntRect()); |
| |
| 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()->ConvertToRootFrame(result.RoundedPointInMainFrame()); |
| IntRect touch_rect = frame_->View()->ConvertToRootFrame( |
| result.GetHitTestLocation().EnclosingIntRect()); |
| 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)); |
| } |
| |
| // 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.TimeStamp()); |
| |
| // 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( |
| EffectiveMouseEventTargetNode(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( |
| EffectiveMouseEventTargetNode(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 && |
| last_show_press_timestamp_) { |
| // 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 = CurrentTimeTicks() - last_show_press_timestamp_.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(), 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. |
| hit_type |= HitTestRequest::kReadOnly; |
| WebGestureEvent adjusted_event = gesture_event; |
| LayoutSize hit_rect_size; |
| if (ShouldApplyTouchAdjustment(gesture_event)) { |
| // If gesture_event unique id matches the stored touch event result, do |
| // point-base hit test. Otherwise add padding and do rect-based hit test. |
| if (GestureCorrespondsToAdjustedTouch(gesture_event)) { |
| adjusted_event.ApplyTouchAdjustment( |
| touch_adjustment_result_.adjusted_point); |
| } else { |
| hit_rect_size = GetHitTestRectForAdjustment( |
| LayoutSize(adjusted_event.TapAreaInRootFrame())); |
| if (!hit_rect_size.IsEmpty()) |
| hit_type |= HitTestRequest::kListBased; |
| } |
| } |
| |
| LocalFrame& root_frame = frame_->LocalFrameRoot(); |
| HitTestResult hit_test_result; |
| if (hit_rect_size.IsEmpty()) { |
| hit_test_result = root_frame.GetEventHandler().HitTestResultAtPoint( |
| LayoutPoint(adjusted_event.PositionInRootFrame()), hit_type); |
| } else { |
| LayoutPoint top_left(adjusted_event.PositionInRootFrame()); |
| top_left.Move(-hit_rect_size * 0.5f); |
| hit_test_result = root_frame.GetEventHandler().HitTestResultAtRect( |
| LayoutRect(top_left, hit_rect_size), hit_type); |
| } |
| |
| if (hit_test_result.IsRectBasedTest()) { |
| // Adjust the location of the gesture to the most likely nearby node, as |
| // appropriate for the type of 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 |
| LocalFrame* hit_frame = hit_test_result.InnerNodeFrame(); |
| if (!hit_frame) |
| hit_frame = frame_; |
| hit_test_result = EventHandlingUtil::HitTestResultInFrame( |
| hit_frame, |
| hit_frame->View()->ConvertFromRootFrame( |
| LayoutPoint(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()); |
| |
| if (ShouldApplyTouchAdjustment(gesture_event) && |
| (gesture_event.GetType() == WebInputEvent::kGestureTap || |
| gesture_event.GetType() == WebInputEvent::kGestureLongPress)) { |
| float adjusted_distance = FloatSize(adjusted_event.PositionInRootFrame() - |
| gesture_event.PositionInRootFrame()) |
| .DiagonalLength(); |
| UMA_HISTOGRAM_COUNTS_100("Event.Touch.TouchAdjustment.AdjustDistance", |
| static_cast<int>(adjusted_distance)); |
| } |
| return GestureEventWithHitTestResults(adjusted_event, hit_test_result); |
| } |
| |
| void EventHandler::ApplyTouchAdjustment(WebGestureEvent* gesture_event, |
| HitTestResult* hit_test_result) { |
| 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()->ConvertFromRootFrame(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_->ReleaseMousePress(); |
| LayoutPoint position_in_contents = |
| v->ConvertFromRootFrame(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( |
| EffectiveMouseEventTargetNode(target_node), EventTypeNames::contextmenu, |
| event, mev.GetHitTestResult().CanvasRegionId(), nullptr); |
| } |
| |
| static bool ShouldShowContextMenuAtSelection(const FrameSelection& selection) { |
| // TODO(editing-dev): The use of UpdateStyleAndLayoutIgnorePendingStylesheets |
| // needs to be audited. See http://crbug.com/590369 for more details. |
| selection.GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| const VisibleSelection& visible_selection = |
| selection.ComputeVisibleSelectionInDOMTree(); |
| 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 defined(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)) { |
| DCHECK(!doc->NeedsLayoutTreeUpdate()); |
| |
| IntRect first_rect = |
| 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 |
| // take the midpoint. |
| int y = (first_rect.MaxY() + first_rect.Y()) / 2; |
| location_in_root_frame = view->ConvertToRootFrame(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(kIncludeScrollbars).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 = |
| ToChromeClient(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; |
| |
| 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, |
| WebInputEvent::kNoModifiers, CurrentTimeTicks(), source_type); |
| |
| // 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(TimeDelta(), 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, FROM_HERE); |
| } |
| |
| bool EventHandler::CursorUpdatePending() { |
| return cursor_update_timer_.IsActive(); |
| } |
| |
| bool EventHandler::FakeMouseMovePending() const { |
| return mouse_event_manager_->FakeMouseMovePending(); |
| } |
| |
| void EventHandler::DispatchFakeMouseMoveEventSoon( |
| MouseEventManager::FakeMouseMoveReason fake_mouse_move_reason) { |
| mouse_event_manager_->DispatchFakeMouseMoveEventSoon(fake_mouse_move_reason); |
| } |
| |
| 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 (auto* layout_object = frame_->ContentLayoutObject()) { |
| if (LocalFrameView* view = frame_->View()) { |
| HitTestRequest request(HitTestRequest::kMove); |
| HitTestResult result(request, |
| view->ViewportToFrame( |
| mouse_event_manager_->LastKnownMousePosition())); |
| layout_object->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) { |
| // Only handle mouse left button as press. |
| if (mev.Event().button != WebPointerProperties::Button::kLeft) |
| return false; |
| |
| Scrollbar* scrollbar = mev.GetScrollbar(); |
| UpdateLastScrollbarUnderMouse(scrollbar, true); |
| |
| if (!scrollbar || !scrollbar->Enabled()) |
| return false; |
| 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::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 |