Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart + ../../
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/live_region.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/platform_view.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart + ../../../flutter/LICENSE
Expand Down Expand Up @@ -4722,6 +4723,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/live_region.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/platform_view.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export 'engine/semantics/image.dart';
export 'engine/semantics/incrementable.dart';
export 'engine/semantics/label_and_value.dart';
export 'engine/semantics/live_region.dart';
export 'engine/semantics/platform_view.dart';
export 'engine/semantics/scrollable.dart';
export 'engine/semantics/semantics.dart';
export 'engine/semantics/semantics_helper.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class PlatformViewManager {
/// The resulting DOM for the `contents` of a Platform View looks like this:
///
/// ```html
/// <flt-platform-view slot="...">
/// <flt-platform-view id="flt-pv-VIEW_ID" slot="...">
/// <arbitrary-html-elements />
/// </flt-platform-view-slot>
/// ```
Expand All @@ -134,6 +134,7 @@ class PlatformViewManager {
return _contents.putIfAbsent(viewId, () {
final DomElement wrapper = domDocument
.createElement('flt-platform-view')
..id = getPlatformViewDomId(viewId)
..setAttribute('slot', slotName);

final Function factoryFunction = _factories[viewType]!;
Expand Down
6 changes: 6 additions & 0 deletions lib/web_ui/lib/src/engine/platform_views/slots.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ String getPlatformViewSlotName(int viewId) {
return 'flt-pv-slot-$viewId';
}

/// Returns the value of the HTML "id" attribute set on the wrapper element that
/// hosts the platform view content.
String getPlatformViewDomId(int viewId) {
return 'flt-pv-$viewId';
}

/// Creates the HTML markup for the `slot` of a Platform View.
///
/// The resulting DOM for a `slot` looks like this:
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export 'semantics/image.dart';
export 'semantics/incrementable.dart';
export 'semantics/label_and_value.dart';
export 'semantics/live_region.dart';
export 'semantics/platform_view.dart';
export 'semantics/scrollable.dart';
export 'semantics/semantics.dart';
export 'semantics/semantics_helper.dart';
Expand Down
42 changes: 42 additions & 0 deletions lib/web_ui/lib/src/engine/semantics/platform_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../dom.dart';
import '../platform_views/slots.dart';
import 'semantics.dart';

/// Manages the semantic element corresponding to a platform view.
///
/// The element in the semantics tree exists only to supply the ARIA traversal
/// order. The actual content of the platform view is managed by
/// [PlatformViewManager].
///
/// The traversal order is established using "aria-owns", by pointing to the
/// element that hosts the view contents. As of this writing, Safari on macOS
/// and on iOS does not support "aria-owns". All other browsers on all operating
/// systems support it.
///
/// See also:
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-owns
/// * https://bugs.webkit.org/show_bug.cgi?id=223798
class PlatformViewRoleManager extends PrimaryRoleManager {
PlatformViewRoleManager(SemanticsObject semanticsObject)
: super.withBasics(PrimaryRole.platformView, semanticsObject);

@override
void update() {
super.update();

if (semanticsObject.isPlatformView) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we already had isPlatformView but it was never used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used elsewhere to set proper pointer event handling, but that aspect is not handled by this role manager since the logic is applied to all roles.

if (semanticsObject.isPlatformViewIdDirty) {
semanticsObject.element.setAttribute(
'aria-owns',
getPlatformViewDomId(semanticsObject.platformViewId),
);
}
} else {
semanticsObject.element.removeAttribute('aria-owns');
}
}
}
24 changes: 17 additions & 7 deletions lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'image.dart';
import 'incrementable.dart';
import 'label_and_value.dart';
import 'live_region.dart';
import 'platform_view.dart';
import 'scrollable.dart';
import 'semantics_helper.dart';
import 'tappable.dart';
Expand Down Expand Up @@ -332,12 +333,6 @@ class SemanticsNodeUpdate {
/// Each value corresponds to the most specific role a semantics node plays in
/// the semantics tree.
enum PrimaryRole {
/// A role used when a more specific role cannot be assigend to
/// a [SemanticsObject].
///
/// Provides a label or a value.
generic,

/// Supports incrementing and/or decrementing its value.
incrementable,

Expand Down Expand Up @@ -378,6 +373,15 @@ enum PrimaryRole {
/// children. For example, a modal barrier has `scopesRoute` set but marking
/// it as a dialog would be wrong.
dialog,

/// The node's primary role is to host a platform view.
platformView,

/// A role used when a more specific role cannot be assigend to
/// a [SemanticsObject].
///
/// Provides a label or a value.
generic,
}

/// Identifies one of the secondary [RoleManager]s of a [PrimaryRoleManager].
Expand Down Expand Up @@ -964,6 +968,9 @@ class SemanticsObject {

static const int _platformViewIdIndex = 1 << 23;

/// Whether the [platformViewId] field has been updated but has not been
/// applied to the DOM yet.
bool get isPlatformViewIdDirty => _isDirty(_platformViewIdIndex);
void _markPlatformViewIdDirty() {
_dirtyFields |= _platformViewIdIndex;
}
Expand Down Expand Up @@ -1462,7 +1469,9 @@ class SemanticsObject {

PrimaryRole _getPrimaryRoleIdentifier() {
// The most specific role should take precedence.
if (isTextField) {
if (isPlatformView) {
return PrimaryRole.platformView;
} else if (isTextField) {
return PrimaryRole.textField;
} else if (isIncrementable) {
return PrimaryRole.incrementable;
Expand Down Expand Up @@ -1490,6 +1499,7 @@ class SemanticsObject {
PrimaryRole.checkable => Checkable(this),
PrimaryRole.dialog => Dialog(this),
PrimaryRole.image => ImageRoleManager(this),
PrimaryRole.platformView => PlatformViewRoleManager(this),
PrimaryRole.generic => GenericRole(this),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ void testMain() {
test('rendered markup contains required attributes', () async {
final DomElement content =
contentManager.renderContent(viewType, viewId, null);
expect(content.getAttribute('slot'), contains('$viewId'));
expect(content.getAttribute('slot'), getPlatformViewSlotName(viewId));
expect(content.getAttribute('id'), getPlatformViewDomId(viewId));

final DomElement userContent = content.querySelector('div')!;
expect(userContent.style.height, '100%');
Expand Down
8 changes: 8 additions & 0 deletions lib/web_ui/test/engine/platform_views/slots_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ void testMain() {
});
});
});

test('getPlatformViewSlotName', () {
expect(getPlatformViewSlotName(42), 'flt-pv-slot-42');
});

test('getPlatformViewDomId', () {
expect(getPlatformViewDomId(42), 'flt-pv-42');
});
Comment on lines +39 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Boy Scout! Thanks!

}
36 changes: 34 additions & 2 deletions lib/web_ui/test/engine/semantics/semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,38 @@ void _testLiveRegion() {
}

void _testPlatformView() {
test('sets and updates aria-owns', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;

// Set.
{
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
updateNode(
builder,
platformViewId: 5,
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
semantics().updateSemantics(builder.build());
expectSemanticsTree('<sem aria-owns="flt-pv-5" style="$rootSemanticStyle"></sem>');
}

// Update.
{
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
updateNode(
builder,
platformViewId: 42,
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
semantics().updateSemantics(builder.build());
expectSemanticsTree('<sem aria-owns="flt-pv-42" style="$rootSemanticStyle"></sem>');
}

semantics().semanticsEnabled = false;
});

test('is transparent w.r.t. hit testing', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
Expand All @@ -2290,7 +2322,7 @@ void _testPlatformView() {
);
semantics().updateSemantics(builder.build());

expectSemanticsTree('<sem style="$rootSemanticStyle"></sem>');
expectSemanticsTree('<sem aria-owns="flt-pv-5" style="$rootSemanticStyle"></sem>');
final DomElement element = appHostNode.querySelector('flt-semantics')!;
expect(element.style.pointerEvents, 'none');

Expand Down Expand Up @@ -2375,7 +2407,7 @@ void _testPlatformView() {
<sem style="$rootSemanticStyle">
<sem-c>
<sem style="z-index: 3"></sem>
<sem style="z-index: 2"></sem>
<sem style="z-index: 2" aria-owns="flt-pv-0"></sem>
<sem style="z-index: 1"></sem>
</sem-c>
</sem>''');
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/test/engine/semantics/semantics_tester.dart
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ class SemanticsTester {
currentValueLength: currentValueLength ?? 0,
textSelectionBase: textSelectionBase ?? 0,
textSelectionExtent: textSelectionExtent ?? 0,
platformViewId: platformViewId ?? 0,
platformViewId: platformViewId ?? -1,
scrollChildren: scrollChildren ?? 0,
scrollIndex: scrollIndex ?? 0,
scrollPosition: scrollPosition ?? 0,
Expand Down