Skip to content

Commit 80adee8

Browse files
Merge pull request #1017 from lightpanda-io/fix_async_script_processing
Fix blockingGet during blockingGet
2 parents 37fe6a6 + ca8877d commit 80adee8

File tree

1 file changed

+45
-39
lines changed

1 file changed

+45
-39
lines changed

src/browser/ScriptManager.zig

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const ScriptManager = @This();
3535

3636
page: *Page,
3737

38-
// used to prevent recursive evalution
38+
// used to prevent recursive evalutaion
3939
is_evaluating: bool,
4040

4141
// used to prevent executing scripts while we're doing a blocking load
@@ -45,10 +45,16 @@ is_blocking: bool = false,
4545
static_scripts_done: bool,
4646

4747
// List of async scripts. We don't care about the execution order of these, but
48-
// on shutdown/abort, we need to co cleanup any pending ones.
48+
// on shutdown/abort, we need to cleanup any pending ones.
4949
asyncs: OrderList,
5050

51-
// Normal scripts (non-deffered & non-async). These must be executed ni order
51+
// When an async script is ready to be evaluated, it's moved from asyncs to
52+
// this list. You might think we can evaluate an async script as soon as it's
53+
// done, but we can only evaluate scripts when `is_blocking == false`. So this
54+
// becomes a list of scripts to execute on the next evaluate().
55+
asyncs_ready: OrderList,
56+
57+
// Normal scripts (non-deferred & non-async). These must be executed in order
5258
scripts: OrderList,
5359

5460
// List of deferred scripts. These must be executed in order, but only once
@@ -72,6 +78,7 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
7278
.asyncs = .{},
7379
.scripts = .{},
7480
.deferreds = .{},
81+
.asyncs_ready = .{},
7582
.is_evaluating = false,
7683
.allocator = allocator,
7784
.client = browser.http_client,
@@ -91,6 +98,7 @@ pub fn reset(self: *ScriptManager) void {
9198
self.clearList(&self.asyncs);
9299
self.clearList(&self.scripts);
93100
self.clearList(&self.deferreds);
101+
self.clearList(&self.asyncs_ready);
94102
self.static_scripts_done = false;
95103
}
96104

@@ -114,7 +122,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
114122
// document.getElementsByTagName('head')[0].appendChild(script)
115123
// that script tag will immediately get executed by our scriptAddedCallback.
116124
// However, if the location where the script tag is inserted happens to be
117-
// below where processHTMLDoc curently is, then we'll re-run that same script
125+
// below where processHTMLDoc currently is, then we'll re-run that same script
118126
// again in processHTMLDoc. This flag is used to let us know if a specific
119127
// <script> has already been processed.
120128
if (try parser.scriptGetProcessed(@ptrCast(element))) {
@@ -169,7 +177,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
169177
if (source == .@"inline" and self.scripts.first == null) {
170178
// inline script with no pending scripts, execute it immediately.
171179
// (if there is a pending script, then we cannot execute this immediately
172-
// as it needs to be executed in order)
180+
// as it needs to best executed in order)
173181
return script.eval(page);
174182
}
175183

@@ -193,7 +201,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
193201
log.debug(.http, "script queue", .{ .url = remote_url.? });
194202
}
195203

196-
self.getList(&pending_script.script).append(&pending_script.node);
204+
pending_script.getList().append(&pending_script.node);
197205

198206
errdefer pending_script.deinit();
199207

@@ -313,14 +321,21 @@ fn evaluate(self: *ScriptManager) void {
313321
if (self.is_blocking) {
314322
// Cannot evaluate scripts while a blocking-load is in progress. Not
315323
// only could that result in incorrect evaluation order, it could
316-
// triger another blocking request, while we're doing a blocking request.
324+
// trigger another blocking request, while we're doing a blocking request.
317325
return;
318326
}
319327

320328
const page = self.page;
321329
self.is_evaluating = true;
322330
defer self.is_evaluating = false;
323331

332+
// every script in asyncs_ready is ready to be evaluated.
333+
while (self.asyncs_ready.first) |n| {
334+
var pending_script: *PendingScript = @fieldParentPtr("node", n);
335+
defer pending_script.deinit();
336+
pending_script.script.eval(page);
337+
}
338+
324339
while (self.scripts.first) |n| {
325340
var pending_script: *PendingScript = @fieldParentPtr("node", n);
326341
if (pending_script.complete == false) {
@@ -355,7 +370,6 @@ fn evaluate(self: *ScriptManager) void {
355370
page.documentIsLoaded();
356371

357372
if (self.asyncs.first == null) {
358-
// if we're here, then its like `asyncDone`
359373
// 1 - there are no async scripts pending
360374
// 2 - we checkecked static_scripts_done == true above
361375
// 3 - we drained self.scripts above
@@ -371,28 +385,6 @@ pub fn isDone(self: *const ScriptManager) bool {
371385
self.deferreds.first == null; // and there are no more <script defer src=> to wait for
372386
}
373387

374-
fn asyncDone(self: *ScriptManager) void {
375-
if (self.isDone()) {
376-
// then the document is considered complete
377-
self.page.documentIsComplete();
378-
}
379-
}
380-
381-
fn getList(self: *ScriptManager, script: *const Script) *OrderList {
382-
// When a script has both the async and defer flag set, it should be
383-
// treated as async. Async is newer, so some websites use both so that
384-
// if async isn't known, it'll fallback to defer.
385-
if (script.is_async) {
386-
return &self.asyncs;
387-
}
388-
389-
if (script.is_defer) {
390-
return &self.deferreds;
391-
}
392-
393-
return &self.scripts;
394-
}
395-
396388
fn startCallback(transfer: *Http.Transfer) !void {
397389
const script: *PendingScript = @ptrCast(@alignCast(transfer.ctx));
398390
script.startCallback(transfer) catch |err| {
@@ -441,12 +433,12 @@ pub const PendingScript = struct {
441433
if (script.source == .remote) {
442434
manager.buffer_pool.release(script.source.remote);
443435
}
444-
manager.getList(script).remove(&self.node);
436+
self.getList().remove(&self.node);
445437
}
446438

447439
fn remove(self: *PendingScript) void {
448440
if (self.node) |*node| {
449-
self.manager.getList(&self.script).remove(node);
441+
self.getList().remove(node);
450442
self.node = null;
451443
}
452444
}
@@ -500,15 +492,12 @@ pub const PendingScript = struct {
500492
log.debug(.http, "script fetch complete", .{ .req = self.script.url });
501493

502494
const manager = self.manager;
495+
self.complete = true;
503496
if (self.script.is_async) {
504-
// async script can be evaluated immediately
505-
self.script.eval(self.manager.page);
506-
self.deinit();
507-
manager.asyncDone();
508-
} else {
509-
self.complete = true;
510-
manager.evaluate();
497+
manager.asyncs.remove(&self.node);
498+
manager.asyncs_ready.append(&self.node);
511499
}
500+
manager.evaluate();
512501
}
513502

514503
fn errorCallback(self: *PendingScript, err: anyerror) void {
@@ -524,6 +513,23 @@ pub const PendingScript = struct {
524513

525514
manager.evaluate();
526515
}
516+
517+
fn getList(self: *const PendingScript) *OrderList {
518+
// When a script has both the async and defer flag set, it should be
519+
// treated as async. Async is newer, so some websites use both so that
520+
// if async isn't known, it'll fallback to defer.
521+
522+
const script = &self.script;
523+
if (script.is_async) {
524+
return if (self.complete) &self.manager.asyncs_ready else &self.manager.asyncs;
525+
}
526+
527+
if (script.is_defer) {
528+
return &self.manager.deferreds;
529+
}
530+
531+
return &self.manager.scripts;
532+
}
527533
};
528534

529535
const Script = struct {

0 commit comments

Comments
 (0)