@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.
@@ -69,7 +69,9 @@ import android.widget.RelativeLayout;
69
69
  import android.widget.TextView;
70
70
  import android.widget.Toast;
71
71
  import android.widget.Toolbar;
72
+ import androidx.activity.ComponentActivity;
72
73
  import androidx.activity.result.ActivityResult;
74
+ import androidx.activity.result.ActivityResultLauncher;
73
75
  import androidx.activity.result.contract.ActivityResultContracts;
74
76
  import androidx.coordinatorlayout.widget.CoordinatorLayout;
75
77
  import androidx.core.content.FileProvider;
@@ -409,10 +411,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
409
411
  private boolean preShowInjectedAtDocumentStart = false;
410
412
 
411
413
  public PermissionRequest currentPermissionRequest;
412
- public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
413
414
  public ValueCallback<Uri> mUploadMessage;
414
415
  public ValueCallback<Uri[]> mFilePathCallback;
415
416
  FileChooserRequestSupport.FileChooserRequest activeFileChooserRequest;
417
+ private ActivityResultLauncher<Intent> cameraCaptureLauncher;
418
+ private ActivityResultLauncher<Intent> fileChooserLauncher;
416
419
  private boolean openWebViewResolved;
417
420
  private boolean isDismissing = false;
418
421
  private PermissionRequest pendingCameraLaunchPermissionRequest;
@@ -447,6 +450,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
447
450
  this.instanceId = id != null ? id : "";
448
451
  }
449
452
 
453
+ void bindToHostActivity() {
454
+ ensureFileChooserLaunchers();
455
+ registerDialogBackHandler();
456
+ }
457
+
450
458
  public String getInstanceId() {
451
459
  return instanceId;
452
460
  }
@@ -2116,10 +2124,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2116
2124
  public void presentWebView() {
2117
2125
  requestWindowFeature(Window.FEATURE_NO_TITLE);
2118
2126
  setCancelable(true);
2119
- Objects.requireNonNull(getWindow()).setFlags(
2120
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
2121
- WindowManager.LayoutParams.FLAG_FULLSCREEN
2122
- );
2123
2127
  setContentView(R.layout.activity_browser);
2124
2128
 
2125
2129
  // If custom dimensions are set, configure for touch passthrough
@@ -2143,18 +2147,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2143
2147
  if (appBar != null) appBar.setFitsSystemWindows(true);
2144
2148
  }
2145
2149
 
2146
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
2147
-
2148
- // Make status bar transparent
2149
- if (getWindow() != null) {
2150
- getWindow().setStatusBarColor(Color.TRANSPARENT);
2151
-
2152
- // Add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window
2153
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
2150
+ // Set dimensions if specified, otherwise fullscreen
2151
+ applyDimensions();
2154
2152
 
2155
- // On Android 30+ clear FLAG_TRANSLUCENT_STATUS flag
2156
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2157
- }
2153
+ SystemUiChromeSupport.prepareInitialDialogWindow(getWindow());
2158
2154
 
2159
2155
  WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(
2160
2156
  getWindow(),
@@ -2206,9 +2202,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2206
2202
  });
2207
2203
  }
2208
2204
 
2209
- // Set dimensions if specified, otherwise fullscreen
2210
- applyDimensions();
2211
-
2212
2205
  this._webView = findViewById(R.id.browser_view);
2213
2206
 
2214
2207
  this.swipeRefreshLayout = findViewById(R.id.content_browser_layout);
@@ -2250,13 +2243,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2250
2243
  if (!_options.getPersistWebViewData()) {
2251
2244
  _webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
2252
2245
  _webView.getSettings().setDatabaseEnabled(false);
2253
- _webView.getSettings().setDomStorageEnabled(false);
2254
2246
  _webView.clearCache(true);
2255
2247
  _webView.clearHistory();
2256
2248
  _webView.clearFormData();
2257
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
2258
- CookieManager.getInstance().setAcceptThirdPartyCookies(_webView, false);
2259
- }
2260
2249
  }
2261
2250
  _webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
2262
2251
  injectDocumentStartJavaScriptInterface();
@@ -2583,38 +2572,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2583
2572
  }
2584
2573
 
2585
2574
  try {
2586
- if (activity instanceof androidx.activity.ComponentActivity) {
2587
- androidx.activity.ComponentActivity componentActivity =
2588
- (androidx.activity.ComponentActivity) activity;
2589
- componentActivity
2590
- .getActivityResultRegistry()
2591
- .register(
2592
- "camera_capture",
2593
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
2594
- (result) -> {
2595
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2596
- return;
2597
- }
2598
- Uri[] results = null;
2599
- if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
2600
- results = new Uri[] { request.tempCameraUri };
2601
- }
2602
- if (
2603
- FileChooserRequestSupport.completeIfActive(
2604
- request,
2605
- activeFileChooserRequest,
2606
- results
2607
- )
2608
- ) {
2609
- request.tempCameraUri = null;
2610
- clearActiveFileChooserRequest(request);
2611
- }
2612
- }
2613
- )
2614
- .launch(takePictureIntent);
2575
+ ensureFileChooserLaunchers();
2576
+ if (cameraCaptureLauncher != null) {
2577
+ cameraCaptureLauncher.launch(takePictureIntent);
2615
2578
  } else {
2616
- // Fallback for non-ComponentActivity
2617
- activity.startActivityForResult(takePictureIntent, FILE_CHOOSER_REQUEST_CODE);
2579
+ Log.e("InAppBrowser", "ComponentActivity required for camera capture, falling back to file picker");
2580
+ fallbackToFilePicker(request);
2618
2581
  }
2619
2582
  } catch (SecurityException e) {
2620
2583
  Log.e("InAppBrowser", "Security exception launching camera: " + e.getMessage(), e);
@@ -2817,6 +2780,15 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2817
2780
  Map<String, String> requestHeaders = buildRequestHeadersExcludingUserAgent();
2818
2781
  applyWebViewUserAgent();
2819
2782
 
2783
+ if (_options.getClearCookiesOnOpen() || _options.getClearCacheOnOpen()) {
2784
+ OpenTimeBrowsingDataClearSupport.applyBeforeFirstNavigation(
2785
+ CookieManager.getInstance(),
2786
+ _webView,
2787
+ _options.getClearCookiesOnOpen(),
2788
+ _options.getClearCacheOnOpen()
2789
+ );
2790
+ }
2791
+
2820
2792
  // Load URL with optional HTTP method and body
2821
2793
  String httpMethod = _options.getHttpMethod();
2822
2794
  String httpBody = _options.getHttpBody();
@@ -2887,6 +2859,52 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2887
2859
  registerConfigurationCallbacks();
2888
2860
  }
2889
2861
 
2862
+ private void registerDialogBackHandler() {
2863
+ setOnKeyListener((dialogInterface, keyCode, event) -> {
2864
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
2865
+ return false;
2866
+ }
2867
+ if (event.getAction() != KeyEvent.ACTION_UP) {
2868
+ return true;
2869
+ }
2870
+ if (!shouldConsumeBackPress()) {
2871
+ return false;
2872
+ }
2873
+ handleBrowserBackNavigation();
2874
+ return true;
2875
+ });
2876
+ }
2877
+
2878
+ private boolean shouldConsumeBackPress() {
2879
+ return isShowing() && !isHiddenModeActive && !backLayerActive;
2880
+ }
2881
+
2882
+ private void handleBrowserBackNavigation() {
2883
+ if (_options == null) {
2884
+ dismiss();
2885
+ return;
2886
+ }
2887
+
2888
+ if (
2889
+ _webView != null &&
2890
+ _webView.canGoBack() &&
2891
+ (TextUtils.equals(_options.getToolbarType(), "navigation") || _options.getActiveNativeNavigationForWebview())
2892
+ ) {
2893
+ _webView.goBack();
2894
+ return;
2895
+ }
2896
+
2897
+ if (_options.getDisableGoBackOnNativeApplication()) {
2898
+ return;
2899
+ }
2900
+
2901
+ String currentUrl = getUrl();
2902
+ if (_options.getCallbacks() != null) {
2903
+ _options.getCallbacks().closeEvent(currentUrl);
2904
+ }
2905
+ dismiss();
2906
+ }
2907
+
2890
2908
  private void applyHiddenMode() {
2891
2909
  Window window = getWindow();
2892
2910
  if (window == null) {
@@ -3033,7 +3051,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3033
3051
  }
3034
3052
 
3035
3053
  // Check if we need Android 15+ specific fixes
3036
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3054
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3037
3055
 
3038
3056
  View toolbarView = findViewById(R.id.tool_bar);
3039
3057
 
@@ -3062,69 +3080,54 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3062
3080
 
3063
3081
  // Handle window decoration - version-specific handling
3064
3082
  if (getWindow() != null) {
3065
- if (isAndroid15Plus) {
3066
- // Android 15+: Use edge-to-edge with proper insets handling
3067
- getWindow().setDecorFitsSystemWindows(false);
3068
- getWindow().setStatusBarColor(Color.TRANSPARENT);
3069
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3083
+ View decorView = getWindow().getDecorView();
3084
+ Integer statusBarColor = null;
3085
+ Integer navigationBarColor = Color.TRANSPARENT;
3086
+ Boolean lightStatusBars = null;
3070
3087
 
3071
- WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
3072
-
3073
- // Set status bar text color
3088
+ if (isAndroid15Plus) {
3074
3089
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3075
3090
  try {
3076
3091
  int backgroundColor = Color.parseColor(_options.getToolbarColor());
3077
- boolean isDarkBackground = isDarkColor(backgroundColor);
3078
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3092
+ lightStatusBars = !isDarkColor(backgroundColor);
3079
3093
  } catch (IllegalArgumentException e) {
3080
3094
  // Ignore color parsing errors
3081
3095
  }
3082
3096
  }
3083
- } else if (Build.VERSION.SDK_INT >= 30) {
3084
- // Android 11-14: Keep navigation bar transparent but respect status bar
3085
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3086
-
3087
- WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
3088
-
3089
- // Set status bar color to match toolbar or use system default
3097
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
3090
3098
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3091
3099
  try {
3092
3100
  int toolbarColor = Color.parseColor(_options.getToolbarColor());
3093
- getWindow().setStatusBarColor(toolbarColor);
3094
- boolean isDarkBackground = isDarkColor(toolbarColor);
3095
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3101
+ statusBarColor = toolbarColor;
3102
+ lightStatusBars = !isDarkColor(toolbarColor);
3096
3103
  } catch (IllegalArgumentException e) {
3097
- // Follow system theme if color parsing fails
3098
3104
  boolean isDarkTheme = isDarkThemeEnabled();
3099
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3100
- getWindow().setStatusBarColor(statusBarColor);
3101
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3105
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3106
+ lightStatusBars = !isDarkTheme;
3102
3107
  }
3103
3108
  } else {
3104
- // Follow system theme if no toolbar color provided
3105
3109
  boolean isDarkTheme = isDarkThemeEnabled();
3106
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3107
- getWindow().setStatusBarColor(statusBarColor);
3108
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3110
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3111
+ lightStatusBars = !isDarkTheme;
3109
3112
  }
3110
3113
  } else {
3111
- // Pre-Android 11: Use deprecated flags for edge-to-edge navigation bar only
3112
- getWindow()
3113
- .getDecorView()
3114
- .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
3115
-
3116
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3117
-
3118
- // Set status bar color to match toolbar
3119
3114
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3120
3115
  try {
3121
- int toolbarColor = Color.parseColor(_options.getToolbarColor());
3122
- getWindow().setStatusBarColor(toolbarColor);
3116
+ statusBarColor = Color.parseColor(_options.getToolbarColor());
3123
3117
  } catch (IllegalArgumentException e) {
3124
3118
  // Use system default
3125
3119
  }
3126
3120
  }
3127
3121
  }
3122
+
3123
+ SystemUiChromeSupport.applyDialogWindowChrome(
3124
+ getWindow(),
3125
+ decorView,
3126
+ isAndroid15Plus,
3127
+ statusBarColor,
3128
+ navigationBarColor,
3129
+ lightStatusBars
3130
+ );
3128
3131
  }
3129
3132
 
3130
3133
  requestSafeAreaInsets();
@@ -3443,7 +3446,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3443
3446
  return;
3444
3447
  }
3445
3448
 
3446
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3449
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3447
3450
  View toolbarView = findBrowserContentDescendant(R.id.tool_bar);
3448
3451
  applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3449
3452
  }
@@ -3515,7 +3518,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3515
3518
  return;
3516
3519
  }
3517
3520
 
3518
- boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isEdgeToEdge);
3521
+ boolean layoutBehindNavigationBar =
3522
+ !backLayerActive && SystemUiChromeSupport.usesLayoutBehindNavigationBar(Build.VERSION.SDK_INT, isEdgeToEdge);
3523
+ boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(
3524
+ _options.getEnabledSafeMargin(),
3525
+ isEdgeToEdge,
3526
+ layoutBehindNavigationBar
3527
+ );
3519
3528
  int statusBarTop = SafeAreaInsetsSupport.resolveStatusBarTop(
3520
3529
  bars.top,
3521
3530
  bars.bottom,
@@ -4086,6 +4095,88 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4086
4095
  }
4087
4096
  }
4088
4097
 
4098
+ private void ensureFileChooserLaunchers() {
4099
+ if (!(activity instanceof ComponentActivity componentActivity)) {
4100
+ return;
4101
+ }
4102
+ if (fileChooserLauncher != null && cameraCaptureLauncher != null) {
4103
+ return;
4104
+ }
4105
+
4106
+ String launcherSuffix = (instanceId != null && !instanceId.isEmpty() ? instanceId + "_" : "") + Integer.toHexString(hashCode());
4107
+ if (cameraCaptureLauncher == null) {
4108
+ cameraCaptureLauncher = componentActivity
4109
+ .getActivityResultRegistry()
4110
+ .register(
4111
+ "inappbrowser_camera_capture_" + launcherSuffix,
4112
+ new ActivityResultContracts.StartActivityForResult(),
4113
+ this::handleCameraCaptureActivityResult
4114
+ );
4115
+ }
4116
+ if (fileChooserLauncher == null) {
4117
+ fileChooserLauncher = componentActivity
4118
+ .getActivityResultRegistry()
4119
+ .register(
4120
+ "inappbrowser_file_chooser_" + launcherSuffix,
4121
+ new ActivityResultContracts.StartActivityForResult(),
4122
+ this::handleFileChooserActivityResult
4123
+ );
4124
+ }
4125
+ }
4126
+
4127
+ private void clearActivityResultLaunchers() {
4128
+ if (cameraCaptureLauncher != null) {
4129
+ cameraCaptureLauncher.unregister();
4130
+ cameraCaptureLauncher = null;
4131
+ }
4132
+ if (fileChooserLauncher != null) {
4133
+ fileChooserLauncher.unregister();
4134
+ fileChooserLauncher = null;
4135
+ }
4136
+ }
4137
+
4138
+ private void handleCameraCaptureActivityResult(ActivityResult result) {
4139
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4140
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4141
+ return;
4142
+ }
4143
+
4144
+ Uri[] results = null;
4145
+ if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
4146
+ results = new Uri[] { request.tempCameraUri };
4147
+ }
4148
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4149
+ request.tempCameraUri = null;
4150
+ clearActiveFileChooserRequest(request);
4151
+ }
4152
+ }
4153
+
4154
+ private void handleFileChooserActivityResult(ActivityResult result) {
4155
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4156
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4157
+ return;
4158
+ }
4159
+
4160
+ Uri[] results = null;
4161
+ if (result.getResultCode() == Activity.RESULT_OK) {
4162
+ Intent data = result.getData();
4163
+ if (data != null) {
4164
+ if (data.getClipData() != null) {
4165
+ int count = data.getClipData().getItemCount();
4166
+ results = new Uri[count];
4167
+ for (int i = 0; i < count; i++) {
4168
+ results[i] = data.getClipData().getItemAt(i).getUri();
4169
+ }
4170
+ } else if (data.getData() != null) {
4171
+ results = new Uri[] { data.getData() };
4172
+ }
4173
+ }
4174
+ }
4175
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4176
+ clearActiveFileChooserRequest(request);
4177
+ }
4178
+ }
4179
+
4089
4180
  private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
4090
4181
  if (activeFileChooserRequest != null) {
4091
4182
  FileChooserRequestSupport.cancel(activeFileChooserRequest);
@@ -4106,17 +4197,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4106
4197
  tempCameraUri = activeFileChooserRequest != null ? activeFileChooserRequest.tempCameraUri : null;
4107
4198
  }
4108
4199
 
4109
- void completeLegacyFileChooserResult(Uri[] results) {
4110
- FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4111
- if (request == null) {
4112
- return;
4113
- }
4114
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4115
- request.tempCameraUri = null;
4116
- clearActiveFileChooserRequest(request);
4117
- }
4118
- }
4119
-
4120
4200
  private void openFileChooser(FileChooserRequestSupport.FileChooserRequest request) {
4121
4201
  if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4122
4202
  return;
@@ -4127,88 +4207,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4127
4207
  Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
4128
4208
 
4129
4209
  try {
4130
- if (activity instanceof androidx.activity.ComponentActivity) {
4131
- androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
4132
- componentActivity
4133
- .getActivityResultRegistry()
4134
- .register(
4135
- "file_chooser",
4136
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4137
- (result) -> {
4138
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4139
- return;
4140
- }
4141
- Uri[] results = null;
4142
- if (result.getResultCode() == Activity.RESULT_OK) {
4143
- Intent data = result.getData();
4144
- if (data != null) {
4145
- if (data.getClipData() != null) {
4146
- // Handle multiple files
4147
- int count = data.getClipData().getItemCount();
4148
- results = new Uri[count];
4149
- for (int i = 0; i < count; i++) {
4150
- results[i] = data.getClipData().getItemAt(i).getUri();
4151
- }
4152
- } else if (data.getData() != null) {
4153
- // Handle single file
4154
- results = new Uri[] { data.getData() };
4155
- }
4156
- }
4157
- }
4158
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4159
- clearActiveFileChooserRequest(request);
4160
- }
4161
- }
4162
- )
4163
- .launch(Intent.createChooser(intent, "Select File"));
4164
- } else {
4165
- // Fallback for non-ComponentActivity
4166
- activity.startActivityForResult(Intent.createChooser(intent, "Select File"), FILE_CHOOSER_REQUEST_CODE);
4167
- }
4210
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4168
4211
  } catch (ActivityNotFoundException e) {
4169
4212
  // If no app can handle the specific MIME type, try with a more generic one
4170
4213
  Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
4171
4214
  intent.setType("*/*");
4172
4215
  intent.removeExtra(Intent.EXTRA_MIME_TYPES);
4173
4216
  try {
4174
- if (activity instanceof androidx.activity.ComponentActivity) {
4175
- androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
4176
- componentActivity
4177
- .getActivityResultRegistry()
4178
- .register(
4179
- "file_chooser",
4180
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4181
- (result) -> {
4182
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4183
- return;
4184
- }
4185
- Uri[] results = null;
4186
- if (result.getResultCode() == Activity.RESULT_OK) {
4187
- Intent data = result.getData();
4188
- if (data != null) {
4189
- if (data.getClipData() != null) {
4190
- // Handle multiple files
4191
- int count = data.getClipData().getItemCount();
4192
- results = new Uri[count];
4193
- for (int i = 0; i < count; i++) {
4194
- results[i] = data.getClipData().getItemAt(i).getUri();
4195
- }
4196
- } else if (data.getData() != null) {
4197
- // Handle single file
4198
- results = new Uri[] { data.getData() };
4199
- }
4200
- }
4201
- }
4202
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4203
- clearActiveFileChooserRequest(request);
4204
- }
4205
- }
4206
- )
4207
- .launch(Intent.createChooser(intent, "Select File"));
4208
- } else {
4209
- // Fallback for non-ComponentActivity
4210
- activity.startActivityForResult(Intent.createChooser(intent, "Select File"), FILE_CHOOSER_REQUEST_CODE);
4211
- }
4217
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4212
4218
  } catch (ActivityNotFoundException ex) {
4213
4219
  // If still failing, report error
4214
4220
  Log.e("InAppBrowser", "No app can handle file picker", ex);
@@ -4219,6 +4225,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4219
4225
  }
4220
4226
  }
4221
4227
 
4228
+ private void launchFileChooserIntent(Intent chooserIntent, FileChooserRequestSupport.FileChooserRequest request) {
4229
+ ensureFileChooserLaunchers();
4230
+ if (fileChooserLauncher == null) {
4231
+ Log.e("InAppBrowser", "ComponentActivity required for file chooser");
4232
+ if (FileChooserRequestSupport.cancelIfActive(request, activeFileChooserRequest)) {
4233
+ clearActiveFileChooserRequest(request);
4234
+ }
4235
+ return;
4236
+ }
4237
+ fileChooserLauncher.launch(chooserIntent);
4238
+ }
4239
+
4222
4240
  public void reload() {
4223
4241
  if (_webView == null) {
4224
4242
  Log.w("InAppBrowser", "Cannot reload - WebView is null");
@@ -4700,8 +4718,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4700
4718
 
4701
4719
  // Also ensure status bar gets the color
4702
4720
  if (getWindow() != null) {
4703
- // Set status bar color
4704
- getWindow().setStatusBarColor(toolbarColor);
4721
+ SystemUiChromeSupport.applyLegacySystemBarColors(getWindow(), toolbarColor, null);
4705
4722
 
4706
4723
  // Determine proper status bar text color (light or dark icons)
4707
4724
  boolean isDarkBackground = isDarkColor(toolbarColor);
@@ -6165,21 +6182,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6165
6182
  return false;
6166
6183
  }
6167
6184
 
6168
- @Override
6169
- public void onBackPressed() {
6170
- if (
6171
- _webView != null &&
6172
- _webView.canGoBack() &&
6173
- (TextUtils.equals(_options.getToolbarType(), "navigation") || _options.getActiveNativeNavigationForWebview())
6174
- ) {
6175
- _webView.goBack();
6176
- } else if (!_options.getDisableGoBackOnNativeApplication()) {
6177
- String currentUrl = getUrl();
6178
- _options.getCallbacks().closeEvent(currentUrl);
6179
- super.onBackPressed();
6180
- }
6181
- }
6182
-
6183
6185
  @Override
6184
6186
  public boolean onKeyDown(int keyCode, KeyEvent event) {
6185
6187
  // Forward volume key events to the MainActivity
@@ -6384,6 +6386,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6384
6386
  // Clear any remaining proxied requests
6385
6387
  proxiedRequestsHashmap.clear();
6386
6388
 
6389
+ clearActivityResultLaunchers();
6390
+
6387
6391
  try {
6388
6392
  super.dismiss();
6389
6393
  } catch (Exception e) {
package/dist/docs.json CHANGED
@@ -1592,7 +1592,39 @@
1592
1592
  "name": "since"
1593
1593
  }
1594
1594
  ],
1595
- "docs": "Controls whether the webview should persist website data such as cache, cookies, local storage,\nIndexedDB, and session data.\n\nWhen false, iOS uses a non-persistent `WKWebsiteDataStore`. Android disables per-view cache and\nDOM/database storage where the system WebView supports it. Android cookies use the shared\nWebView cookie store; clearing them also affects the host WebView, so prefer per-URL cookie\nhelpers instead of relying on process-global wipes.",
1595
+ "docs": "Controls whether the webview should persist website data such as cache, cookies, local storage,\nIndexedDB, and session data.\n\nWhen false, iOS uses a non-persistent `WKWebsiteDataStore`. Android disables per-view cache and\ndatabase storage where the system WebView supports it while keeping DOM storage available so\nSPAs can use `localStorage`. Android cookies use the shared WebView cookie store; clearing them\nalso affects the host WebView, so prefer per-URL cookie helpers instead of relying on\nprocess-global wipes.",
1596
+ "complexTypes": [],
1597
+ "type": "boolean | undefined"
1598
+ },
1599
+ {
1600
+ "name": "clearCookiesOnOpen",
1601
+ "tags": [
1602
+ {
1603
+ "text": "false",
1604
+ "name": "default"
1605
+ },
1606
+ {
1607
+ "text": "8.16.0",
1608
+ "name": "since"
1609
+ }
1610
+ ],
1611
+ "docs": "Clear all cookies from the InAppBrowser data store before the first navigation of this webview.\n\nCordova `clearcache: 'yes'` parity. On Android, uses process-global `CookieManager.removeAllCookies()`\nand blocks until the wipe completes. On iOS, clears cookies from the webview's `WKWebsiteDataStore`\n(isolated plugin store by default). On Web this is a no-op.",
1612
+ "complexTypes": [],
1613
+ "type": "boolean | undefined"
1614
+ },
1615
+ {
1616
+ "name": "clearCacheOnOpen",
1617
+ "tags": [
1618
+ {
1619
+ "text": "false",
1620
+ "name": "default"
1621
+ },
1622
+ {
1623
+ "text": "8.16.0",
1624
+ "name": "since"
1625
+ }
1626
+ ],
1627
+ "docs": "Clear cached website data before the first navigation of this webview.\n\nCordova `cleardata: 'yes'` parity. On Android, calls `WebView.clearCache(true)` after the webview\nis created. On iOS, removes disk and memory cache from the webview's `WKWebsiteDataStore`.\nOn Web this is a no-op.",
1596
1628
  "complexTypes": [],
1597
1629
  "type": "boolean | undefined"
1598
1630
  },
@@ -736,14 +736,37 @@ export interface OpenWebViewOptions {
736
736
  * IndexedDB, and session data.
737
737
  *
738
738
  * When false, iOS uses a non-persistent `WKWebsiteDataStore`. Android disables per-view cache and
739
- * DOM/database storage where the system WebView supports it. Android cookies use the shared
740
- * WebView cookie store; clearing them also affects the host WebView, so prefer per-URL cookie
741
- * helpers instead of relying on process-global wipes.
739
+ * database storage where the system WebView supports it while keeping DOM storage available so
740
+ * SPAs can use `localStorage`. Android cookies use the shared WebView cookie store; clearing them
741
+ * also affects the host WebView, so prefer per-URL cookie helpers instead of relying on
742
+ * process-global wipes.
742
743
  *
743
744
  * @default true
744
745
  * @since 8.6.36
745
746
  */
746
747
  persistWebViewData?: boolean;
748
+ /**
749
+ * Clear all cookies from the InAppBrowser data store before the first navigation of this webview.
750
+ *
751
+ * Cordova `clearcache: 'yes'` parity. On Android, uses process-global `CookieManager.removeAllCookies()`
752
+ * and blocks until the wipe completes. On iOS, clears cookies from the webview's `WKWebsiteDataStore`
753
+ * (isolated plugin store by default). On Web this is a no-op.
754
+ *
755
+ * @default false
756
+ * @since 8.16.0
757
+ */
758
+ clearCookiesOnOpen?: boolean;
759
+ /**
760
+ * Clear cached website data before the first navigation of this webview.
761
+ *
762
+ * Cordova `cleardata: 'yes'` parity. On Android, calls `WebView.clearCache(true)` after the webview
763
+ * is created. On iOS, removes disk and memory cache from the webview's `WKWebsiteDataStore`.
764
+ * On Web this is a no-op.
765
+ *
766
+ * @default false
767
+ * @since 8.16.0
768
+ */
769
+ clearCacheOnOpen?: boolean;
747
770
  /**
748
771
  * Share the host Capacitor WebView's website data store (cookies, local storage, etc.).
749
772
  *