Skip to content

Commit cdc439c

Browse files
authored
Merge pull request #1026 from lightpanda-io/network_idle_500ms_delay
Send NetworkIdle and NetworkAlmostIdle notifications after 500ms delay
2 parents 746168f + 9971de2 commit cdc439c

File tree

2 files changed

+94
-41
lines changed

2 files changed

+94
-41
lines changed

src/browser/Scheduler.zig

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,11 @@ pub fn add(self: *Scheduler, ctx: *anyopaque, func: Task.Func, ms: u32, opts: Ad
6565
});
6666
}
6767

68-
pub fn runHighPriority(self: *Scheduler) !?i32 {
68+
pub fn run(self: *Scheduler) !?i32 {
69+
_ = try self.runQueue(&self.low_priority);
6970
return self.runQueue(&self.high_priority);
7071
}
7172

72-
pub fn runLowPriority(self: *Scheduler) !?i32 {
73-
return self.runQueue(&self.low_priority);
74-
}
75-
7673
fn runQueue(self: *Scheduler, queue: *Queue) !?i32 {
7774
// this is O(1)
7875
if (queue.count() == 0) {
@@ -127,33 +124,24 @@ test "Scheduler" {
127124
var task = TestTask{ .allocator = testing.arena_allocator };
128125

129126
var s = Scheduler.init(testing.arena_allocator);
130-
try testing.expectEqual(null, s.runHighPriority());
127+
try testing.expectEqual(null, s.run());
131128
try testing.expectEqual(0, task.calls.items.len);
132129

133130
try s.add(&task, TestTask.run1, 3, .{});
134131

135-
try testing.expectDelta(3, try s.runHighPriority(), 1);
132+
try testing.expectDelta(3, try s.run(), 1);
136133
try testing.expectEqual(0, task.calls.items.len);
137134

138135
std.Thread.sleep(std.time.ns_per_ms * 5);
139-
try testing.expectEqual(null, s.runHighPriority());
136+
try testing.expectEqual(null, s.run());
140137
try testing.expectEqualSlices(u32, &.{1}, task.calls.items);
141138

142139
try s.add(&task, TestTask.run2, 3, .{});
143140
try s.add(&task, TestTask.run1, 2, .{});
144141

145142
std.Thread.sleep(std.time.ns_per_ms * 5);
146-
try testing.expectDelta(null, try s.runHighPriority(), 1);
147-
try testing.expectEqualSlices(u32, &.{ 1, 1, 2 }, task.calls.items);
148-
149-
std.Thread.sleep(std.time.ns_per_ms * 5);
150-
// won't run low_priority
151-
try testing.expectEqual(null, try s.runHighPriority());
143+
try testing.expectDelta(null, try s.run(), 1);
152144
try testing.expectEqualSlices(u32, &.{ 1, 1, 2 }, task.calls.items);
153-
154-
//runs low_priority
155-
try testing.expectDelta(2, try s.runLowPriority(), 1);
156-
try testing.expectEqualSlices(u32, &.{ 1, 1, 2, 2 }, task.calls.items);
157145
}
158146

159147
const TestTask = struct {

src/browser/page.zig

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,8 @@ 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,
93+
notified_network_idle: IdleNotification = .init,
94+
notified_network_almost_idle: IdleNotification = .init,
9795

9896
const Mode = union(enum) {
9997
pre: void,
@@ -325,8 +323,7 @@ pub const Page = struct {
325323
// scheduler.run could trigger new http transfers, so do not
326324
// store http_client.active BEFORE this call and then use
327325
// it AFTER.
328-
const ms_to_next_task = try scheduler.runHighPriority();
329-
_ = try scheduler.runLowPriority();
326+
const ms_to_next_task = try scheduler.run();
330327

331328
if (try_catch.hasCaught()) {
332329
const msg = (try try_catch.err(self.arena)) orelse "unknown";
@@ -335,6 +332,14 @@ pub const Page = struct {
335332
}
336333

337334
const http_active = http_client.active;
335+
const total_network_activity = http_active + http_client.intercepted;
336+
if (self.notified_network_almost_idle.check(total_network_activity <= 2)) {
337+
self.notifyNetworkAlmostIdle();
338+
}
339+
if (self.notified_network_idle.check(total_network_activity == 0)) {
340+
self.notifyNetworkIdle();
341+
}
342+
338343
if (http_active == 0 and exit_when_done) {
339344
// we don't need to consider http_client.intercepted here
340345
// because exit_when_done is true, and that can only be
@@ -359,7 +364,8 @@ pub const Page = struct {
359364

360365
if (ms > ms_remaining) {
361366
// Same as above, except we have a scheduled task,
362-
// it just happens to be too far into the future.
367+
// it just happens to be too far into the future
368+
// compared to how long we were told to wait.
363369
return .done;
364370
}
365371

@@ -371,9 +377,6 @@ pub const Page = struct {
371377
// loop to see if anything new can be processed.
372378
std.Thread.sleep(std.time.ns_per_ms * @as(u64, @intCast(@min(ms, 20))));
373379
} else {
374-
if (self.notified_network_idle == false and http_active == 0 and http_client.intercepted == 0) {
375-
self.notifyNetworkIdle();
376-
}
377380
// We're here because we either have active HTTP
378381
// connections, or exit_when_done == false (aka, there's
379382
// an extra_socket registered with the http client).
@@ -484,25 +487,14 @@ pub const Page = struct {
484487
}
485488

486489
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;
490+
std.debug.assert(self.notified_network_idle == .done);
497491
self.session.browser.notification.dispatch(.page_network_idle, &.{
498492
.timestamp = timestamp(),
499493
});
500494
}
501495

502496
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;
497+
std.debug.assert(self.notified_network_almost_idle == .done);
506498
self.session.browser.notification.dispatch(.page_network_almost_idle, &.{
507499
.timestamp = timestamp(),
508500
});
@@ -1140,9 +1132,82 @@ pub const NavigateOpts = struct {
11401132
header: ?[:0]const u8 = null,
11411133
};
11421134

1135+
const IdleNotification = union(enum) {
1136+
// hasn't started yet.
1137+
init,
1138+
1139+
// timestamp where the state was first triggered. If the state stays
1140+
// true (e.g. 0 nework activity for NetworkIdle, or <= 2 for NetworkAlmostIdle)
1141+
// for 500ms, it'll send the notification and transition to .done. If
1142+
// the state doesn't stay true, it'll revert to .init.
1143+
triggered: u64,
1144+
1145+
// notification sent - should never be reset
1146+
done,
1147+
1148+
// Returns `true` if we should send a notification. Only returns true if it
1149+
// was previously triggered 500+ milliseconds ago.
1150+
// active == true when the condition for the notification is true
1151+
// active == false when the condition for the notification is false
1152+
pub fn check(self: *IdleNotification, active: bool) bool {
1153+
if (active) {
1154+
switch (self.*) {
1155+
.done => {
1156+
// Notification was already sent.
1157+
},
1158+
.init => {
1159+
// This is the first time the condition was triggered (or
1160+
// the first time after being un-triggered). Record the time
1161+
// so that if the condition holds for long enough, we can
1162+
// send a notification.
1163+
self.* = .{ .triggered = milliTimestamp() };
1164+
},
1165+
.triggered => |ms| {
1166+
// The condition was already triggered and was triggered
1167+
// again. When this condition holds for 500+ms, we'll send
1168+
// a notification.
1169+
if (milliTimestamp() - ms >= 500) {
1170+
// This is the only place in this function where we can
1171+
// return true. The only place where we can tell our caller
1172+
// "send the notification!".
1173+
self.* = .done;
1174+
return true;
1175+
}
1176+
// the state hasn't held for 500ms.
1177+
},
1178+
}
1179+
} else {
1180+
switch (self.*) {
1181+
.done => {
1182+
// The condition became false, but we already sent the notification
1183+
// There's nothing we can do, it stays .done. We never re-send
1184+
// a notification or "undo" a sent notification (not that we can).
1185+
},
1186+
.init => {
1187+
// The condition remains false
1188+
},
1189+
.triggered => {
1190+
// The condition _had_ been true, and we were waiting (500ms)
1191+
// for it to hold, but it hasn't. So we go back to waiting.
1192+
self.* = .init;
1193+
},
1194+
}
1195+
}
1196+
1197+
// See above for the only case where we ever return true. All other
1198+
// paths go here. This means "don't send the notification". Maybe
1199+
// because it's already been sent, maybe because active is false, or
1200+
// maybe because the condition hasn't held long enough.
1201+
return false;
1202+
}
1203+
};
1204+
11431205
fn timestamp() u32 {
11441206
return @import("../datetime.zig").timestamp();
11451207
}
1208+
fn milliTimestamp() u64 {
1209+
return @import("../datetime.zig").milliTimestamp();
1210+
}
11461211

11471212
// A callback from libdom whenever a script tag is added to the DOM.
11481213
// element is guaranteed to be a script element.

0 commit comments

Comments
 (0)