blob: 3bc45814ae2204f6deba6ec04a3b2eddfeb01bc2 [file] [log] [blame]
// 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 "third_party/blink/renderer/core/input/touch_event_manager.h"
#include <memory>
#include "third_party/blink/public/platform/web_coalesced_input_event.h"
#include "third_party/blink/public/platform/web_touch_event.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.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_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/input/event_handling_util.h"
#include "third_party/blink/renderer/core/input/touch_action_util.h"
#include "third_party/blink/renderer/core/layout/hit_test_canvas_result.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
namespace {
// Returns true if there are event listeners of |handler_class| on |touch_node|
// or any of its ancestors inside the document (including DOMWindow).
bool HasEventHandlerInAncestorPath(
Node* touch_node,
EventHandlerRegistry::EventHandlerClass handler_class) {
Document& document = touch_node->GetDocument();
const EventTargetSet* event_target_set =
document.GetFrame()->GetEventHandlerRegistry().EventHandlerTargets(
handler_class);
if (event_target_set->Contains(document.domWindow()))
return true;
for (Node& ancestor : NodeTraversal::InclusiveAncestorsOf(*touch_node)) {
if (event_target_set->Contains(&ancestor))
return true;
}
return false;
}
bool HasTouchHandlers(const EventHandlerRegistry& registry) {
return registry.HasEventHandlers(
EventHandlerRegistry::kTouchStartOrMoveEventBlocking) ||
registry.HasEventHandlers(
EventHandlerRegistry::kTouchStartOrMoveEventBlockingLowLatency) ||
registry.HasEventHandlers(
EventHandlerRegistry::kTouchStartOrMoveEventPassive) ||
registry.HasEventHandlers(
EventHandlerRegistry::kTouchEndOrCancelEventBlocking) ||
registry.HasEventHandlers(
EventHandlerRegistry::kTouchEndOrCancelEventPassive);
}
const AtomicString& TouchEventNameForPointerEventType(
WebInputEvent::Type type) {
switch (type) {
case WebInputEvent::kPointerUp:
return event_type_names::kTouchend;
case WebInputEvent::kPointerCancel:
return event_type_names::kTouchcancel;
case WebInputEvent::kPointerDown:
return event_type_names::kTouchstart;
case WebInputEvent::kPointerMove:
return event_type_names::kTouchmove;
default:
NOTREACHED();
return g_empty_atom;
}
}
enum TouchEventDispatchResultType {
kUnhandledTouches, // Unhandled touch events.
kHandledTouches, // Handled touch events.
kTouchEventDispatchResultTypeMax,
};
WebTouchPoint::State TouchPointStateFromPointerEventType(
WebInputEvent::Type type,
bool stale) {
if (stale)
return WebTouchPoint::kStateStationary;
switch (type) {
case WebInputEvent::Type::kPointerUp:
return WebTouchPoint::kStateReleased;
case WebInputEvent::Type::kPointerCancel:
return WebTouchPoint::kStateCancelled;
case WebInputEvent::Type::kPointerDown:
return WebTouchPoint::kStatePressed;
case WebInputEvent::Type::kPointerMove:
return WebTouchPoint::kStateMoved;
default:
NOTREACHED();
return WebTouchPoint::kStateUndefined;
}
}
WebTouchPoint CreateWebTouchPointFromWebPointerEvent(
const WebPointerEvent& web_pointer_event,
bool stale) {
WebTouchPoint web_touch_point(web_pointer_event);
web_touch_point.state =
TouchPointStateFromPointerEventType(web_pointer_event.GetType(), stale);
web_touch_point.radius_x = web_pointer_event.width / 2.f;
web_touch_point.radius_y = web_pointer_event.height / 2.f;
web_touch_point.rotation_angle = web_pointer_event.rotation_angle;
return web_touch_point;
}
void SetWebTouchEventAttributesFromWebPointerEvent(
WebTouchEvent* web_touch_event,
const WebPointerEvent& web_pointer_event) {
web_touch_event->dispatch_type = web_pointer_event.dispatch_type;
web_touch_event->touch_start_or_first_touch_move =
web_pointer_event.touch_start_or_first_touch_move;
web_touch_event->moved_beyond_slop_region =
web_pointer_event.moved_beyond_slop_region;
web_touch_event->SetFrameScale(web_pointer_event.FrameScale());
web_touch_event->SetFrameTranslate(web_pointer_event.FrameTranslate());
web_touch_event->SetTimeStamp(web_pointer_event.TimeStamp());
web_touch_event->SetModifiers(web_pointer_event.GetModifiers());
}
// Defining this class type local to
// DispatchTouchEventFromAccumulatdTouchPoints() and annotating
// it with STACK_ALLOCATED(), runs into MSVC(VS 2013)'s C4822 warning
// that the local class doesn't provide a local definition for 'operator new'.
// Which it intentionally doesn't and shouldn't.
//
// Work around such toolchain bugginess by lifting out the type, thereby
// taking it out of C4822's reach.
class ChangedTouches final {
STACK_ALLOCATED();
public:
// The touches corresponding to the particular change state this struct
// instance represents.
Member<TouchList> touches_;
using EventTargetSet = HeapHashSet<Member<EventTarget>>;
// Set of targets involved in m_touches.
EventTargetSet targets_;
};
} // namespace
TouchEventManager::TouchEventManager(LocalFrame& frame) : frame_(frame) {
Clear();
}
void TouchEventManager::Clear() {
touch_sequence_document_.Clear();
touch_attribute_map_.clear();
last_coalesced_touch_event_ = WebTouchEvent();
suppressing_touchmoves_within_slop_ = false;
current_touch_action_ = TouchAction::kTouchActionAuto;
}
void TouchEventManager::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_);
visitor->Trace(touch_sequence_document_);
visitor->Trace(touch_attribute_map_);
}
Touch* TouchEventManager::CreateDomTouch(
const TouchEventManager::TouchPointAttributes* point_attr,
bool* known_target) {
Node* touch_node = point_attr->target_;
String region_id = point_attr->region_;
*known_target = false;
LocalFrame* target_frame = nullptr;
if (touch_node) {
Document& doc = touch_node->GetDocument();
// If the target node has moved to a new document while it was being
// touched, we can't send events to the new document because that could
// leak nodes from one document to another. See http://crbug.com/394339.
if (&doc == touch_sequence_document_.Get()) {
target_frame = doc.GetFrame();
*known_target = true;
}
}
if (!(*known_target)) {
// If we don't have a target registered for the point it means we've
// missed our opportunity to do a hit test for it (due to some
// optimization that prevented blink from ever seeing the
// touchstart), or that the touch started outside the active touch
// sequence document. We should still include the touch in the
// Touches list reported to the application (eg. so it can
// differentiate between a one and two finger gesture), but we won't
// actually dispatch any events for it. Set the target to the
// Document so that there's some valid node here. Perhaps this
// should really be LocalDOMWindow, but in all other cases the target of
// a Touch is a Node so using the window could be a breaking change.
// Since we know there was no handler invoked, the specific target
// should be completely irrelevant to the application.
touch_node = touch_sequence_document_;
target_frame = touch_sequence_document_->GetFrame();
}
DCHECK(target_frame);
WebPointerEvent transformed_event =
point_attr->event_.WebPointerEventInRootFrame();
float scale_factor = 1.0f / target_frame->PageZoomFactor();
FloatPoint document_point =
target_frame->View()
->RootFrameToDocument(transformed_event.PositionInWidget())
.ScaledBy(scale_factor);
FloatSize adjusted_radius =
FloatSize(transformed_event.width / 2.f, transformed_event.height / 2.f)
.ScaledBy(scale_factor);
return Touch::Create(target_frame, touch_node, point_attr->event_.id,
transformed_event.PositionInScreen(), document_point,
adjusted_radius, transformed_event.rotation_angle,
transformed_event.force, region_id);
}
WebCoalescedInputEvent TouchEventManager::GenerateWebCoalescedInputEvent() {
DCHECK(!touch_attribute_map_.IsEmpty());
WebTouchEvent event;
const auto& first_touch_pointer_event =
touch_attribute_map_.begin()->value->event_;
SetWebTouchEventAttributesFromWebPointerEvent(&event,
first_touch_pointer_event);
SetWebTouchEventAttributesFromWebPointerEvent(&last_coalesced_touch_event_,
first_touch_pointer_event);
WebInputEvent::Type touch_event_type = WebInputEvent::kTouchMove;
Vector<WebPointerEvent> all_coalesced_events;
Vector<int> available_ids;
for (const auto& id : touch_attribute_map_.Keys())
available_ids.push_back(id);
std::sort(available_ids.begin(), available_ids.end());
for (const int& touch_point_id : available_ids) {
auto* const touch_point_attribute = touch_attribute_map_.at(touch_point_id);
const WebPointerEvent& touch_pointer_event = touch_point_attribute->event_;
event.touches[event.touches_length++] =
CreateWebTouchPointFromWebPointerEvent(touch_pointer_event,
touch_point_attribute->stale_);
// Only change the touch event type from move. So if we have two pointers
// in up and down state we just set the touch event type to the first one
// we see.
// TODO(crbug.com/732842): Note that event sender API allows sending any
// mix of input and as long as we don't crash or anything we should be good
// for now.
if (touch_event_type == WebInputEvent::kTouchMove) {
if (touch_pointer_event.GetType() == WebInputEvent::kPointerDown)
touch_event_type = WebInputEvent::kTouchStart;
else if (touch_pointer_event.GetType() == WebInputEvent::kPointerCancel)
touch_event_type = WebInputEvent::kTouchCancel;
else if (touch_pointer_event.GetType() == WebInputEvent::kPointerUp)
touch_event_type = WebInputEvent::kTouchEnd;
}
for (const WebPointerEvent& coalesced_event :
touch_point_attribute->coalesced_events_) {
all_coalesced_events.push_back(coalesced_event);
}
}
event.SetType(touch_event_type);
last_coalesced_touch_event_.SetType(touch_event_type);
// Create all coalesced touch events based on pointerevents
struct {
bool operator()(const WebPointerEvent& a, const WebPointerEvent& b) {
return a.TimeStamp() < b.TimeStamp();
}
} timestamp_based_event_comparison;
std::sort(all_coalesced_events.begin(), all_coalesced_events.end(),
timestamp_based_event_comparison);
WebCoalescedInputEvent result(event, std::vector<const WebInputEvent*>(),
std::vector<const WebInputEvent*>());
for (const auto& web_pointer_event : all_coalesced_events) {
if (web_pointer_event.GetType() == WebInputEvent::kPointerDown) {
// TODO(crbug.com/732842): Technically we should never receive the
// pointerdown twice for the same touch point. But event sender API allows
// that. So we should handle it gracefully.
WebTouchPoint web_touch_point(web_pointer_event);
bool found_existing_id = false;
for (unsigned i = 0; i < last_coalesced_touch_event_.touches_length;
++i) {
if (last_coalesced_touch_event_.touches[i].id == web_pointer_event.id) {
last_coalesced_touch_event_.touches[i] =
CreateWebTouchPointFromWebPointerEvent(web_pointer_event, false);
last_coalesced_touch_event_.SetTimeStamp(
web_pointer_event.TimeStamp());
found_existing_id = true;
break;
}
}
// If the pointerdown point didn't exist add a new point to the array.
if (!found_existing_id) {
last_coalesced_touch_event_
.touches[last_coalesced_touch_event_.touches_length++] =
CreateWebTouchPointFromWebPointerEvent(web_pointer_event, false);
}
struct {
bool operator()(const WebTouchPoint& a, const WebTouchPoint& b) {
return a.id < b.id;
}
} id_based_event_comparison;
std::sort(last_coalesced_touch_event_.touches,
last_coalesced_touch_event_.touches +
last_coalesced_touch_event_.touches_length,
id_based_event_comparison);
result.AddCoalescedEvent(last_coalesced_touch_event_);
} else {
for (unsigned i = 0; i < last_coalesced_touch_event_.touches_length;
++i) {
if (last_coalesced_touch_event_.touches[i].id == web_pointer_event.id) {
last_coalesced_touch_event_.touches[i] =
CreateWebTouchPointFromWebPointerEvent(web_pointer_event, false);
last_coalesced_touch_event_.SetTimeStamp(
web_pointer_event.TimeStamp());
result.AddCoalescedEvent(last_coalesced_touch_event_);
// Remove up and canceled points.
unsigned result_size = 0;
for (unsigned j = 0; j < last_coalesced_touch_event_.touches_length;
j++) {
if (last_coalesced_touch_event_.touches[j].state !=
WebTouchPoint::kStateCancelled &&
last_coalesced_touch_event_.touches[j].state !=
WebTouchPoint::kStateReleased) {
last_coalesced_touch_event_.touches[result_size++] =
last_coalesced_touch_event_.touches[j];
}
}
last_coalesced_touch_event_.touches_length = result_size;
break;
}
}
}
for (unsigned i = 0; i < event.touches_length; ++i) {
event.touches[i].state = blink::WebTouchPoint::kStateStationary;
event.touches[i].movement_x = 0;
event.touches[i].movement_y = 0;
}
}
return result;
}
WebInputEventResult
TouchEventManager::DispatchTouchEventFromAccumulatdTouchPoints() {
// Build up the lists to use for the |touches|, |targetTouches| and
// |changedTouches| attributes in the JS event. See
// http://www.w3.org/TR/touch-events/#touchevent-interface for how these
// lists fit together.
bool new_touch_point_since_last_dispatch = false;
bool any_touch_canceled_or_ended = false;
bool all_touch_points_pressed = true;
for (const auto& attr : touch_attribute_map_.Values()) {
if (!attr->stale_)
new_touch_point_since_last_dispatch = true;
if (attr->event_.GetType() == WebInputEvent::kPointerUp ||
attr->event_.GetType() == WebInputEvent::kPointerCancel)
any_touch_canceled_or_ended = true;
if (attr->event_.GetType() != WebInputEvent::kPointerDown)
all_touch_points_pressed = false;
}
if (!new_touch_point_since_last_dispatch)
return WebInputEventResult::kNotHandled;
if (any_touch_canceled_or_ended || touch_attribute_map_.size() > 1)
suppressing_touchmoves_within_slop_ = false;
if (suppressing_touchmoves_within_slop_) {
// There is exactly one touch point here otherwise
// |suppressing_touchmoves_within_slop_| would have been false.
DCHECK_EQ(1U, touch_attribute_map_.size());
const auto& touch_point_attribute = touch_attribute_map_.begin()->value;
if (touch_point_attribute->event_.GetType() ==
WebInputEvent::kPointerMove) {
if (!touch_point_attribute->event_.moved_beyond_slop_region)
return WebInputEventResult::kHandledSuppressed;
suppressing_touchmoves_within_slop_ = false;
}
}
// Holds the complete set of touches on the screen.
TouchList* touches = TouchList::Create();
// A different view on the 'touches' list above, filtered and grouped by
// event target. Used for the |targetTouches| list in the JS event.
using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>;
TargetTouchesHeapMap touches_by_target;
// Array of touches per state, used to assemble the |changedTouches| list.
ChangedTouches changed_touches[WebInputEvent::kPointerTypeLast -
WebInputEvent::kPointerTypeFirst + 1];
Vector<int> available_ids;
for (const auto& id : touch_attribute_map_.Keys())
available_ids.push_back(id);
std::sort(available_ids.begin(), available_ids.end());
for (const int& touch_point_id : available_ids) {
auto* const touch_point_attribute = touch_attribute_map_.at(touch_point_id);
WebInputEvent::Type event_type = touch_point_attribute->event_.GetType();
bool known_target;
Touch* touch = CreateDomTouch(touch_point_attribute, &known_target);
EventTarget* touch_target = touch->target();
// Ensure this target's touch list exists, even if it ends up empty, so
// it can always be passed to TouchEvent::Create below.
TargetTouchesHeapMap::iterator target_touches_iterator =
touches_by_target.find(touch_target);
if (target_touches_iterator == touches_by_target.end()) {
touches_by_target.Set(touch_target, TouchList::Create());
target_touches_iterator = touches_by_target.find(touch_target);
}
// |touches| and |targetTouches| should only contain information about
// touches still on the screen, so if this point is released or
// cancelled it will only appear in the |changedTouches| list.
if (event_type != WebInputEvent::kPointerUp &&
event_type != WebInputEvent::kPointerCancel) {
touches->Append(touch);
target_touches_iterator->value->Append(touch);
}
// Now build up the correct list for |changedTouches|.
// Note that any touches that are in the TouchStationary state (e.g. if
// the user had several points touched but did not move them all) should
// never be in the |changedTouches| list so we do not handle them
// explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
// for further discussion about the TouchStationary state.
if (!touch_point_attribute->stale_ && known_target) {
size_t event_type_idx = event_type - WebInputEvent::kPointerTypeFirst;
if (!changed_touches[event_type_idx].touches_)
changed_touches[event_type_idx].touches_ = TouchList::Create();
changed_touches[event_type_idx].touches_->Append(touch);
changed_touches[event_type_idx].targets_.insert(touch_target);
}
}
WebInputEventResult event_result = WebInputEventResult::kNotHandled;
// First we construct the webcoalescedinputevent containing all the coalesced
// touch event.
WebCoalescedInputEvent coalesced_event = GenerateWebCoalescedInputEvent();
// Now iterate through the |changedTouches| list and |m_targets| within it,
// sending TouchEvents to the targets as required.
for (unsigned action = WebInputEvent::kPointerTypeFirst;
action <= WebInputEvent::kPointerTypeLast; ++action) {
size_t action_idx = action - WebInputEvent::kPointerTypeFirst;
if (!changed_touches[action_idx].touches_)
continue;
const AtomicString& event_name(TouchEventNameForPointerEventType(
static_cast<WebInputEvent::Type>(action)));
for (const auto& event_target : changed_touches[action_idx].targets_) {
EventTarget* touch_event_target = event_target;
TouchEvent* touch_event = TouchEvent::Create(
coalesced_event, touches, touches_by_target.at(touch_event_target),
changed_touches[action_idx].touches_.Get(), event_name,
touch_event_target->ToNode()->GetDocument().domWindow(),
current_touch_action_);
DispatchEventResult dom_dispatch_result =
touch_event_target->DispatchEvent(*touch_event);
event_result = event_handling_util::MergeEventResult(
event_result,
event_handling_util::ToWebInputEventResult(dom_dispatch_result));
}
}
if (should_enforce_vertical_scroll_)
event_result = EnsureVerticalScrollIsPossible(event_result);
// Suppress following touchmoves within the slop region if the touchstart is
// not consumed.
if (all_touch_points_pressed &&
event_result == WebInputEventResult::kNotHandled) {
suppressing_touchmoves_within_slop_ = true;
}
return event_result;
}
void TouchEventManager::UpdateTouchAttributeMapsForPointerDown(
const WebPointerEvent& event,
const event_handling_util::PointerEventTarget& pointer_event_target) {
// Touch events implicitly capture to the touched node, and don't change
// active/hover states themselves (Gesture events do). So we only need
// to hit-test on touchstart and when the target could be different than
// the corresponding pointer event target.
DCHECK(event.GetType() == WebInputEvent::kPointerDown);
// Ideally we'd DCHECK(!touch_attribute_map_.Contains(event.id))
// since we shouldn't get a touchstart for a touch that's already
// down. However EventSender allows this to be violated and there's
// some tests that take advantage of it. There may also be edge
// cases in the browser where this happens.
// See http://crbug.com/345372.
touch_attribute_map_.Set(event.id, new TouchPointAttributes(event));
Node* touch_node = pointer_event_target.target_node;
String region = pointer_event_target.region;
HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kTouchEvent |
HitTestRequest::kReadOnly |
HitTestRequest::kActive;
HitTestResult result;
// For the touchPressed points hit-testing is done in
// PointerEventManager. If it was the second touch there is a
// capturing documents for the touch and |m_touchSequenceDocument|
// is not null. So if PointerEventManager should hit-test again
// against |m_touchSequenceDocument| if the target set by
// PointerEventManager was either null or not in
// |m_touchSequenceDocument|.
if (touch_sequence_document_ &&
(!touch_node || &touch_node->GetDocument() != touch_sequence_document_)) {
if (touch_sequence_document_->GetFrame()) {
HitTestLocation location(LayoutPoint(
touch_sequence_document_->GetFrame()->View()->ConvertFromRootFrame(
event.PositionInWidget())));
result = event_handling_util::HitTestResultInFrame(
touch_sequence_document_->GetFrame(), location, hit_type);
Node* node = result.InnerNode();
if (!node)
return;
if (auto* canvas = ToHTMLCanvasElementOrNull(node)) {
HitTestCanvasResult* hit_test_canvas_result =
canvas->GetControlAndIdIfHitRegionExists(
result.PointInInnerNodeFrame());
if (hit_test_canvas_result->GetControl())
node = hit_test_canvas_result->GetControl();
region = hit_test_canvas_result->GetId();
}
// Touch events should not go to text nodes.
if (node->IsTextNode())
node = FlatTreeTraversal::Parent(*node);
touch_node = node;
} else {
return;
}
}
if (!touch_node)
return;
if (!touch_sequence_document_) {
// Keep track of which document should receive all touch events
// in the active sequence. This must be a single document to
// ensure we don't leak Nodes between documents.
touch_sequence_document_ = &(touch_node->GetDocument());
DCHECK(touch_sequence_document_->GetFrame()->View());
}
TouchPointAttributes* attributes = touch_attribute_map_.at(event.id);
attributes->target_ = touch_node;
attributes->region_ = region;
TouchAction effective_touch_action =
touch_action_util::ComputeEffectiveTouchAction(*touch_node);
should_enforce_vertical_scroll_ =
touch_sequence_document_->IsVerticalScrollEnforced();
if (should_enforce_vertical_scroll_ &&
HasEventHandlerInAncestorPath(
touch_node, EventHandlerRegistry::kTouchStartOrMoveEventBlocking)) {
delayed_effective_touch_action_ = delayed_effective_touch_action_.value_or(
TouchAction::kTouchActionAuto) &
effective_touch_action;
}
if (!delayed_effective_touch_action_) {
frame_->GetPage()->GetChromeClient().SetTouchAction(frame_,
effective_touch_action);
}
// Combine the current touch action sequence with the touch action
// for the current finger press.
current_touch_action_ &= effective_touch_action;
}
void TouchEventManager::HandleTouchPoint(
const WebPointerEvent& event,
const Vector<WebPointerEvent>& coalesced_events,
const event_handling_util::PointerEventTarget& pointer_event_target) {
DCHECK_GE(event.GetType(), WebInputEvent::kPointerTypeFirst);
DCHECK_LE(event.GetType(), WebInputEvent::kPointerTypeLast);
DCHECK_NE(event.GetType(), WebInputEvent::kPointerCausedUaAction);
if (touch_attribute_map_.IsEmpty()) {
// Ideally we'd DCHECK(!m_touchSequenceDocument) here since we should
// have cleared the active document when we saw the last release. But we
// have some tests that violate this, ClusterFuzz could trigger it, and
// there may be cases where the browser doesn't reliably release all
// touches. http://crbug.com/345372 tracks this.
AllTouchesReleasedCleanup();
}
DCHECK(frame_->View());
if (touch_sequence_document_ &&
(!touch_sequence_document_->GetFrame() ||
!touch_sequence_document_->GetFrame()->View())) {
// If the active touch document has no frame or view, it's probably being
// destroyed so we can't dispatch events.
// Update the points so they get removed in flush when they are released.
if (touch_attribute_map_.Contains(event.id)) {
TouchPointAttributes* attributes = touch_attribute_map_.at(event.id);
attributes->event_ = event;
}
return;
}
// In touch event model only touch starts can set the target and after that
// the touch event always goes to that target.
if (event.GetType() == WebInputEvent::kPointerDown) {
UpdateTouchAttributeMapsForPointerDown(event, pointer_event_target);
}
// We might not receive the down action for a touch point. In that case we
// would have never added them to |touch_attribute_map_| or hit-tested
// them. For those just keep them in the map with a null target. Later they
// will be targeted at the |touch_sequence_document_|.
if (!touch_attribute_map_.Contains(event.id)) {
touch_attribute_map_.insert(event.id, new TouchPointAttributes(event));
}
TouchPointAttributes* attributes = touch_attribute_map_.at(event.id);
attributes->event_ = event;
attributes->coalesced_events_ = coalesced_events;
attributes->stale_ = false;
}
WebInputEventResult TouchEventManager::FlushEvents() {
WebInputEventResult result = WebInputEventResult::kNotHandled;
// If there's no document receiving touch events, or no handlers on the
// document set to receive the events, then we can skip all the rest of
// sending the event.
if (touch_sequence_document_ && touch_sequence_document_->GetPage() &&
HasTouchHandlers(
touch_sequence_document_->GetFrame()->GetEventHandlerRegistry()) &&
touch_sequence_document_->GetFrame()->View()) {
result = DispatchTouchEventFromAccumulatdTouchPoints();
}
// Cleanup the |touch_attribute_map_| map from released and canceled
// touch points.
Vector<int> released_canceled_points;
for (auto& attributes : touch_attribute_map_.Values()) {
if (attributes->event_.GetType() == WebInputEvent::kPointerUp ||
attributes->event_.GetType() == WebInputEvent::kPointerCancel) {
released_canceled_points.push_back(attributes->event_.id);
} else {
attributes->stale_ = true;
attributes->event_.movement_x = 0;
attributes->event_.movement_y = 0;
attributes->coalesced_events_.clear();
}
}
touch_attribute_map_.RemoveAll(released_canceled_points);
if (touch_attribute_map_.IsEmpty()) {
AllTouchesReleasedCleanup();
}
return result;
}
void TouchEventManager::AllTouchesReleasedCleanup() {
touch_sequence_document_.Clear();
current_touch_action_ = TouchAction::kTouchActionAuto;
last_coalesced_touch_event_ = WebTouchEvent();
// Ideally, we should have DCHECK(!delayed_effective_touch_action_) but we do
// we do actually get here from HandleTouchPoint(). Supposedly, if there has
// been a |touch_sequence_document_| and nothing in the |touch_attribute_map_|
// we still get here and if |touch_sequence_document| was of the type which
// cannot block scroll, then the flag is certainly set
// (https://crbug.com/345372).
delayed_effective_touch_action_ = base::nullopt;
should_enforce_vertical_scroll_ = false;
}
bool TouchEventManager::IsAnyTouchActive() const {
return !touch_attribute_map_.IsEmpty();
}
WebInputEventResult TouchEventManager::EnsureVerticalScrollIsPossible(
WebInputEventResult event_result) {
bool prevent_defaulted =
event_result == WebInputEventResult::kHandledApplication;
if (prevent_defaulted && delayed_effective_touch_action_) {
// Make sure that only vertical scrolling is permitted.
*delayed_effective_touch_action_ &= TouchAction::kTouchActionPanY;
}
if (delayed_effective_touch_action_) {
// If 'touchstart' is preventDefault()-ed then we can proceed with reporting
// the effective 'touch-action'.
// TODO(ekaramad): This does not block horizontal scroll after enforcing
// vertical scrolling. We should ideally send the 'touch-action' to browser
// after the first 'touchmove' event has been dispatched.
// (https://crbug.com/844493).
frame_->GetPage()->GetChromeClient().SetTouchAction(
frame_, delayed_effective_touch_action_.value());
delayed_effective_touch_action_ = base::nullopt;
}
// If the event was canceled the result is ignored to make sure vertical
// scrolling is possible.
return prevent_defaulted ? WebInputEventResult::kNotHandled : event_result;
}
} // namespace blink