///|
/// FFI: Synchronize HTML content into JS runtime document used by script.evaluate.
extern "js" fn js_sync_runtime_html_async(
html : String,
base_url : String,
execute_scripts : Bool,
) -> @core.Any =
#| async (html, baseUrl, executeScripts) => {
#| const currentCtx = String(globalThis.__bidiCurrentContext || "default-context");
#| if (!globalThis.__bidiContextWindows) globalThis.__bidiContextWindows = new Map();
#| if (!globalThis.__bidiContextWindows.has(currentCtx)) {
#| const win = { __bidiContextId: currentCtx };
#| win.window = win;
#| globalThis.__bidiContextWindows.set(currentCtx, win);
#| }
#| const contextWindow = globalThis.__bidiContextWindows.get(currentCtx);
#| const bindContextWindow = () => {
#| if (!contextWindow || typeof contextWindow !== "object") return;
#| contextWindow.window = contextWindow;
#| const frameWindows = Array.isArray(contextWindow.frames) ? contextWindow.frames : [];
#| globalThis.window = contextWindow;
#| globalThis.frames = frameWindows;
#| globalThis.self = contextWindow;
#| globalThis.parent = contextWindow;
#| globalThis.top = contextWindow;
#| if (typeof globalThis.__bidiSyncWindowPropertiesToGlobal === "function") {
#| globalThis.__bidiSyncWindowPropertiesToGlobal(contextWindow);
#| }
#| };
#| if (typeof globalThis.__loadHTML !== "function") {
#| return "missing-loader";
#| }
#| globalThis.__loadHTML(html);
#| globalThis.__pageUrl = baseUrl;
#| if (globalThis.location && typeof globalThis.location === "object") {
#| try { globalThis.location.href = String(baseUrl); } catch (_e) {}
#| }
#| bindContextWindow();
#| if (executeScripts && typeof globalThis.__executeScripts === "function") {
#| try {
#| await globalThis.__executeScripts({ baseUrl });
#| } catch (_e) {}
#| }
#| bindContextWindow();
#| if (contextWindow && typeof contextWindow === "object") {
#| contextWindow.window = contextWindow;
#| if (globalThis.document && typeof globalThis.document === "object") {
#| contextWindow.document = globalThis.document;
#| }
#| if (globalThis.location && typeof globalThis.location === "object") {
#| contextWindow.location = globalThis.location;
#| }
#| }
#| return "ok";
#| }
///|
/// FFI: Synchronize runtime document by fetching and loading a URL.
extern "js" fn js_sync_runtime_page_async(url : String) -> @core.Any =
#| async (url) => {
#| const currentCtx = String(globalThis.__bidiCurrentContext || "default-context");
#| if (!globalThis.__bidiContextWindows) globalThis.__bidiContextWindows = new Map();
#| const ensureContextWindow = () => {
#| if (!globalThis.__bidiContextWindows.has(currentCtx)) {
#| const win = { __bidiContextId: currentCtx };
#| win.window = win;
#| globalThis.__bidiContextWindows.set(currentCtx, win);
#| }
#| return globalThis.__bidiContextWindows.get(currentCtx);
#| };
#| const bindContextWindow = () => {
#| const contextWindow = ensureContextWindow();
#| if (!contextWindow || typeof contextWindow !== "object") return;
#| contextWindow.window = contextWindow;
#| const frameWindows = Array.isArray(contextWindow.frames) ? contextWindow.frames : [];
#| globalThis.window = contextWindow;
#| globalThis.frames = frameWindows;
#| globalThis.self = contextWindow;
#| globalThis.parent = contextWindow;
#| globalThis.top = contextWindow;
#| if (typeof globalThis.__bidiSyncWindowPropertiesToGlobal === "function") {
#| globalThis.__bidiSyncWindowPropertiesToGlobal(contextWindow);
#| }
#| };
#| const persistContextRuntime = () => {
#| const contextWindow = ensureContextWindow();
#| if (contextWindow && typeof contextWindow === "object") {
#| contextWindow.window = contextWindow;
#| if (globalThis.document && typeof globalThis.document === "object") {
#| contextWindow.document = globalThis.document;
#| }
#| if (globalThis.location && typeof globalThis.location === "object") {
#| contextWindow.location = globalThis.location;
#| }
#| }
#| };
#| bindContextWindow();
#| if (typeof globalThis.__loadPageWithScripts === "function") {
#| try {
#| await globalThis.__loadPageWithScripts(url, { executeScripts: true });
#| bindContextWindow();
#| persistContextRuntime();
#| return "ok";
#| } catch (_e) {
#| return "load-failed";
#| }
#| }
#| if (typeof globalThis.__loadPage === "function") {
#| try {
#| await globalThis.__loadPage(url);
#| bindContextWindow();
#| persistContextRuntime();
#| return "ok";
#| } catch (_e) {
#| return "load-failed";
#| }
#| }
#| globalThis.__pageUrl = url;
#| persistContextRuntime();
#| return "missing-loader";
#| }
///|
/// Synchronize runtime page by URL.
pub fn sync_runtime_page(url : String) -> Unit {
let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
let promise = js_sync_runtime_page_async(url)
let _ = js_await_promise(promise)
}
///|
/// Synchronize HTML content into the JS runtime document.
pub fn sync_runtime_html(
html : String,
base_url : String,
execute_scripts : Bool,
) -> Unit {
// Ensure runtime helpers are initialized.
let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
let promise = js_sync_runtime_html_async(html, base_url, execute_scripts)
let _ = js_await_promise(promise)
}
// =============================================================================
// Data URL Support
// =============================================================================
///|
/// FFI: Decode base64 string
extern "js" fn js_decode_base64(base64 : String) -> String =
#| (base64) => {
#| try {
#| return atob(base64);
#| } catch (e) {
#| return "";
#| }
#| }
///|
/// Decode base64 string
pub fn decode_base64(base64 : String) -> String {
js_decode_base64(base64)
}
///|
/// Parse data URL and return decoded content
/// Returns (content_type, content) or None if invalid
pub fn parse_data_url(url : String) -> (String, String)? {
// data:[][;base64],
if !url.has_prefix("data:") {
return None
}
let chars = url.to_array()
// Find comma (start after "data:")
let mut comma_idx = -1
for i = 5; i < chars.length(); i = i + 1 {
if chars[i] == ',' {
comma_idx = i
break
}
}
if comma_idx < 0 {
return None
}
// Build header string (from index 5 to comma)
let header = make_substr(chars, 5, comma_idx)
// Build data string (from comma+1 to end)
let raw_data = make_substr(chars, comma_idx + 1, chars.length())
// Ignore URL fragment (e.g. #domain=alt) in payload.
let data_chars = raw_data.to_array()
let mut hash_idx = -1
for i = 0; i < data_chars.length(); i = i + 1 {
if data_chars[i] == '#' {
hash_idx = i
break
}
}
let data = if hash_idx >= 0 {
make_substr(data_chars, 0, hash_idx)
} else {
raw_data
}
// Check if base64
let is_base64 = header.contains(";base64")
let content_type = if is_base64 {
make_substr(header.to_array(), 0, header.length() - 7)
} else {
header
}
let content = if is_base64 { decode_base64(data) } else { data }
Some((content_type, content))
}
///|
fn make_substr(chars : Array[Char], start : Int, end : Int) -> String {
let buf = StringBuilder::new()
for i = start; i < end; i = i + 1 {
buf.write_char(chars[i])
}
buf.to_string()
}