blob: 0f848ecec4d487db86baa2ab5c8bf01b8a7da911 [file] [log] [blame]
// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'webview_flutter.dart';
/// Interface for callbacks made by [WebViewPlatformController].
///
/// The webview plugin implements this class, and passes an instance to the [WebViewPlatformController].
/// [WebViewPlatformController] is notifying this handler on events that happened on the platform's webview.
abstract class WebViewPlatformCallbacksHandler {
/// Invoked by [WebViewPlatformController] when a JavaScript channel message is received.
void onJavaScriptChannelMessage(String channel, String message);
/// Invoked by [WebViewPlatformController] when a navigation request is pending.
///
/// If true is returned the navigation is allowed, otherwise it is blocked.
FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame});
/// Invoked by [WebViewPlatformController] when a page has started loading.
void onPageStarted(String url);
/// Invoked by [WebViewPlatformController] when a page has finished loading.
void onPageFinished(String url);
/// Report web resource loading error to the host application.
void onWebResourceError(WebResourceError error);
}
/// Possible error type categorizations used by [WebResourceError].
enum WebResourceErrorType {
/// User authentication failed on server.
authentication,
/// Malformed URL.
badUrl,
/// Failed to connect to the server.
connect,
/// Failed to perform SSL handshake.
failedSslHandshake,
/// Generic file error.
file,
/// File not found.
fileNotFound,
/// Server or proxy hostname lookup failed.
hostLookup,
/// Failed to read or write to the server.
io,
/// User authentication failed on proxy.
proxyAuthentication,
/// Too many redirects.
redirectLoop,
/// Connection timed out.
timeout,
/// Too many requests during this load.
tooManyRequests,
/// Generic error.
unknown,
/// Resource load was canceled by Safe Browsing.
unsafeResource,
/// Unsupported authentication scheme (not basic or digest).
unsupportedAuthScheme,
/// Unsupported URI scheme.
unsupportedScheme,
/// The web content process was terminated.
webContentProcessTerminated,
/// The web view was invalidated.
webViewInvalidated,
/// A JavaScript exception occurred.
javaScriptExceptionOccurred,
/// The result of JavaScript execution could not be returned.
javaScriptResultTypeIsUnsupported,
}
/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred.
class WebResourceError {
/// Creates a new [WebResourceError]
///
/// A user should not need to instantiate this class, but will receive one in
/// [WebResourceErrorCallback].
WebResourceError({
@required this.errorCode,
@required this.description,
this.domain,
this.errorType,
this.failingUrl,
}) : assert(errorCode != null),
assert(description != null);
/// Raw code of the error from the respective platform.
///
/// On Android, the error code will be a constant from a
/// [WebViewClient](https://developer.android.com/reference/android/webkit/WebViewClient#summary) and
/// will have a corresponding [errorType].
///
/// On iOS, the error code will be a constant from `NSError.code` in
/// Objective-C. See
/// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html
/// for more information on error handling on iOS. Some possible error codes
/// can be found at https://developer.apple.com/documentation/webkit/wkerrorcode?language=objc.
final int errorCode;
/// The domain of where to find the error code.
///
/// This field is only available on iOS and represents a "domain" from where
/// the [errorCode] is from. This value is taken directly from an `NSError`
/// in Objective-C. See
/// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html
/// for more information on error handling on iOS.
final String domain;
/// Description of the error that can be used to communicate the problem to the user.
final String description;
/// The type this error can be categorized as.
///
/// This will never be `null` on Android, but can be `null` on iOS.
final WebResourceErrorType errorType;
/// Gets the URL for which the resource request was made.
///
/// This value is not provided on iOS. Alternatively, you can keep track of
/// the last values provided to [WebViewPlatformController.loadUrl].
final String failingUrl;
}
/// Interface for talking to the webview's platform implementation.
///
/// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is
/// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated].
///
/// Platform implementations that live in a separate package should extend this class rather than
/// implement it as webview_flutter does not consider newly added methods to be breaking changes.
/// Extending this class (using `extends`) ensures that the subclass will get the default
/// implementation, while platform implementations that `implements` this interface will be broken
/// by newly added [WebViewPlatformController] methods.
abstract class WebViewPlatformController {
/// Creates a new WebViewPlatform.
///
/// Callbacks made by the WebView will be delegated to `handler`.
///
/// The `handler` parameter must not be null.
WebViewPlatformController(WebViewPlatformCallbacksHandler handler);
/// Loads the specified URL.
///
/// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will
/// be added as key value pairs of HTTP headers for the request.
///
/// `url` must not be null.
///
/// Throws an ArgumentError if `url` is not a valid URL string.
Future<void> loadUrl(
String url,
Map<String, String> headers,
) {
throw UnimplementedError(
"WebView loadUrl is not implemented on the current platform");
}
/// Updates the webview settings.
///
/// Any non null field in `settings` will be set as the new setting value.
/// All null fields in `settings` are ignored.
Future<void> updateSettings(WebSettings setting) {
throw UnimplementedError(
"WebView updateSettings is not implemented on the current platform");
}
/// Accessor to the current URL that the WebView is displaying.
///
/// If no URL was ever loaded, returns `null`.
Future<String> currentUrl() {
throw UnimplementedError(
"WebView currentUrl is not implemented on the current platform");
}
/// Checks whether there's a back history item.
Future<bool> canGoBack() {
throw UnimplementedError(
"WebView canGoBack is not implemented on the current platform");
}
/// Checks whether there's a forward history item.
Future<bool> canGoForward() {
throw UnimplementedError(
"WebView canGoForward is not implemented on the current platform");
}
/// Goes back in the history of this WebView.
///
/// If there is no back history item this is a no-op.
Future<void> goBack() {
throw UnimplementedError(
"WebView goBack is not implemented on the current platform");
}
/// Goes forward in the history of this WebView.
///
/// If there is no forward history item this is a no-op.
Future<void> goForward() {
throw UnimplementedError(
"WebView goForward is not implemented on the current platform");
}
/// Reloads the current URL.
Future<void> reload() {
throw UnimplementedError(
"WebView reload is not implemented on the current platform");
}
/// Clears all caches used by the [WebView].
///
/// The following caches are cleared:
/// 1. Browser HTTP Cache.
/// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.
/// These are not yet supported in iOS WkWebView. Service workers tend to use this cache.
/// 3. Application cache.
/// 4. Local Storage.
Future<void> clearCache() {
throw UnimplementedError(
"WebView clearCache is not implemented on the current platform");
}
/// Evaluates a JavaScript expression in the context of the current page.
///
/// The Future completes with an error if a JavaScript error occurred, or if the type of the
/// evaluated expression is not supported(e.g on iOS not all non primitive type can be evaluated).
Future<String> evaluateJavascript(String javascriptString) {
throw UnimplementedError(
"WebView evaluateJavascript is not implemented on the current platform");
}
/// Adds new JavaScript channels to the set of enabled channels.
///
/// For each value in this list the platform's webview should make sure that a corresponding
/// property with a postMessage method is set on `window`. For example for a JavaScript channel
/// named `Foo` it should be possible for JavaScript code executing in the webview to do
///
/// ```javascript
/// Foo.postMessage('hello');
/// ```
///
/// See also: [CreationParams.javascriptChannelNames].
Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
throw UnimplementedError(
"WebView addJavascriptChannels is not implemented on the current platform");
}
/// Removes JavaScript channel names from the set of enabled channels.
///
/// This disables channels that were previously enabled by [addJavaScriptChannels] or through
/// [CreationParams.javascriptChannelNames].
Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
throw UnimplementedError(
"WebView removeJavascriptChannels is not implemented on the current platform");
}
/// Returns the title of the currently loaded page.
Future<String> getTitle() {
throw UnimplementedError(
"WebView getTitle is not implemented on the current platform");
}
/// Set the scrolled position of this view.
///
/// The parameters `x` and `y` specify the position to scroll to in WebView pixels.
Future<void> scrollTo(int x, int y) {
throw UnimplementedError(
"WebView scrollTo is not implemented on the current platform");
}
/// Move the scrolled position of this view.
///
/// The parameters `x` and `y` specify the amount of WebView pixels to scroll by.
Future<void> scrollBy(int x, int y) {
throw UnimplementedError(
"WebView scrollBy is not implemented on the current platform");
}
/// Return the horizontal scroll position of this view.
///
/// Scroll position is measured from left.
Future<int> getScrollX() {
throw UnimplementedError(
"WebView getScrollX is not implemented on the current platform");
}
/// Return the vertical scroll position of this view.
///
/// Scroll position is measured from top.
Future<int> getScrollY() {
throw UnimplementedError(
"WebView getScrollY is not implemented on the current platform");
}
}
/// A single setting for configuring a WebViewPlatform which may be absent.
class WebSetting<T> {
/// Constructs an absent setting instance.
///
/// The [isPresent] field for the instance will be false.
///
/// Accessing [value] for an absent instance will throw.
WebSetting.absent()
: _value = null,
isPresent = false;
/// Constructs a setting of the given `value`.
///
/// The [isPresent] field for the instance will be true.
WebSetting.of(T value)
: _value = value,
isPresent = true;
final T _value;
/// The setting's value.
///
/// Throws if [WebSetting.isPresent] is false.
T get value {
if (!isPresent) {
throw StateError('Cannot access a value of an absent WebSetting');
}
assert(isPresent);
return _value;
}
/// True when this web setting instance contains a value.
///
/// When false the [WebSetting.value] getter throws.
final bool isPresent;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
final WebSetting<T> typedOther = other;
return typedOther.isPresent == isPresent && typedOther._value == _value;
}
@override
int get hashCode => hashValues(_value, isPresent);
}
/// Settings for configuring a WebViewPlatform.
///
/// Initial settings are passed as part of [CreationParams], settings updates are sent with
/// [WebViewPlatform#updateSettings].
///
/// The `userAgent` parameter must not be null.
class WebSettings {
/// Construct an instance with initial settings. Future setting changes can be
/// sent with [WebviewPlatform#updateSettings].
///
/// The `userAgent` parameter must not be null.
WebSettings({
this.javascriptMode,
this.hasNavigationDelegate,
this.debuggingEnabled,
this.gestureNavigationEnabled,
@required this.userAgent,
}) : assert(userAgent != null);
/// The JavaScript execution mode to be used by the webview.
final JavascriptMode javascriptMode;
/// Whether the [WebView] has a [NavigationDelegate] set.
final bool hasNavigationDelegate;
/// Whether to enable the platform's webview content debugging tools.
///
/// See also: [WebView.debuggingEnabled].
final bool debuggingEnabled;
/// The value used for the HTTP `User-Agent:` request header.
///
/// If [userAgent.value] is null the platform's default user agent should be used.
///
/// An absent value ([userAgent.isPresent] is false) represents no change to this setting from the
/// last time it was set.
///
/// See also [WebView.userAgent].
final WebSetting<String> userAgent;
/// Whether to allow swipe based navigation in iOS.
///
/// See also: [WebView.gestureNavigationEnabled]
final bool gestureNavigationEnabled;
@override
String toString() {
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)';
}
}
/// Configuration to use when creating a new [WebViewPlatformController].
///
/// The `autoMediaPlaybackPolicy` parameter must not be null.
class CreationParams {
/// Constructs an instance to use when creating a new
/// [WebViewPlatformController].
///
/// The `autoMediaPlaybackPolicy` parameter must not be null.
CreationParams({
this.initialUrl,
this.webSettings,
this.javascriptChannelNames,
this.userAgent,
this.autoMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
}) : assert(autoMediaPlaybackPolicy != null);
/// The initialUrl to load in the webview.
///
/// When null the webview will be created without loading any page.
final String initialUrl;
/// The initial [WebSettings] for the new webview.
///
/// This can later be updated with [WebViewPlatformController.updateSettings].
final WebSettings webSettings;
/// The initial set of JavaScript channels that are configured for this webview.
///
/// For each value in this set the platform's webview should make sure that a corresponding
/// property with a postMessage method is set on `window`. For example for a JavaScript channel
/// named `Foo` it should be possible for JavaScript code executing in the webview to do
///
/// ```javascript
/// Foo.postMessage('hello');
/// ```
// TODO(amirh): describe what should happen when postMessage is called once that code is migrated
// to PlatformWebView.
final Set<String> javascriptChannelNames;
/// The value used for the HTTP User-Agent: request header.
///
/// When null the platform's webview default is used for the User-Agent header.
final String userAgent;
/// Which restrictions apply on automatic media playback.
final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy;
@override
String toString() {
return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)';
}
}
typedef WebViewPlatformCreatedCallback = void Function(
WebViewPlatformController webViewPlatformController);
/// Interface for a platform implementation of a WebView.
///
/// [WebView.platform] controls the builder that is used by [WebView].
/// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations
/// for Android and iOS respectively.
abstract class WebViewPlatform {
/// Builds a new WebView.
///
/// Returns a Widget tree that embeds the created webview.
///
/// `creationParams` are the initial parameters used to setup the webview.
///
/// `webViewPlatformHandler` will be used for handling callbacks that are made by the created
/// [WebViewPlatformController].
///
/// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatformController]
/// implementation is created with the [WebViewPlatformController] instance as a parameter.
///
/// `gestureRecognizers` specifies which gestures should be consumed by the web view.
/// It is possible for other gesture recognizers to be competing with the web view on pointer
/// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
/// vertical drags. The web view will claim gestures that are recognized by any of the
/// recognizers on this list.
/// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that
/// were not claimed by any other gesture recognizer.
///
/// `webViewPlatformHandler` must not be null.
Widget build({
BuildContext context,
// TODO(amirh): convert this to be the actual parameters.
// I'm starting without it as the PR is starting to become pretty big.
// I'll followup with the conversion PR.
CreationParams creationParams,
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
});
/// Clears all cookies for all [WebView] instances.
///
/// Returns true if cookies were present before clearing, else false.
Future<bool> clearCookies() {
throw UnimplementedError(
"WebView clearCookies is not implemented on the current platform");
}
}