@ai-ecoverse/slicc-browsers 1.10.3 → 1.12.0
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/README.md +20 -6
- package/catalog.json +5 -0
- package/extension-world.mjs +28 -4
- package/guard-driver.mjs +5 -1
- package/guard-runner.mjs +92 -17
- package/guard-vectors.json +1199 -1
- package/package.json +6 -5
- package/protected-error-page.mjs +259 -0
- package/protected.json +37 -0
- package/protected.mjs +16 -1
- package/schema/catalog.schema.json +10 -0
- package/schema/guard-vectors.schema.json +11 -0
- package/schema/protected.schema.json +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"guard-runner.mjs",
|
|
53
53
|
"fixture-server.mjs",
|
|
54
54
|
"extension-world.mjs",
|
|
55
|
-
"unparseable-url.mjs"
|
|
55
|
+
"unparseable-url.mjs",
|
|
56
|
+
"protected-error-page.mjs"
|
|
56
57
|
],
|
|
57
58
|
"scripts": {
|
|
58
59
|
"prepare": "lefthook install",
|
|
@@ -67,18 +68,18 @@
|
|
|
67
68
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
68
69
|
"release": "semantic-release",
|
|
69
70
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
70
|
-
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.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-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/self-contained.test.mjs",
|
|
71
|
+
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.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-error-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/self-contained.test.mjs",
|
|
71
72
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
72
73
|
"test:gates": "node --test test/integration/gates.test.mjs",
|
|
73
74
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
74
75
|
"test:unit:guard": "node --experimental-test-coverage --test --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-lines=100 test/unit/guard-*.test.mjs"
|
|
75
76
|
},
|
|
76
77
|
"devDependencies": {
|
|
77
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
78
|
+
"@ai-ecoverse/slicc-shared-web": "1.29.1",
|
|
78
79
|
"@biomejs/biome": "2.5.15",
|
|
79
80
|
"ajv": "8.20.0",
|
|
80
81
|
"ajv-formats": "3.0.1",
|
|
81
|
-
"lefthook": "2.2.
|
|
82
|
+
"lefthook": "2.2.2",
|
|
82
83
|
"playwright-core": "1.64.1",
|
|
83
84
|
"semantic-release": "25.0.9"
|
|
84
85
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { outcome } from './guard-driver.mjs';
|
|
2
|
+
|
|
3
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
5
|
+
|
|
6
|
+
async function until(check) {
|
|
7
|
+
const deadline = Date.now() + 5000;
|
|
8
|
+
do {
|
|
9
|
+
if (await check()) return true;
|
|
10
|
+
await pause(25);
|
|
11
|
+
} while (Date.now() < deadline);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function loadedDocument(reader, observer, url) {
|
|
16
|
+
return until(async () => {
|
|
17
|
+
const { frameTree } = await reader.send('Page.getFrameTree', {}, observer);
|
|
18
|
+
if (frameTree.frame.url !== url || frameTree.frame.unreachableUrl) return false;
|
|
19
|
+
const ready = await outcome(
|
|
20
|
+
reader.send(
|
|
21
|
+
'Runtime.evaluate',
|
|
22
|
+
{ expression: 'document.readyState', returnByValue: true },
|
|
23
|
+
observer
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
return ready.result?.result?.value === 'complete';
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function lockChecks({ client, session, targetId, result }) {
|
|
31
|
+
result.evaluateRefused = refused(
|
|
32
|
+
await outcome(
|
|
33
|
+
client.send(
|
|
34
|
+
'Runtime.evaluate',
|
|
35
|
+
{ expression: 'document.title', returnByValue: true },
|
|
36
|
+
session
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
result.attachRefused = refused(
|
|
41
|
+
await outcome(client.send('Target.attachToTarget', { targetId, flatten: true }))
|
|
42
|
+
);
|
|
43
|
+
result.closeRefused = refused(await outcome(client.send('Target.closeTarget', { targetId })));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function redaction({ client, targetId, failedURL, protectedOrigin, start, result }) {
|
|
47
|
+
const info = await outcome(client.send('Target.getTargetInfo', { targetId }));
|
|
48
|
+
const list = await outcome(client.send('Target.getTargets'));
|
|
49
|
+
const before = client.events.length;
|
|
50
|
+
const again = await outcome(client.send('Target.attachToTarget', { targetId, flatten: true }));
|
|
51
|
+
const origin = new URL(failedURL).origin;
|
|
52
|
+
const only = (i) => i?.url === origin && i.title === origin;
|
|
53
|
+
const listed = list.ok ? list.result.targetInfos.find((i) => i.targetId === targetId) : undefined;
|
|
54
|
+
await pause(100);
|
|
55
|
+
const attachedInfo = client.events
|
|
56
|
+
.slice(before)
|
|
57
|
+
.find(
|
|
58
|
+
(e) =>
|
|
59
|
+
e.method === 'Target.attachedToTarget' && e.params.sessionId === again.result?.sessionId
|
|
60
|
+
)?.params.targetInfo;
|
|
61
|
+
if (again.ok)
|
|
62
|
+
await outcome(client.send('Target.detachFromTarget', { sessionId: again.result.sessionId }));
|
|
63
|
+
result.infoRedacted = info.ok && only(info.result.targetInfo);
|
|
64
|
+
result.listRedacted = only(listed);
|
|
65
|
+
result.attachRedacted = only(attachedInfo);
|
|
66
|
+
result.fullURL =
|
|
67
|
+
info.result?.targetInfo?.url === failedURL &&
|
|
68
|
+
listed?.url === failedURL &&
|
|
69
|
+
attachedInfo?.url === failedURL;
|
|
70
|
+
const targetEvents = client.events
|
|
71
|
+
.slice(start)
|
|
72
|
+
.filter(
|
|
73
|
+
(e) =>
|
|
74
|
+
e.params?.targetInfo?.targetId === targetId &&
|
|
75
|
+
e.params.targetInfo.url.includes(new URL(protectedOrigin).host)
|
|
76
|
+
);
|
|
77
|
+
result.eventsRedacted =
|
|
78
|
+
targetEvents.length > 0 && targetEvents.every((e) => only(e.params.targetInfo));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const secrets = {
|
|
82
|
+
all: (tag) => `/doc/path-secret-${tag}?query-secret=${tag}#fragment-secret-${tag}`,
|
|
83
|
+
query: (tag) => `/doc?query-secret=${tag}`,
|
|
84
|
+
fragment: (tag) => `/doc#fragment-secret-${tag}`,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export async function protectedErrorPage({
|
|
88
|
+
client,
|
|
89
|
+
reader,
|
|
90
|
+
ordinary,
|
|
91
|
+
protectedOrigin,
|
|
92
|
+
tag,
|
|
93
|
+
mode,
|
|
94
|
+
side,
|
|
95
|
+
internalSessions = new Set(),
|
|
96
|
+
secret = 'all',
|
|
97
|
+
}) {
|
|
98
|
+
const initial = `${protectedOrigin}/doc?n=${tag}-history`;
|
|
99
|
+
const failedURL = `${protectedOrigin}${secrets[secret](tag)}`;
|
|
100
|
+
const result = {};
|
|
101
|
+
const start = client.events.length;
|
|
102
|
+
const { targetId } = await reader.send('Target.createTarget', { url: initial, background: true });
|
|
103
|
+
let session;
|
|
104
|
+
let observer;
|
|
105
|
+
let failing = false;
|
|
106
|
+
let cursor = reader.events.length;
|
|
107
|
+
const { sessionId } = await reader.send('Target.attachToTarget', { targetId, flatten: true });
|
|
108
|
+
observer = sessionId;
|
|
109
|
+
internalSessions.add(observer);
|
|
110
|
+
const frame = async () => (await reader.send('Page.getFrameTree', {}, observer)).frameTree.frame;
|
|
111
|
+
const loaded = (url) => loadedDocument(reader, observer, url);
|
|
112
|
+
let stopped = false;
|
|
113
|
+
const fail = (async () => {
|
|
114
|
+
while (!stopped) {
|
|
115
|
+
for (const event of reader.events.slice(cursor)) {
|
|
116
|
+
cursor++;
|
|
117
|
+
if (event.method === 'Fetch.requestPaused' && event.sessionId === observer)
|
|
118
|
+
await outcome(
|
|
119
|
+
reader.send(
|
|
120
|
+
failing ? 'Fetch.failRequest' : 'Fetch.continueRequest',
|
|
121
|
+
failing
|
|
122
|
+
? { requestId: event.params.requestId, errorReason: 'BlockedByClient' }
|
|
123
|
+
: { requestId: event.params.requestId },
|
|
124
|
+
observer
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
await pause(5);
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
try {
|
|
132
|
+
await reader.send('Page.enable', {}, observer);
|
|
133
|
+
if (!(await loaded(initial)))
|
|
134
|
+
throw new Error('protected-error-page: history seed did not commit');
|
|
135
|
+
await reader.send('Page.navigate', { url: `${ordinary}/blank` }, observer);
|
|
136
|
+
if (!(await loaded(`${ordinary}/blank`)))
|
|
137
|
+
throw new Error('protected-error-page: allowed document did not commit');
|
|
138
|
+
const attached = await outcome(
|
|
139
|
+
client.send('Target.attachToTarget', { targetId, flatten: true })
|
|
140
|
+
);
|
|
141
|
+
session = attached.result?.sessionId;
|
|
142
|
+
if (!session) return { live: false, attached: false };
|
|
143
|
+
await client.send('Page.enable', {}, session);
|
|
144
|
+
await client.send('Runtime.enable', {}, session);
|
|
145
|
+
await client.send('Target.setDiscoverTargets', { discover: true });
|
|
146
|
+
if (side === 'control') {
|
|
147
|
+
failing = true;
|
|
148
|
+
await reader.send(
|
|
149
|
+
'Fetch.enable',
|
|
150
|
+
{ patterns: [{ resourceType: 'Document', requestStage: 'Request' }] },
|
|
151
|
+
observer
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
await client.send(
|
|
155
|
+
'Runtime.evaluate',
|
|
156
|
+
{ expression: `setTimeout(()=>location.href=${JSON.stringify(failedURL)},0)` },
|
|
157
|
+
session
|
|
158
|
+
);
|
|
159
|
+
result.live = await until(async () => {
|
|
160
|
+
const f = await frame();
|
|
161
|
+
const { targetInfo } = await reader.send('Target.getTargetInfo', { targetId });
|
|
162
|
+
result.evidence = { frame: f, targetInfo };
|
|
163
|
+
return (
|
|
164
|
+
f.url === 'chrome-error://chromewebdata/' &&
|
|
165
|
+
f.securityOrigin === '://' &&
|
|
166
|
+
f.unreachableUrl === failedURL &&
|
|
167
|
+
targetInfo.url === failedURL
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
if (!result.live && side !== 'control') return result;
|
|
171
|
+
if (!result.live)
|
|
172
|
+
throw new Error(
|
|
173
|
+
`protected-error-page: missing error commit ${JSON.stringify(result.evidence)}`
|
|
174
|
+
);
|
|
175
|
+
await redaction({ client, targetId, failedURL, protectedOrigin, start, result });
|
|
176
|
+
const beforeReload = (await frame()).loaderId;
|
|
177
|
+
result.reload = (await outcome(client.send('Page.reload', {}, session))).ok;
|
|
178
|
+
if (result.reload) {
|
|
179
|
+
result.reloadedError = await until(async () => {
|
|
180
|
+
const f = await frame();
|
|
181
|
+
return f.loaderId !== beforeReload && f.unreachableUrl === failedURL;
|
|
182
|
+
});
|
|
183
|
+
result.reload = result.reloadedError;
|
|
184
|
+
}
|
|
185
|
+
result.blank = (
|
|
186
|
+
await outcome(client.send('Page.navigate', { url: 'about:blank' }, session))
|
|
187
|
+
).ok;
|
|
188
|
+
if (result.blank) result.blank = await loaded('about:blank');
|
|
189
|
+
result.attached = !client.events
|
|
190
|
+
.slice(start)
|
|
191
|
+
.some((e) => e.method === 'Target.detachedFromTarget' && e.params.sessionId === session);
|
|
192
|
+
if (mode === 'recover') {
|
|
193
|
+
if (result.blank) {
|
|
194
|
+
await client.send(
|
|
195
|
+
'Runtime.evaluate',
|
|
196
|
+
{ expression: `setTimeout(()=>location.href=${JSON.stringify(failedURL)},0)` },
|
|
197
|
+
session
|
|
198
|
+
);
|
|
199
|
+
result.closeError = await until(async () => (await frame()).unreachableUrl === failedURL);
|
|
200
|
+
}
|
|
201
|
+
result.attached &&= !client.events
|
|
202
|
+
.slice(start)
|
|
203
|
+
.some((e) => e.method === 'Target.detachedFromTarget' && e.params.sessionId === session);
|
|
204
|
+
const close = await outcome(client.send('Target.closeTarget', { targetId }));
|
|
205
|
+
result.close = result.closeError === true && close.result?.success === true;
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
if (side === 'control') await reader.send('Fetch.disable', {}, observer);
|
|
209
|
+
failing = false;
|
|
210
|
+
const { entries, currentIndex } = await reader.send('Page.getNavigationHistory', {}, observer);
|
|
211
|
+
const index = entries.findIndex((e) => e.url === initial);
|
|
212
|
+
if (index < 0) throw new Error('protected-error-page: missing protected history');
|
|
213
|
+
const beforeRestore = reader.events.length;
|
|
214
|
+
await reader.send(
|
|
215
|
+
'Runtime.evaluate',
|
|
216
|
+
{ expression: `setTimeout(() => history.go(${index - currentIndex}), 0)` },
|
|
217
|
+
observer
|
|
218
|
+
);
|
|
219
|
+
result.restored = await until(() =>
|
|
220
|
+
reader.events
|
|
221
|
+
.slice(beforeRestore)
|
|
222
|
+
.some(
|
|
223
|
+
(e) =>
|
|
224
|
+
e.sessionId === observer &&
|
|
225
|
+
e.method === 'Page.frameNavigated' &&
|
|
226
|
+
e.params.type === 'BackForwardCacheRestore' &&
|
|
227
|
+
e.params.frame.url === initial
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
if (!result.restored)
|
|
231
|
+
throw new Error('protected-error-page: real protected BFCache restore required');
|
|
232
|
+
await pause(100);
|
|
233
|
+
if (mode === 'sticky') {
|
|
234
|
+
await reader.send('Target.detachFromTarget', { sessionId: observer });
|
|
235
|
+
observer = (await reader.send('Target.attachToTarget', { targetId, flatten: true }))
|
|
236
|
+
.sessionId;
|
|
237
|
+
internalSessions.add(observer);
|
|
238
|
+
failing = true;
|
|
239
|
+
await reader.send(
|
|
240
|
+
'Fetch.enable',
|
|
241
|
+
{ patterns: [{ resourceType: 'Document', requestStage: 'Request' }] },
|
|
242
|
+
observer
|
|
243
|
+
);
|
|
244
|
+
await reader.send('Page.navigate', { url: failedURL }, observer);
|
|
245
|
+
result.stickyError = await until(async () => (await frame()).unreachableUrl === failedURL);
|
|
246
|
+
if (!result.stickyError)
|
|
247
|
+
throw new Error(
|
|
248
|
+
`protected-error-page: missing sticky error ${JSON.stringify(await frame())}`
|
|
249
|
+
);
|
|
250
|
+
await pause(100);
|
|
251
|
+
}
|
|
252
|
+
await lockChecks({ client, session, targetId, result });
|
|
253
|
+
return result;
|
|
254
|
+
} finally {
|
|
255
|
+
stopped = true;
|
|
256
|
+
await fail;
|
|
257
|
+
await outcome(reader.send('Target.closeTarget', { targetId }));
|
|
258
|
+
}
|
|
259
|
+
}
|
package/protected.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
+
"errorPage": { "url": "chrome-error://chromewebdata/", "target": "previous", "redact": "origin" },
|
|
3
4
|
"unparseable": "protected",
|
|
4
5
|
"unparseableRefusal": "slicc: refused: slicc can't parse this URL, so it could be a protected page",
|
|
5
6
|
"origins": ["http://127.0.0.1:5710", "https://seven.example.test"],
|
|
@@ -738,6 +739,42 @@
|
|
|
738
739
|
}
|
|
739
740
|
],
|
|
740
741
|
"targets": [
|
|
742
|
+
{
|
|
743
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
744
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
745
|
+
"unreachableURL": "https://seven.sliccy.ai/other",
|
|
746
|
+
"protected": true
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
750
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
751
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
752
|
+
"navigating": true,
|
|
753
|
+
"protected": true
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
757
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
758
|
+
"protected": true
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
762
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
763
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
764
|
+
"protected": false
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
768
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
769
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
770
|
+
"previous": true,
|
|
771
|
+
"protected": true
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
775
|
+
"documentURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
776
|
+
"protected": true
|
|
777
|
+
},
|
|
741
778
|
{
|
|
742
779
|
"url": "http://exa mple.sliccy.ai/",
|
|
743
780
|
"protected": true
|
package/protected.mjs
CHANGED
|
@@ -68,8 +68,23 @@ export function protectedOrigins(rules, extraOrigins = []) {
|
|
|
68
68
|
);
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const target = ({
|
|
71
|
+
const target = ({
|
|
72
|
+
url: text,
|
|
73
|
+
previous = false,
|
|
74
|
+
opener,
|
|
75
|
+
type,
|
|
76
|
+
documentURL,
|
|
77
|
+
unreachableURL,
|
|
78
|
+
navigating = false,
|
|
79
|
+
}) => {
|
|
72
80
|
if (type && (rules.targetTypes ?? []).includes(type)) return true;
|
|
81
|
+
if (
|
|
82
|
+
rules.errorPage &&
|
|
83
|
+
documentURL === rules.errorPage.url &&
|
|
84
|
+
unreachableURL === text &&
|
|
85
|
+
!navigating
|
|
86
|
+
)
|
|
87
|
+
return previous;
|
|
73
88
|
if (inheriting(text)) return previous || opener === 'protected' || opener === 'unknown';
|
|
74
89
|
return url(text);
|
|
75
90
|
};
|
|
@@ -227,6 +227,16 @@
|
|
|
227
227
|
"uniqueItems": true,
|
|
228
228
|
"items": { "type": "string", "pattern": "^about:[a-z]+$" }
|
|
229
229
|
},
|
|
230
|
+
"errorPage": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"additionalProperties": false,
|
|
233
|
+
"required": ["url", "target", "redact"],
|
|
234
|
+
"properties": {
|
|
235
|
+
"url": { "const": "chrome-error://chromewebdata/" },
|
|
236
|
+
"target": { "const": "previous" },
|
|
237
|
+
"redact": { "const": "origin" }
|
|
238
|
+
}
|
|
239
|
+
},
|
|
230
240
|
"unparseable": { "const": "protected" },
|
|
231
241
|
"unparseableRefusal": {
|
|
232
242
|
"const": "slicc: refused: slicc can't parse this URL, so it could be a protected page"
|
|
@@ -427,6 +427,17 @@
|
|
|
427
427
|
},
|
|
428
428
|
"step": {
|
|
429
429
|
"oneOf": [
|
|
430
|
+
{
|
|
431
|
+
"type": "object",
|
|
432
|
+
"additionalProperties": false,
|
|
433
|
+
"required": ["errorPage", "as"],
|
|
434
|
+
"properties": {
|
|
435
|
+
"errorPage": { "enum": ["recover", "restore", "sticky"] },
|
|
436
|
+
"secret": { "enum": ["all", "query", "fragment"] },
|
|
437
|
+
"requires": { "$ref": "#/$defs/requires" },
|
|
438
|
+
"as": { "$ref": "#/$defs/name" }
|
|
439
|
+
}
|
|
440
|
+
},
|
|
430
441
|
{
|
|
431
442
|
"type": "object",
|
|
432
443
|
"additionalProperties": false,
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
],
|
|
17
17
|
"properties": {
|
|
18
18
|
"schema": { "const": 1 },
|
|
19
|
+
"errorPage": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": ["url", "target", "redact"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"url": { "const": "chrome-error://chromewebdata/" },
|
|
25
|
+
"target": { "const": "previous" },
|
|
26
|
+
"redact": { "const": "origin" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
19
29
|
"unparseable": { "const": "protected" },
|
|
20
30
|
"unparseableRefusal": {
|
|
21
31
|
"const": "slicc: refused: slicc can't parse this URL, so it could be a protected page"
|
|
@@ -43,6 +53,9 @@
|
|
|
43
53
|
"url": { "type": "string" },
|
|
44
54
|
"type": { "type": "string" },
|
|
45
55
|
"previous": { "type": "boolean" },
|
|
56
|
+
"documentURL": { "type": "string" },
|
|
57
|
+
"unreachableURL": { "type": "string" },
|
|
58
|
+
"navigating": { "type": "boolean" },
|
|
46
59
|
"opener": { "enum": ["protected", "unprotected", "unknown"] },
|
|
47
60
|
"protected": { "type": "boolean" }
|
|
48
61
|
}
|