Fix sandbox origin isolation and Android build issues

This commit is contained in:
Alex Gleason
2026-04-08 20:47:42 -05:00
parent 11ff27efe2
commit e482afbd3f
3 changed files with 37 additions and 29 deletions
@@ -13,7 +13,7 @@ import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
@@ -80,9 +80,11 @@ public class SandboxPlugin extends Plugin {
sandboxes.put(sandboxId, sandbox);
// Add the WebView on top of the Capacitor WebView.
// The parent is a CoordinatorLayout — using the wrong LayoutParams
// type causes a ClassCastException when it intercepts touch events.
View capWebView = getBridge().getWebView();
ViewGroup parent = (ViewGroup) capWebView.getParent();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(pxWidth, pxHeight);
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(pxWidth, pxHeight);
params.leftMargin = pxX;
params.topMargin = pxY;
parent.addView(sandbox.webView, params);
@@ -126,7 +128,7 @@ public class SandboxPlugin extends Plugin {
return;
}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(pxWidth, pxHeight);
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(pxWidth, pxHeight);
params.leftMargin = pxX;
params.topMargin = pxY;
sandbox.webView.setLayoutParams(params);
@@ -385,7 +387,7 @@ public class SandboxPlugin extends Plugin {
" onMessage: function(data) {" +
" var event = {" +
" data: data," +
" origin: 'sandbox://app'," +
" origin: 'https://" + sandbox.id + ".sandbox.native'," +
" source: window.parent," +
" type: 'message'" +
" };" +
+3 -4
View File
@@ -194,10 +194,9 @@ private class SandboxInstance: NSObject, WKScriptMessageHandler {
self.id = id
self.plugin = plugin
// Use a shortened ID for the scheme (URL schemes have length limits
// and must start with a letter). We use "sbx-" prefix + first 12 chars.
let schemeId = String(id.prefix(12))
self.customScheme = "sbx-\(schemeId)"
// Each sandbox gets a unique custom URL scheme so that WKWebView
// assigns a distinct origin, isolating localStorage/IndexedDB/cookies.
self.customScheme = "sbx-\(id)"
self.schemeHandler = SandboxSchemeHandler(
sandboxId: id,
+28 -21
View File
@@ -413,7 +413,34 @@ const SandboxFrameNative = forwardRef<SandboxFrameHandle, SandboxFrameProps>(
const rect = el.getBoundingClientRect();
// Create the native WebView.
// Register listeners BEFORE creating the WebView. On Android,
// `shouldInterceptRequest` fires on a background thread as soon
// as the WebView starts loading — if the fetch listener isn't
// registered yet, the event is lost and the request times out
// (the thread blocks via CountDownLatch waiting for a response
// that never arrives).
const fetchListener = await SandboxPlugin.addListener(
'fetch',
(event: SandboxFetchEvent) => {
if (event.id !== id) return;
handleNativeFetch(event);
},
);
listeners.push(fetchListener);
const scriptListener = await SandboxPlugin.addListener(
'scriptMessage',
(event: SandboxScriptMessageEvent) => {
if (event.id !== id) return;
handleNativeScriptMessage(event);
},
);
listeners.push(scriptListener);
if (cancelled || destroyedRef.current) return;
// Create the native WebView. Fetch events from the initial load
// will be handled by the listeners registered above.
await SandboxPlugin.create({
id,
frame: {
@@ -431,26 +458,6 @@ const SandboxFrameNative = forwardRef<SandboxFrameHandle, SandboxFrameProps>(
}
createdRef.current = true;
// Listen for fetch requests from the native URL scheme handler.
const fetchListener = await SandboxPlugin.addListener(
'fetch',
(event: SandboxFetchEvent) => {
if (event.id !== id) return;
handleNativeFetch(event);
},
);
listeners.push(fetchListener);
// Listen for script messages (custom JSON-RPC from injected scripts).
const scriptListener = await SandboxPlugin.addListener(
'scriptMessage',
(event: SandboxScriptMessageEvent) => {
if (event.id !== id) return;
handleNativeScriptMessage(event);
},
);
listeners.push(scriptListener);
}
// ---------------------------------------------------------------