Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Send the correct view ID with semantics actions #56595

Merged
merged 2 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ui.SemanticsActionEvent>(
_onSemanticsActionEvent,
_onSemanticsActionEventZone,
ui.SemanticsActionEvent(
type: action,
nodeId: nodeId,
viewId: 0, // TODO(goderbauer): Wire up the real view ID.
viewId: viewId,
arguments: args,
),
);
Expand Down
18 changes: 13 additions & 5 deletions lib/web_ui/lib/src/engine/pointer_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -312,7 +316,11 @@ class ClickDebouncer {
click.stopPropagation();

EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
semanticsNodeId, ui.SemanticsAction.tap, null);
viewId,
semanticsNodeId,
ui.SemanticsAction.tap,
null,
);
reset();
}

Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/semantics/focusable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class AccessibilityFocusManager {
// shifting focus.
if (_lastEvent != AccessibilityFocusManagerEvent.requestedFocus) {
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
_owner.viewId,
target.semanticsNodeId,
ui.SemanticsAction.focus,
null,
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/incrementable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}));

Expand Down
8 changes: 4 additions & 4 deletions lib/web_ui/lib/src/engine/semantics/scrollable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/semantics/tappable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Tappable extends SemanticBehavior {
_clickListener = createDomEventListener((DomEvent click) {
PointerBinding.clickDebouncer.onClick(
click,
viewId,
semanticsObject.id,
_isListening,
);
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/semantics/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions lib/web_ui/test/engine/pointer_binding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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, <CapturedSemanticsEvent>[
Expand All @@ -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, <CapturedSemanticsEvent>[
(type: ui.SemanticsAction.tap, nodeId: 42)
Expand All @@ -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 '
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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.',
Expand Down Expand Up @@ -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 '
Expand Down
3 changes: 2 additions & 1 deletion lib/web_ui/test/engine/window_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ Future<void> 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', () {
Expand Down