[web] change viewinsets instead of physical size for keyboard (#18328)

* changing viewInsets instead of physicalSize during text editing on mobile devices

* fix indentation

* fixing rotation issues. recalculating insets after physical size change. addressing reviewer comments

* make ui.windowpadding abstract. adding windowpadding implementation to engine.
diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart
index 1c7e805..38670b5 100644
--- a/lib/web_ui/lib/src/engine/browser_detection.dart
+++ b/lib/web_ui/lib/src/engine/browser_detection.dart
@@ -155,3 +155,9 @@
 ///
 /// See [_desktopOperatingSystems].
 bool get isDesktop => _desktopOperatingSystems.contains(operatingSystem);
+
+/// A flag to check if the current browser is running on a mobile device.
+///
+/// See [_desktopOperatingSystems].
+/// See [isDesktop].
+bool get isMobile => !isDesktop;
diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart
index b6f1cd0..7884a54 100644
--- a/lib/web_ui/lib/src/engine/dom_renderer.dart
+++ b/lib/web_ui/lib/src/engine/dom_renderer.dart
@@ -465,9 +465,22 @@
   }
 
   /// Called immediately after browser window metrics change.
+  ///
+  /// When there is a text editing going on in mobile devices, do not change
+  /// the physicalSize, change the [window.viewInsets]. See:
+  /// https://api.flutter.dev/flutter/dart-ui/Window/viewInsets.html
+  /// https://api.flutter.dev/flutter/dart-ui/Window/physicalSize.html
+  ///
+  /// Note: always check for rotations for a mobile device. Update the physical
+  /// size if the change is caused by a rotation.
   void _metricsDidChange(html.Event event) {
-    window._computePhysicalSize();
-    if (window._onMetricsChanged != null) {
+    if(isMobile && !window.isRotation() && textEditing.isEditing) {
+      window.computeOnScreenKeyboardInsets();
+      window.invokeOnMetricsChanged();
+    } else {
+      window._computePhysicalSize();
+      // When physical size changes this value has to be recalculated.
+      window.computeOnScreenKeyboardInsets();
       window.invokeOnMetricsChanged();
     }
   }
diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart
index 298f79e..7c94fff 100644
--- a/lib/web_ui/lib/src/engine/window.dart
+++ b/lib/web_ui/lib/src/engine/window.dart
@@ -62,10 +62,10 @@
     if (!override) {
       double windowInnerWidth;
       double windowInnerHeight;
-      if (html.window.visualViewport != null) {
-        windowInnerWidth = html.window.visualViewport.width * devicePixelRatio;
-        windowInnerHeight =
-            html.window.visualViewport.height * devicePixelRatio;
+      final html.VisualViewport viewport = html.window.visualViewport;
+      if (viewport != null) {
+        windowInnerWidth = viewport.width * devicePixelRatio;
+        windowInnerHeight = viewport.height * devicePixelRatio;
       } else {
         windowInnerWidth = html.window.innerWidth * devicePixelRatio;
         windowInnerHeight = html.window.innerHeight * devicePixelRatio;
@@ -77,6 +77,60 @@
     }
   }
 
+  void computeOnScreenKeyboardInsets() {
+    double windowInnerHeight;
+    final html.VisualViewport viewport = html.window.visualViewport;
+    if (viewport != null) {
+      windowInnerHeight = viewport.height * devicePixelRatio;
+    } else {
+      windowInnerHeight = html.window.innerHeight * devicePixelRatio;
+    }
+    final double bottomPadding = _physicalSize.height - windowInnerHeight;
+    _viewInsets =
+        WindowPadding(bottom: bottomPadding, left: 0, right: 0, top: 0);
+  }
+
+  /// Uses the previous physical size and current innerHeight/innerWidth
+  /// values to decide if a device is rotating.
+  ///
+  /// During a rotation the height and width values will (almost) swap place.
+  /// Values can slightly differ due to space occupied by the browser header.
+  /// For example the following values are collected for Pixel 3 rotation:
+  ///
+  /// height: 658 width: 393
+  /// new height: 313 new width: 738
+  ///
+  /// The following values are from a changed caused by virtual keyboard.
+  ///
+  /// height: 658 width: 393
+  /// height: 368 width: 393
+  bool isRotation() {
+    double height = 0;
+    double width = 0;
+    if (html.window.visualViewport != null) {
+      height = html.window.visualViewport.height * devicePixelRatio;
+      width = html.window.visualViewport.width * devicePixelRatio;
+    } else {
+      height = html.window.innerHeight * devicePixelRatio;
+      width = html.window.innerWidth * devicePixelRatio;
+    }
+    // First confirm both heught and width is effected.
+    if (_physicalSize.height != height && _physicalSize.width != width) {
+      // If prior to rotation height is bigger than width it should be the
+      // opposite after the rotation and vice versa.
+      if ((_physicalSize.height > _physicalSize.width && height < width) ||
+          (_physicalSize.width > _physicalSize.height && width < height)) {
+        // Rotation detected
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  WindowPadding get viewInsets => _viewInsets;
+  WindowPadding _viewInsets = ui.WindowPadding.zero;
+
   /// Lazily populated and cleared at the end of the frame.
   ui.Size _physicalSize;
 
@@ -155,7 +209,9 @@
   /// Engine code should use this method instead of the callback directly.
   /// Otherwise zones won't work properly.
   void invokeOnMetricsChanged() {
-    _invoke(_onMetricsChanged, _onMetricsChangedZone);
+    if (window._onMetricsChanged != null) {
+      _invoke(_onMetricsChanged, _onMetricsChangedZone);
+    }
   }
 
   @override
@@ -617,3 +673,18 @@
 /// API surface, providing Web-specific functionality that the standard
 /// `dart:ui` version does not.
 final EngineWindow window = EngineWindow();
+
+/// The Web implementation of [ui.WindowPadding].
+class WindowPadding implements ui.WindowPadding {
+  const WindowPadding({
+    this.left,
+    this.top,
+    this.right,
+    this.bottom,
+  });
+
+  final double left;
+  final double top;
+  final double right;
+  final double bottom;
+}
diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart
index c46efc4..07fcae0 100644
--- a/lib/web_ui/lib/src/ui/window.dart
+++ b/lib/web_ui/lib/src/ui/window.dart
@@ -89,6 +89,8 @@
 /// [Window.padding]. View insets and padding are preferably read via
 /// [MediaQuery.of].
 ///
+/// For the engine implementation of this class see the [engine.WindowPadding].
+///
 /// For a generic class that represents distances around a rectangle, see the
 /// [EdgeInsets] class.
 ///
@@ -99,8 +101,12 @@
 ///  * [MediaQuery.of], for the preferred mechanism for accessing these values.
 ///  * [Scaffold], which automatically applies the padding in material design
 ///    applications.
-class WindowPadding {
-  const WindowPadding._({this.left, this.top, this.right, this.bottom});
+abstract class WindowPadding {
+  const factory WindowPadding._(
+      {double left,
+      double top,
+      double right,
+      double bottom}) = engine.WindowPadding;
 
   /// The distance from the left edge to the first unpadded pixel, in physical
   /// pixels.