@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.
@@ -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) {
@@ -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
- syncDialogBackCallbackEnabled();
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
- syncDialogBackCallbackEnabled();
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
- syncDialogBackCallbackEnabled();
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
- syncDialogBackCallbackEnabled();
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
- setCancelable(true);
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
- syncDialogBackCallbackEnabled();
2947
+ syncBackNavigationHandlers();
2930
2948
  }
2931
2949
 
2932
- private void syncDialogBackCallbackEnabled() {
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
- syncDialogBackCallbackEnabled();
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
- syncDialogBackCallbackEnabled();
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
- syncDialogBackCallbackEnabled();
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.4"
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
- return _capgo_handlesURLScheme(urlScheme)
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]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.16.4",
3
+ "version": "8.16.6",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",