diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 29b4527ae9f6d..158cefe14a994 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:js_interop'; import 'dart:typed_data'; +import 'package:meta/meta.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; @@ -79,6 +80,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { registerHotRestartListener(dispose); AppLifecycleState.instance.addListener(_setAppLifecycleState); ViewFocusBinding.instance.addListener(invokeOnViewFocusChange); + domDocument.body?.append(accessibilityPlaceholder); _onViewDisposedListener = viewManager.onViewDisposed.listen((_) { // Send a metrics changed event to the framework when a view is disposed. // View creation/resize is handled by the `_didResize` handler in the @@ -93,6 +95,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { static EnginePlatformDispatcher get instance => _instance; static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher(); + @visibleForTesting + final DomElement accessibilityPlaceholder = EngineSemantics + .instance + .semanticsHelper + .prepareAccessibilityPlaceholder(); + PlatformConfiguration configuration = PlatformConfiguration( locales: parseBrowserLanguages(), textScaleFactor: findBrowserTextScaleFactor(), @@ -116,6 +124,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { HighContrastSupport.instance.removeListener(_updateHighContrast); AppLifecycleState.instance.removeListener(_setAppLifecycleState); ViewFocusBinding.instance.removeListener(invokeOnViewFocusChange); + accessibilityPlaceholder.remove(); _onViewDisposedListener.cancel(); viewManager.dispose(); } diff --git a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart index 51ee7248bcae2..22d7cb372d106 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart @@ -8,7 +8,6 @@ import '../configuration.dart'; import '../dom.dart'; import '../platform_views/content_manager.dart'; import '../safe_browser_api.dart'; -import '../semantics/semantics.dart'; import 'style_manager.dart'; /// Manages DOM elements and the DOM structure for a [ui.FlutterView]. @@ -71,11 +70,6 @@ class DomManager { // Rendering host (shadow root) children. - final DomElement accessibilityPlaceholder = EngineSemantics - .instance.semanticsHelper - .prepareAccessibilityPlaceholder(); - - renderingHost.append(accessibilityPlaceholder); renderingHost.append(sceneHost); renderingHost.append(announcementsHost); diff --git a/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart b/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart index 22db57156ca14..89cf4c8388302 100644 --- a/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart +++ b/lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart @@ -22,6 +22,16 @@ void testMain() { }); group('PlatformDispatcher', () { + late EnginePlatformDispatcher dispatcher; + + setUp(() { + dispatcher = EnginePlatformDispatcher(); + }); + + tearDown(() { + dispatcher.dispose(); + }); + test('reports at least one display', () { expect(ui.PlatformDispatcher.instance.displays.length, greaterThan(0)); }); @@ -30,15 +40,16 @@ void testMain() { final MockHighContrastSupport mockHighContrast = MockHighContrastSupport(); HighContrastSupport.instance = mockHighContrast; - final EnginePlatformDispatcher engineDispatcher = + + final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); - expect(engineDispatcher.accessibilityFeatures.highContrast, isTrue); + expect(dispatcher.accessibilityFeatures.highContrast, isTrue); mockHighContrast.isEnabled = false; mockHighContrast.invokeListeners(mockHighContrast.isEnabled); - expect(engineDispatcher.accessibilityFeatures.highContrast, isFalse); + expect(dispatcher.accessibilityFeatures.highContrast, isFalse); - engineDispatcher.dispose(); + dispatcher.dispose(); }); test('AppLifecycleState transitions through all states', () { @@ -295,7 +306,6 @@ void testMain() { }); test('disposes all its views', () { - final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); final EngineFlutterView view1 = EngineFlutterView(dispatcher, createDomHTMLDivElement()); final EngineFlutterView view2 = @@ -319,7 +329,6 @@ void testMain() { }); test('connects view disposal to metrics changed event', () { - final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); final EngineFlutterView view1 = EngineFlutterView(dispatcher, createDomHTMLDivElement()); final EngineFlutterView view2 = @@ -347,7 +356,6 @@ void testMain() { }); test('disconnects view disposal event on dispose', () { - final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); final EngineFlutterView view1 = EngineFlutterView(dispatcher, createDomHTMLDivElement()); @@ -367,7 +375,6 @@ void testMain() { }); test('invokeOnViewFocusChange calls onViewFocusChange', () { - final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); final List dispatchedViewFocusEvents = []; const ui.ViewFocusEvent viewFocusEvent = ui.ViewFocusEvent( viewId: 0, @@ -383,7 +390,6 @@ void testMain() { }); test('invokeOnViewFocusChange preserves the zone', () { - final EnginePlatformDispatcher dispatcher = EnginePlatformDispatcher(); final Zone zone1 = Zone.current.fork(); final Zone zone2 = Zone.current.fork(); const ui.ViewFocusEvent viewFocusEvent = ui.ViewFocusEvent( @@ -402,6 +408,15 @@ void testMain() { dispatcher.invokeOnViewFocusChange(viewFocusEvent); }); }); + + test('appends an accesibility placeholder', () { + expect(dispatcher.accessibilityPlaceholder.isConnected, isTrue); + }); + + test('removes the accesibility placeholder', () { + dispatcher.dispose(); + expect(dispatcher.accessibilityPlaceholder.isConnected, isFalse); + }); }); } diff --git a/lib/web_ui/test/engine/semantics/semantics_auto_enable_test.dart b/lib/web_ui/test/engine/semantics/semantics_auto_enable_test.dart index e85118b44cb76..bdd35a9fbbcca 100644 --- a/lib/web_ui/test/engine/semantics/semantics_auto_enable_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_auto_enable_test.dart @@ -31,11 +31,7 @@ Future testMain() async { .instance.accessibilityFeatures.accessibleNavigation, isFalse); - final DomShadowRoot renderingHost = - EnginePlatformDispatcher.instance.implicitView!.dom.renderingHost; - - final DomElement placeholder = - renderingHost.querySelector('flt-semantics-placeholder')!; + final DomElement placeholder = domDocument.querySelector('flt-semantics-placeholder')!; expect(placeholder.isConnected, isTrue); diff --git a/lib/web_ui/test/engine/semantics/semantics_placeholder_enable_test.dart b/lib/web_ui/test/engine/semantics/semantics_placeholder_enable_test.dart index ec88a349e1af5..ca0943fb56a40 100644 --- a/lib/web_ui/test/engine/semantics/semantics_placeholder_enable_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_placeholder_enable_test.dart @@ -26,10 +26,7 @@ Future testMain() async { expect(semantics().semanticsEnabled, isFalse); // Synthesize a click on the placeholder. - final DomShadowRoot renderingHost = - EnginePlatformDispatcher.instance.implicitView!.dom.renderingHost; - final DomElement placeholder = - renderingHost.querySelector('flt-semantics-placeholder')!; + final DomElement placeholder = domDocument.querySelector('flt-semantics-placeholder')!; expect(placeholder.isConnected, isTrue); diff --git a/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart b/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart index 15ef608dd2fcf..a07409bedb6fc 100644 --- a/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart +++ b/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart @@ -39,11 +39,10 @@ void doTests() { expect(rootChildren[3].tagName, equalsIgnoringCase('style')); final List shadowChildren = domManager.renderingHost.childNodes.cast().toList(); - expect(shadowChildren.length, 4); - expect(shadowChildren[0].tagName, equalsIgnoringCase('flt-semantics-placeholder')); - expect(shadowChildren[1], domManager.sceneHost); - expect(shadowChildren[2], domManager.announcementsHost); - expect(shadowChildren[3].tagName, equalsIgnoringCase('style')); + expect(shadowChildren.length, 3); + expect(shadowChildren[0], domManager.sceneHost); + expect(shadowChildren[1], domManager.announcementsHost); + expect(shadowChildren[2].tagName, equalsIgnoringCase('style')); }); test('hide placeholder text for textfield', () {