Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[webview_flutter] Add unimplemented features to the web version. #5808

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/webview_flutter/webview_flutter_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.1.1

* Fixes unreliable encoding of HTML to the iframe element.
* Adds JavascriptChannels and running Javascript support only for loading Html as a string.

## 0.1.0+3

* Minor fixes for new analysis options.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_web_example/web_view.dart';

void main() {
Expand Down Expand Up @@ -69,4 +70,39 @@ void main() {
expect(element, isNotNull);
expect(element!.src, secondaryUrl);
});

testWidgets('JavascriptChannel', (WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
final List<String> messagesReceived = <String>[];
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: GlobalKey(),
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Echo',
onMessageReceived: (JavascriptMessage message) {
messagesReceived.add(message.message);
},
),
},
),
),
);
final WebViewController controller = await controllerCompleter.future;
await controller.loadHtmlString('<div></div>');

// 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);

await controller.runJavascript('Echo.postMessage("hello");');
expect(messagesReceived, equals(<String>['hello']));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class WebView extends StatefulWidget {
Key? key,
this.onWebViewCreated,
this.initialUrl,
this.javascriptChannels,
}) : super(key: key);

/// The WebView platform that's used by this WebView.
Expand All @@ -45,6 +46,9 @@ class WebView extends StatefulWidget {
/// The initial URL to load.
final String? initialUrl;

/// The set of [JavascriptChannel]s available to JavaScript code running in the web view.
final Set<JavascriptChannel>? javascriptChannels;

@override
State<WebView> createState() => _WebViewState();
}
Expand Down Expand Up @@ -90,7 +94,7 @@ class _WebViewState extends State<WebView> {
webSettings: _webSettingsFromWidget(widget),
),
javascriptChannelRegistry:
JavascriptChannelRegistry(<JavascriptChannel>{}),
JavascriptChannelRegistry(widget.javascriptChannels),
);
}
}
Expand Down Expand Up @@ -159,6 +163,11 @@ class WebViewController {
return _webViewPlatformController.loadRequest(request);
}

/// Loads an Html document.
Future<void> loadHtmlString(String html) async {
return _webViewPlatformController.loadHtmlString(html);
}

/// Accessor to the current URL that the WebView is displaying.
///
/// If [WebView.initialUrl] was never specified, returns `null`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import 'dart:async';
import 'dart:html';
import 'dart:js' as js;
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:html/dom.dart' as dom;
import 'package:html/parser.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'shims/dart_ui.dart' as ui;

Expand Down Expand Up @@ -41,15 +44,29 @@ class WebWebViewPlatform implements WebViewPlatform {
if (onWebViewPlatformCreated == null) {
return;
}

final IFrameElement element =
document.getElementById('webview-$viewId')! as IFrameElement;

final WebWebViewPlatformController controller =
WebWebViewPlatformController(
element, viewId, javascriptChannelRegistry);

js.context['webview${viewId}_getWindow'] = (js.JsObject window) {
controller.window = window;
};

js.context['webview${viewId}_channel'] = (String name, String message) {
javascriptChannelRegistry?.channels.values
.firstWhere((JavascriptChannel channel) => channel.name == name)
.onMessageReceived(JavascriptMessage(message));
};

if (creationParams.initialUrl != null) {
// ignore: unsafe_html
element.src = creationParams.initialUrl;
}
onWebViewPlatformCreated(WebWebViewPlatformController(
element,
));
onWebViewPlatformCreated(controller);
},
);
}
Expand All @@ -64,8 +81,14 @@ class WebWebViewPlatform implements WebViewPlatform {
/// Implementation of [WebViewPlatformController] for web.
class WebWebViewPlatformController implements WebViewPlatformController {
/// Constructs a [WebWebViewPlatformController].
WebWebViewPlatformController(this._element);
WebWebViewPlatformController(this._element,
[this._viewId = 1, this._javascriptChannelRegistry]);

/// The IFrame's Window object.
js.JsObject? window;

final JavascriptChannelRegistry? _javascriptChannelRegistry;
final int _viewId;
final IFrameElement _element;
HttpRequestFactory _httpRequestFactory = HttpRequestFactory();

Expand Down Expand Up @@ -103,7 +126,7 @@ class WebWebViewPlatformController implements WebViewPlatformController {

@override
Future<String> evaluateJavascript(String javascript) {
throw UnimplementedError();
return runJavascriptReturningResult(javascript);
}

@override
Expand Down Expand Up @@ -149,12 +172,27 @@ class WebWebViewPlatformController implements WebViewPlatformController {

@override
Future<void> runJavascript(String javascript) {
throw UnimplementedError();
if (window == null) {
throw UnsupportedError(
'Running Javascript is available only by loading the Html as a string',
);
}

return Future<dynamic>.value(
window!.callMethod('eval', <String>[javascript]));
}

@override
Future<String> runJavascriptReturningResult(String javascript) {
throw UnimplementedError();
if (window == null) {
throw UnsupportedError(
'Running Javascript is available only by loading the Html as a string',
);
}

return Future<dynamic>.value(
window?.callMethod('eval', <String>[javascript]))
.then((dynamic value) => value.toString());
}

@override
Expand Down Expand Up @@ -183,7 +221,10 @@ class WebWebViewPlatformController implements WebViewPlatformController {
String? baseUrl,
}) async {
// ignore: unsafe_html
_element.src = 'data:text/html,${Uri.encodeFull(html)}';
_element.srcdoc = preprocessHtml(html);
final Completer<void> loaded = Completer<void>();
_element.addEventListener('load', (Event event) => loaded.complete());
return loaded.future;
}

@override
Expand All @@ -207,6 +248,35 @@ class WebWebViewPlatformController implements WebViewPlatformController {
Future<void> loadFlutterAsset(String key) {
throw UnimplementedError();
}

/// Change the Html before passing it to the iframe.
String preprocessHtml(String html) {
final dom.Document document = parse(html);

final dom.Element scriptElement = document.createElement('script');
final StringBuffer scriptContent = StringBuffer();

scriptContent.writeln('parent.webview${_viewId}_getWindow(window);');

_javascriptChannelRegistry?.channels
.forEach((String _, JavascriptChannel channel) {
final String funcName = 'parent.webview${_viewId}_channel';

scriptContent.writeln(
'window.${channel.name} = { postMessage: (message) => $funcName("${channel.name}", message) };',
);
});

scriptElement.text = scriptContent.toString();
document.head?.insertBefore(scriptElement, document.head!.firstChild);

String outputHtml = document.outerHtml;
if (!outputHtml.trim().startsWith('<!DOCTYPE html>')) {
outputHtml = '<!DOCTYPE html>$outputHtml';
}

return outputHtml;
}
}

/// Factory class for creating [HttpRequest] instances.
Expand Down
3 changes: 2 additions & 1 deletion packages/webview_flutter/webview_flutter_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter_web
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+3
version: 0.1.1

environment:
sdk: ">=2.14.0 <3.0.0"
Expand All @@ -21,6 +21,7 @@ dependencies:
sdk: flutter
flutter_web_plugins:
sdk: flutter
html: ^0.15.0
webview_flutter_platform_interface: ^1.8.0

dev_dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ void main() {
// Run
controller.loadHtmlString('test html');
// Verify
verify(mockElement.src = 'data:text/html,${Uri.encodeFull('test html')}');
verify(mockElement.srcdoc =
'<!DOCTYPE html><html><head><script>parent.webview1_getWindow(window);\n</script></head><body>test html</body></html>');
});

group('loadRequest', () {
Expand Down