mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-17 17:22:43 -04:00
webapi: don't fire error event when a worker script fetch is aborted
terminate() can cancel a still-inflight script fetch. That's a cancellation, not a failure: a terminated worker must fire no further events, and Chrome is silent here. We logged an error and fired an ErrorEvent — observable by Cloudflare challenge scripts, which create a blob-URL worker and terminate it in the same tick. On error.TransferCanceled, skip the log and the error event; same log skip in SharedWorkerGlobalScope. This was tried in https://github.com/lightpanda-io/browser/pull/3189, but is now ambiguous thanks to https://github.com/lightpanda-io/browser/pull/3368 which introduced a distinct `error.TransferCanceled`.
This commit is contained in:
3 files changed
+61
-10
No files matched your search
@@ -553,3 +553,42 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_terminate_during_fetch_fires_no_error" type=module>
|
||||
// terminate() cancels the still-inflight script fetch. That's a
|
||||
// cancellation, not a load failure: a terminated worker fires no further
|
||||
// events, error included. Challenge scripts create a blob worker and
|
||||
// terminate it in the same tick, and probe for exactly this.
|
||||
{
|
||||
const state = await testing.async();
|
||||
const blobUrl = URL.createObjectURL(new Blob(["postMessage('loaded');"], { type: 'text/javascript' }));
|
||||
const worker = new Worker(blobUrl);
|
||||
let fired = null;
|
||||
worker.onerror = () => { fired = 'error'; };
|
||||
worker.onmessage = () => { fired = 'message'; };
|
||||
worker.terminate();
|
||||
|
||||
setTimeout(() => state.resolve(fired), 100);
|
||||
await state.done((fired) => {
|
||||
testing.expectEqual(null, fired);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_missing_script_fires_error" type=module>
|
||||
// A non-2xx script response is a load failure, and must keep firing the
|
||||
// error event: it travels the same error callback as a cancellation.
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./does-not-exist.js');
|
||||
worker.onerror = (e) => {
|
||||
e.preventDefault();
|
||||
state.resolve(e);
|
||||
};
|
||||
|
||||
await state.done((e) => {
|
||||
testing.expectTrue(e instanceof ErrorEvent);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -250,10 +250,14 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
self._http_transfer = null;
|
||||
self.releaseScriptArena();
|
||||
|
||||
log.err(.browser, "shared worker fetch error", .{
|
||||
.url = self._url,
|
||||
.err = err,
|
||||
});
|
||||
// TransferCanceled is teardown cancelling a still-inflight script fetch,
|
||||
// not a load failure.
|
||||
if (err != error.TransferCanceled) {
|
||||
log.err(.browser, "shared worker fetch error", .{
|
||||
.url = self._url,
|
||||
.err = err,
|
||||
});
|
||||
}
|
||||
|
||||
// The worker will never load and onconnect will never be registered.
|
||||
// Drain the buffered connects so they get dispatched (and dropped at the
|
||||
|
||||
@@ -268,10 +268,16 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
self._http_transfer = null;
|
||||
self.releaseScriptArena();
|
||||
|
||||
log.err(.browser, "worker fetch error", .{
|
||||
.url = self._url,
|
||||
.err = err,
|
||||
});
|
||||
// TransferCanceled is not a load failure: terminate() (or worker teardown)
|
||||
// cancelled a still-inflight script fetch. We shouldn't fireErrorEvent
|
||||
// (or bother logging)
|
||||
const canceled = err == error.TransferCanceled;
|
||||
if (!canceled) {
|
||||
log.err(.browser, "worker fetch error", .{
|
||||
.url = self._url,
|
||||
.err = err,
|
||||
});
|
||||
}
|
||||
|
||||
// The worker will never load and onmessage will never be registered.
|
||||
// Drain any buffered messages so they get dispatched (and silently
|
||||
@@ -280,7 +286,9 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
self._script_loaded = true;
|
||||
self._worker_scope.drainPendingMessages();
|
||||
|
||||
self.fireErrorEvent(@errorName(err), null);
|
||||
if (!canceled) {
|
||||
self.fireErrorEvent(@errorName(err), null);
|
||||
}
|
||||
}
|
||||
|
||||
fn releaseScriptArena(self: *Worker) void {
|
||||
@@ -481,7 +489,7 @@ pub const JsApi = struct {
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: Worker" {
|
||||
testing.silenceLog(&.{.http});
|
||||
testing.silenceLog(&.{.http, .browser, .browser});
|
||||
|
||||
// Worker tests chain a worker-script fetch with a dynamic-import fetch
|
||||
// and a cross-context postMessage. The default 2 s assertion budget can
|
||||
|
||||
Reference in new issue
Block a user