diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 2da5356ff13d5..8070fd1e71d42 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -1298,14 +1298,18 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. void invokeOnSemanticsAction( - int nodeId, ui.SemanticsAction action, ByteData? args) { + int viewId, + int nodeId, + ui.SemanticsAction action, + ByteData? args, + ) { invoke1( _onSemanticsActionEvent, _onSemanticsActionEventZone, ui.SemanticsActionEvent( type: action, nodeId: nodeId, - viewId: 0, // TODO(goderbauer): Wire up the real view ID. + viewId: viewId, arguments: args, ), ); diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index ba7d03086a0c3..4bc0cbe803572 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -271,7 +271,7 @@ class ClickDebouncer { /// Forwards the event to the framework, unless it is deduplicated because /// the corresponding pointer down/up events were recently flushed to the /// framework already. - void onClick(DomEvent click, int semanticsNodeId, bool isListening) { + void onClick(DomEvent click, int viewId, int semanticsNodeId, bool isListening) { assert(click.type == 'click'); if (!isDebouncing) { @@ -280,7 +280,7 @@ class ClickDebouncer { // recently and if the node is currently listening to event, forward to // the framework. if (isListening && _shouldSendClickEventToFramework(click)) { - _sendSemanticsTapToFramework(click, semanticsNodeId); + _sendSemanticsTapToFramework(click, viewId, semanticsNodeId); } return; } @@ -292,7 +292,7 @@ class ClickDebouncer { final DebounceState state = _state!; _state = null; state.timer.cancel(); - _sendSemanticsTapToFramework(click, semanticsNodeId); + _sendSemanticsTapToFramework(click, viewId, semanticsNodeId); } else { // The semantic node is not listening to taps. Flush the pointer events // for the framework to figure out what to do with them. It's possible @@ -301,7 +301,11 @@ class ClickDebouncer { } } - void _sendSemanticsTapToFramework(DomEvent click, int semanticsNodeId) { + void _sendSemanticsTapToFramework( + DomEvent click, + int viewId, + int semanticsNodeId, + ) { // Tappable nodes can be nested inside other tappable nodes. If a click // lands on an inner element and is allowed to propagate, it will also // land on the ancestor tappable, leading to both the descendant and the @@ -312,7 +316,11 @@ class ClickDebouncer { click.stopPropagation(); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsNodeId, ui.SemanticsAction.tap, null); + viewId, + semanticsNodeId, + ui.SemanticsAction.tap, + null, + ); reset(); } diff --git a/lib/web_ui/lib/src/engine/semantics/focusable.dart b/lib/web_ui/lib/src/engine/semantics/focusable.dart index 54bc0d4e55392..745b646d134c0 100644 --- a/lib/web_ui/lib/src/engine/semantics/focusable.dart +++ b/lib/web_ui/lib/src/engine/semantics/focusable.dart @@ -206,6 +206,7 @@ class AccessibilityFocusManager { // shifting focus. if (_lastEvent != AccessibilityFocusManagerEvent.requestedFocus) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + _owner.viewId, target.semanticsNodeId, ui.SemanticsAction.focus, null, diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index c1b6cbd4dc5d6..ef351e660cb2b 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -43,11 +43,11 @@ class SemanticIncrementable extends SemanticRole { if (newInputValue > _currentSurrogateValue) { _currentSurrogateValue += 1; EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.increase, null); + viewId, semanticsObject.id, ui.SemanticsAction.increase, null); } else if (newInputValue < _currentSurrogateValue) { _currentSurrogateValue -= 1; EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.decrease, null); + viewId, semanticsObject.id, ui.SemanticsAction.decrease, null); } })); diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 9679b85f37a21..508e5e6bd954f 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -76,20 +76,20 @@ class SemanticScrollable extends SemanticRole { if (doScrollForward) { if (semanticsObject.isVerticalScrollContainer) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollUp, null); + viewId, semanticsId, ui.SemanticsAction.scrollUp, null); } else { assert(semanticsObject.isHorizontalScrollContainer); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollLeft, null); + viewId, semanticsId, ui.SemanticsAction.scrollLeft, null); } } else { if (semanticsObject.isVerticalScrollContainer) { EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollDown, null); + viewId, semanticsId, ui.SemanticsAction.scrollDown, null); } else { assert(semanticsObject.isHorizontalScrollContainer); EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsId, ui.SemanticsAction.scrollRight, null); + viewId, semanticsId, ui.SemanticsAction.scrollRight, null); } } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index 44a929e797fc5..d35c9c1e69f36 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -444,6 +444,9 @@ abstract class SemanticRole { /// The semantics object managed by this role. final SemanticsObject semanticsObject; + /// The ID of the Flutter View that this [SemanticRole] belongs to. + int get viewId => semanticsObject.owner.viewId; + /// Whether this role accepts pointer events. /// /// This boolean decides whether to set the `pointer-events` CSS property to @@ -764,6 +767,9 @@ abstract class SemanticBehavior { final SemanticRole owner; + /// The ID of the Flutter View that this [SemanticBehavior] belongs to. + int get viewId => semanticsObject.owner.viewId; + /// Whether this role accepts pointer events. /// /// This boolean decides whether to set the `pointer-events` CSS property to @@ -2381,12 +2387,15 @@ class EngineSemantics { /// The top-level service that manages everything semantics-related. class EngineSemanticsOwner { - EngineSemanticsOwner(this.semanticsHost) { + EngineSemanticsOwner(this.viewId, this.semanticsHost) { registerHotRestartListener(() { _rootSemanticsElement?.remove(); }); } + /// The ID of the Flutter View that this semantics owner belongs to. + final int viewId; + /// The permanent element in the view's DOM structure that hosts the semantics /// tree. /// diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 544ad1475a45b..aea41d9e9ddad 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -46,6 +46,7 @@ class Tappable extends SemanticBehavior { _clickListener = createDomEventListener((DomEvent click) { PointerBinding.clickDebouncer.onClick( click, + viewId, semanticsObject.id, _isListening, ); diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 0bf734b0797df..6c58d3b102f27 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -278,7 +278,7 @@ class SemanticTextField extends SemanticRole { // IMPORTANT: because this event listener can be triggered by either or // both a "focus" and a "click" DOM events, this code must be idempotent. EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - semanticsObject.id, ui.SemanticsAction.focus, null); + viewId, semanticsObject.id, ui.SemanticsAction.focus, null); })); editableElement.addEventListener('click', createDomEventListener((DomEvent event) { editableElement.focusWithoutScroll(); diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index f11d92166f5dc..0281aa7b7c287 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -157,7 +157,7 @@ class EngineFlutterView implements ui.FlutterView { final JsViewConstraints? _jsViewConstraints; - late final EngineSemanticsOwner semantics = EngineSemanticsOwner(dom.semanticsHost); + late final EngineSemanticsOwner semantics = EngineSemanticsOwner(viewId, dom.semanticsHost); @override ui.Size get physicalSize { diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 2789ee1f93cb0..6eb37f3b37cd7 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -3021,7 +3021,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect(PointerBinding.clickDebouncer.isDebouncing, false); expect(pointerPackets, isEmpty); expect(semanticsActions, [ @@ -3046,7 +3046,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect(pointerPackets, isEmpty); expect(semanticsActions, [ (type: ui.SemanticsAction.tap, nodeId: 42) @@ -3070,7 +3070,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { } ); - PointerBinding.clickDebouncer.onClick(click, 42, false); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, false); expect( reason: 'When tappable declares that it is not listening to click events ' 'the debouncer flushes the pointer events to the framework and ' @@ -3129,7 +3129,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'Because the DOM click event was deduped.', @@ -3190,7 +3190,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'Because the DOM click event was deduped.', @@ -3245,7 +3245,7 @@ void _testClickDebouncer({required PointerBinding Function() getBinding}) { 'clientY': testElement.getBoundingClientRect().y, } ); - PointerBinding.clickDebouncer.onClick(click, 42, true); + PointerBinding.clickDebouncer.onClick(click, view.viewId, 42, true); expect( reason: 'The DOM click should still be sent to the framework because it ' diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 69cf0fce1c87e..299490dc00c66 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -239,7 +239,8 @@ Future testMain() async { expect(ui.PlatformDispatcher.instance.onSemanticsActionEvent, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction(0, ui.SemanticsAction.tap, null); + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + myWindow.viewId, 0, ui.SemanticsAction.tap, null); }); test('onAccessibilityFeaturesChanged preserves the zone', () {