Commit ad0bd35f authored by W. James MacLean's avatar W. James MacLean Committed by Commit Bot

Shift TouchEventAcking to TEAQ

This CL makes TouchEventAckQueue responsible for calling
ProcessAckedTouchEvents() on the root view. Several main changes:

1) The TEAQ now stores the full TouchEvent, not just the event's
touch ID. This is required by ProcessAckedTouchEvents().

2) With the assumed invariant that now all touch events are
acked, and in-order, we don't need to explicitly mark emulated
touch events as such, since we can use the TouchEmulator to keep
track for us.

3) The TEAQ now holds a pointer to its owning RWHIER, since it
needs access to both (i) the TouchEmulator, and (ii) the function
IsViewInMap(). The latter means having to make IsViewInMap() a
public function on RWHIER.

Bug: 848050
Change-Id: I822424ff1d04b8ba0c67a88956b1517c38a877ec
Reviewed-on: https://chromium-review.googlesource.com/c/1355243
Commit-Queue: James MacLean <wjmaclean@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#616071}
parent 65a3081d
......@@ -98,7 +98,7 @@ class TouchEventAckQueue {
enum class TouchEventAckStatus { TouchEventNotAcked, TouchEventAcked };
enum class TouchEventSource { SystemTouchEvent, EmulatedTouchEvent };
struct AckData {
uint32_t touch_event_id;
TouchEventWithLatencyInfo touch_event;
RenderWidgetHostViewBase* target_view;
RenderWidgetHostViewBase* root_view;
TouchEventSource touch_event_source;
......@@ -106,21 +106,24 @@ class TouchEventAckQueue {
InputEventAckState ack_result;
};
TouchEventAckQueue() {}
explicit TouchEventAckQueue(RenderWidgetHostInputEventRouter* client)
: client_(client) {
DCHECK(client_);
}
void Add(uint32_t touch_event_id,
void Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result);
void Add(uint32_t touch_event_id,
void Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source);
void MarkAcked(uint32_t touch_event_id,
void MarkAcked(const TouchEventWithLatencyInfo& touch_event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view);
......@@ -131,42 +134,55 @@ class TouchEventAckQueue {
void ReportTouchEventAckQueueUmaStats();
std::deque<AckData> ack_queue_;
RenderWidgetHostInputEventRouter* client_;
};
void TouchEventAckQueue::Add(uint32_t touch_event_id,
void TouchEventAckQueue::Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result) {
AckData data = {
touch_event_id, target_view, root_view, touch_event_source,
touch_event_ack_status, ack_result};
AckData data = {touch_event,
target_view,
root_view,
touch_event_source,
touch_event_ack_status,
ack_result};
ack_queue_.push_back(data);
if (touch_event_ack_status == TouchEventAckStatus::TouchEventAcked)
ProcessAckedTouchEvents();
ReportTouchEventAckQueueUmaStats();
}
void TouchEventAckQueue::Add(uint32_t touch_event_id,
void TouchEventAckQueue::Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source) {
Add(touch_event_id, target_view, root_view, touch_event_source,
Add(touch_event, target_view, root_view, touch_event_source,
TouchEventAckStatus::TouchEventNotAcked, INPUT_EVENT_ACK_STATE_UNKNOWN);
}
void TouchEventAckQueue::MarkAcked(uint32_t touch_event_id,
void TouchEventAckQueue::MarkAcked(const TouchEventWithLatencyInfo& touch_event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view) {
auto it = find_if(ack_queue_.begin(), ack_queue_.end(),
[touch_event_id](AckData data) {
return data.touch_event_id == touch_event_id;
[touch_event](AckData data) {
return data.touch_event.event.unique_touch_event_id ==
touch_event.event.unique_touch_event_id;
});
if (it == ack_queue_.end())
if (it == ack_queue_.end()) {
// If the touch-event was sent directly to the view without going through
// RenderWidgetHostInputEventRouter, as is the case with AndroidWebView,
// then we must ack it directly.
DCHECK(target_view);
target_view->ProcessAckedTouchEvent(touch_event, ack_result);
return;
}
DCHECK(it->touch_event_ack_status != TouchEventAckStatus::TouchEventAcked);
DCHECK(target_view && target_view == it->target_view);
it->touch_event = touch_event;
it->touch_event_ack_status = TouchEventAckStatus::TouchEventAcked;
it->ack_result = ack_result;
ProcessAckedTouchEvents();
......@@ -176,15 +192,22 @@ void TouchEventAckQueue::ProcessAckedTouchEvents() {
if (ack_queue_.empty())
return;
// TODO(wjmaclean): modify the following loop to actually forward the acks
// to the root_view. Must verify that the root_view at the time the event
// was registered still exists.
TouchEmulator* touch_emulator =
client_->has_touch_emulator() ? client_->GetTouchEmulator() : nullptr;
while (!ack_queue_.empty() && ack_queue_.front().touch_event_ack_status ==
TouchEventAckStatus::TouchEventAcked) {
// TODO(wjmaclean): We will eventually ack touch events to the root_view
// here. Each ack will require confirmation that the touch event's root
// view (at the time of event dispatch) is still valid, otherwise we just
// discard the ack.
TouchEventAckQueue::AckData ack_data = ack_queue_.front();
if ((!touch_emulator ||
!touch_emulator->HandleTouchEventAck(ack_data.touch_event.event,
ack_data.ack_result)) &&
(client_->IsViewInMap(ack_data.root_view) ||
client_->ViewMapIsEmpty())) {
// Forward acked event and result to the root view associated with the
// event. The view map is only empty for AndroidWebView.
ack_data.root_view->ProcessAckedTouchEvent(ack_data.touch_event,
ack_data.ack_result);
}
// Discard the event from the queue.
ack_queue_.pop_front();
}
}
......@@ -335,7 +358,7 @@ RenderWidgetHostInputEventRouter::RenderWidgetHostInputEventRouter()
gesture_pinch_did_send_scroll_begin_(false),
event_targeter_(std::make_unique<RenderWidgetTargeter>(this)),
use_viz_hit_test_(features::IsVizHitTestingEnabled()),
touch_event_ack_queue_(new TouchEventAckQueue),
touch_event_ack_queue_(new TouchEventAckQueue(this)),
weak_ptr_factory_(this) {}
RenderWidgetHostInputEventRouter::~RenderWidgetHostInputEventRouter() {
......@@ -764,12 +787,9 @@ void RenderWidgetHostInputEventRouter::DispatchTouchEvent(
: TouchEventAckQueue::TouchEventSource::SystemTouchEvent;
if (!touch_target_.target) {
touch_event_ack_queue_->Add(
touch_event.unique_touch_event_id, nullptr, root_view, event_source,
TouchEventAckQueue::TouchEventAckStatus::TouchEventAcked,
TouchEventWithLatencyInfo(touch_event), nullptr, root_view,
event_source, TouchEventAckQueue::TouchEventAckStatus::TouchEventAcked,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
TouchEventWithLatencyInfo touch_with_latency(touch_event, latency);
root_view->ProcessAckedTouchEvent(touch_with_latency,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
......@@ -777,7 +797,7 @@ void RenderWidgetHostInputEventRouter::DispatchTouchEvent(
CancelScrollBubblingIfConflicting(touch_target_.target);
}
touch_event_ack_queue_->Add(touch_event.unique_touch_event_id,
touch_event_ack_queue_->Add(TouchEventWithLatencyInfo(touch_event),
touch_target_.target, root_view, event_source);
blink::WebTouchEvent event(touch_event);
......@@ -792,24 +812,7 @@ void RenderWidgetHostInputEventRouter::ProcessAckedTouchEvent(
const TouchEventWithLatencyInfo& event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* view) {
touch_event_ack_queue_->MarkAcked(event.event.unique_touch_event_id,
ack_result, view);
// TODO(wjmaclean): Eventually we will keep track of which outgoing touch
// events are emulated and which aren't, so the decision to hand off to the
// touch emulator won't just rely on the existence of the touch emulator.
if (touch_emulator_ &&
touch_emulator_->HandleTouchEventAck(event.event, ack_result)) {
return;
}
if (!view)
return;
auto* root_view = view->GetRootView();
if (!root_view)
return;
root_view->ProcessAckedTouchEvent(event, ack_result);
touch_event_ack_queue_->MarkAcked(event, ack_result, view);
}
void RenderWidgetHostInputEventRouter::RouteTouchEvent(
......@@ -1299,6 +1302,10 @@ bool RenderWidgetHostInputEventRouter::IsViewInMap(
return is_registered(view->GetFrameSinkId());
}
bool RenderWidgetHostInputEventRouter::ViewMapIsEmpty() const {
return owner_map_.empty();
}
void RenderWidgetHostInputEventRouter::DispatchTouchscreenGestureEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
......
......@@ -149,6 +149,10 @@ class CONTENT_EXPORT RenderWidgetHostInputEventRouter
// flinging.
void StopFling();
// Returns true if |view| is currently registered in the router's owners map.
bool IsViewInMap(const RenderWidgetHostViewBase* view) const;
bool ViewMapIsEmpty() const;
// TouchEmulatorClient:
void ForwardEmulatedGestureEvent(
const blink::WebGestureEvent& event) override;
......@@ -199,7 +203,6 @@ class CONTENT_EXPORT RenderWidgetHostInputEventRouter
viz::EventSource source,
gfx::PointF* transformed_point) const;
bool IsViewInMap(const RenderWidgetHostViewBase* view) const;
void RouteTouchscreenGestureEvent(RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent* event,
const ui::LatencyInfo& latency);
......
......@@ -63,6 +63,7 @@
#include "content/browser/renderer_host/input/synthetic_gesture.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target.h"
#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
#include "content/browser/renderer_host/input/synthetic_touchscreen_pinch_gesture.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
......@@ -441,7 +442,6 @@ class TestInterstitialDelegate : public InterstitialPageDelegate {
std::string GetHTMLContents() override { return "<p>Interstitial</p>"; }
};
#if defined(OS_ANDROID)
bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
std::unique_ptr<base::Value> value = base::JSONReader::Read(str);
if (!value)
......@@ -458,7 +458,6 @@ bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
point->set_y(y);
return true;
}
#endif // defined (OS_ANDROID)
void OpenURLBlockUntilNavigationComplete(Shell* shell, const GURL& url) {
WaitForLoadStop(shell->web_contents());
......@@ -10750,6 +10749,163 @@ IN_PROC_BROWSER_TEST_F(TouchSelectionControllerClientAndroidSiteIsolationTest,
}
#endif // defined(OS_ANDROID)
class TouchEventObserver : public RenderWidgetHost::InputEventObserver {
public:
TouchEventObserver(std::vector<uint32_t>* outgoing_touch_event_ids,
std::vector<uint32_t>* acked_touch_event_ids)
: outgoing_touch_event_ids_(outgoing_touch_event_ids),
acked_touch_event_ids_(acked_touch_event_ids) {}
void OnInputEvent(const blink::WebInputEvent& event) override {
if (!blink::WebInputEvent::IsTouchEventType(event.GetType()))
return;
const auto& touch_event = static_cast<const blink::WebTouchEvent&>(event);
outgoing_touch_event_ids_->push_back(touch_event.unique_touch_event_id);
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent& event) override {
if (!blink::WebInputEvent::IsTouchEventType(event.GetType()))
return;
const auto& touch_event = static_cast<const blink::WebTouchEvent&>(event);
acked_touch_event_ids_->push_back(touch_event.unique_touch_event_id);
}
private:
std::vector<uint32_t>* outgoing_touch_event_ids_;
std::vector<uint32_t>* acked_touch_event_ids_;
DISALLOW_COPY_AND_ASSIGN(TouchEventObserver);
};
// This test verifies the ability of the TouchEventAckQueue to send TouchEvent
// acks to the root view in the correct order in the event of a slow renderer.
// This test uses a main-frame which acks instantly (no touch handler), and a
// child frame which acks very slowly. A synthetic gesture tap is sent to the
// child first, then the main frame. In this scenario, we expect the touch
// events sent to the main-frame to ack first, which will be problematic if
// the events are acked to the GestureRecognizer out of order.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, TouchEventAckQueueOrdering) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1u, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
// Add a *slow* & passive touch event handler in the child. It needs to be
// passive to ensure TouchStart doesn't get acked until after the touch
// handler completes.
EXPECT_TRUE(ExecuteScript(child_node,
"touch_event_count = 0;\
function touch_handler(ev) {\
var start = Date.now();\
while (Date.now() < start + 1000) {}\
touch_event_count++;\
}\
document.body.addEventListener('touchstart', touch_handler,\
{ passive : false });\
document.body.addEventListener('touchend', touch_handler,\
{ passive : false });"));
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
auto* root_host = static_cast<RenderWidgetHostImpl*>(
root->current_frame_host()->GetRenderWidgetHost());
auto* child_host = static_cast<RenderWidgetHostImpl*>(
child_node->current_frame_host()->GetRenderWidgetHost());
// Create InputEventObserver for both, with access to common queue for
// logging.
std::vector<uint32_t> outgoing_touch_event_ids;
std::vector<uint32_t> acked_touch_event_ids;
TouchEventObserver parent_touch_event_observer(&outgoing_touch_event_ids,
&acked_touch_event_ids);
TouchEventObserver child_touch_event_observer(&outgoing_touch_event_ids,
&acked_touch_event_ids);
root_host->AddInputEventObserver(&parent_touch_event_observer);
child_host->AddInputEventObserver(&child_touch_event_observer);
InputEventAckWaiter root_ack_waiter(root_host,
blink::WebInputEvent::kTouchEnd);
InputEventAckWaiter child_ack_waiter(child_host,
blink::WebInputEvent::kTouchEnd);
InputEventAckWaiter child_gesture_tap_ack_waiter(
child_host, blink::WebInputEvent::kGestureTap);
// Create GestureTap for child.
gfx::PointF child_tap_point;
{
// We need to know the center of the child's body, but in root view
// coordinates.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child_node,
"var rect = document.body.getBoundingClientRect();\
var point = {\
x: rect.left + rect.width / 2,\
y: rect.top + rect.height / 2\
};\
window.domAutomationController.send(JSON.stringify(point));",
&str));
ConvertJSONToPoint(str, &child_tap_point);
child_tap_point = child_node->current_frame_host()
->GetView()
->TransformPointToRootCoordSpaceF(child_tap_point);
}
SyntheticTapGestureParams child_tap_params;
child_tap_params.position = child_tap_point;
child_tap_params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
child_tap_params.duration_ms = 300.f;
auto child_tap_gesture =
std::make_unique<SyntheticTapGesture>(child_tap_params);
// Create GestureTap for root.
SyntheticTapGestureParams root_tap_params;
root_tap_params.position = gfx::PointF(5.f, 5.f);
root_tap_params.duration_ms = 300.f;
root_tap_params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
auto root_tap_gesture =
std::make_unique<SyntheticTapGesture>(root_tap_params);
// Queue both GestureTaps, child first.
root_host->QueueSyntheticGesture(
std::move(child_tap_gesture),
base::BindOnce([](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
}));
root_host->QueueSyntheticGesture(
std::move(root_tap_gesture),
base::BindOnce([](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
}));
root_ack_waiter.Wait();
child_ack_waiter.Wait();
// Verify the child did receive two touch events.
int child_touch_event_count = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
child_node, "window.domAutomationController.send(touch_event_count);",
&child_touch_event_count));
EXPECT_EQ(2, child_touch_event_count);
// Verify Acks from parent arrive first.
EXPECT_EQ(4u, outgoing_touch_event_ids.size());
EXPECT_EQ(4u, acked_touch_event_ids.size());
EXPECT_EQ(outgoing_touch_event_ids[2], acked_touch_event_ids[0]);
EXPECT_EQ(outgoing_touch_event_ids[3], acked_touch_event_ids[1]);
// Verify no DCHECKs from GestureRecognizer, indicating acks happened in
// order.
child_gesture_tap_ack_waiter.Wait();
}
// This test verifies that the main-frame's page scale factor propagates to
// the compositor layertrees in each of the child processes.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment