Skip to content

Commit ca8877d

Browse files
committed
Fix blockingGet during blockingGet
ScriptManager should only ever has one in-flight blockingGet. The is_blocking flag is used to assert this, as well as enforce it in evaluate(). If is_blocking is true, evaluate() exits. This doesn't work for async scripts however, as they aren't executed via evaluate(), but rather execute directly once complete. This PR changes the execution behavior of async scripts. They are now only executed in evaluate() (and thus won't execute when is_blocking == true). However, unlike normal/deferred scripts, async scripts continue to execute in their completion order (not their declared order). Fixes #1016
1 parent 42828c6 commit ca8877d

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)