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 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b7f7f22
Add a text editing host node, attach all text editing nodes to it
htoor3 Feb 16, 2023
1aa89a2
whitespace
htoor3 Feb 16, 2023
f055c0b
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Feb 27, 2023
d9f1dd1
Move semantics tree outside of text editing host, change naming
htoor3 Feb 27, 2023
dfc1bab
Refactor
htoor3 Feb 27, 2023
23b5b6c
fix text editing tests
htoor3 Mar 1, 2023
546b92c
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 1, 2023
9586c58
Fix issues on semantic mode, fix tests, move platform views to new re…
htoor3 Mar 3, 2023
f74442e
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 3, 2023
ae00e40
Fix text field tests
htoor3 Mar 3, 2023
3b6f9eb
Remove unused imports
htoor3 Mar 3, 2023
4f75b6b
Fix host node tests
htoor3 Mar 3, 2023
b600dc6
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 3, 2023
df91ed5
Refactor, fix safari text field tests
htoor3 Mar 3, 2023
d8f96cd
Refactor, fix indentation, remove comments
htoor3 Mar 7, 2023
cc4ed23
Trailing whitespace
htoor3 Mar 7, 2023
de2e916
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 8, 2023
97555e5
Move conditional to compute talkback events when semantics enabled
htoor3 Mar 20, 2023
3ed55b8
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 20, 2023
36a0822
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 20, 2023
bf7f94e
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 27, 2023
401bb1c
Change function naming and comments for offset computations
htoor3 Mar 28, 2023
3815cea
Whitespace
htoor3 Mar 28, 2023
251fa13
Merge remote-tracking branch 'upstream/main' into pass-autofill-exper…
htoor3 Mar 28, 2023
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
10 changes: 8 additions & 2 deletions lib/web_ui/lib/src/engine/embedder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ class FlutterViewEmbedder {
HostNode? get glassPaneShadow => _glassPaneShadow;
HostNode? _glassPaneShadow;

DomElement? get textEditingHostNode => _textEditingHostNode;
DomElement? _textEditingHostNode;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ditman I had a discussion with @htoor3 about this and he mentioned that you were planning to remove host_node.dart. I have a different opinion that I would like to share here and get your input on:

The benefit I see in HostNode is to encapsulate the high-level DOM structure of the app (including the shadow root). This way, the rest of the engine doesn't have to worry about any of the following:

  1. Does the glass pane have a shadow root attached?
  2. Is the semantics tree rendered behind or in front of the scene host?
  3. Are text fields inside or outside the shadow root?

That said, you are more familiar with this area, maybe you have a different vision for this, especially with your work on element embedding? E.g. should HostNode and embedding strategy be merged together? Or maybe all of the HostNode encapsulation should be moved to FlutterViewEmbedder?

One thing that may help here is to determine which classes are supposed to be global/singletons and which classes are supposed to be instantiated for each app or view. To clarify: in the future, we are going to support rendering multiple flutter apps on the same page (into different host elements). In that case, is each app going to have its own FlutterViewEmbedder? Or is that supposed to be global across all apps?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was planning on removing the HostNode because the original version was only to decide if we were going to render in an Element or the ShadowDOM of an element.

We can repurpose the name for any other thing we like.

My idea was to have a FlutterApplicationElement (call it HostNode) that knows how to create all the different elements for a Flutter Web app view (the glass-pane, the different element hosts, etc...), and that can be used later in place of the global "textEditingNode", "glassPaneElement", "semanticsHelper" and family (because it's likely that the global ViewEmbedder needs to go away in a multi-view world)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, I'm glad we are in agreement on the core idea.

I actually don't like the name HostNode because it could easily be confused with the host element for embedding. So a new name is in order.

I also looked at flutter/flutter#116204 which indicates that there may be a need to keep the non-shadow implementation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I also looked at flutter/flutter#116204 which indicates that there may be a need to keep the non-shadow implementation?

Talked to Yegor about it, and there's no need to support the non-shadow implementation moving forward.


static const String defaultFontStyle = 'normal';
static const String defaultFontWeight = 'normal';
static const double defaultFontSize = 14;
Expand Down Expand Up @@ -170,6 +173,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';
Expand All @@ -189,7 +195,6 @@ class FlutterViewEmbedder {
.prepareAccessibilityPlaceholder();

glassPaneElementHostNode.appendAll(<DomNode>[
accessibilityPlaceholder,
_sceneHostElement!,

// The semantic host goes last because hit-test order-wise it must be
Expand All @@ -202,9 +207,10 @@ class FlutterViewEmbedder {
// 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,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@yjbanov would love to get your thoughts on if we can safely move the semantics tree outside of the shadowDOM and into its own node?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With the current problematic implementation of semantics, this shouldn't break anything I know of. However, one issue with the current semantics tree is that it does not contain platform views. This creates some issues with traversal order. I was thinking in the future to slice the semantics tree and interleave it with the render tree such that platform views can be embedded into the semantics tree while preserving the paint order. To do that, the semantics tree would have to be inside the shadow root, otherwise the <slot> elements won't work.

Having said that, I'm willing to cross that bridge when we get there. What's the motivation for moving it out?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Primary motivation to move it out is to fix the password autofill issue for when semantics is enabled, since <input>s will be rendered in the semantics tree in that case and they'll face the same shadowDOM issues. AFAIK, the 2 nodes that host text editing elements would be whatever host we specify in text_editing.dart and the semantics tree.

Comment thread
htoor3 marked this conversation as resolved.
]);

_textEditingHostNode?.appendChild(accessibilityPlaceholder);
_textEditingHostNode?.appendChild(semanticsHostElement);
Comment thread
htoor3 marked this conversation as resolved.
Outdated
// When debugging semantics, make the scene semi-transparent so that the
// semantics tree is more prominent.
if (configuration.debugShowSemanticsNodes) {
Expand Down
30 changes: 25 additions & 5 deletions lib/web_ui/lib/src/engine/host_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ 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.') {
final DomElement element =
domDocument.createElement('flt-shadow-host-node');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need this extra element? What do you think of the following:

                flt-glass-pane
           -------------------------
           /          |            \
#shadow-root    text-editing-host  semantics-host
                      |                  |
                  text nodes       semantics nodes

EDIT: After discussing with @htoor3 it looks like the extra element above the shadow root avoids some issues with event listeners. Please add this explanation in a comment for future reference.

@htoor3 htoor3 Feb 22, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's correct, having a direct sibling to the #shadow-root caused some rendering issues. I noticed the input listeners on the active element weren't being attached, and the inputs weren't functioning correctly.

This is likely due to the entire subtree within flt-glass-pane getting replaced whenever attachShadow gets called on it - which is standard shadowDOM behavior.

Two ways around that:

  1. Contain the #shadow-root within its own node, which is what I did above. When attachShadow gets called on that node, only that node's children will get replaced, which allows the text-editing nodes to render correctly since they exist outside of the #shadow-root's parent node.
  2. We can use <slot>s within our #shadow-root to render any of its siblings. However, it's unclear whether text inputs rendered via <slot>s will face the same Chrome password autofill issues, since the <slot>s are technically within the shadowDOM.

Edit: Option 2 actually would work and keep our tree slightly flatter if we deem that important. Quick jsfiddle shows that the items rendered via the <slot> aren't affected by the Chrome autofill bug.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, IIRC in the presence of a shadow root direct children of the node are treated quite differently from normal elements. There's an expectation that there will be a <slot> inside the shadow root to project direct children into, because the elements are laid out based on constraints provided to the slot. Without one I'm not even sure how the element would be rendered. So moving text-editing-host into a separate branch is probably safer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Would <flt-render-host> be a better name? It seems we're moving all the non-rendering things out of it, and it's used purely for positioning, laying out, and painting, which are all rendering aspects. Also, the -node suffix doesn't add useful information.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think that makes sense. I pushed up the naming change and moved the semantics tree out of the text-editing host

root.appendChild(element);
_shadow = element.attachShadow(<String, dynamic>{
'mode': 'open',
// This needs to stay false to prevent issues like this:
// - https://github.com/flutter/flutter/issues/85759
Expand Down Expand Up @@ -221,6 +222,25 @@ class ElementHostNode implements HostNode {
void appendAll(Iterable<DomNode> nodes) => nodes.forEach(append);
}

DomElement createTextEditingHostNode(DomElement root, String defaultFont) {
const String hostTagName = 'flt-text-editing-host-node';
final DomElement domElement = domDocument.createElement(hostTagName);
final DomHTMLStyleElement styleElement = createDomHTMLStyleElement();

styleElement.id = 'flt-text-editing-stylesheet';
root.appendChild(styleElement);
applyGlobalCssRulesToSheet(
styleElement.sheet! as DomCSSStyleSheet,
hasAutofillOverlay: browserHasAutofillOverlay(),
cssSelectorPrefix: hostTagName,
defaultCssFont: defaultFont,
);

root.appendChild(domElement);

return domElement;
}

// Applies the required global CSS to an incoming [DomCSSStyleSheet] `sheet`.
void applyGlobalCssRulesToSheet(
DomCSSStyleSheet sheet, {
Expand Down
9 changes: 6 additions & 3 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ void _emptyCallback(dynamic _) {}
@visibleForTesting
HostNode get defaultTextEditingRoot => flutterViewEmbedder.glassPaneShadow!;

DomElement get nonShadowTextEditingRoot =>
flutterViewEmbedder.textEditingHostNode!;

/// These style attributes are constant throughout the life time of an input
/// element.
///
Expand Down Expand Up @@ -278,7 +281,7 @@ class EngineAutofillForm {

void placeForm(DomHTMLElement mainTextEditingElement) {
formElement.append(mainTextEditingElement);
defaultTextEditingRoot.append(formElement);
nonShadowTextEditingRoot.append(formElement);
}

void storeForm() {
Expand Down Expand Up @@ -1175,7 +1178,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
// DOM later, when the first location information arrived.
// Otherwise, on Blink based Desktop browsers, the autofill menu appears
// on top left of the screen.
defaultTextEditingRoot.append(activeDomElement);
nonShadowTextEditingRoot.append(activeDomElement);
_appendedToForm = false;
}

Expand Down Expand Up @@ -1661,7 +1664,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
if (hasAutofillGroup) {
placeForm();
} else {
defaultTextEditingRoot.append(activeDomElement);
nonShadowTextEditingRoot.append(activeDomElement);
}
inputConfig.textCapitalization.setAutocapitalizeAttribute(
activeDomElement);
Expand Down