Skip to content

Commit

Permalink
[webview_flutter_web] Migrate to package:web. (flutter#6792)
Browse files Browse the repository at this point in the history
* Migrates to `package:web` flutter#139749
* Updates `HttpRequestFactory.request` to use `package:http` `BrowserClient`
* Updates `ìndex.html` in the example to use `flutter_bootstrap.js`
* Updates minimum dart sdk requirement to `^3.3.0`

Would appreciate help with completing the mock tests if in case it does not work.
(I am somehow stuck with 'loading...' when attempting to test with mockito with --platform chrome)

Integration tests from what i was able to test passes.

Migrated to using BrowserClient for web due to issues creating mock tests with `XMLHttpRequest` which is returned from `package:web`'s `HttpRequest.request` following error:

`Bad state: Interface type 'XMLHttpRequest' which is nether an enum, nor a class, nor a mixin. This case is unknown, please report a bug.`

Co-authored-by: David Iglesias <[email protected]>
Co-authored-by: Navaron Bracke <[email protected]>
  • Loading branch information
3 people authored Jul 10, 2024
1 parent 17188b7 commit c47614c
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 3,102 deletions.
7 changes: 5 additions & 2 deletions packages/webview_flutter/webview_flutter_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## NEXT
## 0.2.3

* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
* Migrates to `package:web`
* Updates `HttpRequestFactory.request` to use the Fetch API.
* Updates `index.html` in the example to use `flutter_bootstrap.js`
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.3.

## 0.2.2+4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html>

<head>
<!--
If you are serving your web app in a path other than the root, change the
Expand Down Expand Up @@ -32,73 +33,12 @@
<title>webview_flutter_web Example</title>
<link rel="manifest" href="manifest.json">
</head>

<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}

if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});

// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script src="flutter_bootstrap.js" async></script>
</body>
</html>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:html';
import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:web/web.dart' as web;

/// Factory class for creating [HttpRequest] instances.
class HttpRequestFactory {
Expand All @@ -11,20 +15,16 @@ class HttpRequestFactory {

/// Creates and sends a URL request for the specified [url].
///
/// Returns an `Object` (so this class can be mocked by mockito), which can be
/// cast as [web.Response] from `package:web`.
///
/// 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.
/// [method] parameter.
///
/// If specified, `sendData` will send data in the form of a [ByteBuffer],
/// [Blob], [Document], [String], or [FormData] along with the HttpRequest.
/// The Future is completed when the [web.Response] is available.
///
/// 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.
/// If specified, [sendData] will be sent as the `body` of the fetch.
///
/// The [withCredentials] parameter specified that credentials such as a cookie
/// (already) set in the header or
Expand Down Expand Up @@ -55,27 +55,33 @@ class HttpRequestFactory {
/// // Do something with the response.
/// });
///
/// Note that requests for file:// URIs are only supported by Chrome extensions
/// 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);
Future<Object> request(
String url, {
String method = 'GET',
bool withCredentials = false,
String? mimeType,
Map<String, String>? requestHeaders,
Uint8List? sendData,
}) async {
final Map<String, String> headers = <String, String>{
if (mimeType != null) 'content-type': mimeType,
...?requestHeaders,
};
return web.window
.fetch(
url.toJS,
web.RequestInit(
method: method,
body: sendData?.toJS,
credentials: withCredentials ? 'include' : 'same-origin',
headers: headers.jsify()! as web.HeadersInit,
))
.toDart;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
// found in the LICENSE file.

import 'dart:convert';
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:ui_web' as ui_web;

import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:web/web.dart' as web;
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';

import 'content_type.dart';
Expand Down Expand Up @@ -38,7 +39,7 @@ class WebWebViewControllerCreationParams

/// The underlying element used as the WebView.
@visibleForTesting
final html.IFrameElement iFrame = html.IFrameElement()
final web.HTMLIFrameElement iFrame = web.HTMLIFrameElement()
..id = 'webView${_nextIFrameId++}'
..style.width = '100%'
..style.height = '100%'
Expand Down Expand Up @@ -86,22 +87,21 @@ class WebWebViewController extends PlatformWebViewController {

/// Performs an AJAX request defined by [params].
Future<void> _updateIFrameFromXhr(LoadRequestParams params) async {
final html.HttpRequest httpReq =
final web.Response response =
await _webWebViewParams.httpRequestFactory.request(
params.uri.toString(),
method: params.method.serialize(),
requestHeaders: params.headers,
sendData: params.body,
);
) as web.Response;

final String header =
httpReq.getResponseHeader('content-type') ?? 'text/html';
final String header = response.headers.get('content-type') ?? 'text/html';
final ContentType contentType = ContentType.parse(header);
final Encoding encoding = Encoding.getByName(contentType.charset) ?? utf8;

// ignore: unsafe_html
_webWebViewParams.iFrame.src = Uri.dataFromString(
httpReq.responseText ?? '',
(await response.text().toDart).toDart,
mimeType: contentType.mimeType,
encoding: encoding,
).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'dart:js_interop';
import 'dart:ui_web' as ui_web;

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:web/web.dart' as web;
// ignore: implementation_imports
import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart';

import 'http_request_factory.dart';

/// Builds an iframe based WebView.
Expand All @@ -23,7 +25,7 @@ class WebWebViewPlatform implements WebViewPlatform {
WebWebViewPlatform() {
ui_web.platformViewRegistry.registerViewFactory(
'webview-iframe',
(int viewId) => IFrameElement()
(int viewId) => web.HTMLIFrameElement()
..id = 'webview-$viewId'
..width = '100%'
..height = '100%'
Expand All @@ -45,11 +47,13 @@ class WebWebViewPlatform implements WebViewPlatform {
if (onWebViewPlatformCreated == null) {
return;
}
final IFrameElement element =
document.getElementById('webview-$viewId')! as IFrameElement;
if (creationParams.initialUrl != null) {
final web.HTMLIFrameElement element = web.document
.getElementById('webview-$viewId')! as web.HTMLIFrameElement;

final String? initialUrl = creationParams.initialUrl;
if (initialUrl != null) {
// ignore: unsafe_html
element.src = creationParams.initialUrl;
element.src = initialUrl;
}
onWebViewPlatformCreated(WebWebViewPlatformController(
element,
Expand All @@ -70,7 +74,7 @@ class WebWebViewPlatformController implements WebViewPlatformController {
/// Constructs a [WebWebViewPlatformController].
WebWebViewPlatformController(this._element);

final IFrameElement _element;
final web.HTMLIFrameElement _element;
HttpRequestFactory _httpRequestFactory = const HttpRequestFactory();

/// Setter for setting the HttpRequestFactory, for testing purposes.
Expand Down Expand Up @@ -199,16 +203,17 @@ class WebWebViewPlatformController implements WebViewPlatformController {
if (!request.uri.hasScheme) {
throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
}
final HttpRequest httpReq = await _httpRequestFactory.request(
final web.Response response = await _httpRequestFactory.request(
request.uri.toString(),
method: request.method.serialize(),
requestHeaders: request.headers,
sendData: request.body);
sendData: request.body) as web.Response;

final String contentType =
httpReq.getResponseHeader('content-type') ?? 'text/html';
// ignore: unsafe_html
response.headers.get('content-type') ?? 'text/html';

_element.src = Uri.dataFromString(
httpReq.responseText ?? '',
(await response.text().toDart).toDart,
mimeType: contentType,
encoding: utf8,
).toString();
Expand Down
7 changes: 4 additions & 3 deletions packages/webview_flutter/webview_flutter_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ 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.2+4
version: 0.2.3

environment:
sdk: ^3.2.0
flutter: ">=3.16.0"
sdk: ^3.3.0
flutter: ">=3.19.0"

flutter:
plugin:
Expand All @@ -21,6 +21,7 @@ dependencies:
sdk: flutter
flutter_web_plugins:
sdk: flutter
web: ^0.5.0
webview_flutter_platform_interface: ^2.0.0

dev_dependencies:
Expand Down
Loading

0 comments on commit c47614c

Please sign in to comment.