@ai-ecoverse/slicc-browsers 1.18.15 → 1.18.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guard-vectors.json +8 -8
- package/package.json +4 -4
- package/protected-error-page.mjs +90 -22
- package/unparseable-url.mjs +2 -1
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.18.
|
|
3
|
+
"setVersion": "1.18.17",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -1200,7 +1200,7 @@
|
|
|
1200
1200
|
}
|
|
1201
1201
|
},
|
|
1202
1202
|
"eventsRedacted": {
|
|
1203
|
-
"means": "protected error-page recover:
|
|
1203
|
+
"means": "protected error-page recover: every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
1204
1204
|
"requires": ["discovery"],
|
|
1205
1205
|
"check": {
|
|
1206
1206
|
"result": "errorPage",
|
|
@@ -1349,7 +1349,7 @@
|
|
|
1349
1349
|
}
|
|
1350
1350
|
},
|
|
1351
1351
|
"eventsRedacted": {
|
|
1352
|
-
"means": "protected error-page recover (query):
|
|
1352
|
+
"means": "protected error-page recover (query): every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
1353
1353
|
"requires": ["discovery"],
|
|
1354
1354
|
"check": {
|
|
1355
1355
|
"result": "errorPage",
|
|
@@ -1498,7 +1498,7 @@
|
|
|
1498
1498
|
}
|
|
1499
1499
|
},
|
|
1500
1500
|
"eventsRedacted": {
|
|
1501
|
-
"means": "protected error-page recover (fragment):
|
|
1501
|
+
"means": "protected error-page recover (fragment): every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
1502
1502
|
"requires": ["discovery"],
|
|
1503
1503
|
"check": {
|
|
1504
1504
|
"result": "errorPage",
|
|
@@ -1646,7 +1646,7 @@
|
|
|
1646
1646
|
}
|
|
1647
1647
|
},
|
|
1648
1648
|
"eventsRedacted": {
|
|
1649
|
-
"means": "protected error-page restore:
|
|
1649
|
+
"means": "protected error-page restore: every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
1650
1650
|
"requires": ["discovery"],
|
|
1651
1651
|
"check": {
|
|
1652
1652
|
"result": "errorPage",
|
|
@@ -1827,7 +1827,7 @@
|
|
|
1827
1827
|
}
|
|
1828
1828
|
},
|
|
1829
1829
|
"eventsRedacted": {
|
|
1830
|
-
"means": "protected error-page sticky:
|
|
1830
|
+
"means": "protected error-page sticky: every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
1831
1831
|
"requires": ["discovery"],
|
|
1832
1832
|
"check": {
|
|
1833
1833
|
"result": "errorPage",
|
|
@@ -2020,7 +2020,7 @@
|
|
|
2020
2020
|
}
|
|
2021
2021
|
},
|
|
2022
2022
|
"eventsRedacted": {
|
|
2023
|
-
"means": "protected error-page sticky (query):
|
|
2023
|
+
"means": "protected error-page sticky (query): every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
2024
2024
|
"requires": ["discovery"],
|
|
2025
2025
|
"check": {
|
|
2026
2026
|
"result": "errorPage",
|
|
@@ -2213,7 +2213,7 @@
|
|
|
2213
2213
|
}
|
|
2214
2214
|
},
|
|
2215
2215
|
"eventsRedacted": {
|
|
2216
|
-
"means": "protected error-page sticky (fragment):
|
|
2216
|
+
"means": "protected error-page sticky (fragment): every discovery event for the tab shows the failed URL's origin only or a masked or absent url, and no url or title carries the failed URL, its path, query or fragment; one after the client's navigation shows that origin, Chrome's error page, or an empty url titled with the protected host",
|
|
2217
2217
|
"requires": ["discovery"],
|
|
2218
2218
|
"check": {
|
|
2219
2219
|
"result": "errorPage",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.17",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"validate": "node scripts/validate.mjs",
|
|
69
69
|
"lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && GOOS=darwin go vet ./... && GOOS=windows go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings && cargo check --target wasm32-wasip1 --no-default-features && cargo check --target wasm32-wasip1 --features serde && cargo check --target wasm32-wasip1 --all-features",
|
|
70
70
|
"format": "biome check --write . && gofmt -w . && cargo fmt",
|
|
71
|
-
"test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-browser-ui.test.mjs test/integration/guard-snapshot-settle.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-exit-refusal.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-marker.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/guard-world-paint.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-sweep-union.test.mjs && node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/chromium-launch.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-bfcache-chrome.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
|
|
71
|
+
"test": "export PATH=\"$HOME/.cargo/bin:$PATH\" && node --test --test-concurrency=1 test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-redirect-history.test.mjs test/integration/guard-redirect-iframe.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-unparseable-wipe.test.mjs test/integration/guard-browser-ui.test.mjs test/integration/guard-snapshot-settle.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-exit-refusal.test.mjs test/integration/guard-frame-read.test.mjs test/integration/guard-bfcache.test.mjs test/integration/guard-bfcache-marker.test.mjs test/integration/guard-bfcache-history.test.mjs test/integration/guard-bfcache-review.test.mjs test/integration/guard-bfcache-quarantine.test.mjs test/integration/guard-error-page-navigation.test.mjs test/integration/guard-error-page-events.test.mjs test/integration/guard-world-paint.test.mjs test/integration/gates.test.mjs test/integration/validate.test.mjs test/integration/go.test.mjs test/integration/cargo.test.mjs test/integration/workflows.test.mjs test/integration/guard-sweep-union.test.mjs && node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs test/integration/chromium-launch.test.mjs test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-redirect-secret.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-discovery.test.mjs test/integration/guard-bfcache-chrome.test.mjs test/integration/guard-browser-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/guard-pre-beacon.test.mjs test/integration/guard-world-pause.test.mjs test/integration/guard-parallel.test.mjs test/integration/guard-client-exit.test.mjs test/integration/guard-runner.test.mjs test/integration/guard-sweep-ordinary.test.mjs test/integration/guard-sweep-seven.test.mjs test/integration/guard-sweep-vault.test.mjs test/integration/guard-sweep-vault-x.test.mjs test/integration/guard-sweep-apex.test.mjs test/integration/guard-sweep-others.test.mjs",
|
|
72
72
|
"test:unit": "mkdir -p coverage/unit && node --experimental-test-coverage --test --test-concurrency=1 --test-reporter=lcov --test-reporter-destination=coverage/unit/lcov.info test/unit/*.test.mjs && go test -race -cover ./...",
|
|
73
73
|
"build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
|
|
74
74
|
"prepublishOnly": "node scripts/set-crate-version.mjs \"$npm_package_version\"",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"test:unit:guard": "mkdir -p coverage/unit && node --experimental-vm-modules --no-warnings --experimental-test-coverage --test --test-concurrency=1 --test-coverage-include=guard-runner.mjs --test-coverage-include=guard-driver.mjs --test-coverage-include=fixture-server.mjs --test-coverage-include=fixture-client.mjs --test-coverage-include=observer-target.mjs --test-coverage-include=frame-read.mjs --test-coverage-include=bfcache-restore.mjs --test-coverage-include=protected-error-page.mjs --test-coverage-include=unparseable-url.mjs --test-coverage-include=scripts/browser-runner.mjs --test-coverage-include=scripts/validate.mjs --test-reporter=lcov --test-reporter-destination=coverage/unit/guard.lcov test/unit/guard-*.test.mjs && slicc-diff-cover coverage/unit/guard.lcov"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@ai-ecoverse/slicc-cdp": "1.
|
|
83
|
-
"@ai-ecoverse/slicc-shared-web": "2.
|
|
82
|
+
"@ai-ecoverse/slicc-cdp": "1.42.3",
|
|
83
|
+
"@ai-ecoverse/slicc-shared-web": "2.13.4",
|
|
84
84
|
"@biomejs/biome": "2.5.15",
|
|
85
85
|
"ajv": "8.20.0",
|
|
86
86
|
"ajv-formats": "3.0.1",
|
package/protected-error-page.mjs
CHANGED
|
@@ -51,12 +51,79 @@ async function lockChecks({ client, session, targetId, result }) {
|
|
|
51
51
|
result.closeRefused = refused(await outcome(client.send('Target.closeTarget', { targetId })));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const discoveryMethods = ['Target.targetCreated', 'Target.targetInfoChanged'];
|
|
55
|
+
const errorPageURL = 'chrome-error://chromewebdata/';
|
|
56
|
+
const lower = (value) => String(value ?? '').toLowerCase();
|
|
57
|
+
const plain = (value) =>
|
|
58
|
+
lower(
|
|
59
|
+
String(value ?? '').replace(/%([0-9a-f]{2})/gi, (_, hex) =>
|
|
60
|
+
String.fromCharCode(Number.parseInt(hex, 16))
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
const unschemed = (value) => value.replace(/^[a-z][a-z\d+.-]*:\/\//, '');
|
|
64
|
+
|
|
65
|
+
const encoded = (url) =>
|
|
66
|
+
Array.from(
|
|
67
|
+
new TextEncoder().encode(url),
|
|
68
|
+
(byte) => `%${byte.toString(16).toUpperCase().padStart(2, '0')}`
|
|
69
|
+
).join('');
|
|
70
|
+
|
|
71
|
+
const triggerURL = (ordinary, url) => `${ordinary}/page?k=deferred&defer=0&to=${encoded(url)}`;
|
|
72
|
+
|
|
73
|
+
function discoveryRules({ failedURL, tag, trigger }) {
|
|
74
|
+
const failed = new URL(failedURL);
|
|
75
|
+
const { origin, host, hostname } = failed;
|
|
76
|
+
const named = [hostname, host, origin];
|
|
77
|
+
const parts = [
|
|
78
|
+
failedURL,
|
|
79
|
+
`${failed.pathname}${failed.search}${failed.hash}`,
|
|
80
|
+
failed.pathname,
|
|
81
|
+
failed.search.slice(1),
|
|
82
|
+
failed.hash.slice(1),
|
|
83
|
+
tag,
|
|
84
|
+
]
|
|
85
|
+
.filter((part) => part && part !== '/')
|
|
86
|
+
.map(plain);
|
|
87
|
+
const forms = (value) => [lower(value), plain(value)];
|
|
88
|
+
const leaks = (value) => forms(value).some((form) => parts.some((part) => form.includes(part)));
|
|
89
|
+
const names = (value) =>
|
|
90
|
+
forms(value).some((form) => form.includes(hostname)) || parseURL(value)?.origin === origin;
|
|
91
|
+
const cleanTitle = (title) => !leaks(title) && (!names(title) || named.includes(lower(title)));
|
|
92
|
+
const ownTitle = ({ url, title }) =>
|
|
93
|
+
plain(title) !== '' && [plain(url), unschemed(plain(url))].includes(plain(title));
|
|
94
|
+
const home = parseURL(trigger)?.href;
|
|
95
|
+
const clean = (info) => {
|
|
96
|
+
if (parseURL(info.url)?.href === home) return ownTitle(info) || cleanTitle(info.title);
|
|
97
|
+
return (
|
|
98
|
+
!leaks(info.url) && (lower(info.url) === origin || !names(info.url)) && cleanTitle(info.title)
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
const shows = ({ url, title }) =>
|
|
102
|
+
[origin, errorPageURL].includes(lower(url)) ||
|
|
103
|
+
(lower(url) === '' && named.includes(lower(title)));
|
|
104
|
+
return { clean, shows };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function eventsRedacted({ events, targetId, start, sent, ...failure }) {
|
|
108
|
+
const infos = (from) =>
|
|
109
|
+
events
|
|
110
|
+
.slice(from)
|
|
111
|
+
.filter(
|
|
112
|
+
(e) => discoveryMethods.includes(e.method) && e.params?.targetInfo?.targetId === targetId
|
|
113
|
+
)
|
|
114
|
+
.map((e) => e.params.targetInfo);
|
|
115
|
+
const { clean, shows } = discoveryRules(failure);
|
|
116
|
+
return infos(sent).some(shows) && infos(start).every(clean);
|
|
117
|
+
}
|
|
118
|
+
|
|
54
119
|
async function redaction({
|
|
55
120
|
client,
|
|
56
121
|
targetId,
|
|
57
122
|
failedURL,
|
|
58
|
-
|
|
123
|
+
ordinary,
|
|
124
|
+
tag,
|
|
59
125
|
start,
|
|
126
|
+
sent,
|
|
60
127
|
result,
|
|
61
128
|
discovery,
|
|
62
129
|
}) {
|
|
@@ -83,16 +150,16 @@ async function redaction({
|
|
|
83
150
|
info.result?.targetInfo?.url === failedURL &&
|
|
84
151
|
listed?.url === failedURL &&
|
|
85
152
|
attachedInfo?.url === failedURL;
|
|
86
|
-
const targetEvents = client.events
|
|
87
|
-
.slice(start)
|
|
88
|
-
.filter(
|
|
89
|
-
(e) =>
|
|
90
|
-
['Target.targetCreated', 'Target.targetInfoChanged'].includes(e.method) &&
|
|
91
|
-
e.params?.targetInfo?.targetId === targetId &&
|
|
92
|
-
e.params.targetInfo.url.includes(new URL(protectedOrigin).host)
|
|
93
|
-
);
|
|
94
153
|
result.eventsRedacted = discovery
|
|
95
|
-
?
|
|
154
|
+
? eventsRedacted({
|
|
155
|
+
events: client.events,
|
|
156
|
+
targetId,
|
|
157
|
+
start,
|
|
158
|
+
sent,
|
|
159
|
+
failedURL,
|
|
160
|
+
tag,
|
|
161
|
+
trigger: triggerURL(ordinary, failedURL),
|
|
162
|
+
})
|
|
96
163
|
: 'not-supported';
|
|
97
164
|
}
|
|
98
165
|
|
|
@@ -120,19 +187,9 @@ async function seed(reader, observer, loaded, initial, allowed) {
|
|
|
120
187
|
return out.ok ? out.result : `observer setup failed: ${out.error.message}`;
|
|
121
188
|
}
|
|
122
189
|
|
|
123
|
-
const encoded = (url) =>
|
|
124
|
-
Array.from(
|
|
125
|
-
new TextEncoder().encode(url),
|
|
126
|
-
(byte) => `%${byte.toString(16).toUpperCase().padStart(2, '0')}`
|
|
127
|
-
).join('');
|
|
128
|
-
|
|
129
190
|
async function scripted(client, session, ordinary, url) {
|
|
130
191
|
const out = await outcome(
|
|
131
|
-
client.send(
|
|
132
|
-
'Page.navigate',
|
|
133
|
-
{ url: `${ordinary}/page?k=deferred&defer=0&to=${encoded(url)}` },
|
|
134
|
-
session
|
|
135
|
-
)
|
|
192
|
+
client.send('Page.navigate', { url: triggerURL(ordinary, url) }, session)
|
|
136
193
|
);
|
|
137
194
|
return out.ok ? undefined : `client navigation failed: ${out.error.message}`;
|
|
138
195
|
}
|
|
@@ -233,6 +290,7 @@ export async function protectedErrorPage({
|
|
|
233
290
|
);
|
|
234
291
|
}
|
|
235
292
|
await outcome(reader.send('Page.bringToFront', {}, observer));
|
|
293
|
+
const sent = client.events.length;
|
|
236
294
|
const unsent = await scripted(client, session, ordinary, failedURL);
|
|
237
295
|
if (unsent) return missing(unsent);
|
|
238
296
|
let evidence;
|
|
@@ -259,7 +317,17 @@ export async function protectedErrorPage({
|
|
|
259
317
|
return missing(`missing error commit ${JSON.stringify(evidence)}`);
|
|
260
318
|
result.live = live;
|
|
261
319
|
if (!live) return { ...result, liveWhy: commitWhy(result.diagnostics.errorCommit) };
|
|
262
|
-
await redaction({
|
|
320
|
+
await redaction({
|
|
321
|
+
client,
|
|
322
|
+
targetId,
|
|
323
|
+
failedURL,
|
|
324
|
+
ordinary,
|
|
325
|
+
tag,
|
|
326
|
+
start,
|
|
327
|
+
sent,
|
|
328
|
+
result,
|
|
329
|
+
discovery,
|
|
330
|
+
});
|
|
263
331
|
const beforeReload = (await frame()).loaderId;
|
|
264
332
|
result.reload = (await outcome(client.send('Page.reload', {}, session))).ok;
|
|
265
333
|
if (result.reload) {
|
package/unparseable-url.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { createObserverTarget } from './observer-target.mjs';
|
|
|
3
3
|
|
|
4
4
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
5
|
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
6
|
+
const rejected = new Set(['', 'about:blank', 'data:text/html,%3Ctitle%3Eblocked%3C%2Ftitle%3E']);
|
|
6
7
|
const hosts = [
|
|
7
8
|
['exa mple.sliccy.ai', 'exa%20mple.sliccy.ai'],
|
|
8
9
|
['exa%20mple.sliccy.ai', 'exa%20mple.sliccy.ai'],
|
|
@@ -63,7 +64,7 @@ async function document(reader, targetId, expected, allowNativeRejection = false
|
|
|
63
64
|
if (out.ok && value?.title === 'doc' && canonicalURL(value.href) === expected) return value;
|
|
64
65
|
await pause(25);
|
|
65
66
|
} while (Date.now() < deadline);
|
|
66
|
-
if (allowNativeRejection &&
|
|
67
|
+
if (allowNativeRejection && rejected.has(canonicalURL(targetURL)))
|
|
67
68
|
return { nativeRejected: true, url: last?.url ?? '' };
|
|
68
69
|
throw new Error(
|
|
69
70
|
`unparseable: Chrome did not load a fixture document at ${expected}: ${JSON.stringify(last)}`
|