Skip to content

Commit bdbcc86

Browse files
committed
refactor: implement idempotent API initialization and improve location handling for about:srcdoc frames
1 parent 6586cbd commit bdbcc86

5 files changed

Lines changed: 55 additions & 21 deletions

File tree

rewriter-rs/clippy.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Clippy configuration for the zp-rewriter (Rust SWC/OXC WASM rewriter).
1+
# Clippy configuration for the zp-rewriter Rust WASM rewriter.
22
#
33
# cognitive-complexity-threshold configures clippy::cognitive_complexity.
44
#

scripts/build.mjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ async function makeRustRewriterClassic() {
230230
);
231231
return [
232232
'/* Generated from Rust WASM ZeroProxy rewriter. */',
233-
js,
234233
'(() => {',
234+
`const installedRustAPI = Object.getOwnPropertyDescriptor(globalThis, 'ZPRustRewriter');`,
235+
`const installedPublicAPI = Object.getOwnPropertyDescriptor(globalThis, 'ZPRewriter');`,
236+
`if (installedRustAPI && installedRustAPI.configurable === false && installedPublicAPI && installedPublicAPI.configurable === false) return;`,
237+
js,
235238
"const VERSION = 'phase3-rust-wasm-ast-4-import-map';",
236239
`const BLOCK_CODE = "throw new DOMException('Blocked by ZeroProxy rewrite policy','NotSupportedError');";`,
237240
`const WASM_URL = '/zp/assets/rust-rewriter.wasm';`,
@@ -284,8 +287,9 @@ async function makeRustRewriterClassic() {
284287
`function rewriteFunctionBodyPublic(source, params, targetUrl, controlPrefix) { const out = rewriteFunctionBodyRaw(source, params, targetUrl, controlPrefix); return out.ok ? publicOk(out.code) : publicBlocked(out.error); }`,
285288
`const rustApi = Object.freeze({ init, initSync, get ready() { return initialized; }, rewriteScript(source, kind, targetUrl, controlPrefix, tabId, runtimeToken) { return lowLevelWithContext(source, kind, targetUrl, controlPrefix, tabId, runtimeToken); }, rewriteScriptURL(raw, kind, targetUrl, controlPrefix, tabId, runtimeToken) { return lowLevelScriptURL(raw, kind, targetUrl, controlPrefix, tabId, runtimeToken); }, rewriteFetchURL(raw, targetUrl, controlPrefix) { return lowLevelFetchURL(raw, targetUrl, controlPrefix); }, rewriteSrcset(raw, targetUrl, controlPrefix) { return lowLevelSrcset(raw, targetUrl, controlPrefix); }, rewriteTargetURL(raw, targetUrl, controlPrefix) { return lowLevelTargetURL(raw, targetUrl, controlPrefix); }, classifyLinkRel(rel) { return lowLevelLinkRel(rel); }, classifyBlockedElement(tag) { return lowLevelBlockedElement(tag); }, classifyMetaPolicy(httpEquiv) { return lowLevelMetaPolicy(httpEquiv); }, classifyAttrPolicy(tag, key) { return lowLevelAttrPolicy(tag, key); }, classifyScriptType(scriptType) { return lowLevelScriptType(scriptType); }, classifyEventHandlerAttr(attrName) { return lowLevelEventHandlerAttr(attrName); }, rewriteCSS(source, baseUrl, controlPrefix) { return lowLevelCSS(source, baseUrl, controlPrefix); }, rewriteImportMap(source, baseUrl, tabId, runtimeToken, controlPrefix) { return { ok: true, code: lowLevelImportMap(source, baseUrl, tabId, runtimeToken, controlPrefix), error: '' }; }, rewriteHTMLDocument(source, targetUrl, controlPrefix, servers, runtimePrelude, tabId, runtimeToken) { return lowLevelHTMLDocument(source, targetUrl, controlPrefix, servers, runtimePrelude, tabId, runtimeToken); }, makeShareURL(target, servers) { return lowLevelShareURL(target, servers); }, rewriteFunctionBody: rewriteFunctionBodyRaw });`,
286289
`const rewriterApi = Object.freeze({ VERSION, get ready() { return initialized; }, init, initSync, rewriteScript: rewriteScriptPublic, rewriteScriptURL: rewriteScriptURLPublic, rewriteFetchURL: rewriteFetchURLPublic, rewriteSrcset: rewriteSrcsetPublic, rewriteTargetURL: rewriteTargetURLPublic, classifyLinkRel: classifyLinkRelPublic, classifyBlockedElement: classifyBlockedElementPublic, classifyMetaPolicy: classifyMetaPolicyPublic, classifyAttrPolicy: classifyAttrPolicyPublic, classifyScriptType: classifyScriptTypePublic, classifyEventHandlerAttr: classifyEventHandlerAttrPublic, rewriteCSS: rewriteCSSPublic, rewriteImportMap: rewriteImportMapPublic, rewriteHTMLDocument: rewriteHTMLDocumentPublic, makeShareURL: makeShareURLPublic, rewriteFunctionBody: rewriteFunctionBodyPublic, blockSource() { return BLOCK_CODE; } });`,
287-
`Object.defineProperty(globalThis, 'ZPRustRewriter', { value: rustApi, enumerable: false, configurable: false, writable: false });`,
288-
`Object.defineProperty(globalThis, 'ZPRewriter', { value: rewriterApi, enumerable: false, configurable: false, writable: false });`,
290+
`function defineHiddenAPI(name, value) { const d = Object.getOwnPropertyDescriptor(globalThis, name); if (d && d.configurable === false) return d.value; Object.defineProperty(globalThis, name, { value, enumerable: false, configurable: false, writable: false }); return value; }`,
291+
`defineHiddenAPI('ZPRustRewriter', rustApi);`,
292+
`defineHiddenAPI('ZPRewriter', rewriterApi);`,
289293
`bootstrapInit();`,
290294
'})();',
291295
'',

test/js/rewriter.test.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,38 @@ test('Rust rewriter asset exposes the public rewriter API without JS fallback as
145145
out.code,
146146
'__zp_get(__zp_get(__zp_get(globalThis,"window"),"location"),"href")',
147147
);
148-
assert.equal('OXCParser' in ctx, false);
148+
assert.equal('wasm_bindgen' in ctx, false);
149149
});
150150

151151
test('Rust rewriter initialization stays within a coarse budget', async () => {
152152
const ctx = await loadBuiltRustContext();
153153
assertWithinBudget('rust-rewriter initSync asset load', ctx.__rustRewriterInitMs, 1000);
154154
});
155155

156+
test('Rust rewriter asset is idempotent in a single realm', async () => {
157+
const ctx = await loadBuiltRustContext();
158+
const rustRewriter = ctx.ZPRustRewriter;
159+
const publicRewriter = ctx.ZPRewriter;
160+
const rustDescriptor = Object.getOwnPropertyDescriptor(ctx, 'ZPRustRewriter');
161+
const publicDescriptor = Object.getOwnPropertyDescriptor(ctx, 'ZPRewriter');
162+
assert.equal(rustDescriptor.enumerable, false);
163+
assert.equal(rustDescriptor.configurable, false);
164+
assert.equal(rustDescriptor.writable, false);
165+
assert.equal(publicDescriptor.enumerable, false);
166+
assert.equal(publicDescriptor.configurable, false);
167+
assert.equal(publicDescriptor.writable, false);
168+
169+
assert.doesNotThrow(() => {
170+
vm.runInContext(
171+
fs.readFileSync(path.join(ctx.__buildOutDir, 'web', 'rust-rewriter.js'), 'utf8'),
172+
ctx,
173+
{ filename: 'rust-rewriter.js' },
174+
);
175+
});
176+
assert.equal(ctx.ZPRustRewriter, rustRewriter);
177+
assert.equal(ctx.ZPRewriter, publicRewriter);
178+
});
179+
156180
test('Rust rewriter latency stays within coarse size-bucket budgets', async () => {
157181
const rewriter = await loadRewriter();
158182
const cases = [
@@ -219,7 +243,7 @@ test('Vite-built runtime prelude remains a classic bundled target asset', async
219243
assert.equal(/^\s*import\s/m.test(runtime), false);
220244
assert.equal(/^\s*export\s/m.test(runtime), false);
221245
assert.ok(runtime.includes('SHARE_INFO_ENC'), 'runtime bundle should include zp-core');
222-
assert.ok(runtime.includes('Object.defineProperty(globalThis, "ZPRustRewriter"'));
246+
assert.ok(runtime.includes('defineHiddenAPI("ZPRustRewriter"'));
223247
assert.ok(runtime.includes('Object.defineProperty(globalThis, "ZPHTTPRewriter"'));
224248
for (const asset of ['zp-core', 'rust-rewriter', 'http-rewriter']) {
225249
assert.equal(runtime.includes(`<script nonce=zp src=/zp/assets/${asset}.js>`), false);
@@ -261,7 +285,7 @@ test('Vite-built service worker remains a classic bundled runtime asset', async
261285
assert.equal(/^\s*import\s/m.test(sw), false);
262286
assert.equal(/^\s*export\s/m.test(sw), false);
263287
assert.ok(sw.includes('SHARE_INFO_ENC'), 'service worker bundle should include zp-core');
264-
assert.ok(sw.includes('Object.defineProperty(globalThis, "ZPRustRewriter"'));
288+
assert.ok(sw.includes('defineHiddenAPI("ZPRustRewriter"'));
265289
assert.ok(sw.includes('Object.defineProperty(globalThis, "ZPHTTPRewriter"'));
266290
assert.ok(sw.includes('globalThis.Go = class'));
267291
assert.ok(sw.includes('const go = new Go()'));

web/runtime-prelude.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { createWorkerFacades } from './runtime/workers/facades.mjs';
5454
let activeProxyFragment = preservedShareFragment(initialProxyURL.hash);
5555
let activeRouteKey = ZP.isSharePath(activeProxyPath) ? ZP.shareRouteKey(activeProxyPath) : '';
5656
let virtualURL = new URL(boot.targetUrl);
57+
const srcdocVisibleURL = initialProxyURL.href === 'about:srcdoc' ? new URL('about:srcdoc') : null;
5758
let activeEntryId = boot.entryId;
5859
let baseURL = virtualURL.href;
5960
let explicitBaseURL = '';
@@ -393,6 +394,9 @@ import { createWorkerFacades } from './runtime/workers/facades.mjs';
393394
return u.href;
394395
} catch { return ''; }
395396
}
397+
function visibleLocationURL() {
398+
return srcdocVisibleURL || virtualURL;
399+
}
396400
function documentReferrerFor(target) {
397401
const source = referrerURLWithoutHash(virtualURL.href);
398402
if (!source) return '';
@@ -997,6 +1001,7 @@ import { createWorkerFacades } from './runtime/workers/facades.mjs';
9971001
const { virtualLocation, crossWindowLocation } = createLocationFacades({
9981002
Native,
9991003
getVirtualURL: () => virtualURL,
1004+
getVisibleURL: visibleLocationURL,
10001005
setVirtualLocation,
10011006
updateVirtualHash,
10021007
maskMethods,
@@ -1789,9 +1794,9 @@ import { createWorkerFacades } from './runtime/workers/facades.mjs';
17891794
return writeAttr(el, ns, attrName, rewritten.actual);
17901795
}
17911796
function installGetterMasking(w) {
1792-
const locGet = p => () => new URL(virtualURL.href)[p];
1797+
const locGet = p => () => new URL(visibleLocationURL().href)[p];
17931798
for (const p of ['href','protocol','host','hostname','port','pathname','search','hash','origin']) defineAccessor(w.Location && w.Location.prototype, p, locGet(p), p === 'href' ? v => { setVirtualLocation(v); } : p === 'hash' ? v => { updateVirtualHash(v); } : undefined);
1794-
define(w.Location && w.Location.prototype, 'toString', function(){ return virtualURL.href; });
1799+
define(w.Location && w.Location.prototype, 'toString', function(){ return visibleLocationURL().href; });
17951800
installDocumentAccessors(w);
17961801
installURLProp(w.HTMLAnchorElement && w.HTMLAnchorElement.prototype, 'href');
17971802
installURLProp(w.HTMLAreaElement && w.HTMLAreaElement.prototype, 'href');

web/runtime/facades/location.mjs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
export function createLocationFacades({
22
Native,
33
getVirtualURL,
4+
getVisibleURL = getVirtualURL,
45
setVirtualLocation,
56
updateVirtualHash,
67
maskMethods,
78
maskNativeFunction,
89
}) {
910
const virtualLocation = {
10-
get href() { return getVirtualURL().href; },
11+
get href() { return getVisibleURL().href; },
1112
set href(v) { setVirtualLocation(v); },
12-
get protocol() { return getVirtualURL().protocol; },
13-
get host() { return getVirtualURL().host; },
14-
get hostname() { return getVirtualURL().hostname; },
15-
get port() { return getVirtualURL().port; },
16-
get pathname() { return getVirtualURL().pathname; },
17-
get search() { return getVirtualURL().search; },
18-
get hash() { return getVirtualURL().hash; },
13+
get protocol() { return getVisibleURL().protocol; },
14+
get host() { return getVisibleURL().host; },
15+
get hostname() { return getVisibleURL().hostname; },
16+
get port() { return getVisibleURL().port; },
17+
get pathname() { return getVisibleURL().pathname; },
18+
get search() { return getVisibleURL().search; },
19+
get hash() { return getVisibleURL().hash; },
1920
set hash(v) { updateVirtualHash(v); },
20-
get origin() { return getVirtualURL().origin; },
21+
get origin() { return getVisibleURL().origin; },
2122
assign(v) { setVirtualLocation(v); },
2223
replace(v) { setVirtualLocation(v, true); },
2324
reload() { Native.locationReload && Native.locationReload(); },
24-
toString() { return getVirtualURL().href; },
25-
valueOf() { return getVirtualURL().href; },
26-
[Symbol.toPrimitive]() { return getVirtualURL().href; }
25+
toString() { return getVisibleURL().href; },
26+
valueOf() { return getVisibleURL().href; },
27+
[Symbol.toPrimitive]() { return getVisibleURL().href; }
2728
};
2829
const crossWindowLocation = {
2930
get href() { return getVirtualURL().href; },

0 commit comments

Comments
 (0)