@capgo/capacitor-inappbrowser 8.16.4 → 8.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +7 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +10 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewBackNavigationSupport.java +35 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +89 -10
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +1 -1
- package/ios/Sources/InAppBrowserPlugin/WKWebView+SchemeHandling.swift +13 -1
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -34,6 +34,11 @@ android {
|
|
|
34
34
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
testOptions {
|
|
38
|
+
unitTests {
|
|
39
|
+
includeAndroidResources = true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
37
42
|
lintOptions {
|
|
38
43
|
abortOnError = false
|
|
39
44
|
}
|
|
@@ -56,6 +61,8 @@ dependencies {
|
|
|
56
61
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
57
62
|
implementation "androidx.window:window:1.5.1"
|
|
58
63
|
testImplementation "junit:junit:$junitVersion"
|
|
64
|
+
testImplementation 'org.robolectric:robolectric:4.14.1'
|
|
65
|
+
testImplementation 'androidx.activity:activity:1.10.1'
|
|
59
66
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
60
67
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
61
68
|
implementation "androidx.browser:browser:$androidxBrowserVersion"
|
|
@@ -428,12 +428,21 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
|
|
|
428
428
|
webViewStack.addLast(id);
|
|
429
429
|
if (makeActive || activeWebViewId == null || webViewDialog == null) {
|
|
430
430
|
setActiveWebView(id, dialog);
|
|
431
|
+
} else {
|
|
432
|
+
dialog.setActiveForBackNavigation(false);
|
|
431
433
|
}
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
private void setActiveWebView(String id, WebViewDialog dialog) {
|
|
435
437
|
activeWebViewId = id;
|
|
436
438
|
webViewDialog = dialog;
|
|
439
|
+
syncActiveWebViewBackNavigation();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private void syncActiveWebViewBackNavigation() {
|
|
443
|
+
for (Map.Entry<String, WebViewDialog> entry : webViewDialogs.entrySet()) {
|
|
444
|
+
entry.getValue().setActiveForBackNavigation(java.util.Objects.equals(entry.getKey(), activeWebViewId));
|
|
445
|
+
}
|
|
437
446
|
}
|
|
438
447
|
|
|
439
448
|
private void unregisterWebView(String id) {
|
|
@@ -441,6 +450,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
|
|
|
441
450
|
webViewStack.remove(id);
|
|
442
451
|
activeWebViewId = webViewStack.peekLast();
|
|
443
452
|
webViewDialog = activeWebViewId != null ? webViewDialogs.get(activeWebViewId) : null;
|
|
453
|
+
syncActiveWebViewBackNavigation();
|
|
444
454
|
if (webViewDialog != null) {
|
|
445
455
|
String url = webViewDialog.getUrl();
|
|
446
456
|
if (url != null) {
|
package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewBackNavigationSupport.java
CHANGED
|
@@ -22,6 +22,41 @@ final class WebViewBackNavigationSupport {
|
|
|
22
22
|
return isShowing && !hiddenMode && !backLayerActive;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* When {@code disableGoBackOnNativeApplication} is true, the dialog must not be cancelable on
|
|
27
|
+
* back/gesture. Otherwise {@link androidx.activity.ComponentDialog}'s built-in back callback
|
|
28
|
+
* dismisses the dialog before our handler can return {@link Action#IGNORE}. While cancelable is
|
|
29
|
+
* false that built-in callback still consumes back without dismissing, so {@link WebViewDialog}
|
|
30
|
+
* must keep its own callback registered last on the dispatcher.
|
|
31
|
+
*/
|
|
32
|
+
static boolean isCancelableOnBack(boolean disableGoBackOnNativeApplication) {
|
|
33
|
+
return !disableGoBackOnNativeApplication;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Activity-level back handling is required when the overlay is hosted on the activity window
|
|
38
|
+
* (back layer) or when predictive-back can bypass the dialog dispatcher while the flag blocks
|
|
39
|
+
* dismiss.
|
|
40
|
+
*/
|
|
41
|
+
static boolean shouldRegisterActivityBackHandler(
|
|
42
|
+
boolean isShowing,
|
|
43
|
+
boolean backLayerActive,
|
|
44
|
+
boolean hiddenMode,
|
|
45
|
+
boolean disableGoBackOnNativeApplication,
|
|
46
|
+
boolean isActiveForBackNavigation
|
|
47
|
+
) {
|
|
48
|
+
if (!isActiveForBackNavigation) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (hiddenMode) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (!isShowing && !backLayerActive) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return backLayerActive || disableGoBackOnNativeApplication;
|
|
58
|
+
}
|
|
59
|
+
|
|
25
60
|
static Action resolveAction(
|
|
26
61
|
boolean customFullscreenActive,
|
|
27
62
|
boolean webViewCanGoBack,
|
|
@@ -345,6 +345,8 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
345
345
|
private Window customFullscreenWindow;
|
|
346
346
|
private OnBackPressedCallback customFullscreenBackCallback;
|
|
347
347
|
private OnBackPressedCallback dialogBackCallback;
|
|
348
|
+
private OnBackPressedCallback dialogActivityBackCallback;
|
|
349
|
+
private boolean activeForBackNavigation = true;
|
|
348
350
|
private Toolbar _toolbar;
|
|
349
351
|
private Options _options = null;
|
|
350
352
|
private final Context _context;
|
|
@@ -461,17 +463,20 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
461
463
|
void bindToHostActivity() {
|
|
462
464
|
ensureFileChooserLaunchers();
|
|
463
465
|
registerDialogBackHandler();
|
|
466
|
+
registerDialogActivityBackHandler();
|
|
464
467
|
}
|
|
465
468
|
|
|
466
469
|
@Override
|
|
467
470
|
protected void onStart() {
|
|
468
471
|
super.onStart();
|
|
469
|
-
|
|
472
|
+
// ComponentDialog registers its own back callback in onCreate; re-add ours last so
|
|
473
|
+
// handleBrowserBackNavigation runs before the built-in cancel callback (LIFO order).
|
|
474
|
+
ensureDialogBackHandlerOnTop();
|
|
470
475
|
}
|
|
471
476
|
|
|
472
477
|
@Override
|
|
473
478
|
protected void onStop() {
|
|
474
|
-
|
|
479
|
+
syncBackNavigationHandlers();
|
|
475
480
|
super.onStop();
|
|
476
481
|
}
|
|
477
482
|
|
|
@@ -670,7 +675,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
670
675
|
backLayerActive = false;
|
|
671
676
|
backLayerParent = null;
|
|
672
677
|
restoreHostTransparency();
|
|
673
|
-
|
|
678
|
+
syncBackNavigationHandlers();
|
|
674
679
|
}
|
|
675
680
|
|
|
676
681
|
private void attachContentToDialogWindow() {
|
|
@@ -713,7 +718,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
713
718
|
super.hide();
|
|
714
719
|
}
|
|
715
720
|
refreshInsetsForHostingLayer();
|
|
716
|
-
|
|
721
|
+
syncBackNavigationHandlers();
|
|
717
722
|
return true;
|
|
718
723
|
}
|
|
719
724
|
|
|
@@ -2145,7 +2150,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2145
2150
|
@SuppressLint({ "SetJavaScriptEnabled", "AddJavascriptInterface" })
|
|
2146
2151
|
public void presentWebView() {
|
|
2147
2152
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
2148
|
-
|
|
2153
|
+
applyBackNavigationPolicy();
|
|
2149
2154
|
setContentView(R.layout.activity_browser);
|
|
2150
2155
|
|
|
2151
2156
|
// If custom dimensions are set, configure for touch passthrough
|
|
@@ -2918,6 +2923,19 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2918
2923
|
if (dialogBackCallback != null) {
|
|
2919
2924
|
return;
|
|
2920
2925
|
}
|
|
2926
|
+
ensureDialogBackHandlerOnTop();
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/**
|
|
2930
|
+
* Re-register the dialog back callback so it sits above {@link ComponentDialog}'s built-in
|
|
2931
|
+
* callback. That callback is always enabled and consumes back without dismissing when
|
|
2932
|
+
* {@link #setCancelable(boolean)} is false, which would block webview history navigation.
|
|
2933
|
+
*/
|
|
2934
|
+
private void ensureDialogBackHandlerOnTop() {
|
|
2935
|
+
if (dialogBackCallback != null) {
|
|
2936
|
+
dialogBackCallback.remove();
|
|
2937
|
+
dialogBackCallback = null;
|
|
2938
|
+
}
|
|
2921
2939
|
// Predictive back (API 33+) never delivers KEYCODE_BACK to Dialog OnKeyListener.
|
|
2922
2940
|
dialogBackCallback = new OnBackPressedCallback(false) {
|
|
2923
2941
|
@Override
|
|
@@ -2926,13 +2944,70 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2926
2944
|
}
|
|
2927
2945
|
};
|
|
2928
2946
|
getOnBackPressedDispatcher().addCallback(dialogBackCallback);
|
|
2929
|
-
|
|
2947
|
+
syncBackNavigationHandlers();
|
|
2930
2948
|
}
|
|
2931
2949
|
|
|
2932
|
-
private void
|
|
2950
|
+
private void registerDialogActivityBackHandler() {
|
|
2951
|
+
if (!(activity instanceof ComponentActivity componentActivity) || dialogActivityBackCallback != null) {
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
dialogActivityBackCallback = new OnBackPressedCallback(false) {
|
|
2955
|
+
@Override
|
|
2956
|
+
public void handleOnBackPressed() {
|
|
2957
|
+
handleBrowserBackNavigation();
|
|
2958
|
+
}
|
|
2959
|
+
};
|
|
2960
|
+
componentActivity.getOnBackPressedDispatcher().addCallback(componentActivity, dialogActivityBackCallback);
|
|
2961
|
+
syncBackNavigationHandlers();
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
void applyBackNavigationPolicy() {
|
|
2965
|
+
boolean disableGoBack = _options != null && _options.getDisableGoBackOnNativeApplication();
|
|
2966
|
+
setCancelable(WebViewBackNavigationSupport.isCancelableOnBack(disableGoBack));
|
|
2967
|
+
if (isShowing()) {
|
|
2968
|
+
ensureDialogBackHandlerOnTop();
|
|
2969
|
+
} else {
|
|
2970
|
+
syncBackNavigationHandlers();
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
private void syncBackNavigationHandlers() {
|
|
2933
2975
|
if (dialogBackCallback != null) {
|
|
2934
2976
|
dialogBackCallback.setEnabled(shouldConsumeBackPress());
|
|
2935
2977
|
}
|
|
2978
|
+
if (dialogActivityBackCallback != null) {
|
|
2979
|
+
dialogActivityBackCallback.setEnabled(shouldRegisterActivityBackHandler());
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
private boolean shouldRegisterActivityBackHandler() {
|
|
2984
|
+
return WebViewBackNavigationSupport.shouldRegisterActivityBackHandler(
|
|
2985
|
+
isShowing(),
|
|
2986
|
+
backLayerActive,
|
|
2987
|
+
isHiddenModeActive,
|
|
2988
|
+
_options != null && _options.getDisableGoBackOnNativeApplication(),
|
|
2989
|
+
activeForBackNavigation
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
void setActiveForBackNavigation(boolean active) {
|
|
2994
|
+
if (activeForBackNavigation == active) {
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
activeForBackNavigation = active;
|
|
2998
|
+
syncBackNavigationHandlers();
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
boolean isActiveForBackNavigation() {
|
|
3002
|
+
return activeForBackNavigation;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
boolean isBackLayerActive() {
|
|
3006
|
+
return backLayerActive;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
boolean isActivityBackHandlerEnabled() {
|
|
3010
|
+
return dialogActivityBackCallback != null && dialogActivityBackCallback.isEnabled();
|
|
2936
3011
|
}
|
|
2937
3012
|
|
|
2938
3013
|
private void unregisterDialogBackHandler() {
|
|
@@ -2940,6 +3015,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2940
3015
|
dialogBackCallback.remove();
|
|
2941
3016
|
dialogBackCallback = null;
|
|
2942
3017
|
}
|
|
3018
|
+
if (dialogActivityBackCallback != null) {
|
|
3019
|
+
dialogActivityBackCallback.remove();
|
|
3020
|
+
dialogActivityBackCallback = null;
|
|
3021
|
+
}
|
|
2943
3022
|
}
|
|
2944
3023
|
|
|
2945
3024
|
private boolean shouldConsumeBackPress() {
|
|
@@ -3146,7 +3225,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3146
3225
|
}
|
|
3147
3226
|
|
|
3148
3227
|
isHiddenModeActive = true;
|
|
3149
|
-
|
|
3228
|
+
syncBackNavigationHandlers();
|
|
3150
3229
|
}
|
|
3151
3230
|
|
|
3152
3231
|
private void restoreVisibleMode() {
|
|
@@ -3184,7 +3263,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3184
3263
|
previousWebViewAlpha = 1f;
|
|
3185
3264
|
previousWebViewVisibility = View.VISIBLE;
|
|
3186
3265
|
isHiddenModeActive = false;
|
|
3187
|
-
|
|
3266
|
+
syncBackNavigationHandlers();
|
|
3188
3267
|
}
|
|
3189
3268
|
|
|
3190
3269
|
public void setHidden(boolean hidden) {
|
|
@@ -3210,7 +3289,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3210
3289
|
// Set flag immediately to prevent race condition if setHidden(false)
|
|
3211
3290
|
// is called before the posted runnable executes
|
|
3212
3291
|
isHiddenModeActive = true;
|
|
3213
|
-
|
|
3292
|
+
syncBackNavigationHandlers();
|
|
3214
3293
|
decorView.post(this::applyHiddenMode);
|
|
3215
3294
|
} catch (Exception e) {
|
|
3216
3295
|
Log.w("InAppBrowser", "Unable to show dialog before hiding", e);
|
|
@@ -446,7 +446,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
446
446
|
case aware = "AWARE"
|
|
447
447
|
case fakeVisible = "FAKE_VISIBLE"
|
|
448
448
|
}
|
|
449
|
-
private let pluginVersion: String = "8.16.
|
|
449
|
+
private let pluginVersion: String = "8.16.6"
|
|
450
450
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
451
451
|
public let jsName = "CapgoInAppBrowser"
|
|
452
452
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -5,6 +5,10 @@ import WebKit
|
|
|
5
5
|
extension WKWebView {
|
|
6
6
|
private static var _overriddenSchemes = Set<String>()
|
|
7
7
|
private static let _overriddenSchemesLock = NSLock()
|
|
8
|
+
private static var _originalHandlesURLSchemeIMP: IMP?
|
|
9
|
+
|
|
10
|
+
// Original +[WKWebView handlesURLScheme:] IMP (saved before swizzle).
|
|
11
|
+
private typealias HandlesURLSchemeIMP = @convention(c) (AnyClass, Selector, NSString) -> Bool
|
|
8
12
|
|
|
9
13
|
private static let _swizzleOnce: Void = {
|
|
10
14
|
let original = class_getClassMethod(WKWebView.self, #selector(WKWebView.handlesURLScheme(_:)))
|
|
@@ -18,6 +22,7 @@ extension WKWebView {
|
|
|
18
22
|
return
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
_originalHandlesURLSchemeIMP = method_getImplementation(original)
|
|
21
26
|
method_exchangeImplementations(original, swizzled)
|
|
22
27
|
}()
|
|
23
28
|
|
|
@@ -28,7 +33,14 @@ extension WKWebView {
|
|
|
28
33
|
_overriddenSchemesLock.unlock()
|
|
29
34
|
|
|
30
35
|
if isOverridden { return false }
|
|
31
|
-
|
|
36
|
+
|
|
37
|
+
guard let imp = _originalHandlesURLSchemeIMP else {
|
|
38
|
+
print("[InAppBrowser][Proxy] WARNING: Original handlesURLScheme IMP missing; assuming not handled")
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let original: HandlesURLSchemeIMP = unsafeBitCast(imp, to: HandlesURLSchemeIMP.self)
|
|
43
|
+
return original(WKWebView.self, #selector(WKWebView.handlesURLScheme(_:)), urlScheme as NSString)
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
static func enableCustomSchemeHandling(for schemes: [String]) {
|