mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Move text editing nodes outside of shadowDOM (flutter/engine#39688)
[web] Move text editing nodes outside of shadowDOM
This commit is contained in:
parent
c8039e2544
commit
c32816a82c
@ -124,6 +124,9 @@ class FlutterViewEmbedder {
|
||||
HostNode get glassPaneShadow => _glassPaneShadow;
|
||||
late HostNode _glassPaneShadow;
|
||||
|
||||
DomElement get textEditingHostNode => _textEditingHostNode;
|
||||
late DomElement _textEditingHostNode;
|
||||
|
||||
static const String defaultFontStyle = 'normal';
|
||||
static const String defaultFontWeight = 'normal';
|
||||
static const double defaultFontSize = 14;
|
||||
@ -168,6 +171,9 @@ class FlutterViewEmbedder {
|
||||
);
|
||||
_glassPaneShadow = glassPaneElementHostNode;
|
||||
|
||||
_textEditingHostNode =
|
||||
createTextEditingHostNode(glassPaneElement, defaultCssFont);
|
||||
|
||||
// Don't allow the scene to receive pointer events.
|
||||
_sceneHostElement = domDocument.createElement('flt-scene-host')
|
||||
..style.pointerEvents = 'none';
|
||||
@ -189,20 +195,20 @@ class FlutterViewEmbedder {
|
||||
glassPaneElementHostNode.appendAll(<DomNode>[
|
||||
accessibilityPlaceholder,
|
||||
_sceneHostElement!,
|
||||
|
||||
// The semantic host goes last because hit-test order-wise it must be
|
||||
// first. If semantics goes under the scene host, platform views will
|
||||
// obscure semantic elements.
|
||||
//
|
||||
// You may be wondering: wouldn't semantics obscure platform views and
|
||||
// make then not accessible? At least with some careful planning, that
|
||||
// should not be the case. The semantics tree makes all of its non-leaf
|
||||
// elements transparent. This way, if a platform view appears among other
|
||||
// interactive Flutter widgets, as long as those widgets do not intersect
|
||||
// with the platform view, the platform view will be reachable.
|
||||
semanticsHostElement,
|
||||
]);
|
||||
|
||||
// The semantic host goes last because hit-test order-wise it must be
|
||||
// first. If semantics goes under the scene host, platform views will
|
||||
// obscure semantic elements.
|
||||
//
|
||||
// You may be wondering: wouldn't semantics obscure platform views and
|
||||
// make then not accessible? At least with some careful planning, that
|
||||
// should not be the case. The semantics tree makes all of its non-leaf
|
||||
// elements transparent. This way, if a platform view appears among other
|
||||
// interactive Flutter widgets, as long as those widgets do not intersect
|
||||
// with the platform view, the platform view will be reachable.
|
||||
glassPaneElement.appendChild(semanticsHostElement);
|
||||
|
||||
// When debugging semantics, make the scene semi-transparent so that the
|
||||
// semantics tree is more prominent.
|
||||
if (configuration.debugShowSemanticsNodes) {
|
||||
@ -393,3 +399,24 @@ FlutterViewEmbedder? _flutterViewEmbedder;
|
||||
FlutterViewEmbedder ensureFlutterViewEmbedderInitialized() =>
|
||||
_flutterViewEmbedder ??=
|
||||
FlutterViewEmbedder(hostElement: configuration.hostElement);
|
||||
|
||||
/// Creates a node to host text editing elements and applies a stylesheet
|
||||
/// to Flutter nodes that exist outside of the shadowDOM.
|
||||
DomElement createTextEditingHostNode(DomElement root, String defaultFont) {
|
||||
final DomElement domElement =
|
||||
domDocument.createElement('flt-text-editing-host');
|
||||
final DomHTMLStyleElement styleElement = createDomHTMLStyleElement();
|
||||
|
||||
styleElement.id = 'flt-text-editing-stylesheet';
|
||||
root.appendChild(styleElement);
|
||||
applyGlobalCssRulesToSheet(
|
||||
styleElement.sheet! as DomCSSStyleSheet,
|
||||
hasAutofillOverlay: browserHasAutofillOverlay(),
|
||||
cssSelectorPrefix: FlutterViewEmbedder.glassPaneTagName,
|
||||
defaultCssFont: defaultFont,
|
||||
);
|
||||
|
||||
root.appendChild(domElement);
|
||||
|
||||
return domElement;
|
||||
}
|
||||
|
||||
@ -94,6 +94,8 @@ abstract class HostNode {
|
||||
/// See:
|
||||
/// * [Document.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)
|
||||
Iterable<DomElement> querySelectorAll(String selectors);
|
||||
|
||||
DomElement get renderHost;
|
||||
}
|
||||
|
||||
/// A [HostNode] implementation, backed by a [DomShadowRoot].
|
||||
@ -110,11 +112,10 @@ class ShadowDomHostNode implements HostNode {
|
||||
/// This also calls [applyGlobalCssRulesToSheet], with the [defaultFont]
|
||||
/// to be used as the default font definition.
|
||||
ShadowDomHostNode(DomElement root, String defaultFont)
|
||||
: assert(
|
||||
root.isConnected ?? true,
|
||||
'The `root` of a ShadowDomHostNode must be connected to the Document object or a ShadowRoot.'
|
||||
) {
|
||||
_shadow = root.attachShadow(<String, dynamic>{
|
||||
: assert(root.isConnected ?? true,
|
||||
'The `root` of a ShadowDomHostNode must be connected to the Document object or a ShadowRoot.') {
|
||||
root.appendChild(renderHost);
|
||||
_shadow = renderHost.attachShadow(<String, dynamic>{
|
||||
'mode': 'open',
|
||||
// This needs to stay false to prevent issues like this:
|
||||
// - https://github.com/flutter/flutter/issues/85759
|
||||
@ -135,6 +136,9 @@ class ShadowDomHostNode implements HostNode {
|
||||
|
||||
late DomShadowRoot _shadow;
|
||||
|
||||
@override
|
||||
final DomElement renderHost = domDocument.createElement('flt-render-host');
|
||||
|
||||
@override
|
||||
DomElement? get activeElement => _shadow.activeElement;
|
||||
|
||||
@ -191,6 +195,9 @@ class ElementHostNode implements HostNode {
|
||||
|
||||
late DomElement _element;
|
||||
|
||||
@override
|
||||
final DomElement renderHost = domDocument.createElement('flt-render-host');
|
||||
|
||||
@override
|
||||
DomElement? get activeElement => _element.ownerDocument?.activeElement;
|
||||
|
||||
|
||||
@ -587,7 +587,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
_platformViewMessageHandler ??= PlatformViewMessageHandler(
|
||||
contentManager: platformViewManager,
|
||||
contentHandler: (DomElement content) {
|
||||
flutterViewEmbedder.glassPaneElement.append(content);
|
||||
flutterViewEmbedder.glassPaneShadow.renderHost.append(content);
|
||||
},
|
||||
);
|
||||
_platformViewMessageHandler!.handlePlatformViewCall(data, callback!);
|
||||
|
||||
@ -128,8 +128,9 @@ class PlatformViewManager {
|
||||
}
|
||||
|
||||
_ensureContentCorrectlySized(content, viewType);
|
||||
wrapper.append(content);
|
||||
|
||||
return wrapper..append(content);
|
||||
return wrapper;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -19,20 +19,22 @@ import '../semantics.dart' show EngineSemanticsOwner;
|
||||
/// It also takes into account semantics being enabled to fix the case where
|
||||
/// offsetX, offsetY == 0 (TalkBack events).
|
||||
ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
|
||||
// On top of a platform view
|
||||
if (event.target != actualTarget) {
|
||||
return _computeOffsetOnPlatformView(event, actualTarget);
|
||||
}
|
||||
// On a TalkBack event
|
||||
if (EngineSemanticsOwner.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) {
|
||||
return _computeOffsetForTalkbackEvent(event, actualTarget);
|
||||
}
|
||||
|
||||
final bool isTargetOutsideOfShadowDOM = event.target != actualTarget;
|
||||
if (isTargetOutsideOfShadowDOM) {
|
||||
return _computeOffsetRelativeToActualTarget(event, actualTarget);
|
||||
}
|
||||
// Return the offsetX/Y in the normal case.
|
||||
// (This works with 3D translations of the parent element.)
|
||||
return ui.Offset(event.offsetX, event.offsetY);
|
||||
}
|
||||
|
||||
/// Computes the event offset when hovering over a platformView.
|
||||
/// Computes the event offset when hovering over any nodes that don't exist in
|
||||
/// the shadowDOM such as platform views or text editing nodes.
|
||||
///
|
||||
/// This still uses offsetX/Y, but adds the offset from the top/left corner of the
|
||||
/// platform view to the glass pane (`actualTarget`).
|
||||
@ -57,7 +59,7 @@ ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarge
|
||||
///
|
||||
/// Event offset relative to FlutterView = (offsetX + xP, offsetY + yP)
|
||||
// TODO(dit): Make this understand 3D transforms, https://github.com/flutter/flutter/issues/117091
|
||||
ui.Offset _computeOffsetOnPlatformView(DomMouseEvent event, DomElement actualTarget) {
|
||||
ui.Offset _computeOffsetRelativeToActualTarget(DomMouseEvent event, DomElement actualTarget) {
|
||||
final DomElement target = event.target! as DomElement;
|
||||
final DomRect targetRect = target.getBoundingClientRect();
|
||||
final DomRect actualTargetRect = actualTarget.getBoundingClientRect();
|
||||
|
||||
@ -7,7 +7,6 @@ import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../browser_detection.dart';
|
||||
import '../dom.dart';
|
||||
import '../embedder.dart';
|
||||
import '../platform_dispatcher.dart';
|
||||
import '../safe_browser_api.dart';
|
||||
import '../text_editing/text_editing.dart';
|
||||
@ -422,14 +421,14 @@ class TextField extends RoleManager {
|
||||
..height = '${semanticsObject.rect!.height}px';
|
||||
|
||||
if (semanticsObject.hasFocus) {
|
||||
if (flutterViewEmbedder.glassPaneShadow.activeElement !=
|
||||
if (domDocument.activeElement !=
|
||||
activeEditableElement) {
|
||||
semanticsObject.owner.addOneTimePostUpdateCallback(() {
|
||||
activeEditableElement.focus();
|
||||
});
|
||||
}
|
||||
SemanticsTextEditingStrategy._instance?.activate(this);
|
||||
} else if (flutterViewEmbedder.glassPaneShadow.activeElement ==
|
||||
} else if (domDocument.activeElement ==
|
||||
activeEditableElement) {
|
||||
if (!isIosSafari) {
|
||||
SemanticsTextEditingStrategy._instance?.deactivate(this);
|
||||
|
||||
@ -51,7 +51,8 @@ void _emptyCallback(dynamic _) {}
|
||||
|
||||
/// The default [HostNode] that hosts all DOM required for text editing when a11y is not enabled.
|
||||
@visibleForTesting
|
||||
HostNode get defaultTextEditingRoot => flutterViewEmbedder.glassPaneShadow;
|
||||
DomElement get defaultTextEditingRoot =>
|
||||
flutterViewEmbedder.textEditingHostNode;
|
||||
|
||||
/// These style attributes are constant throughout the life time of an input
|
||||
/// element.
|
||||
|
||||
@ -16,19 +16,20 @@ void testMain() {
|
||||
|
||||
group('ShadowDomHostNode', () {
|
||||
final HostNode hostNode = ShadowDomHostNode(rootNode, '14px monospace');
|
||||
final DomElement renderHost = domDocument.querySelector('flt-render-host')!;
|
||||
|
||||
test('Initializes and attaches a shadow root', () {
|
||||
expect(domInstanceOfString(hostNode.node, 'ShadowRoot'), isTrue);
|
||||
expect((hostNode.node as DomShadowRoot).host, rootNode);
|
||||
expect(hostNode.node, rootNode.shadowRoot);
|
||||
expect((hostNode.node as DomShadowRoot).host, renderHost);
|
||||
expect(hostNode.node, renderHost.shadowRoot);
|
||||
|
||||
// The shadow root should be initialized with correct parameters.
|
||||
expect(rootNode.shadowRoot!.mode, 'open');
|
||||
expect(renderHost.shadowRoot!.mode, 'open');
|
||||
if (browserEngine != BrowserEngine.firefox &&
|
||||
browserEngine != BrowserEngine.webkit) {
|
||||
// Older versions of Safari and Firefox don't support this flag yet.
|
||||
// See: https://caniuse.com/mdn-api_shadowroot_delegatesfocus
|
||||
expect(rootNode.shadowRoot!.delegatesFocus, isFalse);
|
||||
expect(renderHost.shadowRoot!.delegatesFocus, isFalse);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -157,18 +157,16 @@ void _testEngineSemanticsOwner() {
|
||||
expect(semantics().semanticsEnabled, isFalse);
|
||||
|
||||
// Synthesize a click on the placeholder.
|
||||
final DomElement placeholder =
|
||||
appHostNode.querySelector('flt-semantics-placeholder')!;
|
||||
final DomElement placeholder = flutterViewEmbedder.glassPaneShadow
|
||||
.querySelector('flt-semantics-placeholder')!;
|
||||
|
||||
expect(placeholder.isConnected, isTrue);
|
||||
|
||||
final DomRect rect = placeholder.getBoundingClientRect();
|
||||
placeholder.dispatchEvent(createDomMouseEvent(
|
||||
'click', <Object?, Object?>{
|
||||
'clientX': (rect.left + (rect.right - rect.left) / 2).floor(),
|
||||
'clientY': (rect.top + (rect.bottom - rect.top) / 2).floor(),
|
||||
}
|
||||
));
|
||||
placeholder.dispatchEvent(createDomMouseEvent('click', <Object?, Object?>{
|
||||
'clientX': (rect.left + (rect.right - rect.left) / 2).floor(),
|
||||
'clientY': (rect.top + (rect.bottom - rect.top) / 2).floor(),
|
||||
}));
|
||||
|
||||
// On mobile semantics is enabled asynchronously.
|
||||
if (isMobile) {
|
||||
@ -182,7 +180,8 @@ void _testEngineSemanticsOwner() {
|
||||
|
||||
test('accessibilityFeatures copyWith function works', () {
|
||||
const EngineAccessibilityFeatures original = EngineAccessibilityFeatures(0);
|
||||
EngineAccessibilityFeatures copy = original.copyWith(accessibleNavigation: true);
|
||||
EngineAccessibilityFeatures copy =
|
||||
original.copyWith(accessibleNavigation: true);
|
||||
expect(copy.accessibleNavigation, true);
|
||||
expect(copy.boldText, false);
|
||||
expect(copy.disableAnimations, false);
|
||||
@ -254,8 +253,8 @@ void _testEngineSemanticsOwner() {
|
||||
.instance.accessibilityFeatures.accessibleNavigation,
|
||||
isFalse);
|
||||
|
||||
final DomElement placeholder =
|
||||
appHostNode.querySelector('flt-semantics-placeholder')!;
|
||||
final DomElement placeholder = flutterViewEmbedder.glassPaneShadow
|
||||
.querySelector('flt-semantics-placeholder')!;
|
||||
|
||||
expect(placeholder.isConnected, isTrue);
|
||||
|
||||
@ -428,7 +427,8 @@ void _testEngineSemanticsOwner() {
|
||||
);
|
||||
});
|
||||
|
||||
test('forwards events to framework if shouldEnableSemantics returns true', () {
|
||||
test('forwards events to framework if shouldEnableSemantics returns true',
|
||||
() {
|
||||
final MockSemanticsEnabler mockSemanticsEnabler = MockSemanticsEnabler();
|
||||
semantics().semanticsHelper.semanticsEnabler = mockSemanticsEnabler;
|
||||
final DomEvent pointerEvent = createDomEvent('Event', 'pointermove');
|
||||
@ -439,8 +439,7 @@ void _testEngineSemanticsOwner() {
|
||||
|
||||
class MockSemanticsEnabler implements SemanticsEnabler {
|
||||
@override
|
||||
void dispose() {
|
||||
}
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
bool get isWaitingToEnableSemantics => throw UnimplementedError();
|
||||
@ -716,7 +715,8 @@ void _testContainer() {
|
||||
semantics().semanticsEnabled = false;
|
||||
});
|
||||
|
||||
test('renders in traversal order, hit-tests in reverse z-index order', () async {
|
||||
test('renders in traversal order, hit-tests in reverse z-index order',
|
||||
() async {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
@ -809,7 +809,9 @@ void _testContainer() {
|
||||
semantics().semanticsEnabled = false;
|
||||
});
|
||||
|
||||
test('container nodes are transparent and leaf children are opaque hit-test wise', () async {
|
||||
test(
|
||||
'container nodes are transparent and leaf children are opaque hit-test wise',
|
||||
() async {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
@ -835,10 +837,12 @@ void _testContainer() {
|
||||
final DomElement root = appHostNode.querySelector('#flt-semantic-node-0')!;
|
||||
expect(root.style.pointerEvents, 'none');
|
||||
|
||||
final DomElement child1 = appHostNode.querySelector('#flt-semantic-node-1')!;
|
||||
final DomElement child1 =
|
||||
appHostNode.querySelector('#flt-semantic-node-1')!;
|
||||
expect(child1.style.pointerEvents, 'all');
|
||||
|
||||
final DomElement child2 = appHostNode.querySelector('#flt-semantic-node-2')!;
|
||||
final DomElement child2 =
|
||||
appHostNode.querySelector('#flt-semantic-node-2')!;
|
||||
expect(child2.style.pointerEvents, 'all');
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
@ -1179,8 +1183,8 @@ void _testIncrementables() {
|
||||
<input aria-valuenow="1" aria-valuetext="d" aria-valuemax="2" aria-valuemin="1">
|
||||
</sem>''');
|
||||
|
||||
final DomHTMLInputElement input = appHostNode.querySelector('input')! as
|
||||
DomHTMLInputElement;
|
||||
final DomHTMLInputElement input =
|
||||
appHostNode.querySelector('input')! as DomHTMLInputElement;
|
||||
input.value = '2';
|
||||
input.dispatchEvent(createDomEvent('Event', 'change'));
|
||||
|
||||
@ -1212,8 +1216,8 @@ void _testIncrementables() {
|
||||
<input aria-valuenow="1" aria-valuetext="d" aria-valuemax="1" aria-valuemin="0">
|
||||
</sem>''');
|
||||
|
||||
final DomHTMLInputElement input = appHostNode.querySelector('input')! as
|
||||
DomHTMLInputElement;
|
||||
final DomHTMLInputElement input =
|
||||
appHostNode.querySelector('input')! as DomHTMLInputElement;
|
||||
input.value = '0';
|
||||
input.dispatchEvent(createDomEvent('Event', 'change'));
|
||||
|
||||
@ -1299,11 +1303,11 @@ void _testTextField() {
|
||||
final DomElement textField =
|
||||
appHostNode.querySelector('input[data-semantics-role="text-field"]')!;
|
||||
|
||||
expect(appHostNode.activeElement, isNot(textField));
|
||||
expect(appHostNode.ownerDocument?.activeElement, isNot(textField));
|
||||
|
||||
textField.focus();
|
||||
|
||||
expect(appHostNode.activeElement, textField);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textField);
|
||||
expect(await logger.idLog.first, 0);
|
||||
expect(await logger.actionLog.first, ui.SemanticsAction.tap);
|
||||
|
||||
@ -1616,13 +1620,15 @@ void _testTappable() {
|
||||
}
|
||||
|
||||
updateTappable(enabled: false);
|
||||
expectSemanticsTree('<sem role="button" aria-disabled="true" style="$rootSemanticStyle"></sem>');
|
||||
expectSemanticsTree(
|
||||
'<sem role="button" aria-disabled="true" style="$rootSemanticStyle"></sem>');
|
||||
|
||||
updateTappable(enabled: true);
|
||||
expectSemanticsTree('<sem role="button" style="$rootSemanticStyle"></sem>');
|
||||
|
||||
updateTappable(enabled: false);
|
||||
expectSemanticsTree('<sem role="button" aria-disabled="true" style="$rootSemanticStyle"></sem>');
|
||||
expectSemanticsTree(
|
||||
'<sem role="button" aria-disabled="true" style="$rootSemanticStyle"></sem>');
|
||||
|
||||
updateTappable(enabled: true);
|
||||
expectSemanticsTree('<sem role="button" style="$rootSemanticStyle"></sem>');
|
||||
@ -1647,7 +1653,7 @@ void _testTappable() {
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
expect(flutterViewEmbedder.glassPaneShadow.activeElement, tester.getSemanticsObject(0).element);
|
||||
expect(domDocument.activeElement, tester.getSemanticsObject(0).element);
|
||||
semantics().semanticsEnabled = false;
|
||||
});
|
||||
}
|
||||
@ -1942,13 +1948,13 @@ void _testPlatformView() {
|
||||
ui.window.render(sceneBuilder.build());
|
||||
|
||||
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
|
||||
updateNode(
|
||||
builder,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 20, 60),
|
||||
childrenInTraversalOrder: Int32List.fromList(<int>[1, 2, 3]),
|
||||
childrenInHitTestOrder: Int32List.fromList(<int>[1, 2, 3]),
|
||||
transform: Float64List.fromList(Matrix4.diagonal3Values(ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1).storage)
|
||||
);
|
||||
updateNode(builder,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 20, 60),
|
||||
childrenInTraversalOrder: Int32List.fromList(<int>[1, 2, 3]),
|
||||
childrenInHitTestOrder: Int32List.fromList(<int>[1, 2, 3]),
|
||||
transform: Float64List.fromList(Matrix4.diagonal3Values(
|
||||
ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1)
|
||||
.storage));
|
||||
updateNode(
|
||||
builder,
|
||||
id: 1,
|
||||
@ -2009,22 +2015,19 @@ void _testPlatformView() {
|
||||
|
||||
final DomElement platformViewElement =
|
||||
flutterViewEmbedder.glassPaneElement.querySelector('#view-0')!;
|
||||
final DomRect platformViewRect = platformViewElement.getBoundingClientRect();
|
||||
final DomRect platformViewRect =
|
||||
platformViewElement.getBoundingClientRect();
|
||||
expect(platformViewRect.left, 0);
|
||||
expect(platformViewRect.top, 15);
|
||||
expect(platformViewRect.right, 20);
|
||||
expect(platformViewRect.bottom, 45);
|
||||
|
||||
// This test is only relevant for shadow DOM because we only really support
|
||||
// proper platform view embedding in browsers that support shadow DOM.
|
||||
final DomShadowRoot shadowRoot = appHostNode.node as DomShadowRoot;
|
||||
|
||||
// Hit test child 1
|
||||
expect(shadowRoot.elementFromPoint(10, 10), child1);
|
||||
expect(domDocument.elementFromPoint(10, 10), child1);
|
||||
|
||||
// Hit test overlap between child 1 and 2
|
||||
// TODO(yjbanov): this is a known limitation, see https://github.com/flutter/flutter/issues/101439
|
||||
expect(shadowRoot.elementFromPoint(10, 20), child1);
|
||||
expect(domDocument.elementFromPoint(10, 20), child1);
|
||||
|
||||
// Hit test child 2
|
||||
// Clicking at the location of the middle semantics node should allow the
|
||||
@ -2043,10 +2046,10 @@ void _testPlatformView() {
|
||||
expect(domDocument.elementFromPoint(10, 30), platformViewElement);
|
||||
|
||||
// Hit test overlap between child 2 and 3
|
||||
expect(shadowRoot.elementFromPoint(10, 40), child3);
|
||||
expect(domDocument.elementFromPoint(10, 40), child3);
|
||||
|
||||
// Hit test child 3
|
||||
expect(shadowRoot.elementFromPoint(10, 50), child3);
|
||||
expect(domDocument.elementFromPoint(10, 50), child3);
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
});
|
||||
@ -2111,9 +2114,11 @@ void updateNode(
|
||||
String value = '',
|
||||
List<ui.StringAttribute> valueAttributes = const <ui.StringAttribute>[],
|
||||
String increasedValue = '',
|
||||
List<ui.StringAttribute> increasedValueAttributes = const <ui.StringAttribute>[],
|
||||
List<ui.StringAttribute> increasedValueAttributes =
|
||||
const <ui.StringAttribute>[],
|
||||
String decreasedValue = '',
|
||||
List<ui.StringAttribute> decreasedValueAttributes = const <ui.StringAttribute>[],
|
||||
List<ui.StringAttribute> decreasedValueAttributes =
|
||||
const <ui.StringAttribute>[],
|
||||
String tooltip = '',
|
||||
ui.TextDirection textDirection = ui.TextDirection.ltr,
|
||||
Float64List? transform,
|
||||
|
||||
@ -8,7 +8,6 @@ import 'dart:typed_data';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/embedder.dart';
|
||||
import 'package:ui/src/engine/host_node.dart';
|
||||
import 'package:ui/src/engine/semantics.dart';
|
||||
import 'package:ui/src/engine/util.dart';
|
||||
import 'package:ui/src/engine/vector_math.dart';
|
||||
@ -19,10 +18,11 @@ import '../../common/matchers.dart';
|
||||
/// Gets the DOM host where the Flutter app is being rendered.
|
||||
///
|
||||
/// This function returns the correct host for the flutter app under testing,
|
||||
/// so we don't have to hardcode domDocument across the test. (The host of a
|
||||
/// normal flutter app used to be domDocument, but now that the app is wrapped
|
||||
/// in a Shadow DOM, that's not the case anymore.)
|
||||
HostNode get appHostNode => flutterViewEmbedder.glassPaneShadow;
|
||||
/// so we don't have to hardcode domDocument across the test. The semantics
|
||||
/// tree has moved outside of the shadowDOM as a workaround for a password
|
||||
/// autofill bug on Chrome.
|
||||
/// Ref: https://github.com/flutter/flutter/issues/87735
|
||||
DomElement get appHostNode => flutterViewEmbedder.glassPaneElement;
|
||||
|
||||
/// CSS style applied to the root of the semantics tree.
|
||||
// TODO(yjbanov): this should be handled internally by [expectSemanticsTree].
|
||||
|
||||
@ -102,11 +102,11 @@ void testMain() {
|
||||
final DomElement textField = appHostNode
|
||||
.querySelector('input[data-semantics-role="text-field"]')!;
|
||||
|
||||
expect(appHostNode.activeElement, isNot(textField));
|
||||
expect(appHostNode.ownerDocument?.activeElement, isNot(textField));
|
||||
|
||||
textField.focus();
|
||||
|
||||
expect(appHostNode.activeElement, textField);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textField);
|
||||
expect(await logger.idLog.first, 0);
|
||||
expect(await logger.actionLog.first, ui.SemanticsAction.tap);
|
||||
}, // TODO(yjbanov): https://github.com/flutter/flutter/issues/46638
|
||||
@ -115,8 +115,7 @@ void testMain() {
|
||||
skip: browserEngine != BrowserEngine.blink);
|
||||
|
||||
test('Syncs semantic state from framework', () {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
int changeCount = 0;
|
||||
int actionCount = 0;
|
||||
@ -140,8 +139,7 @@ void testMain() {
|
||||
|
||||
final TextField textField =
|
||||
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
expect(textField.editableElement, strategy.domElement);
|
||||
expect(textField.activeEditableElement.getAttribute('aria-label'), 'greeting');
|
||||
expect(textField.activeEditableElement.style.width, '10px');
|
||||
@ -154,8 +152,7 @@ void testMain() {
|
||||
rect: const ui.Rect.fromLTWH(0, 0, 12, 17),
|
||||
);
|
||||
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
expect(strategy.domElement, null);
|
||||
expect(textField.activeEditableElement.getAttribute('aria-label'), 'farewell');
|
||||
expect(textField.activeEditableElement.style.width, '12px');
|
||||
@ -201,8 +198,7 @@ void testMain() {
|
||||
test(
|
||||
'Updates editing state when receiving framework messages from the text input channel',
|
||||
() {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -246,8 +242,7 @@ void testMain() {
|
||||
});
|
||||
|
||||
test('Gives up focus after DOM blur', () {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -262,13 +257,11 @@ void testMain() {
|
||||
final TextField textField =
|
||||
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
|
||||
expect(textField.editableElement, strategy.domElement);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
// The input should not refocus after blur.
|
||||
textField.activeEditableElement.blur();
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
strategy.disable();
|
||||
});
|
||||
|
||||
@ -288,8 +281,7 @@ void testMain() {
|
||||
isFocused: true,
|
||||
);
|
||||
expect(strategy.domElement, isNotNull);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.disable();
|
||||
expect(strategy.domElement, isNull);
|
||||
@ -300,8 +292,7 @@ void testMain() {
|
||||
expect(appHostNode.contains(textField.editableElement), isTrue);
|
||||
// Editing element is not enabled.
|
||||
expect(strategy.isEnabled, isFalse);
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
});
|
||||
|
||||
test('Refocuses when setting editing state', () {
|
||||
@ -316,13 +307,11 @@ void testMain() {
|
||||
isFocused: true,
|
||||
);
|
||||
expect(strategy.domElement, isNotNull);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
// Blur the element without telling the framework.
|
||||
strategy.activeDomElement.blur();
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
// The input will have focus after editing state is set and semantics updated.
|
||||
strategy.setEditingState(EditingState(text: 'foo'));
|
||||
@ -340,8 +329,7 @@ void testMain() {
|
||||
value: 'hello',
|
||||
isFocused: true,
|
||||
);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.disable();
|
||||
});
|
||||
@ -361,8 +349,7 @@ void testMain() {
|
||||
final DomHTMLTextAreaElement textArea =
|
||||
strategy.domElement! as DomHTMLTextAreaElement;
|
||||
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -371,8 +358,7 @@ void testMain() {
|
||||
);
|
||||
|
||||
textArea.blur();
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
strategy.disable();
|
||||
// It doesn't remove the textarea from the DOM.
|
||||
@ -456,13 +442,14 @@ void testMain() {
|
||||
createTwoFieldSemantics(tester, focusFieldId: 1);
|
||||
expect(tester.apply().length, 3);
|
||||
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, tester.getTextField(1).editableElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement,
|
||||
tester.getTextField(1).editableElement);
|
||||
expect(strategy.domElement, tester.getTextField(1).editableElement);
|
||||
|
||||
createTwoFieldSemantics(tester, focusFieldId: 2);
|
||||
expect(tester.apply().length, 3);
|
||||
expect(appHostNode.activeElement, tester.getTextField(2).editableElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement,
|
||||
tester.getTextField(2).editableElement);
|
||||
expect(strategy.domElement, tester.getTextField(2).editableElement);
|
||||
}
|
||||
});
|
||||
@ -510,8 +497,7 @@ void testMain() {
|
||||
});
|
||||
|
||||
test('Syncs semantic state from framework', () {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
int changeCount = 0;
|
||||
int actionCount = 0;
|
||||
@ -535,8 +521,7 @@ void testMain() {
|
||||
final TextField textField =
|
||||
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
|
||||
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
expect(textField.editableElement, strategy.domElement);
|
||||
expect(textField.activeEditableElement.getAttribute('aria-label'), 'greeting');
|
||||
expect(textField.activeEditableElement.style.width, '10px');
|
||||
@ -552,8 +537,7 @@ void testMain() {
|
||||
appHostNode.querySelector('flt-semantics[role="textbox"]')!;
|
||||
|
||||
expect(strategy.domElement, null);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, textBox);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textBox);
|
||||
expect(textBox.getAttribute('aria-label'), 'farewell');
|
||||
|
||||
strategy.disable();
|
||||
@ -596,8 +580,7 @@ void testMain() {
|
||||
test(
|
||||
'Updates editing state when receiving framework messages from the text input channel',
|
||||
() {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -641,8 +624,7 @@ void testMain() {
|
||||
});
|
||||
|
||||
test('Gives up focus after DOM blur', () {
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(appHostNode.activeElement, null);
|
||||
expect(appHostNode.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -657,15 +639,13 @@ void testMain() {
|
||||
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
|
||||
|
||||
expect(textField.editableElement, strategy.domElement);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
// The input should not refocus after blur.
|
||||
textField.activeEditableElement.blur();
|
||||
final DomElement textBox =
|
||||
appHostNode.querySelector('flt-semantics[role="textbox"]')!;
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, textBox);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textBox);
|
||||
|
||||
strategy.disable();
|
||||
});
|
||||
@ -686,8 +666,7 @@ void testMain() {
|
||||
isFocused: true,
|
||||
);
|
||||
expect(strategy.domElement, isNotNull);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.disable();
|
||||
expect(strategy.domElement, isNull);
|
||||
@ -700,8 +679,7 @@ void testMain() {
|
||||
// Focus is on the semantic object
|
||||
final DomElement textBox =
|
||||
appHostNode.querySelector('flt-semantics[role="textbox"]')!;
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, textBox);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textBox);
|
||||
});
|
||||
|
||||
test('Refocuses when setting editing state', () {
|
||||
@ -716,15 +694,13 @@ void testMain() {
|
||||
isFocused: true,
|
||||
);
|
||||
expect(strategy.domElement, isNotNull);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
// Blur the element without telling the framework.
|
||||
strategy.activeDomElement.blur();
|
||||
final DomElement textBox =
|
||||
appHostNode.querySelector('flt-semantics[role="textbox"]')!;
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, textBox);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textBox);
|
||||
|
||||
// The input will have focus after editing state is set and semantics updated.
|
||||
strategy.setEditingState(EditingState(text: 'foo'));
|
||||
@ -742,8 +718,7 @@ void testMain() {
|
||||
value: 'hello',
|
||||
isFocused: true,
|
||||
);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.disable();
|
||||
});
|
||||
@ -760,9 +735,9 @@ void testMain() {
|
||||
isMultiline: true,
|
||||
);
|
||||
|
||||
final DomHTMLTextAreaElement textArea = strategy.domElement! as DomHTMLTextAreaElement;
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, strategy.domElement);
|
||||
final DomHTMLTextAreaElement textArea =
|
||||
strategy.domElement! as DomHTMLTextAreaElement;
|
||||
expect(appHostNode.ownerDocument?.activeElement, strategy.domElement);
|
||||
|
||||
strategy.enable(
|
||||
singlelineConfig,
|
||||
@ -776,8 +751,7 @@ void testMain() {
|
||||
final DomElement textBox =
|
||||
appHostNode.querySelector('flt-semantics[role="textbox"]')!;
|
||||
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, textBox);
|
||||
expect(appHostNode.ownerDocument?.activeElement, textBox);
|
||||
|
||||
strategy.disable();
|
||||
// It removes the textarea from the DOM.
|
||||
@ -840,13 +814,14 @@ void testMain() {
|
||||
createTwoFieldSemanticsForIos(tester, focusFieldId: 1);
|
||||
|
||||
expect(tester.apply().length, 3);
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(appHostNode.activeElement, tester.getTextField(1).editableElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement,
|
||||
tester.getTextField(1).editableElement);
|
||||
expect(strategy.domElement, tester.getTextField(1).editableElement);
|
||||
|
||||
createTwoFieldSemanticsForIos(tester, focusFieldId: 2);
|
||||
expect(tester.apply().length, 3);
|
||||
expect(appHostNode.activeElement, tester.getTextField(2).editableElement);
|
||||
expect(appHostNode.ownerDocument?.activeElement,
|
||||
tester.getTextField(2).editableElement);
|
||||
expect(strategy.domElement, tester.getTextField(2).editableElement);
|
||||
}
|
||||
});
|
||||
|
||||
@ -91,7 +91,8 @@ Future<void> testMain() async {
|
||||
);
|
||||
// The focus initially is on the body.
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
editingStrategy!.enable(
|
||||
singlelineConfig,
|
||||
@ -106,8 +107,8 @@ Future<void> testMain() async {
|
||||
final DomElement input = defaultTextEditingRoot.querySelector('input')!;
|
||||
// Now the editing element should have focus.
|
||||
|
||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||
expect(defaultTextEditingRoot.activeElement, input);
|
||||
expect(domDocument.activeElement, input);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement, input);
|
||||
|
||||
expect(editingStrategy!.domElement, input);
|
||||
expect(input.getAttribute('type'), null);
|
||||
@ -122,7 +123,8 @@ Future<void> testMain() async {
|
||||
);
|
||||
// The focus is back to the body.
|
||||
expect(domDocument.activeElement, domDocument.body);
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
});
|
||||
|
||||
test('Respects read-only config', () {
|
||||
@ -281,7 +283,7 @@ Future<void> testMain() async {
|
||||
final DomHTMLTextAreaElement textarea =
|
||||
defaultTextEditingRoot.querySelector('textarea')! as DomHTMLTextAreaElement;
|
||||
// Now the textarea should have focus.
|
||||
expect(defaultTextEditingRoot.activeElement, textarea);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement, textarea);
|
||||
expect(editingStrategy!.domElement, textarea);
|
||||
|
||||
textarea.value = 'foo\nbar';
|
||||
@ -303,7 +305,8 @@ Future<void> testMain() async {
|
||||
// The textarea should be cleaned up.
|
||||
expect(defaultTextEditingRoot.querySelectorAll('textarea'), hasLength(0));
|
||||
// The focus is back to the body.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
@ -620,7 +623,7 @@ Future<void> testMain() async {
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
|
||||
expect(defaultTextEditingRoot.activeElement,
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
textEditing!.strategy.domElement);
|
||||
});
|
||||
|
||||
@ -680,7 +683,8 @@ Future<void> testMain() async {
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
|
||||
|
||||
// Editing shouldn't have started yet.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
@ -705,7 +709,7 @@ Future<void> testMain() async {
|
||||
expect(spy.messages, hasLength(0));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
// DOM element still keeps the focus.
|
||||
expect(defaultTextEditingRoot.activeElement,
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
textEditing!.strategy.domElement);
|
||||
});
|
||||
|
||||
@ -723,7 +727,8 @@ Future<void> testMain() async {
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
|
||||
|
||||
// Editing shouldn't have started yet.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
@ -752,7 +757,8 @@ Future<void> testMain() async {
|
||||
spy.messages[0].methodName, 'TextInputClient.onConnectionClosed');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
// DOM element loses the focus.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
},
|
||||
// Test on ios-safari only.
|
||||
skip: browserEngine != BrowserEngine.webkit ||
|
||||
@ -773,7 +779,8 @@ Future<void> testMain() async {
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
|
||||
|
||||
// Editing shouldn't have started yet.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
@ -1152,7 +1159,8 @@ Future<void> testMain() async {
|
||||
// In Safari Desktop Autofill menu appears as soon as an element is
|
||||
// focused, therefore the input element is only focused after the
|
||||
// location is received.
|
||||
expect(defaultTextEditingRoot.activeElement, inputElement);
|
||||
expect(
|
||||
defaultTextEditingRoot.ownerDocument?.activeElement, inputElement);
|
||||
expect(inputElement.selectionStart, 2);
|
||||
expect(inputElement.selectionEnd, 3);
|
||||
}
|
||||
@ -1165,7 +1173,7 @@ Future<void> testMain() async {
|
||||
sendFrameworkMessage(codec.encodeMethodCall(updateSizeAndTransform));
|
||||
|
||||
// Check the element still has focus. User can keep editing.
|
||||
expect(defaultTextEditingRoot.activeElement,
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
textEditing!.strategy.domElement);
|
||||
|
||||
// Check the cursor location is the same.
|
||||
@ -1765,7 +1773,8 @@ Future<void> testMain() async {
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setClient));
|
||||
|
||||
// Editing shouldn't have started yet.
|
||||
expect(defaultTextEditingRoot.activeElement, null);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
|
||||
domDocument.body);
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
@ -2647,7 +2656,7 @@ void checkInputEditingState(
|
||||
expect(element, isNotNull);
|
||||
expect(domInstanceOfString(element, 'HTMLInputElement'), true);
|
||||
final DomHTMLInputElement input = element! as DomHTMLInputElement;
|
||||
expect(defaultTextEditingRoot.activeElement, input);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement, input);
|
||||
expect(input.value, text);
|
||||
expect(input.selectionStart, start);
|
||||
expect(input.selectionEnd, end);
|
||||
@ -2673,7 +2682,7 @@ void checkTextAreaEditingState(
|
||||
int start,
|
||||
int end,
|
||||
) {
|
||||
expect(defaultTextEditingRoot.activeElement, textarea);
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement, textarea);
|
||||
expect(textarea.value, text);
|
||||
expect(textarea.selectionStart, start);
|
||||
expect(textarea.selectionEnd, end);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user