[webview_flutter_web] Copies web implementation of webview_flutter from v4_webview (#6854)

* v4 web impl

* add breaking change

* fix lints

* exclude web plugin
diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md
index 82be36f..3b2c021 100644
--- a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md
@@ -1,5 +1,7 @@
-## NEXT
+## 0.2.0
 
+* **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of
+  `webview_flutter_platform_interface`. See README for updated usage.
 * Updates minimum Flutter version to 2.10.
 
 ## 0.1.0+4
diff --git a/packages/webview_flutter/webview_flutter_web/README.md b/packages/webview_flutter/webview_flutter_web/README.md
index a7711ee..dcd1410 100644
--- a/packages/webview_flutter/webview_flutter_web/README.md
+++ b/packages/webview_flutter/webview_flutter_web/README.md
@@ -5,10 +5,8 @@
 It is currently severely limited and doesn't implement most of the available functionality.
 The following functionality is currently available:
 
-- `loadUrl` (Without headers)
-- `requestUrl`
-- `loadHTMLString` (Without `baseUrl`)
-- Setting the `initialUrl` through `CreationParams`.
+- `loadRequest`
+- `loadHtmlString` (Without `baseUrl`)
 
 Nothing else is currently supported.
 
@@ -20,7 +18,7 @@
 * [Add this package](https://pub.dev/packages/webview_flutter_web/install)
   as an explicit dependency of your project, in addition to depending on
   `webview_flutter`.
-* Register `WebWebViewPlatform` as the `WebView.platform` before creating a
+* Register `WebWebViewPlatform` as the `WebViewPlatform.instance` before creating a
   `WebView`. See below for examples.
 
 Once those steps below are complete, the APIs from `webview_flutter` listed
@@ -39,7 +37,7 @@
 import 'package:webview_flutter_web/webview_flutter_web.dart';
 
 main() {
-  WebView.platform = WebWebViewPlatform();
+  WebViewPlatform.instance = WebWebViewPlatform();
   ...
 ```
 
@@ -55,7 +53,7 @@
 import 'package:webview_flutter_web/webview_flutter_web.dart';
 
 void registerWebViewWebImplementation() {
-  WebView.platform = WebWebViewPlatform();
+  WebViewPlatform.instance = WebWebViewPlatform();
 }
 ```
 
diff --git a/packages/webview_flutter/webview_flutter_web/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_web/example/integration_test/legacy/webview_flutter_test.dart
new file mode 100644
index 0000000..db27f7a
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/example/integration_test/legacy/webview_flutter_test.dart
@@ -0,0 +1,72 @@
+// 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 'dart:async';
+import 'dart:html' as html;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:webview_flutter_web_example/legacy/web_view.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  // URLs to navigate to in tests. These need to be URLs that we are confident will
+  // always be accessible, and won't do redirection. (E.g., just
+  // 'https://www.google.com/' will sometimes redirect traffic that looks
+  // like it's coming from a bot, which is true of these tests).
+  const String primaryUrl = 'https://flutter.dev/';
+  const String secondaryUrl = 'https://www.google.com/robots.txt';
+
+  testWidgets('initialUrl', (WidgetTester tester) async {
+    final Completer<WebViewController> controllerCompleter =
+        Completer<WebViewController>();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebView(
+          key: GlobalKey(),
+          initialUrl: primaryUrl,
+          onWebViewCreated: (WebViewController controller) {
+            controllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+    await controllerCompleter.future;
+
+    // Assert an iframe has been rendered to the DOM with the correct src attribute.
+    final html.IFrameElement? element =
+        html.document.querySelector('iframe') as html.IFrameElement?;
+    expect(element, isNotNull);
+    expect(element!.src, primaryUrl);
+  });
+
+  testWidgets('loadUrl', (WidgetTester tester) async {
+    final Completer<WebViewController> controllerCompleter =
+        Completer<WebViewController>();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebView(
+          key: GlobalKey(),
+          initialUrl: primaryUrl,
+          onWebViewCreated: (WebViewController controller) {
+            controllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+    final WebViewController controller = await controllerCompleter.future;
+    await controller.loadUrl(secondaryUrl);
+
+    // Assert an iframe has been rendered to the DOM with the correct src attribute.
+    final html.IFrameElement? element =
+        html.document.querySelector('iframe') as html.IFrameElement?;
+    expect(element, isNotNull);
+    expect(element!.src, secondaryUrl);
+  });
+}
diff --git a/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart
index 232ecdd..1736d47 100644
--- a/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart
@@ -2,41 +2,48 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:html' as html;
+import 'dart:io';
 
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
-import 'package:webview_flutter_web_example/web_view.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+import 'package:webview_flutter_web/webview_flutter_web.dart';
 
-void main() {
+Future<void> main() async {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 
-  // URLs to navigate to in tests. These need to be URLs that we are confident will
-  // always be accessible, and won't do redirection. (E.g., just
-  // 'https://www.google.com/' will sometimes redirect traffic that looks
-  // like it's coming from a bot, which is true of these tests).
-  const String primaryUrl = 'https://flutter.dev/';
-  const String secondaryUrl = 'https://www.google.com/robots.txt';
+  final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0);
+  server.forEach((HttpRequest request) {
+    if (request.uri.path == '/hello.txt') {
+      request.response.writeln('Hello, world.');
+    } else {
+      fail('unexpected request: ${request.method} ${request.uri}');
+    }
+    request.response.close();
+  });
+  final String prefixUrl = 'http://${server.address.address}:${server.port}';
+  final String primaryUrl = '$prefixUrl/hello.txt';
 
-  testWidgets('initialUrl', (WidgetTester tester) async {
-    final Completer<WebViewController> controllerCompleter =
-        Completer<WebViewController>();
+  testWidgets('loadRequest', (WidgetTester tester) async {
+    final WebWebViewController controller =
+        WebWebViewController(const PlatformWebViewControllerCreationParams())
+          ..loadRequest(
+            LoadRequestParams(uri: Uri.parse(primaryUrl)),
+          );
+
     await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
-        child: WebView(
-          key: GlobalKey(),
-          initialUrl: primaryUrl,
-          onWebViewCreated: (WebViewController controller) {
-            controllerCompleter.complete(controller);
-          },
-        ),
+        child: Builder(builder: (BuildContext context) {
+          return WebWebViewWidget(
+            PlatformWebViewWidgetCreationParams(controller: controller),
+          ).build(context);
+        }),
       ),
     );
-    await controllerCompleter.future;
 
     // Assert an iframe has been rendered to the DOM with the correct src attribute.
     final html.IFrameElement? element =
@@ -45,28 +52,31 @@
     expect(element!.src, primaryUrl);
   });
 
-  testWidgets('loadUrl', (WidgetTester tester) async {
-    final Completer<WebViewController> controllerCompleter =
-        Completer<WebViewController>();
+  testWidgets('loadHtmlString', (WidgetTester tester) async {
+    final WebWebViewController controller =
+        WebWebViewController(const PlatformWebViewControllerCreationParams())
+          ..loadHtmlString(
+            'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}',
+          );
+
     await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
-        child: WebView(
-          key: GlobalKey(),
-          initialUrl: primaryUrl,
-          onWebViewCreated: (WebViewController controller) {
-            controllerCompleter.complete(controller);
-          },
-        ),
+        child: Builder(builder: (BuildContext context) {
+          return WebWebViewWidget(
+            PlatformWebViewWidgetCreationParams(controller: controller),
+          ).build(context);
+        }),
       ),
     );
-    final WebViewController controller = await controllerCompleter.future;
-    await controller.loadUrl(secondaryUrl);
 
     // Assert an iframe has been rendered to the DOM with the correct src attribute.
     final html.IFrameElement? element =
         html.document.querySelector('iframe') as html.IFrameElement?;
     expect(element, isNotNull);
-    expect(element!.src, secondaryUrl);
+    expect(
+      element!.src,
+      'data:text/html;charset=utf-8,data:text/html;charset=utf-8,test%2520html',
+    );
   });
 }
diff --git a/packages/webview_flutter/webview_flutter_web/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_web/example/lib/legacy/web_view.dart
similarity index 97%
rename from packages/webview_flutter/webview_flutter_web/example/lib/web_view.dart
rename to packages/webview_flutter/webview_flutter_web/example/lib/legacy/web_view.dart
index ffd3367..b9b8ce2 100644
--- a/packages/webview_flutter/webview_flutter_web/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_web/example/lib/legacy/web_view.dart
@@ -5,8 +5,10 @@
 import 'dart:async';
 
 import 'package:flutter/material.dart';
-import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
-import 'package:webview_flutter_web/webview_flutter_web.dart';
+// ignore: implementation_imports
+import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart';
+// ignore: implementation_imports
+import 'package:webview_flutter_web/src/webview_flutter_web_legacy.dart';
 
 /// Optional callback invoked when a web view is first created. [controller] is
 /// the [WebViewController] for the created web view.
diff --git a/packages/webview_flutter/webview_flutter_web/example/lib/main.dart b/packages/webview_flutter/webview_flutter_web/example/lib/main.dart
index c183625..ca268a2 100644
--- a/packages/webview_flutter/webview_flutter_web/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_web/example/lib/main.dart
@@ -8,10 +8,10 @@
 import 'dart:typed_data';
 import 'package:flutter/material.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
-
-import 'web_view.dart';
+import 'package:webview_flutter_web/webview_flutter_web.dart';
 
 void main() {
+  WebViewPlatform.instance = WebWebViewPlatform();
   runApp(const MaterialApp(home: _WebViewExample()));
 }
 
@@ -23,8 +23,13 @@
 }
 
 class _WebViewExampleState extends State<_WebViewExample> {
-  final Completer<WebViewController> _controller =
-      Completer<WebViewController>();
+  final PlatformWebViewController _controller = PlatformWebViewController(
+    const PlatformWebViewControllerCreationParams(),
+  )..loadRequest(
+      LoadRequestParams(
+        uri: Uri.parse('https://flutter.dev'),
+      ),
+    );
 
   @override
   Widget build(BuildContext context) {
@@ -32,15 +37,12 @@
       appBar: AppBar(
         title: const Text('Flutter WebView example'),
         actions: <Widget>[
-          _SampleMenu(_controller.future),
+          _SampleMenu(_controller),
         ],
       ),
-      body: WebView(
-        initialUrl: 'https://flutter.dev',
-        onWebViewCreated: (WebViewController controller) {
-          _controller.complete(controller);
-        },
-      ),
+      body: PlatformWebViewWidget(
+        PlatformWebViewWidgetCreationParams(controller: _controller),
+      ).build(context),
     );
   }
 }
@@ -52,41 +54,37 @@
 class _SampleMenu extends StatelessWidget {
   const _SampleMenu(this.controller);
 
-  final Future<WebViewController> controller;
+  final PlatformWebViewController controller;
 
   @override
   Widget build(BuildContext context) {
-    return FutureBuilder<WebViewController>(
-      future: controller,
-      builder:
-          (BuildContext context, AsyncSnapshot<WebViewController> controller) {
-        return PopupMenuButton<_MenuOptions>(
-          onSelected: (_MenuOptions value) {
-            switch (value) {
-              case _MenuOptions.doPostRequest:
-                _onDoPostRequest(controller.data!, context);
-                break;
-            }
-          },
-          itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
-            const PopupMenuItem<_MenuOptions>(
-              value: _MenuOptions.doPostRequest,
-              child: Text('Post Request'),
-            ),
-          ],
-        );
+    return PopupMenuButton<_MenuOptions>(
+      onSelected: (_MenuOptions value) {
+        switch (value) {
+          case _MenuOptions.doPostRequest:
+            _onDoPostRequest(controller);
+            break;
+        }
       },
+      itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
+        const PopupMenuItem<_MenuOptions>(
+          value: _MenuOptions.doPostRequest,
+          child: Text('Post Request'),
+        ),
+      ],
     );
   }
 
-  Future<void> _onDoPostRequest(
-      WebViewController controller, BuildContext context) async {
-    final WebViewRequest request = WebViewRequest(
+  Future<void> _onDoPostRequest(PlatformWebViewController controller) async {
+    final LoadRequestParams params = LoadRequestParams(
       uri: Uri.parse('https://httpbin.org/post'),
-      method: WebViewRequestMethod.post,
-      headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
+      method: LoadRequestMethod.post,
+      headers: const <String, String>{
+        'foo': 'bar',
+        'Content-Type': 'text/plain'
+      },
       body: Uint8List.fromList('Test Body'.codeUnits),
     );
-    await controller.loadRequest(request);
+    await controller.loadRequest(params);
   }
 }
diff --git a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
index e2e0796..782817e 100644
--- a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml
@@ -10,7 +10,7 @@
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  webview_flutter_platform_interface: ^1.8.0
+  webview_flutter_platform_interface: ^2.0.0
   webview_flutter_web:
     # When depending on this package from a real application you should use:
     #   webview_flutter_web: ^x.y.z
diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/http_request_factory.dart b/packages/webview_flutter/webview_flutter_web/lib/src/http_request_factory.dart
new file mode 100644
index 0000000..4bd92f0
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/lib/src/http_request_factory.dart
@@ -0,0 +1,81 @@
+// 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 'dart:html';
+
+/// Factory class for creating [HttpRequest] instances.
+class HttpRequestFactory {
+  /// Creates a [HttpRequestFactory].
+  const HttpRequestFactory();
+
+  /// Creates and sends a URL request for the specified [url].
+  ///
+  /// By default `request` will perform an HTTP GET request, but a different
+  /// method (`POST`, `PUT`, `DELETE`, etc) can be used by specifying the
+  /// [method] parameter. (See also [HttpRequest.postFormData] for `POST`
+  /// requests only.
+  ///
+  /// The Future is completed when the response is available.
+  ///
+  /// If specified, `sendData` will send data in the form of a [ByteBuffer],
+  /// [Blob], [Document], [String], or [FormData] along with the HttpRequest.
+  ///
+  /// If specified, [responseType] sets the desired response format for the
+  /// request. By default it is [String], but can also be 'arraybuffer', 'blob',
+  /// 'document', 'json', or 'text'. See also [HttpRequest.responseType]
+  /// for more information.
+  ///
+  /// The [withCredentials] parameter specified that credentials such as a cookie
+  /// (already) set in the header or
+  /// [authorization headers](http://tools.ietf.org/html/rfc1945#section-10.2)
+  /// should be specified for the request. Details to keep in mind when using
+  /// credentials:
+  ///
+  /// /// Using credentials is only useful for cross-origin requests.
+  /// /// The `Access-Control-Allow-Origin` header of `url` cannot contain a wildcard (///).
+  /// /// The `Access-Control-Allow-Credentials` header of `url` must be set to true.
+  /// /// If `Access-Control-Expose-Headers` has not been set to true, only a subset of all the response headers will be returned when calling [getAllResponseHeaders].
+  ///
+  /// The following is equivalent to the [getString] sample above:
+  ///
+  ///     var name = Uri.encodeQueryComponent('John');
+  ///     var id = Uri.encodeQueryComponent('42');
+  ///     HttpRequest.request('users.json?name=$name&id=$id')
+  ///       .then((HttpRequest resp) {
+  ///         // Do something with the response.
+  ///     });
+  ///
+  /// Here's an example of submitting an entire form with [FormData].
+  ///
+  ///     var myForm = querySelector('form#myForm');
+  ///     var data = new FormData(myForm);
+  ///     HttpRequest.request('/submit', method: 'POST', sendData: data)
+  ///       .then((HttpRequest resp) {
+  ///         // Do something with the response.
+  ///     });
+  ///
+  /// Note that requests for file:// URIs are only supported by Chrome extensions
+  /// with appropriate permissions in their manifest. Requests to file:// URIs
+  /// will also never fail- the Future will always complete successfully, even
+  /// when the file cannot be found.
+  ///
+  /// See also: [authorization headers](http://en.wikipedia.org/wiki/Basic_access_authentication).
+  Future<HttpRequest> request(String url,
+      {String? method,
+      bool? withCredentials,
+      String? responseType,
+      String? mimeType,
+      Map<String, String>? requestHeaders,
+      dynamic sendData,
+      void Function(ProgressEvent e)? onProgress}) {
+    return HttpRequest.request(url,
+        method: method,
+        withCredentials: withCredentials,
+        responseType: responseType,
+        mimeType: mimeType,
+        requestHeaders: requestHeaders,
+        sendData: sendData,
+        onProgress: onProgress);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui.dart b/packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui.dart
similarity index 100%
rename from packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui.dart
rename to packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui.dart
diff --git a/packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui_fake.dart b/packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_fake.dart
similarity index 100%
rename from packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui_fake.dart
rename to packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_fake.dart
diff --git a/packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui_real.dart b/packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_real.dart
similarity index 100%
rename from packages/webview_flutter/webview_flutter_web/lib/shims/dart_ui_real.dart
rename to packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_real.dart
diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart
new file mode 100644
index 0000000..7ef7225
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart
@@ -0,0 +1,116 @@
+// 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 'dart:convert';
+import 'dart:html';
+
+import 'package:flutter/cupertino.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+
+import 'http_request_factory.dart';
+import 'shims/dart_ui.dart' as ui;
+
+/// An implementation of [PlatformWebViewControllerCreationParams] using Flutter
+/// for Web API.
+@immutable
+class WebWebViewControllerCreationParams
+    extends PlatformWebViewControllerCreationParams {
+  /// Creates a new [AndroidWebViewControllerCreationParams] instance.
+  WebWebViewControllerCreationParams({
+    @visibleForTesting this.httpRequestFactory = const HttpRequestFactory(),
+  }) : super();
+
+  /// Creates a [WebWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams].
+  WebWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams(
+    // Recommended placeholder to prevent being broken by platform interface.
+    // ignore: avoid_unused_constructor_parameters
+    PlatformWebViewControllerCreationParams params, {
+    @visibleForTesting
+        HttpRequestFactory httpRequestFactory = const HttpRequestFactory(),
+  }) : this(httpRequestFactory: httpRequestFactory);
+
+  static int _nextIFrameId = 0;
+
+  /// Handles creating and sending URL requests.
+  final HttpRequestFactory httpRequestFactory;
+
+  /// The underlying element used as the WebView.
+  @visibleForTesting
+  final IFrameElement iFrame = IFrameElement()
+    ..id = 'webView${_nextIFrameId++}'
+    ..width = '100%'
+    ..height = '100%'
+    ..style.border = 'none';
+}
+
+/// An implementation of [PlatformWebViewController] using Flutter for Web API.
+class WebWebViewController extends PlatformWebViewController {
+  /// Constructs a [WebWebViewController].
+  WebWebViewController(PlatformWebViewControllerCreationParams params)
+      : super.implementation(params is WebWebViewControllerCreationParams
+            ? params
+            : WebWebViewControllerCreationParams
+                .fromPlatformWebViewControllerCreationParams(params));
+
+  WebWebViewControllerCreationParams get _webWebViewParams =>
+      params as WebWebViewControllerCreationParams;
+
+  @override
+  Future<void> loadHtmlString(String html, {String? baseUrl}) async {
+    // ignore: unsafe_html
+    _webWebViewParams.iFrame.src = Uri.dataFromString(
+      html,
+      mimeType: 'text/html',
+      encoding: utf8,
+    ).toString();
+  }
+
+  @override
+  Future<void> loadRequest(LoadRequestParams params) async {
+    if (!params.uri.hasScheme) {
+      throw ArgumentError(
+          'LoadRequestParams#uri is required to have a scheme.');
+    }
+    final HttpRequest httpReq =
+        await _webWebViewParams.httpRequestFactory.request(
+      params.uri.toString(),
+      method: params.method.serialize(),
+      requestHeaders: params.headers,
+      sendData: params.body,
+    );
+    final String contentType =
+        httpReq.getResponseHeader('content-type') ?? 'text/html';
+    // ignore: unsafe_html
+    _webWebViewParams.iFrame.src = Uri.dataFromString(
+      httpReq.responseText ?? '',
+      mimeType: contentType,
+      encoding: utf8,
+    ).toString();
+  }
+}
+
+/// An implementation of [PlatformWebViewWidget] using Flutter the for Web API.
+class WebWebViewWidget extends PlatformWebViewWidget {
+  /// Constructs a [WebWebViewWidget].
+  WebWebViewWidget(PlatformWebViewWidgetCreationParams params)
+      : super.implementation(params) {
+    final WebWebViewController controller =
+        params.controller as WebWebViewController;
+    ui.platformViewRegistry.registerViewFactory(
+      controller._webWebViewParams.iFrame.id,
+      (int viewId) => controller._webWebViewParams.iFrame,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return HtmlElementView(
+      key: params.key,
+      viewType: (params.controller as WebWebViewController)
+          ._webWebViewParams
+          .iFrame
+          .id,
+    );
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart
new file mode 100644
index 0000000..2624832
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart
@@ -0,0 +1,28 @@
+// 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:flutter_web_plugins/flutter_web_plugins.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+
+import 'web_webview_controller.dart';
+
+/// An implementation of [WebViewPlatform] using Flutter for Web API.
+class WebWebViewPlatform extends WebViewPlatform {
+  @override
+  PlatformWebViewController createPlatformWebViewController(
+    PlatformWebViewControllerCreationParams params,
+  ) {
+    return WebWebViewController(params);
+  }
+
+  @override
+  PlatformWebViewWidget createPlatformWebViewWidget(
+    PlatformWebViewWidgetCreationParams params,
+  ) {
+    return WebWebViewWidget(params);
+  }
+
+  /// Gets called when the plugin is registered.
+  static void registerWith(Registrar registrar) {}
+}
diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/webview_flutter_web_legacy.dart b/packages/webview_flutter/webview_flutter_web/lib/src/webview_flutter_web_legacy.dart
new file mode 100644
index 0000000..ebf3c79
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/lib/src/webview_flutter_web_legacy.dart
@@ -0,0 +1,220 @@
+// 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 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+// ignore: implementation_imports
+import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart';
+import 'http_request_factory.dart';
+import 'shims/dart_ui.dart' as ui;
+
+/// Builds an iframe based WebView.
+///
+/// This is used as the default implementation for [WebView.platform] on web.
+class WebWebViewPlatform implements WebViewPlatform {
+  /// Constructs a new instance of [WebWebViewPlatform].
+  WebWebViewPlatform() {
+    ui.platformViewRegistry.registerViewFactory(
+        'webview-iframe',
+        (int viewId) => IFrameElement()
+          ..id = 'webview-$viewId'
+          ..width = '100%'
+          ..height = '100%'
+          ..style.border = 'none');
+  }
+
+  @override
+  Widget build({
+    required BuildContext context,
+    required CreationParams creationParams,
+    required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
+    required JavascriptChannelRegistry? javascriptChannelRegistry,
+    WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+  }) {
+    return HtmlElementView(
+      viewType: 'webview-iframe',
+      onPlatformViewCreated: (int viewId) {
+        if (onWebViewPlatformCreated == null) {
+          return;
+        }
+        final IFrameElement element =
+            document.getElementById('webview-$viewId')! as IFrameElement;
+        if (creationParams.initialUrl != null) {
+          // ignore: unsafe_html
+          element.src = creationParams.initialUrl;
+        }
+        onWebViewPlatformCreated(WebWebViewPlatformController(
+          element,
+        ));
+      },
+    );
+  }
+
+  @override
+  Future<bool> clearCookies() async => false;
+
+  /// Gets called when the plugin is registered.
+  static void registerWith(Registrar registrar) {}
+}
+
+/// Implementation of [WebViewPlatformController] for web.
+class WebWebViewPlatformController implements WebViewPlatformController {
+  /// Constructs a [WebWebViewPlatformController].
+  WebWebViewPlatformController(this._element);
+
+  final IFrameElement _element;
+  HttpRequestFactory _httpRequestFactory = const HttpRequestFactory();
+
+  /// Setter for setting the HttpRequestFactory, for testing purposes.
+  @visibleForTesting
+  // ignore: avoid_setters_without_getters
+  set httpRequestFactory(HttpRequestFactory factory) {
+    _httpRequestFactory = factory;
+  }
+
+  @override
+  Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<bool> canGoBack() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<bool> canGoForward() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> clearCache() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<String?> currentUrl() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<String> evaluateJavascript(String javascript) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<int> getScrollX() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<int> getScrollY() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<String?> getTitle() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> goBack() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> goForward() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> loadUrl(String url, Map<String, String>? headers) async {
+    // ignore: unsafe_html
+    _element.src = url;
+  }
+
+  @override
+  Future<void> reload() {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> runJavascript(String javascript) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<String> runJavascriptReturningResult(String javascript) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> scrollBy(int x, int y) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> scrollTo(int x, int y) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> updateSettings(WebSettings setting) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> loadFile(String absoluteFilePath) {
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<void> loadHtmlString(
+    String html, {
+    String? baseUrl,
+  }) async {
+    // ignore: unsafe_html
+    _element.src = Uri.dataFromString(
+      html,
+      mimeType: 'text/html',
+      encoding: utf8,
+    ).toString();
+  }
+
+  @override
+  Future<void> loadRequest(WebViewRequest request) async {
+    if (!request.uri.hasScheme) {
+      throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
+    }
+    final HttpRequest httpReq = await _httpRequestFactory.request(
+        request.uri.toString(),
+        method: request.method.serialize(),
+        requestHeaders: request.headers,
+        sendData: request.body);
+    final String contentType =
+        httpReq.getResponseHeader('content-type') ?? 'text/html';
+    // ignore: unsafe_html
+    _element.src = Uri.dataFromString(
+      httpReq.responseText ?? '',
+      mimeType: contentType,
+      encoding: utf8,
+    ).toString();
+  }
+
+  @override
+  Future<void> loadFlutterAsset(String key) {
+    throw UnimplementedError();
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_web/lib/webview_flutter_web.dart b/packages/webview_flutter/webview_flutter_web/lib/webview_flutter_web.dart
index adf6495..f11c85e 100644
--- a/packages/webview_flutter/webview_flutter_web/lib/webview_flutter_web.dart
+++ b/packages/webview_flutter/webview_flutter_web/lib/webview_flutter_web.dart
@@ -2,290 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-import 'dart:convert';
-import 'dart:html';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_web_plugins/flutter_web_plugins.dart';
-import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
-import 'shims/dart_ui.dart' as ui;
+library webview_flutter_web;
 
-/// Builds an iframe based WebView.
-///
-/// This is used as the default implementation for [WebView.platform] on web.
-class WebWebViewPlatform implements WebViewPlatform {
-  /// Constructs a new instance of [WebWebViewPlatform].
-  WebWebViewPlatform() {
-    ui.platformViewRegistry.registerViewFactory(
-        'webview-iframe',
-        (int viewId) => IFrameElement()
-          ..id = 'webview-$viewId'
-          ..width = '100%'
-          ..height = '100%'
-          ..style.border = 'none');
-  }
-
-  @override
-  Widget build({
-    required BuildContext context,
-    required CreationParams creationParams,
-    required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
-    required JavascriptChannelRegistry? javascriptChannelRegistry,
-    WebViewPlatformCreatedCallback? onWebViewPlatformCreated,
-    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
-  }) {
-    return HtmlElementView(
-      viewType: 'webview-iframe',
-      onPlatformViewCreated: (int viewId) {
-        if (onWebViewPlatformCreated == null) {
-          return;
-        }
-        final IFrameElement element =
-            document.getElementById('webview-$viewId')! as IFrameElement;
-        if (creationParams.initialUrl != null) {
-          // ignore: unsafe_html
-          element.src = creationParams.initialUrl;
-        }
-        onWebViewPlatformCreated(WebWebViewPlatformController(
-          element,
-        ));
-      },
-    );
-  }
-
-  @override
-  Future<bool> clearCookies() async => false;
-
-  /// Gets called when the plugin is registered.
-  static void registerWith(Registrar registrar) {}
-}
-
-/// Implementation of [WebViewPlatformController] for web.
-class WebWebViewPlatformController implements WebViewPlatformController {
-  /// Constructs a [WebWebViewPlatformController].
-  WebWebViewPlatformController(this._element);
-
-  final IFrameElement _element;
-  HttpRequestFactory _httpRequestFactory = HttpRequestFactory();
-
-  /// Setter for setting the HttpRequestFactory, for testing purposes.
-  @visibleForTesting
-  // ignore: avoid_setters_without_getters
-  set httpRequestFactory(HttpRequestFactory factory) {
-    _httpRequestFactory = factory;
-  }
-
-  @override
-  Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<bool> canGoBack() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<bool> canGoForward() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> clearCache() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<String?> currentUrl() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<String> evaluateJavascript(String javascript) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<int> getScrollX() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<int> getScrollY() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<String?> getTitle() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> goBack() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> goForward() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> loadUrl(String url, Map<String, String>? headers) async {
-    // ignore: unsafe_html
-    _element.src = url;
-  }
-
-  @override
-  Future<void> reload() {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> runJavascript(String javascript) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<String> runJavascriptReturningResult(String javascript) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> scrollBy(int x, int y) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> scrollTo(int x, int y) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> updateSettings(WebSettings setting) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> loadFile(String absoluteFilePath) {
-    throw UnimplementedError();
-  }
-
-  @override
-  Future<void> loadHtmlString(
-    String html, {
-    String? baseUrl,
-  }) async {
-    // ignore: unsafe_html
-    _element.src = Uri.dataFromString(
-      html,
-      mimeType: 'text/html',
-      encoding: utf8,
-    ).toString();
-  }
-
-  @override
-  Future<void> loadRequest(WebViewRequest request) async {
-    if (!request.uri.hasScheme) {
-      throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
-    }
-    final HttpRequest httpReq = await _httpRequestFactory.request(
-        request.uri.toString(),
-        method: request.method.serialize(),
-        requestHeaders: request.headers,
-        sendData: request.body);
-    final String contentType =
-        httpReq.getResponseHeader('content-type') ?? 'text/html';
-    // ignore: unsafe_html
-    _element.src = Uri.dataFromString(
-      httpReq.responseText ?? '',
-      mimeType: contentType,
-      encoding: utf8,
-    ).toString();
-  }
-
-  @override
-  Future<void> loadFlutterAsset(String key) {
-    throw UnimplementedError();
-  }
-}
-
-/// Factory class for creating [HttpRequest] instances.
-class HttpRequestFactory {
-  /// Creates and sends a URL request for the specified [url].
-  ///
-  /// By default `request` will perform an HTTP GET request, but a different
-  /// method (`POST`, `PUT`, `DELETE`, etc) can be used by specifying the
-  /// [method] parameter. (See also [HttpRequest.postFormData] for `POST`
-  /// requests only.
-  ///
-  /// The Future is completed when the response is available.
-  ///
-  /// If specified, `sendData` will send data in the form of a [ByteBuffer],
-  /// [Blob], [Document], [String], or [FormData] along with the HttpRequest.
-  ///
-  /// If specified, [responseType] sets the desired response format for the
-  /// request. By default it is [String], but can also be 'arraybuffer', 'blob',
-  /// 'document', 'json', or 'text'. See also [HttpRequest.responseType]
-  /// for more information.
-  ///
-  /// The [withCredentials] parameter specified that credentials such as a cookie
-  /// (already) set in the header or
-  /// [authorization headers](http://tools.ietf.org/html/rfc1945#section-10.2)
-  /// should be specified for the request. Details to keep in mind when using
-  /// credentials:
-  ///
-  /// /// Using credentials is only useful for cross-origin requests.
-  /// /// The `Access-Control-Allow-Origin` header of `url` cannot contain a wildcard (///).
-  /// /// The `Access-Control-Allow-Credentials` header of `url` must be set to true.
-  /// /// If `Access-Control-Expose-Headers` has not been set to true, only a subset of all the response headers will be returned when calling [getAllResponseHeaders].
-  ///
-  /// The following is equivalent to the [getString] sample above:
-  ///
-  ///     var name = Uri.encodeQueryComponent('John');
-  ///     var id = Uri.encodeQueryComponent('42');
-  ///     HttpRequest.request('users.json?name=$name&id=$id')
-  ///       .then((HttpRequest resp) {
-  ///         // Do something with the response.
-  ///     });
-  ///
-  /// Here's an example of submitting an entire form with [FormData].
-  ///
-  ///     var myForm = querySelector('form#myForm');
-  ///     var data = new FormData(myForm);
-  ///     HttpRequest.request('/submit', method: 'POST', sendData: data)
-  ///       .then((HttpRequest resp) {
-  ///         // Do something with the response.
-  ///     });
-  ///
-  /// Note that requests for file:// URIs are only supported by Chrome extensions
-  /// with appropriate permissions in their manifest. Requests to file:// URIs
-  /// will also never fail- the Future will always complete successfully, even
-  /// when the file cannot be found.
-  ///
-  /// See also: [authorization headers](http://en.wikipedia.org/wiki/Basic_access_authentication).
-  Future<HttpRequest> request(String url,
-      {String? method,
-      bool? withCredentials,
-      String? responseType,
-      String? mimeType,
-      Map<String, String>? requestHeaders,
-      dynamic sendData,
-      void Function(ProgressEvent e)? onProgress}) {
-    return HttpRequest.request(url,
-        method: method,
-        withCredentials: withCredentials,
-        responseType: responseType,
-        mimeType: mimeType,
-        requestHeaders: requestHeaders,
-        sendData: sendData,
-        onProgress: onProgress);
-  }
-}
+export 'src/http_request_factory.dart';
+export 'src/web_webview_controller.dart';
+export 'src/web_webview_platform.dart';
diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml
index f27e640..10f0680 100644
--- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on web.
 repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 0.1.0+4
+version: 0.2.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -21,7 +21,7 @@
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  webview_flutter_platform_interface: ^1.8.0
+  webview_flutter_platform_interface: ^2.0.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart b/packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.dart
similarity index 95%
rename from packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart
rename to packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.dart
index 76dad6f..54e53bb 100644
--- a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart
+++ b/packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.dart
@@ -9,9 +9,11 @@
 import 'package:flutter_test/flutter_test.dart';
 import 'package:mockito/annotations.dart';
 import 'package:mockito/mockito.dart';
-import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
-import 'package:webview_flutter_web/webview_flutter_web.dart';
-import './webview_flutter_web_test.mocks.dart';
+import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart';
+import 'package:webview_flutter_web/src/http_request_factory.dart';
+import 'package:webview_flutter_web/src/webview_flutter_web_legacy.dart';
+
+import 'webview_flutter_web_test.mocks.dart';
 
 @GenerateMocks(<Type>[
   IFrameElement,
diff --git a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.mocks.dart b/packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.mocks.dart
similarity index 99%
rename from packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.mocks.dart
rename to packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.mocks.dart
index db442ee..ac7122e 100644
--- a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.mocks.dart
@@ -1,5 +1,5 @@
 // Mocks generated by Mockito 5.3.2 from annotations
-// in webview_flutter_web/test/webview_flutter_web_test.dart.
+// in webview_flutter_web/test/legacy/webview_flutter_web_test.dart.
 // Do not manually edit this file.
 
 // ignore_for_file: no_leading_underscores_for_library_prefixes
@@ -11,10 +11,11 @@
 import 'package:flutter/src/widgets/notification_listener.dart' as _i7;
 import 'package:flutter/widgets.dart' as _i4;
 import 'package:mockito/mockito.dart' as _i1;
-import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i8;
-import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'
+import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_callbacks_handler.dart'
     as _i9;
-import 'package:webview_flutter_web/webview_flutter_web.dart' as _i10;
+import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart'
+    as _i8;
+import 'package:webview_flutter_web/src/http_request_factory.dart' as _i10;
 
 // ignore_for_file: type=lint
 // ignore_for_file: avoid_redundant_argument_values
@@ -2117,11 +2118,6 @@
         ),
       ) as _i4.Widget);
   @override
-  bool get mounted => (super.noSuchMethod(
-        Invocation.getter(#mounted),
-        returnValue: false,
-      ) as bool);
-  @override
   bool get debugDoingBuild => (super.noSuchMethod(
         Invocation.getter(#debugDoingBuild),
         returnValue: false,
diff --git a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart
new file mode 100644
index 0000000..6a8f7379
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart
@@ -0,0 +1,150 @@
+// 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 'dart:html';
+// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
+// ignore: unnecessary_import
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+import 'package:webview_flutter_web/webview_flutter_web.dart';
+
+import 'web_webview_controller_test.mocks.dart';
+
+@GenerateMocks(<Type>[
+  HttpRequest,
+  HttpRequestFactory,
+])
+void main() {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  group('WebWebViewController', () {
+    group('WebWebViewControllerCreationParams', () {
+      test('sets iFrame fields', () {
+        final WebWebViewControllerCreationParams params =
+            WebWebViewControllerCreationParams();
+
+        expect(params.iFrame.id, contains('webView'));
+        expect(params.iFrame.width, '100%');
+        expect(params.iFrame.height, '100%');
+        expect(params.iFrame.style.border, 'none');
+      });
+    });
+
+    group('loadHtmlString', () {
+      test('loadHtmlString loads html into iframe', () async {
+        final WebWebViewController controller =
+            WebWebViewController(WebWebViewControllerCreationParams());
+
+        await controller.loadHtmlString('test html');
+        expect(
+          (controller.params as WebWebViewControllerCreationParams).iFrame.src,
+          'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}',
+        );
+      });
+
+      test('loadHtmlString escapes "#" correctly', () async {
+        final WebWebViewController controller =
+            WebWebViewController(WebWebViewControllerCreationParams());
+
+        await controller.loadHtmlString('#');
+        expect(
+          (controller.params as WebWebViewControllerCreationParams).iFrame.src,
+          contains('%23'),
+        );
+      });
+    });
+
+    group('loadRequest', () {
+      test('loadRequest throws ArgumentError on missing scheme', () async {
+        final WebWebViewController controller =
+            WebWebViewController(WebWebViewControllerCreationParams());
+
+        await expectLater(
+            () async => controller.loadRequest(
+                  LoadRequestParams(uri: Uri.parse('flutter.dev')),
+                ),
+            throwsA(const TypeMatcher<ArgumentError>()));
+      });
+
+      test('loadRequest makes request and loads response into iframe',
+          () async {
+        final MockHttpRequestFactory mockHttpRequestFactory =
+            MockHttpRequestFactory();
+        final WebWebViewController controller =
+            WebWebViewController(WebWebViewControllerCreationParams(
+          httpRequestFactory: mockHttpRequestFactory,
+        ));
+
+        final MockHttpRequest mockHttpRequest = MockHttpRequest();
+        when(mockHttpRequest.getResponseHeader('content-type'))
+            .thenReturn('text/plain');
+        when(mockHttpRequest.responseText).thenReturn('test data');
+
+        when(mockHttpRequestFactory.request(
+          any,
+          method: anyNamed('method'),
+          requestHeaders: anyNamed('requestHeaders'),
+          sendData: anyNamed('sendData'),
+        )).thenAnswer((_) => Future<HttpRequest>.value(mockHttpRequest));
+
+        await controller.loadRequest(LoadRequestParams(
+          uri: Uri.parse('https://flutter.dev'),
+          method: LoadRequestMethod.post,
+          body: Uint8List.fromList('test body'.codeUnits),
+          headers: const <String, String>{'Foo': 'Bar'},
+        ));
+
+        verify(mockHttpRequestFactory.request(
+          'https://flutter.dev',
+          method: 'post',
+          requestHeaders: <String, String>{'Foo': 'Bar'},
+          sendData: Uint8List.fromList('test body'.codeUnits),
+        ));
+
+        expect(
+          (controller.params as WebWebViewControllerCreationParams).iFrame.src,
+          'data:;charset=utf-8,${Uri.encodeFull('test data')}',
+        );
+      });
+
+      test('loadRequest escapes "#" correctly', () async {
+        final MockHttpRequestFactory mockHttpRequestFactory =
+            MockHttpRequestFactory();
+        final WebWebViewController controller =
+            WebWebViewController(WebWebViewControllerCreationParams(
+          httpRequestFactory: mockHttpRequestFactory,
+        ));
+
+        final MockHttpRequest mockHttpRequest = MockHttpRequest();
+        when(mockHttpRequest.getResponseHeader('content-type'))
+            .thenReturn('text/html');
+        when(mockHttpRequest.responseText).thenReturn('#');
+        when(mockHttpRequestFactory.request(
+          any,
+          method: anyNamed('method'),
+          requestHeaders: anyNamed('requestHeaders'),
+          sendData: anyNamed('sendData'),
+        )).thenAnswer((_) => Future<HttpRequest>.value(mockHttpRequest));
+
+        await controller.loadRequest(LoadRequestParams(
+          uri: Uri.parse('https://flutter.dev'),
+          method: LoadRequestMethod.post,
+          body: Uint8List.fromList('test body'.codeUnits),
+          headers: const <String, String>{'Foo': 'Bar'},
+        ));
+
+        expect(
+          (controller.params as WebWebViewControllerCreationParams).iFrame.src,
+          contains('%23'),
+        );
+      });
+    });
+  });
+}
diff --git a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart
new file mode 100644
index 0000000..f74359a
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart
@@ -0,0 +1,328 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in webview_flutter_web/test/web_webview_controller_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+import 'dart:html' as _i2;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:webview_flutter_web/src/http_request_factory.dart' as _i4;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeHttpRequestUpload_0 extends _i1.SmartFake
+    implements _i2.HttpRequestUpload {
+  _FakeHttpRequestUpload_0(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeEvents_1 extends _i1.SmartFake implements _i2.Events {
+  _FakeEvents_1(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeHttpRequest_2 extends _i1.SmartFake implements _i2.HttpRequest {
+  _FakeHttpRequest_2(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+/// A class which mocks [HttpRequest].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest {
+  MockHttpRequest() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  Map<String, String> get responseHeaders => (super.noSuchMethod(
+        Invocation.getter(#responseHeaders),
+        returnValue: <String, String>{},
+      ) as Map<String, String>);
+  @override
+  int get readyState => (super.noSuchMethod(
+        Invocation.getter(#readyState),
+        returnValue: 0,
+      ) as int);
+  @override
+  String get responseType => (super.noSuchMethod(
+        Invocation.getter(#responseType),
+        returnValue: '',
+      ) as String);
+  @override
+  set responseType(String? value) => super.noSuchMethod(
+        Invocation.setter(
+          #responseType,
+          value,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  set timeout(int? value) => super.noSuchMethod(
+        Invocation.setter(
+          #timeout,
+          value,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  _i2.HttpRequestUpload get upload => (super.noSuchMethod(
+        Invocation.getter(#upload),
+        returnValue: _FakeHttpRequestUpload_0(
+          this,
+          Invocation.getter(#upload),
+        ),
+      ) as _i2.HttpRequestUpload);
+  @override
+  set withCredentials(bool? value) => super.noSuchMethod(
+        Invocation.setter(
+          #withCredentials,
+          value,
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  _i3.Stream<_i2.Event> get onReadyStateChange => (super.noSuchMethod(
+        Invocation.getter(#onReadyStateChange),
+        returnValue: _i3.Stream<_i2.Event>.empty(),
+      ) as _i3.Stream<_i2.Event>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onAbort => (super.noSuchMethod(
+        Invocation.getter(#onAbort),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onError => (super.noSuchMethod(
+        Invocation.getter(#onError),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onLoad => (super.noSuchMethod(
+        Invocation.getter(#onLoad),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onLoadEnd => (super.noSuchMethod(
+        Invocation.getter(#onLoadEnd),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onLoadStart => (super.noSuchMethod(
+        Invocation.getter(#onLoadStart),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onProgress => (super.noSuchMethod(
+        Invocation.getter(#onProgress),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i3.Stream<_i2.ProgressEvent> get onTimeout => (super.noSuchMethod(
+        Invocation.getter(#onTimeout),
+        returnValue: _i3.Stream<_i2.ProgressEvent>.empty(),
+      ) as _i3.Stream<_i2.ProgressEvent>);
+  @override
+  _i2.Events get on => (super.noSuchMethod(
+        Invocation.getter(#on),
+        returnValue: _FakeEvents_1(
+          this,
+          Invocation.getter(#on),
+        ),
+      ) as _i2.Events);
+  @override
+  void open(
+    String? method,
+    String? url, {
+    bool? async,
+    String? user,
+    String? password,
+  }) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #open,
+          [
+            method,
+            url,
+          ],
+          {
+            #async: async,
+            #user: user,
+            #password: password,
+          },
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  void abort() => super.noSuchMethod(
+        Invocation.method(
+          #abort,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  String getAllResponseHeaders() => (super.noSuchMethod(
+        Invocation.method(
+          #getAllResponseHeaders,
+          [],
+        ),
+        returnValue: '',
+      ) as String);
+  @override
+  String? getResponseHeader(String? name) =>
+      (super.noSuchMethod(Invocation.method(
+        #getResponseHeader,
+        [name],
+      )) as String?);
+  @override
+  void overrideMimeType(String? mime) => super.noSuchMethod(
+        Invocation.method(
+          #overrideMimeType,
+          [mime],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  void send([dynamic body_OR_data]) => super.noSuchMethod(
+        Invocation.method(
+          #send,
+          [body_OR_data],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  void setRequestHeader(
+    String? name,
+    String? value,
+  ) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #setRequestHeader,
+          [
+            name,
+            value,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  void addEventListener(
+    String? type,
+    _i2.EventListener? listener, [
+    bool? useCapture,
+  ]) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #addEventListener,
+          [
+            type,
+            listener,
+            useCapture,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  void removeEventListener(
+    String? type,
+    _i2.EventListener? listener, [
+    bool? useCapture,
+  ]) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #removeEventListener,
+          [
+            type,
+            listener,
+            useCapture,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
+  @override
+  bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod(
+        Invocation.method(
+          #dispatchEvent,
+          [event],
+        ),
+        returnValue: false,
+      ) as bool);
+}
+
+/// A class which mocks [HttpRequestFactory].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockHttpRequestFactory extends _i1.Mock
+    implements _i4.HttpRequestFactory {
+  MockHttpRequestFactory() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i3.Future<_i2.HttpRequest> request(
+    String? url, {
+    String? method,
+    bool? withCredentials,
+    String? responseType,
+    String? mimeType,
+    Map<String, String>? requestHeaders,
+    dynamic sendData,
+    void Function(_i2.ProgressEvent)? onProgress,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #request,
+          [url],
+          {
+            #method: method,
+            #withCredentials: withCredentials,
+            #responseType: responseType,
+            #mimeType: mimeType,
+            #requestHeaders: requestHeaders,
+            #sendData: sendData,
+            #onProgress: onProgress,
+          },
+        ),
+        returnValue: _i3.Future<_i2.HttpRequest>.value(_FakeHttpRequest_2(
+          this,
+          Invocation.method(
+            #request,
+            [url],
+            {
+              #method: method,
+              #withCredentials: withCredentials,
+              #responseType: responseType,
+              #mimeType: mimeType,
+              #requestHeaders: requestHeaders,
+              #sendData: sendData,
+              #onProgress: onProgress,
+            },
+          ),
+        )),
+      ) as _i3.Future<_i2.HttpRequest>);
+}
diff --git a/packages/webview_flutter/webview_flutter_web/test/web_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_web/test/web_webview_widget_test.dart
new file mode 100644
index 0000000..834d95f
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_web/test/web_webview_widget_test.dart
@@ -0,0 +1,33 @@
+// 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:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+import 'package:webview_flutter_web/webview_flutter_web.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('WebWebViewWidget', () {
+    testWidgets('build returns a HtmlElementView', (WidgetTester tester) async {
+      final WebWebViewController controller =
+          WebWebViewController(WebWebViewControllerCreationParams());
+
+      final WebWebViewWidget widget = WebWebViewWidget(
+        PlatformWebViewWidgetCreationParams(
+          key: const Key('keyValue'),
+          controller: controller,
+        ),
+      );
+
+      await tester.pumpWidget(
+        Builder(builder: (BuildContext context) => widget.build(context)),
+      );
+
+      expect(find.byType(HtmlElementView), findsOneWidget);
+      expect(find.byKey(const Key('keyValue')), findsOneWidget);
+    });
+  });
+}
diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_plugins_app.yaml
index c7d589e..a19f5fe 100644
--- a/script/configs/exclude_all_plugins_app.yaml
+++ b/script/configs/exclude_all_plugins_app.yaml
@@ -14,3 +14,4 @@
 - webview_flutter_platform_interface
 - webview_flutter_wkwebview
 - webview_flutter_android
+- webview_flutter_web