@capgo/capacitor-inappbrowser 8.14.2 → 8.15.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.
@@ -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();
@@ -358,6 +361,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
358
361
  private boolean cachedTitleIconResolved;
359
362
  private boolean isHiddenModeActive = false;
360
363
  private boolean toolbarHideInProgress = false;
364
+ private int injectedSafeAreaTop = Integer.MIN_VALUE;
365
+ private int injectedSafeAreaBottom = Integer.MIN_VALUE;
366
+ private int injectedSafeAreaLeft = Integer.MIN_VALUE;
367
+ private int injectedSafeAreaRight = Integer.MIN_VALUE;
361
368
  private boolean configurationCallbacksRegistered = false;
362
369
  private Configuration lastConfiguration;
363
370
  private final ComponentCallbacks2 configurationCallbacks = new ComponentCallbacks2() {
@@ -675,6 +682,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
675
682
  if (isShowing()) {
676
683
  super.hide();
677
684
  }
685
+ refreshInsetsForHostingLayer();
678
686
  return true;
679
687
  }
680
688
 
@@ -688,6 +696,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
688
696
  show();
689
697
  }
690
698
  applyDimensions();
699
+ refreshInsetsForHostingLayer();
700
+ }
701
+
702
+ /**
703
+ * The container padding is computed for the window hosting the content, and the two windows inset
704
+ * differently: the dialog is edge-to-edge on Android 15+, while the host activity fits its own
705
+ * system windows. Recompute after a layer change so padding from the previous host is not left
706
+ * behind (which would stack on top of the host offset).
707
+ */
708
+ private void refreshInsetsForHostingLayer() {
709
+ reapplyInsetsFromWindowRoot();
710
+ mainHandler.post(this::reapplyInsetsFromWindowRoot);
691
711
  }
692
712
 
693
713
  private boolean showAccordingToLayerMode() {
@@ -2215,6 +2235,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2215
2235
  _webView.addJavascriptInterface(proxyBridge, "__capgoProxy");
2216
2236
  proxyBridgeScript = loadProxyBridgeScript();
2217
2237
  }
2238
+ ensureBundledAssetLoader();
2218
2239
  _webView.getSettings().setJavaScriptEnabled(true);
2219
2240
  _webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
2220
2241
  _webView.getSettings().setDatabaseEnabled(true);
@@ -3087,8 +3108,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3087
3108
 
3088
3109
  requestSafeAreaInsets();
3089
3110
 
3090
- mainHandler.postDelayed(this::applyContainerInsetsSnapshot, 300);
3091
- mainHandler.postDelayed(this::applyContainerInsetsSnapshot, 1200);
3111
+ mainHandler.postDelayed(this::reapplyInsetsFromWindowRoot, 300);
3112
+ mainHandler.postDelayed(this::reapplyInsetsFromWindowRoot, 1200);
3092
3113
  }
3093
3114
 
3094
3115
  private void registerConfigurationCallbacks() {
@@ -3312,15 +3333,49 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3312
3333
  statusBarColorView.setVisibility(View.VISIBLE);
3313
3334
  }
3314
3335
 
3315
- ViewGroup.LayoutParams appBarParams = appBarLayout.getLayoutParams();
3316
- if (appBarParams instanceof ViewGroup.MarginLayoutParams marginParams) {
3317
- marginParams.topMargin = statusBarHeight;
3318
- appBarLayout.setLayoutParams(marginParams);
3319
- }
3336
+ applyAppBarTopInset(appBarLayout, appBarHandlesTopInset(toolbarView) ? statusBarHeight : 0);
3320
3337
  appBarLayout.setBackgroundColor(finalBgColor);
3321
3338
  });
3322
3339
  }
3323
3340
 
3341
+ /**
3342
+ * Whether a visible AppBarLayout consumes the top inset itself. It is the only alternative to
3343
+ * padding the WebView container for the status bar, so both mechanisms share this condition and
3344
+ * can never inset the top twice.
3345
+ */
3346
+ private boolean appBarHandlesTopInset(View toolbarView) {
3347
+ return (
3348
+ Build.VERSION.SDK_INT >= 35 &&
3349
+ _options != null &&
3350
+ !TextUtils.equals(_options.getToolbarType(), "blank") &&
3351
+ toolbarView != null &&
3352
+ toolbarView.getVisibility() == View.VISIBLE &&
3353
+ toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout
3354
+ );
3355
+ }
3356
+
3357
+ /**
3358
+ * The appbar must sit below the status bar on edge-to-edge windows. A top margin does that
3359
+ * visually, but CoordinatorLayout's scrolling-view behavior sizes the content container from the
3360
+ * appbar height only and positions it below the appbar margin, so a margin pushes the container
3361
+ * bottom off-screen by the status-bar height (#641). Padding grows the appbar height instead,
3362
+ * which the behavior does account for, keeping the container inside the window.
3363
+ */
3364
+ private void applyAppBarTopInset(com.google.android.material.appbar.AppBarLayout appBarLayout, int statusBarTop) {
3365
+ ViewGroup.LayoutParams appBarParams = appBarLayout.getLayoutParams();
3366
+ if (appBarParams instanceof ViewGroup.MarginLayoutParams marginParams && marginParams.topMargin != 0) {
3367
+ marginParams.topMargin = 0;
3368
+ appBarLayout.setLayoutParams(marginParams);
3369
+ }
3370
+
3371
+ appBarLayout.setPadding(
3372
+ appBarLayout.getPaddingLeft(),
3373
+ Math.max(0, statusBarTop),
3374
+ appBarLayout.getPaddingRight(),
3375
+ appBarLayout.getPaddingBottom()
3376
+ );
3377
+ }
3378
+
3324
3379
  private void applyWindowInsetsToWebView(WindowInsetsCompat windowInsets, boolean isAndroid15Plus, View toolbarView) {
3325
3380
  if (windowInsets == null || _options == null) {
3326
3381
  return;
@@ -3333,53 +3388,73 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3333
3388
  Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
3334
3389
  boolean keyboardVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime());
3335
3390
 
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(
3391
+ applySafeAreaInsets(
3344
3392
  bars,
3345
3393
  navigationBars,
3346
3394
  systemGestures,
3347
3395
  mandatoryGestures,
3348
3396
  ime,
3349
3397
  keyboardVisible,
3350
- appBarHandlesTopInset,
3398
+ appBarHandlesTopInset(toolbarView),
3351
3399
  isAndroid15Plus
3352
3400
  );
3353
3401
  }
3354
3402
 
3355
3403
  /**
3356
- * Re-read root window insets and apply WebView margins plus container padding.
3404
+ * Re-read root window insets and apply the container padding.
3357
3405
  * Dialog windows may not re-dispatch inset listeners after rotation, leaving stale
3358
- * portrait margins that break vertical scrolling in landscape.
3406
+ * portrait insets that break vertical scrolling in landscape.
3359
3407
  */
3360
3408
  private void reapplyInsetsFromWindowRoot() {
3361
3409
  if (_webView == null || _options == null) {
3362
3410
  return;
3363
3411
  }
3364
3412
 
3365
- Window window = getWindow();
3366
- View decorView = window != null ? window.getDecorView() : null;
3367
- if (decorView == null) {
3413
+ View insetsSourceView = resolveHostedInsetsSourceView();
3414
+ if (insetsSourceView == null) {
3368
3415
  return;
3369
3416
  }
3370
3417
 
3371
- WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);
3418
+ WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(insetsSourceView);
3372
3419
  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.
3420
+ // Root insets not ready yet; do not mutate the container padding. Delayed retries and
3421
+ // requestApplyInsets will retry.
3375
3422
  return;
3376
3423
  }
3377
3424
 
3378
3425
  boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3379
- View toolbarView = findViewById(R.id.tool_bar);
3426
+ View toolbarView = findBrowserContentDescendant(R.id.tool_bar);
3380
3427
  applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3381
3428
  }
3382
3429
 
3430
+ /**
3431
+ * Insets must come from the window that currently hosts the content. In back-layer mode the
3432
+ * content lives in the host activity's window and this dialog's window is hidden, so its decor
3433
+ * would report stale insets.
3434
+ */
3435
+ private View resolveHostedInsetsSourceView() {
3436
+ if (backLayerActive) {
3437
+ View contentView = getBrowserContentView();
3438
+ if (contentView != null && contentView.isAttachedToWindow()) {
3439
+ return contentView;
3440
+ }
3441
+ }
3442
+
3443
+ Window window = getWindow();
3444
+ return window != null ? window.getDecorView() : null;
3445
+ }
3446
+
3447
+ /**
3448
+ * Back-layer mode reparents the browser content out of the dialog window, so {@link
3449
+ * #findViewById(int)} no longer reaches it. Resolve through the content view, which owns these
3450
+ * views in both layers.
3451
+ */
3452
+ private View findBrowserContentDescendant(int id) {
3453
+ View contentView = getBrowserContentView();
3454
+ View view = contentView != null ? contentView.findViewById(id) : null;
3455
+ return view != null ? view : findViewById(id);
3456
+ }
3457
+
3383
3458
  private void requestWebViewContentRelayout() {
3384
3459
  View container = findViewById(R.id.content_browser_layout);
3385
3460
  if (container != null) {
@@ -3395,7 +3470,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3395
3470
  _webView.invalidate();
3396
3471
  }
3397
3472
 
3398
- private void applySafeAreaMargins(
3473
+ /**
3474
+ * The WebView is the content child of a SwipeRefreshLayout, which lays that child out inside its
3475
+ * own padding and ignores child margins entirely. Safe-area insets are therefore applied as
3476
+ * padding on the container instead of margins on the WebView (#641).
3477
+ */
3478
+ private void applySafeAreaInsets(
3399
3479
  Insets bars,
3400
3480
  Insets navigationBars,
3401
3481
  Insets systemGestures,
@@ -3403,88 +3483,25 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3403
3483
  Insets ime,
3404
3484
  boolean keyboardVisible,
3405
3485
  boolean appBarHandlesTopInset,
3406
- boolean applyImeAsLayoutMargin
3486
+ boolean isEdgeToEdge
3407
3487
  ) {
3408
3488
  if (_webView == null || _options == null) {
3409
3489
  return;
3410
3490
  }
3411
3491
 
3412
- ViewGroup.LayoutParams layoutParams = _webView.getLayoutParams();
3413
- if (!(layoutParams instanceof ViewGroup.MarginLayoutParams mlp)) {
3492
+ View container = findBrowserContentDescendant(R.id.content_browser_layout);
3493
+ if (container == null) {
3414
3494
  return;
3415
3495
  }
3416
3496
 
3417
- int fallbackBottomInset = _options.getEnabledSafeMargin() ? getSystemNavigationBarHeight() : 0;
3418
- int safeBottomInset = SafeAreaInsetsSupport.resolveSafeBottomInsetWithFallback(
3497
+ boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isEdgeToEdge);
3498
+ int statusBarTop = SafeAreaInsetsSupport.resolveStatusBarTop(
3499
+ bars.top,
3419
3500
  bars.bottom,
3420
- navigationBars.bottom,
3421
- systemGestures.bottom,
3422
- mandatoryGestures.bottom,
3423
3501
  bars.left,
3424
3502
  bars.right,
3425
- navigationBars.left,
3426
- navigationBars.right,
3427
- fallbackBottomInset,
3428
- _options.getEnabledSafeMargin()
3429
- );
3430
- // Android 15+ uses edge-to-edge (decorFitsSystemWindows=false) and needs IME as margin.
3431
- // Older dialogs still resize for the keyboard; re-applying decor IME creates a black gap (#622).
3432
- int imeBottom = SafeAreaInsetsSupport.resolveImeBottomInset(keyboardVisible, ime.bottom, applyImeAsLayoutMargin);
3433
- boolean applyTopFallback = _options.getEnabledSafeTopMargin() && _options.getUseTopInset();
3434
- int fallbackTopInset = applyTopFallback ? getSystemStatusBarHeight() : 0;
3435
- int navTop = SafeAreaInsetsSupport.resolveTopMarginWithFallback(
3436
- _options.getEnabledSafeTopMargin(),
3437
- _options.getUseTopInset(),
3438
- bars.top,
3439
- appBarHandlesTopInset,
3440
- fallbackTopInset,
3441
- applyTopFallback
3503
+ getSystemStatusBarHeight()
3442
3504
  );
3443
- int bottomMargin = SafeAreaInsetsSupport.resolveBottomMargin(_options.getEnabledSafeMargin(), safeBottomInset, imeBottom);
3444
-
3445
- mlp.topMargin = navTop;
3446
- mlp.bottomMargin = bottomMargin;
3447
- mlp.leftMargin = bars.left;
3448
- mlp.rightMargin = bars.right;
3449
- _webView.setLayoutParams(mlp);
3450
- injectSafeAreaCssVariables(navTop, bottomMargin, bars.left, bars.right);
3451
- applyContainerInsetsSnapshot();
3452
- }
3453
-
3454
- private void applyContainerInsetsSnapshot() {
3455
- if (_options == null || _webView == null) {
3456
- return;
3457
- }
3458
-
3459
- View container = findViewById(R.id.content_browser_layout);
3460
- if (container == null) {
3461
- return;
3462
- }
3463
-
3464
- Window window = getWindow();
3465
- View decorView = window != null ? window.getDecorView() : null;
3466
- WindowInsetsCompat windowInsets = decorView != null ? ViewCompat.getRootWindowInsets(decorView) : null;
3467
-
3468
- Insets bars = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) : Insets.NONE;
3469
- Insets navigationBars = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()) : Insets.NONE;
3470
- Insets systemGestures = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()) : Insets.NONE;
3471
- Insets mandatoryGestures =
3472
- windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.mandatorySystemGestures()) : Insets.NONE;
3473
- Insets ime = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.ime()) : Insets.NONE;
3474
- boolean keyboardVisible = windowInsets != null && windowInsets.isVisible(WindowInsetsCompat.Type.ime());
3475
-
3476
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3477
- View toolbarView = findViewById(R.id.tool_bar);
3478
- boolean appBarHandlesTopInset =
3479
- isAndroid15Plus &&
3480
- !TextUtils.equals(_options.getToolbarType(), "blank") &&
3481
- toolbarView != null &&
3482
- toolbarView.getVisibility() == View.VISIBLE &&
3483
- toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout;
3484
-
3485
- boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isAndroid15Plus);
3486
-
3487
- int statusBarTop = bars.top > 0 ? bars.top : getSystemStatusBarHeight();
3488
3505
  int fallbackBottomInset = applyBottomInset ? getSystemNavigationBarHeight() : 0;
3489
3506
  int safeBottomInset = SafeAreaInsetsSupport.resolveSafeBottomInsetWithFallback(
3490
3507
  bars.bottom,
@@ -3498,32 +3515,41 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3498
3515
  fallbackBottomInset,
3499
3516
  applyBottomInset
3500
3517
  );
3518
+ // Android 15+ uses edge-to-edge (decorFitsSystemWindows=false) and needs the IME inset applied.
3519
+ // Older dialogs still resize for the keyboard; re-applying decor IME creates a black gap (#622).
3520
+ int imeBottom = SafeAreaInsetsSupport.resolveImeBottomInset(keyboardVisible, ime.bottom, isEdgeToEdge);
3501
3521
 
3522
+ // In back-layer mode the content is reparented into the host activity's window, which this
3523
+ // plugin never puts in edge-to-edge, so the host owns the top inset and the useTopInset opt-in
3524
+ // still decides there.
3502
3525
  int padTop = SafeAreaInsetsSupport.resolveContainerTopPadding(
3503
3526
  _options.getEnabledSafeTopMargin(),
3504
3527
  _options.getUseTopInset(),
3505
3528
  statusBarTop,
3506
- appBarHandlesTopInset
3507
- );
3508
- int imeBottom = SafeAreaInsetsSupport.resolveImeBottomInset(keyboardVisible, ime.bottom, isAndroid15Plus);
3509
- int padBottom = SafeAreaInsetsSupport.resolveContainerBottomPadding(
3510
- applyBottomInset,
3511
- safeBottomInset,
3512
- imeBottom,
3513
3529
  appBarHandlesTopInset,
3514
- statusBarTop
3530
+ isEdgeToEdge && !backLayerActive
3515
3531
  );
3532
+ int padBottom = SafeAreaInsetsSupport.resolveContainerBottomPadding(applyBottomInset, safeBottomInset, imeBottom);
3516
3533
 
3517
- if (
3518
- container.getPaddingLeft() == bars.left &&
3519
- container.getPaddingTop() == padTop &&
3520
- container.getPaddingRight() == bars.right &&
3521
- container.getPaddingBottom() == padBottom
3522
- ) {
3523
- return;
3534
+ if (appBarHandlesTopInset) {
3535
+ // Keep the appbar inset in sync with the reported inset (cutouts, rotation, multi-window)
3536
+ // instead of the status_bar_height resource used for the initial layout.
3537
+ View toolbarView = findBrowserContentDescendant(R.id.tool_bar);
3538
+ if (toolbarView != null && toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout appBarLayout) {
3539
+ applyAppBarTopInset(appBarLayout, statusBarTop);
3540
+ }
3541
+ }
3542
+
3543
+ boolean paddingChanged =
3544
+ container.getPaddingLeft() != bars.left ||
3545
+ container.getPaddingTop() != padTop ||
3546
+ container.getPaddingRight() != bars.right ||
3547
+ container.getPaddingBottom() != padBottom;
3548
+ if (paddingChanged) {
3549
+ container.setPadding(bars.left, padTop, bars.right, padBottom);
3524
3550
  }
3525
3551
 
3526
- container.setPadding(bars.left, padTop, bars.right, padBottom);
3552
+ injectSafeAreaCssVariables(padTop, padBottom, bars.left, bars.right);
3527
3553
  }
3528
3554
 
3529
3555
  private void configureBlankToolbarLayout() {
@@ -3554,27 +3580,62 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3554
3580
  return getContext().getResources().getDimensionPixelSize(resourceId);
3555
3581
  }
3556
3582
 
3583
+ private void resetInjectedSafeAreaCssVariables() {
3584
+ injectedSafeAreaTop = Integer.MIN_VALUE;
3585
+ injectedSafeAreaBottom = Integer.MIN_VALUE;
3586
+ injectedSafeAreaLeft = Integer.MIN_VALUE;
3587
+ injectedSafeAreaRight = Integer.MIN_VALUE;
3588
+ }
3589
+
3557
3590
  private void injectSafeAreaCssVariables(int top, int bottom, int left, int right) {
3558
3591
  if (_webView == null) {
3559
3592
  return;
3560
3593
  }
3561
3594
 
3595
+ // Insets are dispatched on every layout pass; only touch the document when they differ from
3596
+ // what it already holds. Cleared on page load so a new document is served again.
3597
+ if (
3598
+ injectedSafeAreaTop == top && injectedSafeAreaBottom == bottom && injectedSafeAreaLeft == left && injectedSafeAreaRight == right
3599
+ ) {
3600
+ return;
3601
+ }
3602
+
3603
+ // Recorded before posting so passes that repeat within the same frame are suppressed too.
3604
+ injectedSafeAreaTop = top;
3605
+ injectedSafeAreaBottom = bottom;
3606
+ injectedSafeAreaLeft = left;
3607
+ injectedSafeAreaRight = right;
3608
+
3562
3609
  String script = String.format(
3563
3610
  Locale.US,
3564
3611
  "(function(){var root=document.documentElement;" +
3565
3612
  "root.style.setProperty('--safe-area-inset-top','%dpx');" +
3566
3613
  "root.style.setProperty('--safe-area-inset-bottom','%dpx');" +
3567
3614
  "root.style.setProperty('--safe-area-inset-left','%dpx');" +
3568
- "root.style.setProperty('--safe-area-inset-right','%dpx');})();",
3615
+ "root.style.setProperty('--safe-area-inset-right','%dpx');" +
3616
+ "return root.style.getPropertyValue('--safe-area-inset-top')?1:0;})();",
3569
3617
  top,
3570
3618
  bottom,
3571
3619
  left,
3572
3620
  right
3573
3621
  );
3574
3622
  _webView.post(() -> {
3575
- if (_webView != null) {
3576
- _webView.evaluateJavascript(script, null);
3623
+ if (_webView == null) {
3624
+ resetInjectedSafeAreaCssVariables();
3625
+ return;
3577
3626
  }
3627
+ _webView.evaluateJavascript(script, (value) -> {
3628
+ boolean cacheStillHoldsTheseValues =
3629
+ injectedSafeAreaTop == top &&
3630
+ injectedSafeAreaBottom == bottom &&
3631
+ injectedSafeAreaLeft == left &&
3632
+ injectedSafeAreaRight == right;
3633
+ // The document did not take the variables, so let a later pass inject them again.
3634
+ // A newer injection may already have replaced them, in which case it owns the cache.
3635
+ if (!"1".equals(value) && cacheStillHoldsTheseValues) {
3636
+ resetInjectedSafeAreaCssVariables();
3637
+ }
3638
+ });
3578
3639
  });
3579
3640
  }
3580
3641
 
@@ -4307,6 +4368,60 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4307
4368
  return proxyHeaders;
4308
4369
  }
4309
4370
 
4371
+ private void ensureBundledAssetLoader() {
4372
+ synchronized (bundledAssetLoaderLock) {
4373
+ ensureBundledAssetLoaderLocked();
4374
+ }
4375
+ }
4376
+
4377
+ private void ensureBundledAssetLoaderLocked() {
4378
+ if (bundledAssetLoader != null || _options == null || !_options.getServeBundledAssets()) {
4379
+ return;
4380
+ }
4381
+
4382
+ bundledAssetLoader = BundledAssetSupport.createAssetLoader(
4383
+ _context,
4384
+ _options.getBundledAssetHost(),
4385
+ _options.getBundledAssetScheme()
4386
+ );
4387
+ }
4388
+
4389
+ public void applyBundledAssetResolution(BundledAssetSupport.Resolution resolution, String localUrl) {
4390
+ if (_options == null || resolution == null) {
4391
+ return;
4392
+ }
4393
+
4394
+ synchronized (bundledAssetLoaderLock) {
4395
+ bundledAssetLoader = null;
4396
+ _options.setUrl(resolution.url);
4397
+ if (resolution.needsAssetLoader) {
4398
+ BundledAssetSupport.LocalConfig localConfig = BundledAssetSupport.parseLocalConfig(localUrl);
4399
+ _options.setBundledAssetHost(localConfig != null ? localConfig.host : "localhost");
4400
+ _options.setBundledAssetScheme(localConfig != null ? BundledAssetSupport.assetLoaderScheme(localConfig) : "https");
4401
+ _options.setServeBundledAssets(true);
4402
+ } else {
4403
+ _options.setServeBundledAssets(false);
4404
+ }
4405
+ }
4406
+ }
4407
+
4408
+ private WebResourceResponse interceptBundledAssetRequest(WebResourceRequest request) {
4409
+ final WebViewAssetLoader loader;
4410
+ synchronized (bundledAssetLoaderLock) {
4411
+ if (_options == null || !_options.getServeBundledAssets()) {
4412
+ return null;
4413
+ }
4414
+
4415
+ ensureBundledAssetLoaderLocked();
4416
+ loader = bundledAssetLoader;
4417
+ }
4418
+
4419
+ if (loader == null) {
4420
+ return null;
4421
+ }
4422
+ return loader.shouldInterceptRequest(request.getUrl());
4423
+ }
4424
+
4310
4425
  public void setUrl(String url) {
4311
4426
  if (_webView == null) {
4312
4427
  Log.w("InAppBrowser", "Cannot set URL - WebView is null");
@@ -5529,186 +5644,254 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5529
5644
  if (view == null || _webView == null) {
5530
5645
  return null;
5531
5646
  }
5532
- if (!shouldUseNativeProxy()) {
5533
- return null;
5534
- }
5535
5647
 
5536
- String requestUrl = request.getUrl().toString();
5537
- boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5538
- String originalUrl;
5539
- String method;
5540
- Map<String, String> requestHeaders = new HashMap<>();
5541
- String base64Body = "";
5542
- String credentialsMode = "same-origin";
5543
-
5544
- if (bridgeBackedRequest) {
5545
- Uri uri = request.getUrl();
5546
- originalUrl = uri.getQueryParameter("u");
5547
- String requestId = uri.getQueryParameter("rid");
5548
- if (originalUrl == null || requestId == null) {
5549
- return null;
5550
- }
5648
+ if (shouldUseNativeProxy()) {
5649
+ String requestUrl = request.getUrl().toString();
5650
+ boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5651
+ String originalUrl;
5652
+ String method;
5653
+ Map<String, String> requestHeaders = new HashMap<>();
5654
+ String base64Body = "";
5655
+ String credentialsMode = "same-origin";
5656
+
5657
+ if (bridgeBackedRequest) {
5658
+ Uri uri = request.getUrl();
5659
+ originalUrl = uri.getQueryParameter("u");
5660
+ String requestId = uri.getQueryParameter("rid");
5661
+ if (originalUrl == null || requestId == null) {
5662
+ return null;
5663
+ }
5551
5664
 
5552
- if (
5553
- ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5554
- !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5555
- ) {
5556
- if (proxyBridge != null) {
5557
- proxyBridge.getAndRemove(requestId);
5665
+ if (
5666
+ ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5667
+ !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5668
+ ) {
5669
+ if (proxyBridge != null) {
5670
+ proxyBridge.getAndRemove(requestId);
5671
+ }
5672
+ Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5673
+ return createCanceledResponse();
5558
5674
  }
5559
- Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5560
- return createCanceledResponse();
5561
- }
5562
5675
 
5563
- ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5564
- if (stored == null) {
5565
- Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5566
- return createCanceledResponse();
5567
- }
5568
- method = stored.method;
5569
- base64Body = stored.base64Body;
5570
- credentialsMode = stored.credentialsMode;
5571
- Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(request.getRequestHeaders());
5572
- try {
5573
- requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5574
- requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5575
- } catch (JSONException error) {
5576
- Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5577
- return createCanceledResponse();
5578
- }
5579
- String initiatorUrl = request.getRequestHeaders().get("Referer");
5580
- if (initiatorUrl == null || initiatorUrl.isBlank()) {
5581
- initiatorUrl = getWebViewUrlOrFallback(
5582
- _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5676
+ ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5677
+ if (stored == null) {
5678
+ Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5679
+ return createCanceledResponse();
5680
+ }
5681
+ method = stored.method;
5682
+ base64Body = stored.base64Body;
5683
+ credentialsMode = stored.credentialsMode;
5684
+ Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(
5685
+ request.getRequestHeaders()
5583
5686
  );
5687
+ try {
5688
+ requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5689
+ requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5690
+ } catch (JSONException error) {
5691
+ Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5692
+ return createCanceledResponse();
5693
+ }
5694
+ String initiatorUrl = request.getRequestHeaders().get("Referer");
5695
+ if (initiatorUrl == null || initiatorUrl.isBlank()) {
5696
+ initiatorUrl = getWebViewUrlOrFallback(
5697
+ _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5698
+ );
5699
+ }
5700
+ String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5701
+ if (
5702
+ targetCookies != null &&
5703
+ !targetCookies.isBlank() &&
5704
+ ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5705
+ ) {
5706
+ requestHeaders.put("Cookie", targetCookies);
5707
+ }
5708
+ } else {
5709
+ if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5710
+ return interceptBundledAssetRequest(request);
5711
+ }
5712
+
5713
+ originalUrl = requestUrl;
5714
+ method = request.getMethod();
5715
+ if (request.getRequestHeaders() != null) {
5716
+ requestHeaders.putAll(request.getRequestHeaders());
5717
+ }
5584
5718
  }
5585
- String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5719
+
5720
+ NativeRequestContext requestContext = new NativeRequestContext(
5721
+ originalUrl,
5722
+ method,
5723
+ requestHeaders,
5724
+ base64Body,
5725
+ request.isForMainFrame(),
5726
+ credentialsMode
5727
+ );
5728
+
5586
5729
  if (
5587
- targetCookies != null &&
5588
- !targetCookies.isBlank() &&
5589
- ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5730
+ ProxyRequestSupport.shouldLetWebViewHandleMissingBody(
5731
+ requestUrl,
5732
+ requestContext.method,
5733
+ requestContext.base64Body
5734
+ )
5590
5735
  ) {
5591
- requestHeaders.put("Cookie", targetCookies);
5592
- }
5593
- } else {
5594
- if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5736
+ Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5595
5737
  return null;
5596
5738
  }
5597
5739
 
5598
- originalUrl = requestUrl;
5599
- method = request.getMethod();
5600
- if (request.getRequestHeaders() != null) {
5601
- requestHeaders.putAll(request.getRequestHeaders());
5740
+ boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5741
+ boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5742
+ _options,
5743
+ requestContext.url
5744
+ );
5745
+ NativeResponseData directResponseData = null;
5746
+
5747
+ NativeProxyRule outboundRule =
5748
+ legacyProxyMode && shouldDelegateLegacyRequest
5749
+ ? new NativeProxyRule(
5750
+ null,
5751
+ null,
5752
+ null,
5753
+ null,
5754
+ null,
5755
+ null,
5756
+ null,
5757
+ null,
5758
+ false,
5759
+ NativeProxyRule.Action.DELEGATE_TO_JS
5760
+ )
5761
+ : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5762
+
5763
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5764
+ return createCanceledResponse();
5602
5765
  }
5603
- }
5604
-
5605
- NativeRequestContext requestContext = new NativeRequestContext(
5606
- originalUrl,
5607
- method,
5608
- requestHeaders,
5609
- base64Body,
5610
- request.isForMainFrame(),
5611
- credentialsMode
5612
- );
5613
-
5614
- if (
5615
- ProxyRequestSupport.shouldLetWebViewHandleMissingBody(requestUrl, requestContext.method, requestContext.base64Body)
5616
- ) {
5617
- Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5618
- return null;
5619
- }
5620
5766
 
5621
- boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5622
- boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5623
- _options,
5624
- requestContext.url
5625
- );
5626
- NativeResponseData directResponseData = null;
5627
-
5628
- NativeProxyRule outboundRule =
5629
- legacyProxyMode && shouldDelegateLegacyRequest
5630
- ? new NativeProxyRule(
5631
- null,
5632
- null,
5633
- null,
5634
- null,
5635
- null,
5636
- null,
5637
- null,
5638
- null,
5639
- false,
5640
- NativeProxyRule.Action.DELEGATE_TO_JS
5641
- )
5642
- : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5643
-
5644
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5645
- return createCanceledResponse();
5646
- }
5647
-
5648
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5649
- String proxyId = UUID.randomUUID().toString();
5650
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5651
- proxiedRequest.requestContext = requestContext;
5652
- addProxiedRequest(proxyId, proxiedRequest);
5653
-
5654
- String dialogId = instanceId != null ? instanceId : "";
5655
- _options
5656
- .getCallbacks()
5657
- .proxyRequestEvent(
5658
- proxyId,
5659
- "outbound",
5660
- requestContext.url,
5661
- requestContext.method,
5662
- serializeHeaders(requestContext.headers),
5663
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5664
- null,
5665
- null,
5666
- null,
5667
- dialogId
5668
- );
5767
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5768
+ String proxyId = UUID.randomUUID().toString();
5769
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5770
+ proxiedRequest.requestContext = requestContext;
5771
+ addProxiedRequest(proxyId, proxiedRequest);
5772
+
5773
+ String dialogId = instanceId != null ? instanceId : "";
5774
+ _options
5775
+ .getCallbacks()
5776
+ .proxyRequestEvent(
5777
+ proxyId,
5778
+ "outbound",
5779
+ requestContext.url,
5780
+ requestContext.method,
5781
+ serializeHeaders(requestContext.headers),
5782
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5783
+ null,
5784
+ null,
5785
+ null,
5786
+ dialogId
5787
+ );
5669
5788
 
5670
- try {
5671
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5672
- if (proxiedRequest.canceled) {
5673
- return createCanceledResponse();
5674
- }
5675
- if (proxiedRequest.response != null) {
5676
- return proxiedRequest.response;
5677
- }
5678
- if (proxiedRequest.nativeResponse != null) {
5679
- directResponseData = proxiedRequest.nativeResponse;
5789
+ try {
5790
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5791
+ if (proxiedRequest.canceled) {
5792
+ return createCanceledResponse();
5793
+ }
5794
+ if (proxiedRequest.response != null) {
5795
+ return proxiedRequest.response;
5796
+ }
5797
+ if (proxiedRequest.nativeResponse != null) {
5798
+ directResponseData = proxiedRequest.nativeResponse;
5799
+ } else {
5800
+ requestContext =
5801
+ proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5802
+ }
5680
5803
  } else {
5681
- requestContext = proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5682
- }
5683
- } else {
5684
- synchronized (proxiedRequest) {
5685
- proxiedRequest.timedOut = true;
5804
+ synchronized (proxiedRequest) {
5805
+ proxiedRequest.timedOut = true;
5806
+ }
5807
+ removeProxiedRequest(proxyId);
5808
+ Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5686
5809
  }
5810
+ } catch (InterruptedException error) {
5687
5811
  removeProxiedRequest(proxyId);
5688
- Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5812
+ Thread.currentThread().interrupt();
5813
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5814
+ return bridgeBackedRequest ? createCanceledResponse() : null;
5689
5815
  }
5690
- } catch (InterruptedException error) {
5691
- removeProxiedRequest(proxyId);
5692
- Thread.currentThread().interrupt();
5693
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5694
- return bridgeBackedRequest ? createCanceledResponse() : null;
5695
5816
  }
5696
- }
5697
5817
 
5698
- NativeResponseData nativeResponse = directResponseData;
5699
- if (nativeResponse == null) {
5700
- try {
5701
- nativeResponse = performNativeRequest(requestContext);
5702
- } catch (IOException error) {
5703
- Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5704
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5818
+ NativeResponseData nativeResponse = directResponseData;
5819
+ if (nativeResponse == null) {
5820
+ try {
5821
+ nativeResponse = performNativeRequest(requestContext);
5822
+ } catch (IOException error) {
5823
+ Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5824
+ return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5825
+ }
5705
5826
  }
5706
- }
5707
5827
 
5708
- int redirectsFollowed = 0;
5709
- while (true) {
5710
- NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5711
- if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
5828
+ int redirectsFollowed = 0;
5829
+ while (true) {
5830
+ NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5831
+ if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
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
+ }
5847
+ if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5848
+ return createCanceledResponse();
5849
+ }
5850
+
5851
+ String proxyId = UUID.randomUUID().toString();
5852
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5853
+ proxiedRequest.requestContext = requestContext;
5854
+ proxiedRequest.nativeResponse = nativeResponse;
5855
+ addProxiedRequest(proxyId, proxiedRequest);
5856
+
5857
+ String dialogId = instanceId != null ? instanceId : "";
5858
+ _options
5859
+ .getCallbacks()
5860
+ .proxyRequestEvent(
5861
+ proxyId,
5862
+ "inbound",
5863
+ requestContext.url,
5864
+ requestContext.method,
5865
+ serializeHeaders(requestContext.headers),
5866
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5867
+ nativeResponse.statusCode,
5868
+ serializeHeaders(nativeResponse.headers),
5869
+ Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5870
+ dialogId
5871
+ );
5872
+
5873
+ try {
5874
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5875
+ if (proxiedRequest.canceled) {
5876
+ return createCanceledResponse();
5877
+ }
5878
+ if (proxiedRequest.response != null) {
5879
+ return proxiedRequest.response;
5880
+ }
5881
+ if (proxiedRequest.nativeResponse != null) {
5882
+ nativeResponse = proxiedRequest.nativeResponse;
5883
+ }
5884
+ } else {
5885
+ synchronized (proxiedRequest) {
5886
+ proxiedRequest.timedOut = true;
5887
+ }
5888
+ removeProxiedRequest(proxyId);
5889
+ }
5890
+ } catch (InterruptedException error) {
5891
+ Thread.currentThread().interrupt();
5892
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5893
+ }
5894
+
5712
5895
  RedirectReplayResult redirectReplay;
5713
5896
  try {
5714
5897
  redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
@@ -5724,69 +5907,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5724
5907
  }
5725
5908
  return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5726
5909
  }
5727
- if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5728
- return createCanceledResponse();
5729
- }
5730
-
5731
- String proxyId = UUID.randomUUID().toString();
5732
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5733
- proxiedRequest.requestContext = requestContext;
5734
- proxiedRequest.nativeResponse = nativeResponse;
5735
- addProxiedRequest(proxyId, proxiedRequest);
5736
-
5737
- String dialogId = instanceId != null ? instanceId : "";
5738
- _options
5739
- .getCallbacks()
5740
- .proxyRequestEvent(
5741
- proxyId,
5742
- "inbound",
5743
- requestContext.url,
5744
- requestContext.method,
5745
- serializeHeaders(requestContext.headers),
5746
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5747
- nativeResponse.statusCode,
5748
- serializeHeaders(nativeResponse.headers),
5749
- Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5750
- dialogId
5751
- );
5752
-
5753
- try {
5754
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5755
- if (proxiedRequest.canceled) {
5756
- return createCanceledResponse();
5757
- }
5758
- if (proxiedRequest.response != null) {
5759
- return proxiedRequest.response;
5760
- }
5761
- if (proxiedRequest.nativeResponse != null) {
5762
- nativeResponse = proxiedRequest.nativeResponse;
5763
- }
5764
- } else {
5765
- synchronized (proxiedRequest) {
5766
- proxiedRequest.timedOut = true;
5767
- }
5768
- removeProxiedRequest(proxyId);
5769
- }
5770
- } catch (InterruptedException error) {
5771
- Thread.currentThread().interrupt();
5772
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5773
- }
5774
-
5775
- RedirectReplayResult redirectReplay;
5776
- try {
5777
- redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
5778
- } catch (IOException error) {
5779
- Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
5780
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5781
- }
5782
- if (redirectReplay != null) {
5783
- requestContext = redirectReplay.requestContext;
5784
- nativeResponse = redirectReplay.responseData;
5785
- redirectsFollowed++;
5786
- continue;
5787
- }
5788
- return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5789
5910
  }
5911
+
5912
+ return interceptBundledAssetRequest(request);
5790
5913
  }
5791
5914
 
5792
5915
  @Override
@@ -5953,6 +6076,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5953
6076
  _options.getCallbacks().pageLoaded();
5954
6077
  stopReloadGesture();
5955
6078
  injectJavaScriptInterface();
6079
+ // The freshly parsed document carries no safe-area variables, whatever was
6080
+ // injected into the previous one, so drop the cache and serve them again.
6081
+ resetInjectedSafeAreaCssVariables();
6082
+ reapplyInsetsFromWindowRoot();
5956
6083
 
5957
6084
  // Inject Google Pay polyfills if enabled
5958
6085
  if (_options.getEnableGooglePaySupport()) {