diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index bda3c6c9..de6d12e1 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -284,8 +284,9 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e return; } - if (script.status == 0) { - // an error (that we already logged) + if (script.status < 200 or script.status > 299) { + log.info(.http, "script load error", .{ .status = script.status }); + script.executeCallback(comptime .wrap("error")); script.deinit(); return; } diff --git a/src/browser/ScriptManagerBase.zig b/src/browser/ScriptManagerBase.zig index cf9556c8..6b5faaf2 100644 --- a/src/browser/ScriptManagerBase.zig +++ b/src/browser/ScriptManagerBase.zig @@ -687,7 +687,7 @@ pub const Script = struct { const entry = manager.imported_modules.getPtr(self.url).?; entry.state = .err; }, - .frame => {}, + .frame => self.executeCallback(comptime .wrap("error")), } self.deinit(); manager.evaluate(); @@ -792,7 +792,7 @@ pub const Script = struct { self.executeCallback(comptime .wrap("error")); } - fn executeCallback(self: *const Script, typ: String) void { + pub fn executeCallback(self: *const Script, typ: String) void { const fe = self.extra.frame; const frame = fe.frame; const Event = @import("webapi/Event.zig"); diff --git a/src/browser/tests/page/meta.html b/src/browser/tests/page/meta.html index 98fb1688..7079b5b3 100644 --- a/src/browser/tests/page/meta.html +++ b/src/browser/tests/page/meta.html @@ -44,3 +44,19 @@ + + + + + diff --git a/src/cdp/CDP.zig b/src/cdp/CDP.zig index 92a17aa7..66427c10 100644 --- a/src/cdp/CDP.zig +++ b/src/cdp/CDP.zig @@ -44,7 +44,6 @@ pub const URL_BASE = "chrome://newtab/"; const IS_DEBUG = @import("builtin").mode == .Debug; -const TargetIdGen = Incrementing(u32, "TID"); const SessionIdGen = Incrementing(u32, "SID"); const BrowserContextIdGen = Incrementing(u32, "BID"); @@ -62,7 +61,6 @@ browser: Browser, // when true, any target creation must be attached. target_auto_attach: bool = false, -target_id_gen: TargetIdGen = .{}, session_id_gen: SessionIdGen = .{}, browser_context_id_gen: BrowserContextIdGen = .{}, diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index a06a2908..bb0274e1 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -624,7 +624,8 @@ pub fn frameNavigated(arena: Allocator, bc: *CDP.BrowserContext, event: *const N }, .{ .session_id = session_id }); } - const frame = bc.session.currentFrame() orelse return error.FrameNotLoaded; + const root_frame = bc.session.currentFrame() orelse return error.FrameNotLoaded; + const is_root_frame = event.frame_id == root_frame._frame_id; // When we actually recreated the context we should have the inspector send // this event, see: resetContextGroup. Sending this event will tell the @@ -635,10 +636,17 @@ pub fn frameNavigated(arena: Allocator, bc: *CDP.BrowserContext, event: *const N // frames (iframes), clearing all contexts would destroy the main frame's // context, causing Puppeteer's frame.evaluate()/frame.content() to hang // forever. - if (event.frame_id == frame._frame_id) { + if (is_root_frame) { try cdp.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id }); } + // Look up the actual navigated frame. For main frame navigations this is + // the root frame; for iframes it is the child frame. Using the correct + // frame's JS context for inspector.contextCreated prevents re-registering + // the root context under a new id (which silently invalidates the + // previous id on the V8 side). + const frame = bc.session.findFrameByFrameId(event.frame_id) orelse return error.FrameNotFound; + // frameNavigated event try cdp.sendEvent("Page.frameNavigated", .{ .type = "Navigation", @@ -663,25 +671,32 @@ pub fn frameNavigated(arena: Allocator, bc: *CDP.BrowserContext, event: *const N "", frame.origin orelse "", aux_data, - true, + is_root_frame, ); } - for (bc.isolated_worlds.items) |isolated_world| { - const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id }); + // Isolated worlds are session-wide (single V8 context shared across + // navigations). Only re-register them for main frame navigations; + // re-registering during child frame (iframe) navigations would + // re-register the same V8 context under a new inspector id, silently + // invalidating the id the main frame is using. + if (is_root_frame) { + for (bc.isolated_worlds.items) |isolated_world| { + const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id }); - // Calling contextCreated will assign a new Id to the context and send the contextCreated event + // Calling contextCreated will assign a new Id to the context and send the contextCreated event - var ls: js.Local.Scope = undefined; - (isolated_world.context orelse continue).localScope(&ls); - defer ls.deinit(); + var ls: js.Local.Scope = undefined; + (isolated_world.context orelse continue).localScope(&ls); + defer ls.deinit(); - bc.inspector_session.inspector.contextCreated( - &ls.local, - isolated_world.name, - "://", - aux_json, - false, - ); + bc.inspector_session.inspector.contextCreated( + &ls.local, + isolated_world.name, + "://", + aux_json, + false, + ); + } } // Evaluate scripts registered via Page.addScriptToEvaluateOnNewDocument. @@ -705,18 +720,6 @@ pub fn frameNavigated(arena: Allocator, bc: *CDP.BrowserContext, event: *const N } } - // frameNavigated event - try cdp.sendEvent("Page.frameNavigated", .{ - .type = "Navigation", - .frame = CDPFrame{ - .id = frame_id, - .url = event.url, - .loaderId = loader_id, - .securityOrigin = bc.security_origin, - .secureContextType = bc.secure_context_type, - }, - }, .{ .session_id = session_id }); - // The DOM.documentUpdated event must be send after the frameNavigated one. // chromedp client expects to receive the events is this order. // see https://github.com/chromedp/chromedp/issues/1558 diff --git a/src/testing.zig b/src/testing.zig index 9e935a97..039c1680 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -650,6 +650,18 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void { }); } + if (std.mem.eql(u8, path, "/404.js")) { + // Valid JS body served with a 404 status. Used to assert that + // ScriptManager does NOT execute the body of a failed script + // fetch — if it did, window.__404_body_executed would be set. + return req.respond("window.__404_body_executed = true;", .{ + .status = .not_found, + .extra_headers = &.{ + .{ .name = "Content-Type", .value = "application/javascript" }, + }, + }); + } + if (std.mem.eql(u8, path, "/xhr/500")) { return req.respond("Internal Server Error", .{ .status = .internal_server_error,