diff --git a/patches/components/script/dom/document/document_event_handler.rs.patch b/patches/components/script/dom/document/document_event_handler.rs.patch index 32b45e7..fc93ff1 100644 --- a/patches/components/script/dom/document/document_event_handler.rs.patch +++ b/patches/components/script/dom/document/document_event_handler.rs.patch @@ -452,13 +452,23 @@ } }, // https://w3c.github.io/pointerevents/#dfn-handle-native-mouse-up -@@ -1211,8 +1559,29 @@ +@@ -1206,13 +1554,38 @@ + } + + /// ++ /// ++ /// Returns whether the menu was shown, which is also the answer to "does this ++ /// input still count as a menu request": a cancelled `contextmenu` means the ++ /// page has claimed the gesture for itself. + fn maybe_show_context_menu( + &self, cx: &mut js::context::JSContext, target: &EventTarget, hit_test_result: &HitTestResult, - input_event: &ConstellationInputEvent, +- ) { + source: ContextMenuSource, - ) { ++ ) -> bool { + // Get pointer-specific values based on the source + let (button, pressed_buttons, pointer_id, pointer_type, modifiers) = match source { + ContextMenuSource::Mouse(input_event) => ( @@ -483,7 +493,7 @@ // let menu_event = PointerEvent::new( cx, -@@ -1227,25 +1596,25 @@ +@@ -1227,25 +1600,25 @@ hit_test_result .point_relative_to_initial_containing_block .to_i32(), @@ -528,8 +538,11 @@ ); menu_event.upcast::().set_composed(true); -@@ -1261,6 +1630,91 @@ +@@ -1259,8 +1632,97 @@ + .embedder_controls() + .show_context_menu(cx.no_gc(), hit_test_result); }; ++ result } + /// Start the long-press timer for context menu detection. @@ -589,9 +602,6 @@ + // Clear the long-press state + *self.long_press_state.borrow_mut() = None; + -+ // Track this touch so we can prevent click on touchend -+ self.context_menu_touch_id.set(Some(touch_id)); -+ + // Hit test at the touch point + let flags = HitTestFlags::empty(); + let Some(hit_test_result) = self.window.hit_test_from_point_in_viewport(flags, point) @@ -609,20 +619,42 @@ + }; + + // Fire the contextmenu PointerEvent with touch-specific values. -+ self.maybe_show_context_menu( ++ let showed_menu = self.maybe_show_context_menu( + cx, + el.upcast(), + &hit_test_result, + ContextMenuSource::Touch(touch_id), + ); ++ ++ // Track this touch so we can prevent the click on touchend, but only when ++ // a menu actually appeared. ++ if showed_menu { ++ self.context_menu_touch_id.set(Some(touch_id)); ++ } + } + fn handle_touch_event( &self, cx: &mut JSContext, -@@ -1268,6 +1722,30 @@ +@@ -1267,7 +1729,46 @@ + event: EmbedderTouchEvent, input_event: &ConstellationInputEvent, ) -> InputEventResult { ++ // Consume the long-press marker up front, before any of the early returns ++ // below. A touch that opened a context menu must not also synthesise a ++ // click, but the flag has to be cleared on every release of that touch, ++ // not only on the ones that reach the end of this function: a release ++ // whose hit test misses, that lands on no element, or whose touch point ++ // has already been dropped all return early, and a flag left set then ++ // silently eats the click of the next touch that reuses this id. ++ let opened_context_menu = matches!( ++ event.event_type, ++ TouchEventType::Up | TouchEventType::Cancel ++ ) && self.context_menu_touch_id.get() == Some(event.touch_id); ++ if opened_context_menu { ++ self.context_menu_touch_id.set(None); ++ } ++ let flags = HitTestFlags::empty(); + // Check if this touch was previously forwarded to an embedded webview. + // If so, continue forwarding to the same webview regardless of current hit test. @@ -651,7 +683,7 @@ // Ignore all incoming events without a hit test. let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event) else { -@@ -1275,6 +1753,19 @@ +@@ -1275,6 +1776,19 @@ return Default::default(); }; @@ -671,7 +703,7 @@ let TouchId(identifier) = event.touch_id; let Some(element) = hit_test_result -@@ -1446,6 +1937,10 @@ +@@ -1446,6 +1960,10 @@ .safe_borrow_mut(cx.no_gc()) .push(Dom::from_ref(&*pointer_touch)); self.set_active_element(&element); @@ -682,7 +714,7 @@ (current_target, pointer_touch) }, _ => { -@@ -1480,15 +1975,47 @@ +@@ -1480,15 +1998,47 @@ ); let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc()); @@ -731,27 +763,20 @@ }, TouchEventType::Down => unreachable!("Should have been handled above"), } -@@ -1536,6 +2063,19 @@ +@@ -1536,6 +2086,12 @@ ); let event = touch_event.upcast::(); event.fire(cx, &touch_dispatch_target); + -+ // If this touch triggered a context menu via long-press, prevent click synthesis -+ if let InputEvent::Touch(ref touch_ev) = input_event.event.event { -+ if matches!( -+ touch_ev.event_type, -+ TouchEventType::Up | TouchEventType::Cancel -+ ) && self.context_menu_touch_id.get() == Some(touch_ev.touch_id) -+ { -+ self.context_menu_touch_id.set(None); -+ return InputEventResult::DefaultPrevented; -+ } ++ // This touch opened a context menu, so it must not also click. ++ if opened_context_menu { ++ return InputEventResult::DefaultPrevented; + } + event.flags().into() } -@@ -1672,6 +2212,16 @@ +@@ -1672,6 +2228,16 @@ return Default::default(); }; @@ -768,7 +793,7 @@ let Some(el) = hit_test_result .node .inclusive_ancestors(ShadowIncluding::Yes) -@@ -2236,6 +2786,26 @@ +@@ -2236,6 +2802,26 @@ return; }