@@ -90,6 +90,11 @@ pub const Page = struct {
9090
9191 load_state : LoadState = .parsing ,
9292
93+ // We should only emit these events once per page. To make sure of that, we
94+ // track whether or not we've already emitted the notifications.
95+ notified_network_idle : bool = false ,
96+ notified_network_almost_idle : bool = false ,
97+
9398 const Mode = union (enum ) {
9499 pre : void ,
95100 err : anyerror ,
@@ -329,7 +334,13 @@ pub const Page = struct {
329334 return error .JsError ;
330335 }
331336
332- if (http_client .active == 0 and exit_when_done ) {
337+ const http_active = http_client .active ;
338+ if (http_active == 0 and exit_when_done ) {
339+ // we don't need to consider http_client.intercepted here
340+ // because exit_when_done is true, and that can only be
341+ // the case when interception isn't possible.
342+ std .debug .assert (http_client .intercepted == 0 );
343+
333344 const ms = ms_to_next_task orelse blk : {
334345 // TODO: when jsRunner is fully replaced with the
335346 // htmlRunner, we can remove the first part of this
@@ -341,29 +352,34 @@ pub const Page = struct {
341352 // background jobs.
342353 break :blk 50 ;
343354 }
344- // no http transfers, no cdp extra socket, no
355+ // No http transfers, no cdp extra socket, no
345356 // scheduled tasks, we're done.
346357 return .done ;
347358 };
348359
349360 if (ms > ms_remaining ) {
350- // same as above, except we have a scheduled task,
351- // it just happens to be too far into the future.s
361+ // Same as above, except we have a scheduled task,
362+ // it just happens to be too far into the future.
352363 return .done ;
353364 }
354365
355- // we have a task to run in the not-so-distant future.
366+ // We have a task to run in the not-so-distant future.
356367 // You might think we can just sleep until that task is
357368 // ready, but we should continue to run lowPriority tasks
358369 // in the meantime, and that could unblock things. So
359370 // we'll just sleep for a bit, and then restart our wait
360- // loop to see what's changed
371+ // loop to see if anything new can be processed.
361372 std .Thread .sleep (std .time .ns_per_ms * @as (u64 , @intCast (@min (ms , 20 ))));
362373 } else {
374+ if (self .notified_network_idle == false and http_active == 0 and http_client .intercepted == 0 ) {
375+ self .notifyNetworkIdle ();
376+ }
363377 // We're here because we either have active HTTP
364- // connections, of exit_when_done == false (aka, there's
378+ // connections, or exit_when_done == false (aka, there's
365379 // an extra_socket registered with the http client).
366- const ms_to_wait = @min (ms_remaining , ms_to_next_task orelse 100 );
380+ // We should continue to run lowPriority tasks, so we
381+ // minimize how long we'll poll for network I/O.
382+ const ms_to_wait = @min (200 , @min (ms_remaining , ms_to_next_task orelse 200 ));
367383 if (try http_client .tick (ms_to_wait ) == .extra_socket ) {
368384 // data on a socket we aren't handling, return to caller
369385 return .extra_socket ;
@@ -467,6 +483,31 @@ pub const Page = struct {
467483 }
468484 }
469485
486+ fn notifyNetworkIdle (self : * Page ) void {
487+ // caller should always check that we haven't sent this already;
488+ std .debug .assert (self .notified_network_idle == false );
489+
490+ // if we're going to send networkIdle, we should first send networkAlmostIdle
491+ // if it hasn't already been sent.
492+ if (self .notified_network_almost_idle == false ) {
493+ self .notifyNetworkAlmostIdle ();
494+ }
495+
496+ self .notified_network_idle = true ;
497+ self .session .browser .notification .dispatch (.page_network_idle , &.{
498+ .timestamp = timestamp (),
499+ });
500+ }
501+
502+ fn notifyNetworkAlmostIdle (self : * Page ) void {
503+ // caller should always check that we haven't sent this already;
504+ std .debug .assert (self .notified_network_almost_idle == false );
505+ self .notified_network_almost_idle = true ;
506+ self .session .browser .notification .dispatch (.page_network_almost_idle , &.{
507+ .timestamp = timestamp (),
508+ });
509+ }
510+
470511 pub fn origin (self : * const Page , arena : Allocator ) ! []const u8 {
471512 var aw = std .Io .Writer .Allocating .init (arena );
472513 try self .url .origin (& aw .writer );
@@ -782,12 +823,12 @@ pub const Page = struct {
782823 }
783824
784825 // The transfer arena is useful and interesting, but has a weird lifetime.
785- // When we're transfering from one page to another (via delayed navigation)
826+ // When we're transferring from one page to another (via delayed navigation)
786827 // we need things in memory: like the URL that we're navigating to and
787828 // optionally the body to POST. That cannot exist in the page.arena, because
788829 // the page that we have is going to be destroyed and a new page is going
789830 // to be created. If we used the page.arena, we'd wouldn't be able to reset
790- // it between navigations .
831+ // it between navigation .
791832 // So the transfer arena is meant to exist between a navigation event. It's
792833 // freed when the main html navigation is complete, either in pageDoneCallback
793834 // or pageErrorCallback. It needs to exist for this long because, if we set
@@ -1118,7 +1159,7 @@ pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) c
11181159 return ;
11191160 }
11201161
1121- // It's posisble for a script to be dynamically added without a src.
1162+ // It's possible for a script to be dynamically added without a src.
11221163 // const s = document.createElement('script');
11231164 // document.getElementsByTagName('body')[0].appendChild(s);
11241165 // The src can be set after. We handle that in HTMLScriptElement.set_src,
0 commit comments