///|
/// FFI: Navigate to URL asynchronously, send BiDi success/error response when
/// the runtime document load (__loadPageWithScripts / __loadPage) settles.
///
/// Mirrors the pattern of js_eval_and_send_async: the MoonBit caller hands
/// over the socket + request id, the JS async body awaits the load and emits
/// the response itself via socket.send(). This is the fix for
/// bug.bidi.navigate-wait-complete-returns-early — the previous sync path
/// (sync_runtime_page -> js_await_promise) returned before the load had any
/// chance to complete because MoonBit's sync caller cannot await a JS Promise
/// across the FFI boundary.
extern "js" fn js_navigate_and_send_async(
socket : @core.Any,
request_id : Int,
ctx_id : String,
url : String,
navigation_id : String,
) -> Unit =
#| async (socket, requestId, ctxId, url, navigationId) => {
#| const currentCtx = String(globalThis.__bidiCurrentContext || ctxId || "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();
#|
#| let success = false;
#| let errorMsg = "";
#| if (typeof globalThis.__loadPageWithScripts === "function") {
#| try {
#| await globalThis.__loadPageWithScripts(url, { executeScripts: true });
#| bindContextWindow();
#| persistContextRuntime();
#| success = true;
#| } catch (e) {
#| errorMsg = "load failed: " + ((e && e.message) ? e.message : String(e));
#| }
#| } else if (typeof globalThis.__loadPage === "function") {
#| try {
#| await globalThis.__loadPage(url);
#| bindContextWindow();
#| persistContextRuntime();
#| success = true;
#| } catch (e) {
#| errorMsg = "load failed: " + ((e && e.message) ? e.message : String(e));
#| }
#| } else {
#| // No loader available — surface an error so this class of bug fails
#| // loudly. Previously this branch silently returned success=true, which
#| // masked the navigate-http-url-not-parsed bug: the FFI appeared to
#| // succeed but no page was loaded. Now we set success=false so the BiDi
#| // response carries an explicit error and callers can detect the failure.
#| globalThis.__pageUrl = url;
#| persistContextRuntime();
#| errorMsg = "runtime loader missing: __loadPageWithScripts and __loadPage are not installed";
#| success = false;
#| }
#|
#| const response = success
#| ? {
#| id: requestId,
#| type: "success",
#| result: { navigation: navigationId, url }
#| }
#| : {
#| id: requestId,
#| type: "error",
#| error: "unknown error",
#| message: errorMsg
#| };
#| const payload = JSON.stringify(response);
#| if (socket && typeof socket.send === "function") {
#| try { socket.send(payload); } catch (_e) { /* swallow */ }
#| } else if (typeof globalThis.__bidiSend === "function") {
#| try { globalThis.__bidiSend(payload); } catch (_e) { /* swallow */ }
#| }
#| }
///|
/// MoonBit wrapper around js_navigate_and_send_async. Used by the navigate
/// dispatch when wait=complete is requested for an http(s) URL — instead of
/// the broken sync sync_runtime_page() path, the JS body awaits the page
/// load and emits the BiDi response via socket once the load resolves.
pub fn navigate_and_send_async(
socket : @core.Any,
request_id : Int,
ctx_id : String,
url : String,
navigation_id : String,
) -> Unit {
// Prime runtime helpers; mirrors sync_runtime_page (bidi_runtime_document.mbt:125-129).
// Without this, the first navigate after reset_runtime_js_state runs before
// __loadPageWithScripts is installed (it lives behind js_evaluate_expression's
// setup), so the FFI silently falls into the "missing-loader" branch and
// returns success without actually fetching the page.
// See bug.bidi.navigate-http-url-not-parsed.
let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
js_navigate_and_send_async(socket, request_id, ctx_id, url, navigation_id)
}