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

[webview_flutter_wkwebview] Add WKWebView method stubs #4990

Merged
merged 13 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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,38 @@
// 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 'dart:math';

import '../web_kit/web_kit.dart';

/// A view that allows the scrolling and zooming of its contained views.
///
/// Wraps [UIScrollView](https://developer.apple.com/documentation/uikit/uiscrollview?language=objc).
class UIScrollView {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is one of those things that makes me suspect going all the way to the native boundary isn't going to be a great experience (at least not without automatic language projection); having to plumb in new ancillary objects for minor usage could get ugly.

(No change needed here, just flagging as something we'll want to watch for over time as we evaluate this approach and think about whether we want to flex the boundary back a little.)

/// Constructs a [UIScrollView] that is owned by [webView].
// TODO(bparrishMines): Remove ignore once constructor is implemented.
// ignore: avoid_unused_constructor_parameters
UIScrollView.fromWebView(WKWebView webView);

/// Point at which the origin of the content view is offset from the origin of the scroll view.
Future<Point<double>> get contentOffset {
throw UnimplementedError();
}

/// Move the scrolled position of this view.
///
/// This method is not a part of UIKit and is only a helper method to make
/// scrollBy atomic.
Future<void> scrollBy(Point<double> offset) {
throw UnimplementedError();
}

/// Set point at which the origin of the content view is offset from the origin of the scroll view.
///
/// The default value is `Point<double>(0.0, 0.0)`.
set contentOffset(FutureOr<Point<double>> offset) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart';

import '../foundation/foundation.dart';
import '../ui_kit/ui_kit.dart';

/// Times at which to inject script content into a webpage.
///
Expand Down Expand Up @@ -450,6 +451,9 @@ class WKWebView {
late final WKWebViewConfiguration configuration =
WKWebViewConfiguration._fromWebView(this);

/// The scrollable view associated with the web view.
late final UIScrollView scrollView = UIScrollView.fromWebView(this);

/// Used to integrate custom user interface elements into web view interactions.
set uiDelegate(WKUIDelegate? delegate) {
throw UnimplementedError();
Expand All @@ -472,4 +476,79 @@ class WKWebView {
Future<void> loadRequest(NSUrlRequest request) {
throw UnimplementedError();
}

/// Loads the contents of the specified HTML string and navigates to it.
Future<void> loadHtmlString(String string, {String? baseUrl}) {
throw UnimplementedError();
}

/// Loads the web content from the specified file and navigates to it.
Future<void> loadFileUrl(String url, {required String readAccessUrl}) {
throw UnimplementedError();
}

/// Loads the Flutter asset specified in the pubspec.yaml file.
///
/// This method is not a part of WebKit and is only a Flutter specific helper
/// method.
Future<void> loadFlutterAsset(String key) {
throw UnimplementedError();
}

/// Indicates whether there is a valid back item in the back-forward list.
Future<bool> get canGoBack {
throw UnimplementedError();
}

/// Indicates whether there is a valid forward item in the back-forward list.
Future<bool> get canGoForward {
throw UnimplementedError();
}

/// Navigates to the back item in the back-forward list.
Future<void> goBack() {
throw UnimplementedError();
}

/// Navigates to the forward item in the back-forward list.
Future<void> goForward() {
throw UnimplementedError();
}

/// Reloads the current webpage.
Future<void> reload() {
throw UnimplementedError();
}

/// The page title.
Future<String?> get title {
throw UnimplementedError();
}

/// An estimate of what fraction of the current navigation has been loaded.
Future<double> get estimatedProgress {
throw UnimplementedError();
}

/// Indicates whether horizontal swipe gestures trigger page navigation.
///
/// The default value is false.
set allowsBackForwardNavigationGestures(bool allow) {
throw UnimplementedError();
}

/// The custom user agent string.
///
/// The default value of this property is null.
set customUserAgent(String? userAgent) {
throw UnimplementedError();
}

/// Evaluates the specified JavaScript string.
///
/// Throws a `PlatformException` if an error occurs or return value is not
/// supported.
Future<Object?> evaluateJavaScript(String javaScriptString) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as path;
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';

import 'foundation/foundation.dart';
Expand Down Expand Up @@ -194,6 +197,19 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
};
}

@override
Future<void> loadHtmlString(String html, {String? baseUrl}) {
return webView.loadHtmlString(html, baseUrl: baseUrl);
}

@override
Future<void> loadFile(String absoluteFilePath) async {
await webView.loadFileUrl(
absoluteFilePath,
readAccessUrl: path.dirname(absoluteFilePath),
);
}

@override
Future<void> clearCache() {
return webView.configuration.webSiteDataStore.removeDataOfTypes(
Expand All @@ -207,6 +223,124 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
);
}

@override
Future<void> loadFlutterAsset(String key) async {
assert(key.isNotEmpty);
return webView.loadFlutterAsset(key);
}

@override
Future<void> loadUrl(String url, Map<String, String>? headers) async {
final NSUrlRequest request = NSUrlRequest(
url: url,
allHttpHeaderFields: headers ?? <String, String>{},
);
return webView.loadRequest(request);
}

@override
Future<void> loadRequest(WebViewRequest request) async {
if (!request.uri.hasScheme) {
throw ArgumentError('WebViewRequest#uri is required to have a scheme.');
}

final NSUrlRequest urlRequest = NSUrlRequest(
url: request.uri.toString(),
allHttpHeaderFields: request.headers,
httpMethod: describeEnum(request.method),
httpBody: request.body,
);

return webView.loadRequest(urlRequest);
}

@override
Future<bool> canGoBack() => webView.canGoBack;

@override
Future<bool> canGoForward() => webView.canGoForward;

@override
Future<void> goBack() => webView.goBack();

@override
Future<void> goForward() => webView.goForward();

@override
Future<void> reload() => webView.reload();

@override
Future<String> evaluateJavascript(String javascript) async {
final Object? result = await webView.evaluateJavaScript(javascript);
// The legacy implementation of webview_flutter_wkwebview would convert
// objects to strings before returning them to Dart. This method attempts
// to converts Dart objects to Strings the way it is done in Objective-C
// to avoid breaking users expecting the same String format.
return _asObjectiveCString(result);
}

@override
Future<void> runJavascript(String javascript) async {
try {
await webView.evaluateJavaScript(javascript);
} on PlatformException catch (exception) {
// WebKit will throw an error when the type of the evaluated value is
// unsupported. This also goes for `null` and `undefined` on iOS 14+. For
// example, when running a void function. For ease of use, this specific
// error is ignored when no return value is expected.
// TODO(bparrishMines): Ensure the platform code includes the NSError in
// the FlutterError.details.
if (exception.details is! NSError ||
exception.details.code !=
WKErrorCode.javaScriptResultTypeIsUnsupported) {
rethrow;
}
}
}

@override
Future<String> runJavascriptReturningResult(String javascript) async {
final Object? result = await webView.evaluateJavaScript(javascript);
if (result == null) {
throw ArgumentError(
'Result of JavaScript execution returned a `null` value. '
'Use `runJavascript` when expecting a null return value.',
);
}
return result.toString();
}

@override
Future<String?> getTitle() => webView.title;

@override
Future<void> scrollTo(int x, int y) async {
webView.scrollView.contentOffset = Point<double>(
x.toDouble(),
y.toDouble(),
);
}

@override
Future<void> scrollBy(int x, int y) async {
await webView.scrollView.scrollBy(Point<double>(
x.toDouble(),
y.toDouble(),
));
}

@override
Future<int> getScrollX() async {
final Point<double> offset = await webView.scrollView.contentOffset;
return offset.x.toInt();
}

@override
Future<int> getScrollY() async {
final Point<double> offset = await webView.scrollView.contentOffset;
return offset.y.toInt();
}

@override
Future<void> updateSettings(WebSettings setting) async {
if (setting.hasNavigationDelegate != null) {
Expand Down Expand Up @@ -324,6 +458,35 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
errorType: errorType,
);
}

String _asObjectiveCString(Object? value, {bool inContainer = false}) {
if (value == null) {
// An NSNull inside an NSArray or NSDictionary is represented as a String
// differently than a nil.
if (inContainer) {
return '"<null>"';
}
return '(null)';
} else if (value is List) {
final List<String> stringValues = <String>[];
for (final Object? listValue in value) {
stringValues.add(_asObjectiveCString(listValue, inContainer: true));
}
return '(${stringValues.join(',')})';
} else if (value is Map) {
final List<String> stringValues = <String>[];
for (final MapEntry<Object?, Object?> entry in value.entries) {
stringValues.add(
'${_asObjectiveCString(entry.key, inContainer: true)} '
'= '
'${_asObjectiveCString(entry.value, inContainer: true)}',
);
}
return '{${stringValues.join(';')}}';
}

return value.toString();
}
}

/// Handles constructing objects and calling static methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
path: ^1.8.0
webview_flutter_platform_interface: ^1.8.0

dev_dependencies:
Expand Down
Loading