| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/input/PointerEventManager.h" |
| |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/FlatTreeTraversal.h" |
| #include "core/dom/UserGestureIndicator.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/frame/EventHandlerRegistry.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/canvas/HTMLCanvasElement.h" |
| #include "core/input/EventHandler.h" |
| #include "core/input/EventHandlingUtil.h" |
| #include "core/input/MouseEventManager.h" |
| #include "core/input/TouchActionUtil.h" |
| #include "core/layout/HitTestCanvasResult.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/Page.h" |
| #include "platform/wtf/AutoReset.h" |
| #include "public/platform/WebTouchEvent.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| size_t ToPointerTypeIndex(WebPointerProperties::PointerType t) { |
| return static_cast<size_t>(t); |
| } |
| bool HasPointerEventListener(const EventHandlerRegistry& registry) { |
| return registry.HasEventHandlers(EventHandlerRegistry::kPointerEvent); |
| } |
| |
| } // namespace |
| |
| PointerEventManager::PointerEventManager(LocalFrame& frame, |
| MouseEventManager& mouse_event_manager) |
| : frame_(frame), |
| touch_event_manager_(new TouchEventManager(frame)), |
| mouse_event_manager_(mouse_event_manager) { |
| Clear(); |
| } |
| |
| void PointerEventManager::Clear() { |
| for (auto& entry : prevent_mouse_event_for_pointer_type_) |
| entry = false; |
| touch_event_manager_->Clear(); |
| scroll_capable_pointers_canceled_ = false; |
| pointer_event_factory_.Clear(); |
| touch_ids_for_canceled_pointerdowns_.clear(); |
| node_under_pointer_.clear(); |
| pointer_capture_target_.clear(); |
| pending_pointer_capture_target_.clear(); |
| user_gesture_holder_.reset(); |
| dispatching_pointer_id_ = 0; |
| } |
| |
| void PointerEventManager::Trace(blink::Visitor* visitor) { |
| visitor->Trace(frame_); |
| visitor->Trace(node_under_pointer_); |
| visitor->Trace(pointer_capture_target_); |
| visitor->Trace(pending_pointer_capture_target_); |
| visitor->Trace(touch_event_manager_); |
| visitor->Trace(mouse_event_manager_); |
| } |
| |
| PointerEventManager::PointerEventBoundaryEventDispatcher:: |
| PointerEventBoundaryEventDispatcher( |
| PointerEventManager* pointer_event_manager, |
| PointerEvent* pointer_event) |
| : pointer_event_manager_(pointer_event_manager), |
| pointer_event_(pointer_event) {} |
| |
| void PointerEventManager::PointerEventBoundaryEventDispatcher::DispatchOut( |
| EventTarget* target, |
| EventTarget* related_target) { |
| Dispatch(target, related_target, EventTypeNames::pointerout, false); |
| } |
| |
| void PointerEventManager::PointerEventBoundaryEventDispatcher::DispatchOver( |
| EventTarget* target, |
| EventTarget* related_target) { |
| Dispatch(target, related_target, EventTypeNames::pointerover, false); |
| } |
| |
| void PointerEventManager::PointerEventBoundaryEventDispatcher::DispatchLeave( |
| EventTarget* target, |
| EventTarget* related_target, |
| bool check_for_listener) { |
| Dispatch(target, related_target, EventTypeNames::pointerleave, |
| check_for_listener); |
| } |
| |
| void PointerEventManager::PointerEventBoundaryEventDispatcher::DispatchEnter( |
| EventTarget* target, |
| EventTarget* related_target, |
| bool check_for_listener) { |
| Dispatch(target, related_target, EventTypeNames::pointerenter, |
| check_for_listener); |
| } |
| |
| AtomicString |
| PointerEventManager::PointerEventBoundaryEventDispatcher::GetLeaveEvent() { |
| return EventTypeNames::pointerleave; |
| } |
| |
| AtomicString |
| PointerEventManager::PointerEventBoundaryEventDispatcher::GetEnterEvent() { |
| return EventTypeNames::pointerenter; |
| } |
| |
| void PointerEventManager::PointerEventBoundaryEventDispatcher::Dispatch( |
| EventTarget* target, |
| EventTarget* related_target, |
| const AtomicString& type, |
| bool check_for_listener) { |
| pointer_event_manager_->DispatchPointerEvent( |
| target, |
| pointer_event_manager_->pointer_event_factory_.CreatePointerBoundaryEvent( |
| pointer_event_, type, related_target), |
| check_for_listener); |
| } |
| |
| WebInputEventResult PointerEventManager::DispatchPointerEvent( |
| EventTarget* target, |
| PointerEvent* pointer_event, |
| bool check_for_listener) { |
| if (!target) |
| return WebInputEventResult::kNotHandled; |
| |
| // Set whether node under pointer has received pointerover or not. |
| const int pointer_id = pointer_event->pointerId(); |
| |
| const AtomicString& event_type = pointer_event->type(); |
| if ((event_type == EventTypeNames::pointerout || |
| event_type == EventTypeNames::pointerover) && |
| node_under_pointer_.Contains(pointer_id)) { |
| EventTarget* target_under_pointer = |
| node_under_pointer_.at(pointer_id).target; |
| if (target_under_pointer == target) { |
| node_under_pointer_.Set( |
| pointer_id, |
| EventTargetAttributes(target_under_pointer, |
| event_type == EventTypeNames::pointerover)); |
| } |
| } |
| |
| if (!frame_ || !frame_->GetPage() || |
| !HasPointerEventListener(frame_->GetPage()->GetEventHandlerRegistry())) |
| return WebInputEventResult::kNotHandled; |
| |
| if (event_type == EventTypeNames::pointerdown) { |
| Node* node = target->ToNode(); |
| if (node && IsHTMLCanvasElement(*node) && |
| ToHTMLCanvasElement(*node).NeedsUnbufferedInputEvents()) { |
| frame_->GetChromeClient().RequestUnbufferedInputEvents(frame_); |
| } |
| } |
| |
| if (!check_for_listener || target->HasEventListeners(event_type)) { |
| UseCounter::Count(frame_, WebFeature::kPointerEventDispatch); |
| if (event_type == EventTypeNames::pointerdown) |
| UseCounter::Count(frame_, WebFeature::kPointerEventDispatchPointerDown); |
| |
| DCHECK(!dispatching_pointer_id_); |
| AutoReset<int> dispatch_holder(&dispatching_pointer_id_, pointer_id); |
| DispatchEventResult dispatch_result = target->DispatchEvent(pointer_event); |
| return EventHandlingUtil::ToWebInputEventResult(dispatch_result); |
| } |
| return WebInputEventResult::kNotHandled; |
| } |
| |
| EventTarget* PointerEventManager::GetEffectiveTargetForPointerEvent( |
| EventTarget* target, |
| int pointer_id) { |
| if (EventTarget* capturing_target = GetCapturingNode(pointer_id)) |
| return capturing_target; |
| return target; |
| } |
| |
| void PointerEventManager::SendMouseAndPointerBoundaryEvents( |
| Node* entered_node, |
| const String& canvas_region_id, |
| const WebMouseEvent& mouse_event) { |
| // Mouse event type does not matter as this pointerevent will only be used |
| // to create boundary pointer events and its type will be overridden in |
| // |sendBoundaryEvents| function. |
| PointerEvent* dummy_pointer_event = pointer_event_factory_.Create( |
| EventTypeNames::mousedown, mouse_event, Vector<WebMouseEvent>(), |
| frame_->GetDocument()->domWindow()); |
| |
| // TODO(crbug/545647): This state should reset with pointercancel too. |
| // This function also gets called for compat mouse events of touch at this |
| // stage. So if the event is not frame boundary transition it is only a |
| // compatibility mouse event and we do not need to change pointer event |
| // behavior regarding preventMouseEvent state in that case. |
| if (dummy_pointer_event->buttons() == 0 && dummy_pointer_event->isPrimary()) { |
| prevent_mouse_event_for_pointer_type_[ToPointerTypeIndex( |
| mouse_event.pointer_type)] = false; |
| } |
| |
| ProcessCaptureAndPositionOfPointerEvent(dummy_pointer_event, entered_node, |
| canvas_region_id, &mouse_event); |
| } |
| |
| void PointerEventManager::SendBoundaryEvents(EventTarget* exited_target, |
| EventTarget* entered_target, |
| PointerEvent* pointer_event) { |
| PointerEventBoundaryEventDispatcher boundary_event_dispatcher(this, |
| pointer_event); |
| boundary_event_dispatcher.SendBoundaryEvents(exited_target, entered_target); |
| } |
| |
| void PointerEventManager::SetNodeUnderPointer(PointerEvent* pointer_event, |
| EventTarget* target) { |
| if (node_under_pointer_.Contains(pointer_event->pointerId())) { |
| EventTargetAttributes node = |
| node_under_pointer_.at(pointer_event->pointerId()); |
| if (!target) { |
| node_under_pointer_.erase(pointer_event->pointerId()); |
| } else if (target != |
| node_under_pointer_.at(pointer_event->pointerId()).target) { |
| node_under_pointer_.Set(pointer_event->pointerId(), |
| EventTargetAttributes(target, false)); |
| } |
| SendBoundaryEvents(node.target, target, pointer_event); |
| } else if (target) { |
| node_under_pointer_.insert(pointer_event->pointerId(), |
| EventTargetAttributes(target, false)); |
| SendBoundaryEvents(nullptr, target, pointer_event); |
| } |
| } |
| |
| void PointerEventManager::HandlePointerInterruption( |
| const WebPointerEvent& web_pointer_event) { |
| DCHECK(web_pointer_event.GetType() == |
| WebInputEvent::Type::kPointerCausedUaAction); |
| |
| HeapVector<Member<PointerEvent>> canceled_pointer_events; |
| if (web_pointer_event.pointer_type == |
| WebPointerProperties::PointerType::kMouse) { |
| canceled_pointer_events.push_back( |
| pointer_event_factory_.CreatePointerCancelEvent( |
| PointerEventFactory::kMouseId, |
| TimeTicks::FromSeconds(web_pointer_event.TimeStampSeconds()))); |
| } else { |
| // TODO(nzolghadr): Maybe canceling all the scroll capable pointers is not |
| // the best strategy here. See the github issue for more details: |
| // https://github.com/w3c/pointerevents/issues/226 |
| |
| // Cancel all scroll capable pointers if the pointer is not mouse. |
| if (!scroll_capable_pointers_canceled_) { |
| Vector<int> scroll_capable_pointer_ids = |
| pointer_event_factory_.GetPointerIdsOfScrollCapablePointers(); |
| |
| for (int pointer_id : scroll_capable_pointer_ids) { |
| canceled_pointer_events.push_back( |
| pointer_event_factory_.CreatePointerCancelEvent( |
| pointer_id, |
| TimeTicks::FromSeconds(web_pointer_event.TimeStampSeconds()))); |
| } |
| |
| scroll_capable_pointers_canceled_ = true; |
| } |
| } |
| |
| for (auto pointer_event : canceled_pointer_events) { |
| // If we are sending a pointercancel we have sent the pointerevent to some |
| // target before. |
| DCHECK(node_under_pointer_.Contains(pointer_event->pointerId())); |
| EventTarget* target = |
| node_under_pointer_.at(pointer_event->pointerId()).target; |
| |
| DispatchPointerEvent( |
| GetEffectiveTargetForPointerEvent(target, pointer_event->pointerId()), |
| pointer_event); |
| |
| ReleasePointerCapture(pointer_event->pointerId()); |
| |
| // Send the leave/out events and lostpointercapture if needed. |
| // Note that for mouse due to the web compat we still don't send the |
| // boundary events and for now only send lostpointercapture if needed. |
| // Sending boundary events and possibly updating hover for mouse |
| // in this case may cause some of the existing pages to break. |
| if (web_pointer_event.pointer_type == |
| WebPointerProperties::PointerType::kMouse) { |
| ProcessPendingPointerCapture(pointer_event); |
| } else { |
| ProcessCaptureAndPositionOfPointerEvent(pointer_event, nullptr); |
| } |
| |
| RemovePointer(pointer_event); |
| } |
| } |
| |
| EventHandlingUtil::PointerEventTarget |
| PointerEventManager::ComputePointerEventTarget( |
| const WebPointerEvent& web_pointer_event) { |
| EventHandlingUtil::PointerEventTarget pointer_event_target; |
| |
| int pointer_id = pointer_event_factory_.GetPointerEventId(web_pointer_event); |
| // Do the hit test either when the touch first starts or when the touch |
| // is not captured. |m_pendingPointerCaptureTarget| indicates the target |
| // that will be capturing this event. |m_pointerCaptureTarget| may not |
| // have this target yet since the processing of that will be done right |
| // before firing the event. |
| if (web_pointer_event.GetType() == WebInputEvent::kPointerDown || |
| !pending_pointer_capture_target_.Contains(pointer_id)) { |
| HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kTouchEvent | |
| HitTestRequest::kReadOnly | |
| HitTestRequest::kActive; |
| LayoutPoint page_point = frame_->View()->RootFrameToContents( |
| LayoutPoint(web_pointer_event.PositionInWidget())); |
| HitTestResult hit_test_tesult = |
| frame_->GetEventHandler().HitTestResultAtPoint(page_point, hit_type); |
| Node* node = hit_test_tesult.InnerNode(); |
| if (node) { |
| pointer_event_target.target_frame = node->GetDocument().GetFrame(); |
| if (auto* canvas = ToHTMLCanvasElementOrNull(node)) { |
| HitTestCanvasResult* hit_test_canvas_result = |
| canvas->GetControlAndIdIfHitRegionExists( |
| hit_test_tesult.PointInInnerNodeFrame()); |
| if (hit_test_canvas_result->GetControl()) |
| node = hit_test_canvas_result->GetControl(); |
| pointer_event_target.region = hit_test_canvas_result->GetId(); |
| } |
| // TODO(crbug.com/612456): We need to investigate whether pointer |
| // events should go to text nodes or not. If so we need to |
| // update the mouse code as well. Also this logic looks similar |
| // to the one in TouchEventManager. We should be able to |
| // refactor it better after this investigation. |
| if (node->IsTextNode()) |
| node = FlatTreeTraversal::Parent(*node); |
| pointer_event_target.target_node = node; |
| } |
| } else { |
| // Set the target of pointer event to the captured node as this |
| // pointer is captured otherwise it would have gone to the if block |
| // and perform a hit-test. |
| pointer_event_target.target_node = |
| pending_pointer_capture_target_.at(pointer_id)->ToNode(); |
| pointer_event_target.target_frame = |
| pointer_event_target.target_node->GetDocument().GetFrame(); |
| } |
| return pointer_event_target; |
| } |
| |
| WebInputEventResult PointerEventManager::DispatchTouchPointerEvent( |
| const WebPointerEvent& web_pointer_event, |
| const Vector<WebPointerEvent>& coalesced_events, |
| const EventHandlingUtil::PointerEventTarget& pointer_event_target) { |
| DCHECK_NE(web_pointer_event.GetType(), |
| WebInputEvent::Type::kPointerCausedUaAction); |
| |
| WebInputEventResult result = WebInputEventResult::kHandledSystem; |
| if (pointer_event_target.target_node && pointer_event_target.target_frame && |
| !scroll_capable_pointers_canceled_) { |
| PointerEvent* pointer_event = pointer_event_factory_.Create( |
| web_pointer_event, coalesced_events, |
| pointer_event_target.target_node |
| ? pointer_event_target.target_node->GetDocument().domWindow() |
| : nullptr); |
| |
| result = |
| SendTouchPointerEvent(pointer_event_target.target_node, pointer_event); |
| |
| // If a pointerdown has been canceled, queue the unique id to allow |
| // suppressing mouse events from gesture events. For mouse events |
| // fired from GestureTap & GestureLongPress (which are triggered by |
| // single touches only), it is enough to queue the ids only for |
| // primary pointers. |
| // TODO(mustaq): What about other cases (e.g. GestureTwoFingerTap)? |
| if (result != WebInputEventResult::kNotHandled && |
| pointer_event->type() == EventTypeNames::pointerdown && |
| pointer_event->isPrimary()) { |
| touch_ids_for_canceled_pointerdowns_.push_back( |
| web_pointer_event.unique_touch_event_id); |
| } |
| } |
| return result; |
| } |
| |
| WebInputEventResult PointerEventManager::SendTouchPointerEvent( |
| EventTarget* target, |
| PointerEvent* pointer_event) { |
| if (scroll_capable_pointers_canceled_) |
| return WebInputEventResult::kNotHandled; |
| |
| ProcessCaptureAndPositionOfPointerEvent(pointer_event, target); |
| |
| // Setting the implicit capture for touch |
| if (pointer_event->type() == EventTypeNames::pointerdown) |
| SetPointerCapture(pointer_event->pointerId(), target); |
| |
| WebInputEventResult result = DispatchPointerEvent( |
| GetEffectiveTargetForPointerEvent(target, pointer_event->pointerId()), |
| pointer_event); |
| |
| if (pointer_event->type() == EventTypeNames::pointerup || |
| pointer_event->type() == EventTypeNames::pointercancel) { |
| ReleasePointerCapture(pointer_event->pointerId()); |
| |
| // Sending the leave/out events and lostpointercapture because the next |
| // touch event will have a different id. |
| ProcessCaptureAndPositionOfPointerEvent(pointer_event, nullptr); |
| |
| RemovePointer(pointer_event); |
| } |
| |
| return result; |
| } |
| |
| WebInputEventResult PointerEventManager::FlushEvents() { |
| WebInputEventResult result = touch_event_manager_->FlushEvents(); |
| user_gesture_holder_.reset(); |
| return result; |
| } |
| |
| WebInputEventResult PointerEventManager::HandlePointerEvent( |
| const WebPointerEvent& event, |
| const Vector<WebPointerEvent>& coalesced_events) { |
| if (event.GetType() == WebInputEvent::Type::kPointerCausedUaAction) { |
| HandlePointerInterruption(event); |
| return WebInputEventResult::kHandledSystem; |
| } |
| |
| if (event.scroll_capable) { |
| if (!touch_event_manager_->IsAnyTouchActive()) { |
| scroll_capable_pointers_canceled_ = false; |
| } |
| } |
| |
| // The rest of this function does not handle non-scrolling |
| // (i.e. mouse like) events yet. |
| |
| EventHandlingUtil::PointerEventTarget pointer_event_target = |
| ComputePointerEventTarget(event.WebPointerEventInRootFrame()); |
| |
| // Any finger lifting is a user gesture only when it wasn't associated with a |
| // scroll. |
| // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZu7nvOg/edit# |
| // Re-use the same UserGesture for touchend and pointerup (but not for the |
| // mouse events generated by GestureTap). |
| // For the rare case of multi-finger scenarios spanning documents, it |
| // seems extremely unlikely to matter which document the gesture is |
| // associated with so just pick the pointer event that comes. |
| if (event.GetType() == WebInputEvent::kPointerUp && |
| !scroll_capable_pointers_canceled_ && pointer_event_target.target_frame) { |
| user_gesture_holder_ = |
| Frame::NotifyUserActivation(pointer_event_target.target_frame); |
| } |
| |
| WebInputEventResult result = |
| DispatchTouchPointerEvent(event, coalesced_events, pointer_event_target); |
| |
| touch_event_manager_->HandleTouchPoint(event, coalesced_events, |
| pointer_event_target); |
| |
| return result; |
| } |
| |
| WebInputEventResult PointerEventManager::SendMousePointerEvent( |
| Node* target, |
| const String& canvas_region_id, |
| const AtomicString& mouse_event_type, |
| const WebMouseEvent& mouse_event, |
| const Vector<WebMouseEvent>& coalesced_events) { |
| PointerEvent* pointer_event = pointer_event_factory_.Create( |
| mouse_event_type, mouse_event, coalesced_events, |
| frame_->GetDocument()->domWindow()); |
| |
| bool fake_mouse_event = (mouse_event.GetModifiers() & |
| WebInputEvent::Modifiers::kRelativeMotionEvent); |
| DCHECK(!fake_mouse_event || mouse_event_type == EventTypeNames::mousemove); |
| |
| // This is for when the mouse is released outside of the page. |
| if (!fake_mouse_event && mouse_event_type == EventTypeNames::mousemove && |
| !pointer_event->buttons()) { |
| ReleasePointerCapture(pointer_event->pointerId()); |
| // Send got/lostpointercapture rightaway if necessary. |
| ProcessPendingPointerCapture(pointer_event); |
| |
| if (pointer_event->isPrimary()) { |
| prevent_mouse_event_for_pointer_type_[ToPointerTypeIndex( |
| mouse_event.pointer_type)] = false; |
| } |
| } |
| |
| EventTarget* pointer_event_target = ProcessCaptureAndPositionOfPointerEvent( |
| pointer_event, target, canvas_region_id, &mouse_event); |
| |
| // Don't send fake mouse event to the DOM. |
| if (fake_mouse_event) |
| return WebInputEventResult::kHandledSuppressed; |
| |
| EventTarget* effective_target = GetEffectiveTargetForPointerEvent( |
| pointer_event_target, pointer_event->pointerId()); |
| |
| WebInputEventResult result = |
| DispatchPointerEvent(effective_target, pointer_event); |
| |
| if (result != WebInputEventResult::kNotHandled && |
| pointer_event->type() == EventTypeNames::pointerdown && |
| pointer_event->isPrimary()) { |
| prevent_mouse_event_for_pointer_type_[ToPointerTypeIndex( |
| mouse_event.pointer_type)] = true; |
| } |
| |
| if (pointer_event->isPrimary() && |
| !prevent_mouse_event_for_pointer_type_[ToPointerTypeIndex( |
| mouse_event.pointer_type)]) { |
| EventTarget* mouse_target = effective_target; |
| // Event path could be null if pointer event is not dispatched and |
| // that happens for example when pointer event feature is not enabled. |
| if (!EventHandlingUtil::IsInDocument(mouse_target) && |
| pointer_event->HasEventPath()) { |
| for (const auto& context : |
| pointer_event->GetEventPath().NodeEventContexts()) { |
| if (EventHandlingUtil::IsInDocument(context.GetNode())) { |
| mouse_target = context.GetNode(); |
| break; |
| } |
| } |
| } |
| result = EventHandlingUtil::MergeEventResult( |
| result, mouse_event_manager_->DispatchMouseEvent( |
| mouse_target, mouse_event_type, mouse_event, |
| canvas_region_id, nullptr)); |
| } |
| |
| if (pointer_event->type() == EventTypeNames::pointerup || |
| pointer_event->type() == EventTypeNames::pointercancel) { |
| ReleasePointerCapture(pointer_event->pointerId()); |
| // Send got/lostpointercapture rightaway if necessary. |
| ProcessPendingPointerCapture(pointer_event); |
| |
| if (pointer_event->isPrimary()) { |
| prevent_mouse_event_for_pointer_type_[ToPointerTypeIndex( |
| mouse_event.pointer_type)] = false; |
| } |
| } |
| |
| if (mouse_event.GetType() == WebInputEvent::kMouseLeave && |
| mouse_event.pointer_type == WebPointerProperties::PointerType::kPen) { |
| pointer_event_factory_.Remove(pointer_event->pointerId()); |
| } |
| return result; |
| } |
| |
| bool PointerEventManager::GetPointerCaptureState( |
| int pointer_id, |
| EventTarget** pointer_capture_target, |
| EventTarget** pending_pointer_capture_target) { |
| PointerCapturingMap::const_iterator it; |
| |
| it = pointer_capture_target_.find(pointer_id); |
| EventTarget* pointer_capture_target_temp = |
| (it != pointer_capture_target_.end()) ? it->value : nullptr; |
| it = pending_pointer_capture_target_.find(pointer_id); |
| EventTarget* pending_pointercapture_target_temp = |
| (it != pending_pointer_capture_target_.end()) ? it->value : nullptr; |
| |
| if (pointer_capture_target) |
| *pointer_capture_target = pointer_capture_target_temp; |
| if (pending_pointer_capture_target) |
| *pending_pointer_capture_target = pending_pointercapture_target_temp; |
| |
| return pointer_capture_target_temp != pending_pointercapture_target_temp; |
| } |
| |
| EventTarget* PointerEventManager::ProcessCaptureAndPositionOfPointerEvent( |
| PointerEvent* pointer_event, |
| EventTarget* hit_test_target, |
| const String& canvas_region_id, |
| const WebMouseEvent* mouse_event) { |
| ProcessPendingPointerCapture(pointer_event); |
| |
| PointerCapturingMap::const_iterator it = |
| pointer_capture_target_.find(pointer_event->pointerId()); |
| if (EventTarget* pointercapture_target = |
| (it != pointer_capture_target_.end()) ? it->value : nullptr) |
| hit_test_target = pointercapture_target; |
| |
| SetNodeUnderPointer(pointer_event, hit_test_target); |
| if (mouse_event) { |
| mouse_event_manager_->SetNodeUnderMouse( |
| hit_test_target ? hit_test_target->ToNode() : nullptr, canvas_region_id, |
| *mouse_event); |
| } |
| return hit_test_target; |
| } |
| |
| void PointerEventManager::ProcessPendingPointerCapture( |
| PointerEvent* pointer_event) { |
| EventTarget* pointer_capture_target; |
| EventTarget* pending_pointer_capture_target; |
| const int pointer_id = pointer_event->pointerId(); |
| const bool is_capture_changed = GetPointerCaptureState( |
| pointer_id, &pointer_capture_target, &pending_pointer_capture_target); |
| |
| if (!is_capture_changed) |
| return; |
| |
| // We have to check whether the pointerCaptureTarget is null or not because |
| // we are checking whether it is still connected to its document or not. |
| if (pointer_capture_target) { |
| // Re-target lostpointercapture to the document when the element is |
| // no longer participating in the tree. |
| EventTarget* target = pointer_capture_target; |
| if (target->ToNode() && !target->ToNode()->isConnected()) { |
| target = target->ToNode()->ownerDocument(); |
| } |
| DispatchPointerEvent( |
| target, pointer_event_factory_.CreatePointerCaptureEvent( |
| pointer_event, EventTypeNames::lostpointercapture)); |
| } |
| |
| if (pending_pointer_capture_target) { |
| SetNodeUnderPointer(pointer_event, pending_pointer_capture_target); |
| DispatchPointerEvent(pending_pointer_capture_target, |
| pointer_event_factory_.CreatePointerCaptureEvent( |
| pointer_event, EventTypeNames::gotpointercapture)); |
| pointer_capture_target_.Set(pointer_id, pending_pointer_capture_target); |
| } else { |
| pointer_capture_target_.erase(pointer_id); |
| } |
| } |
| |
| void PointerEventManager::ProcessPendingPointerCaptureForPointerLock( |
| const WebMouseEvent& mouse_event) { |
| PointerEvent* pointer_event = pointer_event_factory_.Create( |
| EventTypeNames::mousemove, mouse_event, Vector<WebMouseEvent>(), |
| frame_->GetDocument()->domWindow()); |
| ProcessPendingPointerCapture(pointer_event); |
| } |
| |
| void PointerEventManager::RemoveTargetFromPointerCapturingMapping( |
| PointerCapturingMap& map, |
| const EventTarget* target) { |
| // We could have kept a reverse mapping to make this deletion possibly |
| // faster but it adds some code complication which might not be worth of |
| // the performance improvement considering there might not be a lot of |
| // active pointer or pointer captures at the same time. |
| PointerCapturingMap tmp = map; |
| for (PointerCapturingMap::iterator it = tmp.begin(); it != tmp.end(); ++it) { |
| if (it->value == target) |
| map.erase(it->key); |
| } |
| } |
| |
| EventTarget* PointerEventManager::GetCapturingNode(int pointer_id) { |
| if (pointer_capture_target_.Contains(pointer_id)) |
| return pointer_capture_target_.at(pointer_id); |
| return nullptr; |
| } |
| |
| void PointerEventManager::RemovePointer(PointerEvent* pointer_event) { |
| int pointer_id = pointer_event->pointerId(); |
| if (pointer_event_factory_.Remove(pointer_id)) { |
| pending_pointer_capture_target_.erase(pointer_id); |
| pointer_capture_target_.erase(pointer_id); |
| node_under_pointer_.erase(pointer_id); |
| } |
| } |
| |
| void PointerEventManager::ElementRemoved(EventTarget* target) { |
| RemoveTargetFromPointerCapturingMapping(pending_pointer_capture_target_, |
| target); |
| } |
| |
| void PointerEventManager::SetPointerCapture(int pointer_id, |
| EventTarget* target) { |
| UseCounter::Count(frame_, WebFeature::kPointerEventSetCapture); |
| if (pointer_event_factory_.IsActiveButtonsState(pointer_id)) { |
| if (pointer_id != dispatching_pointer_id_) { |
| UseCounter::Count(frame_, |
| WebFeature::kPointerEventSetCaptureOutsideDispatch); |
| } |
| pending_pointer_capture_target_.Set(pointer_id, target); |
| } |
| } |
| |
| void PointerEventManager::ReleasePointerCapture(int pointer_id, |
| EventTarget* target) { |
| // Only the element that is going to get the next pointer event can release |
| // the capture. Note that this might be different from |
| // |m_pointercaptureTarget|. |m_pointercaptureTarget| holds the element |
| // that had the capture until now and has been receiving the pointerevents |
| // but |m_pendingPointerCaptureTarget| indicated the element that gets the |
| // very next pointer event. They will be the same if there was no change in |
| // capturing of a particular |pointerId|. See crbug.com/614481. |
| if (pending_pointer_capture_target_.at(pointer_id) == target) |
| ReleasePointerCapture(pointer_id); |
| } |
| |
| void PointerEventManager::ReleaseMousePointerCapture() { |
| ReleasePointerCapture(PointerEventFactory::kMouseId); |
| } |
| |
| bool PointerEventManager::HasPointerCapture(int pointer_id, |
| const EventTarget* target) const { |
| return pending_pointer_capture_target_.at(pointer_id) == target; |
| } |
| |
| bool PointerEventManager::HasProcessedPointerCapture( |
| int pointer_id, |
| const EventTarget* target) const { |
| return pointer_capture_target_.at(pointer_id) == target; |
| } |
| |
| void PointerEventManager::ReleasePointerCapture(int pointer_id) { |
| pending_pointer_capture_target_.erase(pointer_id); |
| } |
| |
| bool PointerEventManager::IsActive(const int pointer_id) const { |
| return pointer_event_factory_.IsActive(pointer_id); |
| } |
| |
| // This function checks the type of the pointer event to be touch as touch |
| // pointer events are the only ones that are directly dispatched from the main |
| // page managers to their target (event if target is in an iframe) and only |
| // those managers will keep track of these pointer events. |
| bool PointerEventManager::IsTouchPointerIdActiveOnFrame( |
| int pointer_id, |
| LocalFrame* frame) const { |
| if (pointer_event_factory_.GetPointerType(pointer_id) != |
| WebPointerProperties::PointerType::kTouch) |
| return false; |
| Node* last_node_receiving_event = |
| node_under_pointer_.Contains(pointer_id) |
| ? node_under_pointer_.at(pointer_id).target->ToNode() |
| : nullptr; |
| return last_node_receiving_event && |
| last_node_receiving_event->GetDocument().GetFrame() == frame; |
| } |
| |
| bool PointerEventManager::IsAnyTouchActive() const { |
| return touch_event_manager_->IsAnyTouchActive(); |
| } |
| |
| bool PointerEventManager::PrimaryPointerdownCanceled( |
| uint32_t unique_touch_event_id) { |
| // It's safe to assume that uniqueTouchEventIds won't wrap back to 0 from |
| // 2^32-1 (>4.2 billion): even with a generous 100 unique ids per touch |
| // sequence & one sequence per 10 second, it takes 13+ years to wrap back. |
| while (!touch_ids_for_canceled_pointerdowns_.IsEmpty()) { |
| uint32_t first_id = touch_ids_for_canceled_pointerdowns_.front(); |
| if (first_id > unique_touch_event_id) |
| return false; |
| touch_ids_for_canceled_pointerdowns_.TakeFirst(); |
| if (first_id == unique_touch_event_id) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace blink |