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

[webview_flutter_wkwebview] Implementation of PlatformNavigationDelegate for WebKit #6151

Merged
merged 24 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7957040
implement controller
bparrishMines Jul 13, 2022
f3c3486
use a proxy
bparrishMines Jul 13, 2022
29340b3
first solid test
bparrishMines Jul 13, 2022
ffeda10
some api improvements and tests
bparrishMines Jul 13, 2022
9fa6d0b
convenience constructors
bparrishMines Jul 13, 2022
2a150de
a few tests left
bparrishMines Jul 13, 2022
b6431cf
rest of tests and licenses
bparrishMines Jul 13, 2022
dcdb8dc
add test for callback method
bparrishMines Jul 14, 2022
e5f6f6a
webkit implementation of navigation delegate
bparrishMines Jul 20, 2022
656ef82
tests
bparrishMines Jul 20, 2022
c3f3029
improve proxy and change background color call order
bparrishMines Jul 26, 2022
3d016ae
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4
bparrishMines Jul 26, 2022
d3982d0
Merge branch 'wkwebview_v4' into wkwebview_v4_navigation
bparrishMines Jul 26, 2022
c3f6cfa
set navigation delegate
bparrishMines Jul 26, 2022
26a329b
setnavigationdelegatetest
bparrishMines Jul 26, 2022
d463962
test improvements and test attempt
bparrishMines Jul 26, 2022
2c66ce5
call observe value
bparrishMines Jul 27, 2022
ee96f37
use creator in method
bparrishMines Jul 27, 2022
f134aa3
finish tests
bparrishMines Jul 27, 2022
e68447b
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines Jul 27, 2022
41af992
more documentation
bparrishMines Jul 27, 2022
45704c2
shorten bool line
bparrishMines Aug 11, 2022
d74c7f9
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines Aug 11, 2022
43f3193
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines Aug 12, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';

import '../../foundation/foundation.dart';
import '../../web_kit/web_kit.dart';
import 'webkit_proxy.dart';

/// An implementation of [WebResourceError] with the WebKit API.
class WebKitWebResourceError extends WebResourceError {
WebKitWebResourceError._(this._nsError)
: super(
errorCode: _nsError.code,
description: _nsError.localizedDescription,
errorType: _toWebResourceErrorType(_nsError.code),
);

static WebResourceErrorType? _toWebResourceErrorType(int code) {
switch (code) {
case WKErrorCode.unknown:
return WebResourceErrorType.unknown;
case WKErrorCode.webContentProcessTerminated:
return WebResourceErrorType.webContentProcessTerminated;
case WKErrorCode.webViewInvalidated:
return WebResourceErrorType.webViewInvalidated;
case WKErrorCode.javaScriptExceptionOccurred:
return WebResourceErrorType.javaScriptExceptionOccurred;
case WKErrorCode.javaScriptResultTypeIsUnsupported:
return WebResourceErrorType.javaScriptResultTypeIsUnsupported;
}

return null;
}

/// A string representing the domain of the error.
String? get domain => _nsError.domain;

final NSError _nsError;
}

/// An implementation of [PlatformNavigationDelegate] with the WebKit API.
class WebKitNavigationDelegate extends PlatformNavigationDelegate {
/// Constructs a [WebKitNavigationDelegate].
WebKitNavigationDelegate(
super.params, {
@visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(),
}) : super.implementation() {
final WeakReference<WebKitNavigationDelegate> weakThis =
WeakReference<WebKitNavigationDelegate>(this);
navigationDelegate = webKitProxy.createNavigationDelegate(
didFinishNavigation: (WKWebView webView, String? url) {
if (weakThis.target?._onPageFinished != null) {
weakThis.target!._onPageFinished!(url ?? '');
}
},
didStartProvisionalNavigation: (WKWebView webView, String? url) {
if (weakThis.target?._onPageStarted != null) {
weakThis.target!._onPageStarted!(url ?? '');
}
},
decidePolicyForNavigationAction: (
WKWebView webView,
WKNavigationAction action,
) async {
if (weakThis.target?._onNavigationRequest != null) {
final bool allow = await weakThis.target!._onNavigationRequest!(
url: action.request.url,
isForMainFrame: action.targetFrame.isMainFrame,
);
return allow
? WKNavigationActionPolicy.allow
: WKNavigationActionPolicy.cancel;
}
return WKNavigationActionPolicy.allow;
},
didFailNavigation: (WKWebView webView, NSError error) {
if (weakThis.target?._onWebResourceError != null) {
weakThis.target!._onWebResourceError!(
WebKitWebResourceError._(error),
);
}
},
didFailProvisionalNavigation: (WKWebView webView, NSError error) {
if (weakThis.target?._onWebResourceError != null) {
weakThis.target!._onWebResourceError!(
WebKitWebResourceError._(error),
);
}
},
webViewWebContentProcessDidTerminate: (WKWebView webView) {
if (weakThis.target?._onWebResourceError != null) {
weakThis.target!._onWebResourceError!(
WebKitWebResourceError._(
const NSError(
code: WKErrorCode.webContentProcessTerminated,
// Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc.
domain: 'WKErrorDomain',
localizedDescription: '',
),
),
);
}
},
);
}

// Used to set `WKWebView.setNavigationDelegate` in `WebKitWebViewController`.
/// WebKit class that handles navigation changes and tracking navigation
/// requests.
late final WKNavigationDelegate navigationDelegate;

void Function(String url)? _onPageFinished;
void Function(String url)? _onPageStarted;
void Function(int progress)? _onProgress;
void Function(WebResourceError error)? _onWebResourceError;
FutureOr<bool> Function({required String url, required bool isForMainFrame})?
_onNavigationRequest;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In defense of this pattern:

I wanted setters for the callbacks methods because I wanted a consistent way for a platform to indicate it doesn't support a feature.

For example, if Android doesn't support WebViewController.callSomeMethod(), it throws an UnimplementedError by default. By using setters for callback methods, the default response for a callback method is to throw an UnimplementedError. I didn't know a way to do this with a class that takes the callback methods as constructor parameters.

Also note that the app facing package will probably use fields:

class NavigationDelegate {
  final Function(String) onPageFinished;
}

But the platform classes will use setters so they can throw when a callback is not supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using setters for callback methods, the default response for a callback method is to throw an UnimplementedError.

I think I'm missing something about the implementation; where does that happen? It looks in the code above this like the default would be a silent no-op for a null method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done in the platform interface class: https://github.com/flutter/plugins/blob/main/packages/webview_flutter/webview_flutter_platform_interface/lib/v4/src/platform_navigation_delegate.dart#L63

PlatformNavigationDelegate has to be extended, so when a new callback method is added (e.g. setOnPageRedirected) platform implementations would throw an UnimplementedError by default.


// `WKWebView` in `WebKitWebViewController` uses this to track loading
// progress. This can't be done with `WKNavigationDelegate`.
/// Callback method that receives progress of a loading page.
void Function(int progress)? get onProgress => _onProgress;

@override
Future<void> setOnPageFinished(
void Function(String url) onPageFinished,
) async {
_onPageFinished = onPageFinished;
}

@override
Future<void> setOnPageStarted(void Function(String url) onPageStarted) async {
_onPageStarted = onPageStarted;
}

@override
Future<void> setOnProgress(void Function(int progress) onProgress) async {
_onProgress = onProgress;
}

@override
Future<void> setOnWebResourceError(
void Function(WebResourceError error) onWebResourceError,
) async {
_onWebResourceError = onWebResourceError;
}

@override
Future<void> setOnNavigationRequest(
FutureOr<bool> Function({required String url, required bool isForMainFrame})
onNavigationRequest,
) async {
_onNavigationRequest = onNavigationRequest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class WebKitProxy {
this.createWebView = WKWebView.new,
this.createWebViewConfiguration = WKWebViewConfiguration.new,
this.createScriptMessageHandler = WKScriptMessageHandler.new,
this.createNavigationDelegate = WKNavigationDelegate.new,
});

/// Constructs a [WKWebView].
Expand All @@ -44,4 +45,20 @@ class WebKitProxy {
)
didReceiveScriptMessage,
}) createScriptMessageHandler;

/// Constructs a [WKNavigationDelegate].
final WKNavigationDelegate Function({
void Function(WKWebView webView, String? url)? didFinishNavigation,
void Function(WKWebView webView, String? url)?
didStartProvisionalNavigation,
Future<WKNavigationActionPolicy> Function(
WKWebView webView,
WKNavigationAction navigationAction,
)?
decidePolicyForNavigationAction,
void Function(WKWebView webView, NSError error)? didFailNavigation,
void Function(WKWebView webView, NSError error)?
didFailProvisionalNavigation,
void Function(WKWebView webView)? webViewWebContentProcessDidTerminate,
}) createNavigationDelegate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_i
import '../../common/weak_reference_utils.dart';
import '../../foundation/foundation.dart';
import '../../web_kit/web_kit.dart';
import 'webkit_navigation_delegate.dart';
import 'webkit_proxy.dart';

/// Object specifying creation parameters for a [WebKitWebViewController].
Expand Down Expand Up @@ -46,8 +47,29 @@ class WebKitWebViewController extends PlatformWebViewController {
? params
: WebKitWebViewControllerCreationParams
.fromPlatformWebViewControllerCreationParams(params)) {
final WeakReference<WebKitWebViewController> weakThis =
WeakReference<WebKitWebViewController>(this);
_webView = webKitProxy.createWebView(
(params as WebKitWebViewControllerCreationParams)._configuration);
(params as WebKitWebViewControllerCreationParams)._configuration,
observeValue: (
String keyPath,
NSObject object,
Map<NSKeyValueChangeKey, Object?> change,
) {
if (weakThis.target?._onProgress != null) {
final double progress =
change[NSKeyValueChangeKey.newValue]! as double;
weakThis.target!._onProgress!((progress * 100).round());
}
},
);
_webView.addObserver(
_webView,
keyPath: 'estimatedProgress',
options: <NSKeyValueObservingOptions>{
NSKeyValueObservingOptions.newValue,
},
);
}

late final WKWebView _webView;
Expand All @@ -56,6 +78,7 @@ class WebKitWebViewController extends PlatformWebViewController {
<String, WebKitJavaScriptChannelParams>{};

bool _zoomEnabled = true;
void Function(int progress)? _onProgress;

@override
Future<void> loadFile(String absoluteFilePath) {
Expand Down Expand Up @@ -270,6 +293,14 @@ class WebKitWebViewController extends PlatformWebViewController {
}
}

@override
Future<void> setPlatformNavigationDelegate(
covariant WebKitNavigationDelegate handler,
) {
_onProgress = handler.onProgress;
return _webView.setNavigationDelegate(handler.navigationDelegate);
}

Future<void> _disableZoom() {
const WKUserScript userScript = WKUserScript(
"var meta = document.createElement('meta');\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@

import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';

import 'webkit_navigation_delegate.dart';
import 'webkit_webview_controller.dart';

/// Implementation of [WebViewPlatform] using the WebKit Api.
/// Implementation of [WebViewPlatform] using the WebKit API.
class WebKitWebViewPlatform extends WebViewPlatform {
@override
WebKitWebViewController createPlatformWebViewController(
PlatformWebViewControllerCreationParams params,
) {
return WebKitWebViewController(params);
}

@override
WebKitNavigationDelegate createPlatformNavigationDelegate(
PlatformNavigationDelegateCreationParams params,
) {
return WebKitNavigationDelegate(params);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

library webview_flutter_wkwebview;

export 'src/webkit_navigation_delegate.dart';
export 'src/webkit_webview_controller.dart';
export 'src/webkit_webview_controller.dart';
Loading