Skip to content

Commit

Permalink
[flutter_releases] Flutter Stable 2.2.1 Engine Cherrypicks (flutter#2…
Browse files Browse the repository at this point in the history
…6405)

* 'Update Dart SDK to 375a2d7'

* Fix a11y tab traversal (flutter#25797)

* fix race with framework when using tab to traverse in a11y mode

* Fix CanvasKit SVG clipPath leak (flutter#26227)

Co-authored-by: Yegor <yjbanov@google.com>
Co-authored-by: Harry Terkelsen <hterkelsen@users.noreply.github.com>
  • Loading branch information
3 people authored May 26, 2021
1 parent a9d88a4 commit 0fdb562
Show file tree
Hide file tree
Showing 14 changed files with 1,632 additions and 774 deletions.
2 changes: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ vars = {
# Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS.
# You can use //tools/dart/create_updated_flutter_deps.py to produce
# updated revision list of existing dependencies.
'dart_revision': '9094f738083c746a15b496ea5f882362a3dc4889',
'dart_revision': '375a2d7c66c8eb5d40872bdb57dd664b5f850d94',

# WARNING: DO NOT EDIT MANUALLY
# The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py
Expand Down
57 changes: 51 additions & 6 deletions lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class HtmlViewEmbedder {
);
_rootViews[viewId] = newPlatformViewRoot;
}
_applyMutators(params.mutators, platformView);
_applyMutators(params.mutators, platformView, viewId);
}

int _countClips(MutatorsStack mutators) {
Expand Down Expand Up @@ -233,11 +233,33 @@ class HtmlViewEmbedder {
return head;
}

void _applyMutators(MutatorsStack mutators, html.Element embeddedView) {
/// Clean up the old SVG clip definitions, as this platform view is about to
/// be recomposited.
void _cleanUpClipDefs(int viewId) {
if (_svgClipDefs.containsKey(viewId)) {
final html.Element clipDefs =
_svgPathDefs!.querySelector('#sk_path_defs')!;
final List<html.Element> nodesToRemove = <html.Element>[];
final Set<String> oldDefs = _svgClipDefs[viewId]!;
for (html.Element child in clipDefs.children) {
if (oldDefs.contains(child.id)) {
nodesToRemove.add(child);
}
}
for (html.Element node in nodesToRemove) {
node.remove();
}
_svgClipDefs[viewId]!.clear();
}
}

void _applyMutators(
MutatorsStack mutators, html.Element embeddedView, int viewId) {
html.Element head = embeddedView;
Matrix4 headTransform = Matrix4.identity();
double embeddedOpacity = 1.0;
_resetAnchor(head);
_cleanUpClipDefs(viewId);

for (final Mutator mutator in mutators) {
switch (mutator.type) {
Expand Down Expand Up @@ -265,28 +287,38 @@ class HtmlViewEmbedder {
html.Element pathDefs =
_svgPathDefs!.querySelector('#sk_path_defs')!;
_clipPathCount += 1;
final String clipId = 'svgClip$_clipPathCount';
html.Node newClipPath = html.DocumentFragment.svg(
'<clipPath id="svgClip$_clipPathCount">'
'<clipPath id="$clipId">'
'<path d="${path.toSvgString()}">'
'</path></clipPath>',
treeSanitizer: _NullTreeSanitizer(),
);
pathDefs.append(newClipPath);
clipView.style.clipPath = 'url(#svgClip$_clipPathCount)';
// Store the id of the node instead of [newClipPath] directly. For
// some reason, calling `newClipPath.remove()` doesn't remove it
// from the DOM.
_svgClipDefs.putIfAbsent(viewId, () => <String>{}).add(clipId);
clipView.style.clipPath = 'url(#$clipId)';
} else if (mutator.path != null) {
final CkPath path = mutator.path as CkPath;
_ensureSvgPathDefs();
html.Element pathDefs =
_svgPathDefs!.querySelector('#sk_path_defs')!;
_clipPathCount += 1;
final String clipId = 'svgClip$_clipPathCount';
html.Node newClipPath = html.DocumentFragment.svg(
'<clipPath id="svgClip$_clipPathCount">'
'<clipPath id="$clipId">'
'<path d="${path.toSvgString()}">'
'</path></clipPath>',
treeSanitizer: _NullTreeSanitizer(),
);
pathDefs.append(newClipPath);
clipView.style.clipPath = 'url(#svgClip$_clipPathCount)';
// Store the id of the node instead of [newClipPath] directly. For
// some reason, calling `newClipPath.remove()` doesn't remove it
// from the DOM.
_svgClipDefs.putIfAbsent(viewId, () => <String>{}).add(clipId);
clipView.style.clipPath = 'url(#$clipId)';
}
_resetAnchor(clipView);
head = clipView;
Expand Down Expand Up @@ -323,6 +355,9 @@ class HtmlViewEmbedder {

html.Element? _svgPathDefs;

/// The nodes containing the SVG clip definitions needed to clip this view.
Map<int, Set<String>> _svgClipDefs = <int, Set<String>>{};

/// Ensures we add a container of SVG path defs to the DOM so they can
/// be referred to in clip-path: url(#blah).
void _ensureSvgPathDefs() {
Expand Down Expand Up @@ -411,6 +446,8 @@ class HtmlViewEmbedder {
_currentCompositionParams.remove(viewId);
_clipCount.remove(viewId);
_viewsToRecomposite.remove(viewId);
_cleanUpClipDefs(viewId);
_svgClipDefs.remove(viewId);
}
_viewsToDispose.clear();
}
Expand Down Expand Up @@ -439,6 +476,14 @@ class HtmlViewEmbedder {

_overlays[viewId] = overlay;
}

/// Deletes SVG clip paths, useful for tests.
void debugCleanupSvgClipPaths() {
_svgPathDefs?.children.single.children.forEach((element) {
element.remove();
});
_svgClipDefs.clear();
}
}

/// Caches surfaces used to overlay platform views.
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/dom_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ class DomRenderer {
/// This getter calls the `hasFocus` method of the `Document` interface.
/// See for more details:
/// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus
bool? get windowHasFocus =>
js_util.callMethod(html.document, 'hasFocus', <dynamic>[]);
bool get windowHasFocus =>
js_util.callMethod(html.document, 'hasFocus', <dynamic>[]) ?? false;

void _setupHotRestart() {
// This persists across hot restarts to clear stale DOM.
Expand Down
9 changes: 3 additions & 6 deletions lib/web_ui/lib/src/engine/semantics/label_and_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,9 @@ class LabelAndValue extends RoleManager {
final bool hasValue = semanticsObject.hasValue;
final bool hasLabel = semanticsObject.hasLabel;

// If the node is incrementable or a text field the value is reported to the
// browser via the respective role managers. We do not need to also render
// it again here.
final bool shouldDisplayValue = hasValue &&
!semanticsObject.isIncrementable &&
!semanticsObject.isTextField;
// If the node is incrementable the value is reported to the browser via
// the respective role manager. We do not need to also render it again here.
final bool shouldDisplayValue = hasValue && !semanticsObject.isIncrementable;

if (!hasLabel && !shouldDisplayValue) {
_cleanUpDom();
Expand Down
31 changes: 20 additions & 11 deletions lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ class SemanticsObject {
}

/// See [ui.SemanticsUpdateBuilder.updateNode].
int? get flags => _flags;
int? _flags;
int get flags => _flags;
int _flags = 0;

/// Whether the [flags] field has been updated but has not been applied to the
/// DOM yet.
Expand Down Expand Up @@ -584,7 +584,7 @@ class SemanticsObject {
SemanticsObject? _parent;

/// Whether this node currently has a given [SemanticsFlag].
bool hasFlag(ui.SemanticsFlag flag) => _flags! & flag.index != 0;
bool hasFlag(ui.SemanticsFlag flag) => _flags & flag.index != 0;

/// Whether [actions] contains the given action.
bool hasAction(ui.SemanticsAction action) => (_actions! & action.index) != 0;
Expand Down Expand Up @@ -785,15 +785,24 @@ class SemanticsObject {
/// > A map literal is ordered: iterating over the keys and/or values of the maps always happens in the order the keys appeared in the source code.
final Map<Role, RoleManager?> _roleManagers = <Role, RoleManager?>{};

/// Returns the role manager for the given [role].
///
/// If a role manager does not exist for the given role, returns null.
RoleManager? debugRoleManagerFor(Role role) => _roleManagers[role];

/// Detects the roles that this semantics object corresponds to and manages
/// the lifecycles of [SemanticsObjectRole] objects.
void _updateRoles() {
_updateRole(Role.labelAndValue, (hasLabel || hasValue) && !isVisualOnly);
_updateRole(Role.labelAndValue, (hasLabel || hasValue) && !isTextField && !isVisualOnly);
_updateRole(Role.textField, isTextField);
_updateRole(
Role.tappable,
hasAction(ui.SemanticsAction.tap) ||
hasFlag(ui.SemanticsFlag.isButton));

bool shouldUseTappableRole =
(hasAction(ui.SemanticsAction.tap) || hasFlag(ui.SemanticsFlag.isButton)) &&
// Text fields manage their own focus/tap interactions. We don't need the
// tappable role manager. It only confuses AT.
!isTextField;

_updateRole(Role.tappable, shouldUseTappableRole);
_updateRole(Role.incrementable, isIncrementable);
_updateRole(Role.scrollable,
isVerticalScrollContainer || isHorizontalScrollContainer);
Expand Down Expand Up @@ -1145,7 +1154,7 @@ class EngineSemanticsOwner {
_instance = null;
}

final Map<int?, SemanticsObject?> _semanticsTree = <int?, SemanticsObject?>{};
final Map<int, SemanticsObject> _semanticsTree = <int, SemanticsObject>{};

/// Map [SemanticsObject.id] to parent [SemanticsObject] it was attached to
/// this frame.
Expand Down Expand Up @@ -1222,8 +1231,8 @@ class EngineSemanticsOwner {
/// Returns the entire semantics tree for testing.
///
/// Works only in debug mode.
Map<int?, SemanticsObject?>? get debugSemanticsTree {
Map<int?, SemanticsObject?>? result;
Map<int, SemanticsObject>? get debugSemanticsTree {
Map<int, SemanticsObject>? result;
assert(() {
result = _semanticsTree;
return true;
Expand Down
9 changes: 9 additions & 0 deletions lib/web_ui/lib/src/engine/semantics/tappable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Tappable extends RoleManager {
void update() {
final html.Element element = semanticsObject.element;

// "tab-index=0" is used to allow keyboard traversal of non-form elements.
// See also: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
element.tabIndex = 0;

semanticsObject.setAriaRole(
'button', semanticsObject.hasFlag(ui.SemanticsFlag.isButton));

Expand Down Expand Up @@ -49,6 +53,11 @@ class Tappable extends RoleManager {
_stopListening();
}
}

// Request focus so that the AT shifts a11y focus to this node.
if (semanticsObject.isFlagsDirty && semanticsObject.hasFocus) {
element.focus();
}
}

void _stopListening() {
Expand Down
Loading

0 comments on commit 0fdb562

Please sign in to comment.