CVE-2026-91724
Overview
Background
- `InputRouterImpl`
- the browser-process object that routes input events to the renderer and owns the embedded per-type queues (
GestureEventQueue,MouseWheelEventQueue,TouchpadPinchEventQueue). - Input event ACK
- the acknowledgement for a dispatched input event (returned by the renderer or synthesized by the browser) whose processing invokes client and disposition-handler callbacks.
- `base::WeakPtr` / `base::WeakAutoReset`
- a weak reference that is invalidated when its referent is destroyed;
base::WeakAutoResetrestores a member’s prior value on scope exit only if the object still exists (unlikebase::AutoReset<bool>, which writes back unconditionally). - Reentrant destruction
- an ACK callback can synchronously run UI actions (overscroll navigation, popup dismissal, context-menu loop) that destroy the owning
RenderWidgetHostImpl/InputRouterImpl, and therefore the queue, in the middle of a queue method.
Root Cause Analysis
Each of the three queues, and InputRouterImpl itself, processed an ACK by invoking a client or disposition-handler callback and then continued to dereference this members afterward. Those callbacks can reenter and synchronously destroy the owning RenderWidgetHostImpl / InputRouterImpl — and thus the queue — so the subsequent member accesses (e.g. sent_events_awaiting_ack_.erase, gesture_event_queue_.OnWheelEventAck, pinch_event_awaiting_ack_.reset(), TryForwardNextEventToRenderer()) operate on freed memory, a use-after-free in the browser process. In the gesture-ack drain loop the danger was compounded by base::AutoReset<bool> process_acks(&processing_acks_, true), whose destructor writes back to processing_acks_ after the object is gone.
The fix captures a base::WeakPtr (via a newly added weak_ptr_factory_) before each callback and early-returns when the weak pointer is invalidated, replaces base::AutoReset with base::WeakAutoReset so the write-back is skipped after destruction, and rebinds the pending-ack callbacks from base::Unretained(this) to weak_ptr_factory_.GetWeakPtr(). Because the guard runs before every post-callback member access, the freed this is never touched.
base::WeakPtr (and a base::WeakAutoReset) and bails out before dereferencing the freed this.Attack Path
- Deliver a blocking input event The attacker page induces a gesture, mouse-wheel, or touchpad-pinch event that the browser dispatches to the renderer and tracks while awaiting an ACK.
- ACK invokes a reentrant UI action
When the ACK is processed, a client/disposition callback (
AckGestureEventToClient,OnWheelEventAck,OnGestureEventForPinchAck,OnGestureEventAck) synchronously runs a UI action — overscroll navigation, popup dismissal, or a context-menu loop — that destroys the owningRenderWidgetHostImpl/InputRouterImpland its queues. - Post-callback access on freed object
Control returns into the queue/router, which (pre-patch) continues touching freed members or lets
base::AutoResetwrite back toprocessing_acks_, dereferencing the freedthis— the use-after-free. - Exploitation (inferred) grooming the freed queue/router allocation so it is reused by attacker-controlled data could turn the dangling access into a browser-process read/write primitive; the patch itself does not demonstrate a specific sink, offset, or field.
Impact Assessment
InputRouterImpl-owned queue in the privileged browser process, where reused memory can corrupt live state. Escalation to arbitrary read/write, sandbox escape, or code execution is (inferred) and not demonstrated by the diff. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/input/gesture_event_queue.cc |
modified | |
ifcomponents/input/input_router_impl.cc |
modified | |
ifcomponents/input/mouse_wheel_event_queue.cc |
modified | |
ifcomponents/input/touchpad_pinch_event_queue.cc |
modified | |
ifcontent/browser/renderer_host/input/mock_input_disposition_handler.cc |
modified | |
ifcomponents/input/gesture_event_queue_unittest.cc |
modified | |
TEST_Fcomponents/input/gesture_event_queue_unittest.cc |
modified | |
ifcomponents/input/mouse_wheel_event_queue_unittest.cc |
modified | |
TEST_Fcomponents/input/mouse_wheel_event_queue_unittest.cc |
modified | |
ifcomponents/input/touchpad_pinch_event_queue_unittest.cc |
modified |
Files Changed
components/input/gesture_event_queue.hcomponents/input/mouse_wheel_event_queue.cccontent/browser/renderer_host/input/mock_input_disposition_handler.cccomponents/input/input_router_impl.cccomponents/input/gesture_event_queue.cccomponents/input/mouse_wheel_event_queue_unittest.cccomponents/input/mouse_wheel_event_queue.hcontent/browser/renderer_host/input/mock_input_disposition_handler.hcomponents/input/touchpad_pinch_event_queue.hcomponents/input/touchpad_pinch_event_queue_unittest.cccontent/browser/renderer_host/input/input_router_impl_unittest.cccomponents/input/touchpad_pinch_event_queue.cccomponents/input/gesture_event_queue_unittest.cc
Audit Directions
- Callback-then-member-accessaudit any browser-process code that invokes an interface/client/observer callback and then reads or writes
thisstate, since such callbacks can reenter and destroy the owning object; require abase::WeakPtrguard between the call and the access. - `base::Unretained(this)` on async/pending callbacksflag
base::BindOnce(..., base::Unretained(this))for callbacks whose invocation is not lifetime-bound to the object (e.g. renderer ACKs), and preferweak_ptr_factory_.GetWeakPtr(). - `base::AutoReset` across reentrant callscheck scopes where a
base::AutoReset<bool>reentrancy guard spans a callback that may free the object, as its destructor writes to a freed member;base::WeakAutoResetis the safe replacement.
Patch
From 8e8909f79833d1f1ec59c3cde98ce29e5f97e201 Mon Sep 17 00:00:00 2001 From: Robert Flack <[email protected]> Date: Tue, 01 Sep 2026 13:50:37 -0700 Subject: [PATCH] Harden GestureEventQueue, MouseWheelEventQueue, and TouchpadPinchEventQueue Follow up to crrev.com/c/7776327 and crrev.com/c/7919026. This change hardens the remaining input queues embedded in InputRouterImpl against synchronous destruction during ACK processing if an ACK callback triggers synchronous UI actions (e.g. overscroll navigation, popup dismissals, or context menu loops) that destroy the owning RenderWidgetHostImpl / InputRouterImpl. Bug: 554953456, 552283275 Change-Id: I9b65eeeb1f9478275ae29bb52afc41de8c6011ed Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8328368 Commit-Queue: Robert Flack <[email protected]> Reviewed-by: Dave Tapuska <[email protected]> Cr-Commit-Position: refs/heads/main@{#1690090} --- diff --git a/components/input/gesture_event_queue.cc b/components/input/gesture_event_queue.cc index 9444a4d..098fa35 100644 --- a/components/input/gesture_event_queue.cc +++ b/components/input/gesture_event_queue.cc @@ -6,6 +6,7 @@ #include "base/auto_reset.h" #include "base/logging.h" +#include "base/memory/weak_auto_reset.h" #include "base/trace_event/trace_event.h" #include "components/input/touchpad_tap_suppression_controller.h" #include "components/input/touchscreen_tap_suppression_controller.h" @@ -223,7 +224,10 @@ if (processing_acks_) { return; } - base::AutoReset<bool> process_acks(&processing_acks_, true); + base::WeakAutoReset reset_processing_acks( + weak_ptr_factory_.GetWeakPtr(), &GestureEventQueue::processing_acks_, + true); + auto weak_this = weak_ptr_factory_.GetWeakPtr(); while (!sent_events_awaiting_ack_.empty()) { auto iter = sent_events_awaiting_ack_.begin(); if (iter->ack_state() == blink::mojom::InputEventResultState::kUnknown) { @@ -233,6 +237,9 @@ sent_events_awaiting_ack_.erase(iter); AckGestureEventToClient(event, event.ack_source(), event.ack_state()); + if (!weak_this) { + return; + } } } diff --git a/components/input/gesture_event_queue.h b/components/input/gesture_event_queue.h index 7a610d0..db528c9 100644 --- a/components/input/gesture_event_queue.h +++ b/components/input/gesture_event_queue.h @@ -10,6 +10,7 @@ #include "base/component_export.h" #include "base/containers/circular_deque.h" #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "components/input/dispatch_to_renderer_callback.h" @@ -246,6 +247,8 @@ // True when the last GSE event is either in the debouncing_deferral_queue_ or // pushed to the queue and dropped from it later on. bool scroll_end_filtered_by_deboucing_deferral_queue_ = false; + + base::WeakPtrFactory<GestureEventQueue> weak_ptr_factory_{this}; }; } // namespace input diff --git a/components/input/input_router_impl.cc b/components/input/input_router_impl.cc index 2d3de8fb..51baeda2 100644 --- a/components/input/input_router_impl.cc +++ b/components/input/input_router_impl.cc @@ -553,7 +553,11 @@ const GestureEventWithLatencyInfo& event, blink::mojom::InputEventResultSource ack_source, blink::mojom::InputEventResultState ack_result) { + auto weak_this = weak_ptr_factory_.GetWeakPtr(); touch_event_queue_.OnGestureEventAck(event, ack_result); + if (!weak_this) { + return; + } disposition_handler_->OnGestureEventAck(event, ack_source, ack_result); } @@ -588,7 +592,11 @@ const MouseWheelEventWithLatencyInfo& event, blink::mojom::InputEventResultSource ack_source, blink::mojom::InputEventResultState ack_result) { + auto weak_this = weak_ptr_factory_.GetWeakPtr(); disposition_handler_->OnWheelEventAck(event, ack_source, ack_result); + if (!weak_this) { + return; + } gesture_event_queue_.OnWheelEventAck(event, ack_source, ack_result); } diff --git a/components/input/mouse_wheel_event_queue.cc b/components/input/mouse_wheel_event_queue.cc index f5468fd..8a8dea1 100644 --- a/components/input/mouse_wheel_event_queue.cc +++ b/components/input/mouse_wheel_event_queue.cc @@ -132,8 +132,12 @@ return; event_sent_for_gesture_ack_->latency.AddNewLatencyFrom(ack_event.latency); + auto weak_this = weak_ptr_factory_.GetWeakPtr(); client_->OnMouseWheelEventAck(*event_sent_for_gesture_ack_, ack_source, ack_result); + if (!weak_this) { + return; + } // If event wasn't consumed then generate a gesture scroll for it. if (CanGenerateGestureScroll(ack_result)) { @@ -311,7 +315,7 @@ client_->SendMouseWheelEventImmediately( *event_sent_for_gesture_ack_, base::BindOnce(&MouseWheelEventQueue::ProcessMouseWheelAck, - base::Unretained(this)), + weak_ptr_factory_.GetWeakPtr()), event_sent_for_gesture_ack_->dispatch_callback); } diff --git a/components/input/mouse_wheel_event_queue.h b/components/input/mouse_wheel_event_queue.h index d7c13d1..e6d136d9 100644 --- a/components/input/mouse_wheel_event_queue.h +++ b/components/input/mouse_wheel_event_queue.h @@ -11,6 +11,7 @@ #include "base/containers/circular_deque.h" #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "components/input/dispatch_to_renderer_callback.h" #include "components/input/event_with_latency_info.h" #include "third_party/blink/public/common/input/web_input_event.h" @@ -121,6 +122,8 @@ bool send_wheel_events_async_; blink::WebGestureDevice scrolling_device_; + + base::WeakPtrFactory<MouseWheelEventQueue> weak_ptr_factory_{this}; }; } // namespace input diff --git a/components/input/touchpad_pinch_event_queue.cc b/components/input/touchpad_pinch_event_queue.cc index af8e12f..c5f2c17 100644 --- a/components/input/touchpad_pinch_event_queue.cc +++ b/components/input/touchpad_pinch_event_queue.cc @@ -148,8 +148,12 @@ (ack_result == blink::mojom::InputEventResultState::kConsumed); pinch_event_awaiting_ack_->latency.AddNewLatencyFrom(ack_event.latency); + auto weak_this = weak_ptr_factory_.GetWeakPtr(); client_->OnGestureEventForPinchAck(*pinch_event_awaiting_ack_, ack_source, ack_result); + if (!weak_this) { + return; + } pinch_event_awaiting_ack_.reset(); TryForwardNextEventToRenderer(); @@ -167,13 +171,22 @@ if (pinch_event_awaiting_ack_->event.GetType() == blink::WebInputEvent::Type::kGesturePinchBegin) { + auto weak_this = weak_ptr_factory_.GetWeakPtr(); + auto dispatch_callback = + std::move(pinch_event_awaiting_ack_->dispatch_callback); + const blink::WebGestureEvent event = pinch_event_awaiting_ack_->event; client_->OnGestureEventForPinchAck( *pinch_event_awaiting_ack_, blink::mojom::InputEventResultSource::kBrowser, blink::mojom::InputEventResultState::kIgnored); - std::move(pinch_event_awaiting_ack_->dispatch_callback) - .Run(pinch_event_awaiting_ack_->event, - DispatchToRendererResult::kNotDispatched); + if (!weak_this) { + return; + } + std::move(dispatch_callback) + .Run(event, DispatchToRendererResult::kNotDispatched); + if (!weak_this) { + return; + } pinch_event_awaiting_ack_.reset(); TryForwardNextEventToRenderer(); return; @@ -215,7 +228,7 @@ client_->SendMouseWheelEventForPinchImmediately( pinch_event_awaiting_ack_->event, synthetic_wheel, base::BindOnce(&TouchpadPinchEventQueue::ProcessMouseWheelAck, - base::Unretained(this)), + weak_ptr_factory_.GetWeakPtr()), pinch_event_awaiting_ack_->dispatch_callback); } diff --git a/components/input/touchpad_pinch_event_queue.h b/components/input/touchpad_pinch_event_queue.h index b6a559b..d3c32cd3 100644 --- a/components/input/touchpad_pinch_event_queue.h +++ b/components/input/touchpad_pinch_event_queue.h @@ -11,6 +11,7 @@ #include "base/component_export.h" #include "base/containers/circular_deque.h" #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "components/input/dispatch_to_renderer_callback.h" #include "components/input/event_with_latency_info.h" #include "third_party/blink/public/common/input/web_input_event.h" @@ -80,6 +81,8 @@ base::circular_deque<std::unique_ptr<QueuedTouchpadPinchEvent>> pinch_queue_; std::unique_ptr<QueuedTouchpadPinchEvent> pinch_event_awaiting_ack_; std::optional<bool> first_event_prevented_; + + base::WeakPtrFactory<TouchpadPinchEventQueue> weak_ptr_factory_{this}; }; } // namespace input diff --git a/content/browser/renderer_host/input/mock_input_disposition_handler.cc b/content/browser/renderer_host/input/mock_input_disposition_handler.cc index 56c2588..9dd1908 100644 --- a/content/browser/renderer_host/input/mock_input_disposition_handler.cc +++ b/content/browser/renderer_host/input/mock_input_disposition_handler.cc @@ -40,6 +40,9 @@ acked_wheel_event_ = event.event; acked_wheel_event_state_ = ack_result; RecordAckCalled(event.event.GetType(), ack_result); + if (on_wheel_event_ack_closure_) { + std::move(on_wheel_event_ack_closure_).Run(); + } } void MockInputDispositionHandler::OnTouchEventAck( @@ -73,6 +76,9 @@ VLOG(1) << __FUNCTION__ << " called!"; acked_gesture_event_ = event.event; RecordAckCalled(event.event.GetType(), ack_result); + if (on_gesture_event_ack_closure_) { + std::move(on_gesture_event_ack_closure_).Run(); + } } size_t MockInputDispositionHandler::GetAndResetAckCount() { diff --git a/content/browser/renderer_host/input/mock_input_disposition_handler.h b/content/browser/renderer_host/input/mock_input_disposition_handler.h index c2e5528..5c3d709 100644 --- a/content/browser/renderer_host/input/mock_input_disposition_handler.h +++ b/content/browser/renderer_host/input/mock_input_disposition_handler.h @@ -57,6 +57,14 @@ on_touch_event_ack_closure_ = std::move(closure); } + void set_on_gesture_event_ack_closure(base::OnceClosure closure) { + on_gesture_event_ack_closure_ = std::move(closure); + } + + void set_on_wheel_event_ack_closure(base::OnceClosure closure) { + on_wheel_event_ack_closure_ = std::move(closure); + } + blink::mojom::InputEventResultState ack_state() const { return ack_state_; } blink::mojom::InputEventResultState acked_wheel_event_state() const { @@ -105,6 +113,8 @@ std::unique_ptr<input::GestureEventWithLatencyInfo> gesture_followup_event_; std::unique_ptr<input::TouchEventWithLatencyInfo> touch_followup_event_; base::OnceClosure on_touch_event_ack_closure_; + base::OnceClosure on_gesture_event_ack_closure_; + base::OnceClosure on_wheel_event_ack_closure_; }; } // namespace content diff --git a/components/input/gesture_event_queue_unittest.cc b/components/input/gesture_event_queue_unittest.cc index 72b7f345..8ff53f8 100644 --- a/components/input/gesture_event_queue_unittest.cc +++ b/components/input/gesture_event_queue_unittest.cc @@ -88,6 +88,10 @@ blink::mojom::InputEventResultState ack_result) override { ++acked_gesture_event_count_; last_acked_event_ = event.event; + if (destroy_queue_on_ack_) { + queue_.reset(); + return; + } if (sync_followup_event_) { auto sync_followup_event = std::move(sync_followup_event_); SimulateGestureEvent(*sync_followup_event); @@ -242,6 +246,10 @@ GestureEventQueue* queue() const { return queue_.get(); } + void set_destroy_queue_on_ack(bool destroy) { + destroy_queue_on_ack_ = destroy; + } + private: base::test::SingleThreadTaskEnvironment task_environment_; std::unique_ptr<GestureEventQueue> queue_; @@ -250,6 +258,7 @@ WebGestureEvent last_acked_event_; std::unique_ptr<blink::mojom::InputEventResultState> sync_ack_result_; std::unique_ptr<WebGestureEvent> sync_followup_event_; + bool destroy_queue_on_ack_ = false; base::test::ScopedFeatureList feature_list_; }; @@ -644,4 +653,16 @@ EXPECT_EQ(0U, GetAndResetSentGestureEventCount()); } +TEST_F(GestureEventQueueTest, SynchronousDestructionDuringAck) { + SimulateGestureEvent(WebInputEvent::Type::kGestureTapDown, + blink::WebGestureDevice::kTouchscreen); + EXPECT_EQ(1U, GestureEventQueueSize()); + + set_destroy_queue_on_ack(true); + SendInputEventACK(WebInputEvent::Type::kGestureTapDown, + blink::mojom::InputEventResultState::kConsumed); + + EXPECT_EQ(nullptr, queue()); +} + } // namespace input diff --git a/components/input/mouse_wheel_event_queue_unittest.cc b/components/input/mouse_wheel_event_queue_unittest.cc index 901e4a0e..ed28ebb 100644 --- a/components/input/mouse_wheel_event_queue_unittest.cc +++ b/components/input/mouse_wheel_event_queue_unittest.cc @@ -198,6 +198,10 @@ ++acked_event_count_; last_acked_event_ = event.event; last_acked_event_state_ = ack_result; + if (destroy_queue_on_ack_) { + queue_.reset(); + return; + } } bool IsWheelScrollInProgress() override { @@ -402,6 +406,10 @@ EXPECT_EQ(1U, GetAndResetSentEventCount()); } + void set_destroy_queue_on_ack(bool destroy) { + destroy_queue_on_ack_ = destroy; + } + base::test::SingleThreadTaskEnvironment task_environment_; std::unique_ptr<MouseWheelEventQueue> queue_; std::vector<std::unique_ptr<WebInputEvent>> sent_events_; @@ -409,6 +417,7 @@ size_t acked_event_count_; blink::mojom::InputEventResultState last_acked_event_state_; WebMouseWheelEvent last_acked_event_; + bool destroy_queue_on_ack_ = false; private: bool is_wheel_scroll_in_progress_ = false; @@ -709,4 +718,18 @@ EXPECT_EQ(2U, GetAndResetSentEventCount()); } #endif +TEST_F(MouseWheelEventQueueTest, SynchronousDestructionDuringAck) { + SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX, + kWheelScrollGlobalY, 1, 1, 0, false, + WebMouseWheelEvent::kPhaseBegan, + WebMouseWheelEvent::kPhaseNone); + EXPECT_EQ(0U, queued_event_count()); + EXPECT_TRUE(event_in_flight()); + + set_destroy_queue_on_ack(true); + SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed); + + EXPECT_EQ(nullptr, queue_); +} + } // namespace input diff --git a/components/input/touchpad_pinch_event_queue_unittest.cc b/components/input/touchpad_pinch_event_queue_unittest.cc index 49e5d9e..3ce1231 100644 --- a/components/input/touchpad_pinch_event_queue_unittest.cc +++ b/components/input/touchpad_pinch_event_queue_unittest.cc @@ -127,11 +127,19 @@ blink::mojom::InputEventResultSource ack_source, blink::mojom::InputEventResultState ack_result) override { mock_client_.OnGestureEventForPinchAck(event, ack_source, ack_result); + if (destroy_queue_on_ack_) { + queue_.reset(); + } + } + + void set_destroy_queue_on_ack(bool destroy) { + destroy_queue_on_ack_ = destroy; } testing::StrictMock<MockTouchpadPinchEventQueueClient> mock_client_; std::unique_ptr<TouchpadPinchEventQueue> queue_; base::circular_deque<HandleEventCallback> callbacks_; + bool destroy_queue_on_ack_ = false; }; ... (file diff truncated)
Regression Test / PoC
diff --git a/components/input/gesture_event_queue_unittest.cc b/components/input/gesture_event_queue_unittest.cc
index 72b7f345..8ff53f8 100644
--- a/components/input/gesture_event_queue_unittest.cc
+++ b/components/input/gesture_event_queue_unittest.cc
@@ -88,6 +88,10 @@
blink::mojom::InputEventResultState ack_result) override {
++acked_gesture_event_count_;
last_acked_event_ = event.event;
+ if (destroy_queue_on_ack_) {
+ queue_.reset();
+ return;
+ }
if (sync_followup_event_) {
auto sync_followup_event = std::move(sync_followup_event_);
SimulateGestureEvent(*sync_followup_event);
@@ -242,6 +246,10 @@
GestureEventQueue* queue() const { return queue_.get(); }
+ void set_destroy_queue_on_ack(bool destroy) {
+ destroy_queue_on_ack_ = destroy;
+ }
+
private:
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<GestureEventQueue> queue_;
@@ -250,6 +258,7 @@
WebGestureEvent last_acked_event_;
std::unique_ptr<blink::mojom::InputEventResultState> sync_ack_result_;
std::unique_ptr<WebGestureEvent> sync_followup_event_;
+ bool destroy_queue_on_ack_ = false;
base::test::ScopedFeatureList feature_list_;
};
@@ -644,4 +653,16 @@
EXPECT_EQ(0U, GetAndResetSentGestureEventCount());
}
+TEST_F(GestureEventQueueTest, SynchronousDestructionDuringAck) {
+ SimulateGestureEvent(WebInputEvent::Type::kGestureTapDown,
+ blink::WebGestureDevice::kTouchscreen);
+ EXPECT_EQ(1U, GestureEventQueueSize());
+
+ set_destroy_queue_on_ack(true);
+ SendInputEventACK(WebInputEvent::Type::kGestureTapDown,
+ blink::mojom::InputEventResultState::kConsumed);
+
+ EXPECT_EQ(nullptr, queue());
+}
+
} // namespace input
diff --git a/components/input/mouse_wheel_event_queue_unittest.cc b/components/input/mouse_wheel_event_queue_unittest.cc
index 901e4a0e..ed28ebb 100644
--- a/components/input/mouse_wheel_event_queue_unittest.cc
+++ b/components/input/mouse_wheel_event_queue_unittest.cc
@@ -198,6 +198,10 @@
++acked_event_count_;
last_acked_event_ = event.event;
last_acked_event_state_ = ack_result;
+ if (destroy_queue_on_ack_) {
+ queue_.reset();
+ return;
+ }
}
bool IsWheelScrollInProgress() override {
@@ -402,6 +406,10 @@
EXPECT_EQ(1U, GetAndResetSentEventCount());
}
+ void set_destroy_queue_on_ack(bool destroy) {
+ destroy_queue_on_ack_ = destroy;
+ }
+
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<MouseWheelEventQueue> queue_;
std::vector<std::unique_ptr<WebInputEvent>> sent_events_;
@@ -409,6 +417,7 @@
size_t acked_event_count_;
blink::mojom::InputEventResultState last_acked_event_state_;
WebMouseWheelEvent last_acked_event_;
+ bool destroy_queue_on_ack_ = false;
private:
bool is_wheel_scroll_in_progress_ = false;
@@ -709,4 +718,18 @@
EXPECT_EQ(2U, GetAndResetSentEventCount());
}
#endif
+TEST_F(MouseWheelEventQueueTest, SynchronousDestructionDuringAck) {
+ SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
+ kWheelScrollGlobalY, 1, 1, 0, false,
+ WebMouseWheelEvent::kPhaseBegan,
+ WebMouseWheelEvent::kPhaseNone);
+ EXPECT_EQ(0U, queued_event_count());
+ EXPECT_TRUE(event_in_flight());
+
+ set_destroy_queue_on_ack(true);
+ SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
+
+ EXPECT_EQ(nullptr, queue_);
+}
+
} // namespace input
diff --git a/components/input/touchpad_pinch_event_queue_unittest.cc b/components/input/touchpad_pinch_event_queue_unittest.cc
index 49e5d9e..3ce1231 100644
--- a/components/input/touchpad_pinch_event_queue_unittest.cc
+++ b/components/input/touchpad_pinch_event_queue_unittest.cc
@@ -127,11 +127,19 @@
blink::mojom::InputEventResultSource ack_source,
blink::mojom::InputEventResultState ack_result) override {
mock_client_.OnGestureEventForPinchAck(event, ack_source, ack_result);
+ if (destroy_queue_on_ack_) {
+ queue_.reset();
+ }
+ }
+
+ void set_destroy_queue_on_ack(bool destroy) {
+ destroy_queue_on_ack_ = destroy;
}
testing::StrictMock<MockTouchpadPinchEventQueueClient> mock_client_;
std::unique_ptr<TouchpadPinchEventQueue> queue_;
base::circular_deque<HandleEventCallback> callbacks_;
+ bool destroy_queue_on_ack_ = false;
};
MATCHER_P(EventHasType,
@@ -630,4 +638,42 @@
blink::mojom::InputEventResultState::kIgnored);
}
+TEST_F(TouchpadPinchEventQueueTest, SynchronousDestructionDuringPinchBeginAck) {
+ set_destroy_queue_on_ack(true);
+ EXPECT_CALL(mock_client_,
+ OnGestureEventForPinchAck(
+ EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
+ blink::mojom::InputEventResultSource::kBrowser,
+ blink::mojom::InputEventResultState::kIgnored));
+ QueuePinchBegin();
+ EXPECT_EQ(nullptr, queue_);
+}
+
+TEST_F(TouchpadPinchEventQueueTest,
+ SynchronousDestructionDuringPinchUpdateAck) {
+ ::testing::InSequence sequence;
+ EXPECT_CALL(mock_client_,
+ OnGestureEventForPinchAck(
+ EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
+ blink::mojom::InputEventResultSource::kBrowser,
+ blink::mojom::InputEventResultState::kIgnored));
+ EXPECT_CALL(mock_client_,
+ SendMouseWheelEventForPinchImmediately(
+ ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
+ EXPECT_CALL(mock_client_,
+ OnGestureEventForPinchAck(
+ EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
+ blink::mojom::InputEventResultSource::kMainThread,
+ blink::mojom::InputEventResultState::kConsumed));
+
+ QueuePinchBegin();
+ QueuePinchUpdate(1.23, false);
+
+ set_destroy_queue_on_ack(true);
+ SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
+ blink::mojom::InputEventResultState::kConsumed);
+
+ EXPECT_EQ(nullptr, queue_);
+}
+
} // namespace input
diff --git a/content/browser/renderer_host/input/input_router_impl_unittest.cc b/content/browser/renderer_host/input/input_router_impl_unittest.cc
index 72cff09..9dcc7ec 100644
--- a/content/browser/renderer_host/input/input_router_impl_unittest.cc
+++ b/content/browser/renderer_host/input/input_router_impl_unittest.cc
@@ -2957,4 +2957,46 @@
EXPECT_EQ(nullptr, input_router_);
}
+TEST_F(InputRouterImplTest, SynchronousDestructionDuringGestureAck) {
+ blink::WebGestureEvent gesture =
+ blink::SyntheticWebGestureEventBuilder::Build(
+ WebInputEvent::Type::kGestureTapDown,
+ blink::WebGestureDevice::kTouchscreen);
+
+ // Set the disposition handler to destroy the input router during ACK.
+ disposition_handler_->set_on_gesture_event_ack_closure(base::BindOnce(
+ [](std::unique_ptr<input::InputRouterImpl>* router) { router->reset(); },
+ &input_router_));
+
+ // Trigger an ACK.
+ input::GestureEventWithLatencyInfo gesture_event(gesture);
+ static_cast<input::GestureEventQueueClient*>(input_router_.get())
+ ->OnGestureEventAck(gesture_event,
+ blink::mojom::InputEventResultSource::kMainThread,
+ blink::mojom::InputEventResultState::kConsumed);
+
+ EXPECT_EQ(nullptr, input_router_);
+}
+
+TEST_F(InputRouterImplTest, SynchronousDestructionDuringWheelAck) {
+ blink::WebMouseWheelEvent wheel =
+ blink::SyntheticWebMouseWheelEventBuilder::Build(
+ 10, 10, 50, 50, 1, 1, 0,
+ ui::ScrollGranularity::kScrollByPrecisePixel);
+
+ // Set the disposition handler to destroy the input router during ACK.
+ disposition_handler_->set_on_wheel_event_ack_closure(base::BindOnce(
+ [](std::unique_ptr<input::InputRouterImpl>* router) { router->reset(); },
+ &input_router_));
+
+ // Trigger an ACK.
+ input::MouseWheelEventWithLatencyInfo wheel_event(wheel);
+ static_cast<input::MouseWheelEventQueueClient*>(input_router_.get())
+ ->OnMouseWheelEventAck(wheel_event,
+ blink::mojom::InputEventResultSource::kMainThread,
+ blink::mojom::InputEventResultState::kConsumed);
+
+ EXPECT_EQ(nullptr, input_router_);
+}
+
} // namespace content