diff --git a/packages/webview_flutter/webview_flutter_web/AUTHORS b/packages/webview_flutter/webview_flutter_web/AUTHORS index 05432a7fbf9..d7fd51bac63 100644 --- a/packages/webview_flutter/webview_flutter_web/AUTHORS +++ b/packages/webview_flutter/webview_flutter_web/AUTHORS @@ -5,4 +5,4 @@ Google Inc. Bodhi Mulders - +Sophie Bremer diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md index 7296d418b68..9501b67d01a 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.3.0 +* Adds support for `baseUrl` parameter in `loadHtmlString`. +* Adds `runJavaScript`. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.2.3+4 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 index 4be9dc2a375..613e2c2192a 100644 --- 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 @@ -2,11 +2,13 @@ // 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:js_interop'; import 'dart:ui_web' as ui_web; import 'package:flutter/widgets.dart'; +import 'package:web/helpers.dart'; import 'package:web/web.dart' as web; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; @@ -18,7 +20,7 @@ import 'http_request_factory.dart'; @immutable class WebWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { - /// Creates a new [AndroidWebViewControllerCreationParams] instance. + /// Creates a new [WebWebViewControllerCreationParams] instance. WebWebViewControllerCreationParams({ @visibleForTesting this.httpRequestFactory = const HttpRequestFactory(), }) : super(); @@ -45,6 +47,8 @@ class WebWebViewControllerCreationParams ..style.width = '100%' ..style.height = '100%' ..style.border = 'none'; + + final Duration _iFrameWaitDelay = const Duration(milliseconds: 100); } /// An implementation of [PlatformWebViewController] using Flutter for Web API. @@ -62,14 +66,72 @@ class WebWebViewController extends PlatformWebViewController { WebWebViewControllerCreationParams get _webWebViewParams => params as WebWebViewControllerCreationParams; + // Retrieves the iFrame's content body after attachment to DOM. + Future _getIFrameBody() async { + final web.Document document = await _getIFrameDocument(); + + while (document.body == null) { + await Future.delayed(_webWebViewParams._iFrameWaitDelay); + } + + return document.body! as HTMLBodyElement; + } + + // Retrieves the iFrame's content document after attachment to DOM. + Future _getIFrameDocument() async { + try { + // If the document is not yet available, wait for the 'load' event. + if (_webWebViewParams.iFrame.contentDocument == null) { + final Completer completer = Completer(); + _webWebViewParams.iFrame.addEventListener( + 'load', + (web.Event _) { + completer.complete(); + }.toJS, + AddEventListenerOptions(once: true), + ); + // If src is not set, the iframe will never load. + if (_webWebViewParams.iFrame.src.isEmpty) { + _webWebViewParams.iFrame.src = 'about:blank'; + } + await completer.future; + } + // Test origin permission + _webWebViewParams.iFrame.contentDocument!.body; + // Return on success + return _webWebViewParams.iFrame.contentDocument!; + } catch (_) { + throw StateError('Web view origin mismatch'); + } + } + @override - Future loadHtmlString(String html, {String? baseUrl}) async { - _webWebViewParams.iFrame.src = - Uri.dataFromString( - html, - mimeType: 'text/html', - encoding: utf8, - ).toString(); + Future loadHtmlString(String html, {String? baseUrl}) { + final Completer loading = Completer(); + + // Load listener for load completion + _webWebViewParams.iFrame.addEventListener( + 'load', + () { + try { + _webWebViewParams.iFrame.contentDocument?.write(html.toJS); + } finally { + loading.complete(); + } + }.toJS, + AddEventListenerOptions(once: true), + ); + + // Initiate load + _webWebViewParams.iFrame.src = baseUrl ?? 'about:blank'; + + // Time out in case load listener is not triggered + Future.delayed( + const Duration(minutes: 3), + ).then((_) => loading.complete()); + + // Return future completion + return loading.future; } @override @@ -89,6 +151,49 @@ class WebWebViewController extends PlatformWebViewController { } } + @override + Future runJavaScript(String javaScript) async { + final Completer run = Completer(); + final web.Document document = await _getIFrameDocument(); + final web.HTMLBodyElement body = await _getIFrameBody(); + final web.HTMLScriptElement script = + document.createElement('script') as web.HTMLScriptElement; + + // Load listener for script completion + script.addEventListener( + 'load', + () { + try { + body.removeChild(script); + } finally { + run.complete(); + } + }.toJS, + AddEventListenerOptions(once: true), + ); + + // Prepare script + script.src = + Uri.dataFromString( + javaScript, + mimeType: 'text/javascript', + encoding: utf8, + ).toString(); + + // Initiate script execution + body.appendChild(script); + + // Time out in case load listener is not triggered + unawaited( + Future.delayed( + const Duration(seconds: 3), + ).then((_) => run.complete()), + ); + + // Return future completion + await run.future; + } + /// Performs an AJAX request defined by [params]. Future _updateIFrameFromXhr(LoadRequestParams params) async { final web.Response response = diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml index c77b853f16f..259ab990cf2 100644 --- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_web description: A Flutter plugin that provides a WebView widget on web. repository: https://github.com/flutter/packages/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.2.3+4 +version: 0.3.0 environment: sdk: ^3.7.0 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 index 23d996ed3ca..bdd4b93062e 100644 --- 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 @@ -44,22 +44,18 @@ void main() { WebWebViewControllerCreationParams(), ); - await controller.loadHtmlString('test html'); + await controller.loadHtmlString('test #html'); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, - 'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}', + 'about:blank', ); - }); - - test('loadHtmlString escapes "#" correctly', () async { - final WebWebViewController controller = WebWebViewController( - WebWebViewControllerCreationParams(), - ); - - await controller.loadHtmlString('#'); expect( - (controller.params as WebWebViewControllerCreationParams).iFrame.src, - contains('%23'), + (controller.params as WebWebViewControllerCreationParams) + .iFrame + .contentDocument! + .body! + .innerHTML, + 'test #html', ); }); });