@ai-ecoverse/slicc-browsers 1.7.7 → 1.7.8
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 +12 -8
- package/corpus.json +118 -0
- package/guard-runner.mjs +164 -41
- package/guard-vectors.json +338 -11
- package/package.json +2 -2
- package/schema/browsers.schema.json +2 -1
- package/schema/corpus.schema.json +2 -1
- package/schema/guard-vectors.schema.json +3 -0
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ Every feature builds for `wasm32-wasip1`.
|
|
|
81
81
|
- `rank`: `tiers`, 1 to 6: `sevenOpen` (an endpoint with a seven tab), `endpoint` (any other endpoint), `chrome` (installed `chromeId`), `chromium` (other installed Chromium browsers), `electron` (installed Electron apps), `six` (an endpoint in SLICC six's profile, with or without a seven tab; see [SLICC six's profiles](#slicc-sixs-profiles)).
|
|
82
82
|
- `discovery`:
|
|
83
83
|
- `activePortFile` (`DevToolsActivePort`): its first line is the port (1 to 65535) and its second the browser's websocket path, which starts with `/`. A file without both is ignored. A file is live only when its port answers `/json/version` with a `webSocketDebuggerUrl` whose path is that websocket path; otherwise it's stale and ignored. When the port accepts connections but serves no `/json/version`, the endpoint is listed as `wsOnly`, with `ws://127.0.0.1:<port><path>`;
|
|
84
|
-
- `probePorts` (9222 to 9229
|
|
84
|
+
- `probePorts` (9222 to 9229);
|
|
85
85
|
- every endpoint's `cdp` is `<scheme>://<host>:<port>`, with the host in lower case and `localhost` and `::1` written as `127.0.0.1`, whatever form the URL was found in. Endpoints with the same host and port, written that way, are one endpoint. A `ws://` `--cdp` URL that is listed as `wsOnly` keeps its websocket URL as given;
|
|
86
86
|
- `sourceRank`: when two sources find the same host and port, the lower rank wins, and on a tie the first found;
|
|
87
87
|
- `names`: an endpoint's name when it isn't from a known browser's user-data dir: `electron` when the user agent contains `electronUserAgent`, else `products[<product>]` for the product before the `/` in `/json/version`'s `Browser`, else that product, else `fallback`;
|
|
@@ -131,7 +131,7 @@ A same-site frame shares its parent's process and has no target of its own. Ever
|
|
|
131
131
|
## Guard vectors
|
|
132
132
|
|
|
133
133
|
Every CDP guard runs the vectors in `guard-vectors.json` (schema `schema/guard-vectors.schema.json`): bios, slicc-cdp, slicc-extension and slicc-cli. The sources are listed in `sources`:
|
|
134
|
-
- slicc-cli's `protectVectors` at `2d5d981` (`test/integration/testdata/link/protect.js`), with the ids kept;
|
|
134
|
+
- slicc-cli's `protectVectors` at `2d5d981` (`test/integration/testdata/link/protect.js`), with the ids kept, and at `fe33052` for `C3.redirect-popup-stays-out` and `C3.direct-popup-stays-out`: a popup opened straight onto, or through a redirect chain to, a protected origin never reaches the client, not at open and not after the page later navigates itself and auto-attach runs again;
|
|
135
135
|
- slicc-cdp's `test/integration/guard-vectors.json` at `4907a45`, where its `E1`, `E2`, `E3`, `E5` and `B2` are renamed `E1.onload`, `E2.binding`, `E3.bypass-csp`, `E5.console` and `B2.headers`;
|
|
136
136
|
- the cert lane's E and B rows;
|
|
137
137
|
- `C5.continue-request` and `C5.continue-response`, added next to `C5.document`.
|
|
@@ -171,7 +171,7 @@ Any `cdp`, `page` or `children` step can take `requires`, the capabilities it ne
|
|
|
171
171
|
`hops` routes `url` through `/hop` on each `origin`, with its `code`.
|
|
172
172
|
- `{ "answer": { "url", "stage"?, "cdp"?, "params"?, "hold"? } }`: answer each paused request or auth challenge for `url` with that call; `hold` leaves it paused. Every other paused request continues unchanged.
|
|
173
173
|
- `{ "children": steps, "where"? }`: run these steps on each target auto-attached from then on, then resume it. `where` scopes them to the row's own target: `{ "openerId": "page" }` (opened by the row's page) or `{ "urlPrefix": url }`; other attached targets are resumed and otherwise left alone.
|
|
174
|
-
- `{ "settle": { "beacon"?, "title"?, "ms"?, "variant"?, "session"? } }`: wait for a beacon from `beacon`, for the page `title`, or for the session to detach. It waits up to `ms` (10000 by default), then another 600 ms. `variant` waits the run's detach variant instead.
|
|
174
|
+
- `{ "settle": { "beacon"?, "served"?, "title"?, "ms"?, "variant"?, "session"? } }`: wait for a beacon from `beacon`, for the fixture to log a request for the URL `served`, for the page `title`, or for the session to detach. It waits up to `ms` (10000 by default), then another 600 ms. `variant` waits the run's detach variant instead.
|
|
175
175
|
- `{ "wait": ms }`.
|
|
176
176
|
- `{ "paused": { "url", "ms" } }`: wait until the client has seen the pause that an `answer` rule for `url` matches (`Fetch.requestPaused` at the rule's stage, or `Fetch.authRequired` for a `Fetch.continueWithAuth` rule). It waits up to `ms`. On the control leg, a pause that never comes fails the row with `paused: no pause for <url> within <ms>ms`. On the guarded leg the guard may hide that pause, so the step waits the full `ms` and doesn't fail. The C5 guessed-id vectors and `C5.auth` use it before their second request, so the probe's interception id falls between the two ids the guess spans.
|
|
177
177
|
- `{ "guess": { "between", "cdp", "params" }, "as" }`: the call for each request id between the ids paused for the two URLs.
|
|
@@ -191,7 +191,7 @@ Popups are observed by `Target.targetCreated` with the opener's `openerId`, or b
|
|
|
191
191
|
|
|
192
192
|
**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`.
|
|
193
193
|
|
|
194
|
-
**Refusals.** Each refusable step has a refusal name: its `as`, or `refused.<i>`, where `i` is its index in the vector's steps, setup then trigger, with each `children` step's steps right after it, counted from 0 before `on` is applied. When a guard refuses the step and the error matches `refusal.match`, the runner sets that name to `true` in the leg's observations. On that leg, an observation whose `dependsOn` lists the name is skipped; every other observation is checked as usual. A guard that refuses the step on every origin passes in one of two ways: through the guard, with the step refused on both legs, or with an unguarded control and the guard's matched refusal on the guarded leg. With `via: "guard"`, a refusal on one leg only fails that leg. The comparison only counts steps that ran on both legs. A step that ran on one leg only (for example a `children` step on the control's popup, when the guarded leg never gets that popup attached) doesn't fail the row by itself, and the row reports it in `ran`, as `{ "refused.5": "control-only" }` or `"guarded-only"`. Observations that depend on such a step follow their `dependsOn` on each leg. A guard that allows the step must pass its control through the guard.
|
|
194
|
+
**Refusals.** Each refusable step has a refusal name: its `as`, or `refused.<i>`, where `i` is its index in the vector's steps, setup then trigger, with each `children` step's steps right after it, counted from 0 before `on` is applied. When a guard refuses the step and the error matches `refusal.match`, the runner sets that name to `true` in the leg's observations. On that leg, an observation whose `dependsOn` lists the name is skipped; every other observation is checked as usual. A guard that refuses the step on every origin passes in one of two ways: through the guard, with the step refused on both legs, or with an unguarded control and the guard's matched refusal on the guarded leg. With `via: "guard"`, a refusal on one leg only fails that leg. The comparison only counts steps that ran on both legs. A step that ran on one leg only (for example a `children` step on the control's popup, when the guarded leg never gets that popup attached) doesn't fail the row by itself, and the row reports it in `ran`, as `{ "refused.5": "control-only" }` or `"guarded-only"`. Observations that depend on such a step follow their `dependsOn` on each leg. A refusable step that fails without a matching refusal, for example with an internal error or a timeout, is not a refusal: it fails the row with `<name> failed without a refusal: <message>`. A step skipped because an earlier step failed therefore never passes through `ran`, and only a matched refusal skips the observations that depend on it. A guard that allows the step must pass its control through the guard.
|
|
195
195
|
|
|
196
196
|
**Liveness.** A runner checks `live` through its own CDP, never through the guard path: either the protected document loaded (title, marker or URL), or the guard returned its own refusal for a refusable step. A failing `live` fails the id.
|
|
197
197
|
|
|
@@ -285,7 +285,11 @@ target. In observations and liveness checks, an `any` alternative that reference
|
|
|
285
285
|
a `null` origin is dropped and the rest are evaluated; the row skips only when
|
|
286
286
|
every alternative of an `any`, or a required (`all`, `not` or plain) check, needs it. Tags and cookie names are unique per leg. `variant`
|
|
287
287
|
selects one variant or an array; omission selects all. `parallel` defaults to 1.
|
|
288
|
-
`
|
|
288
|
+
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.
|
|
289
|
+
|
|
290
|
+
`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.
|
|
291
|
+
|
|
292
|
+
`log(row)` receives completed rows. Besides `control`, `guarded`, `live`, `why` and `errors`, a row may carry `ran` (steps that ran on one leg only) and `notes` (settles skipped because their registration was refused). Both are informational: they explain a row's verdict, and adopters (bios, slicc-cli, slicc-cdp) must not count them as failures, or as passes. The verdict is `control`, `guarded` and `live`. A variant supplies the wait before an
|
|
289
293
|
explicit `Target.detachFromTarget` step and for a `settle.variant` step.
|
|
290
294
|
|
|
291
295
|
Every runnable vector has a declarative `liveCheck`; every observation has a
|
|
@@ -439,7 +443,7 @@ A six endpoint has tier `six`, whatever its seven tabs. **A program never choose
|
|
|
439
443
|
|
|
440
444
|
## Output schema 1
|
|
441
445
|
|
|
442
|
-
`schema/browsers.schema.json` describes `slicc browsers --json`, which `playwright-cli list --json` also prints. [slicc-cli's README](https://github.com/ai-ecoverse/slicc-cli#slicc-browsers---json---cdp-url---no-bundle-scan) is the authoritative spec for it; this schema is a copy and follows it. Fields can be added; `source` is one of `devtools-active-port`, `flag`, `probe` and `process`. `tier` is 1 to 6, and `six: true` marks an endpoint in SLICC six's profile (tier 6).
|
|
446
|
+
`schema/browsers.schema.json` describes `slicc browsers --json`, which `playwright-cli list --json` also prints. [slicc-cli's README](https://github.com/ai-ecoverse/slicc-cli#slicc-browsers---json---cdp-url---no-bundle-scan) is the authoritative spec for it; this schema is a copy and follows it. Fields can be added; `source` is one of `devtools-active-port`, `flag`, `probe` and `process`. `tier` is 1 to 6, and `six: true` marks an endpoint in SLICC six's profile (tier 6). An endpoint may carry `tabs`: every page target except those on protected origins. Seven's own tabs are protected, so they are in `sevenTabs` only, never in `tabs`. `playwright-cli list --json` prints it, and a corpus case that checks it needs `tabs`.
|
|
443
447
|
|
|
444
448
|
## The corpus
|
|
445
449
|
|
|
@@ -447,7 +451,7 @@ Each case is a fake machine: a home tree, `DevToolsActivePort` files, fake `/jso
|
|
|
447
451
|
|
|
448
452
|
**`case.json`:**
|
|
449
453
|
- `description`, and `os?`: the OSes the case runs on (`darwin`, `linux`, `windows`; default all).
|
|
450
|
-
- `needs`: what a program must support to run the case: `user-data-dirs` (`DevToolsActivePort` in browsers' default user-data dirs), `seven-profiles` (in the profile roots), `flag` (`--cdp` URLs), `process`, `probe` (port probes
|
|
454
|
+
- `needs`: what a program must support to run the case: `user-data-dirs` (`DevToolsActivePort` in browsers' default user-data dirs), `seven-profiles` (in the profile roots), `flag` (`--cdp` URLs), `process`, `probe` (port probes), `origins` (extra seven origins), `installed` (ranking installed browsers), `six` (finding and marking SLICC six's profiles), `tabs` (each endpoint's `tabs` list). A program skips a case that needs something it doesn't do.
|
|
451
455
|
- `env?`: environment variables for discovery, which sees nothing else from the real environment.
|
|
452
456
|
- `files?`: file contents by path. Every path starts with a token.
|
|
453
457
|
- `cdp?`, `origins?`, `probePorts?`: `--cdp` URLs, extra seven origins, and the ports to probe.
|
|
@@ -508,7 +512,7 @@ for case in corpus().cases {
|
|
|
508
512
|
|
|
509
513
|
Go 1.26, Rust stable with `wasm32-wasip1`, Node ≥ 24.
|
|
510
514
|
- `npm run corpus` regenerates `corpus.json` from `corpus/`; the pre-commit hook and CI fail when it's stale.
|
|
511
|
-
- `npm run validate` checks `catalog.json`, `corpus.json`, `protected.json` and `guard-vectors.json` against the schemas, runs the protected-origin vectors through `protected.mjs`, and checks what a schema can't say: unique ids, bundle ids and names, known Windows roots, rising tiers, compiling patterns, known tokens, tier order, `sevenOpen`, `six` and `auto` in each `expected.json`, and no port from 9222 to 9229.
|
|
515
|
+
- `npm run validate` checks `catalog.json`, `corpus.json`, `protected.json` and `guard-vectors.json` against the schemas, runs the protected-origin vectors through `protected.mjs`, and checks what a schema can't say: unique ids, bundle ids and names, known Windows roots, rising tiers, compiling patterns, known tokens, tier order, `sevenOpen`, `tabs` (which holds no entry of `sevenTabs`), `six` and `auto` in each `expected.json`, and no port from 9222 to 9229.
|
|
512
516
|
- `npm run lint`: the slicc lint tools (Biome, no comments, no unit tests in git, `AGENTS.md`), `gofmt`, `go mod tidy -diff`, `go vet`, golangci-lint, `cargo fmt --check` and clippy.
|
|
513
517
|
- `npm test`: `validate`, the Go tests in `test/integration/`, and `cargo test --all-features`.
|
|
514
518
|
- `npm run build`: the Go module, the crate (also for `wasm32-wasip1`), and `npm pack --dry-run`.
|
package/corpus.json
CHANGED
|
@@ -664,6 +664,124 @@
|
|
|
664
664
|
]
|
|
665
665
|
}
|
|
666
666
|
},
|
|
667
|
+
{
|
|
668
|
+
"name": "endpoint-tabs",
|
|
669
|
+
"case": {
|
|
670
|
+
"description": "tabs lists every page target except those on protected origins; seven's own tabs are only in sevenTabs, and iframes, workers and other protected pages are left out",
|
|
671
|
+
"needs": [
|
|
672
|
+
"flag",
|
|
673
|
+
"tabs"
|
|
674
|
+
],
|
|
675
|
+
"cdp": [
|
|
676
|
+
"127.0.0.1:9331"
|
|
677
|
+
],
|
|
678
|
+
"http": {
|
|
679
|
+
"127.0.0.1:9331": {
|
|
680
|
+
"version": {
|
|
681
|
+
"Browser": "Chrome/154.0.8037.98",
|
|
682
|
+
"Protocol-Version": "1.3",
|
|
683
|
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.8037.98 Safari/537.36",
|
|
684
|
+
"V8-Version": "15.4.79.1",
|
|
685
|
+
"WebKit-Version": "537.36 (@a1b2c3)",
|
|
686
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1:9331/devtools/browser/t1"
|
|
687
|
+
},
|
|
688
|
+
"list": [
|
|
689
|
+
{
|
|
690
|
+
"id": "P1",
|
|
691
|
+
"type": "page",
|
|
692
|
+
"url": "https://lars.sliccy.ai/",
|
|
693
|
+
"title": "seven",
|
|
694
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P1"
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
"id": "P2",
|
|
698
|
+
"type": "page",
|
|
699
|
+
"url": "https://example.test/docs",
|
|
700
|
+
"title": "docs",
|
|
701
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P2"
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
"id": "P3",
|
|
705
|
+
"type": "page",
|
|
706
|
+
"url": "https://www.sliccy.ai/",
|
|
707
|
+
"title": "SLICC",
|
|
708
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P3"
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
"id": "P4",
|
|
712
|
+
"type": "page",
|
|
713
|
+
"url": "https://vault.sliccy.ai/",
|
|
714
|
+
"title": "vault",
|
|
715
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P4"
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
"id": "P5",
|
|
719
|
+
"type": "page",
|
|
720
|
+
"url": "about:blank",
|
|
721
|
+
"title": "",
|
|
722
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P5"
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"id": "P6",
|
|
726
|
+
"type": "iframe",
|
|
727
|
+
"url": "https://example.test/frame",
|
|
728
|
+
"title": "frame",
|
|
729
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P6"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"id": "P7",
|
|
733
|
+
"type": "service_worker",
|
|
734
|
+
"url": "https://example.test/sw.js",
|
|
735
|
+
"title": "worker",
|
|
736
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1/devtools/page/P7"
|
|
737
|
+
}
|
|
738
|
+
]
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
"expected": {
|
|
743
|
+
"schema": 1,
|
|
744
|
+
"minChromium": 116,
|
|
745
|
+
"entries": [
|
|
746
|
+
{
|
|
747
|
+
"id": "cdp:127.0.0.1:9331",
|
|
748
|
+
"kind": "endpoint",
|
|
749
|
+
"tier": 1,
|
|
750
|
+
"name": "Chrome",
|
|
751
|
+
"version": "154.0.8037.98",
|
|
752
|
+
"endpoint": {
|
|
753
|
+
"cdp": "http://127.0.0.1:9331",
|
|
754
|
+
"webSocketDebuggerUrl": "ws://127.0.0.1:9331/devtools/browser/t1",
|
|
755
|
+
"name": "Chrome",
|
|
756
|
+
"version": "154.0.8037.98",
|
|
757
|
+
"product": "Chrome/154.0.8037.98",
|
|
758
|
+
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.8037.98 Safari/537.36",
|
|
759
|
+
"source": "flag",
|
|
760
|
+
"sevenOpen": true,
|
|
761
|
+
"sevenTabs": [
|
|
762
|
+
{
|
|
763
|
+
"id": "P1",
|
|
764
|
+
"url": "https://lars.sliccy.ai/",
|
|
765
|
+
"title": "seven"
|
|
766
|
+
}
|
|
767
|
+
],
|
|
768
|
+
"tabs": [
|
|
769
|
+
{
|
|
770
|
+
"id": "P2",
|
|
771
|
+
"url": "https://example.test/docs",
|
|
772
|
+
"title": "docs"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"id": "P5",
|
|
776
|
+
"url": "about:blank",
|
|
777
|
+
"title": ""
|
|
778
|
+
}
|
|
779
|
+
]
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
},
|
|
667
785
|
{
|
|
668
786
|
"name": "extra-origins",
|
|
669
787
|
"case": {
|
package/guard-runner.mjs
CHANGED
|
@@ -385,12 +385,21 @@ class Execution {
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
async createContext() {
|
|
389
|
+
const client = this.reader ?? this.client;
|
|
390
|
+
try {
|
|
391
|
+
return (await client.send('Target.createBrowserContext', {})).browserContextId;
|
|
392
|
+
} catch (error) {
|
|
393
|
+
const message = String(error?.message ?? error);
|
|
394
|
+
if (!/timed? ?out/i.test(message)) throw error;
|
|
395
|
+
this.notes.push(`Target.createBrowserContext retried once after: ${message}`);
|
|
396
|
+
return (await client.send('Target.createBrowserContext', {})).browserContextId;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
388
400
|
async seed() {
|
|
389
401
|
if (supported(['browserContexts'], this.options.capabilities)) {
|
|
390
|
-
const
|
|
391
|
-
'Target.createBrowserContext',
|
|
392
|
-
{}
|
|
393
|
-
);
|
|
402
|
+
const browserContextId = await this.createContext();
|
|
394
403
|
this.vars.context = browserContextId;
|
|
395
404
|
this.contexts.push(browserContextId);
|
|
396
405
|
}
|
|
@@ -414,7 +423,7 @@ class Execution {
|
|
|
414
423
|
}
|
|
415
424
|
|
|
416
425
|
async pump() {
|
|
417
|
-
while (!this.stopped) {
|
|
426
|
+
while (!this.stopped && !this.clients.flight?.abandoned) {
|
|
418
427
|
for (const event of this.client.events.slice(this.eventStart)) {
|
|
419
428
|
if (this.processed.has(event)) continue;
|
|
420
429
|
this.processed.add(event);
|
|
@@ -546,32 +555,47 @@ class Execution {
|
|
|
546
555
|
.some((e) => e.method === 'Target.detachedFromTarget' && e.params?.sessionId === session);
|
|
547
556
|
}
|
|
548
557
|
|
|
558
|
+
record(step, out, session, phase, gone) {
|
|
559
|
+
const refused =
|
|
560
|
+
!gone &&
|
|
561
|
+
(step.refusable
|
|
562
|
+
? isRefusal(out, step)
|
|
563
|
+
: /refused/.test(String(out.error?.message ?? out.error)));
|
|
564
|
+
this.trace.push({
|
|
565
|
+
phase,
|
|
566
|
+
declared: Boolean(step.refusable),
|
|
567
|
+
session,
|
|
568
|
+
out,
|
|
569
|
+
refused,
|
|
570
|
+
...(gone ? { detached: true } : {}),
|
|
571
|
+
});
|
|
572
|
+
if (step.refusable) this.refused[step.refusal.name] = refused;
|
|
573
|
+
if (step.refusable && !out.ok && !refused && !gone)
|
|
574
|
+
this.errors.push(
|
|
575
|
+
`${step.refusal.name} failed without a refusal: ${String(out.error?.message ?? out.error)}`
|
|
576
|
+
);
|
|
577
|
+
if (step.as && !out.result?.pending) this.calls[step.as] = out;
|
|
578
|
+
if (step.cdp) (this.calls.$cdp ??= []).push({ method: step.cdp, ...out });
|
|
579
|
+
}
|
|
580
|
+
|
|
549
581
|
async execute(list, childSession, phase = 'child') {
|
|
550
582
|
if (phase === 'trigger') await this.childrenSettled();
|
|
551
|
-
for (const raw of list) {
|
|
583
|
+
for (const [index, raw] of list.entries()) {
|
|
584
|
+
if (this.clients.flight)
|
|
585
|
+
Object.assign(this.clients.flight, { leg: this.side, phase, step: index });
|
|
552
586
|
const step = expand(raw, this.vars);
|
|
553
587
|
if (!supported(step.requires, this.options.capabilities) || step.text) continue;
|
|
554
588
|
if (!childSession && step.session === 'child') await this.childrenSettled();
|
|
555
589
|
const session = childSession ?? this.session(step.session);
|
|
556
590
|
const out = await this.operation(step, session, phase);
|
|
557
591
|
if (!out) continue;
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
!gone &&
|
|
561
|
-
(step.refusable
|
|
562
|
-
? isRefusal(out, step)
|
|
563
|
-
: /refused/.test(String(out.error?.message ?? out.error)));
|
|
564
|
-
this.trace.push({
|
|
565
|
-
phase,
|
|
566
|
-
declared: Boolean(step.refusable),
|
|
567
|
-
session,
|
|
592
|
+
this.record(
|
|
593
|
+
step,
|
|
568
594
|
out,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (step.as && !out.result?.pending) this.calls[step.as] = out;
|
|
574
|
-
if (step.cdp) (this.calls.$cdp ??= []).push({ method: step.cdp, ...out });
|
|
595
|
+
session,
|
|
596
|
+
phase,
|
|
597
|
+
Boolean(childSession) && !out.ok && this.detached(childSession)
|
|
598
|
+
);
|
|
575
599
|
this.options.debug?.({ step, out, side: this.side });
|
|
576
600
|
}
|
|
577
601
|
}
|
|
@@ -608,7 +632,7 @@ class Execution {
|
|
|
608
632
|
const refused = () => (settle.dependsOn ?? []).some((name) => this.refused[name]);
|
|
609
633
|
const skipped = () => {
|
|
610
634
|
this.notes.push(
|
|
611
|
-
`settle:skipped-refused ${settle.beacon ? `beacon ${settle.beacon}` : settle.title ? `title ${settle.title}` : 'load'}`
|
|
635
|
+
`settle:skipped-refused ${settle.beacon ? `beacon ${settle.beacon}` : settle.served ? `request ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'}`
|
|
612
636
|
);
|
|
613
637
|
};
|
|
614
638
|
if (refused()) return skipped();
|
|
@@ -619,6 +643,7 @@ class Execution {
|
|
|
619
643
|
}
|
|
620
644
|
const met = await this.poll(async () => {
|
|
621
645
|
if (refused()) return true;
|
|
646
|
+
if (settle.served) return this.requestCheck({ served: settle.served, tagged: false });
|
|
622
647
|
if (settle.beacon)
|
|
623
648
|
return (
|
|
624
649
|
(await this.requestCheck({ beacon: settle.beacon }, this.actionRequestStart)) &&
|
|
@@ -639,7 +664,7 @@ class Execution {
|
|
|
639
664
|
if (refused()) return skipped();
|
|
640
665
|
if (settle.required && !met)
|
|
641
666
|
this.errors.push(
|
|
642
|
-
`settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.title ? `title ${settle.title}` : 'load'} within ${settle.ms ?? 1500}ms`
|
|
667
|
+
`settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${settle.ms ?? 1500}ms`
|
|
643
668
|
);
|
|
644
669
|
}
|
|
645
670
|
|
|
@@ -1138,8 +1163,87 @@ function variables(targets, target, side, slot, index) {
|
|
|
1138
1163
|
return vars;
|
|
1139
1164
|
}
|
|
1140
1165
|
|
|
1141
|
-
|
|
1166
|
+
function tracked(client, calls, name) {
|
|
1167
|
+
if (!client || typeof client.send !== 'function') return client;
|
|
1168
|
+
let next = 0;
|
|
1169
|
+
const send = (method, params, sessionId) => {
|
|
1170
|
+
const id = ++next;
|
|
1171
|
+
calls.set(id, { name, method, session: sessionId, since: Date.now() });
|
|
1172
|
+
return Promise.resolve()
|
|
1173
|
+
.then(() => client.send(method, params, sessionId))
|
|
1174
|
+
.finally(() => calls.delete(id));
|
|
1175
|
+
};
|
|
1176
|
+
return new Proxy(client, {
|
|
1177
|
+
get(base, key) {
|
|
1178
|
+
if (key === 'send') return send;
|
|
1179
|
+
const value = Reflect.get(base, key);
|
|
1180
|
+
return typeof value === 'function' ? value.bind(base) : value;
|
|
1181
|
+
},
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function view(flight, base, clients) {
|
|
1186
|
+
const now = Date.now();
|
|
1187
|
+
const pending = (name) =>
|
|
1188
|
+
[...flight.calls.values()]
|
|
1189
|
+
.filter((call) => call.name === name)
|
|
1190
|
+
.map(({ method, session, since }) => ({ method, session, ageMs: now - since }));
|
|
1191
|
+
return {
|
|
1192
|
+
id: base.id,
|
|
1193
|
+
target: base.target,
|
|
1194
|
+
variant: base.variant,
|
|
1195
|
+
leg: flight.leg,
|
|
1196
|
+
phase: flight.phase,
|
|
1197
|
+
step: flight.step,
|
|
1198
|
+
clients: {
|
|
1199
|
+
client: pending('client'),
|
|
1200
|
+
...(clients.unguarded && clients.unguarded !== clients.client
|
|
1201
|
+
? { unguarded: pending('unguarded') }
|
|
1202
|
+
: {}),
|
|
1203
|
+
},
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
async function watch(work, ms, snapshot) {
|
|
1208
|
+
if (!ms) {
|
|
1209
|
+
await work;
|
|
1210
|
+
return undefined;
|
|
1211
|
+
}
|
|
1212
|
+
let timer;
|
|
1213
|
+
const expired = new Promise((resolve) => {
|
|
1214
|
+
timer = setTimeout(() => resolve(snapshot()), ms);
|
|
1215
|
+
});
|
|
1216
|
+
const stuck = await Promise.race([work.then(() => undefined), expired]);
|
|
1217
|
+
clearTimeout(timer);
|
|
1218
|
+
return stuck;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
async function timedOut(stuck, base, made, work, opts) {
|
|
1222
|
+
const close = made.close;
|
|
1223
|
+
made.close = undefined;
|
|
1224
|
+
await close?.call(made);
|
|
1225
|
+
work.catch(() => {});
|
|
1226
|
+
const failed = {
|
|
1227
|
+
...base,
|
|
1228
|
+
control: 'fail',
|
|
1229
|
+
guarded: 'fail',
|
|
1230
|
+
live: 'fail',
|
|
1231
|
+
why: `row timed out after ${opts.rowTimeoutMs}ms`,
|
|
1232
|
+
errors: [`row timed out after ${opts.rowTimeoutMs}ms: ${JSON.stringify(stuck)}`],
|
|
1233
|
+
inFlight: stuck,
|
|
1234
|
+
};
|
|
1235
|
+
opts.log?.(failed);
|
|
1236
|
+
return failed;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
async function executeRow(job, opts, made, set, slot, index, flights) {
|
|
1142
1240
|
const { target, variant } = job;
|
|
1241
|
+
const calls = new Map();
|
|
1242
|
+
const client = tracked(made.client, calls, 'client');
|
|
1243
|
+
const unguarded =
|
|
1244
|
+
made.unguarded === made.client ? client : tracked(made.unguarded, calls, 'unguarded');
|
|
1245
|
+
const flight = { calls, leg: undefined, phase: undefined, step: undefined };
|
|
1246
|
+
const clients = { ...made, client, unguarded, flight };
|
|
1143
1247
|
const scoped = available(job.vector, target, opts.targets);
|
|
1144
1248
|
const vector = scoped.vector ?? job.vector;
|
|
1145
1249
|
const base = {
|
|
@@ -1197,20 +1301,30 @@ async function executeRow(job, opts, clients, set, slot, index) {
|
|
|
1197
1301
|
reason: 'not-supported:unguarded-observer',
|
|
1198
1302
|
};
|
|
1199
1303
|
const legs = {};
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
side,
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1304
|
+
flights.add({ flight, base, clients });
|
|
1305
|
+
opts.onStart?.(base);
|
|
1306
|
+
const sides = (async () => {
|
|
1307
|
+
for (const side of ['control', 'guarded']) {
|
|
1308
|
+
try {
|
|
1309
|
+
const vars = variables(opts.targets, target, side, slot, index);
|
|
1310
|
+
legs[side] = await new Execution(
|
|
1311
|
+
vector,
|
|
1312
|
+
side,
|
|
1313
|
+
opts,
|
|
1314
|
+
clients,
|
|
1315
|
+
vars,
|
|
1316
|
+
set.variants[variant]
|
|
1317
|
+
).run();
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
legs[side] = { observed: {}, live: false, errors: [String(error.message ?? error)] };
|
|
1320
|
+
}
|
|
1213
1321
|
}
|
|
1322
|
+
})();
|
|
1323
|
+
const stuck = await watch(sides, opts.rowTimeoutMs, () => view(flight, base, clients));
|
|
1324
|
+
for (const entry of flights) if (entry.flight === flight) flights.delete(entry);
|
|
1325
|
+
if (stuck) {
|
|
1326
|
+
flight.abandoned = true;
|
|
1327
|
+
return timedOut(stuck, base, made, sides, opts);
|
|
1214
1328
|
}
|
|
1215
1329
|
const row = {
|
|
1216
1330
|
...result(vector, {
|
|
@@ -1287,7 +1401,16 @@ function validate(set, opts) {
|
|
|
1287
1401
|
return variants;
|
|
1288
1402
|
}
|
|
1289
1403
|
|
|
1290
|
-
export
|
|
1404
|
+
export function run(set, options) {
|
|
1405
|
+
const flights = new Set();
|
|
1406
|
+
const inFlight = () =>
|
|
1407
|
+
[...flights].map(({ flight, base, clients }) => view(flight, base, clients));
|
|
1408
|
+
const running = runAll(set, options, flights).then((done) => ({ ...done, inFlight }));
|
|
1409
|
+
running.inFlight = inFlight;
|
|
1410
|
+
return running;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
async function runAll(set, options, flights) {
|
|
1291
1414
|
const opts = { capabilities: {}, variant: Object.keys(set.variants), parallel: 1, ...options };
|
|
1292
1415
|
const variants = validate(set, opts);
|
|
1293
1416
|
const jobs = set.vectors.flatMap((vector) =>
|
|
@@ -1331,11 +1454,11 @@ export async function run(set, options) {
|
|
|
1331
1454
|
});
|
|
1332
1455
|
await previous;
|
|
1333
1456
|
try {
|
|
1334
|
-
results[index] = await executeRow(job, opts, clients, set, slot, index);
|
|
1457
|
+
results[index] = await executeRow(job, opts, clients, set, slot, index, flights);
|
|
1335
1458
|
} finally {
|
|
1336
1459
|
done();
|
|
1337
1460
|
}
|
|
1338
|
-
} else results[index] = await executeRow(job, opts, clients, set, slot, index);
|
|
1461
|
+
} else results[index] = await executeRow(job, opts, clients, set, slot, index, flights);
|
|
1339
1462
|
} finally {
|
|
1340
1463
|
await clients.close?.();
|
|
1341
1464
|
}
|
package/guard-vectors.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.7.
|
|
3
|
+
"setVersion": "1.7.8",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
7
7
|
"ref": "2d5d98188667cd70180f825c7cba985399167732",
|
|
8
8
|
"path": "test/integration/testdata/link/protect.js"
|
|
9
9
|
},
|
|
10
|
+
{
|
|
11
|
+
"repo": "ai-ecoverse/slicc-cli",
|
|
12
|
+
"ref": "fe33052afd7bf94e98f38f85e95d29df7cb9e8c2",
|
|
13
|
+
"path": "test/integration/testdata/link/protect.js"
|
|
14
|
+
},
|
|
10
15
|
{
|
|
11
16
|
"repo": "ai-ecoverse/slicc-cdp",
|
|
12
17
|
"ref": "4907a45d5ab8547483eaa013d4378deafe9f82d8",
|
|
@@ -4825,6 +4830,302 @@
|
|
|
4825
4830
|
"observer": "client"
|
|
4826
4831
|
}
|
|
4827
4832
|
},
|
|
4833
|
+
{
|
|
4834
|
+
"id": "C3.redirect-popup-stays-out",
|
|
4835
|
+
"family": "C",
|
|
4836
|
+
"kind": "cdp",
|
|
4837
|
+
"scope": "any",
|
|
4838
|
+
"setup": [
|
|
4839
|
+
{
|
|
4840
|
+
"text": "an allowed opener with auto-attach opens a popup on a redirect chain that ends on a page that later navigates itself to another path of its origin, then auto-attaches again without detaching"
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
"page": "tab",
|
|
4844
|
+
"url": "{ordinary}/doc?n=opener"
|
|
4845
|
+
},
|
|
4846
|
+
{
|
|
4847
|
+
"cdp": "Target.setAutoAttach",
|
|
4848
|
+
"params": {
|
|
4849
|
+
"autoAttach": true,
|
|
4850
|
+
"waitForDebuggerOnStart": true,
|
|
4851
|
+
"flatten": true
|
|
4852
|
+
},
|
|
4853
|
+
"session": "browser",
|
|
4854
|
+
"refusable": true,
|
|
4855
|
+
"refusal": {
|
|
4856
|
+
"match": "^slicc: refused"
|
|
4857
|
+
},
|
|
4858
|
+
"requires": ["autoAttach", "browserSession"]
|
|
4859
|
+
},
|
|
4860
|
+
{
|
|
4861
|
+
"children": [
|
|
4862
|
+
{
|
|
4863
|
+
"cdp": "Page.enable"
|
|
4864
|
+
}
|
|
4865
|
+
],
|
|
4866
|
+
"where": {
|
|
4867
|
+
"openerId": "page"
|
|
4868
|
+
},
|
|
4869
|
+
"requires": ["autoAttach"]
|
|
4870
|
+
}
|
|
4871
|
+
],
|
|
4872
|
+
"trigger": [
|
|
4873
|
+
{
|
|
4874
|
+
"page": "open",
|
|
4875
|
+
"url": "{target}/page?k=deferred&defer=3000&n={tag}&to={target}/doc?n={tag}-later",
|
|
4876
|
+
"hops": [
|
|
4877
|
+
{
|
|
4878
|
+
"origin": "{ordinary}",
|
|
4879
|
+
"code": 302
|
|
4880
|
+
},
|
|
4881
|
+
{
|
|
4882
|
+
"origin": "{cross}",
|
|
4883
|
+
"code": 307
|
|
4884
|
+
}
|
|
4885
|
+
]
|
|
4886
|
+
},
|
|
4887
|
+
{
|
|
4888
|
+
"settle": {
|
|
4889
|
+
"served": "{target}/doc?n={tag}-later",
|
|
4890
|
+
"ms": 15000,
|
|
4891
|
+
"required": true
|
|
4892
|
+
}
|
|
4893
|
+
},
|
|
4894
|
+
{
|
|
4895
|
+
"cdp": "Target.setAutoAttach",
|
|
4896
|
+
"params": {
|
|
4897
|
+
"autoAttach": true,
|
|
4898
|
+
"waitForDebuggerOnStart": false,
|
|
4899
|
+
"flatten": true
|
|
4900
|
+
},
|
|
4901
|
+
"session": "browser",
|
|
4902
|
+
"refusable": true,
|
|
4903
|
+
"refusal": {
|
|
4904
|
+
"match": "refused"
|
|
4905
|
+
},
|
|
4906
|
+
"requires": ["autoAttach", "browserSession"]
|
|
4907
|
+
},
|
|
4908
|
+
{
|
|
4909
|
+
"wait": 1000
|
|
4910
|
+
},
|
|
4911
|
+
{
|
|
4912
|
+
"cdp": "Target.setAutoAttach",
|
|
4913
|
+
"params": {
|
|
4914
|
+
"autoAttach": false,
|
|
4915
|
+
"waitForDebuggerOnStart": false,
|
|
4916
|
+
"flatten": true
|
|
4917
|
+
},
|
|
4918
|
+
"session": "browser",
|
|
4919
|
+
"refusable": true,
|
|
4920
|
+
"refusal": {
|
|
4921
|
+
"match": "refused"
|
|
4922
|
+
},
|
|
4923
|
+
"requires": ["autoAttach", "browserSession"]
|
|
4924
|
+
}
|
|
4925
|
+
],
|
|
4926
|
+
"targets": ["seven", "vault", "vault-x", "apex"],
|
|
4927
|
+
"requires": ["autoAttach", "browserSession"],
|
|
4928
|
+
"observe": {
|
|
4929
|
+
"committed": {
|
|
4930
|
+
"means": "the stand-in served the popup's first page on {target}",
|
|
4931
|
+
"dependsOn": [],
|
|
4932
|
+
"check": {
|
|
4933
|
+
"served": "/page",
|
|
4934
|
+
"origin": "{target}",
|
|
4935
|
+
"observer": "fixture"
|
|
4936
|
+
}
|
|
4937
|
+
},
|
|
4938
|
+
"navigatedLater": {
|
|
4939
|
+
"means": "the popup navigated itself to {target}/doc?n={tag}-later",
|
|
4940
|
+
"dependsOn": [],
|
|
4941
|
+
"check": {
|
|
4942
|
+
"served": "{target}/doc?n={tag}-later",
|
|
4943
|
+
"observer": "fixture"
|
|
4944
|
+
}
|
|
4945
|
+
},
|
|
4946
|
+
"attached": {
|
|
4947
|
+
"means": "the client got Target.attachedToTarget for the popup on {target}, at open or later",
|
|
4948
|
+
"dependsOn": ["refused.2"],
|
|
4949
|
+
"requires": ["autoAttach"],
|
|
4950
|
+
"check": {
|
|
4951
|
+
"attached": "{target}",
|
|
4952
|
+
"session": "child",
|
|
4953
|
+
"observer": "client"
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
},
|
|
4957
|
+
"live": "the runner's own CDP sees {target}'s document load (title, marker or URL), or the guard returned its refusal for a refusable step",
|
|
4958
|
+
"control": {
|
|
4959
|
+
"origin": "ordinary",
|
|
4960
|
+
"via": "guard",
|
|
4961
|
+
"expected": {
|
|
4962
|
+
"committed": true,
|
|
4963
|
+
"navigatedLater": true,
|
|
4964
|
+
"attached": true
|
|
4965
|
+
}
|
|
4966
|
+
},
|
|
4967
|
+
"expected": {
|
|
4968
|
+
"committed": true,
|
|
4969
|
+
"navigatedLater": true,
|
|
4970
|
+
"attached": false
|
|
4971
|
+
},
|
|
4972
|
+
"liveCheck": {
|
|
4973
|
+
"any": [
|
|
4974
|
+
{
|
|
4975
|
+
"loaded": "{target}/doc?n={tag}-later",
|
|
4976
|
+
"observer": "fixture"
|
|
4977
|
+
},
|
|
4978
|
+
{
|
|
4979
|
+
"registration": "any",
|
|
4980
|
+
"observer": "client"
|
|
4981
|
+
}
|
|
4982
|
+
],
|
|
4983
|
+
"observer": "client"
|
|
4984
|
+
}
|
|
4985
|
+
},
|
|
4986
|
+
{
|
|
4987
|
+
"id": "C3.direct-popup-stays-out",
|
|
4988
|
+
"family": "C",
|
|
4989
|
+
"kind": "cdp",
|
|
4990
|
+
"scope": "any",
|
|
4991
|
+
"setup": [
|
|
4992
|
+
{
|
|
4993
|
+
"text": "an allowed opener with auto-attach opens a popup straight on a page that later navigates itself to another path of its origin, then auto-attaches again without detaching"
|
|
4994
|
+
},
|
|
4995
|
+
{
|
|
4996
|
+
"page": "tab",
|
|
4997
|
+
"url": "{ordinary}/doc?n=opener"
|
|
4998
|
+
},
|
|
4999
|
+
{
|
|
5000
|
+
"cdp": "Target.setAutoAttach",
|
|
5001
|
+
"params": {
|
|
5002
|
+
"autoAttach": true,
|
|
5003
|
+
"waitForDebuggerOnStart": true,
|
|
5004
|
+
"flatten": true
|
|
5005
|
+
},
|
|
5006
|
+
"session": "browser",
|
|
5007
|
+
"refusable": true,
|
|
5008
|
+
"refusal": {
|
|
5009
|
+
"match": "^slicc: refused"
|
|
5010
|
+
},
|
|
5011
|
+
"requires": ["autoAttach", "browserSession"]
|
|
5012
|
+
},
|
|
5013
|
+
{
|
|
5014
|
+
"children": [
|
|
5015
|
+
{
|
|
5016
|
+
"cdp": "Page.enable"
|
|
5017
|
+
}
|
|
5018
|
+
],
|
|
5019
|
+
"where": {
|
|
5020
|
+
"openerId": "page"
|
|
5021
|
+
},
|
|
5022
|
+
"requires": ["autoAttach"]
|
|
5023
|
+
}
|
|
5024
|
+
],
|
|
5025
|
+
"trigger": [
|
|
5026
|
+
{
|
|
5027
|
+
"page": "open",
|
|
5028
|
+
"url": "{target}/page?k=deferred&defer=3000&n={tag}&to={target}/doc?n={tag}-later"
|
|
5029
|
+
},
|
|
5030
|
+
{
|
|
5031
|
+
"settle": {
|
|
5032
|
+
"served": "{target}/doc?n={tag}-later",
|
|
5033
|
+
"ms": 15000,
|
|
5034
|
+
"required": true
|
|
5035
|
+
}
|
|
5036
|
+
},
|
|
5037
|
+
{
|
|
5038
|
+
"cdp": "Target.setAutoAttach",
|
|
5039
|
+
"params": {
|
|
5040
|
+
"autoAttach": true,
|
|
5041
|
+
"waitForDebuggerOnStart": false,
|
|
5042
|
+
"flatten": true
|
|
5043
|
+
},
|
|
5044
|
+
"session": "browser",
|
|
5045
|
+
"refusable": true,
|
|
5046
|
+
"refusal": {
|
|
5047
|
+
"match": "refused"
|
|
5048
|
+
},
|
|
5049
|
+
"requires": ["autoAttach", "browserSession"]
|
|
5050
|
+
},
|
|
5051
|
+
{
|
|
5052
|
+
"wait": 1000
|
|
5053
|
+
},
|
|
5054
|
+
{
|
|
5055
|
+
"cdp": "Target.setAutoAttach",
|
|
5056
|
+
"params": {
|
|
5057
|
+
"autoAttach": false,
|
|
5058
|
+
"waitForDebuggerOnStart": false,
|
|
5059
|
+
"flatten": true
|
|
5060
|
+
},
|
|
5061
|
+
"session": "browser",
|
|
5062
|
+
"refusable": true,
|
|
5063
|
+
"refusal": {
|
|
5064
|
+
"match": "refused"
|
|
5065
|
+
},
|
|
5066
|
+
"requires": ["autoAttach", "browserSession"]
|
|
5067
|
+
}
|
|
5068
|
+
],
|
|
5069
|
+
"targets": ["seven", "vault", "vault-x", "apex"],
|
|
5070
|
+
"requires": ["autoAttach", "browserSession"],
|
|
5071
|
+
"observe": {
|
|
5072
|
+
"committed": {
|
|
5073
|
+
"means": "the stand-in served the popup's first page on {target}",
|
|
5074
|
+
"dependsOn": [],
|
|
5075
|
+
"check": {
|
|
5076
|
+
"served": "/page",
|
|
5077
|
+
"origin": "{target}",
|
|
5078
|
+
"observer": "fixture"
|
|
5079
|
+
}
|
|
5080
|
+
},
|
|
5081
|
+
"navigatedLater": {
|
|
5082
|
+
"means": "the popup navigated itself to {target}/doc?n={tag}-later",
|
|
5083
|
+
"dependsOn": [],
|
|
5084
|
+
"check": {
|
|
5085
|
+
"served": "{target}/doc?n={tag}-later",
|
|
5086
|
+
"observer": "fixture"
|
|
5087
|
+
}
|
|
5088
|
+
},
|
|
5089
|
+
"attached": {
|
|
5090
|
+
"means": "the client got Target.attachedToTarget for the popup on {target}, at open or later",
|
|
5091
|
+
"dependsOn": ["refused.2"],
|
|
5092
|
+
"requires": ["autoAttach"],
|
|
5093
|
+
"check": {
|
|
5094
|
+
"attached": "{target}",
|
|
5095
|
+
"session": "child",
|
|
5096
|
+
"observer": "client"
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
},
|
|
5100
|
+
"live": "the runner's own CDP sees {target}'s document load (title, marker or URL), or the guard returned its refusal for a refusable step",
|
|
5101
|
+
"control": {
|
|
5102
|
+
"origin": "ordinary",
|
|
5103
|
+
"via": "guard",
|
|
5104
|
+
"expected": {
|
|
5105
|
+
"committed": true,
|
|
5106
|
+
"navigatedLater": true,
|
|
5107
|
+
"attached": true
|
|
5108
|
+
}
|
|
5109
|
+
},
|
|
5110
|
+
"expected": {
|
|
5111
|
+
"committed": true,
|
|
5112
|
+
"navigatedLater": true,
|
|
5113
|
+
"attached": false
|
|
5114
|
+
},
|
|
5115
|
+
"liveCheck": {
|
|
5116
|
+
"any": [
|
|
5117
|
+
{
|
|
5118
|
+
"loaded": "{target}/doc?n={tag}-later",
|
|
5119
|
+
"observer": "fixture"
|
|
5120
|
+
},
|
|
5121
|
+
{
|
|
5122
|
+
"registration": "any",
|
|
5123
|
+
"observer": "client"
|
|
5124
|
+
}
|
|
5125
|
+
],
|
|
5126
|
+
"observer": "client"
|
|
5127
|
+
}
|
|
5128
|
+
},
|
|
4828
5129
|
{
|
|
4829
5130
|
"id": "C4.static-cross-site",
|
|
4830
5131
|
"family": "C",
|
|
@@ -5458,11 +5759,24 @@
|
|
|
5458
5759
|
"means": "a child frame is a chrome-error page for {target}/doc?n={tag}",
|
|
5459
5760
|
"dependsOn": ["refused.8"],
|
|
5460
5761
|
"check": {
|
|
5461
|
-
"
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5762
|
+
"any": [
|
|
5763
|
+
{
|
|
5764
|
+
"result": "frameTree",
|
|
5765
|
+
"path": "result",
|
|
5766
|
+
"equals": {
|
|
5767
|
+
"$regex": "chrome-error://"
|
|
5768
|
+
},
|
|
5769
|
+
"observer": "client"
|
|
5770
|
+
},
|
|
5771
|
+
{
|
|
5772
|
+
"frameBlocked": {
|
|
5773
|
+
"url": "{target}/doc?n={tag}",
|
|
5774
|
+
"session": "page",
|
|
5775
|
+
"frame": true
|
|
5776
|
+
},
|
|
5777
|
+
"observer": "unguarded"
|
|
5778
|
+
}
|
|
5779
|
+
],
|
|
5466
5780
|
"observer": "client"
|
|
5467
5781
|
}
|
|
5468
5782
|
}
|
|
@@ -5690,11 +6004,24 @@
|
|
|
5690
6004
|
"means": "a child frame is a chrome-error page for {target}/doc?n={tag}",
|
|
5691
6005
|
"dependsOn": ["refused.8"],
|
|
5692
6006
|
"check": {
|
|
5693
|
-
"
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
6007
|
+
"any": [
|
|
6008
|
+
{
|
|
6009
|
+
"result": "frameTree",
|
|
6010
|
+
"path": "result",
|
|
6011
|
+
"equals": {
|
|
6012
|
+
"$regex": "chrome-error://"
|
|
6013
|
+
},
|
|
6014
|
+
"observer": "client"
|
|
6015
|
+
},
|
|
6016
|
+
{
|
|
6017
|
+
"frameBlocked": {
|
|
6018
|
+
"url": "{target}/doc?n={tag}",
|
|
6019
|
+
"session": "page",
|
|
6020
|
+
"frame": true
|
|
6021
|
+
},
|
|
6022
|
+
"observer": "unguarded"
|
|
6023
|
+
}
|
|
6024
|
+
],
|
|
5698
6025
|
"observer": "client"
|
|
5699
6026
|
}
|
|
5700
6027
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
66
66
|
"release": "semantic-release",
|
|
67
67
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
68
|
-
"test:guard-integration": "node --test 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",
|
|
68
|
+
"test:guard-integration": "node --test 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-diagnostics.test.mjs",
|
|
69
69
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
70
70
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
71
71
|
"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,7 +75,8 @@
|
|
|
75
75
|
"wsOnly": { "const": true },
|
|
76
76
|
"six": { "const": true },
|
|
77
77
|
"sevenOpen": { "type": "boolean" },
|
|
78
|
-
"sevenTabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } }
|
|
78
|
+
"sevenTabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } },
|
|
79
|
+
"tabs": { "type": "array", "items": { "$ref": "#/$defs/tab" } }
|
|
79
80
|
}
|
|
80
81
|
},
|
|
81
82
|
"installed": {
|