@@ -18,6 +18,8 @@ import '../semantics.dart';
1818import '../services.dart' ;
1919import '../text/paragraph.dart' ;
2020import '../util.dart' ;
21+ import '../view_embedder/flutter_view_manager.dart' ;
22+ import '../window.dart' ;
2123import 'autofill_hint.dart' ;
2224import 'composition_aware_mixin.dart' ;
2325import 'input_action.dart' ;
@@ -48,12 +50,6 @@ const String transparentTextEditingClass = 'transparentTextEditing';
4850
4951void _emptyCallback (dynamic _) {}
5052
51- /// The default [HostNode] that hosts all DOM required for text editing when a11y is not enabled.
52- @visibleForTesting
53- // TODO(mdebbar): There could be multiple views with multiple text editing hosts.
54- // https://github.com/flutter/flutter/issues/137344
55- DomElement get defaultTextEditingRoot => EnginePlatformDispatcher .instance.implicitView! .dom.textEditingHost;
56-
5753/// These style attributes are constant throughout the life time of an input
5854/// element.
5955///
@@ -147,13 +143,45 @@ void _styleAutofillElements(
147143 elementStyle.setProperty ('caret-color' , 'transparent' );
148144}
149145
146+ void _ensureEditingElementInView (DomElement element, int viewId) {
147+ final bool isAlreadyAppended = element.isConnected ?? false ;
148+ if (! isAlreadyAppended) {
149+ // If the element is not already appended to a view, we don't need to move
150+ // it anywhere.
151+ return ;
152+ }
153+
154+ final FlutterViewManager viewManager = EnginePlatformDispatcher .instance.viewManager;
155+ final EngineFlutterView ? currentView = viewManager.findViewForElement (element);
156+ if (currentView == null ) {
157+ // For some reason, the input element was in the DOM, but it wasn't part of
158+ // any Flutter view. Should we throw?
159+ return ;
160+ }
161+
162+ if (currentView.viewId != viewId) {
163+ _insertEditingElementInView (element, viewId);
164+ }
165+ }
166+
167+ void _insertEditingElementInView (DomElement element, int viewId) {
168+ final FlutterViewManager viewManager = EnginePlatformDispatcher .instance.viewManager;
169+ final EngineFlutterView ? view = viewManager[viewId];
170+ assert (
171+ view != null ,
172+ 'Could not find View with id $viewId . This should never happen, please file a bug!' ,
173+ );
174+ view! .dom.textEditingHost.append (element);
175+ }
176+
150177/// Form that contains all the fields in the same AutofillGroup.
151178///
152179/// An [EngineAutofillForm] will only be constructed when autofill is enabled
153180/// (the default) on the current input field. See the [fromFrameworkMessage]
154181/// static method.
155182class EngineAutofillForm {
156183 EngineAutofillForm ({
184+ required this .viewId,
157185 required this .formElement,
158186 this .elements,
159187 this .items,
@@ -177,6 +205,9 @@ class EngineAutofillForm {
177205 /// See [formsOnTheDom] .
178206 final String formIdentifier;
179207
208+ /// The ID of the view that this form is rendered into.
209+ final int viewId;
210+
180211 /// Creates an [EngineAutofillFrom] from the JSON representation of a Flutter
181212 /// framework `TextInputConfiguration` object.
182213 ///
@@ -189,6 +220,7 @@ class EngineAutofillForm {
189220 ///
190221 /// Returns null if autofill is disabled for the input field.
191222 static EngineAutofillForm ? fromFrameworkMessage (
223+ int viewId,
192224 Map <String , dynamic >? focusedElementAutofill,
193225 List <dynamic >? fields,
194226 ) {
@@ -312,6 +344,7 @@ class EngineAutofillForm {
312344 insertionReferenceNode ?? = submitButton;
313345
314346 return EngineAutofillForm (
347+ viewId: viewId,
315348 formElement: formElement,
316349 elements: elements,
317350 items: items,
@@ -330,7 +363,7 @@ class EngineAutofillForm {
330363 }
331364
332365 formElement.insertBefore (mainTextEditingElement, insertionReferenceNode);
333- defaultTextEditingRoot. append (formElement);
366+ _insertEditingElementInView (formElement, viewId );
334367 }
335368
336369 void storeForm () {
@@ -944,6 +977,7 @@ class EditingState {
944977/// This corresponds to Flutter's [TextInputConfiguration] .
945978class InputConfiguration {
946979 InputConfiguration ({
980+ required this .viewId,
947981 this .inputType = EngineInputType .text,
948982 this .inputAction = 'TextInputAction.done' ,
949983 this .obscureText = false ,
@@ -958,7 +992,8 @@ class InputConfiguration {
958992
959993 InputConfiguration .fromFrameworkMessage (
960994 Map <String , dynamic > flutterInputConfiguration)
961- : inputType = EngineInputType .fromName (
995+ : viewId = flutterInputConfiguration.tryInt ('viewId' ) ?? kImplicitViewId,
996+ inputType = EngineInputType .fromName (
962997 flutterInputConfiguration.readJson ('inputType' ).readString ('name' ),
963998 isDecimal: flutterInputConfiguration.readJson ('inputType' ).tryBool ('decimal' ) ?? false ,
964999 isMultiline: flutterInputConfiguration.readJson ('inputType' ).tryBool ('isMultiline' ) ?? false ,
@@ -976,11 +1011,15 @@ class InputConfiguration {
9761011 flutterInputConfiguration.readJson ('autofill' ))
9771012 : null ,
9781013 autofillGroup = EngineAutofillForm .fromFrameworkMessage (
1014+ flutterInputConfiguration.tryInt ('viewId' ) ?? kImplicitViewId,
9791015 flutterInputConfiguration.tryJson ('autofill' ),
9801016 flutterInputConfiguration.tryList ('fields' ),
9811017 ),
9821018 enableDeltaModel = flutterInputConfiguration.tryBool ('enableDeltaModel' ) ?? false ;
9831019
1020+ /// The ID of the view that contains the text field.
1021+ final int viewId;
1022+
9841023 /// The type of information being edited in the input control.
9851024 final EngineInputType inputType;
9861025
@@ -1257,7 +1296,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
12571296 // DOM later, when the first location information arrived.
12581297 // Otherwise, on Blink based Desktop browsers, the autofill menu appears
12591298 // on top left of the screen.
1260- defaultTextEditingRoot. append (activeDomElement);
1299+ _insertEditingElementInView (activeDomElement, inputConfig.viewId );
12611300 _appendedToForm = false ;
12621301 }
12631302
@@ -1293,6 +1332,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
12931332 autofill.applyToDomElement (activeDomElement, focusedElement: true );
12941333 } else {
12951334 activeDomElement.setAttribute ('autocomplete' , 'off' );
1335+ // When the new input configuration contains a different view ID, we need
1336+ // to move the input element to the new view.
1337+ _ensureEditingElementInView (activeDomElement, inputConfiguration.viewId);
12961338 }
12971339
12981340 final String autocorrectValue = config.autocorrect ? 'on' : 'off' ;
@@ -1757,7 +1799,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
17571799 if (hasAutofillGroup) {
17581800 placeForm ();
17591801 } else {
1760- defaultTextEditingRoot. append (activeDomElement);
1802+ _insertEditingElementInView (activeDomElement, inputConfig.viewId );
17611803 }
17621804 inputConfig.textCapitalization.setAutocapitalizeAttribute (
17631805 activeDomElement);
0 commit comments