@capgo/capacitor-inappbrowser 8.14.0 → 8.14.2

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.
@@ -76,6 +76,14 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
76
76
  }
77
77
  );
78
78
 
79
+ private static final ExecutorService CUSTOM_TABS_LIFECYCLE_EXECUTOR = Executors.newSingleThreadExecutor((runnable) -> {
80
+ Thread thread = new Thread(runnable, "CapgoInAppBrowser-CustomTabsLifecycle");
81
+ thread.setDaemon(true);
82
+ return thread;
83
+ });
84
+
85
+ private final CustomTabsLifecycleSupport customTabsLifecycleSupport = new CustomTabsLifecycleSupport(CUSTOM_TABS_LIFECYCLE_EXECUTOR);
86
+
79
87
  private final String pluginVersion = "8.6.17";
80
88
 
81
89
  private boolean resolveClientCertificatePrompt(PluginCall call) {
@@ -670,10 +678,16 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
670
678
  currentPermissionNeedsMicrophone = false;
671
679
  }
672
680
 
681
+ private final Object customTabsClientLock = new Object();
682
+ private static final long CUSTOM_TABS_CONNECTION_TIMEOUT_MS = 2000;
683
+
673
684
  CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
674
685
  @Override
675
686
  public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
676
687
  customTabsClient = client;
688
+ synchronized (customTabsClientLock) {
689
+ customTabsClientLock.notifyAll();
690
+ }
677
691
  }
678
692
 
679
693
  @Override
@@ -682,6 +696,27 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
682
696
  }
683
697
  };
684
698
 
699
+ private final CustomTabsLifecycleSupport.Binder customTabsBinder = new CustomTabsLifecycleSupport.Binder() {
700
+ @Override
701
+ public boolean bindCustomTabsService() {
702
+ try {
703
+ boolean ok = CustomTabsClient.bindCustomTabsService(getContext(), CUSTOM_TAB_PACKAGE_NAME, connection);
704
+ if (!ok) {
705
+ Log.e(getLogTag(), "Error binding to custom tabs service");
706
+ }
707
+ return ok;
708
+ } catch (RuntimeException e) {
709
+ Log.e(getLogTag(), "Error binding to custom tabs service", e);
710
+ return false;
711
+ }
712
+ }
713
+
714
+ @Override
715
+ public void unbindCustomTabsService() {
716
+ getContext().unbindService(connection);
717
+ }
718
+ };
719
+
685
720
  @PluginMethod
686
721
  public void requestCameraPermission(PluginCall call) {
687
722
  if (isAndroidPermissionGranted(Manifest.permission.CAMERA)) {
@@ -748,8 +783,10 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
748
783
 
749
784
  if (TextUtils.isEmpty(url)) {
750
785
  call.reject("Invalid URL");
786
+ return;
751
787
  }
752
788
  currentUrl = url;
789
+ awaitCustomTabsClientIfNeeded();
753
790
  CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(getCustomTabsSession());
754
791
 
755
792
  // --- Chrome Custom Tab UI customization ---
@@ -1839,10 +1876,8 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
1839
1876
  }
1840
1877
 
1841
1878
  protected void handleOnResume() {
1842
- boolean ok = CustomTabsClient.bindCustomTabsService(getContext(), CUSTOM_TAB_PACKAGE_NAME, connection);
1843
- if (!ok) {
1844
- Log.e(getLogTag(), "Error binding to custom tabs service");
1845
- }
1879
+ customTabsLifecycleSupport.onResume(customTabsBinder);
1880
+
1846
1881
  // If we have a saved call and user returned without callback, reject
1847
1882
  if (openSecureWindowSavedCall != null) {
1848
1883
  openSecureWindowSavedCall.reject("OAuth cancelled or no callback received");
@@ -1851,7 +1886,29 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
1851
1886
  }
1852
1887
 
1853
1888
  protected void handleOnPause() {
1854
- getContext().unbindService(connection);
1889
+ customTabsLifecycleSupport.onPause(customTabsBinder);
1890
+ }
1891
+
1892
+ private void awaitCustomTabsClientIfNeeded() {
1893
+ if (customTabsClient != null || !customTabsLifecycleSupport.isBindingOrBound()) {
1894
+ return;
1895
+ }
1896
+
1897
+ synchronized (customTabsClientLock) {
1898
+ long deadline = System.currentTimeMillis() + CUSTOM_TABS_CONNECTION_TIMEOUT_MS;
1899
+ while (customTabsClient == null && customTabsLifecycleSupport.isBindingOrBound() && System.currentTimeMillis() < deadline) {
1900
+ long remaining = deadline - System.currentTimeMillis();
1901
+ if (remaining <= 0) {
1902
+ break;
1903
+ }
1904
+ try {
1905
+ customTabsClientLock.wait(remaining);
1906
+ } catch (InterruptedException e) {
1907
+ Thread.currentThread().interrupt();
1908
+ break;
1909
+ }
1910
+ }
1911
+ }
1855
1912
  }
1856
1913
 
1857
1914
  public CustomTabsSession getCustomTabsSession() {
@@ -0,0 +1,84 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import java.util.concurrent.Executor;
4
+
5
+ final class CustomTabsLifecycleSupport {
6
+
7
+ interface Binder {
8
+ boolean bindCustomTabsService();
9
+
10
+ void unbindCustomTabsService();
11
+ }
12
+
13
+ private final Executor executor;
14
+ private final Object lock = new Object();
15
+ private boolean bound = false;
16
+ private boolean bindInProgress = false;
17
+
18
+ CustomTabsLifecycleSupport(Executor executor) {
19
+ this.executor = executor;
20
+ }
21
+
22
+ void onResume(Binder binder) {
23
+ executor.execute(() -> performBind(binder));
24
+ }
25
+
26
+ void onPause(Binder binder) {
27
+ executor.execute(() -> performUnbind(binder));
28
+ }
29
+
30
+ boolean isBindingOrBound() {
31
+ synchronized (lock) {
32
+ return bindInProgress || bound;
33
+ }
34
+ }
35
+
36
+ boolean isBound() {
37
+ synchronized (lock) {
38
+ return bound;
39
+ }
40
+ }
41
+
42
+ private void performBind(Binder binder) {
43
+ synchronized (lock) {
44
+ if (bound || bindInProgress) {
45
+ return;
46
+ }
47
+ bindInProgress = true;
48
+ }
49
+
50
+ boolean ok = false;
51
+ try {
52
+ ok = binder.bindCustomTabsService();
53
+ } catch (RuntimeException ignored) {
54
+ // Caller logs bind failures.
55
+ }
56
+
57
+ synchronized (lock) {
58
+ bindInProgress = false;
59
+ if (!ok) {
60
+ return;
61
+ }
62
+
63
+ bound = true;
64
+ }
65
+ }
66
+
67
+ private void performUnbind(Binder binder) {
68
+ synchronized (lock) {
69
+ if (bindInProgress) {
70
+ return;
71
+ }
72
+ if (!bound) {
73
+ return;
74
+ }
75
+ bound = false;
76
+ }
77
+
78
+ try {
79
+ binder.unbindCustomTabsService();
80
+ } catch (RuntimeException ignored) {
81
+ // Service may already be unbound.
82
+ }
83
+ }
84
+ }
@@ -3013,30 +3013,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3013
3013
  final View insetsSourceView = resolveSafeAreaInsetsSourceView();
3014
3014
 
3015
3015
  ViewCompat.setOnApplyWindowInsetsListener(insetsSourceView, (v, windowInsets) -> {
3016
- Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
3017
- Insets navigationBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
3018
- Insets systemGestures = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
3019
- Insets mandatoryGestures = windowInsets.getInsets(WindowInsetsCompat.Type.mandatorySystemGestures());
3020
- Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
3021
- boolean keyboardVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime());
3022
-
3023
- boolean appBarHandlesTopInset =
3024
- isAndroid15Plus &&
3025
- !TextUtils.equals(_options.getToolbarType(), "blank") &&
3026
- toolbarView != null &&
3027
- toolbarView.getVisibility() == View.VISIBLE &&
3028
- toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout;
3029
- applySafeAreaMargins(
3030
- bars,
3031
- navigationBars,
3032
- systemGestures,
3033
- mandatoryGestures,
3034
- ime,
3035
- keyboardVisible,
3036
- appBarHandlesTopInset,
3037
- isAndroid15Plus
3038
- );
3039
-
3016
+ applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3040
3017
  return windowInsets;
3041
3018
  });
3042
3019
  requestSafeAreaInsets();
@@ -3209,6 +3186,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3209
3186
 
3210
3187
  if (isHiddenModeActive) {
3211
3188
  requestSafeAreaInsets();
3189
+ reapplyInsetsFromWindowRoot();
3212
3190
  return;
3213
3191
  }
3214
3192
 
@@ -3235,21 +3213,24 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3235
3213
  }
3236
3214
 
3237
3215
  if (swipeRefreshLayout != null) {
3216
+ swipeRefreshLayout.setRefreshing(false);
3238
3217
  swipeRefreshLayout.requestLayout();
3239
3218
  }
3240
3219
 
3241
3220
  _webView.requestLayout();
3242
3221
  _webView.invalidate();
3243
3222
  requestSafeAreaInsets();
3223
+ reapplyInsetsFromWindowRoot();
3244
3224
 
3245
- mainHandler.post(this::applyContainerInsetsSnapshot);
3246
- mainHandler.postDelayed(this::applyContainerInsetsSnapshot, 100);
3247
- mainHandler.postDelayed(this::applyContainerInsetsSnapshot, 300);
3225
+ mainHandler.post(this::reapplyInsetsFromWindowRoot);
3226
+ mainHandler.postDelayed(this::reapplyInsetsFromWindowRoot, 100);
3227
+ mainHandler.postDelayed(this::reapplyInsetsFromWindowRoot, 300);
3248
3228
 
3249
3229
  _webView.post(() -> {
3250
3230
  if (_webView == null) {
3251
3231
  return;
3252
3232
  }
3233
+ requestWebViewContentRelayout();
3253
3234
  _webView.evaluateJavascript("(function(){window.dispatchEvent(new Event('resize'));})();", null);
3254
3235
  });
3255
3236
  }
@@ -3340,6 +3321,80 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3340
3321
  });
3341
3322
  }
3342
3323
 
3324
+ private void applyWindowInsetsToWebView(WindowInsetsCompat windowInsets, boolean isAndroid15Plus, View toolbarView) {
3325
+ if (windowInsets == null || _options == null) {
3326
+ return;
3327
+ }
3328
+
3329
+ Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
3330
+ Insets navigationBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
3331
+ Insets systemGestures = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
3332
+ Insets mandatoryGestures = windowInsets.getInsets(WindowInsetsCompat.Type.mandatorySystemGestures());
3333
+ Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
3334
+ boolean keyboardVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime());
3335
+
3336
+ boolean appBarHandlesTopInset =
3337
+ isAndroid15Plus &&
3338
+ !TextUtils.equals(_options.getToolbarType(), "blank") &&
3339
+ toolbarView != null &&
3340
+ toolbarView.getVisibility() == View.VISIBLE &&
3341
+ toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout;
3342
+
3343
+ applySafeAreaMargins(
3344
+ bars,
3345
+ navigationBars,
3346
+ systemGestures,
3347
+ mandatoryGestures,
3348
+ ime,
3349
+ keyboardVisible,
3350
+ appBarHandlesTopInset,
3351
+ isAndroid15Plus
3352
+ );
3353
+ }
3354
+
3355
+ /**
3356
+ * Re-read root window insets and apply WebView margins plus container padding.
3357
+ * Dialog windows may not re-dispatch inset listeners after rotation, leaving stale
3358
+ * portrait margins that break vertical scrolling in landscape.
3359
+ */
3360
+ private void reapplyInsetsFromWindowRoot() {
3361
+ if (_webView == null || _options == null) {
3362
+ return;
3363
+ }
3364
+
3365
+ Window window = getWindow();
3366
+ View decorView = window != null ? window.getDecorView() : null;
3367
+ if (decorView == null) {
3368
+ return;
3369
+ }
3370
+
3371
+ WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);
3372
+ if (windowInsets == null) {
3373
+ // Root insets not ready yet; do not mutate margins/padding (would desync WebView
3374
+ // margins from container padding). Delayed retries and requestApplyInsets will retry.
3375
+ return;
3376
+ }
3377
+
3378
+ boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3379
+ View toolbarView = findViewById(R.id.tool_bar);
3380
+ applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3381
+ }
3382
+
3383
+ private void requestWebViewContentRelayout() {
3384
+ View container = findViewById(R.id.content_browser_layout);
3385
+ if (container != null) {
3386
+ container.requestLayout();
3387
+ container.invalidate();
3388
+ }
3389
+
3390
+ if (_webView == null) {
3391
+ return;
3392
+ }
3393
+
3394
+ _webView.requestLayout();
3395
+ _webView.invalidate();
3396
+ }
3397
+
3343
3398
  private void applySafeAreaMargins(
3344
3399
  Insets bars,
3345
3400
  Insets navigationBars,
@@ -3554,7 +3609,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3554
3609
  ViewCompat.requestApplyInsets(insetsSourceView);
3555
3610
  insetsSourceView.post(() -> {
3556
3611
  ViewCompat.requestApplyInsets(insetsSourceView);
3557
- applyContainerInsetsSnapshot();
3612
+ reapplyInsetsFromWindowRoot();
3558
3613
  });
3559
3614
  }
3560
3615
 
@@ -415,7 +415,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
415
415
  case aware = "AWARE"
416
416
  case fakeVisible = "FAKE_VISIBLE"
417
417
  }
418
- private let pluginVersion: String = "8.14.0"
418
+ private let pluginVersion: String = "8.14.2"
419
419
  public let identifier = "CapgoInAppBrowserPlugin"
420
420
  public let jsName = "CapgoInAppBrowser"
421
421
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.14.0",
3
+ "version": "8.14.2",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",