[web] Remove non-ShadowDom mode (#39915)

If we still want to do this, here's a quick PR :)

Fixes https://github.com/flutter/flutter/issues/116204
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index d86db3f..3e80846 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1908,7 +1908,7 @@
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_change_util.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/host_node.dart + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/global_styles.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart + ../../../flutter/LICENSE
@@ -4505,7 +4505,7 @@
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_change_util.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart
-FILE: ../../../flutter/lib/web_ui/lib/src/engine/host_node.dart
+FILE: ../../../flutter/lib/web_ui/lib/src/engine/global_styles.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart
diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart
index 2af3a17..6c152e9 100644
--- a/lib/web_ui/lib/src/engine.dart
+++ b/lib/web_ui/lib/src/engine.dart
@@ -65,7 +65,7 @@
 export 'engine/font_change_util.dart';
 export 'engine/fonts.dart';
 export 'engine/frame_reference.dart';
-export 'engine/host_node.dart';
+export 'engine/global_styles.dart';
 export 'engine/html/backdrop_filter.dart';
 export 'engine/html/bitmap_canvas.dart';
 export 'engine/html/canvas.dart';
diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart
index 245fd26..5ac02cf 100644
--- a/lib/web_ui/lib/src/engine/embedder.dart
+++ b/lib/web_ui/lib/src/engine/embedder.dart
@@ -4,13 +4,14 @@
 
 import 'dart:async';
 
+import 'package:ui/src/engine/safe_browser_api.dart';
 import 'package:ui/ui.dart' as ui;
 
 import '../engine.dart' show buildMode, renderer, window;
 import 'browser_detection.dart';
 import 'configuration.dart';
 import 'dom.dart';
-import 'host_node.dart';
+import 'global_styles.dart';
 import 'keyboard_binding.dart';
 import 'platform_dispatcher.dart';
 import 'pointer_binding.dart';
@@ -127,9 +128,9 @@
   DomElement get glassPaneElement => _glassPaneElement;
   late DomElement _glassPaneElement;
 
-  /// The [HostNode] of the [glassPaneElement], which contains the whole Flutter app.
-  HostNode get glassPaneShadow => _glassPaneShadow;
-  late HostNode _glassPaneShadow;
+  /// The shadow root of the [glassPaneElement], which contains the whole Flutter app.
+  DomShadowRoot get glassPaneShadow => _glassPaneShadow;
+  late DomShadowRoot _glassPaneShadow;
 
   DomElement get textEditingHostNode => _textEditingHostNode;
   late DomElement _textEditingHostNode;
@@ -171,15 +172,29 @@
     _embeddingStrategy.attachGlassPane(flutterViewElement);
     flutterViewElement.appendChild(glassPaneElement);
 
+    if (getJsProperty<Object?>(glassPaneElement, 'attachShadow') == null) {
+      throw UnsupportedError('ShadowDOM is not supported in this browser.');
+    }
+
     // Create a [HostNode] under the glass pane element, and attach everything
     // there, instead of directly underneath the glass panel.
-    //
-    // TODO(dit): clean HostNode, https://github.com/flutter/flutter/issues/116204
-    final HostNode glassPaneElementHostNode = HostNode.create(
-      glassPaneElement,
-      defaultCssFont,
+    final DomShadowRoot shadowRoot = glassPaneElement.attachShadow(<String, dynamic>{
+      'mode': 'open',
+      // This needs to stay false to prevent issues like this:
+      // - https://github.com/flutter/flutter/issues/85759
+      'delegatesFocus': false,
+    });
+    _glassPaneShadow = shadowRoot;
+
+    final DomHTMLStyleElement shadowRootStyleElement = createDomHTMLStyleElement();
+    shadowRootStyleElement.id = 'flt-internals-stylesheet';
+    // The shadowRootStyleElement must be appended to the DOM, or its `sheet` will be null later.
+    shadowRoot.appendChild(shadowRootStyleElement);
+    applyGlobalCssRulesToSheet(
+      shadowRootStyleElement,
+      hasAutofillOverlay: browserHasAutofillOverlay(),
+      defaultCssFont: defaultCssFont,
     );
-    _glassPaneShadow = glassPaneElementHostNode;
 
     _textEditingHostNode =
         createTextEditingHostNode(flutterViewElement, defaultCssFont);
@@ -202,10 +217,8 @@
         .instance.semanticsHelper
         .prepareAccessibilityPlaceholder();
 
-    glassPaneElementHostNode.appendAll(<DomNode>[
-      accessibilityPlaceholder,
-      _sceneHostElement!,
-    ]);
+    shadowRoot.append(accessibilityPlaceholder);
+    shadowRoot.append(_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
@@ -354,8 +367,7 @@
         _embeddingStrategy.attachResourcesHost(resourcesHost,
             nextTo: flutterViewElement);
       } else {
-        glassPaneShadow.node
-            .insertBefore(resourcesHost, glassPaneShadow.node.firstChild);
+        glassPaneShadow.insertBefore(resourcesHost, glassPaneShadow.firstChild);
       }
       _resourcesHost = resourcesHost;
     }
@@ -420,7 +432,7 @@
   styleElement.id = 'flt-text-editing-stylesheet';
   root.appendChild(styleElement);
   applyGlobalCssRulesToSheet(
-    styleElement.sheet! as DomCSSStyleSheet,
+    styleElement,
     hasAutofillOverlay: browserHasAutofillOverlay(),
     cssSelectorPrefix: FlutterViewEmbedder.flutterViewTagName,
     defaultCssFont: defaultFont,
diff --git a/lib/web_ui/lib/src/engine/global_styles.dart b/lib/web_ui/lib/src/engine/global_styles.dart
new file mode 100644
index 0000000..6be6914
--- /dev/null
+++ b/lib/web_ui/lib/src/engine/global_styles.dart
@@ -0,0 +1,151 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'browser_detection.dart';
+import 'dom.dart';
+import 'text_editing/text_editing.dart';
+
+// Applies the required global CSS to an incoming [DomCSSStyleSheet] `sheet`.
+void applyGlobalCssRulesToSheet(
+  DomHTMLStyleElement styleElement, {
+  required bool hasAutofillOverlay,
+  String cssSelectorPrefix = '',
+  required String defaultCssFont,
+}) {
+  // TODO(web): use more efficient CSS selectors; descendant selectors are slow.
+  // More info: https://csswizardry.com/2011/09/writing-efficient-css-selectors
+
+  assert(styleElement.sheet != null);
+  final DomCSSStyleSheet sheet = styleElement.sheet! as DomCSSStyleSheet;
+
+  // These are intentionally outrageous font parameters to make sure that the
+  // apps fully specify their text styles.
+  //
+  // Fixes #115216 by ensuring that our parameters only affect the flt-scene-host children.
+  sheet.insertRule('''
+    $cssSelectorPrefix flt-scene-host {
+      color: red;
+      font: $defaultCssFont;
+    }
+  ''', sheet.cssRules.length);
+
+  // By default on iOS, Safari would highlight the element that's being tapped
+  // on using gray background. This CSS rule disables that.
+  if (isSafari) {
+    sheet.insertRule('''
+      $cssSelectorPrefix * {
+      -webkit-tap-highlight-color: transparent;
+    }
+    ''', sheet.cssRules.length);
+  }
+
+  if (isFirefox) {
+    // For firefox set line-height, otherwise text at same font-size will
+    // measure differently in ruler.
+    //
+    // - See: https://github.com/flutter/flutter/issues/44803
+    sheet.insertRule('''
+      $cssSelectorPrefix flt-paragraph,
+      $cssSelectorPrefix flt-span {
+        line-height: 100%;
+      }
+    ''', sheet.cssRules.length);
+  }
+
+  // This undoes browser's default painting and layout attributes of range
+  // input, which is used in semantics.
+  sheet.insertRule('''
+    $cssSelectorPrefix flt-semantics input[type=range] {
+      appearance: none;
+      -webkit-appearance: none;
+      width: 100%;
+      position: absolute;
+      border: none;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+    }
+  ''', sheet.cssRules.length);
+
+  if (isSafari) {
+    sheet.insertRule('''
+      $cssSelectorPrefix flt-semantics input[type=range]::-webkit-slider-thumb {
+        -webkit-appearance: none;
+      }
+    ''', sheet.cssRules.length);
+  }
+
+  // The invisible semantic text field may have a visible cursor and selection
+  // highlight. The following 2 CSS rules force everything to be transparent.
+  sheet.insertRule('''
+    $cssSelectorPrefix input::selection {
+      background-color: transparent;
+    }
+  ''', sheet.cssRules.length);
+  sheet.insertRule('''
+    $cssSelectorPrefix textarea::selection {
+      background-color: transparent;
+    }
+  ''', sheet.cssRules.length);
+
+  sheet.insertRule('''
+    $cssSelectorPrefix flt-semantics input,
+    $cssSelectorPrefix flt-semantics textarea,
+    $cssSelectorPrefix flt-semantics [contentEditable="true"] {
+      caret-color: transparent;
+    }
+    ''', sheet.cssRules.length);
+
+  // Hide placeholder text
+  sheet.insertRule('''
+    $cssSelectorPrefix .flt-text-editing::placeholder {
+      opacity: 0;
+    }
+  ''', sheet.cssRules.length);
+
+  // This CSS makes the autofill overlay transparent in order to prevent it
+  // from overlaying on top of Flutter-rendered text inputs.
+  // See: https://github.com/flutter/flutter/issues/118337.
+  if (browserHasAutofillOverlay()) {
+    sheet.insertRule('''
+      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill,
+      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:hover,
+      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:focus,
+      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:active {
+        opacity: 0 !important;
+      }
+    ''', sheet.cssRules.length);
+  }
+
+  // Removes password reveal icon for text inputs in Edge browsers.
+  // Non-Edge browsers will crash trying to parse -ms-reveal CSS selector,
+  // so we guard it behind an isEdge check.
+  // Fixes: https://github.com/flutter/flutter/issues/83695
+  if (isEdge) {
+    // We try-catch this, because in testing, we fake Edge via the UserAgent,
+    // so the below will throw an exception (because only real Edge understands
+    // the ::-ms-reveal pseudo-selector).
+    try {
+      sheet.insertRule('''
+        $cssSelectorPrefix input::-ms-reveal {
+          display: none;
+        }
+        ''', sheet.cssRules.length);
+    } on DomException catch (e) {
+      // Browsers that don't understand ::-ms-reveal throw a DOMException
+      // of type SyntaxError.
+      domWindow.console.warn(e);
+      // Add a fake rule if our code failed because we're under testing
+      assert(() {
+        sheet.insertRule('''
+          $cssSelectorPrefix input.fallback-for-fakey-browser-in-ci {
+            display: none;
+          }
+          ''', sheet.cssRules.length);
+        return true;
+      }());
+    }
+  }
+}
diff --git a/lib/web_ui/lib/src/engine/host_node.dart b/lib/web_ui/lib/src/engine/host_node.dart
deleted file mode 100644
index dea385d..0000000
--- a/lib/web_ui/lib/src/engine/host_node.dart
+++ /dev/null
@@ -1,361 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'browser_detection.dart';
-import 'dom.dart';
-import 'embedder.dart';
-import 'safe_browser_api.dart';
-import 'text_editing/text_editing.dart';
-
-/// The interface required to host a flutter app in the DOM, and its tests.
-///
-/// Consider this as the intersection in functionality between [DomShadowRoot]
-/// (preferred Flutter rendering method) and [DomDocument] (fallback).
-///
-/// Not to be confused with [DomDocumentOrShadowRoot].
-///
-/// This also handles the stylesheet that is applied to the different types of
-/// HostNodes; for ShadowDOM there's not much to do, but for ElementNodes, the
-/// stylesheet is "namespaced" by the `flt-glass-pane` prefix, so it "only"
-/// affects things that Flutter web owns.
-abstract class HostNode {
-  /// Returns an appropriate HostNode for the given [root].
-  ///
-  /// If `attachShadow` is supported, this returns a [ShadowDomHostNode], else
-  /// this will fall-back to an [ElementHostNode].
-  factory HostNode.create(DomElement root, String defaultFont) {
-    if (getJsProperty<Object?>(root, 'attachShadow') != null) {
-      return ShadowDomHostNode(root, defaultFont);
-    } else {
-      // attachShadow not available, fall back to ElementHostNode.
-      return ElementHostNode(root, defaultFont);
-    }
-  }
-
-  /// Retrieves the [DomElement] that currently has focus.
-  ///
-  /// See:
-  /// * [Document.activeElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement)
-  DomElement? get activeElement;
-
-  /// Adds a node to the end of the child [nodes] list of this node.
-  ///
-  /// If the node already exists in this document, it will be removed from its
-  /// current parent node, then added to this node.
-  ///
-  /// This method is more efficient than `nodes.add`, and is the preferred
-  /// way of appending a child node.
-  ///
-  /// See:
-  /// * [Node.appendChild](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)
-  DomNode append(DomNode node);
-
-  /// Appends all of an [Iterable<DomNode>] to this [HostNode].
-  void appendAll(Iterable<DomNode> nodes);
-
-  /// Returns true if this node contains the specified node.
-  /// See:
-  /// * [Node.contains](https://developer.mozilla.org/en-US/docs/Web/API/Node.contains)
-  bool contains(DomNode? other);
-
-  /// Returns the currently wrapped [DomNode].
-  DomNode get node;
-
-  /// Finds the first descendant element of this document that matches the
-  /// specified group of selectors.
-  ///
-  /// [selectors] should be a string using CSS selector syntax.
-  ///
-  /// ```dart
-  /// var element1 = document.querySelector('.className');
-  /// var element2 = document.querySelector('#id');
-  /// ```
-  ///
-  /// For details about CSS selector syntax, see the
-  /// [CSS selector specification](http://www.w3.org/TR/css3-selectors/).
-  ///
-  /// See:
-  /// * [Document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
-  DomElement? querySelector(String selectors);
-
-  /// Finds all descendant elements of this document that match the specified
-  /// group of selectors.
-  ///
-  /// [selectors] should be a string using CSS selector syntax.
-  ///
-  /// ```dart
-  /// var items = document.querySelectorAll('.itemClassName');
-  /// ```
-  ///
-  /// For details about CSS selector syntax, see the
-  /// [CSS selector specification](http://www.w3.org/TR/css3-selectors/).
-  ///
-  /// See:
-  /// * [Document.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)
-  Iterable<DomElement> querySelectorAll(String selectors);
-}
-
-/// A [HostNode] implementation, backed by a [DomShadowRoot].
-///
-/// This is the preferred flutter implementation, but it might not be supported
-/// by all browsers yet.
-///
-/// The constructor might throw when calling `attachShadow`, if ShadowDOM is not
-/// supported in the current environment. In this case, a fallback [ElementHostNode]
-/// should be created instead.
-class ShadowDomHostNode implements HostNode {
-  /// Build a HostNode by attaching a [DomShadowRoot] to the `root` element.
-  ///
-  /// 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>{
-      'mode': 'open',
-      // This needs to stay false to prevent issues like this:
-      // - https://github.com/flutter/flutter/issues/85759
-      'delegatesFocus': false,
-    });
-
-    final DomHTMLStyleElement shadowRootStyleElement =
-        createDomHTMLStyleElement();
-    shadowRootStyleElement.id = 'flt-internals-stylesheet';
-    // The shadowRootStyleElement must be appended to the DOM, or its `sheet` will be null later.
-    _shadow.appendChild(shadowRootStyleElement);
-    applyGlobalCssRulesToSheet(
-      shadowRootStyleElement.sheet! as DomCSSStyleSheet,
-      hasAutofillOverlay: browserHasAutofillOverlay(),
-      defaultCssFont: defaultFont,
-    );
-  }
-
-  late DomShadowRoot _shadow;
-
-  @override
-  DomElement? get activeElement => _shadow.activeElement;
-
-  @override
-  DomElement? querySelector(String selectors) {
-    return _shadow.querySelector(selectors);
-  }
-
-  @override
-  Iterable<DomElement> querySelectorAll(String selectors) {
-    return _shadow.querySelectorAll(selectors);
-  }
-
-  @override
-  DomNode append(DomNode node) {
-    return _shadow.appendChild(node);
-  }
-
-  @override
-  bool contains(DomNode? other) {
-    return _shadow.contains(other);
-  }
-
-  @override
-  DomNode get node => _shadow;
-
-  @override
-  void appendAll(Iterable<DomNode> nodes) => nodes.forEach(append);
-}
-
-/// A [HostNode] implementation, backed by a [DomElement].
-///
-/// This is a fallback implementation, in case [ShadowDomHostNode] fails when
-/// being constructed.
-class ElementHostNode implements HostNode {
-  /// Build a HostNode by attaching a child [DomElement] to the `root` element.
-  ElementHostNode(DomElement root, String defaultFont) {
-    // Append the stylesheet here, so this class is completely symmetric to the
-    // ShadowDOM version.
-    final DomHTMLStyleElement styleElement = createDomHTMLStyleElement();
-    styleElement.id = 'flt-internals-stylesheet';
-    // The styleElement must be appended to the DOM, or its `sheet` will be null later.
-    root.appendChild(styleElement);
-    applyGlobalCssRulesToSheet(
-      styleElement.sheet! as DomCSSStyleSheet,
-      hasAutofillOverlay: browserHasAutofillOverlay(),
-      cssSelectorPrefix: FlutterViewEmbedder.flutterViewTagName,
-      defaultCssFont: defaultFont,
-    );
-
-    _element = domDocument.createElement('flt-element-host-node');
-    root.appendChild(_element);
-  }
-
-  late DomElement _element;
-
-  @override
-  DomElement? get activeElement => _element.ownerDocument?.activeElement;
-
-  @override
-  DomElement? querySelector(String selectors) {
-    return _element.querySelector(selectors);
-  }
-
-  @override
-  Iterable<DomElement> querySelectorAll(String selectors) {
-    return _element.querySelectorAll(selectors);
-  }
-
-  @override
-  DomNode append(DomNode node) {
-    return _element.appendChild(node);
-  }
-
-  @override
-  bool contains(DomNode? other) {
-    return _element.contains(other);
-  }
-
-  @override
-  DomNode get node => _element;
-
-  @override
-  void appendAll(Iterable<DomNode> nodes) => nodes.forEach(append);
-}
-
-// Applies the required global CSS to an incoming [DomCSSStyleSheet] `sheet`.
-void applyGlobalCssRulesToSheet(
-  DomCSSStyleSheet sheet, {
-  required bool hasAutofillOverlay,
-  String cssSelectorPrefix = '',
-  required String defaultCssFont,
-}) {
-  // TODO(web): use more efficient CSS selectors; descendant selectors are slow.
-  // More info: https://csswizardry.com/2011/09/writing-efficient-css-selectors
-
-  // These are intentionally outrageous font parameters to make sure that the
-  // apps fully specify their text styles.
-  //
-  // Fixes #115216 by ensuring that our parameters only affect the flt-scene-host children.
-  sheet.insertRule('''
-    $cssSelectorPrefix flt-scene-host {
-      color: red;
-      font: $defaultCssFont;
-    }
-  ''', sheet.cssRules.length);
-
-  // By default on iOS, Safari would highlight the element that's being tapped
-  // on using gray background. This CSS rule disables that.
-  if (isSafari) {
-    sheet.insertRule('''
-      $cssSelectorPrefix * {
-      -webkit-tap-highlight-color: transparent;
-    }
-    ''', sheet.cssRules.length);
-  }
-
-  if (isFirefox) {
-    // For firefox set line-height, otherwise text at same font-size will
-    // measure differently in ruler.
-    //
-    // - See: https://github.com/flutter/flutter/issues/44803
-    sheet.insertRule('''
-      $cssSelectorPrefix flt-paragraph,
-      $cssSelectorPrefix flt-span {
-        line-height: 100%;
-      }
-    ''', sheet.cssRules.length);
-  }
-
-  // This undoes browser's default painting and layout attributes of range
-  // input, which is used in semantics.
-  sheet.insertRule('''
-    $cssSelectorPrefix flt-semantics input[type=range] {
-      appearance: none;
-      -webkit-appearance: none;
-      width: 100%;
-      position: absolute;
-      border: none;
-      top: 0;
-      right: 0;
-      bottom: 0;
-      left: 0;
-    }
-  ''', sheet.cssRules.length);
-
-  if (isSafari) {
-    sheet.insertRule('''
-      $cssSelectorPrefix flt-semantics input[type=range]::-webkit-slider-thumb {
-        -webkit-appearance: none;
-      }
-    ''', sheet.cssRules.length);
-  }
-
-  // The invisible semantic text field may have a visible cursor and selection
-  // highlight. The following 2 CSS rules force everything to be transparent.
-  sheet.insertRule('''
-    $cssSelectorPrefix input::selection {
-      background-color: transparent;
-    }
-  ''', sheet.cssRules.length);
-  sheet.insertRule('''
-    $cssSelectorPrefix textarea::selection {
-      background-color: transparent;
-    }
-  ''', sheet.cssRules.length);
-
-  sheet.insertRule('''
-    $cssSelectorPrefix flt-semantics input,
-    $cssSelectorPrefix flt-semantics textarea,
-    $cssSelectorPrefix flt-semantics [contentEditable="true"] {
-      caret-color: transparent;
-    }
-    ''', sheet.cssRules.length);
-
-  // Hide placeholder text
-  sheet.insertRule('''
-    $cssSelectorPrefix .flt-text-editing::placeholder {
-      opacity: 0;
-    }
-  ''', sheet.cssRules.length);
-
-  // This CSS makes the autofill overlay transparent in order to prevent it
-  // from overlaying on top of Flutter-rendered text inputs.
-  // See: https://github.com/flutter/flutter/issues/118337.
-  if (browserHasAutofillOverlay()) {
-    sheet.insertRule('''
-      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill,
-      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:hover,
-      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:focus,
-      $cssSelectorPrefix .transparentTextEditing:-webkit-autofill:active {
-        opacity: 0 !important;
-      }
-    ''', sheet.cssRules.length);
-  }
-
-  // Removes password reveal icon for text inputs in Edge browsers.
-  // Non-Edge browsers will crash trying to parse -ms-reveal CSS selector,
-  // so we guard it behind an isEdge check.
-  // Fixes: https://github.com/flutter/flutter/issues/83695
-  if (isEdge) {
-    // We try-catch this, because in testing, we fake Edge via the UserAgent,
-    // so the below will throw an exception (because only real Edge understands
-    // the ::-ms-reveal pseudo-selector).
-    try {
-      sheet.insertRule('''
-        $cssSelectorPrefix input::-ms-reveal {
-          display: none;
-        }
-        ''', sheet.cssRules.length);
-    } on DomException catch (e) {
-      // Browsers that don't understand ::-ms-reveal throw a DOMException
-      // of type SyntaxError.
-      domWindow.console.warn(e);
-      // Add a fake rule if our code failed because we're under testing
-      assert(() {
-        sheet.insertRule('''
-          $cssSelectorPrefix input.fallback-for-fakey-browser-in-ci {
-            display: none;
-          }
-          ''', sheet.cssRules.length);
-        return true;
-      }());
-    }
-  }
-}
diff --git a/lib/web_ui/lib/src/engine/text/measurement.dart b/lib/web_ui/lib/src/engine/text/measurement.dart
index 3a026e2..a1f3bdf 100644
--- a/lib/web_ui/lib/src/engine/text/measurement.dart
+++ b/lib/web_ui/lib/src/engine/text/measurement.dart
@@ -29,7 +29,7 @@
       ..height = '0';
 
     if (root == null) {
-      flutterViewEmbedder.glassPaneShadow.node.appendChild(_rulerHost);
+      flutterViewEmbedder.glassPaneShadow.appendChild(_rulerHost);
     } else {
       root.appendChild(_rulerHost);
     }
diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
index 3e22f3d..2be7350 100644
--- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
+++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
@@ -12,7 +12,6 @@
 import '../browser_detection.dart';
 import '../dom.dart';
 import '../embedder.dart';
-import '../host_node.dart';
 import '../platform_dispatcher.dart';
 import '../safe_browser_api.dart';
 import '../semantics.dart';
diff --git a/lib/web_ui/test/engine/embedder_test.dart b/lib/web_ui/test/engine/embedder_test.dart
index 86a8283..ecba37c 100644
--- a/lib/web_ui/test/engine/embedder_test.dart
+++ b/lib/web_ui/test/engine/embedder_test.dart
@@ -52,28 +52,6 @@
     );
   });
 
-  test('renders a shadowRoot by default', () {
-    final FlutterViewEmbedder embedder = FlutterViewEmbedder();
-    final HostNode hostNode = embedder.glassPaneShadow;
-    expect(domInstanceOfString(hostNode.node, 'ShadowRoot'), isTrue);
-  });
-
-  test('starts without shadowDom available too', () {
-    final dynamic oldAttachShadow = attachShadow;
-    expect(oldAttachShadow, isNotNull);
-
-    attachShadow = null; // Break ShadowDOM
-
-    final FlutterViewEmbedder embedder = FlutterViewEmbedder();
-    final HostNode hostNode = embedder.glassPaneShadow;
-    expect(domInstanceOfString(hostNode.node, 'Element'), isTrue);
-    expect(
-      (hostNode.node as DomElement).tagName,
-      equalsIgnoringCase('flt-element-host-node'),
-    );
-    attachShadow = oldAttachShadow; // Restore ShadowDOM
-  });
-
   test('should add/remove global resource', () {
     final FlutterViewEmbedder embedder = FlutterViewEmbedder();
     final DomHTMLDivElement resource = createDomHTMLDivElement();
@@ -110,6 +88,52 @@
     expect(style, isNotNull);
     expect(style.opacity, '0');
   }, skip: browserEngine != BrowserEngine.firefox);
+
+  group('Shadow root', () {
+    late FlutterViewEmbedder embedder;
+
+    setUp(() {
+      embedder = FlutterViewEmbedder();
+    });
+
+    tearDown(() {
+      embedder.glassPaneElement.remove();
+    });
+
+    test('throws when shadowDom is not available', () {
+      final dynamic oldAttachShadow = attachShadow;
+      expect(oldAttachShadow, isNotNull);
+
+      attachShadow = null; // Break ShadowDOM
+
+      expect(() => FlutterViewEmbedder(), throwsUnsupportedError);
+      attachShadow = oldAttachShadow; // Restore ShadowDOM
+    });
+
+    test('Initializes and attaches a shadow root', () {
+      expect(domInstanceOfString(embedder.glassPaneShadow, 'ShadowRoot'), isTrue);
+      expect(embedder.glassPaneShadow.host, embedder.glassPaneElement);
+      expect(embedder.glassPaneShadow, embedder.glassPaneElement.shadowRoot);
+
+      // The shadow root should be initialized with correct parameters.
+      expect(embedder.glassPaneShadow.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(embedder.glassPaneShadow.delegatesFocus, isFalse);
+      }
+    });
+
+    test('Attaches a stylesheet to the shadow root', () {
+      final DomElement? style =
+          embedder.glassPaneShadow.querySelector('#flt-internals-stylesheet');
+
+      expect(style, isNotNull);
+      expect(style!.tagName, equalsIgnoringCase('style'));
+      expect(style.parentNode, embedder.glassPaneShadow);
+    });
+  });
 }
 
 @JS('Element.prototype.attachShadow')
diff --git a/lib/web_ui/test/engine/global_styles_test.dart b/lib/web_ui/test/engine/global_styles_test.dart
new file mode 100644
index 0000000..3abeb1f
--- /dev/null
+++ b/lib/web_ui/test/engine/global_styles_test.dart
@@ -0,0 +1,131 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/bootstrap/browser.dart';
+import 'package:test/test.dart';
+import 'package:ui/src/engine.dart';
+
+const String _kDefaultCssFont = '14px monospace';
+
+void main() {
+  internalBootstrapBrowserTest(() => testMain);
+}
+
+void testMain() {
+  late DomHTMLStyleElement styleElement;
+
+  setUp(() {
+    styleElement = createDomHTMLStyleElement();
+    domDocument.body!.append(styleElement);
+    applyGlobalCssRulesToSheet(
+      styleElement,
+      hasAutofillOverlay: browserHasAutofillOverlay(),
+      defaultCssFont: _kDefaultCssFont,
+    );
+  });
+  tearDown(() {
+    styleElement.remove();
+  });
+
+  test('(Self-test) hasCssRule can extract rules', () {
+    final bool hasRule = hasCssRule(styleElement,
+        selector: '.flt-text-editing::placeholder', declaration: 'opacity: 0');
+
+    final bool hasFakeRule = hasCssRule(styleElement,
+        selector: 'input::selection', declaration: 'color: #fabada;');
+
+    expect(hasRule, isTrue);
+    expect(hasFakeRule, isFalse);
+  });
+
+  test('Attaches outrageous text styles to flt-scene-host', () {
+    final bool hasColorRed = hasCssRule(styleElement,
+        selector: 'flt-scene-host', declaration: 'color: red');
+
+    bool hasFont = false;
+    if (isSafari) {
+      // Safari expands the shorthand rules, so we check for all we've set (separately).
+      hasFont = hasCssRule(styleElement,
+              selector: 'flt-scene-host',
+              declaration: 'font-family: monospace') &&
+          hasCssRule(styleElement,
+              selector: 'flt-scene-host', declaration: 'font-size: 14px');
+    } else {
+      hasFont = hasCssRule(styleElement,
+          selector: 'flt-scene-host', declaration: 'font: $_kDefaultCssFont');
+    }
+
+    expect(hasColorRed, isTrue,
+        reason: 'Should make foreground color red within scene host.');
+    expect(hasFont, isTrue, reason: 'Should pass default css font.');
+  });
+
+  test('Attaches styling to remove password reveal icons on Edge', () {
+    // Check that style.sheet! contains input::-ms-reveal rule
+    final bool hidesRevealIcons = hasCssRule(styleElement,
+        selector: 'input::-ms-reveal', declaration: 'display: none');
+
+    final bool codeRanInFakeyBrowser = hasCssRule(styleElement,
+        selector: 'input.fallback-for-fakey-browser-in-ci',
+        declaration: 'display: none');
+
+    if (codeRanInFakeyBrowser) {
+      print('Please, fix https://github.com/flutter/flutter/issues/116302');
+    }
+
+    expect(hidesRevealIcons || codeRanInFakeyBrowser, isTrue,
+        reason: 'In Edge, stylesheet must contain "input::-ms-reveal" rule.');
+  }, skip: !isEdge);
+
+  test('Does not attach the Edge-specific style tag on non-Edge browsers', () {
+    // Check that style.sheet! contains input::-ms-reveal rule
+    final bool hidesRevealIcons = hasCssRule(styleElement,
+        selector: 'input::-ms-reveal', declaration: 'display: none');
+
+    expect(hidesRevealIcons, isFalse);
+  }, skip: isEdge);
+
+  test(
+      'Attaches styles to hide the autofill overlay for browsers that support it',
+      () {
+    final String vendorPrefix = (isSafari || isFirefox) ? '' : '-webkit-';
+    final bool autofillOverlay = hasCssRule(styleElement,
+        selector: '.transparentTextEditing:${vendorPrefix}autofill',
+        declaration: 'opacity: 0 !important');
+    final bool autofillOverlayHovered = hasCssRule(styleElement,
+        selector: '.transparentTextEditing:${vendorPrefix}autofill:hover',
+        declaration: 'opacity: 0 !important');
+    final bool autofillOverlayFocused = hasCssRule(styleElement,
+        selector: '.transparentTextEditing:${vendorPrefix}autofill:focus',
+        declaration: 'opacity: 0 !important');
+    final bool autofillOverlayActive = hasCssRule(styleElement,
+        selector: '.transparentTextEditing:${vendorPrefix}autofill:active',
+        declaration: 'opacity: 0 !important');
+
+    expect(autofillOverlay, isTrue);
+    expect(autofillOverlayHovered, isTrue);
+    expect(autofillOverlayFocused, isTrue);
+    expect(autofillOverlayActive, isTrue);
+  }, skip: !browserHasAutofillOverlay());
+}
+
+/// Finds out whether a given CSS Rule ([selector] { [declaration]; }) exists in a [styleElement].
+bool hasCssRule(
+  DomHTMLStyleElement styleElement, {
+  required String selector,
+  required String declaration,
+}) {
+  assert(styleElement.sheet != null);
+
+  // regexr.com/740ff
+  final RegExp ruleLike =
+      RegExp('[^{]*(?:$selector)[^{]*{[^}]*(?:$declaration)[^}]*}');
+
+  final DomCSSStyleSheet sheet = styleElement.sheet! as DomCSSStyleSheet;
+
+  // Check that the cssText of any rule matches the ruleLike RegExp.
+  return sheet.cssRules
+      .map((DomCSSRule rule) => rule.cssText)
+      .any((String rule) => ruleLike.hasMatch(rule));
+}
diff --git a/lib/web_ui/test/engine/host_node_test.dart b/lib/web_ui/test/engine/host_node_test.dart
deleted file mode 100644
index 205bdaf..0000000
--- a/lib/web_ui/test/engine/host_node_test.dart
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:test/bootstrap/browser.dart';
-import 'package:test/test.dart';
-import 'package:ui/src/engine.dart';
-
-void main() {
-  internalBootstrapBrowserTest(() => testMain);
-}
-
-void testMain() {
-  final DomElement rootNode = domDocument.createElement('div');
-  domDocument.body!.append(rootNode);
-
-  group('ShadowDomHostNode', () {
-    final HostNode hostNode = ShadowDomHostNode(rootNode, '14px monospace');
-
-    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);
-
-      // The shadow root should be initialized with correct parameters.
-      expect(rootNode.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);
-      }
-    });
-
-    test('Attaches a stylesheet to the shadow root', () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-
-      expect(style, isNotNull);
-      expect(style!.tagName, equalsIgnoringCase('style'));
-    });
-
-    test('(Self-test) hasCssRule can extract rules', () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-
-      final bool hasRule = hasCssRule(style,
-          selector: '.flt-text-editing::placeholder',
-          declaration: 'opacity: 0');
-
-      final bool hasFakeRule = hasCssRule(style,
-          selector: 'input::selection', declaration: 'color: #fabada;');
-
-      expect(hasRule, isTrue);
-      expect(hasFakeRule, isFalse);
-    });
-
-    test('Attaches outrageous text styles to flt-scene-host', () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-
-      final bool hasColorRed = hasCssRule(style,
-          selector: 'flt-scene-host', declaration: 'color: red');
-
-      bool hasFont = false;
-      if (isSafari) {
-        // Safari expands the shorthand rules, so we check for all we've set (separately).
-        hasFont = hasCssRule(style,
-                selector: 'flt-scene-host',
-                declaration: 'font-family: monospace') &&
-            hasCssRule(style,
-                selector: 'flt-scene-host', declaration: 'font-size: 14px');
-      } else {
-        hasFont = hasCssRule(style,
-            selector: 'flt-scene-host', declaration: 'font: 14px monospace');
-      }
-
-      expect(hasColorRed, isTrue,
-          reason: 'Should make foreground color red within scene host.');
-      expect(hasFont, isTrue, reason: 'Should pass default css font.');
-    });
-
-    test('Attaches styling to remove password reveal icons on Edge', () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-
-      // Check that style.sheet! contains input::-ms-reveal rule
-      final bool hidesRevealIcons = hasCssRule(style,
-          selector: 'input::-ms-reveal', declaration: 'display: none');
-
-      final bool codeRanInFakeyBrowser = hasCssRule(style,
-          selector: 'input.fallback-for-fakey-browser-in-ci',
-          declaration: 'display: none');
-
-      if (codeRanInFakeyBrowser) {
-        print('Please, fix https://github.com/flutter/flutter/issues/116302');
-      }
-
-      expect(hidesRevealIcons || codeRanInFakeyBrowser, isTrue,
-          reason: 'In Edge, stylesheet must contain "input::-ms-reveal" rule.');
-    }, skip: !isEdge);
-
-    test('Does not attach the Edge-specific style tag on non-Edge browsers',
-        () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-
-      // Check that style.sheet! contains input::-ms-reveal rule
-      final bool hidesRevealIcons = hasCssRule(style,
-          selector: 'input::-ms-reveal', declaration: 'display: none');
-
-      expect(hidesRevealIcons, isFalse);
-    }, skip: isEdge);
-
-    test(
-        'Attaches styles to hide the autofill overlay for browsers that support it',
-        () {
-      final DomElement? style =
-          hostNode.querySelector('#flt-internals-stylesheet');
-      final String vendorPrefix = (isSafari || isFirefox) ? '' : '-webkit-';
-      final bool autofillOverlay = hasCssRule(style,
-          selector: '.transparentTextEditing:${vendorPrefix}autofill',
-          declaration: 'opacity: 0 !important');
-      final bool autofillOverlayHovered = hasCssRule(style,
-          selector: '.transparentTextEditing:${vendorPrefix}autofill:hover',
-          declaration: 'opacity: 0 !important');
-      final bool autofillOverlayFocused = hasCssRule(style,
-          selector: '.transparentTextEditing:${vendorPrefix}autofill:focus',
-          declaration: 'opacity: 0 !important');
-      final bool autofillOverlayActive = hasCssRule(style,
-          selector: '.transparentTextEditing:${vendorPrefix}autofill:active',
-          declaration: 'opacity: 0 !important');
-
-      expect(autofillOverlay, isTrue);
-      expect(autofillOverlayHovered, isTrue);
-      expect(autofillOverlayFocused, isTrue);
-      expect(autofillOverlayActive, isTrue);
-    }, skip: !browserHasAutofillOverlay());
-
-    _runDomTests(hostNode);
-  });
-
-  group('ElementHostNode', () {
-    final HostNode hostNode = ElementHostNode(rootNode, '');
-
-    test('Initializes and attaches a child element', () {
-      expect(domInstanceOfString(hostNode.node, 'Element'), isTrue);
-      expect((hostNode.node as DomElement).shadowRoot, isNull);
-      expect(hostNode.node.parentNode, rootNode);
-    });
-
-    _runDomTests(hostNode);
-  });
-}
-
-// The common test suite that all types of HostNode implementations need to pass.
-void _runDomTests(HostNode hostNode) {
-  group('DOM operations', () {
-    final DomElement target = domDocument.createElement('div')..id = 'yep';
-
-    setUp(() {
-      hostNode.appendAll(<DomNode>[
-        domDocument.createElement('div'),
-        target,
-        domDocument.createElement('flt-span'),
-        domDocument.createElement('div'),
-      ]);
-    });
-
-    tearDown(() {
-      hostNode.node.clearChildren();
-    });
-
-    test('querySelector', () {
-      final DomElement? found = hostNode.querySelector('#yep');
-
-      expect(found, target);
-    });
-
-    test('.contains and .append', () {
-      final DomElement another = domDocument.createElement('div')
-        ..id = 'another';
-
-      expect(hostNode.contains(target), isTrue);
-      expect(hostNode.contains(another), isFalse);
-      expect(hostNode.contains(null), isFalse);
-
-      hostNode.append(another);
-      expect(hostNode.contains(another), isTrue);
-    });
-
-    test('querySelectorAll', () {
-      final List<DomNode> found = hostNode.querySelectorAll('div').toList();
-
-      expect(found.length, 3);
-      expect(found[1], target);
-    });
-  });
-}
-
-/// Finds out whether a given CSS Rule ([selector] { [declaration]; }) exists in a [styleSheet].
-bool hasCssRule(
-  DomElement? styleSheet, {
-  required String selector,
-  required String declaration,
-}) {
-  assert(styleSheet != null);
-  assert((styleSheet! as DomHTMLStyleElement).sheet != null);
-
-  // regexr.com/740ff
-  final RegExp ruleLike =
-      RegExp('[^{]*(?:$selector)[^{]*{[^}]*(?:$declaration)[^}]*}');
-
-  final DomCSSStyleSheet sheet =
-      (styleSheet! as DomHTMLStyleElement).sheet! as DomCSSStyleSheet;
-
-  // Check that the cssText of any rule matches the ruleLike RegExp.
-  return sheet.cssRules
-      .map((DomCSSRule rule) => rule.cssText)
-      .any((String rule) => ruleLike.hasMatch(rule));
-}