@ai-ecoverse/slicc-browsers 1.16.2 → 1.17.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 CHANGED
@@ -189,6 +189,8 @@ Every CDP guard runs the vectors in `guard-vectors.json` (schema `schema/guard-v
189
189
 
190
190
  `X1.after-exit` (family `X`, client lifecycle) needs `clientExit`. Through the guard, the client opens an ordinary tab, registers `Page.addScriptToEvaluateOnNewDocument` with `{beacon}`, `Network.setExtraHTTPHeaders` with `x-probe: {tag}` and `Fetch.enable` `*` with a rule that fulfils the destination with a forged `FULFILLED` page, then loads the `/page?k=href&tag={tag}&to={target}/canary/{tag}` trigger whose `go()` navigates to `{target}/canary/{tag}`. Before the exit it must see its init-script beacon, its header on the trigger request and the trigger's `Fetch.requestPaused`, matched up to `&to=`, so a guard that redacts the embedded protected URL to its origin still matches: those are the positive evidence on both legs. Then the client exits, the unguarded observer calls `go()`, and the destination must be served and read as the real `canary` document, with no beacon from `{target}`, no `x-probe` on its request, no forged body and no `Fetch.requestPaused` for it reaching the client: nothing the client registered may outlive it. The control runs the same on `{plain}` through the guard, and expects the same, since an ordinary navigation after the exit must be just as clean. A guard that keeps its upstream session, or its own init scripts, headers or interception, past the client's exit fails the row.
191
191
 
192
+ `C4.iframe-preattach.blank` and `C4.iframe-preattach.srcdoc` read a protected frame's same-process child that committed before the client attached. The unguarded observer opens `<from>/framed` around `{target}/nest?n=<tag>&kind=blank` or `kind=srcdoc`: the protected frame holds an `about:blank` child that it fills, or an `about:srcdoc` child, each named `frame-marker-<tag>` and showing that marker, and then moves itself with `history.replaceState` to `/nest/path-secret-<tag>?query-secret=<tag>#fragment-secret-<tag>`. `from` is `{ordinary-same-site}` on the guarded leg, so both frames share the page's process under site isolation, and `{plain}` on the control. The `.shared` rows open the page from `{ordinary}` on both legs and require `siteIsolationOff`; a transport without it reports them as `skip` with `not-supported:siteIsolationOff`. The observer must find the child in the page's own frame tree and pierced document, and must see the marker or the secret through every read. Then the client attaches to the settled page and makes the same reads: `Page.getFrameTree`, `Accessibility.getFullAXTree` with the protected frame's and the child's `frameId`, `Accessibility.getPartialAXTree` of the marker element's `backendNodeId` with `fetchRelatives`, `DOM.getDocument` with `depth: -1` and `pierce: true`, `DOM.describeNode` of the child's document and `DOM.getOuterHTML` of the marker element, and `DOMSnapshot.captureSnapshot`. Each `<read>Shown` holds when a reply carries the marker or the secret path, query or fragment, and `eventsShown` when an event on the client's session does. The guarded leg expects all of them false: a refusal, an error or origin-only data passes, and a refused attach refuses every read. The control runs unguarded and expects every read to show the content. Missing evidence is `incomplete`, with `frame-read: <reason>`: no same-process child within 5000 ms (scaled by `settleScale`), an observer read that doesn't show the content (that field reads `incomplete`), a client read or attach that was never answered (an error that says `timed out` or `closed`), or a control attach without a session.
193
+
192
194
  The `C9` foreign extension isolated-world family requires `extensionWorld`: a browser hosting a foreign content-script extension on the ordinary fixture page. `C9.world-hidden` requires the page default context to remain visible after `Runtime.enable`, while the extension context and Runtime/Debugger identifiers remain hidden without cutting or detaching the session. `C9.world-select` probes numeric and unique context selectors, object and argument handles, promises, properties, named bindings and DOM node resolution; each command selecting the hidden world must fail with the `slicc: refused:` prefix, never an ordinary browser error. Numeric guesses also sweep ids 1–32: an id not exposed to this client must be refused, while every exposed numeric context must remain usable; none of those guarded probes may reach the fixture content-script marker. The marker combines the content script’s global lexical `root` binding with its extension-id DOM marker and `chrome.runtime.id`, and is independently required to be absent from the page default world. `C9.world-debugger` pauses inside that content script: the control must reach its frame, while the guard must refuse or hide it. The control waits up to 5000 ms (scaled by `settleScale`) for `Debugger.paused`; when it never pauses, `reachable` records `"incomplete"`, not `false`, and the row fails with `extensionWorld debugger: no Debugger.paused within <ms>ms`. The guarded leg waits up to 1500 ms, scaled by `refusalScale`. `C9.page-drivable` evaluates `self.origin`, captures a DOM snapshot and dispatches an Input click on the same ordinary page; cutting the entire session fails this regression row. All four use an unguarded control, the default browser context and observer discovery of the real extension world, not an invented isolated world. Protected default contexts remain session-cutting evidence; hiding a foreign isolated world does not weaken any seven-origin row.
193
195
 
194
196
  The integration launcher loads shared-web 1.25.2’s harness extension exactly once through `Extensions.loadUnpacked` (`ankmbodmgimjemnojmeddfcdlbocolob`), whose content script runs on HTTP(S) pages. shared-web 1.25.2 also loads it by default in its own harness. A transport unable to host a foreign content-script extension omits `extensionWorld` and reports `not-supported:extensionWorld`; an advertised capability with no observed world fails, rather than silently skipping.
@@ -218,7 +220,7 @@ A client may register `Fetch.enable` patterns naming a protected origin in eithe
218
220
  - `setVersion`: the package version. The release flow stamps it into the npm package and the crate (`scripts/set-crate-version.mjs`). In git, and so in the Go module, it reads `0.0.0`; Go code gets the version from the module.
219
221
  - `targets`: the names a runner maps to a scheme, host and port: `seven`, `vault`, `vault-x` (a vault branch preview), `apex`, `own` (the client page's origin, `{client}`), `ordinary`, `ordinary-same-site`, `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-search`, `network-error`, `chrome-extension-page` and `file-root`. Each has a `description` and, for the protected ones, a default `host`. `chrome-newtab` (`chrome://newtab/`), `chrome-new-tab-page` (`chrome://new-tab-page/`), `chrome-settings` (`chrome://settings/`), `chrome-passwords` (`chrome://password-manager/`), `chrome-search` (`chrome-search://most-visited/`), `network-error` (`http://nonexistent.invalid/`), `chrome-extension-page` (a page of Chrome's built-in PDF viewer extension) and `file-root` (`file:///`) carry a `url`, which the runner uses unless `targets` maps the name to something else. No other URL in the file is one a runner must use.
220
222
  - `variants`: the detach variants in ms. `prompt` and `lagging` are documented; a runner may add others, such as `lagging-8s`, and reports the name it ran.
221
- - `capabilities`: what a transport may lack. `autoAttach` covers `Target.setAutoAttach`, `children` steps and child sessions; `browserSession` covers calls on the browser session; `browserContexts` covers `Target.createBrowserContext` and `Target.disposeBrowserContext`; `extensionWorld` means it can host a foreign content-script extension isolated world on the ordinary fixture; `discovery` means session-less `Target.setDiscoverTargets` and `Target.targetCreated`/`Target.targetInfoChanged` delivery; `chromeUrlsAttach` means the transport can attach to and drive `chrome://` pages. CDP over a DevTools port can; `chrome.debugger` can't, since Chrome refuses it every `chrome://` page, even with `--extensions-on-chrome-urls`. `clientExit` means the harness can close the guarded client in the middle of a row: the client has `close()`, the `clientFactory` makes a fresh guarded client for the next leg, and an unguarded observer independent of the client outlives it.
223
+ - `capabilities`: what a transport may lack. `autoAttach` covers `Target.setAutoAttach`, `children` steps and child sessions; `browserSession` covers calls on the browser session; `browserContexts` covers `Target.createBrowserContext` and `Target.disposeBrowserContext`; `extensionWorld` means it can host a foreign content-script extension isolated world on the ordinary fixture; `discovery` means session-less `Target.setDiscoverTargets` and `Target.targetCreated`/`Target.targetInfoChanged` delivery; `chromeUrlsAttach` means the transport can attach to and drive `chrome://` pages. CDP over a DevTools port can; `chrome.debugger` can't, since Chrome refuses it every `chrome://` page, even with `--extensions-on-chrome-urls`. `clientExit` means the harness can close the guarded client in the middle of a row: the client has `close()`, the `clientFactory` makes a fresh guarded client for the next leg, and an unguarded observer independent of the client outlives it. `siteIsolationOff` means the browser runs without site isolation, so a cross-site iframe shares its parent's process.
222
224
  - `placeholders`: every `{name}` used in the vectors, with what it stands for. `{client}` is the client page's origin, which the runner maps.
223
225
 
224
226
  **A vector** has:
@@ -264,6 +266,7 @@ Popups are observed by `Target.targetCreated` with the opener's `openerId`, or b
264
266
  - `/data?n=`: `real-<n>`, with CORS;
265
267
  - `/script?n=`: a script setting `globalThis.vectorScript` to `real-<n>`;
266
268
  - `/page?k=&to=`: a trigger page whose `go()` sends the page to `to` (`k=href`, `replace`, `popup` or `form`), and `/canary/<tag>`: a document titled `canary` with the marker;
269
+ - `/nest?n=&kind=` (and any path under `/nest`): the `C4.iframe-preattach` frame, with its `about:blank` or `about:srcdoc` child;
267
270
  - `/img`: a 1×1 PNG;
268
271
  - `/auth`: Basic `u`:`p`, then `authed`;
269
272
  - `/rewritten` (title `rewritten`);
@@ -280,6 +283,8 @@ Popups are observed by `Target.targetCreated` with the opener's `openerId`, or b
280
283
 
281
284
  For remote/page harnesses, `fixtureClient(base)` is exported by `./guard-runner` and `./fixture-client`, or `run(set, { fixtureBase: base, ... })` constructs it. The bundled server serves CORS-safe `GET /log?since=N` as `{ from: N, entries: [...] }`, with an append-only history unaffected by `fixture.reset()`, and `POST /redirect` with a JSON-encoded string body (a simple text/plain request, without a preflight). Control requests do not appear in the log. Registration descriptors are `{ path, host?, search?, to?, code?, body, remove? }`: `path` starts with `/`, `host` selects the callback hostname, `search` distinguishes query-bearing callbacks, `to` creates a redirect with the four Location/Content-Location/Refresh/Link headers, `code` defaults to 302, an omitted `to` serves the callback body, and `remove: true` unregisters it. The bundled endpoint returns 204; custom servers must acknowledge with a successful status and actually serve the registered routes. Custom fixtures may instead supply an async `fixture.redirectSecret` hook directly; a separate log/control server can implement the same `GET /log` and `POST /redirect` contract. A Node harness using browser-only DNS aliases should pass a reachable `fixtureBase` or its direct hook.
282
285
 
286
+ `{ "frameRead": "blank" | "srcdoc", "from", "as": "frames", "on"?, "requires"? }` runs the `C4.iframe-preattach` compound probe: the observer's page and reads, then the client's attach and reads, with boolean evidence for ordinary `result` checks.
287
+
283
288
  `{ "unparseable": true, "as": "unparseable" }` runs the C8 malformed-URL compound probe on the current ordinary page: independent browser ground truth, raw/canonical destination commands, discovery/attach and cookie checks. Boolean evidence is consumed by ordinary `result` checks, with native Chrome outcomes retained in its diagnostics.
284
289
 
285
290
  **The control rule.** Every vector has a control on an ordinary origin: the same setup and trigger against an unprotected origin must succeed. A missing or failing control fails the id. With `via: "guard"`, the control runs through the guard. With `via: "unguarded"`, the runner runs setup and trigger through its own CDP against the ordinary origin and against the protected stand-in, and both must show the effect. The runner reports the control's transport as `via`.
@@ -404,13 +409,13 @@ every alternative of an `any`, or a required (`all`, `not` or plain) check, need
404
409
  selects one variant or an array; omission selects all. `parallel` defaults to 1.
405
410
  With the `browserContexts` capability each leg runs in its own browser context. A `Target.createBrowserContext` that fails with a timeout is retried once, and the row's `notes` records it; any other failure fails the row.
406
411
 
407
- Every target created by the runner’s own unguarded observer uses `Target.createTarget` with `newWindow: true`, then verifies through `Target.getTargetInfo` that `openerId` is absent. A supplied opener or missing target evidence fails and cleans up that target (in the protected-error-page probe, the row is `incomplete` instead), rather than inheriting the active client tab (which could be seven). This includes ordinary control setup, storage canaries, protected-error history seeds and the malformed-URL observer. The row notes record `newWindow=true, openerId absent`. Intentional guarded-client creation commands and popup/opener tests keep their original parameters.
412
+ Every target created by the runner’s own unguarded observer uses `Target.createTarget` with `newWindow: true`, then verifies through `Target.getTargetInfo` that `openerId` is absent. A supplied opener or missing target evidence fails and cleans up that target (in the protected-error-page probe, the row is `incomplete` instead), rather than inheriting the active client tab (which could be seven). This includes ordinary control setup, storage canaries, protected-error history seeds, the frame-read page and the malformed-URL observer. The row notes record `newWindow=true, openerId absent`. Intentional guarded-client creation commands and popup/opener tests keep their original parameters.
408
413
 
409
414
  `settleScale` (default 1) multiplies the bounds of waits for events the leg expects to happen: settles (beacon, served, title and load) on the control leg and `required` settles on either leg, `paused` on the control leg, page and child loads, the wait for a background step's `as` result (`<as> did not complete within <ms>ms`), the wait for the pre-action beacons, the C9 control's `Debugger.paused`, and the wait for an observation the leg expects to be `true`. Adopters on slow hosts can set 3 or 4. Every wait polls and returns as soon as its event arrives, so a larger bound only costs time when something is really missing, and each timeout error names the bound it used. An observation read from the fixture (`observer: "fixture"`, such as a beacon or a served request) that the leg expects to be `true` is polled for up to 5000 ms (scaled) instead of read once. One that the leg expects to be `false` is still read once.
410
415
 
411
416
  `refusalScale` (default 1) multiplies the bounds of waits for what the guarded leg expects to be absent, the beacon, request, title or navigation that the guard should block: settles that are not `required`, and `paused`, on the guarded leg. Such a wait runs to its full bound whenever the guard works, so `settleScale` leaves it alone. The trade-off: a larger `refusalScale` gives more sensitivity to a leak that arrives late on a slow host, because the observations after the wait see it, at the cost of that much more time per guarded row. Adopters should keep `refusalScale` at 1 or higher, never below 1; the runner refuses a `refusalScale` below 1 with `refusalScale must be a number of at least 1`.
412
417
 
413
- **Incomplete.** A probe that returns `incomplete` (only `errorPage` and `redirectSecret` steps) records the reason in the row's `incomplete` as `<leg> <probe>: <reason>`. Its unmeasured observations read `"incomplete"`, and so does guarded liveness when the probe never measured it. A leg whose measured observations all match is then `incomplete`, never `pass`, and `live` is `incomplete` when only its evidence is missing; a measured mismatch still makes the leg `fail`, and any row error still fails both legs. `summary()` counts `incomplete` per leg, returns the number of rows with any `incomplete` status as `incomplete`, appends `incomplete <n>` to `line`, and never passes such a run. A leg that throws keeps the errors it recorded before the throw.
418
+ **Incomplete.** A probe that returns `incomplete` (only `errorPage`, `redirectSecret` and `frameRead` steps) records the reason in the row's `incomplete` as `<leg> <probe>: <reason>`. Its unmeasured observations read `"incomplete"`, and so does guarded liveness when the probe never measured it. A leg whose measured observations all match is then `incomplete`, never `pass`, and `live` is `incomplete` when only its evidence is missing; a measured mismatch still makes the leg `fail`, and any row error still fails both legs. `summary()` counts `incomplete` per leg, returns the number of rows with any `incomplete` status as `incomplete`, appends `incomplete <n>` to `line`, and never passes such a run. A leg that throws keeps the errors it recorded before the throw.
414
419
 
415
420
  `onStart(row)` receives each row's `id`, `target`, `variant`, `transport` and `via` as the row starts. `run()` returns a promise that also has `inFlight()`, and its result has `inFlight` too. `inFlight()` lists the rows that are running now, each as `{ id, target, variant, leg, phase, step, clients }`, where `step` is the index of the current step in its `phase` (`setup`, `trigger` or `child`), and `clients` lists each client's pending CDP calls as `{ method, session, ageMs }` (`client`, plus `unguarded` when it is a separate client). With `rowTimeoutMs`, a row still running after that many milliseconds fails on its own. Its `errors` hold `row timed out after <ms>ms: <the in-flight view>`, `inFlight` holds the view, and the clients that `clientFactory` made for it are closed. The run then goes on to the next row. It is off by default, and a client passed as `client` is never closed.
416
421
 
@@ -517,7 +522,7 @@ Without `tls`, the server and logged URLs remain HTTP. It returns
517
522
  The server serves document, frame, redirect, CSP, authentication, data, image,
518
523
  canary and beacon paths. Configure host resolution in your test browser, not in
519
524
  the runner. `npm run test:guard` exercises Chromium twice with no guard, one target per
520
- invocation: controls must pass and guarded protection expectations must fail; the `C8.newtab` reachability control and `X1.after-exit`, whose plain client leaves nothing behind when it exits, must instead pass both legs. For a single
525
+ invocation: controls must pass and guarded protection expectations must fail; the `C8.newtab` reachability control and `X1.after-exit`, whose plain client leaves nothing behind when it exits, must instead pass both legs. Rows that require `siteIsolationOff` run in one more Chromium launched without site isolation. For a single
521
526
  target use `GUARD_TARGET=seven npm run test:guard-target`. The fixture also serves
522
527
  `/live` image requests from stand-in documents, independently of injected code.
523
528
 
@@ -29,6 +29,17 @@ function trigger(p) {
29
29
  return `<p>trigger</p><button onclick="go()">Go</button>${frame}<script>const to=${JSON.stringify(to)};function go(){const k=${JSON.stringify(kind)};if(k==='href'||k==='deferred')location.href=to;if(k==='replace')location.replace(to);if(k==='popup')window.open(to);if(k==='form'){const u=new URL(to);const f=document.createElement('form');f.action=u.origin+u.pathname;for(const [name,value] of u.searchParams){const i=document.createElement('input');i.type='hidden';i.name=name;i.value=value;f.append(i);}document.body.append(f);f.submit();}return 'went';}${kind === 'deferred' ? `setTimeout(go,${Number(p.get('defer') ?? 3000)});` : ''}</script>`;
30
30
  }
31
31
 
32
+ function nest(p) {
33
+ const n = p.get('n') ?? '';
34
+ const marker = escape(`frame-marker-${n}`);
35
+ const child =
36
+ p.get('kind') === 'srcdoc'
37
+ ? `<iframe name="${marker}" srcdoc="${escape(`<p>${marker}</p>`)}"></iframe>`
38
+ : `<iframe name="${marker}"></iframe><script>const f=document.querySelector('iframe');const p=f.contentDocument.createElement('p');p.textContent=f.name;f.contentDocument.body.append(p)</script>`;
39
+ const tag = JSON.stringify(n).replaceAll('<', '\\u003c');
40
+ return `<p>${marker}</p>${child}<script>const n=${tag};history.replaceState(null,'',location.pathname+'/path-'+'secret-'+n+'?query-'+'secret='+n+'#fragment-'+'secret-'+n)</script>`;
41
+ }
42
+
32
43
  function route(req, url, send, html, redirects) {
33
44
  const secret =
34
45
  redirects.get(`${url.hostname}${url.pathname}${url.search ?? ''}`) ??
@@ -95,6 +106,7 @@ function route(req, url, send, html, redirects) {
95
106
  'canary',
96
107
  `<p data-canary="${escape(p.get('value') ?? 'planted')}">canary-marker ${escape(url.host)}</p>`
97
108
  );
109
+ if (url.pathname.startsWith('/nest')) return html('nest', nest(p));
98
110
  const handler = routes[url.pathname];
99
111
  if (handler) return handler();
100
112
  return send(404, 'missing', 'text/plain');
package/frame-read.mjs ADDED
@@ -0,0 +1,182 @@
1
+ import { outcome, parseURL, waitFor } from './guard-driver.mjs';
2
+ import { createObserverTarget } from './observer-target.mjs';
3
+
4
+ export const frameReads = ['frameTree', 'axFull', 'axPartial', 'document', 'node', 'snapshot'];
5
+
6
+ const unanswered = (out) => !out.ok && /timed out|closed/i.test(out.error?.message ?? '');
7
+
8
+ export function frameReadCalls(layout) {
9
+ return {
10
+ frameTree: [['Page.getFrameTree', {}]],
11
+ axFull: [
12
+ ['Accessibility.getFullAXTree', { frameId: layout.frame }],
13
+ ['Accessibility.getFullAXTree', { frameId: layout.child }],
14
+ ],
15
+ axPartial: [
16
+ ['Accessibility.getPartialAXTree', { backendNodeId: layout.marker, fetchRelatives: true }],
17
+ ],
18
+ document: [['DOM.getDocument', { depth: -1, pierce: true }]],
19
+ node: [
20
+ ['DOM.describeNode', { backendNodeId: layout.childDocument, depth: -1, pierce: true }],
21
+ ['DOM.getOuterHTML', { backendNodeId: layout.marker }],
22
+ ],
23
+ snapshot: [['DOMSnapshot.captureSnapshot', { computedStyles: [] }]],
24
+ };
25
+ }
26
+
27
+ export function frameNeedles(tag) {
28
+ return [
29
+ `frame-marker-${tag}`,
30
+ `path-secret-${tag}`,
31
+ `query-secret=${tag}`,
32
+ `fragment-secret-${tag}`,
33
+ ];
34
+ }
35
+
36
+ const shows = (value, needles) => {
37
+ const text = JSON.stringify(value) ?? '';
38
+ return needles.some((needle) => text.includes(needle));
39
+ };
40
+
41
+ function find(node, test) {
42
+ if (!node) return undefined;
43
+ if (test(node)) return node;
44
+ for (const next of [
45
+ ...(node.children ?? []),
46
+ node.contentDocument,
47
+ ...(node.shadowRoots ?? []),
48
+ ]) {
49
+ const hit = find(next, test);
50
+ if (hit) return hit;
51
+ }
52
+ return undefined;
53
+ }
54
+
55
+ export async function frameLayout(reader, session, { framed, marker, kind }) {
56
+ const { frameTree } = await reader.send('Page.getFrameTree', {}, session);
57
+ const host = (frameTree.childFrames ?? []).find((node) => {
58
+ const url = parseURL(node.frame.url);
59
+ return url?.origin === parseURL(framed)?.origin && url.pathname.startsWith('/nest');
60
+ });
61
+ const child = (host?.childFrames ?? []).find((node) => node.frame.name === marker);
62
+ if (!child || child.frame.url !== kind) return undefined;
63
+ const { root } = await reader.send('DOM.getDocument', { depth: -1, pierce: true }, session);
64
+ const owner = find(
65
+ root,
66
+ (node) =>
67
+ node.nodeName === 'IFRAME' &&
68
+ node.frameId === child.frame.id &&
69
+ (node.attributes ?? []).includes(marker)
70
+ );
71
+ const text = find(
72
+ owner?.contentDocument,
73
+ (node) => node.nodeType === 3 && node.nodeValue === marker
74
+ );
75
+ const paragraph = find(owner?.contentDocument, (node) => (node.children ?? []).includes(text));
76
+ if (!paragraph) return undefined;
77
+ return {
78
+ frame: host.frame.id,
79
+ child: child.frame.id,
80
+ childDocument: owner.contentDocument.backendNodeId,
81
+ marker: paragraph.backendNodeId,
82
+ };
83
+ }
84
+
85
+ async function read(client, session, calls, needles) {
86
+ let shown = false;
87
+ let silent = false;
88
+ for (const [method, params] of calls) {
89
+ const out = await outcome(client.send(method, params, session));
90
+ shown ||= shows(out, needles);
91
+ silent ||= unanswered(out);
92
+ }
93
+ return shown || (silent ? 'incomplete' : false);
94
+ }
95
+
96
+ async function enable(client, session) {
97
+ for (const method of ['Page.enable', 'DOM.enable', 'Accessibility.enable'])
98
+ await outcome(client.send(method, {}, session));
99
+ }
100
+
101
+ export async function frameRead({
102
+ client,
103
+ reader,
104
+ top,
105
+ framed,
106
+ tag,
107
+ mode,
108
+ side,
109
+ notes = [],
110
+ budget = 5000,
111
+ }) {
112
+ const kind = mode === 'srcdoc' ? 'about:srcdoc' : 'about:blank';
113
+ const marker = `frame-marker-${tag}`;
114
+ const needles = frameNeedles(tag);
115
+ const nest = `${framed}/nest?n=${encodeURIComponent(tag)}&kind=${mode}`;
116
+ const page = `${top}/framed?tag=${encodeURIComponent(tag)}&url=${encodeURIComponent(nest)}`;
117
+ const result = {};
118
+ const missing = [];
119
+ const done = () =>
120
+ missing.length ? { ...result, incomplete: `frame-read: ${missing.join('; ')}` } : result;
121
+ const opened = await outcome(
122
+ createObserverTarget(reader, { url: 'about:blank', background: true }, notes)
123
+ );
124
+ if (!opened.ok)
125
+ return { incomplete: `frame-read: observer setup failed: ${opened.error.message}` };
126
+ const { targetId } = opened.result;
127
+ let session;
128
+ try {
129
+ const observer = (await reader.send('Target.attachToTarget', { targetId, flatten: true }))
130
+ .sessionId;
131
+ await enable(reader, observer);
132
+ await reader.send('Page.navigate', { url: page }, observer);
133
+ let layout;
134
+ await waitFor(async () => {
135
+ const out = await outcome(frameLayout(reader, observer, { framed, marker, kind }));
136
+ layout = out.result;
137
+ return Boolean(layout);
138
+ }, budget);
139
+ if (!layout) {
140
+ await outcome(reader.send('Target.detachFromTarget', { sessionId: observer }));
141
+ return {
142
+ incomplete: `frame-read: no ${kind} child of ${framed} in the page's renderer within ${budget}ms`,
143
+ };
144
+ }
145
+ result.live = true;
146
+ const calls = frameReadCalls(layout);
147
+ const proven = {};
148
+ for (const name of frameReads) {
149
+ proven[name] = (await read(reader, observer, calls[name], needles)) === true;
150
+ if (!proven[name]) missing.push(`observer ${name} did not show the frame's content`);
151
+ }
152
+ await outcome(reader.send('Target.detachFromTarget', { sessionId: observer }));
153
+ const start = client.events.length;
154
+ const attached = await outcome(
155
+ client.send('Target.attachToTarget', { targetId, flatten: true })
156
+ );
157
+ session = attached.result?.sessionId;
158
+ result.attached = Boolean(session);
159
+ if (!session && (unanswered(attached) || side === 'control')) {
160
+ missing.push(`attach ${attached.error?.message ?? 'returned no session'}`);
161
+ return done();
162
+ }
163
+ if (session) await enable(client, session);
164
+ for (const name of frameReads) {
165
+ const value = session ? await read(client, session, calls[name], needles) : false;
166
+ if (value === 'incomplete') missing.push(`${name} unanswered`);
167
+ result[`${name}Shown`] = value === true || proven[name] ? value : 'incomplete';
168
+ }
169
+ result.eventsShown = shows(
170
+ client.events
171
+ .slice(start)
172
+ .filter(
173
+ (event) => session && (event.sessionId === session || event.params?.sessionId === session)
174
+ ),
175
+ needles
176
+ );
177
+ return done();
178
+ } finally {
179
+ if (session) await outcome(client.send('Target.detachFromTarget', { sessionId: session }));
180
+ await outcome(reader.send('Target.closeTarget', { targetId }));
181
+ }
182
+ }
package/guard-runner.mjs CHANGED
@@ -3,6 +3,7 @@ import { fixtureClient, prepareFixture } from './fixture-client.mjs';
3
3
  export { fixtureClient };
4
4
 
5
5
  import { extensionWorld } from './extension-world.mjs';
6
+ import { frameRead } from './frame-read.mjs';
6
7
  import {
7
8
  beacon,
8
9
  canary,
@@ -669,12 +670,20 @@ class Execution {
669
670
  );
670
671
  if (step.extensionWorld && out.result?.incomplete)
671
672
  this.errors.push(`extensionWorld ${step.extensionWorld}: ${out.result.incomplete}`);
672
- const probe = ['redirectSecret', 'unparseable', 'errorPage', 'extensionWorld'].find(
673
- (key) => step[key]
674
- );
673
+ const probe = [
674
+ 'redirectSecret',
675
+ 'unparseable',
676
+ 'errorPage',
677
+ 'extensionWorld',
678
+ 'frameRead',
679
+ ].find((key) => step[key]);
675
680
  if (probe && !out.ok) this.errors.push(`${probe}: ${out.error?.message ?? 'probe failed'}`);
676
681
  if (step.client && !out.ok) this.errors.push(`client exit: ${out.error.message}`);
677
- if (['errorPage', 'redirectSecret'].includes(probe) && out.ok && out.result?.incomplete)
682
+ if (
683
+ ['errorPage', 'redirectSecret', 'frameRead'].includes(probe) &&
684
+ out.ok &&
685
+ out.result?.incomplete
686
+ )
678
687
  this.incomplete.push(`${probe}: ${out.result.incomplete}`);
679
688
  if (step.as && !out.result?.pending) this.calls[step.as] = out;
680
689
  if (step.cdp) (this.calls.$cdp ??= []).push({ method: step.cdp, ...out });
@@ -767,6 +776,20 @@ class Execution {
767
776
  budget: this.bound(5000),
768
777
  })
769
778
  );
779
+ if (step.frameRead)
780
+ return outcome(
781
+ frameRead({
782
+ client: this.client,
783
+ reader: this.reader,
784
+ top: step.from,
785
+ framed: this.vars.target,
786
+ tag: this.vars.tag,
787
+ mode: step.frameRead,
788
+ side: this.side,
789
+ notes: this.notes,
790
+ budget: this.bound(5000),
791
+ })
792
+ );
770
793
  if (step.unparseable)
771
794
  return outcome(
772
795
  unparseableURLs({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "setVersion": "1.16.2",
3
+ "setVersion": "1.17.0",
4
4
  "sources": [
5
5
  {
6
6
  "repo": "ai-ecoverse/slicc-cli",
@@ -98,7 +98,8 @@
98
98
  "extensionWorld": "a browser hosting a foreign content-script extension isolated world on the ordinary fixture page",
99
99
  "discovery": "session-less Target.setDiscoverTargets and targetCreated/targetInfoChanged delivery; absent transports must emit neither event",
100
100
  "chromeUrlsAttach": "attaching to and driving chrome:// pages, which CDP over a DevTools port can and chrome.debugger can't",
101
- "clientExit": "closing the guarded client mid-row: the client has close(), the clientFactory makes a fresh guarded client for the next leg, and an independent unguarded observer outlives it"
101
+ "clientExit": "closing the guarded client mid-row: the client has close(), the clientFactory makes a fresh guarded client for the next leg, and an independent unguarded observer outlives it",
102
+ "siteIsolationOff": "the browser runs without site isolation, so a cross-site iframe shares its parent's process"
102
103
  },
103
104
  "placeholders": {
104
105
  "target": "the target's origin; {plain} on the control",
@@ -13042,6 +13043,524 @@
13042
13043
  "observer": "client"
13043
13044
  }
13044
13045
  },
13046
+ {
13047
+ "id": "C4.iframe-preattach.blank",
13048
+ "family": "C",
13049
+ "kind": "cdp",
13050
+ "scope": "any",
13051
+ "defaultContext": true,
13052
+ "setup": [
13053
+ {
13054
+ "page": "tab",
13055
+ "url": "{ordinary}/blank"
13056
+ }
13057
+ ],
13058
+ "trigger": [
13059
+ {
13060
+ "frameRead": "blank",
13061
+ "from": "{ordinary-same-site}",
13062
+ "on": "guarded",
13063
+ "as": "frames"
13064
+ },
13065
+ {
13066
+ "frameRead": "blank",
13067
+ "from": "{plain}",
13068
+ "on": "control",
13069
+ "as": "frames"
13070
+ }
13071
+ ],
13072
+ "targets": ["seven"],
13073
+ "observe": {
13074
+ "live": {
13075
+ "means": "the observer saw the protected frame's about:blank child and its marker in the page's process",
13076
+ "check": {
13077
+ "result": "frames",
13078
+ "path": "result.live",
13079
+ "equals": true,
13080
+ "observer": "client"
13081
+ }
13082
+ },
13083
+ "frameTreeShown": {
13084
+ "means": "the client's Page.getFrameTree showed the child's marker or the protected path, query or fragment",
13085
+ "check": {
13086
+ "result": "frames",
13087
+ "path": "result.frameTreeShown",
13088
+ "equals": true,
13089
+ "observer": "client"
13090
+ }
13091
+ },
13092
+ "axFullShown": {
13093
+ "means": "the client's frame-scoped Accessibility.getFullAXTree showed the marker",
13094
+ "check": {
13095
+ "result": "frames",
13096
+ "path": "result.axFullShown",
13097
+ "equals": true,
13098
+ "observer": "client"
13099
+ }
13100
+ },
13101
+ "axPartialShown": {
13102
+ "means": "the client's Accessibility.getPartialAXTree of the marker node showed the marker",
13103
+ "check": {
13104
+ "result": "frames",
13105
+ "path": "result.axPartialShown",
13106
+ "equals": true,
13107
+ "observer": "client"
13108
+ }
13109
+ },
13110
+ "documentShown": {
13111
+ "means": "the client's pierced DOM.getDocument showed the marker or the protected path, query or fragment",
13112
+ "check": {
13113
+ "result": "frames",
13114
+ "path": "result.documentShown",
13115
+ "equals": true,
13116
+ "observer": "client"
13117
+ }
13118
+ },
13119
+ "nodeShown": {
13120
+ "means": "the client's DOM.describeNode and DOM.getOuterHTML of the child's nodes showed the marker",
13121
+ "check": {
13122
+ "result": "frames",
13123
+ "path": "result.nodeShown",
13124
+ "equals": true,
13125
+ "observer": "client"
13126
+ }
13127
+ },
13128
+ "snapshotShown": {
13129
+ "means": "the client's DOMSnapshot.captureSnapshot showed the marker or the protected path, query or fragment",
13130
+ "check": {
13131
+ "result": "frames",
13132
+ "path": "result.snapshotShown",
13133
+ "equals": true,
13134
+ "observer": "client"
13135
+ }
13136
+ },
13137
+ "eventsShown": {
13138
+ "means": "an event on the client's session showed the marker or the protected path, query or fragment",
13139
+ "check": {
13140
+ "result": "frames",
13141
+ "path": "result.eventsShown",
13142
+ "equals": true,
13143
+ "observer": "client"
13144
+ }
13145
+ }
13146
+ },
13147
+ "live": "the independent observer, before the client attaches, sees {target}'s about:blank child frame and its marker in the page's own process through every read",
13148
+ "liveCheck": {
13149
+ "result": "frames",
13150
+ "path": "result.live",
13151
+ "equals": true,
13152
+ "observer": "client"
13153
+ },
13154
+ "control": {
13155
+ "origin": "ordinary",
13156
+ "via": "unguarded",
13157
+ "expected": {
13158
+ "live": true,
13159
+ "frameTreeShown": true,
13160
+ "axFullShown": true,
13161
+ "axPartialShown": true,
13162
+ "documentShown": true,
13163
+ "nodeShown": true,
13164
+ "snapshotShown": true
13165
+ }
13166
+ },
13167
+ "expected": {
13168
+ "live": true,
13169
+ "frameTreeShown": false,
13170
+ "axFullShown": false,
13171
+ "axPartialShown": false,
13172
+ "documentShown": false,
13173
+ "nodeShown": false,
13174
+ "snapshotShown": false,
13175
+ "eventsShown": false
13176
+ }
13177
+ },
13178
+ {
13179
+ "id": "C4.iframe-preattach.srcdoc",
13180
+ "family": "C",
13181
+ "kind": "cdp",
13182
+ "scope": "any",
13183
+ "defaultContext": true,
13184
+ "setup": [
13185
+ {
13186
+ "page": "tab",
13187
+ "url": "{ordinary}/blank"
13188
+ }
13189
+ ],
13190
+ "trigger": [
13191
+ {
13192
+ "frameRead": "srcdoc",
13193
+ "from": "{ordinary-same-site}",
13194
+ "on": "guarded",
13195
+ "as": "frames"
13196
+ },
13197
+ {
13198
+ "frameRead": "srcdoc",
13199
+ "from": "{plain}",
13200
+ "on": "control",
13201
+ "as": "frames"
13202
+ }
13203
+ ],
13204
+ "targets": ["seven"],
13205
+ "observe": {
13206
+ "live": {
13207
+ "means": "the observer saw the protected frame's about:srcdoc child and its marker in the page's process",
13208
+ "check": {
13209
+ "result": "frames",
13210
+ "path": "result.live",
13211
+ "equals": true,
13212
+ "observer": "client"
13213
+ }
13214
+ },
13215
+ "frameTreeShown": {
13216
+ "means": "the client's Page.getFrameTree showed the child's marker or the protected path, query or fragment",
13217
+ "check": {
13218
+ "result": "frames",
13219
+ "path": "result.frameTreeShown",
13220
+ "equals": true,
13221
+ "observer": "client"
13222
+ }
13223
+ },
13224
+ "axFullShown": {
13225
+ "means": "the client's frame-scoped Accessibility.getFullAXTree showed the marker",
13226
+ "check": {
13227
+ "result": "frames",
13228
+ "path": "result.axFullShown",
13229
+ "equals": true,
13230
+ "observer": "client"
13231
+ }
13232
+ },
13233
+ "axPartialShown": {
13234
+ "means": "the client's Accessibility.getPartialAXTree of the marker node showed the marker",
13235
+ "check": {
13236
+ "result": "frames",
13237
+ "path": "result.axPartialShown",
13238
+ "equals": true,
13239
+ "observer": "client"
13240
+ }
13241
+ },
13242
+ "documentShown": {
13243
+ "means": "the client's pierced DOM.getDocument showed the marker or the protected path, query or fragment",
13244
+ "check": {
13245
+ "result": "frames",
13246
+ "path": "result.documentShown",
13247
+ "equals": true,
13248
+ "observer": "client"
13249
+ }
13250
+ },
13251
+ "nodeShown": {
13252
+ "means": "the client's DOM.describeNode and DOM.getOuterHTML of the child's nodes showed the marker",
13253
+ "check": {
13254
+ "result": "frames",
13255
+ "path": "result.nodeShown",
13256
+ "equals": true,
13257
+ "observer": "client"
13258
+ }
13259
+ },
13260
+ "snapshotShown": {
13261
+ "means": "the client's DOMSnapshot.captureSnapshot showed the marker or the protected path, query or fragment",
13262
+ "check": {
13263
+ "result": "frames",
13264
+ "path": "result.snapshotShown",
13265
+ "equals": true,
13266
+ "observer": "client"
13267
+ }
13268
+ },
13269
+ "eventsShown": {
13270
+ "means": "an event on the client's session showed the marker or the protected path, query or fragment",
13271
+ "check": {
13272
+ "result": "frames",
13273
+ "path": "result.eventsShown",
13274
+ "equals": true,
13275
+ "observer": "client"
13276
+ }
13277
+ }
13278
+ },
13279
+ "live": "the independent observer, before the client attaches, sees {target}'s about:srcdoc child frame and its marker in the page's own process through every read",
13280
+ "liveCheck": {
13281
+ "result": "frames",
13282
+ "path": "result.live",
13283
+ "equals": true,
13284
+ "observer": "client"
13285
+ },
13286
+ "control": {
13287
+ "origin": "ordinary",
13288
+ "via": "unguarded",
13289
+ "expected": {
13290
+ "live": true,
13291
+ "frameTreeShown": true,
13292
+ "axFullShown": true,
13293
+ "axPartialShown": true,
13294
+ "documentShown": true,
13295
+ "nodeShown": true,
13296
+ "snapshotShown": true
13297
+ }
13298
+ },
13299
+ "expected": {
13300
+ "live": true,
13301
+ "frameTreeShown": false,
13302
+ "axFullShown": false,
13303
+ "axPartialShown": false,
13304
+ "documentShown": false,
13305
+ "nodeShown": false,
13306
+ "snapshotShown": false,
13307
+ "eventsShown": false
13308
+ }
13309
+ },
13310
+ {
13311
+ "id": "C4.iframe-preattach.blank.shared",
13312
+ "family": "C",
13313
+ "kind": "cdp",
13314
+ "scope": "any",
13315
+ "defaultContext": true,
13316
+ "setup": [
13317
+ {
13318
+ "page": "tab",
13319
+ "url": "{ordinary}/blank"
13320
+ }
13321
+ ],
13322
+ "trigger": [
13323
+ {
13324
+ "frameRead": "blank",
13325
+ "from": "{ordinary}",
13326
+ "as": "frames",
13327
+ "requires": ["siteIsolationOff"]
13328
+ }
13329
+ ],
13330
+ "targets": ["seven", "vault", "vault-x", "apex"],
13331
+ "requires": ["siteIsolationOff"],
13332
+ "observe": {
13333
+ "live": {
13334
+ "means": "the observer saw the protected frame's about:blank child and its marker in the page's process",
13335
+ "check": {
13336
+ "result": "frames",
13337
+ "path": "result.live",
13338
+ "equals": true,
13339
+ "observer": "client"
13340
+ }
13341
+ },
13342
+ "frameTreeShown": {
13343
+ "means": "the client's Page.getFrameTree showed the child's marker or the protected path, query or fragment",
13344
+ "check": {
13345
+ "result": "frames",
13346
+ "path": "result.frameTreeShown",
13347
+ "equals": true,
13348
+ "observer": "client"
13349
+ }
13350
+ },
13351
+ "axFullShown": {
13352
+ "means": "the client's frame-scoped Accessibility.getFullAXTree showed the marker",
13353
+ "check": {
13354
+ "result": "frames",
13355
+ "path": "result.axFullShown",
13356
+ "equals": true,
13357
+ "observer": "client"
13358
+ }
13359
+ },
13360
+ "axPartialShown": {
13361
+ "means": "the client's Accessibility.getPartialAXTree of the marker node showed the marker",
13362
+ "check": {
13363
+ "result": "frames",
13364
+ "path": "result.axPartialShown",
13365
+ "equals": true,
13366
+ "observer": "client"
13367
+ }
13368
+ },
13369
+ "documentShown": {
13370
+ "means": "the client's pierced DOM.getDocument showed the marker or the protected path, query or fragment",
13371
+ "check": {
13372
+ "result": "frames",
13373
+ "path": "result.documentShown",
13374
+ "equals": true,
13375
+ "observer": "client"
13376
+ }
13377
+ },
13378
+ "nodeShown": {
13379
+ "means": "the client's DOM.describeNode and DOM.getOuterHTML of the child's nodes showed the marker",
13380
+ "check": {
13381
+ "result": "frames",
13382
+ "path": "result.nodeShown",
13383
+ "equals": true,
13384
+ "observer": "client"
13385
+ }
13386
+ },
13387
+ "snapshotShown": {
13388
+ "means": "the client's DOMSnapshot.captureSnapshot showed the marker or the protected path, query or fragment",
13389
+ "check": {
13390
+ "result": "frames",
13391
+ "path": "result.snapshotShown",
13392
+ "equals": true,
13393
+ "observer": "client"
13394
+ }
13395
+ },
13396
+ "eventsShown": {
13397
+ "means": "an event on the client's session showed the marker or the protected path, query or fragment",
13398
+ "check": {
13399
+ "result": "frames",
13400
+ "path": "result.eventsShown",
13401
+ "equals": true,
13402
+ "observer": "client"
13403
+ }
13404
+ }
13405
+ },
13406
+ "live": "the independent observer, before the client attaches, sees {target}'s about:blank child frame and its marker in the page's own process through every read",
13407
+ "liveCheck": {
13408
+ "result": "frames",
13409
+ "path": "result.live",
13410
+ "equals": true,
13411
+ "observer": "client"
13412
+ },
13413
+ "control": {
13414
+ "origin": "ordinary",
13415
+ "via": "unguarded",
13416
+ "expected": {
13417
+ "live": true,
13418
+ "frameTreeShown": true,
13419
+ "axFullShown": true,
13420
+ "axPartialShown": true,
13421
+ "documentShown": true,
13422
+ "nodeShown": true,
13423
+ "snapshotShown": true
13424
+ }
13425
+ },
13426
+ "expected": {
13427
+ "live": true,
13428
+ "frameTreeShown": false,
13429
+ "axFullShown": false,
13430
+ "axPartialShown": false,
13431
+ "documentShown": false,
13432
+ "nodeShown": false,
13433
+ "snapshotShown": false,
13434
+ "eventsShown": false
13435
+ }
13436
+ },
13437
+ {
13438
+ "id": "C4.iframe-preattach.srcdoc.shared",
13439
+ "family": "C",
13440
+ "kind": "cdp",
13441
+ "scope": "any",
13442
+ "defaultContext": true,
13443
+ "setup": [
13444
+ {
13445
+ "page": "tab",
13446
+ "url": "{ordinary}/blank"
13447
+ }
13448
+ ],
13449
+ "trigger": [
13450
+ {
13451
+ "frameRead": "srcdoc",
13452
+ "from": "{ordinary}",
13453
+ "as": "frames",
13454
+ "requires": ["siteIsolationOff"]
13455
+ }
13456
+ ],
13457
+ "targets": ["seven", "vault", "vault-x", "apex"],
13458
+ "requires": ["siteIsolationOff"],
13459
+ "observe": {
13460
+ "live": {
13461
+ "means": "the observer saw the protected frame's about:srcdoc child and its marker in the page's process",
13462
+ "check": {
13463
+ "result": "frames",
13464
+ "path": "result.live",
13465
+ "equals": true,
13466
+ "observer": "client"
13467
+ }
13468
+ },
13469
+ "frameTreeShown": {
13470
+ "means": "the client's Page.getFrameTree showed the child's marker or the protected path, query or fragment",
13471
+ "check": {
13472
+ "result": "frames",
13473
+ "path": "result.frameTreeShown",
13474
+ "equals": true,
13475
+ "observer": "client"
13476
+ }
13477
+ },
13478
+ "axFullShown": {
13479
+ "means": "the client's frame-scoped Accessibility.getFullAXTree showed the marker",
13480
+ "check": {
13481
+ "result": "frames",
13482
+ "path": "result.axFullShown",
13483
+ "equals": true,
13484
+ "observer": "client"
13485
+ }
13486
+ },
13487
+ "axPartialShown": {
13488
+ "means": "the client's Accessibility.getPartialAXTree of the marker node showed the marker",
13489
+ "check": {
13490
+ "result": "frames",
13491
+ "path": "result.axPartialShown",
13492
+ "equals": true,
13493
+ "observer": "client"
13494
+ }
13495
+ },
13496
+ "documentShown": {
13497
+ "means": "the client's pierced DOM.getDocument showed the marker or the protected path, query or fragment",
13498
+ "check": {
13499
+ "result": "frames",
13500
+ "path": "result.documentShown",
13501
+ "equals": true,
13502
+ "observer": "client"
13503
+ }
13504
+ },
13505
+ "nodeShown": {
13506
+ "means": "the client's DOM.describeNode and DOM.getOuterHTML of the child's nodes showed the marker",
13507
+ "check": {
13508
+ "result": "frames",
13509
+ "path": "result.nodeShown",
13510
+ "equals": true,
13511
+ "observer": "client"
13512
+ }
13513
+ },
13514
+ "snapshotShown": {
13515
+ "means": "the client's DOMSnapshot.captureSnapshot showed the marker or the protected path, query or fragment",
13516
+ "check": {
13517
+ "result": "frames",
13518
+ "path": "result.snapshotShown",
13519
+ "equals": true,
13520
+ "observer": "client"
13521
+ }
13522
+ },
13523
+ "eventsShown": {
13524
+ "means": "an event on the client's session showed the marker or the protected path, query or fragment",
13525
+ "check": {
13526
+ "result": "frames",
13527
+ "path": "result.eventsShown",
13528
+ "equals": true,
13529
+ "observer": "client"
13530
+ }
13531
+ }
13532
+ },
13533
+ "live": "the independent observer, before the client attaches, sees {target}'s about:srcdoc child frame and its marker in the page's own process through every read",
13534
+ "liveCheck": {
13535
+ "result": "frames",
13536
+ "path": "result.live",
13537
+ "equals": true,
13538
+ "observer": "client"
13539
+ },
13540
+ "control": {
13541
+ "origin": "ordinary",
13542
+ "via": "unguarded",
13543
+ "expected": {
13544
+ "live": true,
13545
+ "frameTreeShown": true,
13546
+ "axFullShown": true,
13547
+ "axPartialShown": true,
13548
+ "documentShown": true,
13549
+ "nodeShown": true,
13550
+ "snapshotShown": true
13551
+ }
13552
+ },
13553
+ "expected": {
13554
+ "live": true,
13555
+ "frameTreeShown": false,
13556
+ "axFullShown": false,
13557
+ "axPartialShown": false,
13558
+ "documentShown": false,
13559
+ "nodeShown": false,
13560
+ "snapshotShown": false,
13561
+ "eventsShown": false
13562
+ }
13563
+ },
13045
13564
  {
13046
13565
  "id": "C5.fulfill",
13047
13566
  "family": "C",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-browsers",
3
- "version": "1.16.2",
3
+ "version": "1.17.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",
@@ -58,6 +58,7 @@
58
58
  "redirect-secret.mjs",
59
59
  "redirect-fragments.mjs",
60
60
  "observer-target.mjs",
61
+ "frame-read.mjs",
61
62
  "fixture-client.mjs"
62
63
  ],
63
64
  "scripts": {
@@ -66,7 +67,7 @@
66
67
  "validate": "node scripts/validate.mjs",
67
68
  "lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings",
68
69
  "format": "biome check --write . && gofmt -w . && cargo fmt",
69
- "test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs test/integration/release-line.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
70
+ "test": "npm run validate && node --test test/integration/self-contained.test.mjs test/integration/browser-runner.test.mjs test/integration/fixture-client.test.mjs test/integration/git-retry.test.mjs test/integration/release-line.test.mjs test/integration/guard-redirect-carrier.test.mjs test/integration/guard-redirect-session-end.test.mjs test/integration/guard-incomplete.test.mjs test/integration/guard-stall.test.mjs test/integration/guard-forged.test.mjs test/integration/guard-exit-steps.test.mjs test/integration/guard-frame-read.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
70
71
  "test:unit": "go test -race -cover ./...",
71
72
  "build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
72
73
  "trust": "npx fledgling sync -y --skip-publish",
@@ -78,10 +79,10 @@
78
79
  "test:gates": "node --test test/integration/gates.test.mjs",
79
80
  "test:guard-target": "node --test test/integration/guard-runner.test.mjs",
80
81
  "test:unit:redirect": "mkdir -p coverage/unit && node --experimental-test-coverage --test --test-coverage-include=redirect-secret.mjs --test-coverage-include=redirect-fragments.mjs --test-reporter=lcov --test-reporter-destination=coverage/unit/redirect.lcov test/unit/guard-redirect-*.test.mjs && slicc-diff-cover coverage/unit/redirect.lcov",
81
- "test:unit:guard": "mkdir -p coverage/unit && node --experimental-vm-modules --no-warnings --experimental-test-coverage --test --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-include=fixture-client.mjs --test-coverage-include=observer-target.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"
82
+ "test:unit:guard": "mkdir -p coverage/unit && node --experimental-vm-modules --no-warnings --experimental-test-coverage --test --test-coverage-include=guard-runner.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=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"
82
83
  },
83
84
  "devDependencies": {
84
- "@ai-ecoverse/slicc-shared-web": "2.7.4",
85
+ "@ai-ecoverse/slicc-shared-web": "2.7.5",
85
86
  "@biomejs/biome": "2.5.15",
86
87
  "ajv": "8.20.0",
87
88
  "ajv-formats": "3.0.1",
@@ -88,7 +88,8 @@
88
88
  "chromeUrlsAttach",
89
89
  "extensionWorld",
90
90
  "discovery",
91
- "clientExit"
91
+ "clientExit",
92
+ "siteIsolationOff"
92
93
  ],
93
94
  "properties": {
94
95
  "autoAttach": {
@@ -109,7 +110,8 @@
109
110
  "type": "string",
110
111
  "minLength": 1
111
112
  },
112
- "clientExit": { "type": "string", "minLength": 1 }
113
+ "clientExit": { "type": "string", "minLength": 1 },
114
+ "siteIsolationOff": { "type": "string", "minLength": 1 }
113
115
  }
114
116
  },
115
117
  "placeholders": {
@@ -139,7 +141,8 @@
139
141
  "chromeUrlsAttach",
140
142
  "extensionWorld",
141
143
  "discovery",
142
- "clientExit"
144
+ "clientExit",
145
+ "siteIsolationOff"
143
146
  ]
144
147
  },
145
148
  "requires": {
@@ -453,6 +456,18 @@
453
456
  "as": { "$ref": "#/$defs/name" }
454
457
  }
455
458
  },
459
+ {
460
+ "type": "object",
461
+ "additionalProperties": false,
462
+ "required": ["frameRead", "from", "as"],
463
+ "properties": {
464
+ "frameRead": { "enum": ["blank", "srcdoc"] },
465
+ "from": { "type": "string", "minLength": 1 },
466
+ "on": { "$ref": "#/$defs/on" },
467
+ "requires": { "$ref": "#/$defs/requires" },
468
+ "as": { "$ref": "#/$defs/name" }
469
+ }
470
+ },
456
471
  {
457
472
  "type": "object",
458
473
  "additionalProperties": false,