This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[webview_flutter_wkwebview] Implementation of PlatformNavigationDelegate for WebKit #6151
Merged
auto-submit
merged 24 commits into
flutter:main
from
bparrishMines:wkwebview_v4_navigation
Aug 12, 2022
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
7957040
implement controller
bparrishMines f3c3486
use a proxy
bparrishMines 29340b3
first solid test
bparrishMines ffeda10
some api improvements and tests
bparrishMines 9fa6d0b
convenience constructors
bparrishMines 2a150de
a few tests left
bparrishMines b6431cf
rest of tests and licenses
bparrishMines dcdb8dc
add test for callback method
bparrishMines e5f6f6a
webkit implementation of navigation delegate
bparrishMines 656ef82
tests
bparrishMines c3f3029
improve proxy and change background color call order
bparrishMines 3d016ae
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4
bparrishMines d3982d0
Merge branch 'wkwebview_v4' into wkwebview_v4_navigation
bparrishMines c3f6cfa
set navigation delegate
bparrishMines 26a329b
setnavigationdelegatetest
bparrishMines d463962
test improvements and test attempt
bparrishMines 2c66ce5
call observe value
bparrishMines ee96f37
use creator in method
bparrishMines f134aa3
finish tests
bparrishMines e68447b
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines 41af992
more documentation
bparrishMines 45704c2
shorten bool line
bparrishMines d74c7f9
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines 43f3193
Merge branch 'main' of github.com:flutter/plugins into wkwebview_v4_n…
bparrishMines File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
161 changes: 161 additions & 0 deletions
161
.../webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
// `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; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 anUnimplementedError
by default. By using setters for callback methods, the default response for a callback method is to throw anUnimplementedError
. 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:
But the platform classes will use setters so they can throw when a callback is not supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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 anUnimplementedError
by default.