| /* |
| * 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/common/input/web_input_event.h" |
| #include "third_party/blink/public/common/input/web_mouse_wheel_event.h" |
| #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h" |
| #include "third_party/blink/public/platform/task_type.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/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/local_caret_rect.h" |
| #include "third_party/blink/renderer/core/editing/selection_controller.h" |
| #include "third_party/blink/renderer/core/editing/selection_template.h" |
| #include "third_party/blink/renderer/core/editing/text_affinity.h" |
| #include "third_party/blink/renderer/core/editing/visible_position.h" |
| #include "third_party/blink/renderer/core/editing/visible_selection.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/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/portal/html_portal_element.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_embedded_content.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/scroll/scroll_animator_base.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar.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/cursors.h" |
| #include "third_party/blink/renderer/platform/geometry/float_point.h" |
| #include "third_party/blink/renderer/platform/geometry/int_point.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/image.h" |
| #include "third_party/blink/renderer/platform/graphics/image_orientation.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.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/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/mojom/cursor_type.mojom-blink.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; |
| if (auto* shadow_root = DynamicTo<ShadowRoot>(target_node)) |
| return IsA<HTMLInputElement>(shadow_root->host()); |
| return false; |
| } |
| |
| IntPoint GetMiddleSelectionCaretOfPosition( |
| const PositionWithAffinity& position) { |
| const LocalCaretRect& local_caret_rect = LocalCaretRectOfPosition(position); |
| if (local_caret_rect.IsEmpty()) |
| return IntPoint(); |
| const IntRect rect = AbsoluteCaretBoundsOf(position); |
| // In a multiline edit, rect.MaxY() would end up on the next line, so |
| // take the midpoint in order to use this corner point directly. |
| if (local_caret_rect.layout_object->IsHorizontalWritingMode()) |
| return {rect.X(), (rect.Y() + rect.MaxY()) / 2}; |
| |
| // When text is vertical, rect.MaxX() would end up on the next line, so |
| // take the midpoint in order to use this corner point directly. |
| return {(rect.X() + rect.MaxX()) / 2, rect.Y()}; |
| } |
| |
| bool ContainsEvenAtEdge(const IntRect& rect, const IntPoint& point) { |
| return point.X() >= rect.X() && point.X() <= rect.MaxX() && |
| point.Y() >= rect.Y() && point.Y() <= rect.MaxY(); |
| } |
| |
| IntPoint DetermineHotSpot(const Image& image, |
| bool hot_spot_specified, |
| const IntPoint& specified_hot_spot) { |
| if (image.IsNull()) |
| return IntPoint(); |
| |
| IntRect image_rect = image.Rect(); |
| |
| // Hot spot must be inside cursor rectangle. |
| if (hot_spot_specified) { |
| if (image_rect.Contains(specified_hot_spot)) |
| return specified_hot_spot; |
| |
| return IntPoint(clampTo<int>(specified_hot_spot.X(), image_rect.X(), |
| image_rect.MaxX() - 1), |
| clampTo<int>(specified_hot_spot.Y(), image_rect.Y(), |
| image_rect.MaxY() - 1)); |
| } |
| |
| // If hot spot is not specified externally, it can be extracted from some |
| // image formats (e.g. .cur). |
| IntPoint intrinsic_hot_spot; |
| bool image_has_intrinsic_hot_spot = image.GetHotSpot(intrinsic_hot_spot); |
| if (image_has_intrinsic_hot_spot && image_rect.Contains(intrinsic_hot_spot)) |
| return intrinsic_hot_spot; |
| |
| // If neither is provided, use a default value of (0, 0). |
| return IntPoint(); |
| } |
| |
| } // namespace |
| |
| // 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 constexpr base::TimeDelta kCursorUpdateInterval = |
| base::TimeDelta::FromMilliseconds(20); |
| |
| static const int kMaximumCursorSize = 128; |
| |
| // The maximum size a cursor can be without falling back to the default cursor |
| // when intersecting browser native UI. |
| static const int kMaximumCursorSizeWithoutFallback = 32; |
| |
| // 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 base::TimeDelta kMinimumActiveInterval = |
| base::TimeDelta::FromSecondsD(0.15); |
| |
| EventHandler::EventHandler(LocalFrame& frame) |
| : frame_(frame), |
| selection_controller_(MakeGarbageCollected<SelectionController>(frame)), |
| hover_timer_(frame.GetTaskRunner(TaskType::kUserInteraction), |
| this, |
| &EventHandler::HoverTimerFired), |
| cursor_update_timer_( |
| frame.GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &EventHandler::CursorUpdateTimerFired), |
| should_only_fire_drag_over_event_(false), |
| event_handler_registry_( |
| frame_->IsLocalRoot() |
| ? MakeGarbageCollected<EventHandlerRegistry>(*frame_) |
| : &frame_->LocalFrameRoot().GetEventHandlerRegistry()), |
| scroll_manager_(MakeGarbageCollected<ScrollManager>(frame)), |
| mouse_event_manager_( |
| MakeGarbageCollected<MouseEventManager>(frame, *scroll_manager_)), |
| mouse_wheel_event_manager_( |
| MakeGarbageCollected<MouseWheelEventManager>(frame, |
| *scroll_manager_)), |
| keyboard_event_manager_( |
| MakeGarbageCollected<KeyboardEventManager>(frame, *scroll_manager_)), |
| pointer_event_manager_( |
| MakeGarbageCollected<PointerEventManager>(frame, |
| *mouse_event_manager_)), |
| gesture_manager_( |
| MakeGarbageCollected<GestureManager>(frame, |
| *scroll_manager_, |
| *mouse_event_manager_, |
| *pointer_event_manager_, |
| *selection_controller_)), |
| active_interval_timer_(frame.GetTaskRunner(TaskType::kUserInteraction), |
| this, |
| &EventHandler::ActiveIntervalTimerFired) { |
| if (RuntimeEnabledFeatures::FallbackCursorModeEnabled() && |
| frame.IsMainFrame()) { |
| fallback_cursor_event_manager_ = |
| MakeGarbageCollected<FallbackCursorEventManager>(frame); |
| } |
| } |
| |
| void EventHandler::Trace(Visitor* visitor) { |
| visitor->Trace(frame_); |
| visitor->Trace(selection_controller_); |
| visitor->Trace(capturing_mouse_events_element_); |
| visitor->Trace(capturing_subframe_element_); |
| 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(fallback_cursor_event_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; |
| capturing_mouse_events_element_ = nullptr; |
| capturing_subframe_element_ = 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; |
| 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; |
| LayoutBox* scrollable = LayoutBox::FindAutoscrollable( |
| layout_object, /*is_middle_click_autoscroll*/ true); |
| Page* page = frame_->GetPage(); |
| bool vertical_scroll_offset = false; |
| bool horizontal_scroll_offset = false; |
| if (page) { |
| ScrollOffset maximum_scroll_offset = |
| page->GetVisualViewport().MaximumScrollOffset(); |
| vertical_scroll_offset = maximum_scroll_offset.Height() > 0; |
| horizontal_scroll_offset = maximum_scroll_offset.Width() > 0; |
| } |
| controller->StartMiddleClickAutoscroll( |
| layout_object->GetFrame(), LastKnownMousePositionInRootFrame(), |
| mouse_event_manager_->LastKnownMouseScreenPosition(), |
| scrollable->HasScrollableOverflowY() || vertical_scroll_offset, |
| scrollable->HasScrollableOverflowX() || horizontal_scroll_offset); |
| mouse_event_manager_->InvalidateClick(); |
| } |
| |
| void EventHandler::PerformHitTest(const HitTestLocation& location, |
| 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(location, result); |
| } else { |
| frame_->ContentLayoutObject()->HitTest(location, result); |
| } |
| const HitTestRequest& request = result.GetHitTestRequest(); |
| if (!request.ReadOnly()) { |
| frame_->GetDocument()->UpdateHoverActiveState( |
| request.Active(), !request.Move(), result.InnerElement()); |
| } |
| } |
| |
| HitTestResult EventHandler::HitTestResultAtLocation( |
| const HitTestLocation& location, |
| HitTestRequest::HitTestRequestType hit_type, |
| const LayoutObject* stop_node, |
| bool no_lifecycle_update) { |
| TRACE_EVENT0("blink", "EventHandler::HitTestResultAtLocation"); |
| |
| // We always send HitTestResultAtLocation 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) { |
| HitTestLocation adjusted_location; |
| if (location.IsRectBasedTest()) { |
| DCHECK(location.IsRectilinear()); |
| if (hit_type & HitTestRequest::kHitTestVisualOverflow) { |
| // Apply ancestor transforms to location rect |
| PhysicalRect local_rect = location.BoundingBox(); |
| PhysicalRect main_frame_rect = |
| frame_view->GetLayoutView()->LocalToAncestorRect( |
| local_rect, main_view->GetLayoutView(), |
| kTraverseDocumentBoundaries); |
| adjusted_location = HitTestLocation(main_frame_rect); |
| } else { |
| // Don't apply ancestor transforms to bounding box |
| PhysicalOffset main_content_point = main_view->ConvertFromRootFrame( |
| frame_view->ConvertToRootFrame(location.BoundingBox().offset)); |
| adjusted_location = HitTestLocation( |
| PhysicalRect(main_content_point, location.BoundingBox().size)); |
| } |
| } else { |
| adjusted_location = HitTestLocation(main_view->ConvertFromRootFrame( |
| frame_view->ConvertToRootFrame(location.Point()))); |
| } |
| return main_frame.GetEventHandler().HitTestResultAtLocation( |
| adjusted_location, hit_type, stop_node, no_lifecycle_update); |
| } |
| } |
| } |
| // HitTestResultAtLocation 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, location); |
| PerformHitTest(location, 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(mojom::blink::ScrollDirection direction, |
| ScrollGranularity granularity, |
| Node* starting_node) { |
| return scroll_manager_->BubblingScroll( |
| direction, granularity, starting_node, |
| mouse_event_manager_->MousePressNode()); |
| } |
| |
| FloatPoint EventHandler::LastKnownMousePositionInRootFrame() const { |
| return frame_->GetPage()->GetVisualViewport().ViewportToRootFrame( |
| mouse_event_manager_->LastKnownMousePositionInViewport()); |
| } |
| |
| FloatPoint EventHandler::LastKnownMouseScreenPosition() const { |
| return mouse_event_manager_->LastKnownMouseScreenPosition(); |
| } |
| |
| 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) { |
| auto* html_input_element = DynamicTo<HTMLInputElement>(node); |
| return html_input_element && |
| html_input_element->type() == input_type_names::kImage; |
| } |
| |
| 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(DocumentUpdateReason::kInput); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | |
| HitTestRequest::kAllowChildFrameContent); |
| HitTestLocation location(view->ViewportToFrame( |
| mouse_event_manager_->LastKnownMousePositionInViewport())); |
| HitTestResult result(request, location); |
| layout_view->HitTest(location, result); |
| |
| if (LocalFrame* frame = result.InnerNodeFrame()) { |
| base::Optional<ui::Cursor> optional_cursor = |
| frame->GetEventHandler().SelectCursor(location, result); |
| if (optional_cursor.has_value()) { |
| view->SetCursor(optional_cursor.value()); |
| } |
| } |
| } |
| |
| bool EventHandler::ShouldShowResizeForNode(const Node* node, |
| const HitTestLocation& location) { |
| if (LayoutObject* layout_object = node->GetLayoutObject()) { |
| PaintLayer* layer = layout_object->EnclosingLayer(); |
| if (layer->GetScrollableArea() && |
| layer->GetScrollableArea()->IsPointInResizeControl( |
| RoundedIntPoint(location.Point()), 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_element_ && |
| 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); |
| } |
| |
| base::Optional<ui::Cursor> EventHandler::SelectCursor( |
| const HitTestLocation& location, |
| const HitTestResult& result) { |
| if (scroll_manager_->InResizeMode()) |
| return base::nullopt; |
| |
| Page* page = frame_->GetPage(); |
| if (!page) |
| return base::nullopt; |
| if (scroll_manager_->MiddleClickAutoscrollInProgress()) |
| return base::nullopt; |
| |
| if (result.GetScrollbar() && !result.GetScrollbar()->IsCustomScrollbar()) |
| return PointerCursor(); |
| |
| Node* node = result.InnerPossiblyPseudoNode(); |
| if (!node) |
| return SelectAutoCursor(result, node, IBeamCursor()); |
| |
| if (ShouldShowResizeForNode(node, location)) { |
| const LayoutBox* box = |
| node->GetLayoutObject()->EnclosingLayer()->GetLayoutBox(); |
| EResize resize = box->StyleRef().Resize(box->ContainingBlock()->StyleRef()); |
| switch (resize) { |
| case EResize::kVertical: |
| return NorthSouthResizeCursor(); |
| case EResize::kHorizontal: |
| return EastWestResizeCursor(); |
| case EResize::kBoth: |
| if (box->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| return SouthWestResizeCursor(); |
| } else { |
| return SouthEastResizeCursor(); |
| } |
| default: |
| return PointerCursor(); |
| } |
| } |
| |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| const ComputedStyle* style = layout_object ? layout_object->Style() : nullptr; |
| |
| if (layout_object) { |
| ui::Cursor override_cursor; |
| switch (layout_object->GetCursor(result.LocalPoint(), override_cursor)) { |
| case kSetCursorBasedOnStyle: |
| break; |
| case kSetCursor: |
| return override_cursor; |
| case kDoNotSetCursor: |
| return base::nullopt; |
| } |
| } |
| |
| 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(); |
| IntPoint hot_spot = (*cursors)[i].HotSpot(); |
| 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; |
| |
| // For large cursors below the max size, limit their ability to cover UI |
| // elements by removing them when they intersect with the visual viewport. |
| // TODO(csharrison): Consider sending a fallback cursor in the IPC to the |
| // browser process so we can do that calculation there instead. |
| if (size.Width() > kMaximumCursorSizeWithoutFallback || |
| size.Height() > kMaximumCursorSizeWithoutFallback) { |
| IntRect cursor_rect(IntPoint(location.RoundedPoint() - hot_spot), size); |
| IntRect visible_rect = page->GetVisualViewport().VisibleContentRect(); |
| if (!visible_rect.Contains(cursor_rect)) { |
| Deprecation::CountDeprecation( |
| &node->GetDocument(), |
| WebFeature::kCustomCursorIntersectsViewport); |
| continue; |
| } |
| } |
| |
| Image* image = cached_image->GetImage(); |
| // Ensure no overflow possible in calculations above. |
| if (scale < kMinimumCursorScale) |
| continue; |
| |
| // Convert from logical pixels to physical pixels. |
| hot_spot.Scale(scale, scale); |
| |
| ui::Cursor cursor(ui::mojom::blink::CursorType::kCustom); |
| cursor.set_custom_bitmap( |
| image ? image->AsSkBitmapForCurrentFrame(kRespectImageOrientation) |
| : SkBitmap()); |
| cursor.set_custom_hotspot( |
| DetermineHotSpot(*image, hot_spot_specified, hot_spot)); |
| cursor.set_image_scale_factor(scale); |
| return cursor; |
| } |
| } |
| |
| bool horizontal_text = !style || style->IsHorizontalWritingMode(); |
| const ui::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(); |
| } |
| |
| base::Optional<ui::Cursor> EventHandler::SelectAutoCursor( |
| const HitTestResult& result, |
| Node* node, |
| const ui::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, |
| const Vector<WebPointerEvent>& predicted_events) { |
| return pointer_event_manager_->HandlePointerEvent( |
| web_pointer_event, coalesced_events, predicted_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; |
| |
| capturing_mouse_events_element_ = nullptr; |
| mouse_event_manager_->HandleMousePressEventUpdateStates(mouse_event); |
| if (!frame_->View()) |
| return WebInputEventResult::kNotHandled; |
| |
| HitTestRequest request(HitTestRequest::kActive | |
| HitTestRequest::kRetargetForInert); |
| // Save the document point we generate in case the window coordinate is |
| // invalidated by what happens when we dispatch the event. |
| PhysicalOffset document_point = frame_->View()->ConvertFromRootFrame( |
| PhysicalOffset(FlooredIntPoint(mouse_event.PositionInRootFrame()))); |
| MouseEventWithHitTestResults mev = GetMouseEventTarget(request, mouse_event); |
| if (!mev.InnerNode()) { |
| mouse_event_manager_->InvalidateClick(); |
| return WebInputEventResult::kNotHandled; |
| } |
| |
| mouse_event_manager_->SetMousePressNode(mev.InnerNode()); |
| frame_->GetDocument()->SetSequentialFocusNavigationStartingPoint( |
| mev.InnerNode()); |
| |
| if (RuntimeEnabledFeatures::FallbackCursorModeEnabled()) { |
| // TODO(crbug.com/944575) Should support oopif. |
| DCHECK(frame_->LocalFrameRoot().IsMainFrame()); |
| frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .fallback_cursor_event_manager_->HandleMousePressEvent(mouse_event); |
| } |
| |
| LocalFrame* subframe = event_handling_util::GetTargetSubframe(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_element_ = mev.InnerElement(); |
| capturing_subframe_element_ = mev.InnerElement(); |
| } |
| |
| mouse_event_manager_->InvalidateClick(); |
| return result; |
| } |
| |
| if (event_handling_util::ShouldDiscardEventTargetingFrame(mev.Event(), |
| *frame_)) { |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| |
| LocalFrame::NotifyUserActivation( |
| frame_, |
| RuntimeEnabledFeatures::BrowserVerifiedUserActivationMouseEnabled()); |
| |
| 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.InnerElement(), mev.CanvasRegionId(), |
| mev.Event(), Vector<WebMouseEvent>(), 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 = event_handling_util::HitTestResultInFrame( |
| frame_, HitTestLocation(document_point), |
| HitTestRequest::kReadOnly | HitTestRequest::kRetargetForInert); |
| 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); |
| } |
| |
| if ((!RuntimeEnabledFeatures::MouseSubframeNoImplicitCaptureEnabled() && |
| event_result == WebInputEventResult::kNotHandled) || |
| mev.GetScrollbar()) { |
| mouse_event_manager_->SetCapturesDragging(true); |
| // Main frames don't implicitly capture mouse input on MouseDown, just |
| // subframes do (regardless of whether local or remote). |
| if (!frame_->IsMainFrame()) |
| CaptureMouseEventsToWidget(true); |
| } else { |
| mouse_event_manager_->SetCapturesDragging(false); |
| } |
| |
| if (PassMousePressEventToScrollbar(mev)) |
| event_result = WebInputEventResult::kHandledSystem; |
| |
| if (event_result == WebInputEventResult::kNotHandled) { |
| if (ShouldRefetchEventTarget(mev)) { |
| HitTestRequest read_only_request(HitTestRequest::kReadOnly | |
| HitTestRequest::kActive | |
| HitTestRequest::kRetargetForInert); |
| mev = frame_->GetDocument()->PerformMouseEventHitTest( |
| read_only_request, document_point, mouse_event); |
| } |
| 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, |
| const Vector<WebMouseEvent>& predicted_events) { |
| TRACE_EVENT0("blink", "EventHandler::handleMouseMoveEvent"); |
| HitTestResult hovered_node_result; |
| HitTestLocation location; |
| WebInputEventResult result = |
| HandleMouseMoveOrLeaveEvent(event, coalesced_events, predicted_events, |
| &hovered_node_result, &location); |
| |
| Page* page = frame_->GetPage(); |
| if (!page) |
| return result; |
| |
| if (PaintLayer* layer = |
| event_handling_util::LayerForNode(hovered_node_result.InnerNode())) { |
| if (ScrollableArea* layer_scrollable_area = |
| event_handling_util::AssociatedScrollableArea(layer)) |
| layer_scrollable_area->MouseMovedInContentArea(); |
| } |
| |
| hovered_node_result.SetToShadowHostIfInRestrictedShadowRoot(); |
| page->GetChromeClient().MouseDidMoveOverElement(*frame_, location, |
| hovered_node_result); |
| |
| if (RuntimeEnabledFeatures::FallbackCursorModeEnabled()) { |
| // TODO(crbug.com/944575) Should support oopif. |
| DCHECK(frame_->LocalFrameRoot().IsMainFrame()); |
| frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .fallback_cursor_event_manager_->HandleMouseMoveEvent(event); |
| } |
| |
| 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>(), |
| Vector<WebMouseEvent>(), nullptr, nullptr, false, |
| true); |
| pointer_event_manager_->RemoveLastMousePosition(); |
| } |
| |
| WebInputEventResult EventHandler::HandleMouseMoveOrLeaveEvent( |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events, |
| const Vector<WebMouseEvent>& predicted_events, |
| HitTestResult* hovered_node_result, |
| HitTestLocation* hit_test_location, |
| 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_->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(); |
| capturing_mouse_events_element_ = nullptr; |
| ReleaseMouseCaptureFromLocalRoot(); |
| } |
| |
| if (RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled()) { |
| if (Page* page = frame_->GetPage()) { |
| page->GetAutoscrollController().HandleMouseMoveForMiddleClickAutoscroll( |
| frame_, mouse_event_manager_->LastKnownMouseScreenPosition(), |
| 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, predicted_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 | HitTestRequest::kRetargetForInert; |
| 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); |
| HitTestLocation out_location((PhysicalOffset())); |
| MouseEventWithHitTestResults mev = MouseEventWithHitTestResults( |
| mouse_event, out_location, HitTestResult(request, out_location)); |
| |
| // 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.Active(), |
| !request.Move(), nullptr); |
| } else { |
| mev = GetMouseEventTarget(request, mouse_event); |
| } |
| |
| if (hovered_node_result) |
| *hovered_node_result = mev.GetHitTestResult(); |
| |
| if (hit_test_location) |
| *hit_test_location = mev.GetHitTestLocation(); |
| |
| 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; |
| bool is_portal = |
| mev.InnerElement() && IsA<HTMLPortalElement>(*mev.InnerElement()); |
| bool is_remote_frame = false; |
| LocalFrame* new_subframe = event_handling_util::GetTargetSubframe( |
| mev, capturing_mouse_events_element_, &is_remote_frame); |
| |
| // 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( |
| EffectiveMouseEventTargetElement(mev.InnerElement()), |
| 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, predicted_events, |
| new_subframe, hovered_node_result); |
| } |
| } else { |
| if (scrollbar && !mouse_event_manager_->MousePressed()) { |
| // Handle hover effects on platforms that support visual feedback on |
| // scrollbar hovering. |
| scrollbar->MouseMoved(mev.Event()); |
| } |
| LocalFrameView* view = frame_->View(); |
| if ((!is_remote_frame || is_portal) && view) { |
| base::Optional<ui::Cursor> optional_cursor = |
| SelectCursor(mev.GetHitTestLocation(), mev.GetHitTestResult()); |
| if (optional_cursor.has_value()) { |
| view->SetCursor(optional_cursor.value()); |
| } |
| } |
| } |
| |
| last_mouse_move_event_subframe_ = new_subframe; |
| |
| if (event_result != WebInputEventResult::kNotHandled) |
| return event_result; |
| |
| event_result = DispatchMousePointerEvent( |
| WebInputEvent::kPointerMove, mev.InnerElement(), mev.CanvasRegionId(), |
| mev.Event(), coalesced_events, predicted_events); |
| // TODO(crbug.com/346473): Since there is no default action for the mousemove |
| // event we should consider doing drag&drop even when js cancels the |
| // mouse move event. |
| // https://w3c.github.io/uievents/#event-type-mousemove |
| 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_) { |
| WebInputEventResult result = |
| mouse_event_manager_->SetMousePositionAndDispatchMouseEvent( |
| EffectiveMouseEventTargetElement(frame_set_being_resized_.Get()), |
| String(), event_type_names::kMouseup, mouse_event); |
| // crbug.com/1053385 release mouse capture only if there are no more mouse |
| // buttons depressed |
| if (MouseEvent::WebInputEventModifiersToButtons( |
| mouse_event.GetModifiers()) == 0) |
| ReleaseMouseCaptureFromLocalRoot(); |
| return result; |
| } |
| |
| if (last_scrollbar_under_mouse_) { |
| mouse_event_manager_->InvalidateClick(); |
| last_scrollbar_under_mouse_->MouseUp(mouse_event); |
| // crbug.com/1053385 release mouse capture only if there are no more mouse |
| // buttons depressed |
| if (MouseEvent::WebInputEventModifiersToButtons( |
| mouse_event.GetModifiers()) == 0) { |
| ReleaseMouseCaptureFromLocalRoot(); |
| } |
| return DispatchMousePointerEvent( |
| WebInputEvent::kPointerUp, mouse_event_manager_->GetElementUnderMouse(), |
| String(), mouse_event, Vector<WebMouseEvent>(), |
| Vector<WebMouseEvent>()); |
| } |
| |
| // Mouse events simulated from touch should not hit-test again. |
| DCHECK(!mouse_event.FromTouch()); |
| HitTestRequest::HitTestRequestType hit_type = |
| HitTestRequest::kRelease | HitTestRequest::kRetargetForInert; |
| HitTestRequest request(hit_type); |
| MouseEventWithHitTestResults mev = GetMouseEventTarget(request, mouse_event); |
| LocalFrame* subframe = event_handling_util::GetTargetSubframe( |
| mev, capturing_mouse_events_element_.Get()); |
| capturing_mouse_events_element_ = nullptr; |
| if (subframe) |
| return PassMouseReleaseEventToSubframe(mev, subframe); |
| |
| WebInputEventResult event_result = WebInputEventResult::kNotHandled; |
| |
| if (event_handling_util::ShouldDiscardEventTargetingFrame(mev.Event(), |
| *frame_)) { |
| event_result = WebInputEventResult::kHandledSuppressed; |
| } else { |
| event_result = DispatchMousePointerEvent( |
| WebInputEvent::kPointerUp, mev.InnerElement(), mev.CanvasRegionId(), |
| mev.Event(), Vector<WebMouseEvent>(), Vector<WebMouseEvent>(), |
| (GetSelectionController().HasExtendedSelection() && |
| IsSelectionOverLink(mev))); |
| } |
| scroll_manager_->ClearResizeScrollableArea(false); |
| |
| if (event_result == WebInputEventResult::kNotHandled) |
| event_result = mouse_event_manager_->HandleMouseReleaseEvent(mev); |
| |
| mouse_event_manager_->HandleMouseReleaseEventUpdateStates(); |
| |
| // crbug.com/1053385 release mouse capture only if there are no more mouse |
| // buttons depressed |
| if (MouseEvent::WebInputEventModifiersToButtons(mouse_event.GetModifiers()) == |
| 0) |
| ReleaseMouseCaptureFromLocalRoot(); |
| |
| return event_result; |
| } |
| |
| static LocalFrame* LocalFrameFromTargetNode(Node* target) { |
| auto* html_frame_base_element = DynamicTo<HTMLFrameElementBase>(target); |
| if (!html_frame_base_element) |
| return nullptr; |
| |
| // Cross-process drag and drop is not yet supported. |
| return DynamicTo<LocalFrame>(html_frame_base_element->ContentFrame()); |
| } |
| |
| WebInputEventResult EventHandler::UpdateDragAndDrop( |
| const WebMouseEvent& event, |
| DataTransfer* data_transfer) { |
| WebInputEventResult event_result = WebInputEventResult::kNotHandled; |
| |
| if (!frame_->View()) |
| return event_result; |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | |
| HitTestRequest::kRetargetForInert); |
| MouseEventWithHitTestResults mev = |
| event_handling_util::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, FloatPoint(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 |
| // web_tests/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>. |
| if (auto* target_frame = LocalFrameFromTargetNode(new_target)) { |
| 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(event_type_names::kDrag, |
| event); |
| } |
| event_result = mouse_event_manager_->DispatchDragEvent( |
| event_type_names::kDragenter, new_target, drag_target_, event, |
| data_transfer); |
| } |
| |
| if (auto* target_frame = LocalFrameFromTargetNode(drag_target_.Get())) { |
| event_result = target_frame->GetEventHandler().UpdateDragAndDrop( |
| event, data_transfer); |
| } else if (drag_target_) { |
| mouse_event_manager_->DispatchDragEvent(event_type_names::kDragleave, |
| 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 { |
| if (auto* target_frame = LocalFrameFromTargetNode(new_target)) { |
| 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(event_type_names::kDrag, |
| event); |
| } |
| event_result = mouse_event_manager_->DispatchDragEvent( |
| event_type_names::kDragover, 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) { |
| if (auto* target_frame = LocalFrameFromTargetNode(drag_target_.Get())) { |
| target_frame->GetEventHandler().CancelDragAndDrop(event, data_transfer); |
| } else if (drag_target_.Get()) { |
| if (mouse_event_manager_->GetDragState().drag_src_) { |
| mouse_event_manager_->DispatchDragSrcEvent(event_type_names::kDrag, |
| event); |
| } |
| mouse_event_manager_->DispatchDragEvent(event_type_names::kDragleave, |
| drag_target_.Get(), nullptr, event, |
| data_transfer); |
| } |
| ClearDragState(); |
| } |
| |
| WebInputEventResult EventHandler::PerformDragAndDrop( |
| const WebMouseEvent& event, |
| DataTransfer* data_transfer) { |
| WebInputEventResult result = WebInputEventResult::kNotHandled; |
| if (auto* target_frame = LocalFrameFromTargetNode(drag_target_.Get())) { |
| result = target_frame->GetEventHandler().PerformDragAndDrop(event, |
| data_transfer); |
| } else if (drag_target_.Get()) { |
| result = mouse_event_manager_->DispatchDragEvent( |
| event_type_names::kDrop, drag_target_.Get(), nullptr, event, |
| data_transfer); |
| } |
| ClearDragState(); |
| return result; |
| } |
| |
| void EventHandler::ClearDragState() { |
| scroll_manager_->StopAutoscroll(); |
| drag_target_ = nullptr; |
| capturing_mouse_events_element_ = nullptr; |
| ReleaseMouseCaptureFromLocalRoot(); |
| should_only_fire_drag_over_event_ = false; |
| } |
| |
| void EventHandler::AnimateSnapFling(base::TimeTicks monotonic_time) { |
| scroll_manager_->AnimateSnapFling(monotonic_time); |
| } |
| |
| void EventHandler::RecomputeMouseHoverStateIfNeeded() { |
| mouse_event_manager_->RecomputeMouseHoverStateIfNeeded(); |
| } |
| |
| void EventHandler::MarkHoverStateDirty() { |
| mouse_event_manager_->MarkHoverStateDirty(); |
| } |
| |
| Element* EventHandler::EffectiveMouseEventTargetElement( |
| Element* target_element) { |
| Element* new_element_under_mouse = target_element; |
| if (RuntimeEnabledFeatures::UnifiedPointerCaptureInBlinkEnabled()) { |
| if (pointer_event_manager_->GetMouseCaptureTarget()) |
| new_element_under_mouse = pointer_event_manager_->GetMouseCaptureTarget(); |
| } else { |
| if (capturing_mouse_events_element_) |
| new_element_under_mouse = capturing_mouse_events_element_.Get(); |
| } |
| return new_element_under_mouse; |
| } |
| |
| bool EventHandler::IsPointerIdActiveOnFrame(PointerId pointer_id, |
| LocalFrame* frame) const { |
| DCHECK(frame_ == &frame_->LocalFrameRoot() || frame_ == frame); |
| return pointer_event_manager_->IsPointerIdActiveOnFrame(pointer_id, frame); |
| } |
| |
| bool EventHandler::RootFrameTrackedActivePointerInCurrentFrame( |
| PointerId pointer_id) const { |
| return frame_ != &frame_->LocalFrameRoot() && |
| frame_->LocalFrameRoot().GetEventHandler().IsPointerIdActiveOnFrame( |
| pointer_id, frame_); |
| } |
| |
| bool EventHandler::IsPointerEventActive(PointerId pointer_id) { |
| return pointer_event_manager_->IsActive(pointer_id) || |
| RootFrameTrackedActivePointerInCurrentFrame(pointer_id); |
| } |
| |
| LocalFrame* EventHandler::DetermineActivePointerTrackerFrame( |
| PointerId pointer_id) const { |
| // If pointer_id is active on current |frame_|, pointer states are in |
| // current frame's PEM; otherwise, check if it's a touch-like pointer that |
| // have its active states in the local frame root's PEM. |
| if (IsPointerIdActiveOnFrame(pointer_id, frame_)) |
| return frame_; |
| if (RootFrameTrackedActivePointerInCurrentFrame(pointer_id)) |
| return &frame_->LocalFrameRoot(); |
| return nullptr; |
| } |
| |
| void EventHandler::SetPointerCapture(PointerId pointer_id, Element* target) { |
| // TODO(crbug.com/591387): This functionality should be per page not per |
| // frame. |
| LocalFrame* tracking_frame = DetermineActivePointerTrackerFrame(pointer_id); |
| |
| bool captured = |
| tracking_frame && |
| tracking_frame->GetEventHandler() |
| .pointer_event_manager_->SetPointerCapture(pointer_id, target); |
| |
| if (captured && pointer_id == PointerEventFactory::kMouseId) { |
| CaptureMouseEventsToWidget(true); |
| |
| // TODO(crbug.com/919908) This is a temporary approach to make pointer |
| // capture work across in-process frames while mouse subframe capture |
| // disabled. It's to experiment removing the frame capture logic. This |
| // must be re-write before the flag enabled. |
| if (RuntimeEnabledFeatures::MouseSubframeNoImplicitCaptureEnabled()) { |
| LocalFrame* frame = frame_; |
| LocalFrame* parent = DynamicTo<LocalFrame>(frame_->Tree().Parent()); |
| while (parent) { |
| Element* subframe_element = nullptr; |
| if (frame->OwnerLayoutObject() && |
| frame->OwnerLayoutObject()->GetNode()) { |
| subframe_element = |
| DynamicTo<Element>(frame->OwnerLayoutObject()->GetNode()); |
| } |
| |
| parent->GetEventHandler().capturing_subframe_element_ = |
| subframe_element; |
| frame = parent; |
| parent = DynamicTo<LocalFrame>(parent->Tree().Parent()); |
| } |
| } |
| } |
| } |
| |
| void EventHandler::ReleasePointerCapture(PointerId pointer_id, |
| Element* target) { |
| LocalFrame* tracking_frame = DetermineActivePointerTrackerFrame(pointer_id); |
| |
| bool released = |
| tracking_frame && |
| tracking_frame->GetEventHandler() |
| .pointer_event_manager_->ReleasePointerCapture(pointer_id, target); |
| |
| if (released && pointer_id == PointerEventFactory::kMouseId) { |
| CaptureMouseEventsToWidget(false); |
| |
| // TODO(crbug/919908) same as SetPointerCapture, this is temporary |
| // approach for removing mouse subframe capture. It must be re-write |
| // before enable the flag. |
| if (RuntimeEnabledFeatures::MouseSubframeNoImplicitCaptureEnabled()) { |
| LocalFrame* frame = frame_; |
| LocalFrame* parent = DynamicTo<LocalFrame>(frame_->Tree().Parent()); |
| while (parent) { |
| Element* subframe_element = nullptr; |
| if (frame->OwnerLayoutObject() && |
| frame->OwnerLayoutObject()->GetNode()) { |
| subframe_element = |
| DynamicTo<Element>(frame->OwnerLayoutObject()->GetNode()); |
| } |
| |
| parent->GetEventHandler().capturing_subframe_element_ = nullptr; |
| frame = parent; |
| parent = DynamicTo<LocalFrame>(parent->Tree().Parent()); |
| } |
| } |
| } |
| } |
| |
| void EventHandler::ReleaseMousePointerCapture() { |
| ReleaseMouseCaptureFromLocalRoot(); |
| } |
| |
| bool EventHandler::HasPointerCapture(PointerId pointer_id, |
| const Element* target) const { |
| if (LocalFrame* tracking_frame = |
| DetermineActivePointerTrackerFrame(pointer_id)) { |
| return tracking_frame->GetEventHandler() |
| .pointer_event_manager_->HasPointerCapture(pointer_id, target); |
| } |
| return false; |
| } |
| |
| void EventHandler::ElementRemoved(Element* target) { |
| pointer_event_manager_->ElementRemoved(target); |
| if (target) |
| mouse_wheel_event_manager_->ElementRemoved(target); |
| } |
| |
| void EventHandler::ResetMousePositionForPointerUnlock() { |
| pointer_event_manager_->RemoveLastMousePosition(); |
| } |
| |
| bool EventHandler::LongTapShouldInvokeContextMenu() { |
| return gesture_manager_->LongTapShouldInvokeContextMenu(); |
| } |
| |
| WebInputEventResult EventHandler::DispatchMousePointerEvent( |
| const WebInputEvent::Type event_type, |
| Element* target_element, |
| const String& canvas_region_id, |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events, |
| const Vector<WebMouseEvent>& predicted_events, |
| bool skip_click_dispatch) { |
| const auto& event_result = pointer_event_manager_->SendMousePointerEvent( |
| EffectiveMouseEventTargetElement(target_element), canvas_region_id, |
| event_type, mouse_event, coalesced_events, predicted_events, |
| skip_click_dispatch); |
| return event_result; |
| } |
| |
| WebInputEventResult EventHandler::HandleWheelEvent( |
| const WebMouseWheelEvent& event) { |
| return mouse_wheel_event_manager_->HandleWheelEvent(event); |
| } |
| |
| // TODO(crbug.com/665924): This function bypasses all Handle*Event path. |
| // It should be using that flow instead of creating/sending events directly. |
| WebInputEventResult EventHandler::HandleTargetedMouseEvent( |
| Element* target, |
| const WebMouseEvent& event, |
| const AtomicString& mouse_event_type, |
| const Vector<WebMouseEvent>& coalesced_events, |
| const Vector<WebMouseEvent>& predicted_events, |
| const String& canvas_region_id) { |
| mouse_event_manager_->SetClickCount(event.click_count); |
| return pointer_event_manager_->DirectDispatchMousePointerEvent( |
| target, event, mouse_event_type, coalesced_events, predicted_events, |
| canvas_region_id); |
| } |
| |
| 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_ = base::TimeTicks::Now(); |
| |
| // 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) { |
| if (event_handling_util::ShouldDiscardEventTargetingFrame( |
| targeted_event.Event(), *frame_)) { |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| 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); |
| } |
| |
| 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 HitTestLocation& location, |
| 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(location.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(RoundedIntPoint(location.Point())); |
| IntRect touch_rect = |
| frame_->View()->ConvertToRootFrame(location.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 HitTestLocation& location, |
| const HitTestResult& result, |
| IntPoint& target_point, |
| Node*& target_node) { |
| DCHECK(location.IsRectBasedTest()); |
| IntPoint touch_center = |
| frame_->View()->ConvertToRootFrame(RoundedIntPoint(location.Point())); |
| IntRect touch_rect = |
| frame_->View()->ConvertToRootFrame(location.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. This logic is |
| // different than the mouse case because mice send MouseLeave events to frames |
| // as they're exited. With gestures or manual applications, a single event |
| // conceptually both 'leaves' whatever frame currently had hover and enters a |
| // new frame so we need to update state in the old frame chain as well. |
| void EventHandler::UpdateCrossFrameHoverActiveState(bool is_active, |
| 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 = DynamicTo<LocalFrame>(parent_frame); |
| } |
| |
| 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) { |
| wtf_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]; |
| |
| auto* owner = To<HTMLFrameOwnerElement>(old_hover_element_in_cur_doc); |
| LocalFrame* old_hover_frame = |
| DynamicTo<LocalFrame>(owner->ContentFrame()); |
| if (!old_hover_frame) |
| break; |
| |
| 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(is_active, |
| /*update_active_chain=*/true, nullptr); |
| } |
| } |
| } |
| |
| // Recursively set the new active/hover states on every frame in the chain of |
| // innerElement. |
| frame_->GetDocument()->UpdateHoverActiveState(is_active, |
| /*update_active_chain=*/true, |
| 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 = DynamicTo<LocalFrame>(parent_frame); |
| } |
| |
| wtf_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_->GetElementUnderMouse(); |
| if (!last_node_under_tap) |
| break; |
| |
| LocalFrame* next_exited_frame_in_document = nullptr; |
| if (auto* owner = DynamicTo<HTMLFrameOwnerElement>(last_node_under_tap)) { |
| next_exited_frame_in_document = |
| DynamicTo<LocalFrame>(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 |
| wtf_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_->SetElementUnderMouse( |
| EffectiveMouseEventTargetElement(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 (auto* parent_local_frame = DynamicTo<LocalFrame>(parent_frame)) { |
| parent_local_frame->GetEventHandler() |
| .mouse_event_manager_->SetElementUnderMouse( |
| EffectiveMouseEventTargetElement(To<HTMLFrameOwnerElement>( |
| 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()); |
| base::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 = |
| base::TimeTicks::Now() - 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()) { |
| UpdateCrossFrameHoverActiveState( |
| request.Active(), |
| 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, 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::kRetargetForInert; |
| 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( |
| *frame_, LayoutSize(adjusted_event.TapAreaInRootFrame())); |
| if (!hit_rect_size.IsEmpty()) |
| hit_type |= HitTestRequest::kListBased; |
| } |
| } |
| |
| HitTestLocation location; |
| LocalFrame& root_frame = frame_->LocalFrameRoot(); |
| HitTestResult hit_test_result; |
| if (hit_rect_size.IsEmpty()) { |
| location = |
| HitTestLocation(FloatPoint(adjusted_event.PositionInRootFrame())); |
| hit_test_result = root_frame.GetEventHandler().HitTestResultAtLocation( |
| location, hit_type); |
| } else { |
| PhysicalOffset top_left = PhysicalOffset::FromFloatPointRound( |
| FloatPoint(adjusted_event.PositionInRootFrame())); |
| top_left -= PhysicalOffset(hit_rect_size * 0.5f); |
| location = HitTestLocation(PhysicalRect(top_left, hit_rect_size)); |
| hit_test_result = root_frame.GetEventHandler().HitTestResultAtLocation( |
| location, hit_type); |
| } |
| |
| if (location.IsRectBasedTest()) { |
| // Adjust the location of the gesture to the most likely nearby node, as |
| // appropriate for the type of event. |
| ApplyTouchAdjustment(&adjusted_event, location, &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_; |
| location = |
| HitTestLocation(FloatPoint(adjusted_event.PositionInRootFrame())); |
| hit_test_result = root_frame.GetEventHandler().HitTestResultAtLocation( |
| location, |
| (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(!location.IsRectBasedTest()); |
| |
| return GestureEventWithHitTestResults(adjusted_event, location, |
| hit_test_result); |
| } |
| |
| void EventHandler::ApplyTouchAdjustment(WebGestureEvent* gesture_event, |
| HitTestLocation& location, |
| 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( |
| location, *hit_test_result, adjusted_point, adjusted_node); |
| break; |
| case WebInputEvent::kGestureLongPress: |
| case WebInputEvent::kGestureLongTap: |
| case WebInputEvent::kGestureTwoFingerTap: |
| adjusted = BestContextMenuNodeForHitTestResult( |
| location, *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) { |
| PhysicalOffset point(frame_->View()->ConvertFromRootFrame(adjusted_point)); |
| DCHECK(location.ContainsPoint(FloatPoint(point))); |
| DCHECK(location.IsRectBasedTest()); |
| location = hit_test_result->ResolveRectBasedTest(adjusted_node, point); |
| gesture_event->ApplyTouchAdjustment( |
| gfx::PointF(adjusted_point.X(), adjusted_point.Y())); |
| } |
| } |
| |
| WebInputEventResult EventHandler::SendContextMenuEvent( |
| const WebMouseEvent& event, |
| Element* override_target_element) { |
| 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(); |
| if (last_scrollbar_under_mouse_) |
| last_scrollbar_under_mouse_->MouseUp(event); |
| |
| PhysicalOffset position_in_contents( |
| v->ConvertFromRootFrame(FlooredIntPoint(event.PositionInRootFrame()))); |
| HitTestRequest request(HitTestRequest::kActive | |
| HitTestRequest::kRetargetForInert); |
| 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()->UpdateStyleAndLayout( |
| DocumentUpdateReason::kContextMenu); |
| |
| GetSelectionController().UpdateSelectionForContextMenuEvent( |
| mev, position_in_contents); |
| |
| Element* target_element = |
| override_target_element ? override_target_element : mev.InnerElement(); |
| return mouse_event_manager_->DispatchMouseEvent( |
| EffectiveMouseEventTargetElement(target_element), |
| event_type_names::kContextmenu, event, |
| mev.GetHitTestResult().CanvasRegionId(), nullptr, nullptr); |
| } |
| |
| static bool ShouldShowContextMenuAtSelection(const FrameSelection& selection) { |
| // TODO(editing-dev): The use of UpdateStyleAndLayout |
| // needs to be audited. See http://crbug.com/590369 for more details. |
| selection.GetDocument().UpdateStyleAndLayout( |
| DocumentUpdateReason::kContextMenu); |
| |
| 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; |
| |
| 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()); |
| |
| // Enclose the selection rect fully between the handles. If the handles are |
| // on the same line, the selection rect is empty. |
| const SelectionInDOMTree& visible_selection = |
| selection.ComputeVisibleSelectionInDOMTree().AsSelection(); |
| const PositionWithAffinity start_position( |
| visible_selection.ComputeStartPosition(), visible_selection.Affinity()); |
| const IntPoint start_point = |
| GetMiddleSelectionCaretOfPosition(start_position); |
| const PositionWithAffinity end_position( |
| visible_selection.ComputeEndPosition(), visible_selection.Affinity()); |
| const IntPoint end_point = GetMiddleSelectionCaretOfPosition(end_position); |
| |
| int left = std::min(start_point.X(), end_point.X()); |
| int top = std::min(start_point.Y(), end_point.Y()); |
| int right = std::max(start_point.X(), end_point.X()); |
| int bottom = std::max(start_point.Y(), end_point.Y()); |
| |
| // Intersect the selection rect and the visible bounds of focused_element. |
| if (focused_element) { |
| IntRect clipped_rect = view->ViewportToFrame( |
| focused_element->VisibleBoundsInVisualViewport()); |
| left = std::max(clipped_rect.X(), left); |
| top = std::max(clipped_rect.Y(), top); |
| right = std::min(clipped_rect.MaxX(), right); |
| bottom = std::min(clipped_rect.MaxY(), bottom); |
| } |
| IntRect selection_rect = IntRect(left, top, right - left, bottom - top); |
| |
| if (ContainsEvenAtEdge(selection_rect, start_point)) { |
| location_in_root_frame = view->ConvertToRootFrame(start_point); |
| } else if (ContainsEvenAtEdge(selection_rect, end_point)) { |
| location_in_root_frame = view->ConvertToRootFrame(end_point); |
| } else { |
| location_in_root_frame = |
| view->ConvertToRootFrame(selection_rect.Center()); |
| } |
| } 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( |
| 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 | |
| HitTestRequest::kRetargetForInert); |
| HitTestLocation location(location_in_root_frame); |
| HitTestResult result(request, location); |
| result.SetInnerNode(target_node); |
| doc->UpdateHoverActiveState(request.Active(), !request.Move(), |
| 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, |
| gfx::PointF(location_in_root_frame.X(), location_in_root_frame.Y()), |
| gfx::PointF(global_position.X(), global_position.Y()), |
| WebPointerProperties::Button::kNoButton, /* clickCount */ 0, |
| WebInputEvent::kNoModifiers, base::TimeTicks::Now(), 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(base::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(); |
| } |
| |
| void EventHandler::SetResizingFrameSet(HTMLFrameSetElement* frame_set) { |
| CaptureMouseEventsToWidget(true); |
| 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 | |
| HitTestRequest::kRetargetForInert); |
| HitTestLocation location(view->ViewportToFrame( |
| mouse_event_manager_->LastKnownMousePositionInViewport())); |
| HitTestResult result(request, location); |
| layout_object->HitTest(location, result); |
| frame_->GetDocument()->UpdateHoverActiveState( |
| request.Active(), !request.Move(), 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 | |
| HitTestRequest::kRetargetForInert); |
| frame_->GetDocument()->UpdateHoverActiveState( |
| request.Active(), !request.Move(), 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()); |
| } |
| |
| bool EventHandler::HandleFallbackCursorModeBackEvent() { |
| DCHECK(RuntimeEnabledFeatures::FallbackCursorModeEnabled()); |
| // TODO(crbug.com/944575) Should support oopif. |
| DCHECK(frame_->LocalFrameRoot().IsMainFrame()); |
| |
| return fallback_cursor_event_manager_->HandleKeyBackEvent(); |
| } |
| |
| 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 | |
| HitTestRequest::kRetargetForInert); |
| MouseEventWithHitTestResults mev = |
| event_handling_util::PerformMouseEventHitTest(frame_, request, event); |
| |
| if (auto* target_frame = LocalFrameFromTargetNode(mev.InnerNode())) { |
| 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. |
| auto* keyboard_event = DynamicTo<KeyboardEvent>(underlying_event); |
| DCHECK(!keyboard_event || |
| keyboard_event->type() == event_type_names::kKeypress); |
| |
| 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) { |
| // Do not pass the mouse press to scrollbar if scrollbar pressed. If the |
| // user's left button is down, then the cursor moves outside the scrollbar |
| // and presses the middle button , we should not clear |
| // last_scrollbar_under_mouse_. |
| if (last_scrollbar_under_mouse_ && |
| last_scrollbar_under_mouse_->PressedPart() != ScrollbarPart::kNoPart) { |
| return false; |
| } |
| |
| Scrollbar* scrollbar = mev.GetScrollbar(); |
| UpdateLastScrollbarUnderMouse(scrollbar, true); |
| |
| if (!scrollbar || !scrollbar->Enabled()) |
| return false; |
| scrollbar->MouseDown(mev.Event()); |
| if (scrollbar->PressedPart() == ScrollbarPart::kThumbPart) |
| CaptureMouseEventsToWidget(true); |
| 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, |
| const Vector<WebMouseEvent>& predicted_events, |
| LocalFrame* subframe, |
| HitTestResult* hovered_node, |
| HitTestLocation* hit_test_location) { |
| if (mouse_event_manager_->MouseDownMayStartDrag()) |
| return WebInputEventResult::kNotHandled; |
| WebInputEventResult result = |
| subframe->GetEventHandler().HandleMouseMoveOrLeaveEvent( |
| mev.Event(), coalesced_events, predicted_events, hovered_node, |
| hit_test_location); |
| if (result != WebInputEventResult::kNotHandled) |
| return result; |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| WebInputEventResult EventHandler::PassMouseReleaseEventToSubframe( |
| MouseEventWithHitTestResults& mev, |
| LocalFrame* subframe) { |
| return subframe->GetEventHandler().HandleMouseReleaseEvent(mev.Event()); |
| } |
| |
| void EventHandler::CaptureMouseEventsToWidget(bool capture) { |
| if (!frame_->IsLocalRoot()) { |
| frame_->LocalFrameRoot().GetEventHandler().CaptureMouseEventsToWidget( |
| capture); |
| return; |
| } |
| |
| if (capture == is_widget_capturing_mouse_events_) |
| return; |
| |
| frame_->LocalFrameRoot().Client()->SetMouseCapture(capture); |
| is_widget_capturing_mouse_events_ = capture; |
| } |
| |
| MouseEventWithHitTestResults EventHandler::GetMouseEventTarget( |
| const HitTestRequest& request, |
| const WebMouseEvent& event) { |
| PhysicalOffset document_point = |
| event_handling_util::ContentPointFromRootFrame( |
| frame_, FloatPoint(event.PositionInRootFrame())); |
| |
| // TODO(eirage): This does not handle chorded buttons yet. |
| if (RuntimeEnabledFeatures::UnifiedPointerCaptureInBlinkEnabled() && |
| event.GetType() != WebInputEvent::kMouseDown) { |
| HitTestResult result(request, HitTestLocation(document_point)); |
| |
| Element* capture_target; |
| if (event_handling_util::SubframeForTargetNode( |
| capturing_subframe_element_)) { |
| capture_target = capturing_subframe_element_; |
| result.SetIsOverEmbeddedContentView(true); |
| } else { |
| capture_target = pointer_event_manager_->GetMouseCaptureTarget(); |
| } |
| |
| if (capture_target) { |
| LayoutObject* layout_object = capture_target->GetLayoutObject(); |
| PhysicalOffset local_point = |
| layout_object ? layout_object->AbsoluteToLocalPoint(document_point) |
| : document_point; |
| result.SetNodeAndPosition(capture_target, local_point); |
| |
| result.SetScrollbar(last_scrollbar_under_mouse_); |
| result.SetURLElement(capture_target->EnclosingLinkEventParentOrSelf()); |
| |
| if (!request.ReadOnly()) { |
| frame_->GetDocument()->UpdateHoverActiveState( |
| request.Active(), !request.Move(), result.InnerElement()); |
| } |
| |
| return MouseEventWithHitTestResults( |
| event, HitTestLocation(document_point), result); |
| } |
| } |
| return frame_->GetDocument()->PerformMouseEventHitTest(request, |
| document_point, event); |
| } |
| |
| void EventHandler::ReleaseMouseCaptureFromLocalRoot() { |
| CaptureMouseEventsToWidget(false); |
| |
| if (RuntimeEnabledFeatures::UnifiedPointerCaptureInBlinkEnabled()) { |
| frame_->LocalFrameRoot() |
| .GetEventHandler() |
| .ReleaseMouseCaptureFromCurrentFrame(); |
| } |
| } |
| |
| void EventHandler::ReleaseMouseCaptureFromCurrentFrame() { |
| if (LocalFrame* subframe = event_handling_util::SubframeForTargetNode( |
| capturing_subframe_element_)) |
| subframe->GetEventHandler().ReleaseMouseCaptureFromCurrentFrame(); |
| pointer_event_manager_->ReleaseMousePointerCapture(); |
| capturing_subframe_element_ = nullptr; |
| } |
| |
| void EventHandler::SetIsFallbackCursorModeOn(bool is_on) { |
| DCHECK(RuntimeEnabledFeatures::FallbackCursorModeEnabled()); |
| // TODO(crbug.com/944575) Should support oopif. |
| DCHECK(frame_->IsMainFrame()); |
| fallback_cursor_event_manager_->SetIsFallbackCursorModeOn(is_on); |
| } |
| |
| } // namespace blink |