@capgo/capacitor-inappbrowser 8.15.3 → 8.16.0

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/README.md CHANGED
@@ -1584,7 +1584,9 @@ See the README section "openSecureWindow (OAuth)" for full setup examples.
1584
1584
  | **`jsInterface`** | | JavaScript Interface: The webview automatically injects a JavaScript interface providing: - `window.mobileApp.close()`: Closes the webview from JavaScript - `window.mobileApp.postMessage(obj)`: Sends a message to the app (listen via "messageFromWebview" event) - `window.mobileApp.hide()` / `window.mobileApp.show()` when allowWebViewJsVisibilityControl is true in CapacitorConfig - `window.mobileApp.takeScreenshot()` when `allowScreenshotsFromWebPage` is true | | 6.10.0 |
1585
1585
  | **`allowScreenshotsFromWebPage`** | <code>boolean</code> | Allows page JavaScript to call `window.mobileApp.takeScreenshot()`. Disabled by default so only the host app can trigger native screenshots through the plugin API. | <code>false</code> | 8.4.0 |
1586
1586
  | **`captureConsoleLogs`** | <code>boolean</code> | Emits `consoleMessage` events for JavaScript `console.*` output coming from the managed page. Useful when the webview stays hidden and you still need page-level diagnostics. | <code>false</code> | 8.6.0 |
1587
- | **`persistWebViewData`** | <code>boolean</code> | Controls whether the webview should persist website data such as cache, cookies, local storage, IndexedDB, and session data. When false, iOS uses a non-persistent `WKWebsiteDataStore`. Android disables per-view cache and DOM/database storage where the system WebView supports it. Android cookies use the shared WebView cookie store; clearing them also affects the host WebView, so prefer per-URL cookie helpers instead of relying on process-global wipes. | <code>true</code> | 8.6.36 |
1587
+ | **`persistWebViewData`** | <code>boolean</code> | Controls whether the webview should persist website data such as cache, cookies, local storage, IndexedDB, and session data. When false, iOS uses a non-persistent `WKWebsiteDataStore`. Android disables per-view cache and database storage where the system WebView supports it while keeping DOM storage available so SPAs can use `localStorage`. Android cookies use the shared WebView cookie store; clearing them also affects the host WebView, so prefer per-URL cookie helpers instead of relying on process-global wipes. | <code>true</code> | 8.6.36 |
1588
+ | **`clearCookiesOnOpen`** | <code>boolean</code> | Clear all cookies from the InAppBrowser data store before the first navigation of this webview. Cordova `clearcache: 'yes'` parity. On Android, uses process-global `CookieManager.removeAllCookies()` and blocks until the wipe completes. On iOS, clears cookies from the webview's `WKWebsiteDataStore` (isolated plugin store by default). On Web this is a no-op. | <code>false</code> | 8.16.0 |
1589
+ | **`clearCacheOnOpen`** | <code>boolean</code> | Clear cached website data before the first navigation of this webview. Cordova `cleardata: 'yes'` parity. On Android, calls `WebView.clearCache(true)` after the webview is created. On iOS, removes disk and memory cache from the webview's `WKWebsiteDataStore`. On Web this is a no-op. | <code>false</code> | 8.16.0 |
1588
1590
  | **`useSharedDataStore`** | <code>boolean</code> | Share the host Capacitor WebView's website data store (cookies, local storage, etc.). On iOS 17+, InAppBrowser uses an isolated plugin-owned `WKWebsiteDataStore` by default so host and browser data stay separate. Set this to `true` to use `WKWebsiteDataStore.default()` instead, which shares session cookies with the Capacitor host WebView. Useful for SSO / OIDC silent login when the IdP session was established in the main app WebView. Requires `persistWebViewData: true` (the default). When `persistWebViewData` is false, a non-persistent store is used and this option has no effect. On Android this is a no-op: cookies are already process-global via `CookieManager`. On Web this is a no-op. Warning: clearing cookies/cache for a webview opened with this flag can affect the host WebView on iOS, because both share the same store. `clearAllBrowsingData()` still skips the host store. | <code>false</code> | 8.13.6 |
1589
1591
  | **`clientCertificate`** | <code>'none' \| 'prompt'</code> | Controls Android TLS client certificate prompts during HTTPS handshakes. Use `prompt` to show the system certificate picker; omit or use `none` to cancel silently (default). | <code>"none"</code> | 8.7.5 |
1590
1592
  | **`clientCertificatePrompt`** | <code>boolean</code> | | | |
@@ -20,7 +20,6 @@ import android.util.Log;
20
20
  import android.webkit.CookieManager;
21
21
  import android.webkit.PermissionRequest;
22
22
  import android.webkit.WebView;
23
- import androidx.activity.result.ActivityResult;
24
23
  import androidx.activity.result.ActivityResultLauncher;
25
24
  import androidx.activity.result.contract.ActivityResultContracts;
26
25
  import androidx.annotation.NonNull;
@@ -63,8 +62,7 @@ import org.json.JSONObject;
63
62
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA }),
64
63
  @Permission(alias = "microphone", strings = { Manifest.permission.RECORD_AUDIO }),
65
64
  @Permission(alias = "storage", strings = { Manifest.permission.READ_EXTERNAL_STORAGE })
66
- },
67
- requestCodes = { WebViewDialog.FILE_CHOOSER_REQUEST_CODE }
65
+ }
68
66
  )
69
67
  public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
70
68
 
@@ -108,7 +106,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
108
106
  private boolean currentPermissionNeedsCamera = false;
109
107
  private boolean currentPermissionNeedsMicrophone = false;
110
108
 
111
- private ActivityResultLauncher<Intent> fileChooserLauncher;
112
109
  private ActivityResultLauncher<String[]> webPermissionLauncher;
113
110
  private PluginCall pendingCameraPermissionCall;
114
111
 
@@ -118,10 +115,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
118
115
  @Override
119
116
  public void load() {
120
117
  super.load();
121
- fileChooserLauncher = getActivity().registerForActivityResult(
122
- new ActivityResultContracts.StartActivityForResult(),
123
- this::handleFileChooserResult
124
- );
125
118
  webPermissionLauncher = getActivity().registerForActivityResult(
126
119
  new ActivityResultContracts.RequestMultiplePermissions(),
127
120
  this::handleWebPermissionResult
@@ -355,6 +348,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
355
348
  );
356
349
  dialog.setInstanceId(webViewId);
357
350
  dialog.activity = CapgoInAppBrowserPlugin.this.getActivity();
351
+ dialog.bindToHostActivity();
358
352
  registerWebView(webViewId, dialog, makeActive);
359
353
  return dialog;
360
354
  }
@@ -525,59 +519,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
525
519
  return targetWebViews;
526
520
  }
527
521
 
528
- private WebViewDialog findFileChooserDialog() {
529
- if (activeWebViewId != null) {
530
- WebViewDialog dialog = webViewDialogs.get(activeWebViewId);
531
- if (dialog != null && dialog.mFilePathCallback != null) {
532
- return dialog;
533
- }
534
- }
535
-
536
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
537
- return webViewDialog;
538
- }
539
-
540
- for (WebViewDialog dialog : webViewDialogs.values()) {
541
- if (dialog != null && dialog.mFilePathCallback != null) {
542
- return dialog;
543
- }
544
- }
545
-
546
- return null;
547
- }
548
-
549
- private void handleFileChooserResult(ActivityResult result) {
550
- WebViewDialog webViewDialog = findFileChooserDialog();
551
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
552
- Uri[] results = null;
553
- Intent data = result.getData();
554
-
555
- if (result.getResultCode() == Activity.RESULT_OK) {
556
- // Handle camera capture result
557
- if (webViewDialog.tempCameraUri != null && (data == null || data.getData() == null)) {
558
- results = new Uri[] { webViewDialog.tempCameraUri };
559
- }
560
- // Handle regular file picker result
561
- else if (data != null) {
562
- if (data.getClipData() != null) {
563
- // Handle multiple files
564
- int count = data.getClipData().getItemCount();
565
- results = new Uri[count];
566
- for (int i = 0; i < count; i++) {
567
- results[i] = data.getClipData().getItemAt(i).getUri();
568
- }
569
- } else if (data.getData() != null) {
570
- // Handle single file
571
- results = new Uri[] { data.getData() };
572
- }
573
- }
574
- }
575
-
576
- // Send the result to WebView and clean up
577
- webViewDialog.completeLegacyFileChooserResult(results);
578
- }
579
- }
580
-
581
522
  public void handleMicrophonePermissionRequest(PermissionRequest request) {
582
523
  if (request == null) {
583
524
  return;
@@ -796,6 +737,16 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
796
737
  );
797
738
  }
798
739
 
740
+ private CustomTabColorSchemeParams buildCustomTabColorScheme(int colorInt) {
741
+ return applyCustomTabNavigationBarColor(new CustomTabColorSchemeParams.Builder().setToolbarColor(colorInt), colorInt).build();
742
+ }
743
+
744
+ @SuppressWarnings("deprecation")
745
+ private CustomTabColorSchemeParams.Builder applyCustomTabNavigationBarColor(CustomTabColorSchemeParams.Builder builder, int colorInt) {
746
+ // androidx.browser 1.9.0 still exposes only setNavigationBarColor; no non-deprecated replacement.
747
+ return builder.setNavigationBarColor(colorInt);
748
+ }
749
+
799
750
  @PluginMethod
800
751
  public void open(PluginCall call) {
801
752
  String url = call.getString("url");
@@ -828,10 +779,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
828
779
  if (toolbarColor != null) {
829
780
  try {
830
781
  int colorInt = Color.parseColor(toolbarColor);
831
- CustomTabColorSchemeParams colorParams = new CustomTabColorSchemeParams.Builder()
832
- .setToolbarColor(colorInt)
833
- .setNavigationBarColor(colorInt)
834
- .build();
782
+ CustomTabColorSchemeParams colorParams = buildCustomTabColorScheme(colorInt);
835
783
  builder.setDefaultColorSchemeParams(colorParams);
836
784
  builder.setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, colorParams);
837
785
  } catch (IllegalArgumentException e) {
@@ -1152,6 +1100,8 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
1152
1100
  options.setHandleDownloads(Boolean.TRUE.equals(call.getBoolean("handleDownloads", false)));
1153
1101
 
1154
1102
  options.setPersistWebViewData(call.getBoolean("persistWebViewData", true));
1103
+ options.setClearCookiesOnOpen(Boolean.TRUE.equals(call.getBoolean("clearCookiesOnOpen", false)));
1104
+ options.setClearCacheOnOpen(Boolean.TRUE.equals(call.getBoolean("clearCacheOnOpen", false)));
1155
1105
  // No-op on Android: CookieManager is already process-global (API parity with iOS).
1156
1106
  options.setUseSharedDataStore(Boolean.TRUE.equals(call.getBoolean("useSharedDataStore", false)));
1157
1107
  // Set text zoom if specified in options (default is 100)
@@ -11,7 +11,6 @@ import com.getcapacitor.annotation.Permission;
11
11
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA }),
12
12
  @Permission(alias = "microphone", strings = { Manifest.permission.RECORD_AUDIO }),
13
13
  @Permission(alias = "storage", strings = { Manifest.permission.READ_EXTERNAL_STORAGE })
14
- },
15
- requestCodes = { WebViewDialog.FILE_CHOOSER_REQUEST_CODE }
14
+ }
16
15
  )
17
16
  public class InAppBrowserPlugin extends CapgoInAppBrowserPlugin {}
@@ -0,0 +1,40 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.webkit.CookieManager;
4
+ import android.webkit.WebView;
5
+ import java.util.concurrent.CountDownLatch;
6
+ import java.util.concurrent.TimeUnit;
7
+
8
+ /**
9
+ * Applies cordova-style open-time cookie/cache clearing before the first navigation.
10
+ */
11
+ final class OpenTimeBrowsingDataClearSupport {
12
+
13
+ private static final long COOKIE_CLEAR_TIMEOUT_SECONDS = 10;
14
+
15
+ private OpenTimeBrowsingDataClearSupport() {}
16
+
17
+ static void applyBeforeFirstNavigation(
18
+ CookieManager cookieManager,
19
+ WebView webView,
20
+ boolean clearCookiesOnOpen,
21
+ boolean clearCacheOnOpen
22
+ ) {
23
+ if (clearCookiesOnOpen) {
24
+ final CountDownLatch latch = new CountDownLatch(1);
25
+ cookieManager.removeAllCookies((value) -> latch.countDown());
26
+ try {
27
+ if (!latch.await(COOKIE_CLEAR_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
28
+ throw new IllegalStateException("Timed out waiting for cookie clear");
29
+ }
30
+ } catch (InterruptedException e) {
31
+ Thread.currentThread().interrupt();
32
+ }
33
+ cookieManager.flush();
34
+ }
35
+
36
+ if (clearCacheOnOpen && webView != null) {
37
+ webView.clearCache(true);
38
+ }
39
+ }
40
+ }
@@ -204,6 +204,8 @@ public class Options {
204
204
  private boolean captureConsoleLogs = false;
205
205
  private boolean handleDownloads = false;
206
206
  private boolean persistWebViewData = true;
207
+ private boolean clearCookiesOnOpen = false;
208
+ private boolean clearCacheOnOpen = false;
207
209
  /** Accepted for API parity with iOS; Android cookies are already process-global. */
208
210
  private boolean useSharedDataStore = false;
209
211
  private InvisibilityMode invisibilityMode = InvisibilityMode.AWARE;
@@ -320,6 +322,22 @@ public class Options {
320
322
  this.persistWebViewData = persistWebViewData;
321
323
  }
322
324
 
325
+ public boolean getClearCookiesOnOpen() {
326
+ return clearCookiesOnOpen;
327
+ }
328
+
329
+ public void setClearCookiesOnOpen(boolean clearCookiesOnOpen) {
330
+ this.clearCookiesOnOpen = clearCookiesOnOpen;
331
+ }
332
+
333
+ public boolean getClearCacheOnOpen() {
334
+ return clearCacheOnOpen;
335
+ }
336
+
337
+ public void setClearCacheOnOpen(boolean clearCacheOnOpen) {
338
+ this.clearCacheOnOpen = clearCacheOnOpen;
339
+ }
340
+
323
341
  public boolean getUseSharedDataStore() {
324
342
  return useSharedDataStore;
325
343
  }
@@ -857,6 +875,8 @@ public class Options {
857
875
  copy.setBlockedHosts(new ArrayList<>(getBlockedHosts()));
858
876
  copy.setWidth(width);
859
877
  copy.setPersistWebViewData(persistWebViewData);
878
+ copy.setClearCookiesOnOpen(clearCookiesOnOpen);
879
+ copy.setClearCacheOnOpen(clearCacheOnOpen);
860
880
  copy.setUseSharedDataStore(useSharedDataStore);
861
881
  copy.setHeight(height);
862
882
  copy.setToBack(toBack);
@@ -122,9 +122,14 @@ final class SafeAreaInsetsSupport {
122
122
  * Whether the WebView container must be inset for the bottom system bar. On Android 15+ the dialog
123
123
  * is forced edge-to-edge (decorFitsSystemWindows=false), so the window always draws under the
124
124
  * navigation bar and insetting is mandatory to keep bottom content on-screen, regardless of the
125
- * enabledSafeBottomMargin opt-in that older versions honour.
125
+ * enabledSafeBottomMargin opt-in that older versions honour. Pre-API 30 windows that lay out behind
126
+ * a transparent navigation bar need the same treatment.
126
127
  */
128
+ static boolean shouldInsetBottomForContainer(boolean enabledSafeBottomMargin, boolean isEdgeToEdge, boolean layoutBehindNavigationBar) {
129
+ return enabledSafeBottomMargin || isEdgeToEdge || layoutBehindNavigationBar;
130
+ }
131
+
127
132
  static boolean shouldInsetBottomForContainer(boolean enabledSafeBottomMargin, boolean isEdgeToEdge) {
128
- return enabledSafeBottomMargin || isEdgeToEdge;
133
+ return shouldInsetBottomForContainer(enabledSafeBottomMargin, isEdgeToEdge, false);
129
134
  }
130
135
  }
@@ -0,0 +1,109 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.graphics.Color;
4
+ import android.os.Build;
5
+ import android.view.View;
6
+ import android.view.Window;
7
+ import android.view.WindowManager;
8
+ import androidx.core.view.WindowCompat;
9
+ import androidx.core.view.WindowInsetsControllerCompat;
10
+
11
+ /**
12
+ * Centralizes window chrome decisions so WebViewDialog can avoid deprecated system UI APIs on
13
+ * Android 15+ while keeping API 24–34 behavior unchanged.
14
+ */
15
+ final class SystemUiChromeSupport {
16
+
17
+ static final int EDGE_TO_EDGE_SDK = Build.VERSION_CODES.VANILLA_ICE_CREAM;
18
+
19
+ private SystemUiChromeSupport() {}
20
+
21
+ static boolean requiresEdgeToEdgeChrome(int sdkInt) {
22
+ return sdkInt >= EDGE_TO_EDGE_SDK;
23
+ }
24
+
25
+ static boolean shouldApplyLegacySystemBarColors(int sdkInt) {
26
+ return sdkInt < EDGE_TO_EDGE_SDK;
27
+ }
28
+
29
+ static boolean shouldUsePreApi30LayoutFlags(int sdkInt) {
30
+ return sdkInt < Build.VERSION_CODES.R;
31
+ }
32
+
33
+ static boolean usesLayoutBehindNavigationBar(int sdkInt, boolean edgeToEdge) {
34
+ return !edgeToEdge && shouldUsePreApi30LayoutFlags(sdkInt);
35
+ }
36
+
37
+ static void setDecorFitsSystemWindows(Window window, boolean decorFitsSystemWindows) {
38
+ if (window == null) {
39
+ return;
40
+ }
41
+ WindowCompat.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
42
+ }
43
+
44
+ static void prepareInitialDialogWindow(Window window) {
45
+ if (window == null) {
46
+ return;
47
+ }
48
+
49
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
50
+ if (shouldApplyLegacySystemBarColors(Build.VERSION.SDK_INT)) {
51
+ applyLegacySystemBarColors(window, Color.TRANSPARENT, null);
52
+ clearLegacyTranslucentStatusFlag(window);
53
+ }
54
+ }
55
+
56
+ static void applyDialogWindowChrome(
57
+ Window window,
58
+ View decorView,
59
+ boolean edgeToEdge,
60
+ Integer statusBarColor,
61
+ Integer navigationBarColor,
62
+ Boolean lightStatusBars
63
+ ) {
64
+ if (window == null || decorView == null) {
65
+ return;
66
+ }
67
+
68
+ if (edgeToEdge) {
69
+ setDecorFitsSystemWindows(window, false);
70
+ } else if (!shouldUsePreApi30LayoutFlags(Build.VERSION.SDK_INT)) {
71
+ setDecorFitsSystemWindows(window, true);
72
+ applyLegacySystemBarColors(window, statusBarColor, navigationBarColor);
73
+ } else {
74
+ applyPreApi30LayoutBehindNavigationBar(decorView);
75
+ applyLegacySystemBarColors(window, statusBarColor, navigationBarColor);
76
+ }
77
+
78
+ WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(window, decorView);
79
+ if (lightStatusBars != null) {
80
+ controller.setAppearanceLightStatusBars(lightStatusBars);
81
+ }
82
+ }
83
+
84
+ @SuppressWarnings("deprecation")
85
+ private static void applyPreApi30LayoutBehindNavigationBar(View decorView) {
86
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
87
+ }
88
+
89
+ @SuppressWarnings("deprecation")
90
+ static void applyLegacySystemBarColors(Window window, Integer statusBarColor, Integer navigationBarColor) {
91
+ if (window == null || !shouldApplyLegacySystemBarColors(Build.VERSION.SDK_INT)) {
92
+ return;
93
+ }
94
+
95
+ if (statusBarColor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
96
+ window.setStatusBarColor(statusBarColor);
97
+ }
98
+ if (navigationBarColor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
99
+ window.setNavigationBarColor(navigationBarColor);
100
+ }
101
+ }
102
+
103
+ @SuppressWarnings("deprecation")
104
+ private static void clearLegacyTranslucentStatusFlag(Window window) {
105
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
106
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
107
+ }
108
+ }
109
+ }