Skip to content

Commit e3ebc88

Browse files
[webview_flutter_wkwebview] Add support for WKUIDelegate (flutter#4809)
1 parent aa7a474 commit e3ebc88

File tree

5 files changed

+207
-34
lines changed

5 files changed

+207
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:typed_data';
6+
7+
import 'package:flutter/foundation.dart';
8+
9+
/// A URL load request that is independent of protocol or URL scheme.
10+
///
11+
/// Wraps [NSUrlRequest](https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc).
12+
@immutable
13+
class NSUrlRequest {
14+
/// Constructs an [NSUrlRequest].
15+
const NSUrlRequest({
16+
required this.url,
17+
this.httpMethod,
18+
this.httpBody,
19+
this.allHttpHeaderFields = const <String, String>{},
20+
});
21+
22+
/// The URL being requested.
23+
final String url;
24+
25+
/// The HTTP request method.
26+
///
27+
/// The default HTTP method is “GET”.
28+
final String? httpMethod;
29+
30+
/// Data sent as the message body of a request, as in an HTTP POST request.
31+
final Uint8List? httpBody;
32+
33+
/// All of the HTTP header fields for a request.
34+
final Map<String, String> allHttpHeaderFields;
35+
}

Diff for: packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart

+69-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
6+
7+
import '../foundation/foundation.dart';
8+
59
/// Times at which to inject script content into a webpage.
610
///
711
/// Wraps [WKUserScriptInjectionTime](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc).
@@ -42,12 +46,43 @@ enum WKAudiovisualMediaType {
4246
all,
4347
}
4448

49+
/// An object that contains information about an action that causes navigation to occur.
50+
///
51+
/// Wraps [WKNavigationAction](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc).
52+
@immutable
53+
class WKNavigationAction {
54+
/// Constructs a [WKNavigationAction].
55+
const WKNavigationAction({required this.request, required this.targetFrame});
56+
57+
/// The URL request object associated with the navigation action.
58+
final NSUrlRequest request;
59+
60+
/// The frame in which to display the new content.
61+
final WKFrameInfo targetFrame;
62+
}
63+
64+
/// An object that contains information about a frame on a webpage.
65+
///
66+
/// An instance of this class is a transient, data-only object; it does not
67+
/// uniquely identify a frame across multiple delegate method calls.
68+
///
69+
/// Wraps [WKFrameInfo](https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc).
70+
@immutable
71+
class WKFrameInfo {
72+
/// Construct a [WKFrameInfo].
73+
const WKFrameInfo({required this.isMainFrame});
74+
75+
/// Indicates whether the frame is the web site's main frame or a subframe.
76+
final bool isMainFrame;
77+
}
78+
4579
/// A script that the web view injects into a webpage.
4680
///
4781
/// Wraps [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript?language=objc).
82+
@immutable
4883
class WKUserScript {
4984
/// Constructs a [UserScript].
50-
WKUserScript(
85+
const WKUserScript(
5186
this.source,
5287
this.injectionTime, {
5388
required this.isMainFrameOnly,
@@ -66,9 +101,10 @@ class WKUserScript {
66101
/// An object that encapsulates a message sent by JavaScript code from a webpage.
67102
///
68103
/// Wraps [WKScriptMessage](https://developer.apple.com/documentation/webkit/wkscriptmessage?language=objc).
104+
@immutable
69105
class WKScriptMessage {
70106
/// Constructs a [WKScriptMessage].
71-
WKScriptMessage({required this.name, this.body});
107+
const WKScriptMessage({required this.name, this.body});
72108

73109
/// The name of the message handler to which the message is sent.
74110
final String name;
@@ -88,7 +124,7 @@ class WKScriptMessageHandler {
88124
/// Use this method to respond to a message sent from the webpage’s
89125
/// JavaScript code. Use the [message] parameter to get the message contents and
90126
/// to determine the originating web view.
91-
Future<void> setDidReceiveScriptMessage(
127+
set didReceiveScriptMessage(
92128
void Function(
93129
WKUserContentController userContentController,
94130
WKScriptMessage message,
@@ -169,7 +205,7 @@ class WKUserContentController {
169205

170206
/// A collection of properties that you use to initialize a web view.
171207
///
172-
/// Wraps [WKWebViewConfiguration](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc)
208+
/// Wraps [WKWebViewConfiguration](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc).
173209
class WKWebViewConfiguration {
174210
/// Constructs a [WKWebViewConfiguration].
175211
WKWebViewConfiguration({required this.userContentController});
@@ -202,6 +238,22 @@ class WKWebViewConfiguration {
202238
}
203239
}
204240

241+
/// The methods for presenting native user interface elements on behalf of a webpage.
242+
///
243+
/// Wraps [WKUIDelegate](https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc).
244+
class WKUIDelegate {
245+
/// Indicates a new [WebView] was requested to be created with [configuration].
246+
set onCreateWebView(
247+
void Function(
248+
WKWebViewConfiguration configuration,
249+
WKNavigationAction navigationAction,
250+
)
251+
onCreateeWebView,
252+
) {
253+
throw UnimplementedError();
254+
}
255+
}
256+
205257
/// Object that displays interactive web content, such as for an in-app browser.
206258
///
207259
/// Wraps [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview?language=objc).
@@ -232,4 +284,17 @@ class WKWebView {
232284
/// property contains a default configuration object.
233285
late final WKWebViewConfiguration configuration =
234286
WKWebViewConfiguration._fromWebView(this);
287+
288+
/// Used to integrate custom user interface elements into web view interactions.
289+
set uiDelegate(WKUIDelegate delegate) {
290+
throw UnimplementedError();
291+
}
292+
293+
/// Loads the web content referenced by the specified URL request object and navigates to it.
294+
///
295+
/// Use this method to load a page from a local or network-based URL. For
296+
/// example, you might use it to navigate to a network-based webpage.
297+
Future<void> loadRequest(NSUrlRequest request) {
298+
throw UnimplementedError();
299+
}
235300
}

Diff for: packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit_webview_widget.dart

+28-11
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
8787
userContentController: WKUserContentController(),
8888
),
8989
);
90+
91+
webView.uiDelegate = uiDelegate;
92+
uiDelegate.onCreateWebView = (
93+
WKWebViewConfiguration configuration,
94+
WKNavigationAction navigationAction,
95+
) {
96+
if (!navigationAction.targetFrame.isMainFrame) {
97+
webView.loadRequest(navigationAction.request);
98+
}
99+
};
90100
}
91101

92102
final Map<String, WKScriptMessageHandler> _scriptMessageHandlers =
@@ -106,6 +116,10 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
106116
/// Represents the WebView maintained by platform code.
107117
late final WKWebView webView;
108118

119+
/// Used to integrate custom user interface elements into web view interactions.
120+
@visibleForTesting
121+
late final WKUIDelegate uiDelegate = webViewProxy.createUIDelgate();
122+
109123
Future<void> _setCreationParams(
110124
CreationParams params, {
111125
required WKWebViewConfiguration configuration,
@@ -158,17 +172,15 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
158172
(String channelName) {
159173
final WKScriptMessageHandler handler =
160174
webViewProxy.createScriptMessageHandler()
161-
..setDidReceiveScriptMessage(
162-
(
163-
WKUserContentController userContentController,
164-
WKScriptMessage message,
165-
) {
166-
javascriptChannelRegistry.onJavascriptChannelMessage(
167-
message.name,
168-
message.body!.toString(),
169-
);
170-
},
171-
);
175+
..didReceiveScriptMessage = (
176+
WKUserContentController userContentController,
177+
WKScriptMessage message,
178+
) {
179+
javascriptChannelRegistry.onJavascriptChannelMessage(
180+
message.name,
181+
message.body!.toString(),
182+
);
183+
};
172184
_scriptMessageHandlers[channelName] = handler;
173185

174186
final String wrapperSource =
@@ -231,4 +243,9 @@ class WebViewWidgetProxy {
231243
WKScriptMessageHandler createScriptMessageHandler() {
232244
return WKScriptMessageHandler();
233245
}
246+
247+
/// Constructs a [WKUIDelegate].
248+
WKUIDelegate createUIDelgate() {
249+
return WKUIDelegate();
250+
}
234251
}

Diff for: packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart

+27-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
99
import 'package:mockito/annotations.dart';
1010
import 'package:mockito/mockito.dart';
1111
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
12+
import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart';
1213
import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';
1314
import 'package:webview_flutter_wkwebview/src/web_kit_webview_widget.dart';
1415

@@ -18,6 +19,7 @@ import 'web_kit_webview_widget_test.mocks.dart';
1819
WKScriptMessageHandler,
1920
WKWebView,
2021
WKWebViewConfiguration,
22+
WKUIDelegate,
2123
WKUserContentController,
2224
JavascriptChannelRegistry,
2325
WebViewPlatformCallbacksHandler,
@@ -31,6 +33,7 @@ void main() {
3133
late MockWebViewWidgetProxy mockWebViewWidgetProxy;
3234
late MockWKUserContentController mockUserContentController;
3335
late MockWKWebViewConfiguration mockWebViewConfiguration;
36+
late MockWKUIDelegate mockUIDelegate;
3437

3538
late MockWebViewPlatformCallbacksHandler mockCallbacksHandler;
3639
late MockJavascriptChannelRegistry mockJavascriptChannelRegistry;
@@ -41,9 +44,11 @@ void main() {
4144
mockWebView = MockWKWebView();
4245
mockWebViewConfiguration = MockWKWebViewConfiguration();
4346
mockUserContentController = MockWKUserContentController();
47+
mockUIDelegate = MockWKUIDelegate();
4448
mockWebViewWidgetProxy = MockWebViewWidgetProxy();
4549

4650
when(mockWebViewWidgetProxy.createWebView(any)).thenReturn(mockWebView);
51+
when(mockWebViewWidgetProxy.createUIDelgate()).thenReturn(mockUIDelegate);
4752
when(mockWebView.configuration).thenReturn(mockWebViewConfiguration);
4853
when(mockWebViewConfiguration.userContentController).thenReturn(
4954
mockUserContentController,
@@ -84,6 +89,26 @@ void main() {
8489
await buildWidget(tester);
8590
});
8691

92+
testWidgets('Requests to open a new window loads request in same window',
93+
(WidgetTester tester) async {
94+
await buildWidget(tester);
95+
96+
final dynamic onCreateWebView =
97+
verify(mockUIDelegate.onCreateWebView = captureAny).captured.single
98+
as void Function(WKWebViewConfiguration, WKNavigationAction);
99+
100+
const NSUrlRequest request = NSUrlRequest(url: 'https://google.com');
101+
onCreateWebView(
102+
mockWebViewConfiguration,
103+
const WKNavigationAction(
104+
request: request,
105+
targetFrame: WKFrameInfo(isMainFrame: false),
106+
),
107+
);
108+
109+
verify(mockWebView.loadRequest(request));
110+
});
111+
87112
group('$CreationParams', () {
88113
testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async {
89114
await buildWidget(
@@ -271,7 +296,7 @@ void main() {
271296
.single as MockWKScriptMessageHandler;
272297

273298
final dynamic didReceiveScriptMessage =
274-
verify(messageHandler.setDidReceiveScriptMessage(captureAny))
299+
verify(messageHandler.didReceiveScriptMessage = captureAny)
275300
.captured
276301
.single as void Function(
277302
WKUserContentController userContentController,
@@ -280,7 +305,7 @@ void main() {
280305

281306
didReceiveScriptMessage(
282307
mockUserContentController,
283-
WKScriptMessage(name: 'hello', body: 'A message.'),
308+
const WKScriptMessage(name: 'hello', body: 'A message.'),
284309
);
285310
verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage(
286311
'hello',

0 commit comments

Comments
 (0)