js: fix Blob-URL Worker race and update location synchronously on navigation

URL.createObjectURL stored blob content asynchronously (blob.text().then) but
the Worker constructor read __blobStore synchronously, so new Worker(blobURL)
in the same tick raced the store, fell through to fetch(blobURL), and failed
with net::ERR_FAILED. This broke Blob-URL workers including the AWS WAF
challenge proof-of-work worker. Store synchronously from Blob._bytes, which the
obscura Blob materializes in its constructor.

location.reload was a no-op, so challenges that reload after setting a token
cookie never re-fetched. It now navigates like assign/replace.

location.href/assign/replace/reload cleared __virtualUrl immediately, so a
script that set location.href and re-read it saw the old URL until the async
navigation completed; real browsers update location.href synchronously. Preview
the resolved target in __virtualUrl and clear it in __obscura_init once the
navigation completes, so immediate reads match and redirects still win.
This commit is contained in:
SGavrl committed 2026-06-23 10:28:42 +00:00
1 parent 2e0981ef3f
commit 402de261aa
1 file changed
+26 -8
+26 -8
View File
@@ -2499,7 +2499,7 @@ function __currentUrl() {
}
globalThis.location = {
get href() { return __currentUrl(); },
set href(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
set href(url) { var r = _resolveUrl(url); globalThis.__virtualUrl = r; Deno.core.ops.op_navigate(r, 'GET', ''); },
get origin() { try { return new URL(this.href).origin; } catch { return ""; } },
get protocol() { try { return new URL(this.href).protocol; } catch { return ""; } },
get host() { try { return new URL(this.href).host; } catch { return ""; } },
@@ -2509,14 +2509,14 @@ globalThis.location = {
get hash() { try { return new URL(this.href).hash; } catch { return ""; } },
get port() { try { return new URL(this.href).port; } catch { return ""; } },
toString() { return this.href; },
assign(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
reload() {},
replace(url) { globalThis.__virtualUrl = null; Deno.core.ops.op_navigate(_resolveUrl(url), 'GET', ''); },
assign(url) { var r = _resolveUrl(url); globalThis.__virtualUrl = r; Deno.core.ops.op_navigate(r, 'GET', ''); },
reload() { var r = _resolveUrl(this.href); globalThis.__virtualUrl = r; Deno.core.ops.op_navigate(r, 'GET', ''); },
replace(url) { var r = _resolveUrl(url); globalThis.__virtualUrl = r; Deno.core.ops.op_navigate(r, 'GET', ''); },
};
const _locationObj = globalThis.location;
Object.defineProperty(globalThis, 'location', {
get() { return _locationObj; },
set(url) { Deno.core.ops.op_navigate(_resolveUrl(String(url)), 'GET', ''); },
set(url) { var r = _resolveUrl(String(url)); globalThis.__virtualUrl = r; Deno.core.ops.op_navigate(r, 'GET', ''); },
configurable: false,
enumerable: true,
});
@@ -6218,11 +6218,25 @@ globalThis.Worker = class Worker {
};
globalThis.__blobStore = globalThis.__blobStore || {};
const _origCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(blob) {
if (blob && typeof blob.text === 'function') {
if (blob) {
const id = 'blob:obscura/' + Math.random().toString(36).substring(2);
blob.text().then(text => { globalThis.__blobStore[id] = text; });
// Store synchronously so a Worker built from the blob URL in the same
// tick sees its source. Blob-URL Worker construction is synchronous in
// real browsers; the previous async blob.text().then() store raced the
// Worker constructor, so new Worker(blobURL) fell through to fetch() and
// failed (net::ERR_FAILED), which broke AWS WAF's proof-of-work worker.
// The obscura Blob materializes _bytes in its constructor; fall back to
// the async text() store only for foreign Blob shims without _bytes.
if (blob._bytes) {
let text = '';
try { text = new TextDecoder().decode(blob._bytes); } catch (e) {}
globalThis.__blobStore[id] = text;
} else if (typeof blob.text === 'function') {
blob.text().then(text => { globalThis.__blobStore[id] = text; });
} else {
globalThis.__blobStore[id] = '';
}
return id;
}
return 'blob:obscura/fallback';
@@ -6733,6 +6747,10 @@ if (typeof ShadowRoot !== 'undefined' && !ShadowRoot.prototype.elementFromPoint)
globalThis.__obscura_init = function() {
_fpSeed = Date.now() ^ (Math.random() * 0xFFFFFFFF >>> 0);
_fpCache = null;
// A real navigation just completed (this runs after set_url), so drop any
// URL a location setter previewed synchronously and let document_url drive
// location.href again, including any redirect target.
globalThis.__virtualUrl = null;
_installWasmStreamingFallback();
globalThis.document = new Document(+_dom("document_node_id"));