@ai-ecoverse/slicc-browsers 1.22.1 → 1.24.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.
@@ -76,6 +76,16 @@ function route(req, url, send, html, redirects) {
76
76
  { sourcemap: '/listener.js.map' }
77
77
  ),
78
78
  '/page': () => html('trigger', trigger(p)),
79
+ '/blob-nav': () =>
80
+ html(
81
+ 'blob-nav',
82
+ `<p>blob-nav ${escape(p.get('n') ?? '')}</p><script>const b=URL.createObjectURL(new Blob(['<!doctype html><title>blob</title><p>blob ${escape(p.get('n') ?? '')}</p>'],{type:'text/html'}));location.replace(b);</script>`
83
+ ),
84
+ '/blank': () => html('blank', `<p>blank ${escape(p.get('n') ?? '')}</p>`),
85
+ '/blank-open': () => html('blank-open', `<p>blank-open ${escape(p.get('n') ?? '')}</p>`),
86
+ '/os/': () => html('os', `<p>os ${escape(p.get('n') ?? '')}</p>`),
87
+ '/os/index.html': () => html('os', `<p>os ${escape(p.get('n') ?? '')}</p>`),
88
+ '/os/callback.html': () => html('os-callback', `<p>callback ${escape(p.get('n') ?? '')}</p>`),
79
89
  '/api/cookies': () => send(200, req.headers.cookie ?? 'none', 'text/plain'),
80
90
  '/plant': () =>
81
91
  html('plant', '<script>localStorage.setItem("canary","planted")</script>', {
@@ -91,6 +101,25 @@ function route(req, url, send, html, redirects) {
91
101
  `<script>navigator.sendBeacon(${JSON.stringify(pre)})</script><iframe src="${escape(to)}"></iframe>`
92
102
  );
93
103
  },
104
+ '/oopif-nest': () => {
105
+ const mid = p.get('mid') ?? 'about:blank';
106
+ return html(
107
+ 'oopif-nest',
108
+ `<p>oopif-nest ${escape(p.get('n') ?? '')}</p><iframe src="${escape(mid)}"></iframe>`
109
+ );
110
+ },
111
+ '/oopif-mid': () => {
112
+ const inner = p.get('inner') ?? 'about:blank';
113
+ return html(
114
+ 'oopif-mid',
115
+ `<p>oopif-mid ${escape(p.get('n') ?? '')}</p><iframe src="${escape(inner)}"></iframe>`
116
+ );
117
+ },
118
+ '/oopif-srcdoc': () =>
119
+ html(
120
+ 'oopif-srcdoc',
121
+ `<p>oopif-srcdoc ${escape(p.get('n') ?? '')}</p><iframe sandbox="allow-scripts allow-forms" srcdoc="${escape('<!doctype html><title>srcdoc</title><p>ordinary srcdoc</p>')}"></iframe>`
122
+ ),
94
123
  '/bfcache': () => {
95
124
  const marker = JSON.stringify(`bfcache-marker-${p.get('n') ?? ''}`).replaceAll(
96
125
  '<',
@@ -147,10 +176,31 @@ function requestURL(req, scheme) {
147
176
  };
148
177
  }
149
178
 
179
+ async function holdSlow({ req, res, url, p, html, entries, history, slows }) {
180
+ const n = p.get('n') ?? '';
181
+ const ms = Math.min(Math.max(Number(p.get('ms') ?? 15000) || 15000, 1), 60000);
182
+ const entry = {
183
+ time: Date.now(),
184
+ url: url.href,
185
+ method: req.method,
186
+ headers: { ...req.headers },
187
+ };
188
+ entries.push(entry);
189
+ history.push(entry);
190
+ await new Promise((resolve) => {
191
+ const timer = setTimeout(resolve, ms);
192
+ slows.set(n, { resolve, timer });
193
+ });
194
+ slows.delete(n);
195
+ if (res.writableEnded) return;
196
+ return html('slow', `<p>slow ${escape(n)}</p>`);
197
+ }
198
+
150
199
  export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency } = {}) {
151
200
  let entries = [];
152
201
  const history = [];
153
202
  const redirects = new Map();
203
+ const slows = new Map();
154
204
  const redirectSecret = async ({ path, host = '', search = '', remove, ...value }) => {
155
205
  if (
156
206
  typeof path !== 'string' ||
@@ -162,6 +212,14 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
162
212
  if (remove) redirects.delete(key);
163
213
  else redirects.set(key, value);
164
214
  };
215
+ const releaseSlow = (n) => {
216
+ const entry = slows.get(String(n ?? ''));
217
+ if (!entry) return false;
218
+ clearTimeout(entry.timer);
219
+ entry.resolve();
220
+ slows.delete(String(n ?? ''));
221
+ return true;
222
+ };
165
223
  const scheme = tls ? 'https' : 'http';
166
224
  const handler = async (req, res) => {
167
225
  const url = requestURL(req, scheme);
@@ -183,6 +241,10 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
183
241
  'text/html',
184
242
  headers
185
243
  );
244
+ if (req.method === 'GET' && url.pathname === '/slow') {
245
+ await holdSlow({ req, res, url, p, html, entries, history, slows });
246
+ return;
247
+ }
186
248
  if (req.method === 'GET' && url.pathname === '/log') {
187
249
  const from = Number(p.get('since') ?? 0);
188
250
  if (!Number.isInteger(from) || from < 0 || from > history.length)
@@ -220,6 +282,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
220
282
  return {
221
283
  redirects,
222
284
  redirectSecret,
285
+ releaseSlow,
223
286
  port: address.port,
224
287
  origin: `${scheme}://${host}:${address.port}`,
225
288
  log: () => structuredClone(entries),
@@ -228,6 +291,7 @@ export async function fixtureServer({ host = '127.0.0.1', port = 0, tls, latency
228
291
  },
229
292
  close: () =>
230
293
  new Promise((resolve, reject) => {
294
+ for (const n of [...slows.keys()]) releaseSlow(n);
231
295
  server.closeAllConnections();
232
296
  server.close((error) => (error ? reject(error) : resolve()));
233
297
  }),
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 { bfcacheRestore } from './bfcache-restore.mjs';
6
+ import { closeProtectedShape } from './close-protected-shape.mjs';
6
7
  import { extensionFrame } from './extension-frame.mjs';
7
8
  import { DEFAULT_EXTENSION_WORLD_ID, extensionWorld } from './extension-world.mjs';
8
9
  import { frameRead } from './frame-read.mjs';
@@ -31,6 +32,7 @@ import {
31
32
  } from './guard-driver.mjs';
32
33
  import { createObserverTarget, discoveryViolation } from './observer-target.mjs';
33
34
  import { protectedErrorPage } from './protected-error-page.mjs';
35
+ import { protectedParentIframes } from './protected-parent-iframes.mjs';
34
36
  import {
35
37
  leftoverPersistentWorker,
36
38
  persistentWorkerTypes,
@@ -194,6 +196,7 @@ class Execution {
194
196
  this.unsupported = undefined;
195
197
  this.preSent = [];
196
198
  this.notes = [];
199
+ this.sharedNotes = Array.isArray(options.notes) ? options.notes : undefined;
197
200
  this.answers = [];
198
201
  this.internalSessions = new Set();
199
202
  this.attachSettings = new Map();
@@ -222,6 +225,10 @@ class Execution {
222
225
  note(message) {
223
226
  if (this.notes.length >= 80) this.notes.shift();
224
227
  Array.prototype.push.call(this.notes, message);
228
+ if (this.sharedNotes && this.sharedNotes !== this.notes) {
229
+ if (this.sharedNotes.length >= 80) this.sharedNotes.shift();
230
+ Array.prototype.push.call(this.sharedNotes, message);
231
+ }
225
232
  }
226
233
 
227
234
  installClientTrace() {
@@ -894,6 +901,8 @@ class Execution {
894
901
  'redirectSecret',
895
902
  'unparseable',
896
903
  'errorPage',
904
+ 'closeProtectedShape',
905
+ 'protectedParentIframes',
897
906
  'extensionWorld',
898
907
  'extensionFrame',
899
908
  'frameRead',
@@ -902,9 +911,15 @@ class Execution {
902
911
  if (probe && !out.ok) this.errors.push(`${probe}: ${out.error?.message ?? 'probe failed'}`);
903
912
  if (step.client && !out.ok) this.errors.push(`client exit: ${out.error.message}`);
904
913
  if (
905
- ['errorPage', 'redirectSecret', 'extensionFrame', 'frameRead', 'bfcacheRestore'].includes(
906
- probe
907
- ) &&
914
+ [
915
+ 'errorPage',
916
+ 'closeProtectedShape',
917
+ 'protectedParentIframes',
918
+ 'redirectSecret',
919
+ 'extensionFrame',
920
+ 'frameRead',
921
+ 'bfcacheRestore',
922
+ ].includes(probe) &&
908
923
  out.ok &&
909
924
  out.result?.incomplete
910
925
  )
@@ -1000,9 +1015,15 @@ class Execution {
1000
1015
  return;
1001
1016
  }
1002
1017
  if (step.client) return outcome(this.exit());
1003
- if (step.redirectSecret || step.errorPage) return this.originProbe(step, session);
1004
- if (step.spawnWorker) return this.spawnWorker(step);
1005
- if (step.probeSessions) return this.probeSessions(step);
1018
+ if (
1019
+ step.redirectSecret ||
1020
+ step.errorPage ||
1021
+ step.closeProtectedShape ||
1022
+ step.protectedParentIframes ||
1023
+ step.spawnWorker ||
1024
+ step.probeSessions
1025
+ )
1026
+ return this.stepProbe(step, session);
1006
1027
  if (step.bfcacheRestore)
1007
1028
  return outcome(
1008
1029
  bfcacheRestore({
@@ -1143,6 +1164,14 @@ class Execution {
1143
1164
  }
1144
1165
  }
1145
1166
 
1167
+ stepProbe(step, session) {
1168
+ if (step.redirectSecret || step.errorPage) return this.originProbe(step, session);
1169
+ if (step.closeProtectedShape) return this.runCloseProtectedShape(step);
1170
+ if (step.protectedParentIframes) return this.runProtectedParentIframes(step);
1171
+ if (step.spawnWorker) return this.spawnWorker(step);
1172
+ return this.probeSessions(step);
1173
+ }
1174
+
1146
1175
  async originProbe(step, session) {
1147
1176
  if (step.redirectSecret) {
1148
1177
  this.internalSessions.add(session);
@@ -1187,6 +1216,44 @@ class Execution {
1187
1216
  );
1188
1217
  }
1189
1218
 
1219
+ runCloseProtectedShape(step) {
1220
+ return outcome(
1221
+ closeProtectedShape({
1222
+ client: this.client,
1223
+ reader: this.reader,
1224
+ ordinary: this.vars.ordinary,
1225
+ protectedOrigin: this.vars.target,
1226
+ shape: step.closeProtectedShape,
1227
+ tag: this.vars.tag,
1228
+ internalSessions: this.internalSessions,
1229
+ notes: this.notes,
1230
+ budget: this.bound(5000),
1231
+ releaseSlow: this.options.fixture.releaseSlow,
1232
+ })
1233
+ );
1234
+ }
1235
+
1236
+ async runProtectedParentIframes(step) {
1237
+ const out = await outcome(
1238
+ protectedParentIframes({
1239
+ client: this.client,
1240
+ reader: this.reader,
1241
+ protectedOrigin: this.vars.target,
1242
+ cross: this.options.targets?.cross,
1243
+ ordinary: this.vars.ordinary,
1244
+ mode: step.protectedParentIframes,
1245
+ tag: this.vars.tag,
1246
+ internalSessions: this.internalSessions,
1247
+ notes: this.notes,
1248
+ budget: this.bound(5000),
1249
+ discovery: supported(['discovery'], this.options.capabilities),
1250
+ eventStart: this.eventStart,
1251
+ })
1252
+ );
1253
+ if (out.ok && out.result?.pageId) this.targets.push(out.result.pageId);
1254
+ return out;
1255
+ }
1256
+
1190
1257
  spawnWorkerKind() {
1191
1258
  return allSteps([...(this.vector.setup ?? []), ...(this.vector.trigger ?? [])]).find(
1192
1259
  (step) => step.spawnWorker
@@ -2173,10 +2240,15 @@ const observerRequired = (check) => {
2173
2240
  return check?.not ? observerRequired(check.not) : false;
2174
2241
  };
2175
2242
  const needsObserver = (vector) =>
2176
- vector.control.via === 'unguarded' ||
2177
2243
  allSteps([...(vector.setup ?? []), ...(vector.trigger ?? [])]).some(
2178
- (step) => step.isolatedWorld || step.observer || step.spawnWorker
2244
+ (step) =>
2245
+ step.isolatedWorld ||
2246
+ step.observer ||
2247
+ step.spawnWorker ||
2248
+ step.closeProtectedShape ||
2249
+ step.protectedParentIframes
2179
2250
  ) ||
2251
+ vector.control.via === 'unguarded' ||
2180
2252
  observerRequired(vector.liveCheck) ||
2181
2253
  Object.values(vector.observe ?? {}).some(
2182
2254
  (observation) => observerRequired(observation.check) || observerRequired(observation.eitherWhen)