@capgo/capacitor-inappbrowser 8.14.3 → 8.15.1

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.
@@ -81,6 +81,7 @@ import androidx.core.view.WindowInsetsControllerCompat;
81
81
  import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
82
82
  import androidx.webkit.WebMessageCompat;
83
83
  import androidx.webkit.WebSettingsCompat;
84
+ import androidx.webkit.WebViewAssetLoader;
84
85
  import androidx.webkit.WebViewCompat;
85
86
  import androidx.webkit.WebViewFeature;
86
87
  import com.caverock.androidsvg.SVG;
@@ -344,6 +345,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
344
345
  private String instanceId = "";
345
346
  private final Map<String, ProxiedRequest> proxiedRequestsHashmap = new ConcurrentHashMap<>();
346
347
  private ProxyBridge proxyBridge;
348
+ private volatile WebViewAssetLoader bundledAssetLoader;
349
+ private final Object bundledAssetLoaderLock = new Object();
347
350
  private String proxyBridgeScript;
348
351
  private String proxyAccessToken;
349
352
  private final ExecutorService executorService = Executors.newCachedThreadPool();
@@ -409,6 +412,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
409
412
  public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
410
413
  public ValueCallback<Uri> mUploadMessage;
411
414
  public ValueCallback<Uri[]> mFilePathCallback;
415
+ FileChooserRequestSupport.FileChooserRequest activeFileChooserRequest;
412
416
  private boolean openWebViewResolved;
413
417
  private boolean isDismissing = false;
414
418
  private PermissionRequest pendingCameraLaunchPermissionRequest;
@@ -2232,6 +2236,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2232
2236
  _webView.addJavascriptInterface(proxyBridge, "__capgoProxy");
2233
2237
  proxyBridgeScript = loadProxyBridgeScript();
2234
2238
  }
2239
+ ensureBundledAssetLoader();
2235
2240
  _webView.getSettings().setJavaScriptEnabled(true);
2236
2241
  _webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
2237
2242
  _webView.getSettings().setDatabaseEnabled(true);
@@ -2344,12 +2349,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2344
2349
  );
2345
2350
 
2346
2351
  // Check if the file chooser is already open
2347
- if (mFilePathCallback != null) {
2348
- mFilePathCallback.onReceiveValue(null);
2349
- mFilePathCallback = null;
2350
- }
2351
-
2352
- mFilePathCallback = filePathCallback;
2352
+ final boolean isMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
2353
+ beginFileChooserRequest(filePathCallback, acceptType, isMultiple);
2354
+ final FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
2353
2355
 
2354
2356
  // Direct check for capture attribute in URL (fallback method)
2355
2357
  boolean isCaptureInUrl;
@@ -2424,12 +2426,16 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2424
2426
  """;
2425
2427
 
2426
2428
  webView.evaluateJavascript(js, (value) -> {
2429
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2430
+ return;
2431
+ }
2432
+
2427
2433
  Log.d("InAppBrowser", "Capture attribute JS result: " + value);
2428
2434
 
2429
2435
  // If we already found capture in URL, use that directly
2430
2436
  if (isCaptureInUrl) {
2431
2437
  Log.d("InAppBrowser", "Using capture from URL: " + captureMode);
2432
- launchCamera(captureMode.equals("user"));
2438
+ launchCamera(captureMode.equals("user"), request);
2433
2439
  return;
2434
2440
  }
2435
2441
 
@@ -2440,7 +2446,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2440
2446
  Log.d("InAppBrowser", "Found capture attribute: " + captureValue);
2441
2447
 
2442
2448
  if (!captureValue.isEmpty()) {
2443
- activity.runOnUiThread(() -> launchCamera(captureValue.equals("user")));
2449
+ activity.runOnUiThread(() -> launchCamera(captureValue.equals("user"), request));
2444
2450
  return;
2445
2451
  }
2446
2452
  }
@@ -2448,6 +2454,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2448
2454
  // Look for hints in the web page source
2449
2455
  Log.d("InAppBrowser", "Looking for camera hints in page content");
2450
2456
  webView.evaluateJavascript("(function() { return document.documentElement.innerHTML; })()", (htmlSource) -> {
2457
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2458
+ return;
2459
+ }
2460
+
2451
2461
  if (htmlSource != null && htmlSource.length() > 10) {
2452
2462
  boolean hasCameraOrSelfieKeyword =
2453
2463
  htmlSource.contains("capture=") || htmlSource.contains("camera") || htmlSource.contains("selfie");
@@ -2460,25 +2470,21 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2460
2470
  (currentUrl.contains("selfie") || currentUrl.contains("camera") || currentUrl.contains("photo"))
2461
2471
  ) {
2462
2472
  Log.d("InAppBrowser", "URL suggests camera usage, launching camera");
2463
- activity.runOnUiThread(() -> launchCamera(currentUrl.contains("selfie")));
2473
+ activity.runOnUiThread(() -> launchCamera(currentUrl.contains("selfie"), request));
2464
2474
  return;
2465
2475
  }
2466
2476
  }
2467
2477
 
2468
2478
  // If all detection methods fail, fall back to regular file picker
2469
2479
  Log.d("InAppBrowser", "No capture attribute detected, using file picker");
2470
- openFileChooser(
2471
- filePathCallback,
2472
- acceptType,
2473
- fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE
2474
- );
2480
+ openFileChooser(request);
2475
2481
  });
2476
2482
  });
2477
2483
  return true;
2478
2484
  }
2479
2485
 
2480
2486
  // For non-image types, use regular file picker
2481
- openFileChooser(filePathCallback, acceptType, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
2487
+ openFileChooser(request);
2482
2488
  return true;
2483
2489
  }
2484
2490
 
@@ -2486,7 +2492,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2486
2492
  * Launch the camera app for capturing images
2487
2493
  * @param useFrontCamera true to use front camera, false for back camera
2488
2494
  */
2489
- private void launchCamera(boolean useFrontCamera) {
2495
+ private void launchCamera(boolean useFrontCamera, FileChooserRequestSupport.FileChooserRequest request) {
2496
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2497
+ return;
2498
+ }
2499
+
2490
2500
  Log.d("InAppBrowser", "Launching camera, front camera: " + useFrontCamera);
2491
2501
 
2492
2502
  // First check if we have camera permission
@@ -2510,8 +2520,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2510
2520
  Log.d("InAppBrowser", "Ignoring delayed camera permission grant during dismiss");
2511
2521
  return;
2512
2522
  }
2523
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2524
+ return;
2525
+ }
2513
2526
  // Permission granted, now launch the camera
2514
- launchCameraWithPermission(useFrontCamera);
2527
+ launchCameraWithPermission(useFrontCamera, request);
2515
2528
  }
2516
2529
 
2517
2530
  @Override
@@ -2521,9 +2534,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2521
2534
  Log.d("InAppBrowser", "Ignoring delayed camera permission denial during dismiss");
2522
2535
  return;
2523
2536
  }
2537
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2538
+ return;
2539
+ }
2524
2540
  // Permission denied, fall back to file picker
2525
2541
  Log.e("InAppBrowser", "Camera permission denied, falling back to file picker");
2526
- fallbackToFilePicker();
2542
+ fallbackToFilePicker(request);
2527
2543
  }
2528
2544
  };
2529
2545
 
@@ -2535,13 +2551,17 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2535
2551
  }
2536
2552
 
2537
2553
  // If we can't request permission, try launching directly
2538
- launchCameraWithPermission(useFrontCamera);
2554
+ launchCameraWithPermission(useFrontCamera, request);
2539
2555
  }
2540
2556
 
2541
2557
  /**
2542
2558
  * Launch camera after permission is granted
2543
2559
  */
2544
- private void launchCameraWithPermission(boolean useFrontCamera) {
2560
+ private void launchCameraWithPermission(boolean useFrontCamera, FileChooserRequestSupport.FileChooserRequest request) {
2561
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2562
+ return;
2563
+ }
2564
+
2545
2565
  try {
2546
2566
  Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
2547
2567
  if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
@@ -2550,17 +2570,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2550
2570
  photoFile = createImageFile();
2551
2571
  } catch (IOException ex) {
2552
2572
  Log.e("InAppBrowser", "Error creating image file", ex);
2553
- fallbackToFilePicker();
2573
+ fallbackToFilePicker(request);
2554
2574
  return;
2555
2575
  }
2556
2576
 
2557
2577
  if (photoFile != null) {
2558
- tempCameraUri = FileProvider.getUriForFile(
2578
+ request.tempCameraUri = FileProvider.getUriForFile(
2559
2579
  activity,
2560
2580
  activity.getPackageName() + ".fileprovider",
2561
2581
  photoFile
2562
2582
  );
2563
- takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempCameraUri);
2583
+ syncFileChooserPublicFields();
2584
+ takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, request.tempCameraUri);
2564
2585
 
2565
2586
  if (useFrontCamera) {
2566
2587
  takePictureIntent.putExtra("android.intent.extras.CAMERA_FACING", 1);
@@ -2576,15 +2597,23 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2576
2597
  "camera_capture",
2577
2598
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
2578
2599
  (result) -> {
2579
- if (result.getResultCode() == Activity.RESULT_OK) {
2580
- if (tempCameraUri != null) {
2581
- mFilePathCallback.onReceiveValue(new Uri[] { tempCameraUri });
2582
- }
2583
- } else {
2584
- mFilePathCallback.onReceiveValue(null);
2600
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2601
+ return;
2602
+ }
2603
+ Uri[] results = null;
2604
+ if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
2605
+ results = new Uri[] { request.tempCameraUri };
2606
+ }
2607
+ if (
2608
+ FileChooserRequestSupport.completeIfActive(
2609
+ request,
2610
+ activeFileChooserRequest,
2611
+ results
2612
+ )
2613
+ ) {
2614
+ request.tempCameraUri = null;
2615
+ clearActiveFileChooserRequest(request);
2585
2616
  }
2586
- mFilePathCallback = null;
2587
- tempCameraUri = null;
2588
2617
  }
2589
2618
  )
2590
2619
  .launch(takePictureIntent);
@@ -2594,26 +2623,27 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2594
2623
  }
2595
2624
  } catch (SecurityException e) {
2596
2625
  Log.e("InAppBrowser", "Security exception launching camera: " + e.getMessage(), e);
2597
- fallbackToFilePicker();
2626
+ fallbackToFilePicker(request);
2598
2627
  }
2599
2628
  } else {
2600
2629
  Log.e("InAppBrowser", "Failed to create photo URI, falling back to file picker");
2601
- fallbackToFilePicker();
2630
+ fallbackToFilePicker(request);
2602
2631
  }
2603
2632
  }
2604
2633
  } catch (Exception e) {
2605
2634
  Log.e("InAppBrowser", "Camera launch failed: " + e.getMessage(), e);
2606
- fallbackToFilePicker();
2635
+ fallbackToFilePicker(request);
2607
2636
  }
2608
2637
  }
2609
2638
 
2610
2639
  /**
2611
2640
  * Fall back to file picker when camera launch fails
2612
2641
  */
2613
- private void fallbackToFilePicker() {
2614
- if (mFilePathCallback != null) {
2615
- openFileChooser(mFilePathCallback, "image/*", false);
2642
+ private void fallbackToFilePicker(FileChooserRequestSupport.FileChooserRequest request) {
2643
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2644
+ return;
2616
2645
  }
2646
+ openFileChooser(request);
2617
2647
  }
2618
2648
 
2619
2649
  // Grant permissions for cam
@@ -4061,8 +4091,43 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4061
4091
  }
4062
4092
  }
4063
4093
 
4064
- private void openFileChooser(ValueCallback<Uri[]> filePathCallback, String acceptType, boolean isMultiple) {
4065
- mFilePathCallback = filePathCallback;
4094
+ private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String acceptType, boolean isMultiple) {
4095
+ if (activeFileChooserRequest != null) {
4096
+ FileChooserRequestSupport.cancel(activeFileChooserRequest);
4097
+ }
4098
+ activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback, acceptType, isMultiple);
4099
+ syncFileChooserPublicFields();
4100
+ }
4101
+
4102
+ private void clearActiveFileChooserRequest(FileChooserRequestSupport.FileChooserRequest request) {
4103
+ if (activeFileChooserRequest == request) {
4104
+ activeFileChooserRequest = null;
4105
+ syncFileChooserPublicFields();
4106
+ }
4107
+ }
4108
+
4109
+ private void syncFileChooserPublicFields() {
4110
+ mFilePathCallback = activeFileChooserRequest != null ? activeFileChooserRequest.callback : null;
4111
+ tempCameraUri = activeFileChooserRequest != null ? activeFileChooserRequest.tempCameraUri : null;
4112
+ }
4113
+
4114
+ void completeLegacyFileChooserResult(Uri[] results) {
4115
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4116
+ if (request == null) {
4117
+ return;
4118
+ }
4119
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4120
+ request.tempCameraUri = null;
4121
+ clearActiveFileChooserRequest(request);
4122
+ }
4123
+ }
4124
+
4125
+ private void openFileChooser(FileChooserRequestSupport.FileChooserRequest request) {
4126
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4127
+ return;
4128
+ }
4129
+ String acceptType = request.acceptType;
4130
+ boolean isMultiple = request.multiple;
4066
4131
  Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
4067
4132
  intent.addCategory(Intent.CATEGORY_OPENABLE);
4068
4133
 
@@ -4110,26 +4175,29 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4110
4175
  "file_chooser",
4111
4176
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4112
4177
  (result) -> {
4178
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4179
+ return;
4180
+ }
4181
+ Uri[] results = null;
4113
4182
  if (result.getResultCode() == Activity.RESULT_OK) {
4114
4183
  Intent data = result.getData();
4115
4184
  if (data != null) {
4116
4185
  if (data.getClipData() != null) {
4117
4186
  // Handle multiple files
4118
4187
  int count = data.getClipData().getItemCount();
4119
- Uri[] results = new Uri[count];
4188
+ results = new Uri[count];
4120
4189
  for (int i = 0; i < count; i++) {
4121
4190
  results[i] = data.getClipData().getItemAt(i).getUri();
4122
4191
  }
4123
- mFilePathCallback.onReceiveValue(results);
4124
4192
  } else if (data.getData() != null) {
4125
4193
  // Handle single file
4126
- mFilePathCallback.onReceiveValue(new Uri[] { data.getData() });
4194
+ results = new Uri[] { data.getData() };
4127
4195
  }
4128
4196
  }
4129
- } else {
4130
- mFilePathCallback.onReceiveValue(null);
4131
4197
  }
4132
- mFilePathCallback = null;
4198
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4199
+ clearActiveFileChooserRequest(request);
4200
+ }
4133
4201
  }
4134
4202
  )
4135
4203
  .launch(Intent.createChooser(intent, "Select File"));
@@ -4150,26 +4218,29 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4150
4218
  "file_chooser",
4151
4219
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4152
4220
  (result) -> {
4221
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4222
+ return;
4223
+ }
4224
+ Uri[] results = null;
4153
4225
  if (result.getResultCode() == Activity.RESULT_OK) {
4154
4226
  Intent data = result.getData();
4155
4227
  if (data != null) {
4156
4228
  if (data.getClipData() != null) {
4157
4229
  // Handle multiple files
4158
4230
  int count = data.getClipData().getItemCount();
4159
- Uri[] results = new Uri[count];
4231
+ results = new Uri[count];
4160
4232
  for (int i = 0; i < count; i++) {
4161
4233
  results[i] = data.getClipData().getItemAt(i).getUri();
4162
4234
  }
4163
- mFilePathCallback.onReceiveValue(results);
4164
4235
  } else if (data.getData() != null) {
4165
4236
  // Handle single file
4166
- mFilePathCallback.onReceiveValue(new Uri[] { data.getData() });
4237
+ results = new Uri[] { data.getData() };
4167
4238
  }
4168
4239
  }
4169
- } else {
4170
- mFilePathCallback.onReceiveValue(null);
4171
4240
  }
4172
- mFilePathCallback = null;
4241
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4242
+ clearActiveFileChooserRequest(request);
4243
+ }
4173
4244
  }
4174
4245
  )
4175
4246
  .launch(Intent.createChooser(intent, "Select File"));
@@ -4180,9 +4251,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4180
4251
  } catch (ActivityNotFoundException ex) {
4181
4252
  // If still failing, report error
4182
4253
  Log.e("InAppBrowser", "No app can handle file picker", ex);
4183
- if (mFilePathCallback != null) {
4184
- mFilePathCallback.onReceiveValue(null);
4185
- mFilePathCallback = null;
4254
+ if (FileChooserRequestSupport.cancelIfActive(request, activeFileChooserRequest)) {
4255
+ clearActiveFileChooserRequest(request);
4186
4256
  }
4187
4257
  }
4188
4258
  }
@@ -4364,6 +4434,60 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4364
4434
  return proxyHeaders;
4365
4435
  }
4366
4436
 
4437
+ private void ensureBundledAssetLoader() {
4438
+ synchronized (bundledAssetLoaderLock) {
4439
+ ensureBundledAssetLoaderLocked();
4440
+ }
4441
+ }
4442
+
4443
+ private void ensureBundledAssetLoaderLocked() {
4444
+ if (bundledAssetLoader != null || _options == null || !_options.getServeBundledAssets()) {
4445
+ return;
4446
+ }
4447
+
4448
+ bundledAssetLoader = BundledAssetSupport.createAssetLoader(
4449
+ _context,
4450
+ _options.getBundledAssetHost(),
4451
+ _options.getBundledAssetScheme()
4452
+ );
4453
+ }
4454
+
4455
+ public void applyBundledAssetResolution(BundledAssetSupport.Resolution resolution, String localUrl) {
4456
+ if (_options == null || resolution == null) {
4457
+ return;
4458
+ }
4459
+
4460
+ synchronized (bundledAssetLoaderLock) {
4461
+ bundledAssetLoader = null;
4462
+ _options.setUrl(resolution.url);
4463
+ if (resolution.needsAssetLoader) {
4464
+ BundledAssetSupport.LocalConfig localConfig = BundledAssetSupport.parseLocalConfig(localUrl);
4465
+ _options.setBundledAssetHost(localConfig != null ? localConfig.host : "localhost");
4466
+ _options.setBundledAssetScheme(localConfig != null ? BundledAssetSupport.assetLoaderScheme(localConfig) : "https");
4467
+ _options.setServeBundledAssets(true);
4468
+ } else {
4469
+ _options.setServeBundledAssets(false);
4470
+ }
4471
+ }
4472
+ }
4473
+
4474
+ private WebResourceResponse interceptBundledAssetRequest(WebResourceRequest request) {
4475
+ final WebViewAssetLoader loader;
4476
+ synchronized (bundledAssetLoaderLock) {
4477
+ if (_options == null || !_options.getServeBundledAssets()) {
4478
+ return null;
4479
+ }
4480
+
4481
+ ensureBundledAssetLoaderLocked();
4482
+ loader = bundledAssetLoader;
4483
+ }
4484
+
4485
+ if (loader == null) {
4486
+ return null;
4487
+ }
4488
+ return loader.shouldInterceptRequest(request.getUrl());
4489
+ }
4490
+
4367
4491
  public void setUrl(String url) {
4368
4492
  if (_webView == null) {
4369
4493
  Log.w("InAppBrowser", "Cannot set URL - WebView is null");
@@ -5586,186 +5710,254 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5586
5710
  if (view == null || _webView == null) {
5587
5711
  return null;
5588
5712
  }
5589
- if (!shouldUseNativeProxy()) {
5590
- return null;
5591
- }
5592
5713
 
5593
- String requestUrl = request.getUrl().toString();
5594
- boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5595
- String originalUrl;
5596
- String method;
5597
- Map<String, String> requestHeaders = new HashMap<>();
5598
- String base64Body = "";
5599
- String credentialsMode = "same-origin";
5600
-
5601
- if (bridgeBackedRequest) {
5602
- Uri uri = request.getUrl();
5603
- originalUrl = uri.getQueryParameter("u");
5604
- String requestId = uri.getQueryParameter("rid");
5605
- if (originalUrl == null || requestId == null) {
5606
- return null;
5607
- }
5714
+ if (shouldUseNativeProxy()) {
5715
+ String requestUrl = request.getUrl().toString();
5716
+ boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5717
+ String originalUrl;
5718
+ String method;
5719
+ Map<String, String> requestHeaders = new HashMap<>();
5720
+ String base64Body = "";
5721
+ String credentialsMode = "same-origin";
5722
+
5723
+ if (bridgeBackedRequest) {
5724
+ Uri uri = request.getUrl();
5725
+ originalUrl = uri.getQueryParameter("u");
5726
+ String requestId = uri.getQueryParameter("rid");
5727
+ if (originalUrl == null || requestId == null) {
5728
+ return null;
5729
+ }
5608
5730
 
5609
- if (
5610
- ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5611
- !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5612
- ) {
5613
- if (proxyBridge != null) {
5614
- proxyBridge.getAndRemove(requestId);
5731
+ if (
5732
+ ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5733
+ !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5734
+ ) {
5735
+ if (proxyBridge != null) {
5736
+ proxyBridge.getAndRemove(requestId);
5737
+ }
5738
+ Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5739
+ return createCanceledResponse();
5615
5740
  }
5616
- Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5617
- return createCanceledResponse();
5618
- }
5619
5741
 
5620
- ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5621
- if (stored == null) {
5622
- Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5623
- return createCanceledResponse();
5624
- }
5625
- method = stored.method;
5626
- base64Body = stored.base64Body;
5627
- credentialsMode = stored.credentialsMode;
5628
- Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(request.getRequestHeaders());
5629
- try {
5630
- requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5631
- requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5632
- } catch (JSONException error) {
5633
- Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5634
- return createCanceledResponse();
5635
- }
5636
- String initiatorUrl = request.getRequestHeaders().get("Referer");
5637
- if (initiatorUrl == null || initiatorUrl.isBlank()) {
5638
- initiatorUrl = getWebViewUrlOrFallback(
5639
- _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5742
+ ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5743
+ if (stored == null) {
5744
+ Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5745
+ return createCanceledResponse();
5746
+ }
5747
+ method = stored.method;
5748
+ base64Body = stored.base64Body;
5749
+ credentialsMode = stored.credentialsMode;
5750
+ Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(
5751
+ request.getRequestHeaders()
5640
5752
  );
5753
+ try {
5754
+ requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5755
+ requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5756
+ } catch (JSONException error) {
5757
+ Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5758
+ return createCanceledResponse();
5759
+ }
5760
+ String initiatorUrl = request.getRequestHeaders().get("Referer");
5761
+ if (initiatorUrl == null || initiatorUrl.isBlank()) {
5762
+ initiatorUrl = getWebViewUrlOrFallback(
5763
+ _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5764
+ );
5765
+ }
5766
+ String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5767
+ if (
5768
+ targetCookies != null &&
5769
+ !targetCookies.isBlank() &&
5770
+ ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5771
+ ) {
5772
+ requestHeaders.put("Cookie", targetCookies);
5773
+ }
5774
+ } else {
5775
+ if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5776
+ return interceptBundledAssetRequest(request);
5777
+ }
5778
+
5779
+ originalUrl = requestUrl;
5780
+ method = request.getMethod();
5781
+ if (request.getRequestHeaders() != null) {
5782
+ requestHeaders.putAll(request.getRequestHeaders());
5783
+ }
5641
5784
  }
5642
- String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5785
+
5786
+ NativeRequestContext requestContext = new NativeRequestContext(
5787
+ originalUrl,
5788
+ method,
5789
+ requestHeaders,
5790
+ base64Body,
5791
+ request.isForMainFrame(),
5792
+ credentialsMode
5793
+ );
5794
+
5643
5795
  if (
5644
- targetCookies != null &&
5645
- !targetCookies.isBlank() &&
5646
- ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5796
+ ProxyRequestSupport.shouldLetWebViewHandleMissingBody(
5797
+ requestUrl,
5798
+ requestContext.method,
5799
+ requestContext.base64Body
5800
+ )
5647
5801
  ) {
5648
- requestHeaders.put("Cookie", targetCookies);
5649
- }
5650
- } else {
5651
- if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5802
+ Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5652
5803
  return null;
5653
5804
  }
5654
5805
 
5655
- originalUrl = requestUrl;
5656
- method = request.getMethod();
5657
- if (request.getRequestHeaders() != null) {
5658
- requestHeaders.putAll(request.getRequestHeaders());
5806
+ boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5807
+ boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5808
+ _options,
5809
+ requestContext.url
5810
+ );
5811
+ NativeResponseData directResponseData = null;
5812
+
5813
+ NativeProxyRule outboundRule =
5814
+ legacyProxyMode && shouldDelegateLegacyRequest
5815
+ ? new NativeProxyRule(
5816
+ null,
5817
+ null,
5818
+ null,
5819
+ null,
5820
+ null,
5821
+ null,
5822
+ null,
5823
+ null,
5824
+ false,
5825
+ NativeProxyRule.Action.DELEGATE_TO_JS
5826
+ )
5827
+ : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5828
+
5829
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5830
+ return createCanceledResponse();
5659
5831
  }
5660
- }
5661
-
5662
- NativeRequestContext requestContext = new NativeRequestContext(
5663
- originalUrl,
5664
- method,
5665
- requestHeaders,
5666
- base64Body,
5667
- request.isForMainFrame(),
5668
- credentialsMode
5669
- );
5670
-
5671
- if (
5672
- ProxyRequestSupport.shouldLetWebViewHandleMissingBody(requestUrl, requestContext.method, requestContext.base64Body)
5673
- ) {
5674
- Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5675
- return null;
5676
- }
5677
5832
 
5678
- boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5679
- boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5680
- _options,
5681
- requestContext.url
5682
- );
5683
- NativeResponseData directResponseData = null;
5684
-
5685
- NativeProxyRule outboundRule =
5686
- legacyProxyMode && shouldDelegateLegacyRequest
5687
- ? new NativeProxyRule(
5688
- null,
5689
- null,
5690
- null,
5691
- null,
5692
- null,
5693
- null,
5694
- null,
5695
- null,
5696
- false,
5697
- NativeProxyRule.Action.DELEGATE_TO_JS
5698
- )
5699
- : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5700
-
5701
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5702
- return createCanceledResponse();
5703
- }
5704
-
5705
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5706
- String proxyId = UUID.randomUUID().toString();
5707
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5708
- proxiedRequest.requestContext = requestContext;
5709
- addProxiedRequest(proxyId, proxiedRequest);
5710
-
5711
- String dialogId = instanceId != null ? instanceId : "";
5712
- _options
5713
- .getCallbacks()
5714
- .proxyRequestEvent(
5715
- proxyId,
5716
- "outbound",
5717
- requestContext.url,
5718
- requestContext.method,
5719
- serializeHeaders(requestContext.headers),
5720
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5721
- null,
5722
- null,
5723
- null,
5724
- dialogId
5725
- );
5833
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5834
+ String proxyId = UUID.randomUUID().toString();
5835
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5836
+ proxiedRequest.requestContext = requestContext;
5837
+ addProxiedRequest(proxyId, proxiedRequest);
5838
+
5839
+ String dialogId = instanceId != null ? instanceId : "";
5840
+ _options
5841
+ .getCallbacks()
5842
+ .proxyRequestEvent(
5843
+ proxyId,
5844
+ "outbound",
5845
+ requestContext.url,
5846
+ requestContext.method,
5847
+ serializeHeaders(requestContext.headers),
5848
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5849
+ null,
5850
+ null,
5851
+ null,
5852
+ dialogId
5853
+ );
5726
5854
 
5727
- try {
5728
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5729
- if (proxiedRequest.canceled) {
5730
- return createCanceledResponse();
5731
- }
5732
- if (proxiedRequest.response != null) {
5733
- return proxiedRequest.response;
5734
- }
5735
- if (proxiedRequest.nativeResponse != null) {
5736
- directResponseData = proxiedRequest.nativeResponse;
5855
+ try {
5856
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5857
+ if (proxiedRequest.canceled) {
5858
+ return createCanceledResponse();
5859
+ }
5860
+ if (proxiedRequest.response != null) {
5861
+ return proxiedRequest.response;
5862
+ }
5863
+ if (proxiedRequest.nativeResponse != null) {
5864
+ directResponseData = proxiedRequest.nativeResponse;
5865
+ } else {
5866
+ requestContext =
5867
+ proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5868
+ }
5737
5869
  } else {
5738
- requestContext = proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5739
- }
5740
- } else {
5741
- synchronized (proxiedRequest) {
5742
- proxiedRequest.timedOut = true;
5870
+ synchronized (proxiedRequest) {
5871
+ proxiedRequest.timedOut = true;
5872
+ }
5873
+ removeProxiedRequest(proxyId);
5874
+ Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5743
5875
  }
5876
+ } catch (InterruptedException error) {
5744
5877
  removeProxiedRequest(proxyId);
5745
- Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5878
+ Thread.currentThread().interrupt();
5879
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5880
+ return bridgeBackedRequest ? createCanceledResponse() : null;
5746
5881
  }
5747
- } catch (InterruptedException error) {
5748
- removeProxiedRequest(proxyId);
5749
- Thread.currentThread().interrupt();
5750
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5751
- return bridgeBackedRequest ? createCanceledResponse() : null;
5752
5882
  }
5753
- }
5754
5883
 
5755
- NativeResponseData nativeResponse = directResponseData;
5756
- if (nativeResponse == null) {
5757
- try {
5758
- nativeResponse = performNativeRequest(requestContext);
5759
- } catch (IOException error) {
5760
- Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5761
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5884
+ NativeResponseData nativeResponse = directResponseData;
5885
+ if (nativeResponse == null) {
5886
+ try {
5887
+ nativeResponse = performNativeRequest(requestContext);
5888
+ } catch (IOException error) {
5889
+ Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5890
+ return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5891
+ }
5762
5892
  }
5763
- }
5764
5893
 
5765
- int redirectsFollowed = 0;
5766
- while (true) {
5767
- NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5768
- if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
5894
+ int redirectsFollowed = 0;
5895
+ while (true) {
5896
+ NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5897
+ if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
5898
+ RedirectReplayResult redirectReplay;
5899
+ try {
5900
+ redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
5901
+ } catch (IOException error) {
5902
+ Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
5903
+ return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5904
+ }
5905
+ if (redirectReplay != null) {
5906
+ requestContext = redirectReplay.requestContext;
5907
+ nativeResponse = redirectReplay.responseData;
5908
+ redirectsFollowed++;
5909
+ continue;
5910
+ }
5911
+ return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5912
+ }
5913
+ if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5914
+ return createCanceledResponse();
5915
+ }
5916
+
5917
+ String proxyId = UUID.randomUUID().toString();
5918
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5919
+ proxiedRequest.requestContext = requestContext;
5920
+ proxiedRequest.nativeResponse = nativeResponse;
5921
+ addProxiedRequest(proxyId, proxiedRequest);
5922
+
5923
+ String dialogId = instanceId != null ? instanceId : "";
5924
+ _options
5925
+ .getCallbacks()
5926
+ .proxyRequestEvent(
5927
+ proxyId,
5928
+ "inbound",
5929
+ requestContext.url,
5930
+ requestContext.method,
5931
+ serializeHeaders(requestContext.headers),
5932
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5933
+ nativeResponse.statusCode,
5934
+ serializeHeaders(nativeResponse.headers),
5935
+ Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5936
+ dialogId
5937
+ );
5938
+
5939
+ try {
5940
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5941
+ if (proxiedRequest.canceled) {
5942
+ return createCanceledResponse();
5943
+ }
5944
+ if (proxiedRequest.response != null) {
5945
+ return proxiedRequest.response;
5946
+ }
5947
+ if (proxiedRequest.nativeResponse != null) {
5948
+ nativeResponse = proxiedRequest.nativeResponse;
5949
+ }
5950
+ } else {
5951
+ synchronized (proxiedRequest) {
5952
+ proxiedRequest.timedOut = true;
5953
+ }
5954
+ removeProxiedRequest(proxyId);
5955
+ }
5956
+ } catch (InterruptedException error) {
5957
+ Thread.currentThread().interrupt();
5958
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5959
+ }
5960
+
5769
5961
  RedirectReplayResult redirectReplay;
5770
5962
  try {
5771
5963
  redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
@@ -5781,69 +5973,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5781
5973
  }
5782
5974
  return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5783
5975
  }
5784
- if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5785
- return createCanceledResponse();
5786
- }
5787
-
5788
- String proxyId = UUID.randomUUID().toString();
5789
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5790
- proxiedRequest.requestContext = requestContext;
5791
- proxiedRequest.nativeResponse = nativeResponse;
5792
- addProxiedRequest(proxyId, proxiedRequest);
5793
-
5794
- String dialogId = instanceId != null ? instanceId : "";
5795
- _options
5796
- .getCallbacks()
5797
- .proxyRequestEvent(
5798
- proxyId,
5799
- "inbound",
5800
- requestContext.url,
5801
- requestContext.method,
5802
- serializeHeaders(requestContext.headers),
5803
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5804
- nativeResponse.statusCode,
5805
- serializeHeaders(nativeResponse.headers),
5806
- Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5807
- dialogId
5808
- );
5809
-
5810
- try {
5811
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5812
- if (proxiedRequest.canceled) {
5813
- return createCanceledResponse();
5814
- }
5815
- if (proxiedRequest.response != null) {
5816
- return proxiedRequest.response;
5817
- }
5818
- if (proxiedRequest.nativeResponse != null) {
5819
- nativeResponse = proxiedRequest.nativeResponse;
5820
- }
5821
- } else {
5822
- synchronized (proxiedRequest) {
5823
- proxiedRequest.timedOut = true;
5824
- }
5825
- removeProxiedRequest(proxyId);
5826
- }
5827
- } catch (InterruptedException error) {
5828
- Thread.currentThread().interrupt();
5829
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5830
- }
5831
-
5832
- RedirectReplayResult redirectReplay;
5833
- try {
5834
- redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
5835
- } catch (IOException error) {
5836
- Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
5837
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5838
- }
5839
- if (redirectReplay != null) {
5840
- requestContext = redirectReplay.requestContext;
5841
- nativeResponse = redirectReplay.responseData;
5842
- redirectsFollowed++;
5843
- continue;
5844
- }
5845
- return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5846
5976
  }
5977
+
5978
+ return interceptBundledAssetRequest(request);
5847
5979
  }
5848
5980
 
5849
5981
  @Override
@@ -6192,11 +6324,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6192
6324
  }
6193
6325
 
6194
6326
  // Clear any pending callbacks to prevent memory leaks
6195
- if (mFilePathCallback != null) {
6196
- mFilePathCallback.onReceiveValue(null);
6197
- mFilePathCallback = null;
6327
+ if (activeFileChooserRequest != null) {
6328
+ FileChooserRequestSupport.cancel(activeFileChooserRequest);
6329
+ activeFileChooserRequest = null;
6330
+ syncFileChooserPublicFields();
6198
6331
  }
6199
- tempCameraUri = null;
6200
6332
 
6201
6333
  // Clear file inputs for security/privacy before destroying WebView
6202
6334
  try {