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

Commit f2944b5

Browse files
[webview_flutter_wkwebview] Start work on new ios implementation (#4626)
1 parent 049ae07 commit f2944b5

5 files changed

Lines changed: 491 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
/// The media types that require a user gesture to begin playing.
6+
///
7+
/// Wraps [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc).
8+
enum WKAudiovisualMediaType {
9+
/// No media types require a user gesture to begin playing.
10+
///
11+
/// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypenone?language=objc.
12+
none,
13+
14+
/// Media types that contain audio require a user gesture to begin playing.
15+
///
16+
/// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypeaudio?language=objc.
17+
audio,
18+
19+
/// Media types that contain video require a user gesture to begin playing.
20+
///
21+
/// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypevideo?language=objc.
22+
video,
23+
24+
/// All media types require a user gesture to begin playing.
25+
///
26+
/// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypeall?language=objc.
27+
all,
28+
}
29+
30+
/// A collection of properties that you use to initialize a web view.
31+
///
32+
/// Wraps [WKWebViewConfiguration](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc)
33+
class WKWebViewConfiguration {
34+
/// Indicates whether HTML5 videos play inline or use the native full-screen controller.
35+
set allowsInlineMediaPlayback(bool allow) {
36+
throw UnimplementedError();
37+
}
38+
39+
/// The media types that require a user gesture to begin playing.
40+
///
41+
/// Use [WKAudiovisualMediaType.none] to indicate that no user gestures are
42+
/// required to begin playing media.
43+
set mediaTypesRequiringUserActionForPlayback(
44+
Set<WKAudiovisualMediaType> types,
45+
) {
46+
assert(types.isNotEmpty);
47+
throw UnimplementedError();
48+
}
49+
}
50+
51+
/// Object that displays interactive web content, such as for an in-app browser.
52+
///
53+
/// Wraps [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview?language=objc).
54+
class WKWebView {
55+
/// Construct a [WKWebView].
56+
///
57+
/// [configuration] contains the configuration details for the web view. This
58+
/// method saves a copy of your configuration object. Changes you make to your
59+
/// original object after calling this method have no effect on the web view’s
60+
/// configuration. For a list of configuration options and their default
61+
/// values, see [WKWebViewConfiguration]. If you didn’t create your web view
62+
/// using the `configuration` parameter, this value uses a default
63+
/// configuration object.
64+
// TODO(bparrishMines): Remove ignore once constructor is implemented.
65+
// ignore: avoid_unused_constructor_parameters
66+
WKWebView([WKWebViewConfiguration? configuration]) {
67+
throw UnimplementedError();
68+
}
69+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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:async';
6+
7+
import 'package:flutter/foundation.dart';
8+
import 'package:flutter/widgets.dart';
9+
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
10+
11+
import 'web_kit/web_kit.dart';
12+
13+
/// A [Widget] that displays a [WKWebView].
14+
class WebKitWebViewWidget extends StatefulWidget {
15+
/// Constructs a [WebKitWebViewWidget].
16+
const WebKitWebViewWidget({
17+
required this.creationParams,
18+
required this.callbacksHandler,
19+
required this.javascriptChannelRegistry,
20+
required this.onBuildWidget,
21+
this.configuration,
22+
@visibleForTesting this.webViewProxy = const WebViewProxy(),
23+
});
24+
25+
/// The initial parameters used to setup the WebView.
26+
final CreationParams creationParams;
27+
28+
/// The handler of callbacks made made by [NavigationDelegate].
29+
final WebViewPlatformCallbacksHandler callbacksHandler;
30+
31+
/// Manager of named JavaScript channels and forwarding incoming messages on the correct channel.
32+
final JavascriptChannelRegistry javascriptChannelRegistry;
33+
34+
/// A collection of properties used to initialize a web view.
35+
///
36+
/// If null, a default configuration is used.
37+
final WKWebViewConfiguration? configuration;
38+
39+
/// The handler for constructing [WKWebView]s and calling static methods.
40+
///
41+
/// This should only be changed for testing purposes.
42+
final WebViewProxy webViewProxy;
43+
44+
/// A callback to build a widget once [WKWebView] has been initialized.
45+
final Widget Function(WebKitWebViewPlatformController controller)
46+
onBuildWidget;
47+
48+
@override
49+
State<StatefulWidget> createState() => _WebKitWebViewWidgetState();
50+
}
51+
52+
class _WebKitWebViewWidgetState extends State<WebKitWebViewWidget> {
53+
late final WebKitWebViewPlatformController controller;
54+
55+
@override
56+
void initState() {
57+
super.initState();
58+
controller = WebKitWebViewPlatformController(
59+
creationParams: widget.creationParams,
60+
callbacksHandler: widget.callbacksHandler,
61+
javascriptChannelRegistry: widget.javascriptChannelRegistry,
62+
configuration: widget.configuration,
63+
webViewProxy: widget.webViewProxy,
64+
);
65+
}
66+
67+
@override
68+
Widget build(BuildContext context) {
69+
return widget.onBuildWidget(controller);
70+
}
71+
}
72+
73+
/// An implementation of [WebViewPlatformController] with the WebKit api.
74+
class WebKitWebViewPlatformController extends WebViewPlatformController {
75+
/// Construct a [WebKitWebViewPlatformController].
76+
WebKitWebViewPlatformController({
77+
required CreationParams creationParams,
78+
required this.callbacksHandler,
79+
required this.javascriptChannelRegistry,
80+
WKWebViewConfiguration? configuration,
81+
@visibleForTesting this.webViewProxy = const WebViewProxy(),
82+
}) : super(callbacksHandler) {
83+
_setCreationParams(
84+
creationParams,
85+
configuration: configuration ?? WKWebViewConfiguration(),
86+
).then((_) => _initializationCompleter.complete());
87+
}
88+
89+
final Completer<void> _initializationCompleter = Completer<void>();
90+
91+
/// Handles callbacks that are made by navigation.
92+
final WebViewPlatformCallbacksHandler callbacksHandler;
93+
94+
/// Manages named JavaScript channels and forwarding incoming messages on the correct channel.
95+
final JavascriptChannelRegistry javascriptChannelRegistry;
96+
97+
/// Handles constructing a [WKWebView].
98+
///
99+
/// This should only be changed when used for testing.
100+
final WebViewProxy webViewProxy;
101+
102+
/// Represents the WebView maintained by platform code.
103+
late final WKWebView webView;
104+
105+
Future<void> _setCreationParams(
106+
CreationParams params, {
107+
required WKWebViewConfiguration configuration,
108+
}) async {
109+
_setWebViewConfiguration(
110+
configuration,
111+
allowsInlineMediaPlayback: params.webSettings?.allowsInlineMediaPlayback,
112+
autoMediaPlaybackPolicy: params.autoMediaPlaybackPolicy,
113+
);
114+
115+
webView = webViewProxy.createWebView(configuration);
116+
}
117+
118+
void _setWebViewConfiguration(
119+
WKWebViewConfiguration configuration, {
120+
required bool? allowsInlineMediaPlayback,
121+
required AutoMediaPlaybackPolicy autoMediaPlaybackPolicy,
122+
}) {
123+
if (allowsInlineMediaPlayback != null) {
124+
configuration.allowsInlineMediaPlayback = allowsInlineMediaPlayback;
125+
}
126+
127+
late final bool requiresUserAction;
128+
switch (autoMediaPlaybackPolicy) {
129+
case AutoMediaPlaybackPolicy.require_user_action_for_all_media_types:
130+
requiresUserAction = true;
131+
break;
132+
case AutoMediaPlaybackPolicy.always_allow:
133+
requiresUserAction = false;
134+
break;
135+
}
136+
137+
configuration.mediaTypesRequiringUserActionForPlayback =
138+
<WKAudiovisualMediaType>{
139+
if (requiresUserAction) WKAudiovisualMediaType.all,
140+
if (!requiresUserAction) WKAudiovisualMediaType.none,
141+
};
142+
}
143+
}
144+
145+
/// Handles constructing [WKWebView]s and calling static methods.
146+
///
147+
/// This should only be used for testing purposes.
148+
@visibleForTesting
149+
class WebViewProxy {
150+
/// Creates a [WebViewProxy].
151+
const WebViewProxy();
152+
153+
/// Constructs a [WKWebView].
154+
WKWebView createWebView(WKWebViewConfiguration configuration) {
155+
return WKWebView(configuration);
156+
}
157+
}

packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ dependencies:
2121
webview_flutter_platform_interface: ^1.8.0
2222

2323
dev_dependencies:
24+
build_runner: ^2.1.5
2425
flutter_driver:
2526
sdk: flutter
2627
flutter_test:
2728
sdk: flutter
29+
mockito: ^5.0.16
2830
pedantic: ^1.10.0
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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:async';
6+
7+
import 'package:flutter/widgets.dart';
8+
import 'package:flutter_test/flutter_test.dart';
9+
import 'package:mockito/annotations.dart';
10+
import 'package:mockito/mockito.dart';
11+
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
12+
import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';
13+
import 'package:webview_flutter_wkwebview/src/web_kit_webview_widget.dart';
14+
15+
import 'web_kit_webview_widget_test.mocks.dart';
16+
17+
@GenerateMocks(<Type>[
18+
WKWebView,
19+
WKWebViewConfiguration,
20+
JavascriptChannelRegistry,
21+
WebViewPlatformCallbacksHandler,
22+
WebViewProxy,
23+
])
24+
void main() {
25+
TestWidgetsFlutterBinding.ensureInitialized();
26+
27+
group('$WebKitWebViewWidget', () {
28+
late MockWKWebView mockWebView;
29+
late MockWebViewProxy mockWebViewProxy;
30+
late MockWKWebViewConfiguration mockWebViewConfiguration;
31+
32+
late MockWebViewPlatformCallbacksHandler mockCallbacksHandler;
33+
late MockJavascriptChannelRegistry mockJavascriptChannelRegistry;
34+
35+
setUp(() {
36+
mockWebView = MockWKWebView();
37+
mockWebViewConfiguration = MockWKWebViewConfiguration();
38+
mockWebViewProxy = MockWebViewProxy();
39+
40+
when(mockWebViewProxy.createWebView(any)).thenReturn(mockWebView);
41+
42+
mockCallbacksHandler = MockWebViewPlatformCallbacksHandler();
43+
mockJavascriptChannelRegistry = MockJavascriptChannelRegistry();
44+
});
45+
46+
// Builds a WebViewCupertinoWidget with default parameters.
47+
Future<void> buildWidget(
48+
WidgetTester tester, {
49+
CreationParams? creationParams,
50+
bool hasNavigationDelegate = false,
51+
bool hasProgressTracking = false,
52+
bool useHybridComposition = false,
53+
}) async {
54+
await tester.pumpWidget(WebKitWebViewWidget(
55+
creationParams: creationParams ??
56+
CreationParams(
57+
webSettings: WebSettings(
58+
userAgent: const WebSetting<String?>.absent(),
59+
hasNavigationDelegate: hasNavigationDelegate,
60+
hasProgressTracking: hasProgressTracking,
61+
)),
62+
callbacksHandler: mockCallbacksHandler,
63+
javascriptChannelRegistry: mockJavascriptChannelRegistry,
64+
webViewProxy: mockWebViewProxy,
65+
configuration: mockWebViewConfiguration,
66+
onBuildWidget: (WebKitWebViewPlatformController controller) {
67+
return Container();
68+
},
69+
));
70+
await tester.pumpAndSettle();
71+
}
72+
73+
testWidgets('build $WebKitWebViewWidget', (WidgetTester tester) async {
74+
await buildWidget(tester);
75+
});
76+
77+
group('$CreationParams', () {
78+
testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async {
79+
await buildWidget(
80+
tester,
81+
creationParams: CreationParams(
82+
autoMediaPlaybackPolicy:
83+
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
84+
webSettings: WebSettings(
85+
userAgent: const WebSetting<String?>.absent(),
86+
hasNavigationDelegate: false,
87+
),
88+
),
89+
);
90+
91+
verify(
92+
mockWebViewConfiguration.mediaTypesRequiringUserActionForPlayback =
93+
<WKAudiovisualMediaType>{
94+
WKAudiovisualMediaType.all,
95+
});
96+
});
97+
98+
testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async {
99+
await buildWidget(
100+
tester,
101+
creationParams: CreationParams(
102+
autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
103+
webSettings: WebSettings(
104+
userAgent: const WebSetting<String?>.absent(),
105+
hasNavigationDelegate: false,
106+
),
107+
),
108+
);
109+
110+
verify(
111+
mockWebViewConfiguration.mediaTypesRequiringUserActionForPlayback =
112+
<WKAudiovisualMediaType>{
113+
WKAudiovisualMediaType.none,
114+
});
115+
});
116+
117+
group('$WebSettings', () {
118+
testWidgets('allowsInlineMediaPlayback', (WidgetTester tester) async {
119+
await buildWidget(
120+
tester,
121+
creationParams: CreationParams(
122+
webSettings: WebSettings(
123+
userAgent: const WebSetting<String?>.absent(),
124+
allowsInlineMediaPlayback: true,
125+
),
126+
),
127+
);
128+
129+
verify(mockWebViewConfiguration.allowsInlineMediaPlayback = true);
130+
});
131+
});
132+
});
133+
});
134+
}

0 commit comments

Comments
 (0)