Removed willSuppressErrorPage WebView Android setting in favor of disableDefaultErrorPage, Added isMultiProcessEnabled static method on InAppWebViewController for Android, Added onContentSizeChanged WebView event for iOS, Added onPermissionRequestCanceled and onRequestFocus WebView events for Android, Added defaultVideoPoster WebView setting for Android, Added TracingController for Android WebViews

This commit is contained in:
Lorenzo Pichilli 2022-10-31 02:09:00 +01:00
parent 9ad20bd13a
commit 06668703c4
39 changed files with 1332 additions and 165 deletions

View File

@ -1,3 +1,16 @@
## 6.0.0-beta.12
- Removed `willSuppressErrorPage` WebView Android setting in favor of `disableDefaultErrorPage`.
- Added `isMultiProcessEnabled` static method on `InAppWebViewController` for Android
- Added `onContentSizeChanged` WebView event for iOS
- Added `onPermissionRequestCanceled`, `onRequestFocus` WebView events for Android
- Added `defaultVideoPoster` WebView setting for Android
- Added `TracingController` for Android WebViews
### BREAKING CHANGES
- Removed `willSuppressErrorPage` WebView Android setting. Use `disableDefaultErrorPage` instead.
## 6.0.0-beta.11
- Fixed "[webRTC / macOS] onPermissionRequest not called on HeadlessInAppWebView" [#1405](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1405)
@ -19,7 +32,7 @@
- Added `onNavigationEvent`, `onServiceConnected`, `onRelationshipValidationResult` events on `ChromeSafariBrowser` for Android
- Added `mayLaunchUrl`, `launchUrl`, `updateActionButton`, `validateRelationship`, `setSecondaryToolbar`, `updateSecondaryToolbar` methods on `ChromeSafariBrowser` for Android
- Added `startAnimations`, `exitAnimations`, `navigationBarColor`, `navigationBarDividerColor`, `secondaryToolbarColor`, `alwaysUseBrowserUI` ChromeSafariBrowser settings for Android
- Added `getMaxToolbarItems` static methods on `ChromeSafariBrowser` for Android
- Added `getMaxToolbarItems` static method on `ChromeSafariBrowser` for Android
- Added `ChromeSafariBrowserMenuItem.image` property for iOS
- Added `didLoadSuccessfully` optional argument on `ChromeSafariBrowser.onCompletedInitialLoad` event for iOS
- Added `onInitialLoadDidRedirect`, `onWillOpenInBrowser` events on `ChromeSafariBrowser` for iOS

View File

@ -16,6 +16,7 @@ import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.Headless
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager;
import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager;
import com.pichillilorenzo.flutter_inappwebview.tracing.TracingControllerManager;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
@ -54,6 +55,8 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
@Nullable
public PrintJobManager printJobManager;
@Nullable
public TracingControllerManager tracingControllerManager;
@Nullable
public static ValueCallback<Uri> filePathCallbackLegacy;
@Nullable
public static ValueCallback<Uri[]> filePathCallback;
@ -121,6 +124,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager = new PrintJobManager();
}
tracingControllerManager = new TracingControllerManager(this);
}
@Override
@ -173,6 +177,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
printJobManager.dispose();
printJobManager = null;
}
if (tracingControllerManager != null) {
tracingControllerManager.dispose();
tracingControllerManager = null;
}
filePathCallbackLegacy = null;
filePathCallback = null;
}

View File

@ -122,6 +122,14 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
result.success(null);
}
break;
case "isMultiProcessEnabled":
if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROCESS)) {
result.success(WebViewCompat.isMultiProcessEnabled());
}
else {
result.success(false);
}
break;
default:
result.notImplemented();
}

View File

@ -0,0 +1,77 @@
package com.pichillilorenzo.flutter_inappwebview.tracing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.TracingConfig;
import androidx.webkit.TracingController;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Map;
import java.util.concurrent.Executors;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class TracingControllerChannelDelegate extends ChannelDelegateImpl {
@Nullable
private TracingControllerManager tracingControllerManager;
public TracingControllerChannelDelegate(@NonNull TracingControllerManager tracingControllerManager, @NonNull MethodChannel channel) {
super(channel);
this.tracingControllerManager = tracingControllerManager;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
TracingController tracingController = TracingControllerManager.tracingController;
switch (call.method) {
case "isTracing":
if (tracingController != null) {
result.success(tracingController.isTracing());
} else {
result.success(false);
}
break;
case "start":
if (tracingController != null && WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
Map<String, Object> settingsMap = (Map<String, Object>) call.argument("settings");
TracingSettings settings = new TracingSettings();
settings.parse(settingsMap);
TracingConfig config = TracingControllerManager.buildTracingConfig(settings);
tracingController.start(config);
result.success(true);
} else {
result.success(false);
}
break;
case "stop":
if (tracingController != null && WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
String filePath = (String) call.argument("filePath");
try {
result.success(tracingController.stop(
filePath != null ? new FileOutputStream(filePath) : null,
Executors.newSingleThreadExecutor()));
} catch (FileNotFoundException e) {
e.printStackTrace();
result.success(false);
}
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
tracingControllerManager = null;
}
}

View File

@ -0,0 +1,60 @@
package com.pichillilorenzo.flutter_inappwebview.tracing;
import androidx.annotation.Nullable;
import androidx.webkit.TracingConfig;
import androidx.webkit.TracingController;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import io.flutter.plugin.common.MethodChannel;
public class TracingControllerManager implements Disposable {
protected static final String LOG_TAG = "TracingControllerMan";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_tracingcontroller";
@Nullable
public TracingControllerChannelDelegate channelDelegate;
@Nullable
public static TracingController tracingController;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public TracingControllerManager(final InAppWebViewFlutterPlugin plugin) {
this.plugin = plugin;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME);
this.channelDelegate = new TracingControllerChannelDelegate(this, channel);
if (WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
tracingController = TracingController.getInstance();
} else {
tracingController = null;
}
}
public static TracingConfig buildTracingConfig(TracingSettings settings) {
TracingConfig.Builder builder = new TracingConfig.Builder();
for (Object category : settings.categories) {
if (category instanceof String) {
builder.addCategories((String) category);
}
if (category instanceof Integer) {
builder.addCategories((Integer) category);
}
}
if (settings.tracingMode != null) {
builder.setTracingMode(settings.tracingMode);
}
return builder.build();
}
@Override
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
tracingController = null;
plugin = null;
}
}

View File

@ -0,0 +1,61 @@
package com.pichillilorenzo.flutter_inappwebview.tracing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.TracingController;
import com.pichillilorenzo.flutter_inappwebview.ISettings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TracingSettings implements ISettings<TracingController> {
public static final String LOG_TAG = "TracingSettings";
@NonNull
public List<Object> categories = new ArrayList<>();
@Nullable
public Integer tracingMode;
@NonNull
@Override
public TracingSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "categories":
categories = (List<Object>) value;
break;
case "tracingMode":
tracingMode = (Integer) value;
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("categories", categories);
settings.put("tracingMode", tracingMode);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull TracingController tracingController) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}

View File

@ -952,6 +952,15 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
channel.invokeMethod("onPermissionRequest", obj, callback);
}
public void onPermissionRequestCanceled(String origin, List<String> resources) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("origin", origin);
obj.put("resources", resources);
channel.invokeMethod("onPermissionRequestCanceled", obj);
}
public static class ShouldOverrideUrlLoadingCallback extends BaseCallbackResultImpl<NavigationActionPolicy> {
@Nullable
@Override
@ -1287,6 +1296,13 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
channel.invokeMethod("onPrintRequest", obj, callback);
}
public void onRequestFocus() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onRequestFocus", obj);
}
@Override
public void dispose() {
super.dispose();

View File

@ -382,7 +382,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
}
if (WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) {
WebSettingsCompat.setWillSuppressErrorPage(settings, customSettings.willSuppressErrorPage);
WebSettingsCompat.setWillSuppressErrorPage(settings, customSettings.disableDefaultErrorPage);
}
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, customSettings.algorithmicDarkeningAllowed);
@ -1033,10 +1033,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
setHorizontalScrollbarTrackDrawable(new ColorDrawable(Color.parseColor(newCustomSettings.horizontalScrollbarTrackColor)));
}
if (newSettingsMap.get("willSuppressErrorPage") != null &&
!Util.objEquals(customSettings.willSuppressErrorPage, newCustomSettings.willSuppressErrorPage) &&
if (newSettingsMap.get("disableDefaultErrorPage") != null &&
!Util.objEquals(customSettings.disableDefaultErrorPage, newCustomSettings.disableDefaultErrorPage) &&
WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) {
WebSettingsCompat.setWillSuppressErrorPage(settings, newCustomSettings.willSuppressErrorPage);
WebSettingsCompat.setWillSuppressErrorPage(settings, newCustomSettings.disableDefaultErrorPage);
}
if (newSettingsMap.get("algorithmicDarkeningAllowed") != null &&
!Util.objEquals(customSettings.algorithmicDarkeningAllowed, newCustomSettings.algorithmicDarkeningAllowed) &&

View File

@ -3,11 +3,13 @@ package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
@ -18,6 +20,7 @@ import android.os.Parcelable;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@ -33,6 +36,8 @@ import android.webkit.WebView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -42,6 +47,7 @@ import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.types.CreateWindowAction;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.ActivityResultListener;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserDelegate;
@ -125,8 +131,17 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
plugin.activityPluginBinding.addActivityResultListener(this);
}
@Nullable
@Override
public Bitmap getDefaultVideoPoster() {
if (inAppWebView != null && inAppWebView.customSettings.defaultVideoPoster != null) {
final byte[] data = inAppWebView.customSettings.defaultVideoPoster;
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
return BitmapFactory.decodeByteArray(
data, 0, data.length, bitmapOptions
);
}
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
}
@ -1238,6 +1253,22 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
}
}
@Override
public void onRequestFocus(WebView view) {
if(inAppWebView != null && inAppWebView.channelDelegate != null) {
inAppWebView.channelDelegate.onRequestFocus();
}
}
@Override
public void onPermissionRequestCanceled(PermissionRequest request) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
inAppWebView != null && inAppWebView.channelDelegate != null) {
inAppWebView.channelDelegate.onPermissionRequestCanceled(request.getOrigin().toString(),
Arrays.asList(request.getResources()));
}
}
@Nullable
private Activity getActivity() {
if (inAppBrowserDelegate != null) {

View File

@ -289,11 +289,13 @@ public class InAppWebViewClient extends WebViewClient {
}
}
@SuppressLint("RestrictedApi")
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
final InAppWebView webView = (InAppWebView) view;
if (webView.customSettings.disableDefaultErrorPage) {
if (!WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE) &&
webView.customSettings.disableDefaultErrorPage) {
webView.stopLoading();
webView.loadUrl("about:blank");
}

View File

@ -114,13 +114,14 @@ public class InAppWebViewSettings implements ISettings<InAppWebViewInterface> {
public String horizontalScrollbarThumbColor;
@Nullable
public String horizontalScrollbarTrackColor;
public Boolean willSuppressErrorPage = false;
public Boolean algorithmicDarkeningAllowed = false;
@Nullable
public Integer requestedWithHeaderMode;
public Boolean enterpriseAuthenticationAppLinkPolicyEnabled = true;
@Nullable
public Map<String, Object> webViewAssetLoader;
@Nullable
public byte[] defaultVideoPoster;
@NonNull
@Override
@ -379,9 +380,6 @@ public class InAppWebViewSettings implements ISettings<InAppWebViewInterface> {
case "horizontalScrollbarTrackColor":
horizontalScrollbarTrackColor = (String) value;
break;
case "willSuppressErrorPage":
willSuppressErrorPage = (Boolean) value;
break;
case "algorithmicDarkeningAllowed":
algorithmicDarkeningAllowed = (Boolean) value;
break;
@ -397,6 +395,9 @@ public class InAppWebViewSettings implements ISettings<InAppWebViewInterface> {
case "webViewAssetLoader":
webViewAssetLoader = (Map<String, Object>) value;
break;
case "defaultVideoPoster":
defaultVideoPoster = (byte[]) value;
break;
}
}
@ -489,11 +490,11 @@ public class InAppWebViewSettings implements ISettings<InAppWebViewInterface> {
settings.put("verticalScrollbarTrackColor", verticalScrollbarTrackColor);
settings.put("horizontalScrollbarThumbColor", horizontalScrollbarThumbColor);
settings.put("horizontalScrollbarTrackColor", horizontalScrollbarTrackColor);
settings.put("willSuppressErrorPage", willSuppressErrorPage);
settings.put("algorithmicDarkeningAllowed", algorithmicDarkeningAllowed);
settings.put("requestedWithHeaderMode", requestedWithHeaderMode);
settings.put("enterpriseAuthenticationAppLinkPolicyEnabled", enterpriseAuthenticationAppLinkPolicyEnabled);
settings.put("allowBackgroundAudioPlaying", allowBackgroundAudioPlaying);
settings.put("defaultVideoPoster", defaultVideoPoster);
return settings;
}
@ -580,7 +581,7 @@ public class InAppWebViewSettings implements ISettings<InAppWebViewInterface> {
realSettings.put("rendererPriorityPolicy", rendererPriorityPolicy);
}
if (WebViewFeature.isFeatureSupported(WebViewFeature.SUPPRESS_ERROR_PAGE)) {
realSettings.put("willSuppressErrorPage", WebSettingsCompat.willSuppressErrorPage(settings));
realSettings.put("disableDefaultErrorPage", WebSettingsCompat.willSuppressErrorPage(settings));
}
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
realSettings.put("algorithmicDarkeningAllowed", WebSettingsCompat.isAlgorithmicDarkeningAllowed(settings));

View File

@ -37,6 +37,7 @@ import 'load_file.dart';
import 'load_file_url.dart';
import 'load_url.dart';
import 'on_console_message.dart';
import 'on_content_size_changed.dart';
import 'on_download_start_request.dart';
import 'on_js_before_unload.dart';
import 'on_received_error.dart';
@ -172,5 +173,6 @@ void main() {
applePayAPI();
handlesURLScheme();
webViewAssetLoader();
onContentSizeChanged();
}, skip: shouldSkip);
}

View File

@ -0,0 +1,38 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
import '../constants.dart';
void onContentSizeChanged() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.iOS,
TargetPlatform.macOS,
].contains(defaultTargetPlatform);
testWidgets('onContentSizeChanged', (WidgetTester tester) async {
final Completer<void> onContentSizeChangedCompleter =
Completer<void>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialUrlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1),
onContentSizeChanged: (controller, oldContentSize, newContentSize) {
if (!onContentSizeChangedCompleter.isCompleted) {
onContentSizeChangedCompleter.complete();
}
},
),
),
);
await expectLater(onContentSizeChangedCompleter.future, completes);
}, skip: shouldSkip);
}

View File

@ -16,8 +16,7 @@ void onPermissionRequest() {
TargetPlatform.macOS,
].contains(defaultTargetPlatform);
var expectedValue = [];
expectedValue = [PermissionResourceType.CAMERA];
final expectedValue = [PermissionResourceType.CAMERA];
testWidgets('onPermissionRequest', (WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter =
@ -38,8 +37,7 @@ void onPermissionRequest() {
onLoadStop: (controller, url) {
pageLoaded.complete();
},
onPermissionRequest:
(controller, PermissionRequest permissionRequest) async {
onPermissionRequest: (controller, permissionRequest) async {
onPermissionRequestCompleter.complete(permissionRequest.resources);
return PermissionResponse(
resources: permissionRequest.resources,
@ -59,4 +57,66 @@ void onPermissionRequest() {
expect(listEquals(resources, expectedValue), true);
}, skip: shouldSkip);
final shouldSkip2 = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
testWidgets('onPermissionRequestCanceled', (WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
final Completer<List<PermissionResourceType>> onPermissionRequestCompleter =
Completer<List<PermissionResourceType>>();
final Completer<List<PermissionResourceType>>
onPermissionRequestCancelCompleter =
Completer<List<PermissionResourceType>>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialUrlRequest: URLRequest(url: TEST_PERMISSION_SITE),
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
onLoadStop: (controller, url) {
if (pageLoaded.isCompleted) {
pageLoaded.complete();
}
},
onPermissionRequest: (controller, permissionRequest) async {
onPermissionRequestCompleter.complete(permissionRequest.resources);
await Future.delayed(Duration(seconds: 30));
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT);
},
onPermissionRequestCanceled: (controller, permissionRequest) {
onPermissionRequestCancelCompleter
.complete(permissionRequest.resources);
},
),
),
);
final InAppWebViewController controller = await controllerCompleter.future;
await pageLoaded.future;
await controller.evaluateJavascript(
source: "document.querySelector('#camera').click();");
await tester.pump();
final List<PermissionResourceType> resources =
await onPermissionRequestCompleter.future;
expect(listEquals(resources, expectedValue), true);
controller.reload();
final List<PermissionResourceType> canceledResources =
await onPermissionRequestCancelCompleter.future;
expect(listEquals(canceledResources, expectedValue), true);
}, skip: shouldSkip2);
}

View File

@ -5,7 +5,11 @@ import 'set_service_worker_client.dart';
import 'should_intercept_request.dart';
void main() {
final shouldSkip = kIsWeb;
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
group('Service Worker Controller', () {
shouldInterceptRequest();

View File

@ -0,0 +1,16 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'start_and_stop.dart';
void main() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
group('Tracing Controller', () {
startAndStop();
}, skip: shouldSkip);
}

View File

@ -0,0 +1,63 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import '../constants.dart';
void startAndStop() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
testWidgets('start and stop', (WidgetTester tester) async {
final Completer<void> pageLoaded = Completer<void>();
final tracingAvailable = await WebViewFeature.isFeatureSupported(
WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE);
if (!tracingAvailable) {
return;
}
final tracingController = TracingController.instance();
expect(await tracingController.isTracing(), false);
await tracingController.start(
settings: TracingSettings(
tracingMode: TracingMode.RECORD_CONTINUOUSLY,
categories: [
TracingCategory.CATEGORIES_ANDROID_WEBVIEW,
"blink*"
]));
expect(await tracingController.isTracing(), true);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialUrlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1),
onLoadStop: (controller, url) {
if (!pageLoaded.isCompleted) {
pageLoaded.complete();
}
},
),
),
);
await pageLoaded.future;
Directory appDocDir = await getApplicationDocumentsDirectory();
String traceFilePath = '${appDocDir.path}${Platform.pathSeparator}trace.json';
expect(
await tracingController.stop(filePath: traceFilePath), true);
expect(await tracingController.isTracing(), false);
}, skip: shouldSkip);
}

View File

@ -12,6 +12,7 @@ import 'cookie_manager/main.dart' as cookie_manager_tests;
import 'in_app_browser/main.dart' as in_app_browser_tests;
import 'chrome_safari_browser/main.dart' as chrome_safari_browser_tests;
import 'in_app_localhost_server/main.dart' as in_app_localhost_server_tests;
import 'tracing_controller/main.dart' as tracing_controller_tests;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@ -37,6 +38,7 @@ void main() {
find_interaction_controller_tests.main();
service_worker_controller_tests.main();
proxy_controller_tests.main();
tracing_controller_tests.main();
headless_in_app_webview_tests.main();
cookie_manager_tests.main();
in_app_browser_tests.main();

View File

@ -1,13 +1,14 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/3.3.5"
export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/3.3.6"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"
export "PACKAGE_CONFIG=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/.dart_tool/package_config.json"

View File

@ -13,7 +13,7 @@ publish_to: none
environment:
sdk: ">=2.14.0 <3.0.0"
flutter: ">=2.5.0"
flutter: ">=3.0.0"
dependencies:
flutter:

View File

@ -291,6 +291,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
scrollView.addGestureRecognizer(self.panGestureRecognizer)
scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.new, .old], context: nil)
scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale), options: [.new, .old], context: nil)
scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize), options: [.new, .old], context: nil)
addObserver(self,
forKeyPath: #keyPath(WKWebView.estimatedProgress),
@ -664,6 +665,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
self.onScrollChanged(startedByUser: startedByUser, oldContentOffset: oldContentOffset)
}
}
} else if keyPath == #keyPath(UIScrollView.contentSize) {
if let newContentSize = change?[.newKey] as? CGSize,
let oldContentSize = change?[.oldKey] as? CGSize,
newContentSize != oldContentSize {
DispatchQueue.main.async {
self.onContentSizeChanged(oldContentSize: oldContentSize)
}
}
}
else if #available(iOS 15.0, *) {
if keyPath == #keyPath(WKWebView.cameraCaptureState) || keyPath == #keyPath(WKWebView.microphoneCaptureState) {
@ -2376,6 +2385,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
}
public func onContentSizeChanged(oldContentSize: CGSize) {
channelDelegate?.onContentSizeChanged(oldContentSize: oldContentSize,
newContentSize: scrollView.contentSize)
}
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
let newScale = Float(scrollView.zoomScale)
if newScale != oldZoomScale {
@ -3111,6 +3125,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
// }
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset))
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale))
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize))
resumeTimers()
stopLoading()
disposeWebMessageChannels()

View File

@ -695,6 +695,14 @@ public class WebViewChannelDelegate : ChannelDelegate {
channel?.invokeMethod("onScrollChanged", arguments: arguments)
}
public func onContentSizeChanged(oldContentSize: CGSize, newContentSize: CGSize) {
let arguments: [String: Any?] = [
"oldContentSize": oldContentSize.toMap(),
"newContentSize": newContentSize.toMap()
]
channel?.invokeMethod("onContentSizeChanged", arguments: arguments)
}
public func onDownloadStartRequest(request: DownloadStartRequest) {
channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap())
}

View File

@ -0,0 +1,21 @@
//
// CGSize.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 28/10/22.
//
import Foundation
extension CGSize {
public static func fromMap(map: [String: Double]) -> CGSize {
return CGSize(width: map["width"]!, height: map["height"]!)
}
public func toMap () -> [String:Any?] {
return [
"width": width,
"height": height
]
}
}

View File

@ -8,3 +8,4 @@ export 'webview_asset_loader.dart'
AssetsPathHandler,
ResourcesPathHandler,
InternalStoragePathHandler;
export 'tracing_controller.dart' show TracingController, TracingSettings;

View File

@ -0,0 +1,154 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import '../types/tracing_mode.dart';
import 'webview_feature.dart';
import '../in_app_webview/webview.dart';
import '../types/main.dart';
part 'tracing_controller.g.dart';
///Manages tracing of [WebView]s.
///In particular provides functionality for the app to enable/disable tracing of parts of code and to collect tracing data.
///This is useful for profiling performance issues, debugging and memory usage analysis in production and real life scenarios.
///
///The resulting trace data is sent back as a byte sequence in json format.
///This file can be loaded in "chrome://tracing" for further analysis.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingController](https://developer.android.com/reference/androidx/webkit/TracingController))
class TracingController {
static TracingController? _instance;
static const MethodChannel _channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_tracingcontroller');
TracingController._();
///Gets the [TracingController] shared instance.
///
///This method should only be called if [WebViewFeature.isFeatureSupported] returns `true`
///for [WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE].
static TracingController instance() {
return (_instance != null) ? _instance! : _init();
}
static TracingController _init() {
_channel.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
_instance = TracingController._();
return _instance!;
}
static Future<dynamic> _handleMethod(MethodCall call) async {
// TracingController controller = TracingController.instance();
switch (call.method) {
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
// return null;
}
///Starts tracing all [WebView]s.
///Depending on the trace mode in trace config specifies how the trace events are recorded.
///For tracing modes [TracingMode.RECORD_UNTIL_FULL] and [TracingMode.RECORD_CONTINUOUSLY]
///the events are recorded using an internal buffer and flushed to the outputStream
///when [stop] is called.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingController.start](https://developer.android.com/reference/android/webkit/TracingController#start(android.webkit.TracingConfig)))
Future<void> start({required TracingSettings settings}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("settings", () => settings.toMap());
await _channel.invokeMethod('start', args);
}
///Stops tracing and flushes tracing data to the specified output stream.
///The data is sent to the specified output stream in json format typically in
///chunks.
///
///Returns `false` if the WebView framework was not tracing at the time of the call,
///`true` otherwise.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingController.stop](https://developer.android.com/reference/android/webkit/TracingController#stop(java.io.OutputStream,%20java.util.concurrent.Executor)))
Future<bool> stop({String? filePath}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("filePath", () => filePath);
return await _channel.invokeMethod('stop', args);
}
///Returns whether the WebView framework is tracing.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingController.isTracing](https://developer.android.com/reference/android/webkit/TracingController#isTracing()))
Future<bool> isTracing() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isTracing', args);
}
}
List<dynamic> _deserializeCategories(List<dynamic> categories) {
List<dynamic> deserializedCategories = [];
for (dynamic category in categories) {
if (category is String) {
deserializedCategories.add(category);
} else if (category is int) {
final mode = TracingCategory.fromNativeValue(category);
if (mode != null) {
deserializedCategories.add(mode);
}
}
}
return deserializedCategories;
}
List<dynamic> _serializeCategories(List<dynamic> categories) {
List<dynamic> serializedCategories = [];
for (dynamic category in categories) {
if (category is String) {
serializedCategories.add(category);
} else if (category is TracingCategory) {
serializedCategories.add(category.toNativeValue());
}
}
return serializedCategories;
}
///Class that represents the settings used to configure the [TracingController].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingConfig](https://developer.android.com/reference/androidx/webkit/TracingConfig))
@ExchangeableObject(copyMethod: true)
class TracingSettings_ {
///Adds predefined [TracingCategory] and/or custom [String] sets of categories to be included in the trace output.
///
///Note that the categories are defined by the currently-in-use version of WebView.
///They live in chromium code and are not part of the Android API.
///See [chromium documentation on tracing](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool)
///for more details.
///
///A category pattern can contain wildcards, e.g. `"blink*"` or full category name e.g. `"renderer.scheduler"`.
@ExchangeableObjectProperty(
deserializer: _deserializeCategories, serializer: _serializeCategories)
List<dynamic> categories;
///The tracing mode for this configuration.
///When tracingMode is not set explicitly, the default is [TracingMode.RECORD_CONTINUOUSLY].
TracingMode_? tracingMode;
@ExchangeableObjectConstructor()
TracingSettings_({this.categories = const [], this.tracingMode}) {
assert(
this
.categories
.map((e) => e.runtimeType is String || e.runtimeType is TracingCategory)
.contains(false),
"categories must contain only String or TracingCategory items");
}
}

View File

@ -0,0 +1,71 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tracing_controller.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents the settings used to configure the [TracingController].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - TracingConfig](https://developer.android.com/reference/androidx/webkit/TracingConfig))
class TracingSettings {
///Adds predefined [TracingCategory] and/or custom [String] sets of categories to be included in the trace output.
///
///Note that the categories are defined by the currently-in-use version of WebView.
///They live in chromium code and are not part of the Android API.
///See [chromium documentation on tracing](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool)
///for more details.
///
///A category pattern can contain wildcards, e.g. `"blink*"` or full category name e.g. `"renderer.scheduler"`.
List<dynamic> categories;
///The tracing mode for this configuration.
///When tracingMode is not set explicitly, the default is [TracingMode.RECORD_CONTINUOUSLY].
TracingMode? tracingMode;
TracingSettings({this.categories = const [], this.tracingMode}) {
assert(
this
.categories
.map((e) =>
e.runtimeType is String || e.runtimeType is TracingCategory)
.contains(false),
"categories must contain only String or TracingCategory items");
}
///Gets a possible [TracingSettings] instance from a [Map] value.
static TracingSettings? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = TracingSettings(
tracingMode: TracingMode.fromNativeValue(map['tracingMode']),
);
instance.categories = _deserializeCategories(map['categories']);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"categories": _serializeCategories(categories),
"tracingMode": tracingMode?.toNativeValue(),
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
///Returns a copy of TracingSettings.
TracingSettings copy() {
return TracingSettings.fromMap(toMap()) ?? TracingSettings();
}
@override
String toString() {
return 'TracingSettings{categories: $categories, tracingMode: $tracingMode}';
}
}

View File

@ -969,9 +969,8 @@ class InAppBrowser {
///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied.
///
///[origin] represents the origin of the web page which is trying to access the restricted resources.
///
///[resources] represents the array of resources the web content wants to access.
///[permissionRequest] represents the permission request with an array of resources the web content wants to access
///and the origin of the web page which is trying to access the restricted resources.
///
///**NOTE for Android**: available only on Android 23+.
///
@ -1197,6 +1196,25 @@ class InAppBrowser {
///- Android native WebView ([Official API - WebViewClient.onReceivedLoginRequest](https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedLoginRequest(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String)))
void onReceivedLoginRequest(LoginRequest loginRequest) {}
///Notify the host application that the given permission request has been canceled. Any related UI should therefore be hidden.
///
///[permissionRequest] represents the permission request that needs be canceled
///with an array of resources the web content wants to access
///and the origin of the web page which is trying to access the restricted resources.
///
///**NOTE for Android**: available only on Android 21+.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebChromeClient.onPermissionRequestCanceled](https://developer.android.com/reference/android/webkit/WebChromeClient#onPermissionRequestCanceled(android.webkit.PermissionRequest)))
void onPermissionRequestCanceled(PermissionRequest permissionRequest) {}
///Request display and focus for this WebView.
///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebChromeClient.onRequestFocus](https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView)))
void onRequestFocus() {}
///Use [onWebContentProcessDidTerminate] instead.
@Deprecated('Use onWebContentProcessDidTerminate instead')
void iosOnWebContentProcessDidTerminate() {}
@ -1277,6 +1295,7 @@ class InAppBrowser {
MediaCaptureState? newState,
) {}
///Event fired when a change in the microphone capture state occurred.
///Event fired when a change in the microphone capture state occurred.
///
///**NOTE for iOS**: available only on iOS 15.0+.
@ -1291,6 +1310,16 @@ class InAppBrowser {
MediaCaptureState? newState,
) {}
///Event fired when the content size of the [WebView] changes.
///
///[oldContentSize] represents the old content size value.
///
///[newContentSize] represents the new content size value.
///
///**Supported Platforms/Implementations**:
///- iOS
void onContentSizeChanged(Size oldContentSize, Size newContentSize) {}
void throwIfAlreadyOpened({String message = ''}) {
if (this.isOpened()) {
throw InAppBrowserAlreadyOpenedException([

View File

@ -52,13 +52,14 @@ class HeadlessInAppWebView implements WebView, Disposable {
///**NOTE for Android**: `Size` width and height values will be converted to `int` values because they cannot have `double` values.
final Size initialSize;
HeadlessInAppWebView({
this.initialSize = const Size(-1, -1),
HeadlessInAppWebView(
{this.initialSize = const Size(-1, -1),
this.windowId,
this.initialUrlRequest,
this.initialFile,
this.initialData,
@Deprecated('Use initialSettings instead') this.initialOptions,
@Deprecated('Use initialSettings instead')
this.initialOptions,
this.initialSettings,
this.contextMenu,
this.initialUserScripts,
@ -68,16 +69,19 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
@Deprecated("Use onReceivedError instead") this.onLoadError,
@Deprecated("Use onReceivedError instead")
this.onLoadError,
this.onReceivedError,
@Deprecated("Use onReceivedHttpError instead") this.onLoadHttpError,
@Deprecated("Use onReceivedHttpError instead")
this.onLoadHttpError,
this.onReceivedHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
@Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart,
@Deprecated('Use onDownloadStartRequest instead')
this.onDownloadStart,
this.onDownloadStartRequest,
@Deprecated('Use onLoadResourceWithCustomScheme instead')
this.onLoadResourceCustomScheme,
@ -97,7 +101,8 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
@Deprecated("Use onPrintRequest instead") this.onPrint,
@Deprecated("Use onPrintRequest instead")
this.onPrint,
this.onPrintRequest,
this.onLongPressHitTestResult,
this.onEnterFullscreen,
@ -107,7 +112,8 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.onWindowFocus,
this.onWindowBlur,
this.onOverScrolled,
@Deprecated('Use onSafeBrowsingHit instead') this.androidOnSafeBrowsingHit,
@Deprecated('Use onSafeBrowsingHit instead')
this.androidOnSafeBrowsingHit,
this.onSafeBrowsingHit,
@Deprecated('Use onPermissionRequest instead')
this.androidOnPermissionRequest,
@ -133,17 +139,22 @@ class HeadlessInAppWebView implements WebView, Disposable {
@Deprecated('Use onFormResubmission instead')
this.androidOnFormResubmission,
this.onFormResubmission,
@Deprecated('Use onZoomScaleChanged instead') this.androidOnScaleChanged,
@Deprecated('Use onReceivedIcon instead') this.androidOnReceivedIcon,
@Deprecated('Use onZoomScaleChanged instead')
this.androidOnScaleChanged,
@Deprecated('Use onReceivedIcon instead')
this.androidOnReceivedIcon,
this.onReceivedIcon,
@Deprecated('Use onReceivedTouchIconUrl instead')
this.androidOnReceivedTouchIconUrl,
this.onReceivedTouchIconUrl,
@Deprecated('Use onJsBeforeUnload instead') this.androidOnJsBeforeUnload,
@Deprecated('Use onJsBeforeUnload instead')
this.androidOnJsBeforeUnload,
this.onJsBeforeUnload,
@Deprecated('Use onReceivedLoginRequest instead')
this.androidOnReceivedLoginRequest,
this.onReceivedLoginRequest,
this.onPermissionRequestCanceled,
this.onRequestFocus,
@Deprecated('Use onWebContentProcessDidTerminate instead')
this.iosOnWebContentProcessDidTerminate,
this.onWebContentProcessDidTerminate,
@ -158,7 +169,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.shouldAllowDeprecatedTLS,
this.onCameraCaptureStateChanged,
this.onMicrophoneCaptureStateChanged,
}) {
this.onContentSizeChanged}) {
id = IdGenerator.generate();
webViewController = new InAppWebViewController(id, this);
this._channel =
@ -668,6 +679,14 @@ class HeadlessInAppWebView implements WebView, Disposable {
void Function(InAppWebViewController controller, LoginRequest loginRequest)?
onReceivedLoginRequest;
@override
final void Function(InAppWebViewController controller,
PermissionRequest permissionRequest)? onPermissionRequestCanceled;
@override
final void Function(InAppWebViewController controller)?
onRequestFocus;
@override
void Function(
InAppWebViewController controller, WebUri url, bool precomposed)?
@ -719,6 +738,10 @@ class HeadlessInAppWebView implements WebView, Disposable {
MediaCaptureState? oldState,
MediaCaptureState? newState,
)? onMicrophoneCaptureStateChanged;
@override
final void Function(InAppWebViewController controller, Size oldContentSize,
Size newContentSize)? onContentSizeChanged;
}
extension InternalHeadlessInAppWebView on HeadlessInAppWebView {

View File

@ -149,6 +149,8 @@ class InAppWebView extends StatefulWidget implements WebView {
@Deprecated('Use onReceivedLoginRequest instead')
this.androidOnReceivedLoginRequest,
this.onReceivedLoginRequest,
this.onPermissionRequestCanceled,
this.onRequestFocus,
@Deprecated('Use onWebContentProcessDidTerminate instead')
this.iosOnWebContentProcessDidTerminate,
this.onWebContentProcessDidTerminate,
@ -163,6 +165,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.shouldAllowDeprecatedTLS,
this.onCameraCaptureStateChanged,
this.onMicrophoneCaptureStateChanged,
this.onContentSizeChanged,
this.gestureRecognizers,
this.headlessWebView,
}) : super(key: key);
@ -550,6 +553,14 @@ class InAppWebView extends StatefulWidget implements WebView {
InAppWebViewController controller, LoginRequest loginRequest)?
onReceivedLoginRequest;
@override
final void Function(InAppWebViewController controller,
PermissionRequest permissionRequest)? onPermissionRequestCanceled;
@override
final void Function(InAppWebViewController controller)?
onRequestFocus;
@override
final void Function(
InAppWebViewController controller, WebUri url, bool precomposed)?
@ -603,6 +614,10 @@ class InAppWebView extends StatefulWidget implements WebView {
MediaCaptureState? oldState,
MediaCaptureState? newState,
)? onMicrophoneCaptureStateChanged;
@override
final void Function(InAppWebViewController controller, Size oldContentSize,
Size newContentSize)? onContentSizeChanged;
}
class _InAppWebViewState extends State<InAppWebView> {

View File

@ -735,6 +735,30 @@ class InAppWebViewController {
}
}
break;
case "onPermissionRequestCanceled":
if ((_webview != null &&
_webview!.onPermissionRequestCanceled != null) ||
_inAppBrowser != null) {
Map<String, dynamic> arguments =
call.arguments.cast<String, dynamic>();
PermissionRequest permissionRequest =
PermissionRequest.fromMap(arguments)!;
if (_webview != null && _webview!.onPermissionRequestCanceled != null)
_webview!.onPermissionRequestCanceled!(this, permissionRequest);
else
_inAppBrowser!.onPermissionRequestCanceled(permissionRequest);
}
break;
case "onRequestFocus":
if ((_webview != null && _webview!.onRequestFocus != null) ||
_inAppBrowser != null) {
if (_webview != null && _webview!.onRequestFocus != null)
_webview!.onRequestFocus!(this);
else
_inAppBrowser!.onRequestFocus();
}
break;
case "onReceivedHttpAuthRequest":
if ((_webview != null && _webview!.onReceivedHttpAuthRequest != null) ||
_inAppBrowser != null) {
@ -1188,6 +1212,21 @@ class InAppWebViewController {
_inAppBrowser!.onMicrophoneCaptureStateChanged(oldState, newState);
}
break;
case "onContentSizeChanged":
if ((_webview != null && _webview!.onContentSizeChanged != null) ||
_inAppBrowser != null) {
var oldContentSize = MapSize.fromMap(
call.arguments["oldContentSize"]?.cast<String, dynamic>())!;
var newContentSize = MapSize.fromMap(
call.arguments["newContentSize"]?.cast<String, dynamic>())!;
if (_webview != null && _webview!.onContentSizeChanged != null)
_webview!.onContentSizeChanged!(
this, oldContentSize, newContentSize);
else
_inAppBrowser!.onContentSizeChanged(oldContentSize, newContentSize);
}
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
@ -3697,6 +3736,23 @@ class InAppWebViewController {
return await _staticChannel.invokeMethod('getVariationsHeader', args);
}
///Returns `true` if WebView is running in multi process mode.
///
///In Android O and above, WebView may run in "multiprocess" mode.
///In multiprocess mode, rendering of web content is performed by a sandboxed
///renderer process separate to the application process.
///This renderer process may be shared with other WebViews in the application,
///but is not shared with other application processes.
///
///**NOTE for Android native WebView**: This method should only be called if [WebViewFeature.isFeatureSupported] returns `true` for [WebViewFeature.MULTI_PROCESS].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebViewCompat.isMultiProcessEnabled](https://developer.android.com/reference/androidx/webkit/WebViewCompat#isMultiProcessEnabled()))
static Future<bool> isMultiProcessEnabled() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _staticChannel.invokeMethod('isMultiProcessEnabled', args);
}
///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme.
///
///[urlScheme] represents the URL scheme associated with the resource.

View File

@ -700,8 +700,8 @@ class InAppWebViewSettings_ {
///- Android native WebView
RendererPriorityPolicy_? rendererPriorityPolicy;
///Sets whether the default Android error page should be disabled.
///The default value is `false`.
///Sets whether the default Android WebViews internal error page should be suppressed or displayed for bad navigations.
///`true` means suppressed (not shown), `false` means it will be displayed. The default value is `false`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
@ -739,15 +739,6 @@ class InAppWebViewSettings_ {
///- Android native WebView
Color? horizontalScrollbarTrackColor;
///Sets whether the WebViews internal error page should be suppressed or displayed for bad navigations.
///`true` means suppressed (not shown), `false` means it will be displayed. The default value is `false`.
///
///**NOTE**: available on Android only if [WebViewFeature.SUPPRESS_ERROR_PAGE] feature is supported.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
bool? willSuppressErrorPage;
///Control whether algorithmic darkening is allowed.
///
///WebView always sets the media query `prefers-color-scheme` according to the app's theme attribute `isLightTheme`,
@ -794,6 +785,15 @@ class InAppWebViewSettings_ {
///- Android native WebView
bool? enterpriseAuthenticationAppLinkPolicyEnabled;
///When not playing, video elements are represented by a 'poster' image.
///The image to use can be specified by the poster attribute of the video tag in HTML.
///If the attribute is absent, then a default poster will be used.
///This property allows the WebView to provide that default image.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
Uint8List? defaultVideoPoster;
///Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
///
///**Supported Platforms/Implementations**:
@ -1358,10 +1358,10 @@ class InAppWebViewSettings_ {
this.verticalScrollbarTrackColor,
this.horizontalScrollbarThumbColor,
this.horizontalScrollbarTrackColor,
this.willSuppressErrorPage = false,
this.algorithmicDarkeningAllowed = false,
this.requestedWithHeaderMode,
this.enterpriseAuthenticationAppLinkPolicyEnabled = true,
this.defaultVideoPoster,
this.disallowOverScroll = false,
this.enableViewportScale = false,
this.suppressesIncrementalRendering = false,

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
import 'dart:collection';
import 'dart:typed_data';
import 'dart:ui';
import '../find_interaction/find_interaction_controller.dart';
import '../pull_to_refresh/pull_to_refresh_controller.dart';
@ -612,9 +613,8 @@ abstract class WebView {
///Event fired when the WebView is requesting permission to access the specified resources and the permission currently isn't granted or denied.
///
///[origin] represents the origin of the web page which is trying to access the restricted resources.
///
///[resources] represents the array of resources the web content wants to access.
///[permissionRequest] represents the permission request with an array of resources the web content wants to access
///and the origin of the web page which is trying to access the restricted resources.
///
///**NOTE for Android**: available only on Android 23+.
///
@ -845,6 +845,27 @@ abstract class WebView {
InAppWebViewController controller, LoginRequest loginRequest)?
onReceivedLoginRequest;
///Notify the host application that the given permission request has been canceled. Any related UI should therefore be hidden.
///
///[permissionRequest] represents the permission request that needs be canceled
///with an array of resources the web content wants to access
///and the origin of the web page which is trying to access the restricted resources.
///
///**NOTE for Android**: available only on Android 21+.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebChromeClient.onPermissionRequestCanceled](https://developer.android.com/reference/android/webkit/WebChromeClient#onPermissionRequestCanceled(android.webkit.PermissionRequest)))
final void Function(InAppWebViewController controller,
PermissionRequest permissionRequest)? onPermissionRequestCanceled;
///Request display and focus for this WebView.
///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebChromeClient.onRequestFocus](https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView)))
final void Function(InAppWebViewController controller)?
onRequestFocus;
///Use [onWebContentProcessDidTerminate] instead.
@Deprecated('Use onWebContentProcessDidTerminate instead')
final void Function(InAppWebViewController controller)?
@ -941,6 +962,17 @@ abstract class WebView {
MediaCaptureState? newState,
)? onMicrophoneCaptureStateChanged;
///Event fired when the content size of the [WebView] changes.
///
///[oldContentSize] represents the old content size value.
///
///[newContentSize] represents the new content size value.
///
///**Supported Platforms/Implementations**:
///- iOS
final void Function(InAppWebViewController controller, Size oldContentSize,
Size newContentSize)? onContentSizeChanged;
///Initial url request that will be loaded.
///
///**NOTE for Android**: when loading an URL Request using "POST" method, headers are ignored.
@ -1115,6 +1147,8 @@ abstract class WebView {
@Deprecated('Use onReceivedLoginRequest instead')
this.androidOnReceivedLoginRequest,
this.onReceivedLoginRequest,
this.onPermissionRequestCanceled,
this.onRequestFocus,
@Deprecated('Use onWebContentProcessDidTerminate instead')
this.iosOnWebContentProcessDidTerminate,
this.onWebContentProcessDidTerminate,
@ -1129,6 +1163,7 @@ abstract class WebView {
this.shouldAllowDeprecatedTLS,
this.onCameraCaptureStateChanged,
this.onMicrophoneCaptureStateChanged,
this.onContentSizeChanged,
this.initialUrlRequest,
this.initialFile,
this.initialData,

View File

@ -223,3 +223,5 @@ export 'android_resource.dart' show AndroidResource;
export 'ui_image.dart' show UIImage;
export 'activity_button.dart' show ActivityButton;
export 'ui_event_attribution.dart' show UIEventAttribution;
export 'tracing_mode.dart' show TracingMode;
export 'tracing_category.dart' show TracingCategory;

View File

@ -0,0 +1,43 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'tracing_category.g.dart';
///Constants that describe the results summary the find panel UI includes.
@ExchangeableEnum()
class TracingCategory_ {
// ignore: unused_field
final int _value;
const TracingCategory_._internal(this._value);
///Predefined set of categories, includes all categories enabled by default in chromium.
///Use with caution: this setting may produce large trace output.
static const CATEGORIES_ALL = const TracingCategory_._internal(1);
///Predefined set of categories typically useful for analyzing WebViews.
///Typically includes "android_webview" and "Java" categories.
static const CATEGORIES_ANDROID_WEBVIEW = const TracingCategory_._internal(2);
///Predefined set of categories for studying difficult rendering performance problems.
///Typically includes "blink", "compositor", "gpu", "renderer.scheduler", "v8"
///and some other compositor categories which are disabled by default.
static const CATEGORIES_FRAME_VIEWER = const TracingCategory_._internal(64);
///Predefined set of categories for analyzing input latency issues.
///Typically includes "input", "renderer.scheduler" categories.
static const CATEGORIES_INPUT_LATENCY = const TracingCategory_._internal(8);
///Predefined set of categories for analyzing javascript and rendering issues.
///Typically includes "blink", "compositor", "gpu", "renderer.scheduler" and "v8" categories.
static const CATEGORIES_JAVASCRIPT_AND_RENDERING = const TracingCategory_._internal(32);
///Indicates that there are no predefined categories.
static const CATEGORIES_NONE = const TracingCategory_._internal(0);
///Predefined set of categories for analyzing rendering issues.
///Typically includes "blink", "compositor" and "gpu" categories.
static const CATEGORIES_RENDERING = const TracingCategory_._internal(16);
///Predefined set of categories typically useful for web developers.
///Typically includes "blink", "compositor", "renderer.scheduler" and "v8" categories.
static const CATEGORIES_WEB_DEVELOPER = const TracingCategory_._internal(4);
}

View File

@ -0,0 +1,124 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tracing_category.dart';
// **************************************************************************
// ExchangeableEnumGenerator
// **************************************************************************
///Constants that describe the results summary the find panel UI includes.
class TracingCategory {
final int _value;
final int _nativeValue;
const TracingCategory._internal(this._value, this._nativeValue);
// ignore: unused_element
factory TracingCategory._internalMultiPlatform(
int value, Function nativeValue) =>
TracingCategory._internal(value, nativeValue());
///Predefined set of categories, includes all categories enabled by default in chromium.
///Use with caution: this setting may produce large trace output.
static const CATEGORIES_ALL = TracingCategory._internal(1, 1);
///Predefined set of categories typically useful for analyzing WebViews.
///Typically includes "android_webview" and "Java" categories.
static const CATEGORIES_ANDROID_WEBVIEW = TracingCategory._internal(2, 2);
///Predefined set of categories for studying difficult rendering performance problems.
///Typically includes "blink", "compositor", "gpu", "renderer.scheduler", "v8"
///and some other compositor categories which are disabled by default.
static const CATEGORIES_FRAME_VIEWER = TracingCategory._internal(64, 64);
///Predefined set of categories for analyzing input latency issues.
///Typically includes "input", "renderer.scheduler" categories.
static const CATEGORIES_INPUT_LATENCY = TracingCategory._internal(8, 8);
///Predefined set of categories for analyzing javascript and rendering issues.
///Typically includes "blink", "compositor", "gpu", "renderer.scheduler" and "v8" categories.
static const CATEGORIES_JAVASCRIPT_AND_RENDERING =
TracingCategory._internal(32, 32);
///Indicates that there are no predefined categories.
static const CATEGORIES_NONE = TracingCategory._internal(0, 0);
///Predefined set of categories for analyzing rendering issues.
///Typically includes "blink", "compositor" and "gpu" categories.
static const CATEGORIES_RENDERING = TracingCategory._internal(16, 16);
///Predefined set of categories typically useful for web developers.
///Typically includes "blink", "compositor", "renderer.scheduler" and "v8" categories.
static const CATEGORIES_WEB_DEVELOPER = TracingCategory._internal(4, 4);
///Set of all values of [TracingCategory].
static final Set<TracingCategory> values = [
TracingCategory.CATEGORIES_ALL,
TracingCategory.CATEGORIES_ANDROID_WEBVIEW,
TracingCategory.CATEGORIES_FRAME_VIEWER,
TracingCategory.CATEGORIES_INPUT_LATENCY,
TracingCategory.CATEGORIES_JAVASCRIPT_AND_RENDERING,
TracingCategory.CATEGORIES_NONE,
TracingCategory.CATEGORIES_RENDERING,
TracingCategory.CATEGORIES_WEB_DEVELOPER,
].toSet();
///Gets a possible [TracingCategory] instance from [int] value.
static TracingCategory? fromValue(int? value) {
if (value != null) {
try {
return TracingCategory.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets a possible [TracingCategory] instance from a native value.
static TracingCategory? fromNativeValue(int? value) {
if (value != null) {
try {
return TracingCategory.values
.firstWhere((element) => element.toNativeValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets [int] value.
int toValue() => _value;
///Gets [int] native value.
int toNativeValue() => _nativeValue;
@override
int get hashCode => _value.hashCode;
@override
bool operator ==(value) => value == _value;
@override
String toString() {
switch (_value) {
case 1:
return 'CATEGORIES_ALL';
case 2:
return 'CATEGORIES_ANDROID_WEBVIEW';
case 64:
return 'CATEGORIES_FRAME_VIEWER';
case 8:
return 'CATEGORIES_INPUT_LATENCY';
case 32:
return 'CATEGORIES_JAVASCRIPT_AND_RENDERING';
case 0:
return 'CATEGORIES_NONE';
case 16:
return 'CATEGORIES_RENDERING';
case 4:
return 'CATEGORIES_WEB_DEVELOPER';
}
return _value.toString();
}
}

View File

@ -0,0 +1,23 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'tracing_mode.g.dart';
///Constants that describe the results summary the find panel UI includes.
@ExchangeableEnum()
class TracingMode_ {
// ignore: unused_field
final int _value;
const TracingMode_._internal(this._value);
///Record trace events until the internal tracing buffer is full.
///Typically the buffer memory usage is larger than [RECORD_CONTINUOUSLY].
///Depending on the implementation typically allows up to 256k events to be stored.
static const RECORD_UNTIL_FULL = const TracingMode_._internal(0);
///Record trace events continuously using an internal ring buffer.
///Default tracing mode.
///Overwrites old events if they exceed buffer capacity.
///Uses less memory than the [RECORD_UNTIL_FULL] mode.
///Depending on the implementation typically allows up to 64k events to be stored.
static const RECORD_CONTINUOUSLY = const TracingMode_._internal(1);
}

View File

@ -0,0 +1,84 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tracing_mode.dart';
// **************************************************************************
// ExchangeableEnumGenerator
// **************************************************************************
///Constants that describe the results summary the find panel UI includes.
class TracingMode {
final int _value;
final int _nativeValue;
const TracingMode._internal(this._value, this._nativeValue);
// ignore: unused_element
factory TracingMode._internalMultiPlatform(int value, Function nativeValue) =>
TracingMode._internal(value, nativeValue());
///Record trace events until the internal tracing buffer is full.
///Typically the buffer memory usage is larger than [RECORD_CONTINUOUSLY].
///Depending on the implementation typically allows up to 256k events to be stored.
static const RECORD_UNTIL_FULL = TracingMode._internal(0, 0);
///Record trace events continuously using an internal ring buffer.
///Default tracing mode.
///Overwrites old events if they exceed buffer capacity.
///Uses less memory than the [RECORD_UNTIL_FULL] mode.
///Depending on the implementation typically allows up to 64k events to be stored.
static const RECORD_CONTINUOUSLY = TracingMode._internal(1, 1);
///Set of all values of [TracingMode].
static final Set<TracingMode> values = [
TracingMode.RECORD_UNTIL_FULL,
TracingMode.RECORD_CONTINUOUSLY,
].toSet();
///Gets a possible [TracingMode] instance from [int] value.
static TracingMode? fromValue(int? value) {
if (value != null) {
try {
return TracingMode.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets a possible [TracingMode] instance from a native value.
static TracingMode? fromNativeValue(int? value) {
if (value != null) {
try {
return TracingMode.values
.firstWhere((element) => element.toNativeValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets [int] value.
int toValue() => _value;
///Gets [int] native value.
int toNativeValue() => _nativeValue;
@override
int get hashCode => _value.hashCode;
@override
bool operator ==(value) => value == _value;
@override
String toString() {
switch (_value) {
case 0:
return 'RECORD_UNTIL_FULL';
case 1:
return 'RECORD_CONTINUOUSLY';
}
return _value.toString();
}
}

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 6.0.0-beta.11
version: 6.0.0-beta.12
homepage: https://inappwebview.dev/
repository: https://github.com/pichillilorenzo/flutter_inappwebview
issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues