diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index c9b8b28e945e8..7a3df9c5b767b 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -853,25 +853,9 @@ class SemanticsObject { hasIdentityTransform && verticalContainerAdjustment == 0.0 && horizontalContainerAdjustment == 0.0) { - if (isDesktop) { - element.style - ..removeProperty('transform-origin') - ..removeProperty('transform'); - } else { - element.style - ..removeProperty('top') - ..removeProperty('left'); - } + _resetElementOffsets(element); if (containerElement != null) { - if (isDesktop) { - containerElement.style - ..removeProperty('transform-origin') - ..removeProperty('transform'); - } else { - containerElement.style - ..removeProperty('top') - ..removeProperty('left'); - } + _resetElementOffsets(containerElement); } return; } @@ -905,11 +889,15 @@ class SemanticsObject { effectiveTransformIsIdentity = false; } - if (!effectiveTransformIsIdentity) { + if (!effectiveTransformIsIdentity || isMacOrIOS) { + if (effectiveTransformIsIdentity) { + effectiveTransform = Matrix4.identity(); + } if (isDesktop) { element.style ..transformOrigin = '0 0 0' - ..transform = matrix4ToCssTransform(effectiveTransform); + ..transform = (effectiveTransformIsIdentity ? 'translate(0px 0px 0px)' + : matrix4ToCssTransform(effectiveTransform)); } else { // Mobile screen readers observed to have errors while calculating the // semantics focus borders if css `transform` properties are used. @@ -927,20 +915,13 @@ class SemanticsObject { ..height = '${rect.height}px'; } } else { + _resetElementOffsets(element); // TODO: https://github.com/flutter/flutter/issues/73347 - if (isDesktop) { - element.style - ..removeProperty('transform-origin') - ..removeProperty('transform'); - } else { - element.style - ..removeProperty('top') - ..removeProperty('left'); - } } if (containerElement != null) { if (!hasZeroRectOffset || + isMacOrIOS || verticalContainerAdjustment != 0.0 || horizontalContainerAdjustment != 0.0) { final double translateX = -_rect!.left + horizontalContainerAdjustment; @@ -955,15 +936,33 @@ class SemanticsObject { ..left = '${translateX}px'; } } else { - if (isDesktop) { - containerElement.style - ..removeProperty('transform-origin') - ..removeProperty('transform'); - } else { - containerElement.style - ..removeProperty('top') - ..removeProperty('left'); - } + _resetElementOffsets(containerElement); + } + } + } + + // On Mac OS and iOS, VoiceOver requires left=0 top=0 value to correctly + // handle order. See https://github.com/flutter/flutter/issues/73347. + static void _resetElementOffsets(html.Element element) { + if (isMacOrIOS) { + if (isDesktop) { + element.style + ..transformOrigin = '0 0 0' + ..transform = 'translate(0px, 0px)'; + } else { + element.style + ..top = '0px' + ..left = '0px'; + } + } else { + if (isDesktop) { + element.style + ..removeProperty('transform-origin') + ..removeProperty('transform'); + } else { + element.style + ..removeProperty('top') + ..removeProperty('left'); } } } diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 877bd519f2b23..ada70cd6abcad 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -446,9 +446,9 @@ const Set _genericFontFamilies = { /// For iOS, default to -apple-system, where it should be available, otherwise /// default to Arial. BlinkMacSystemFont is used for Chrome on iOS. final String _fallbackFontFamily = - _isMacOrIOS ? '-apple-system, BlinkMacSystemFont' : 'Arial'; + isMacOrIOS ? '-apple-system, BlinkMacSystemFont' : 'Arial'; -bool get _isMacOrIOS => +bool get isMacOrIOS => operatingSystem == OperatingSystem.iOs || operatingSystem == OperatingSystem.macOs; @@ -460,7 +460,7 @@ String? canonicalizeFontFamily(String? fontFamily) { if (_genericFontFamilies.contains(fontFamily)) { return fontFamily; } - if (_isMacOrIOS) { + if (isMacOrIOS) { // Unlike Safari, Chrome on iOS does not correctly fallback to cupertino // on sans-serif. // Map to San Francisco Text/Display fonts, use -apple-system, diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index af671c133bc0a..2199bd5fe209a 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -27,8 +27,13 @@ void main() { internalBootstrapBrowserTest(() => testMain); } +String rootSemanticStyle = ''; + void testMain() { setUp(() { + rootSemanticStyle = browserEngine != BrowserEngine.edge + ? 'filter: opacity(0%); color: rgba(0, 0, 0, 0)' : + 'color: rgba(0, 0, 0, 0); filter: opacity(0%)'; EngineSemanticsOwner.debugResetSemantics(); }); @@ -140,7 +145,7 @@ void _testEngineSemanticsOwner() { expect(tree[1].label, 'Hello'); expectSemanticsTree(''' - + Hello @@ -152,7 +157,7 @@ void _testEngineSemanticsOwner() { renderLabel('World'); expectSemanticsTree(''' - + World @@ -164,16 +169,14 @@ void _testEngineSemanticsOwner() { renderLabel(''); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('clears semantics tree when disabled', () { expect(semantics().debugSemanticsTree, isEmpty); @@ -248,15 +251,13 @@ void _testHeader() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + Header of the page '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); } void _testLongestIncreasingSubsequence() { @@ -326,7 +327,7 @@ void _testContainer() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -337,15 +338,19 @@ void _testContainer() { final html.Element container = html.document.querySelector('flt-semantics-container'); - expect(parentElement.style.transform, ''); - expect(parentElement.style.transformOrigin, ''); - expect(container.style.transform, ''); - expect(container.style.transformOrigin, ''); - + if (operatingSystem == OperatingSystem.macOs) { + expect(parentElement.style.transform, 'translate(0px, 0px)'); + expect(parentElement.style.transformOrigin, '0px 0px 0px'); + expect(container.style.transform, 'translate(0px, 0px)'); + expect(container.style.transformOrigin, '0px 0px 0px'); + } else { + expect(parentElement.style.transform, ''); + expect(parentElement.style.transformOrigin, ''); + expect(container.style.transform, ''); + expect(container.style.transformOrigin, ''); + } semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('container node compensates for rect offset', () async { semantics() @@ -366,7 +371,7 @@ void _testContainer() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -389,9 +394,61 @@ void _testContainer() { expect(container.style.left, '-10px'); } semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); + + test('0 offsets are not removed for voiceover', () async { + semantics() + ..debugOverrideTimestampFunction(() => _testTime) + ..semanticsEnabled = true; + + final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); + updateNode( + builder, + id: 0, + actions: 0, + flags: 0, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(0, 0, 20, 20), + childrenInHitTestOrder: Int32List.fromList([1]), + childrenInTraversalOrder: Int32List.fromList([1]), + ); + + semantics().updateSemantics(builder.build()); + if (browserEngine == BrowserEngine.edge) { + expectSemanticsTree(''' + + + + +'''); + } else { + expectSemanticsTree(''' + + + + +'''); + } + final html.Element parentElement = + html.document.querySelector('flt-semantics'); + final html.Element container = + html.document.querySelector('flt-semantics-container'); + if (operatingSystem == OperatingSystem.macOs || + operatingSystem == OperatingSystem.iOs) { + if (isDesktop) { + expect(parentElement.style.transform, 'translate(0px, 0px)'); + expect(parentElement.style.transformOrigin, '0px 0px 0px'); + expect(container.style.transform, 'translate(0px, 0px)'); + expect(container.style.transformOrigin, '0px 0px 0px'); + } else { + expect(parentElement.style.top, '0px'); + expect(parentElement.style.left, '0px'); + expect(container.style.top, '0px'); + expect(container.style.left, '0px'); + } + } + semantics().semanticsEnabled = false; + }); } void _testVerticalScrolling() { @@ -412,13 +469,11 @@ void _testVerticalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('scrollable node with children has a container node', () async { semantics() @@ -439,7 +494,7 @@ void _testVerticalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -453,9 +508,7 @@ void _testVerticalScrolling() { expect(scrollable.scrollTop, 0); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('scrollable node dispatches scroll events', () async { final StreamController idLogController = StreamController(); @@ -509,7 +562,7 @@ void _testVerticalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -547,9 +600,7 @@ void _testVerticalScrolling() { expect(scrollable.scrollTop >= (10 - browserMaxScrollDiff), isTrue); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); } void _testHorizontalScrolling() { @@ -570,13 +621,11 @@ void _testHorizontalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('scrollable node with children has a container node', () async { semantics() @@ -597,7 +646,7 @@ void _testHorizontalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -611,9 +660,7 @@ void _testHorizontalScrolling() { expect(scrollable.scrollLeft, 0); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('scrollable node dispatches scroll events', () async { final SemanticsActionLogger logger = SemanticsActionLogger(); @@ -648,7 +695,7 @@ void _testHorizontalScrolling() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -685,9 +732,7 @@ void _testHorizontalScrolling() { expect(scrollable.scrollLeft >= (10 - browserMaxScrollDiff), isTrue); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); } void _testIncrementables() { @@ -709,14 +754,12 @@ void _testIncrementables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('increments', () async { final SemanticsActionLogger logger = SemanticsActionLogger(); @@ -738,7 +781,7 @@ void _testIncrementables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); @@ -751,9 +794,7 @@ void _testIncrementables() { expect(await logger.actionLog.first, ui.SemanticsAction.increase); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); test('decrements', () async { final SemanticsActionLogger logger = SemanticsActionLogger(); @@ -775,7 +816,7 @@ void _testIncrementables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); @@ -814,7 +855,7 @@ void _testIncrementables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); @@ -843,7 +884,7 @@ void _testTextField() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); @@ -914,7 +955,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -942,7 +983,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -970,7 +1011,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -999,7 +1040,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1027,7 +1068,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1055,7 +1096,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1085,7 +1126,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1114,7 +1155,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1143,7 +1184,7 @@ void _testCheckables() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1173,7 +1214,7 @@ void _testTappable() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1200,7 +1241,7 @@ void _testTappable() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1228,7 +1269,7 @@ void _testImage() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; @@ -1256,7 +1297,7 @@ void _testImage() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -1286,7 +1327,7 @@ void _testImage() { semantics().updateSemantics(builder.build()); expectSemanticsTree( - ''''''); + ''''''); semantics().semanticsEnabled = false; }, @@ -1312,7 +1353,7 @@ void _testImage() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + @@ -1345,7 +1386,7 @@ void _testLiveRegion() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' -This is a snackbar +This is a snackbar '''); semantics().semanticsEnabled = false; @@ -1370,13 +1411,11 @@ void _testLiveRegion() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''' - + '''); semantics().semanticsEnabled = false; - }, - // TODO(nurhan): https://github.com/flutter/flutter/issues/50754 - skip: browserEngine == BrowserEngine.edge); + }); } void expectSemanticsTree(String semanticsHtml) {