@wenathlan/extension 1.1.46 → 1.1.48
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 +6 -4
- package/dist/emulation.d.ts +89 -0
- package/dist/emulation.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +989 -11
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +89 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +39 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/profilers.d.ts +167 -0
- package/dist/profilers.d.ts.map +1 -0
- package/dist/protocol.d.ts +68 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +238 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1483 -15
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +430 -6
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +22 -6
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +319 -2
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/dist/style.css +3 -1
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,379 @@
|
|
|
1
|
+
// profilers.ts
|
|
2
|
+
var profilerkinds = ["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"];
|
|
3
|
+
var flowmetricnames = ["navigation", "paint", "lcp", "fid", "interaction", "blocking"];
|
|
4
|
+
var tracecategories = ["navigation", "scripting", "rendering", "painting", "loading", "network"];
|
|
5
|
+
function attachtargetof(value) {
|
|
6
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
7
|
+
const entry = value;
|
|
8
|
+
const kinds = ["page", "iframe", "worker", "serviceworker"];
|
|
9
|
+
const kind = typeof entry.kind === "string" && kinds.includes(entry.kind) ? entry.kind : void 0;
|
|
10
|
+
const url = typeof entry.url === "string" && entry.url.trim() ? entry.url.trim() : void 0;
|
|
11
|
+
if (kind === void 0 || url === void 0) return void 0;
|
|
12
|
+
if (kind !== "page" && !/^https:\/\//.test(url)) return void 0;
|
|
13
|
+
return { kind, url };
|
|
14
|
+
}
|
|
15
|
+
function flowspecof(value) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
17
|
+
const entry = value;
|
|
18
|
+
const prefix = typeof entry.prefix === "string" && entry.prefix.trim() ? entry.prefix.trim() : void 0;
|
|
19
|
+
const steps = Array.isArray(entry.steps) ? entry.steps.filter((step) => typeof step === "string" && step.trim().length > 0) : [];
|
|
20
|
+
const metrics = Array.isArray(entry.metrics) ? entry.metrics.filter((metric) => typeof metric === "string" && flowmetricnames.includes(metric)) : [];
|
|
21
|
+
if (prefix === void 0 || steps.length === 0 || metrics.length === 0) return void 0;
|
|
22
|
+
return { prefix, steps, metrics };
|
|
23
|
+
}
|
|
24
|
+
function stepwindows(spec, marks) {
|
|
25
|
+
const windows = [];
|
|
26
|
+
for (const stepid of spec.steps) {
|
|
27
|
+
const start = marks.find((mark) => mark.type === "mark" && mark.name === `${spec.prefix}:${stepid}:start`);
|
|
28
|
+
const end = marks.find((mark) => mark.type === "mark" && mark.name === `${spec.prefix}:${stepid}:end`);
|
|
29
|
+
if (start === void 0 || end === void 0) continue;
|
|
30
|
+
windows.push({ stepid, start: start.start, end: Math.max(end.start, start.start) });
|
|
31
|
+
}
|
|
32
|
+
return windows;
|
|
33
|
+
}
|
|
34
|
+
function measure(input) {
|
|
35
|
+
const metrics = [];
|
|
36
|
+
const windows = stepwindows(input.spec, input.entries.filter((entry) => entry.type === "mark"));
|
|
37
|
+
const stepsof = (start, end) => windows.filter((window2) => window2.end >= start && window2.start <= end).map((window2) => window2.stepid);
|
|
38
|
+
const push = (name, start, end, steps) => {
|
|
39
|
+
if (!input.spec.metrics.includes(name)) return;
|
|
40
|
+
metrics.push({ id: `${input.runid}-${input.stepid}-${name}-${metrics.length}`, runid: input.runid, stepid: input.stepid, name, start, end, duration: Math.max(0, end - start), steps, at: input.now });
|
|
41
|
+
};
|
|
42
|
+
const navigation = input.entries.find((entry) => entry.type === "navigation");
|
|
43
|
+
if (navigation !== void 0) push("navigation", navigation.start, navigation.start + navigation.duration, stepsof(navigation.start, navigation.start + navigation.duration));
|
|
44
|
+
for (const paint of input.entries.filter((entry) => entry.type === "paint")) {
|
|
45
|
+
if (!input.spec.metrics.includes("paint")) break;
|
|
46
|
+
push("paint", paint.start, paint.start + paint.duration, stepsof(paint.start, paint.start + paint.duration));
|
|
47
|
+
}
|
|
48
|
+
const lcps = input.entries.filter((entry) => entry.type === "largest-contentful-paint");
|
|
49
|
+
const lcp = lcps.length > 0 ? lcps.reduce((largest, entry) => entry.start > largest.start ? entry : largest) : void 0;
|
|
50
|
+
if (lcp !== void 0) push("lcp", lcp.start, lcp.start + lcp.duration, stepsof(lcp.start, lcp.start + lcp.duration));
|
|
51
|
+
const firstinput = input.entries.find((entry) => entry.type === "first-input");
|
|
52
|
+
if (firstinput !== void 0) push("fid", firstinput.start, firstinput.start + firstinput.duration, stepsof(firstinput.start, firstinput.start + firstinput.duration));
|
|
53
|
+
const interactions = input.entries.filter((entry) => entry.type === "event");
|
|
54
|
+
if (interactions.length > 0) {
|
|
55
|
+
const start = interactions.reduce((earliest, entry) => entry.start < earliest.start ? entry : earliest).start;
|
|
56
|
+
const end = interactions.reduce((latest, entry) => entry.start + entry.duration > latest ? entry.start + entry.duration : latest, start);
|
|
57
|
+
push("interaction", start, end, stepsof(start, end));
|
|
58
|
+
}
|
|
59
|
+
for (const window2 of windows) {
|
|
60
|
+
if (!input.spec.metrics.includes("blocking")) break;
|
|
61
|
+
const blocking = input.entries.filter((entry) => entry.type === "longtask" && entry.start >= window2.start && entry.start <= window2.end).reduce((total, entry) => total + Math.max(0, entry.duration - 50), 0);
|
|
62
|
+
metrics.push({ id: `${input.runid}-${input.stepid}-blocking-${window2.stepid}`, runid: input.runid, stepid: input.stepid, name: "blocking", start: window2.start, end: window2.end, duration: blocking, steps: [window2.stepid], at: input.now });
|
|
63
|
+
}
|
|
64
|
+
return metrics;
|
|
65
|
+
}
|
|
66
|
+
function heapintervalallowed(lastcapturedat, interval, now) {
|
|
67
|
+
if (interval === void 0 || lastcapturedat === void 0) return true;
|
|
68
|
+
return now - lastcapturedat >= interval;
|
|
69
|
+
}
|
|
70
|
+
function heapsnap(input) {
|
|
71
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, bytesize: input.usedbytes, nodecount: input.nodecount, capturedat: input.now };
|
|
72
|
+
}
|
|
73
|
+
function growsampleof(input) {
|
|
74
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, usedbytes: input.usedbytes, limitbytes: input.limitbytes, at: input.now };
|
|
75
|
+
}
|
|
76
|
+
function growthtrend(input) {
|
|
77
|
+
const ordered = [...input.samples].sort((left, right) => left.at - right.at);
|
|
78
|
+
const first = ordered[0];
|
|
79
|
+
const last = ordered[ordered.length - 1];
|
|
80
|
+
const computed = first !== void 0 && last !== void 0 && last.at > first.at ? (last.usedbytes - first.usedbytes) / (last.at - first.at) : 0;
|
|
81
|
+
const flaggedsteps = [];
|
|
82
|
+
for (let index = 1; index < ordered.length; index += 1) {
|
|
83
|
+
const previous = ordered[index - 1];
|
|
84
|
+
const current = ordered[index];
|
|
85
|
+
if (previous === void 0 || current === void 0) continue;
|
|
86
|
+
const growth = current.at > previous.at ? (current.usedbytes - previous.usedbytes) / (current.at - previous.at) : 0;
|
|
87
|
+
if (growth > input.slope && !flaggedsteps.includes(current.stepid)) flaggedsteps.push(current.stepid);
|
|
88
|
+
}
|
|
89
|
+
return { runid: input.runid, slope: computed, samples: ordered.length, flaggedsteps, at: input.now };
|
|
90
|
+
}
|
|
91
|
+
function cpusnap(input) {
|
|
92
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const sample of input.samples) ranked.set(sample.name, (ranked.get(sample.name) ?? 0) + sample.time);
|
|
94
|
+
const hotfunctions = [...ranked.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map((entry) => entry[0]);
|
|
95
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, duration: input.duration, samplecount: input.samples.length, hotfunctions, at: input.now };
|
|
96
|
+
}
|
|
97
|
+
function shiftentryof(value) {
|
|
98
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
99
|
+
const entry = value;
|
|
100
|
+
const score = typeof entry.score === "number" && Number.isFinite(entry.score) && entry.score >= 0 ? entry.score : void 0;
|
|
101
|
+
const starttime = typeof entry.starttime === "number" && Number.isFinite(entry.starttime) ? entry.starttime : void 0;
|
|
102
|
+
if (score === void 0 || starttime === void 0) return void 0;
|
|
103
|
+
const selectors = Array.isArray(entry.selectors) ? entry.selectors.filter((selector) => typeof selector === "string" && selector.trim().length > 0) : [];
|
|
104
|
+
return { id: typeof entry.id === "string" ? entry.id : "", runid: typeof entry.runid === "string" ? entry.runid : "", stepid: typeof entry.stepid === "string" ? entry.stepid : "", score, starttime, selectors, at: typeof entry.at === "number" ? entry.at : starttime };
|
|
105
|
+
}
|
|
106
|
+
function tracestart(input) {
|
|
107
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, categories: [...new Set(input.categories)], bytesize: 0, events: 0, annotations: [], startedat: input.now, endedat: input.now };
|
|
108
|
+
}
|
|
109
|
+
function tracetofile(trace, events) {
|
|
110
|
+
const payload = { devthinktrace: "1.1.47", runid: trace.runid, origin: trace.origin, categories: trace.categories, startedat: trace.startedat, endedat: trace.endedat, annotations: trace.annotations, traceevents: events.map((event) => ({ name: event.name, cat: event.category, offset: event.offset })) };
|
|
111
|
+
const content = JSON.stringify(payload);
|
|
112
|
+
return { content, bytesize: content.length, events: events.length };
|
|
113
|
+
}
|
|
114
|
+
function annotationof(value) {
|
|
115
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
116
|
+
const entry = value;
|
|
117
|
+
const stepid = typeof entry.stepid === "string" && entry.stepid.trim() ? entry.stepid.trim() : void 0;
|
|
118
|
+
const label = typeof entry.label === "string" && entry.label.trim() ? entry.label.trim() : void 0;
|
|
119
|
+
if (stepid === void 0 || label === void 0) return void 0;
|
|
120
|
+
const offset = typeof entry.offset === "number" && Number.isFinite(entry.offset) && entry.offset >= 0 ? entry.offset : 0;
|
|
121
|
+
return { stepid, label, offset };
|
|
122
|
+
}
|
|
123
|
+
function annotatetrace(input) {
|
|
124
|
+
const aligned = input.annotations.map((annotation) => {
|
|
125
|
+
const entry = input.timeline.find((item) => item.stepid === annotation.stepid);
|
|
126
|
+
if (entry === void 0) return annotation;
|
|
127
|
+
return { ...annotation, offset: Math.max(0, entry.time - input.trace.startedat) };
|
|
128
|
+
});
|
|
129
|
+
return { ...input.trace, annotations: aligned, endedat: Math.max(input.trace.endedat, input.now) };
|
|
130
|
+
}
|
|
131
|
+
function replaytrace(content) {
|
|
132
|
+
let parsed;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(content);
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error("The trace file is not valid json.");
|
|
137
|
+
}
|
|
138
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("The trace file is not a json object.");
|
|
139
|
+
const record2 = parsed;
|
|
140
|
+
const runid = typeof record2.runid === "string" ? record2.runid : "";
|
|
141
|
+
const annotations = Array.isArray(record2.annotations) ? record2.annotations.flatMap((item) => {
|
|
142
|
+
const annotation = annotationof(item);
|
|
143
|
+
return annotation !== void 0 ? [annotation] : [];
|
|
144
|
+
}) : [];
|
|
145
|
+
const rawevents = Array.isArray(record2.traceevents) ? record2.traceevents : [];
|
|
146
|
+
const categories = {};
|
|
147
|
+
const events = rawevents.flatMap((item) => {
|
|
148
|
+
if (!item || typeof item !== "object") return [];
|
|
149
|
+
const entry = item;
|
|
150
|
+
if (typeof entry.name !== "string" || typeof entry.cat !== "string" || typeof entry.offset !== "number") return [];
|
|
151
|
+
const offset = entry.offset;
|
|
152
|
+
categories[entry.cat] = (categories[entry.cat] ?? 0) + 1;
|
|
153
|
+
const annotation = annotations.find((candidate) => Math.abs(candidate.offset - offset) < 1);
|
|
154
|
+
return [{ name: entry.name, category: entry.cat, offset, ...annotation !== void 0 ? { stepid: annotation.stepid } : {} }];
|
|
155
|
+
});
|
|
156
|
+
return { traceid: typeof record2.id === "string" ? record2.id : "", runid, categories, events, annotations };
|
|
157
|
+
}
|
|
158
|
+
function mapurlof(scripturl, source) {
|
|
159
|
+
const match = /[#@]\s*sourceMappingURL=(\S+)/.exec(source);
|
|
160
|
+
if (match === null || match[1] === void 0) return void 0;
|
|
161
|
+
try {
|
|
162
|
+
return new URL(match[1], scripturl).toString();
|
|
163
|
+
} catch {
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function capturesourcemaps(input) {
|
|
168
|
+
return input.scripts.flatMap((script) => {
|
|
169
|
+
const mapurl = mapurlof(script.url, script.source);
|
|
170
|
+
if (mapurl === void 0) return [];
|
|
171
|
+
return [{ id: `${input.runid}-${script.url}`, runid: input.runid, stepid: input.stepid, origin: input.origin, scripturl: script.url, mapurl, parsed: false, at: input.now }];
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function decodevlq(segment) {
|
|
175
|
+
const values = [];
|
|
176
|
+
let shift = 0;
|
|
177
|
+
let value = 0;
|
|
178
|
+
for (const character of segment) {
|
|
179
|
+
const digit = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(character);
|
|
180
|
+
if (digit < 0) return void 0;
|
|
181
|
+
value += (digit & 31) << shift;
|
|
182
|
+
shift += 5;
|
|
183
|
+
if ((digit & 32) === 0) {
|
|
184
|
+
const negative = (value & 1) === 1;
|
|
185
|
+
values.push(negative ? -(value >>> 1) : value >>> 1);
|
|
186
|
+
value = 0;
|
|
187
|
+
shift = 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return values.length > 0 ? values : void 0;
|
|
191
|
+
}
|
|
192
|
+
function rewritesourcelocation(input, map) {
|
|
193
|
+
const sources = Array.isArray(map.sources) ? map.sources.filter((source2) => typeof source2 === "string") : [];
|
|
194
|
+
if (sources.length === 0 || typeof map.mappings !== "string" || map.mappings.length === 0) return void 0;
|
|
195
|
+
const lines = map.mappings.split(";");
|
|
196
|
+
if (input.line >= lines.length) return void 0;
|
|
197
|
+
let sourceindex = 0;
|
|
198
|
+
let sourceline = 0;
|
|
199
|
+
for (let line = 0; line <= input.line; line += 1) {
|
|
200
|
+
const linemappings = lines[line];
|
|
201
|
+
if (linemappings === void 0) continue;
|
|
202
|
+
const first = linemappings.split(",")[0] ?? "";
|
|
203
|
+
if (first.length === 0) continue;
|
|
204
|
+
const values = decodevlq(first);
|
|
205
|
+
if (values === void 0 || values.length < 4) continue;
|
|
206
|
+
sourceindex = Math.max(0, sourceindex + (values[1] ?? 0));
|
|
207
|
+
sourceline = Math.max(0, sourceline + (values[2] ?? 0));
|
|
208
|
+
}
|
|
209
|
+
const targetmappings = lines[input.line];
|
|
210
|
+
const target = targetmappings !== void 0 ? targetmappings.split(",")[0] ?? "" : "";
|
|
211
|
+
if (target.length === 0) return void 0;
|
|
212
|
+
const source = sources[Math.min(sources.length - 1, sourceindex)];
|
|
213
|
+
if (source === void 0) return void 0;
|
|
214
|
+
return { url: source, line: sourceline };
|
|
215
|
+
}
|
|
216
|
+
function expireprofilerecords(input) {
|
|
217
|
+
const retention = input.retention;
|
|
218
|
+
if (retention === void 0) return { heaps: input.heaps, profiles: input.profiles, traces: input.traces };
|
|
219
|
+
const expired = (at) => input.now - at > retention;
|
|
220
|
+
return {
|
|
221
|
+
heaps: input.heaps.map((heap) => expired(heap.capturedat) && heap.bytesexpired !== true ? { ...heap, bytesexpired: true } : heap),
|
|
222
|
+
profiles: input.profiles.map((profile) => expired(profile.at) && profile.samplesexpired !== true ? { ...profile, samplesexpired: true } : profile),
|
|
223
|
+
traces: input.traces.map((trace) => expired(trace.endedat) && trace.bytesexpired !== true ? { ...trace, bytesexpired: true } : trace)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// emulation.ts
|
|
228
|
+
var emulationkinds = ["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"];
|
|
229
|
+
var browserpermissions = ["geolocation", "notifications", "camera", "microphone", "clipboard-read", "clipboard-write", "midi", "persistent-storage"];
|
|
230
|
+
var permissionstates = ["granted", "denied", "prompt"];
|
|
231
|
+
function familyofkind(kind) {
|
|
232
|
+
if (kind === "emulatedevice") return "device";
|
|
233
|
+
if (kind === "emulatenetwork") return "network";
|
|
234
|
+
if (kind === "emulatelocate") return "location";
|
|
235
|
+
if (kind === "setuseragent") return "agent";
|
|
236
|
+
if (kind === "overridepermission") return "permission";
|
|
237
|
+
if (kind === "blackboxscripts") return "blackbox";
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
function devicepresetof(value) {
|
|
241
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
242
|
+
const entry = value;
|
|
243
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : void 0;
|
|
244
|
+
const width = typeof entry.width === "number" && Number.isInteger(entry.width) && entry.width > 0 ? entry.width : void 0;
|
|
245
|
+
const height = typeof entry.height === "number" && Number.isInteger(entry.height) && entry.height > 0 ? entry.height : void 0;
|
|
246
|
+
const pixelratio = typeof entry.pixelratio === "number" && Number.isFinite(entry.pixelratio) && entry.pixelratio > 0 ? entry.pixelratio : void 0;
|
|
247
|
+
if (name === void 0 || width === void 0 || height === void 0 || pixelratio === void 0) return void 0;
|
|
248
|
+
return { name, width, height, pixelratio, mobile: entry.mobile === true };
|
|
249
|
+
}
|
|
250
|
+
function networkpresetof(value) {
|
|
251
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
252
|
+
const entry = value;
|
|
253
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : void 0;
|
|
254
|
+
const latency = typeof entry.latency === "number" && Number.isFinite(entry.latency) && entry.latency >= 0 ? entry.latency : void 0;
|
|
255
|
+
const download = typeof entry.download === "number" && Number.isFinite(entry.download) && entry.download >= 0 ? entry.download : void 0;
|
|
256
|
+
const upload = typeof entry.upload === "number" && Number.isFinite(entry.upload) && entry.upload >= 0 ? entry.upload : void 0;
|
|
257
|
+
if (name === void 0 || latency === void 0 || download === void 0 || upload === void 0) return void 0;
|
|
258
|
+
return { name, latency, download, upload, offline: entry.offline === true };
|
|
259
|
+
}
|
|
260
|
+
function locationpresetof(value) {
|
|
261
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
262
|
+
const entry = value;
|
|
263
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : void 0;
|
|
264
|
+
const latitude = typeof entry.latitude === "number" && Number.isFinite(entry.latitude) ? entry.latitude : void 0;
|
|
265
|
+
const longitude = typeof entry.longitude === "number" && Number.isFinite(entry.longitude) ? entry.longitude : void 0;
|
|
266
|
+
const accuracy = typeof entry.accuracy === "number" && Number.isFinite(entry.accuracy) && entry.accuracy >= 0 ? entry.accuracy : void 0;
|
|
267
|
+
if (name === void 0 || latitude === void 0 || longitude === void 0 || accuracy === void 0) return void 0;
|
|
268
|
+
if (!locationrangevalid(latitude, longitude)) return void 0;
|
|
269
|
+
return { name, latitude, longitude, accuracy };
|
|
270
|
+
}
|
|
271
|
+
function agentpresetof(value) {
|
|
272
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
273
|
+
const entry = value;
|
|
274
|
+
const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : void 0;
|
|
275
|
+
const useragent = typeof entry.useragent === "string" ? entry.useragent : void 0;
|
|
276
|
+
const platform = typeof entry.platform === "string" && entry.platform.trim() ? entry.platform.trim() : void 0;
|
|
277
|
+
const brands = Array.isArray(entry.brands) ? entry.brands.filter((brand) => typeof brand === "string" && brand.trim().length > 0) : [];
|
|
278
|
+
if (name === void 0 || useragent === void 0 || platform === void 0 || brands.length === 0) return void 0;
|
|
279
|
+
if (!agentgrammarvalid(useragent)) return void 0;
|
|
280
|
+
return { name, useragent, platform, brands: [...new Set(brands)] };
|
|
281
|
+
}
|
|
282
|
+
function permissiongrantof(value) {
|
|
283
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
284
|
+
const entry = value;
|
|
285
|
+
const name = typeof entry.name === "string" && browserpermissions.includes(entry.name) ? entry.name : void 0;
|
|
286
|
+
const state = typeof entry.state === "string" && permissionstates.includes(entry.state) ? entry.state : void 0;
|
|
287
|
+
if (name === void 0 || state === void 0) return void 0;
|
|
288
|
+
return { name, state, runscope: entry.runscope !== false };
|
|
289
|
+
}
|
|
290
|
+
function blackboxruleof(value) {
|
|
291
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
292
|
+
const entry = value;
|
|
293
|
+
const urlpatterns = Array.isArray(entry.urlpatterns) ? entry.urlpatterns.filter((pattern) => typeof pattern === "string" && /^https:\/\//.test(pattern)) : [];
|
|
294
|
+
const tracescope = entry.tracescope;
|
|
295
|
+
if (urlpatterns.length === 0) return void 0;
|
|
296
|
+
if (tracescope !== "profiles" && tracescope !== "traces" && tracescope !== "both") return void 0;
|
|
297
|
+
return { urlpatterns: [...new Set(urlpatterns)], tracescope };
|
|
298
|
+
}
|
|
299
|
+
function revertplanof(value) {
|
|
300
|
+
const steps = Array.isArray(value) ? value.filter((step) => typeof step === "string" && step.trim().length > 0) : [];
|
|
301
|
+
return steps.length > 0 ? steps : void 0;
|
|
302
|
+
}
|
|
303
|
+
function newlayer(input) {
|
|
304
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, family: input.family, name: input.name, originscope: input.originscope, appliedat: input.at, ...input.prior !== void 0 ? { prior: input.prior } : {}, revertplan: [...input.revertplan] };
|
|
305
|
+
}
|
|
306
|
+
function emulationstateof(input) {
|
|
307
|
+
return { runid: input.runid, tabid: input.tabid, origin: input.origin, layers: [], updatedat: input.now };
|
|
308
|
+
}
|
|
309
|
+
function applylayer(state, layer, at) {
|
|
310
|
+
const layers = [...state.layers.filter((item) => item.id !== layer.id), layer];
|
|
311
|
+
return { ...state, layers, updatedat: at };
|
|
312
|
+
}
|
|
313
|
+
function revertalllayers(state, at) {
|
|
314
|
+
const reverted = [...state.layers].reverse().filter((layer) => layer.revertedat === void 0);
|
|
315
|
+
const layers = state.layers.map((layer) => layer.revertedat === void 0 ? { ...layer, revertedat: at } : layer);
|
|
316
|
+
return { state: { ...state, layers, updatedat: at }, reverted };
|
|
317
|
+
}
|
|
318
|
+
function activelayers(state) {
|
|
319
|
+
return state ? state.layers.filter((layer) => layer.revertedat === void 0) : [];
|
|
320
|
+
}
|
|
321
|
+
function layernames(state) {
|
|
322
|
+
return activelayers(state).map((layer) => layer.name);
|
|
323
|
+
}
|
|
324
|
+
function locationrangevalid(latitude, longitude) {
|
|
325
|
+
return Number.isFinite(latitude) && Number.isFinite(longitude) && latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180;
|
|
326
|
+
}
|
|
327
|
+
function agentgrammarvalid(useragent) {
|
|
328
|
+
const text2 = useragent.trim();
|
|
329
|
+
if (text2.length === 0 || text2.length > 512) return false;
|
|
330
|
+
if (/[\r\n]/.test(text2)) return false;
|
|
331
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._+\-()/:; ,]*$/.test(text2)) return false;
|
|
332
|
+
return /\/\d/.test(text2) || /\d+\.\d+/.test(text2);
|
|
333
|
+
}
|
|
334
|
+
function permissiongrade(name) {
|
|
335
|
+
return name === "geolocation" || name === "camera" || name === "microphone" || name === "notifications" ? "powerful" : "standard";
|
|
336
|
+
}
|
|
337
|
+
function expirelayers(state, retention, now) {
|
|
338
|
+
if (retention === void 0) return state;
|
|
339
|
+
const layers = state.layers.map((layer) => {
|
|
340
|
+
if (layer.revertedat === void 0 || layer.prior === void 0 || layer.priorexpired === true) return layer;
|
|
341
|
+
if (now - layer.revertedat <= retention) return layer;
|
|
342
|
+
const { prior, ...metadata } = layer;
|
|
343
|
+
void prior;
|
|
344
|
+
return { ...metadata, priorexpired: true };
|
|
345
|
+
});
|
|
346
|
+
return { ...state, layers, updatedat: now };
|
|
347
|
+
}
|
|
348
|
+
function exportpresetlibrary(input) {
|
|
349
|
+
return { version: 1, devices: [...input.devices], networks: [...input.networks], locations: [...input.locations], agents: [...input.agents], exportedat: input.now };
|
|
350
|
+
}
|
|
351
|
+
function importpresetlibrary(value) {
|
|
352
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
353
|
+
const entry = value;
|
|
354
|
+
const devices = (Array.isArray(entry.devices) ? entry.devices : []).flatMap((preset) => {
|
|
355
|
+
const parsed = devicepresetof(preset);
|
|
356
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
357
|
+
});
|
|
358
|
+
const networks = (Array.isArray(entry.networks) ? entry.networks : []).flatMap((preset) => {
|
|
359
|
+
const parsed = networkpresetof(preset);
|
|
360
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
361
|
+
});
|
|
362
|
+
const locations = (Array.isArray(entry.locations) ? entry.locations : []).flatMap((preset) => {
|
|
363
|
+
const parsed = locationpresetof(preset);
|
|
364
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
365
|
+
});
|
|
366
|
+
const agents = (Array.isArray(entry.agents) ? entry.agents : []).flatMap((preset) => {
|
|
367
|
+
const parsed = agentpresetof(preset);
|
|
368
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
369
|
+
});
|
|
370
|
+
if (devices.length + networks.length + locations.length + agents.length === 0) return void 0;
|
|
371
|
+
return { version: typeof entry.version === "number" && Number.isInteger(entry.version) && entry.version >= 1 ? entry.version : 1, devices, networks, locations, agents, exportedat: typeof entry.exportedat === "number" ? entry.exportedat : Date.now() };
|
|
372
|
+
}
|
|
373
|
+
function locationconsentcovers(origin, latitude, longitude, consents) {
|
|
374
|
+
return consents.some((consent) => consent.origin === origin && consent.approved === true && consent.revokedat === void 0 && consent.latitude === latitude && consent.longitude === longitude);
|
|
375
|
+
}
|
|
376
|
+
|
|
1
377
|
// memory.ts
|
|
2
378
|
var sessionmemory = class {
|
|
3
379
|
constructor(adapter) {
|
|
@@ -1241,6 +1617,210 @@ var sessionmemory = class {
|
|
|
1241
1617
|
async getlevelsummaries() {
|
|
1242
1618
|
return await this.adapter.get("levelsummaries") ?? [];
|
|
1243
1619
|
}
|
|
1620
|
+
/** Stores one measured flow metric of the run beside its step span; the flow series stays per run. */
|
|
1621
|
+
async addflowmetric(metric) {
|
|
1622
|
+
const records = await this.getflowmetrics();
|
|
1623
|
+
await this.adapter.set("flowmetrics", [metric, ...records]);
|
|
1624
|
+
}
|
|
1625
|
+
/** Returns every stored flow metric, newest first. */
|
|
1626
|
+
async getflowmetrics() {
|
|
1627
|
+
return await this.adapter.get("flowmetrics") ?? [];
|
|
1628
|
+
}
|
|
1629
|
+
/** Returns the flow metrics of one run, newest first. */
|
|
1630
|
+
async listflowmetrics(runid) {
|
|
1631
|
+
const records = await this.getflowmetrics();
|
|
1632
|
+
return records.filter((metric) => metric.runid === runid);
|
|
1633
|
+
}
|
|
1634
|
+
/** Stores one heap snapshot record with its byte and node counts; the user configured profile retention window expires the heavy snapshot bytes while the counts survive. */
|
|
1635
|
+
async setheaprecord(heap) {
|
|
1636
|
+
const records = (await this.adapter.get("heaprecords") ?? []).filter((item) => item.id !== heap.id);
|
|
1637
|
+
const retention = (await this.getsettings())?.profileretention;
|
|
1638
|
+
const { heaps } = expireprofilerecords({ heaps: [heap, ...records], profiles: [], traces: [], retention, now: Date.now() });
|
|
1639
|
+
await this.adapter.set("heaprecords", heaps);
|
|
1640
|
+
}
|
|
1641
|
+
/** Returns every stored heap snapshot record, newest first. */
|
|
1642
|
+
async getheaprecords() {
|
|
1643
|
+
return await this.adapter.get("heaprecords") ?? [];
|
|
1644
|
+
}
|
|
1645
|
+
/** Stores one heap growth sample taken beside a step. */
|
|
1646
|
+
async addgrowsample(sample) {
|
|
1647
|
+
const records = await this.adapter.get("growsamples") ?? [];
|
|
1648
|
+
await this.adapter.set("growsamples", [sample, ...records]);
|
|
1649
|
+
}
|
|
1650
|
+
/** Returns every stored heap growth sample, newest first. */
|
|
1651
|
+
async getgrowsamples() {
|
|
1652
|
+
return await this.adapter.get("growsamples") ?? [];
|
|
1653
|
+
}
|
|
1654
|
+
/** Returns the heap growth samples of one run, newest first. */
|
|
1655
|
+
async listgrowsamples(runid) {
|
|
1656
|
+
const records = await this.getgrowsamples();
|
|
1657
|
+
return records.filter((sample) => sample.runid === runid);
|
|
1658
|
+
}
|
|
1659
|
+
/** Stores one computed heap growth trend of a run with its slope and flagged steps, replacing the previous trend of the run. */
|
|
1660
|
+
async settrend(trend) {
|
|
1661
|
+
const records = await this.adapter.get("memorytrends") ?? [];
|
|
1662
|
+
await this.adapter.set("memorytrends", [trend, ...records.filter((item) => item.runid !== trend.runid)]);
|
|
1663
|
+
}
|
|
1664
|
+
/** Returns every stored heap growth trend, newest first. */
|
|
1665
|
+
async gettrends() {
|
|
1666
|
+
return await this.adapter.get("memorytrends") ?? [];
|
|
1667
|
+
}
|
|
1668
|
+
/** Stores one cpu profile record with its sample count and hot function list; the profile retention window expires the heavy sample payload while the counts and hot functions survive. */
|
|
1669
|
+
async setcpuprofile(profile) {
|
|
1670
|
+
const records = (await this.adapter.get("cpuprofiles") ?? []).filter((item) => item.id !== profile.id);
|
|
1671
|
+
const retention = (await this.getsettings())?.profileretention;
|
|
1672
|
+
const { profiles } = expireprofilerecords({ heaps: [], profiles: [profile, ...records], traces: [], retention, now: Date.now() });
|
|
1673
|
+
await this.adapter.set("cpuprofiles", profiles);
|
|
1674
|
+
}
|
|
1675
|
+
/** Returns every stored cpu profile record, newest first. */
|
|
1676
|
+
async getcpuprofiles() {
|
|
1677
|
+
return await this.adapter.get("cpuprofiles") ?? [];
|
|
1678
|
+
}
|
|
1679
|
+
/** Stores one layout shift entry with its score and impacted selectors. */
|
|
1680
|
+
async addshiftentry(entry) {
|
|
1681
|
+
const records = await this.adapter.get("shiftentries") ?? [];
|
|
1682
|
+
await this.adapter.set("shiftentries", [entry, ...records]);
|
|
1683
|
+
}
|
|
1684
|
+
/** Returns every stored layout shift entry, newest first. */
|
|
1685
|
+
async getshiftentries() {
|
|
1686
|
+
return await this.adapter.get("shiftentries") ?? [];
|
|
1687
|
+
}
|
|
1688
|
+
/** Stores one trace record with its category list, byte size and step annotations; the profile retention window expires the heavy trace bytes while the metadata and annotations survive. */
|
|
1689
|
+
async settracerecord(trace) {
|
|
1690
|
+
const records = (await this.adapter.get("tracerecords") ?? []).filter((item) => item.id !== trace.id);
|
|
1691
|
+
const retention = (await this.getsettings())?.profileretention;
|
|
1692
|
+
const { traces } = expireprofilerecords({ heaps: [], profiles: [], traces: [trace, ...records], retention, now: Date.now() });
|
|
1693
|
+
await this.adapter.set("tracerecords", traces);
|
|
1694
|
+
}
|
|
1695
|
+
/** Returns every stored trace record, newest first. */
|
|
1696
|
+
async gettracerecords() {
|
|
1697
|
+
return await this.adapter.get("tracerecords") ?? [];
|
|
1698
|
+
}
|
|
1699
|
+
/** Returns the trace records filtered by run and applied categories. */
|
|
1700
|
+
async listtraces(filter) {
|
|
1701
|
+
const records = await this.gettracerecords();
|
|
1702
|
+
return records.filter((trace) => (filter.runid === void 0 || trace.runid === filter.runid) && (filter.categories === void 0 || filter.categories.every((category) => trace.categories.includes(category))));
|
|
1703
|
+
}
|
|
1704
|
+
/** Stores the exported file content of one trace beside its record; the retention window drops the file bytes of expired traces while the record survives. */
|
|
1705
|
+
async settracefile(traceid, content) {
|
|
1706
|
+
const records = (await this.adapter.get("tracefiles") ?? []).filter((item) => item.traceid !== traceid);
|
|
1707
|
+
const trace = (await this.gettracerecords()).find((item) => item.id === traceid);
|
|
1708
|
+
const retention = (await this.getsettings())?.profileretention;
|
|
1709
|
+
const kept = retention === void 0 || trace === void 0 || Date.now() - trace.endedat <= retention ? [{ traceid, content, savedat: Date.now() }, ...records] : records;
|
|
1710
|
+
await this.adapter.set("tracefiles", kept);
|
|
1711
|
+
}
|
|
1712
|
+
/** Returns the exported file content of one trace, or undefined when the retention window dropped the bytes. */
|
|
1713
|
+
async gettracefile(traceid) {
|
|
1714
|
+
const records = await this.adapter.get("tracefiles") ?? [];
|
|
1715
|
+
return records.find((item) => item.traceid === traceid)?.content;
|
|
1716
|
+
}
|
|
1717
|
+
/** Stores one source map reference of a run with its script url, map url and parsed state. */
|
|
1718
|
+
async setsourcemapref(ref) {
|
|
1719
|
+
const records = (await this.adapter.get("sourcemaprefs") ?? []).filter((item) => item.id !== ref.id);
|
|
1720
|
+
await this.adapter.set("sourcemaprefs", [ref, ...records]);
|
|
1721
|
+
}
|
|
1722
|
+
/** Returns every stored source map reference, newest first. */
|
|
1723
|
+
async getsourcemaps() {
|
|
1724
|
+
return await this.adapter.get("sourcemaprefs") ?? [];
|
|
1725
|
+
}
|
|
1726
|
+
/** Stores one source map capture consent decision per origin, replacing the previous decision of its id. */
|
|
1727
|
+
async setsourcemapconsent(consent) {
|
|
1728
|
+
const records = (await this.adapter.get("sourcemapconsents") ?? []).filter((item) => item.id !== consent.id);
|
|
1729
|
+
await this.adapter.set("sourcemapconsents", [consent, ...records]);
|
|
1730
|
+
}
|
|
1731
|
+
/** Returns every source map capture consent decision, newest first. */
|
|
1732
|
+
async getsourcemapconsents() {
|
|
1733
|
+
return await this.adapter.get("sourcemapconsents") ?? [];
|
|
1734
|
+
}
|
|
1735
|
+
/** Revokes every approved source map consent of one origin so the next capture needs a new reviewed prompt. */
|
|
1736
|
+
async revokesourcemapconsents(origin, at) {
|
|
1737
|
+
const records = await this.getsourcemapconsents();
|
|
1738
|
+
let revoked = 0;
|
|
1739
|
+
const updated = records.map((consent) => {
|
|
1740
|
+
if (consent.origin !== origin || consent.revokedat !== void 0) return consent;
|
|
1741
|
+
revoked += 1;
|
|
1742
|
+
return { ...consent, revokedat: at };
|
|
1743
|
+
});
|
|
1744
|
+
await this.adapter.set("sourcemapconsents", updated);
|
|
1745
|
+
return revoked;
|
|
1746
|
+
}
|
|
1747
|
+
/** Stores the emulation state of one run keyed by its run id; the reverted layer prior states expire after the user configured retention window while the layer history always survives. */
|
|
1748
|
+
async setemulationstate(state) {
|
|
1749
|
+
const retention = (await this.getsettings())?.emulationretention;
|
|
1750
|
+
await this.adapter.set(`emulationstate${state.runid}`, expirelayers(state, retention, Date.now()));
|
|
1751
|
+
}
|
|
1752
|
+
/** Returns the persisted emulation state of one run so the layers survive service worker restarts. */
|
|
1753
|
+
async getemulationstate(runid) {
|
|
1754
|
+
return this.adapter.get(`emulationstate${runid}`);
|
|
1755
|
+
}
|
|
1756
|
+
/** Returns the active and past layers of one run, newest last in apply order; the listlayers accessor of the emulation memory. */
|
|
1757
|
+
async listlayers(runid) {
|
|
1758
|
+
const state = await this.getemulationstate(runid);
|
|
1759
|
+
return state?.layers ?? [];
|
|
1760
|
+
}
|
|
1761
|
+
/** Stores one user curated device preset by its name so the preset library stays user data instead of a hardcoded list. */
|
|
1762
|
+
async setdevicepreset(preset) {
|
|
1763
|
+
const records = (await this.adapter.get("devicepresets") ?? []).filter((item) => item.name !== preset.name);
|
|
1764
|
+
await this.adapter.set("devicepresets", [...records, preset]);
|
|
1765
|
+
}
|
|
1766
|
+
/** Returns every user curated device preset. */
|
|
1767
|
+
async getdevicepresets() {
|
|
1768
|
+
return await this.adapter.get("devicepresets") ?? [];
|
|
1769
|
+
}
|
|
1770
|
+
/** Stores one user curated network preset by its name with editable values. */
|
|
1771
|
+
async setnetworkpreset(preset) {
|
|
1772
|
+
const records = (await this.adapter.get("networkpresets") ?? []).filter((item) => item.name !== preset.name);
|
|
1773
|
+
await this.adapter.set("networkpresets", [...records, preset]);
|
|
1774
|
+
}
|
|
1775
|
+
/** Returns every user curated network preset. */
|
|
1776
|
+
async getnetworkpresets() {
|
|
1777
|
+
return await this.adapter.get("networkpresets") ?? [];
|
|
1778
|
+
}
|
|
1779
|
+
/** Stores one user curated location preset by its name. */
|
|
1780
|
+
async setlocationpreset(preset) {
|
|
1781
|
+
const records = (await this.adapter.get("locationpresets") ?? []).filter((item) => item.name !== preset.name);
|
|
1782
|
+
await this.adapter.set("locationpresets", [...records, preset]);
|
|
1783
|
+
}
|
|
1784
|
+
/** Returns every user curated location preset. */
|
|
1785
|
+
async getlocationpresets() {
|
|
1786
|
+
return await this.adapter.get("locationpresets") ?? [];
|
|
1787
|
+
}
|
|
1788
|
+
/** Stores one user curated agent preset by its name. */
|
|
1789
|
+
async setagentpreset(preset) {
|
|
1790
|
+
const records = (await this.adapter.get("agentpresets") ?? []).filter((item) => item.name !== preset.name);
|
|
1791
|
+
await this.adapter.set("agentpresets", [...records, preset]);
|
|
1792
|
+
}
|
|
1793
|
+
/** Returns every user curated agent preset. */
|
|
1794
|
+
async getagentpresets() {
|
|
1795
|
+
return await this.adapter.get("agentpresets") ?? [];
|
|
1796
|
+
}
|
|
1797
|
+
/** Replaces the blackbox rule set of one origin so third party script blackboxing stays scoped per origin. */
|
|
1798
|
+
async setblackboxrules(origin, rules) {
|
|
1799
|
+
const records = (await this.adapter.get("blackboxrules") ?? []).filter((item) => item.origin !== origin);
|
|
1800
|
+
await this.adapter.set("blackboxrules", [...records, { origin, rules }]);
|
|
1801
|
+
}
|
|
1802
|
+
/** Returns every stored blackbox rule set with its origin. */
|
|
1803
|
+
async getblackboxrules() {
|
|
1804
|
+
return await this.adapter.get("blackboxrules") ?? [];
|
|
1805
|
+
}
|
|
1806
|
+
/** Records one permission override of a run with its prior state captured for the exact restore. */
|
|
1807
|
+
async addpermissionoverride(record2) {
|
|
1808
|
+
const records = (await this.adapter.get("permissionoverrides") ?? []).filter((item) => item.id !== record2.id);
|
|
1809
|
+
await this.adapter.set("permissionoverrides", [record2, ...records]);
|
|
1810
|
+
}
|
|
1811
|
+
/** Returns the permission override history with restore states, newest first. */
|
|
1812
|
+
async getpermissionoverrides() {
|
|
1813
|
+
return await this.adapter.get("permissionoverrides") ?? [];
|
|
1814
|
+
}
|
|
1815
|
+
/** Stores one location consent decision per origin, replacing the previous decision of its id. */
|
|
1816
|
+
async setlocationconsent(consent) {
|
|
1817
|
+
const records = (await this.adapter.get("locationconsents") ?? []).filter((item) => item.id !== consent.id);
|
|
1818
|
+
await this.adapter.set("locationconsents", [consent, ...records]);
|
|
1819
|
+
}
|
|
1820
|
+
/** Returns every location consent decision, newest first. */
|
|
1821
|
+
async getlocationconsents() {
|
|
1822
|
+
return await this.adapter.get("locationconsents") ?? [];
|
|
1823
|
+
}
|
|
1244
1824
|
};
|
|
1245
1825
|
function mediakindof(record2) {
|
|
1246
1826
|
if ("pages" in record2) return "pdf";
|
|
@@ -2460,9 +3040,9 @@ function consolediff(input) {
|
|
|
2460
3040
|
}
|
|
2461
3041
|
|
|
2462
3042
|
// policy.ts
|
|
2463
|
-
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript"]);
|
|
3043
|
+
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission"]);
|
|
2464
3044
|
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr"]);
|
|
2465
|
-
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp"]);
|
|
3045
|
+
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts"]);
|
|
2466
3046
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
2467
3047
|
var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
|
|
2468
3048
|
var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement", "captureframe", "shotcanvas"]);
|
|
@@ -2480,6 +3060,8 @@ var netwatchactions = /* @__PURE__ */ new Set(["watchrequests", "readheaders", "
|
|
|
2480
3060
|
var controlactions = /* @__PURE__ */ new Set(["blockrequest", "mockresponse", "rewriteheaders", "setcookies", "readcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
|
|
2481
3061
|
var debugactions = /* @__PURE__ */ new Set(["watchconsole", "watcherrors", "watchtasks"]);
|
|
2482
3062
|
var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "watchcdp", "setbreakpoint", "stepcode", "watchexpr", "overridescript"]);
|
|
3063
|
+
var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
|
|
3064
|
+
var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
|
|
2483
3065
|
var credentialheaders = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "cookie2", "set-cookie", "api-key", "x-api-key", "x-auth-token", "x-session-token", "proxy-authorization"]);
|
|
2484
3066
|
var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
|
|
2485
3067
|
var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
|
|
@@ -2501,6 +3083,12 @@ function isdebugkind(kind) {
|
|
|
2501
3083
|
function iscdpkind(kind) {
|
|
2502
3084
|
return cdpactions.has(kind);
|
|
2503
3085
|
}
|
|
3086
|
+
function isprofilekind(kind) {
|
|
3087
|
+
return profileractions.has(kind);
|
|
3088
|
+
}
|
|
3089
|
+
function isemulationkind(kind) {
|
|
3090
|
+
return emulationactions.has(kind);
|
|
3091
|
+
}
|
|
2504
3092
|
function actionrisk(kind) {
|
|
2505
3093
|
if (!allowedactions.has(kind)) throw new Error("Unsupported browser action.");
|
|
2506
3094
|
if (sensitiveactions.has(kind)) return "sensitive";
|
|
@@ -3816,6 +4404,28 @@ function debuggerconsentcovers(origin, domains, grants) {
|
|
|
3816
4404
|
if (grants.some((grant) => grant.origin === origin && grant.revokedat !== void 0)) return { allowed: false, reason: `The debugger consent on ${origin} was revoked; approve a new prompt before the devtools protocol runs again.` };
|
|
3817
4405
|
return { allowed: false, reason: `The devtools protocol on ${origin} needs the reviewed debugger consent for ${needed.join(", ")} first; approve the prompt with the domain allowlist shown in the review panel.` };
|
|
3818
4406
|
}
|
|
4407
|
+
function targetgate(input) {
|
|
4408
|
+
const base = debuggate(input.session, input.tabid, input.origin, input.now);
|
|
4409
|
+
if (!base.allowed) return base;
|
|
4410
|
+
for (const target of input.targets) {
|
|
4411
|
+
if (target.kind === "page") continue;
|
|
4412
|
+
const origincheckresult = origincheck(input.session, target.url);
|
|
4413
|
+
if (!origincheckresult.allowed) return { allowed: false, reason: `The ${target.kind} target ${target.url} stays outside the granted origins; profiling refuses to attach.` };
|
|
4414
|
+
}
|
|
4415
|
+
if (input.grants === void 0) return { allowed: true };
|
|
4416
|
+
const consent = debuggerconsentcovers(input.origin, [], input.grants);
|
|
4417
|
+
if (!consent.allowed) return { allowed: false, reason: `The profiling step on ${input.origin} needs the reviewed debugger grant of the origin first; approve the prompt with the profiling derivation shown in the review panel.` };
|
|
4418
|
+
return { allowed: true };
|
|
4419
|
+
}
|
|
4420
|
+
function sourcemapconsentcovers(origin, consents) {
|
|
4421
|
+
const covering = consents.find((consent) => consent.origin === origin && consent.approved === true && consent.revokedat === void 0);
|
|
4422
|
+
if (covering) return { allowed: true };
|
|
4423
|
+
if (consents.some((consent) => consent.origin === origin && consent.revokedat !== void 0)) return { allowed: false, reason: `The source map capture consent on ${origin} was revoked; approve a new prompt before another map file is fetched.` };
|
|
4424
|
+
return { allowed: false, reason: `The source map capture on ${origin} needs the reviewed per origin consent first; approve the prompt shown in the review panel.` };
|
|
4425
|
+
}
|
|
4426
|
+
function traceceilingof(settings) {
|
|
4427
|
+
return settings?.traceceiling;
|
|
4428
|
+
}
|
|
3819
4429
|
function validatebreakpointcondition(condition) {
|
|
3820
4430
|
const expression = condition.trim();
|
|
3821
4431
|
if (expression.length === 0) return { allowed: false, reason: "The breakpoint condition must not be empty." };
|
|
@@ -3842,6 +4452,74 @@ function breakpointbudgetallowed(active, ceiling) {
|
|
|
3842
4452
|
function breakpointceilingof(settings) {
|
|
3843
4453
|
return settings?.breakpointceiling;
|
|
3844
4454
|
}
|
|
4455
|
+
function emugate(input) {
|
|
4456
|
+
const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: "emulate the run tab" });
|
|
4457
|
+
if (!gate.allowed) return gate;
|
|
4458
|
+
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Emulation layers need an approved plan before they apply." };
|
|
4459
|
+
let options = {};
|
|
4460
|
+
try {
|
|
4461
|
+
options = parseoptions(input.step);
|
|
4462
|
+
} catch {
|
|
4463
|
+
options = {};
|
|
4464
|
+
}
|
|
4465
|
+
if (options.reviewed !== true) return { allowed: false, reason: `The ${input.step.kind} layer needs the explicit reviewed flag before any mask applies.` };
|
|
4466
|
+
if (revertplanof(options.revertplan) === void 0) return { allowed: false, reason: `Every ${input.step.kind} layer needs a reviewed revert plan beside it before any mask applies.` };
|
|
4467
|
+
return { allowed: true };
|
|
4468
|
+
}
|
|
4469
|
+
function emulationstackallowed(plan, kind, active) {
|
|
4470
|
+
if (!plan) return { allowed: false, reason: "Layer stacking needs the reviewed plan first." };
|
|
4471
|
+
const listed = plan.steps.filter((step) => step.kind === kind).length;
|
|
4472
|
+
if (active >= listed) return { allowed: false, reason: `The plan lists ${listed} reviewed ${kind} step${listed === 1 ? "" : "s"} and ${active} layer${active === 1 ? "" : "s"} of that family are already active; stacking beyond the reviewed plan is refused.` };
|
|
4473
|
+
return { allowed: true };
|
|
4474
|
+
}
|
|
4475
|
+
function locationconsentgate(origin, latitude, longitude, consents) {
|
|
4476
|
+
if (consents.some((consent) => consent.origin === origin && consent.revokedat !== void 0)) return { allowed: false, reason: `The location consent on ${origin} was revoked; approve a new prompt before the location override runs again.` };
|
|
4477
|
+
if (locationconsentcovers(origin, latitude, longitude, consents)) return { allowed: true };
|
|
4478
|
+
return { allowed: false, reason: `The location override of ${latitude}, ${longitude} on ${origin} needs the reviewed location consent first; approve the prompt with the coordinates shown in the review panel.` };
|
|
4479
|
+
}
|
|
4480
|
+
function validateemulationgrammar(step, options) {
|
|
4481
|
+
const kind = step.kind;
|
|
4482
|
+
if (revertplanof(options.revertplan) === void 0) return { allowed: false, reason: `Every ${kind} layer needs a reviewed revert plan before any mask applies.` };
|
|
4483
|
+
if (kind === "emulatedevice") {
|
|
4484
|
+
const preset = devicepresetof(options.device);
|
|
4485
|
+
if (!preset) return { allowed: false, reason: "The device layer needs a reviewed preset with a name, positive integer width and height and a positive pixel ratio." };
|
|
4486
|
+
if (options.reload !== void 0 && typeof options.reload !== "boolean") return { allowed: false, reason: "The reviewed reload flag must be a boolean; the page reloads only when the reviewed plan asks." };
|
|
4487
|
+
return { allowed: true };
|
|
4488
|
+
}
|
|
4489
|
+
if (kind === "emulatenetwork") {
|
|
4490
|
+
const preset = networkpresetof(options.network);
|
|
4491
|
+
if (!preset) return { allowed: false, reason: "The network layer needs a reviewed preset with a name and zero or positive latency, download and upload bounds." };
|
|
4492
|
+
if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isFinite(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed offline window must be zero or a positive number of milliseconds with no code ceiling." };
|
|
4493
|
+
return { allowed: true };
|
|
4494
|
+
}
|
|
4495
|
+
if (kind === "emulatelocate") {
|
|
4496
|
+
const preset = locationpresetof(options.location);
|
|
4497
|
+
if (!preset) return { allowed: false, reason: "The location layer needs a reviewed preset with a name, a latitude inside -90 and 90, a longitude inside -180 and 180 and a zero or positive accuracy radius." };
|
|
4498
|
+
if (!locationrangevalid(preset.latitude, preset.longitude)) return { allowed: false, reason: "The reviewed latitude must stay inside -90 and 90 degrees and the longitude inside -180 and 180 degrees." };
|
|
4499
|
+
return { allowed: true };
|
|
4500
|
+
}
|
|
4501
|
+
if (kind === "setuseragent") {
|
|
4502
|
+
const preset = agentpresetof(options.agent);
|
|
4503
|
+
if (!preset) return { allowed: false, reason: "The agent layer needs a reviewed preset with a user agent string of the reviewed grammar, a platform and a non-empty brand list." };
|
|
4504
|
+
if (!agentgrammarvalid(preset.useragent)) return { allowed: false, reason: "The reviewed user agent string must use the reviewed grammar of tokens, separators and version marks without line breaks." };
|
|
4505
|
+
return { allowed: true };
|
|
4506
|
+
}
|
|
4507
|
+
if (kind === "overridepermission") {
|
|
4508
|
+
const grant = permissiongrantof(options.permission);
|
|
4509
|
+
if (!grant) return { allowed: false, reason: `The permission override needs a reviewed name of the browser permission set (${browserpermissions.join(", ")}) and a state of ${permissionstates.join(", ")}.` };
|
|
4510
|
+
void permissiongrade(grant.name);
|
|
4511
|
+
return { allowed: true };
|
|
4512
|
+
}
|
|
4513
|
+
if (kind === "blackboxscripts") {
|
|
4514
|
+
const rules = Array.isArray(options.rules) ? options.rules.flatMap((rule) => {
|
|
4515
|
+
const parsed = blackboxruleof(rule);
|
|
4516
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
4517
|
+
}) : [];
|
|
4518
|
+
if (rules.length === 0) return { allowed: false, reason: "The blackbox layer needs a reviewed non-empty rule list where every pattern names its origin explicitly and carries a trace scope." };
|
|
4519
|
+
return { allowed: true };
|
|
4520
|
+
}
|
|
4521
|
+
return { allowed: true };
|
|
4522
|
+
}
|
|
3845
4523
|
function validatecdpgrammar(step, options) {
|
|
3846
4524
|
const kind = step.kind;
|
|
3847
4525
|
if (kind === "attachcdp") {
|
|
@@ -3908,6 +4586,66 @@ function validatecdpgrammar(step, options) {
|
|
|
3908
4586
|
}
|
|
3909
4587
|
return { allowed: true };
|
|
3910
4588
|
}
|
|
4589
|
+
function validateprofilegrammar(step, options) {
|
|
4590
|
+
const kind = step.kind;
|
|
4591
|
+
if (kind === "measureflow") {
|
|
4592
|
+
if (flowspecof(options.flow) === void 0) return { allowed: false, reason: `The flow measurement needs a reviewed flow spec with its mark prefix, step window and metric list of the reviewed metric set: navigation, paint, lcp, fid, interaction, blocking.` };
|
|
4593
|
+
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
4594
|
+
if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The flow measurement needs a reviewed watch window of zero or more milliseconds." };
|
|
4595
|
+
const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
4596
|
+
if (!budgetcheck.allowed) return budgetcheck;
|
|
4597
|
+
return { allowed: true };
|
|
4598
|
+
}
|
|
4599
|
+
if (kind === "heapshot") {
|
|
4600
|
+
const heap = options.heap && typeof options.heap === "object" && !Array.isArray(options.heap) ? options.heap : {};
|
|
4601
|
+
if (heap.interval !== void 0 && (typeof heap.interval !== "number" || !Number.isFinite(heap.interval) || heap.interval < 0)) return { allowed: false, reason: "The reviewed heap snapshot interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling." };
|
|
4602
|
+
return { allowed: true };
|
|
4603
|
+
}
|
|
4604
|
+
if (kind === "trackmemory") {
|
|
4605
|
+
const growth = options.growth && typeof options.growth === "object" && !Array.isArray(options.growth) ? options.growth : void 0;
|
|
4606
|
+
if (!growth || typeof growth.slope !== "number" || !Number.isFinite(growth.slope) || growth.slope < 0) return { allowed: false, reason: "Memory growth tracking needs the reviewed slope in bytes per millisecond before any sample is flagged." };
|
|
4607
|
+
if (growth.interval !== void 0 && (typeof growth.interval !== "number" || !Number.isFinite(growth.interval) || growth.interval < 0)) return { allowed: false, reason: "The reviewed sampling interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling." };
|
|
4608
|
+
return { allowed: true };
|
|
4609
|
+
}
|
|
4610
|
+
if (kind === "profilecpu") {
|
|
4611
|
+
const profile = options.profile && typeof options.profile === "object" && !Array.isArray(options.profile) ? options.profile : void 0;
|
|
4612
|
+
if (!profile || typeof profile.duration !== "number" || !Number.isFinite(profile.duration) || profile.duration < 0) return { allowed: false, reason: "The cpu profile needs a reviewed duration of zero or more milliseconds." };
|
|
4613
|
+
const budgetcheck = debugwaitbudgetallowed(profile.duration, typeof options.wait === "number" ? options.wait : void 0);
|
|
4614
|
+
if (!budgetcheck.allowed) return budgetcheck;
|
|
4615
|
+
return { allowed: true };
|
|
4616
|
+
}
|
|
4617
|
+
if (kind === "watchshifts") {
|
|
4618
|
+
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
4619
|
+
if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The layout shift watch needs a reviewed observation window of zero or more milliseconds; the window stays a user choice with no code ceiling." };
|
|
4620
|
+
if (options.threshold !== void 0 && (typeof options.threshold !== "number" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: "The reviewed shift score threshold must be zero or a positive number." };
|
|
4621
|
+
const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
4622
|
+
if (!budgetcheck.allowed) return budgetcheck;
|
|
4623
|
+
return { allowed: true };
|
|
4624
|
+
}
|
|
4625
|
+
if (kind === "traceload") {
|
|
4626
|
+
const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
|
|
4627
|
+
if (!trace || !Array.isArray(trace.categories) || trace.categories.length === 0 || !trace.categories.every((category) => typeof category === "string" && tracecategories.includes(category))) return { allowed: false, reason: `The trace record needs a non-empty reviewed category list of the reviewed category grammar: ${tracecategories.join(", ")}.` };
|
|
4628
|
+
if (typeof trace.window !== "number" || !Number.isFinite(trace.window) || trace.window < 0) return { allowed: false, reason: "The trace record needs a reviewed window of zero or more milliseconds and stops at the reviewed window end." };
|
|
4629
|
+
if (trace.exporttarget !== void 0 && trace.exporttarget !== "memory" && trace.exporttarget !== "download") return { allowed: false, reason: "The trace export target must be memory or download." };
|
|
4630
|
+
const budgetcheck = debugwaitbudgetallowed(trace.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
4631
|
+
if (!budgetcheck.allowed) return budgetcheck;
|
|
4632
|
+
return { allowed: true };
|
|
4633
|
+
}
|
|
4634
|
+
if (kind === "annotatetrace" || kind === "replaytrace") {
|
|
4635
|
+
const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
|
|
4636
|
+
if (!trace || typeof trace.traceid !== "string" || !trace.traceid.trim()) return { allowed: false, reason: `The ${kind === "annotatetrace" ? "trace annotation" : "trace replay"} needs the stored trace id of a recorded trace.` };
|
|
4637
|
+
if (kind === "replaytrace") return { allowed: true };
|
|
4638
|
+
if (!Array.isArray(options.annotations) || options.annotations.length === 0 || !options.annotations.every((annotation) => annotationof(annotation) !== void 0)) return { allowed: false, reason: "Exported traces carry their step annotations: every annotation needs a step id, a label and an optional offset from the trace start." };
|
|
4639
|
+
return { allowed: true };
|
|
4640
|
+
}
|
|
4641
|
+
if (kind === "capturesourcemaps") {
|
|
4642
|
+
if (options.scripts !== void 0) {
|
|
4643
|
+
if (!Array.isArray(options.scripts) || options.scripts.length === 0 || !options.scripts.every((url) => typeof url === "string" && ishttpsurl(url))) return { allowed: false, reason: "The source map capture scripts must be a non-empty list of reviewed HTTPS urls." };
|
|
4644
|
+
}
|
|
4645
|
+
return { allowed: true };
|
|
4646
|
+
}
|
|
4647
|
+
return { allowed: true };
|
|
4648
|
+
}
|
|
3911
4649
|
function planallowlist(steps) {
|
|
3912
4650
|
const attach = steps.find((step) => step.kind === "attachcdp");
|
|
3913
4651
|
if (!attach) return void 0;
|
|
@@ -4339,6 +5077,14 @@ function validatestep(step, origin) {
|
|
|
4339
5077
|
const cdpcheck = validatecdpgrammar(step, options);
|
|
4340
5078
|
if (!cdpcheck.allowed) return cdpcheck;
|
|
4341
5079
|
}
|
|
5080
|
+
if (isprofilekind(step.kind)) {
|
|
5081
|
+
const profilecheck = validateprofilegrammar(step, options);
|
|
5082
|
+
if (!profilecheck.allowed) return profilecheck;
|
|
5083
|
+
}
|
|
5084
|
+
if (isemulationkind(step.kind)) {
|
|
5085
|
+
const emulationcheck = validateemulationgrammar(step, options);
|
|
5086
|
+
if (!emulationcheck.allowed) return emulationcheck;
|
|
5087
|
+
}
|
|
4342
5088
|
if (step.kind === "tabcreate") {
|
|
4343
5089
|
if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
|
|
4344
5090
|
if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
|
|
@@ -4472,18 +5218,46 @@ function canexecute(input) {
|
|
|
4472
5218
|
if (input.step.kind === "setbreakpoint") {
|
|
4473
5219
|
const breakpoint = breakpointinputof(cdpoptions.breakpoint);
|
|
4474
5220
|
if (breakpoint) {
|
|
4475
|
-
const
|
|
4476
|
-
if (!
|
|
5221
|
+
const targetgate2 = origincheck(input.session, breakpoint.url);
|
|
5222
|
+
if (!targetgate2.allowed) return targetgate2;
|
|
4477
5223
|
}
|
|
4478
5224
|
}
|
|
4479
5225
|
if (input.step.kind === "overridescript") {
|
|
4480
5226
|
const override = overrideinputof(cdpoptions.override);
|
|
4481
5227
|
if (override) {
|
|
4482
|
-
const
|
|
4483
|
-
if (!
|
|
5228
|
+
const targetgate2 = origincheck(input.session, override.urlpattern);
|
|
5229
|
+
if (!targetgate2.allowed) return targetgate2;
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
if (isprofilekind(input.step.kind)) {
|
|
5234
|
+
let profileoptions = {};
|
|
5235
|
+
try {
|
|
5236
|
+
profileoptions = parseoptions(input.step);
|
|
5237
|
+
} catch {
|
|
5238
|
+
profileoptions = {};
|
|
5239
|
+
}
|
|
5240
|
+
const targets = [
|
|
5241
|
+
...attachtargetof(profileoptions.target) !== void 0 ? [attachtargetof(profileoptions.target)] : [],
|
|
5242
|
+
...Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap((target) => {
|
|
5243
|
+
const parsed = attachtargetof(target);
|
|
5244
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
5245
|
+
}) : []
|
|
5246
|
+
];
|
|
5247
|
+
const targetgatecheck = targetgate({ session: input.session, tabid: input.tabid, origin: input.origin, targets, grants: void 0, now });
|
|
5248
|
+
if (!targetgatecheck.allowed) return targetgatecheck;
|
|
5249
|
+
if (input.step.kind === "capturesourcemaps") {
|
|
5250
|
+
for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {
|
|
5251
|
+
if (typeof url !== "string") continue;
|
|
5252
|
+
const scriptgate = origincheck(input.session, url);
|
|
5253
|
+
if (!scriptgate.allowed) return scriptgate;
|
|
4484
5254
|
}
|
|
4485
5255
|
}
|
|
4486
5256
|
}
|
|
5257
|
+
if (isemulationkind(input.step.kind)) {
|
|
5258
|
+
const emugatecheck = emugate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
|
|
5259
|
+
if (!emugatecheck.allowed) return emugatecheck;
|
|
5260
|
+
}
|
|
4487
5261
|
if (iscontrolkind(input.step.kind)) {
|
|
4488
5262
|
const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
|
|
4489
5263
|
if (!controlgate.allowed) return controlgate;
|
|
@@ -4529,8 +5303,8 @@ function canexecute(input) {
|
|
|
4529
5303
|
}
|
|
4530
5304
|
const target = controltarget(input.step);
|
|
4531
5305
|
if (target !== void 0) {
|
|
4532
|
-
const
|
|
4533
|
-
if (!
|
|
5306
|
+
const targetgate2 = origincheck(input.session, target);
|
|
5307
|
+
if (!targetgate2.allowed) return targetgate2;
|
|
4534
5308
|
}
|
|
4535
5309
|
}
|
|
4536
5310
|
if (input.step.kind === "extractapi") {
|
|
@@ -4723,9 +5497,20 @@ function recordcdp(progress, planid, stepid, entry, now) {
|
|
|
4723
5497
|
const outcome = { stepid, ok: entry.errorclass === void 0, summary, details: { cdp: entry }, at: now };
|
|
4724
5498
|
return recordoutcome(base, planid, outcome, now);
|
|
4725
5499
|
}
|
|
5500
|
+
function recordprofile(progress, planid, stepid, entry, now) {
|
|
5501
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
5502
|
+
const counts = `${entry.metrics !== void 0 ? `${entry.metrics} metric${entry.metrics === 1 ? "" : "s"}, ` : ""}${entry.samples !== void 0 ? `${entry.samples} sample${entry.samples === 1 ? "" : "s"}, ` : ""}${entry.nodes !== void 0 ? `${entry.nodes} node${entry.nodes === 1 ? "" : "s"}, ` : ""}${entry.events !== void 0 ? `${entry.events} event${entry.events === 1 ? "" : "s"}, ` : ""}${entry.bytes !== void 0 ? `${entry.bytes} byte${entry.bytes === 1 ? "" : "s"}, ` : ""}${entry.flagged !== void 0 ? `${entry.flagged} flagged step${entry.flagged === 1 ? "" : "s"}, ` : ""}`.replace(/, $/, "");
|
|
5503
|
+
const outcome = { stepid, ok: true, summary: `The profiling ${entry.family} capture ran${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { profile: entry }, at: now };
|
|
5504
|
+
return recordoutcome(base, planid, outcome, now);
|
|
5505
|
+
}
|
|
5506
|
+
function recordemulation(progress, planid, stepid, entry, now) {
|
|
5507
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
5508
|
+
const outcome = { stepid, ok: true, summary: `${entry.reason}: ${entry.applied.length} applied layer${entry.applied.length === 1 ? "" : "s"}${entry.applied.length > 0 ? ` (${entry.applied.join(", ")})` : ""} and ${entry.reverted.length} reverted layer${entry.reverted.length === 1 ? "" : "s"}${entry.reverted.length > 0 ? ` (${entry.reverted.join(", ")})` : ""}.`, details: { emulation: entry }, at: now };
|
|
5509
|
+
return recordoutcome(base, planid, outcome, now);
|
|
5510
|
+
}
|
|
4726
5511
|
|
|
4727
5512
|
// version.ts
|
|
4728
|
-
var packageversion = "1.1.
|
|
5513
|
+
var packageversion = "1.1.48";
|
|
4729
5514
|
|
|
4730
5515
|
// types.ts
|
|
4731
5516
|
var protocolversion = packageversion;
|
|
@@ -4844,6 +5629,81 @@ function parseproposal(value, origin, grants) {
|
|
|
4844
5629
|
if (!allowlistcovers(planallowlist2, method)) throw new Error(`The raw command ${method} stays outside the enabled domain allowlist of the plan attach.`);
|
|
4845
5630
|
}
|
|
4846
5631
|
}
|
|
5632
|
+
if (isprofilekind(step.kind)) {
|
|
5633
|
+
const granted = covered.some((pattern) => {
|
|
5634
|
+
try {
|
|
5635
|
+
return new URL(origin).origin === new URL(pattern).origin;
|
|
5636
|
+
} catch {
|
|
5637
|
+
return false;
|
|
5638
|
+
}
|
|
5639
|
+
});
|
|
5640
|
+
if (!granted) throw new Error(`The ${step.kind} step of ${origin} targets an origin outside the grants.`);
|
|
5641
|
+
let profileoptions = {};
|
|
5642
|
+
try {
|
|
5643
|
+
profileoptions = parseoptions(step);
|
|
5644
|
+
} catch {
|
|
5645
|
+
profileoptions = {};
|
|
5646
|
+
}
|
|
5647
|
+
const targets = [
|
|
5648
|
+
...attachtargetof(profileoptions.target) !== void 0 ? [attachtargetof(profileoptions.target)] : [],
|
|
5649
|
+
...Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap((target2) => {
|
|
5650
|
+
const parsed = attachtargetof(target2);
|
|
5651
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
5652
|
+
}) : []
|
|
5653
|
+
];
|
|
5654
|
+
for (const target2 of targets) {
|
|
5655
|
+
if (target2.kind === "page") continue;
|
|
5656
|
+
const targetgranted = covered.some((pattern) => {
|
|
5657
|
+
try {
|
|
5658
|
+
return new URL(target2.url).origin === new URL(pattern).origin;
|
|
5659
|
+
} catch {
|
|
5660
|
+
return false;
|
|
5661
|
+
}
|
|
5662
|
+
});
|
|
5663
|
+
if (!targetgranted) throw new Error(`The ${target2.kind} target ${target2.url} of the ${step.kind} step stays outside the granted origins.`);
|
|
5664
|
+
}
|
|
5665
|
+
if (step.kind === "traceload") {
|
|
5666
|
+
const trace = profileoptions.trace && typeof profileoptions.trace === "object" && !Array.isArray(profileoptions.trace) ? profileoptions.trace : void 0;
|
|
5667
|
+
const categories = trace !== void 0 && Array.isArray(trace.categories) ? trace.categories : [];
|
|
5668
|
+
if (categories.some((category) => typeof category !== "string" || !tracecategories.includes(category))) throw new Error(`Trace categories outside the reviewed list are refused: ${tracecategories.join(", ")}.`);
|
|
5669
|
+
}
|
|
5670
|
+
if (step.kind === "capturesourcemaps") {
|
|
5671
|
+
for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {
|
|
5672
|
+
if (typeof url !== "string") continue;
|
|
5673
|
+
const scriptgranted = covered.some((pattern) => {
|
|
5674
|
+
try {
|
|
5675
|
+
return new URL(url).origin === new URL(pattern).origin;
|
|
5676
|
+
} catch {
|
|
5677
|
+
return false;
|
|
5678
|
+
}
|
|
5679
|
+
});
|
|
5680
|
+
if (!scriptgranted) throw new Error(`The source map capture of ${url} targets an origin outside the grants.`);
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
if (step.kind === "annotatetrace" && (!Array.isArray(profileoptions.annotations) || profileoptions.annotations.length === 0 || !profileoptions.annotations.every((annotation) => annotationof(annotation) !== void 0))) throw new Error("Trace annotation steps without reviewed step annotations are refused.");
|
|
5684
|
+
}
|
|
5685
|
+
if (isemulationkind(step.kind)) {
|
|
5686
|
+
const granted = covered.some((pattern) => {
|
|
5687
|
+
try {
|
|
5688
|
+
return new URL(origin).origin === new URL(pattern).origin;
|
|
5689
|
+
} catch {
|
|
5690
|
+
return false;
|
|
5691
|
+
}
|
|
5692
|
+
});
|
|
5693
|
+
if (!granted) throw new Error(`The ${step.kind} step of ${origin} targets an origin outside the grants.`);
|
|
5694
|
+
let emulationoptions = {};
|
|
5695
|
+
try {
|
|
5696
|
+
emulationoptions = parseoptions(step);
|
|
5697
|
+
} catch {
|
|
5698
|
+
emulationoptions = {};
|
|
5699
|
+
}
|
|
5700
|
+
if (revertplanof(emulationoptions.revertplan) === void 0) throw new Error("Emulation steps without a reviewed revert plan are refused.");
|
|
5701
|
+
if (step.kind === "emulatelocate") {
|
|
5702
|
+
const preset = locationpresetof(emulationoptions.location);
|
|
5703
|
+
if (preset === void 0) throw new Error("Location emulation needs a reviewed preset with coordinates inside the latitude and longitude ranges.");
|
|
5704
|
+
}
|
|
5705
|
+
if (step.kind === "overridepermission" && permissiongrantof(emulationoptions.permission) === void 0) throw new Error("Permission overrides of unknown permission names are refused.");
|
|
5706
|
+
}
|
|
4847
5707
|
const evaluation = validatestep(step, origin);
|
|
4848
5708
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
4849
5709
|
const target = outboundtarget(step);
|
|
@@ -4918,7 +5778,7 @@ function requestbody(input) {
|
|
|
4918
5778
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
4919
5779
|
}
|
|
4920
5780
|
function outcomeresponse(input) {
|
|
4921
|
-
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {} });
|
|
5781
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {} });
|
|
4922
5782
|
}
|
|
4923
5783
|
function mapresponse(input) {
|
|
4924
5784
|
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
@@ -5033,6 +5893,22 @@ function cdpreport(input) {
|
|
|
5033
5893
|
});
|
|
5034
5894
|
return { version: protocolversion, sessions: input.sessions, commands: input.commands, events: input.events, breakpoints: input.breakpoints, pauses: input.pauses, watches: input.watches, overrides, grants };
|
|
5035
5895
|
}
|
|
5896
|
+
function profilereport(input) {
|
|
5897
|
+
const consents = input.consents.map((consent) => {
|
|
5898
|
+
const { prompt, ...metadata } = consent;
|
|
5899
|
+
void prompt;
|
|
5900
|
+
return metadata;
|
|
5901
|
+
});
|
|
5902
|
+
return { version: protocolversion, flows: input.flows, heaps: input.heaps, samples: input.samples, trends: input.trends, profiles: input.profiles, shifts: input.shifts, traces: input.traces, sourcemaps: input.sourcemaps, consents };
|
|
5903
|
+
}
|
|
5904
|
+
function emulationreport(input) {
|
|
5905
|
+
const consents = input.consents.map((consent) => {
|
|
5906
|
+
const { prompt, ...metadata } = consent;
|
|
5907
|
+
void prompt;
|
|
5908
|
+
return metadata;
|
|
5909
|
+
});
|
|
5910
|
+
return { version: protocolversion, ...input.state !== void 0 ? { state: input.state } : {}, layers: input.state?.layers ?? [], devices: input.devices, networks: input.networks, locations: input.locations, agents: input.agents, blackbox: input.blackbox, permissions: input.permissions, consents };
|
|
5911
|
+
}
|
|
5036
5912
|
|
|
5037
5913
|
// capture.ts
|
|
5038
5914
|
var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
|
|
@@ -6721,7 +7597,7 @@ function stepoptions2(step) {
|
|
|
6721
7597
|
}
|
|
6722
7598
|
async function refreshcapabilities() {
|
|
6723
7599
|
const report = await readcapabilities();
|
|
6724
|
-
const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds] };
|
|
7600
|
+
const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds], emulation: [...emulationkinds] };
|
|
6725
7601
|
await memory.setcapabilities(withmedia);
|
|
6726
7602
|
return withmedia;
|
|
6727
7603
|
}
|
|
@@ -6877,6 +7753,7 @@ function stepauditkind(step, ok) {
|
|
|
6877
7753
|
if (step.kind === "consentpassword") return "consent";
|
|
6878
7754
|
if (step.kind === "handoffcaptcha") return "handoff";
|
|
6879
7755
|
if (iscapturekind(step.kind)) return "capture";
|
|
7756
|
+
if (isprofilekind(step.kind)) return "profile";
|
|
6880
7757
|
if (ismediakind(step.kind)) return "media";
|
|
6881
7758
|
if (isfileskind(step.kind)) {
|
|
6882
7759
|
if (step.kind === "interceptmime") return "intercept";
|
|
@@ -7159,6 +8036,10 @@ async function tracktabupdate(tabid2, changeinfo) {
|
|
|
7159
8036
|
if (active.session.detachedat !== void 0 || active.session.tabid !== tabid2) continue;
|
|
7160
8037
|
await detachcdpforrun(runid, `the run tab navigated to ${url}`).catch(() => {
|
|
7161
8038
|
});
|
|
8039
|
+
await stopprofileinstrumentsforrun(runid, `the run tab navigated to ${url}`).catch(() => {
|
|
8040
|
+
});
|
|
8041
|
+
await revertemulationforrun(runid, `the run tab navigated to ${url}`).catch(() => {
|
|
8042
|
+
});
|
|
7162
8043
|
}
|
|
7163
8044
|
return;
|
|
7164
8045
|
}
|
|
@@ -7184,6 +8065,10 @@ chrome.tabs.onActivated.addListener((activeinfo) => {
|
|
|
7184
8065
|
void recordtabwatchevent("activated", activeinfo.tabId);
|
|
7185
8066
|
});
|
|
7186
8067
|
chrome.tabs.onRemoved.addListener((tabid2) => {
|
|
8068
|
+
for (const [runid, state] of [...activeemulation.entries()]) {
|
|
8069
|
+
if (state.tabid === tabid2) void revertemulationforrun(runid, `the run tab ${tabid2} dropped`, tabid2).catch(() => {
|
|
8070
|
+
});
|
|
8071
|
+
}
|
|
7187
8072
|
const url = lastknownurls.get(tabid2);
|
|
7188
8073
|
const title = lastknowntitles.get(tabid2) ?? "";
|
|
7189
8074
|
const windowid = 0;
|
|
@@ -10001,11 +10886,24 @@ async function executecdpstep(step, session, plan, tabid2, origin) {
|
|
|
10001
10886
|
if (!output.ok) return output;
|
|
10002
10887
|
const record2 = attachcdpsession({ id: sessionid, runid: plan.id, stepid: step.id, tabid: tabid2, origin, domains, now: Date.now(), debuggerversion: cdpderivation });
|
|
10003
10888
|
cdpstateof(plan.id, record2, teardown);
|
|
10889
|
+
const attachtargets = profiletargetsof(options);
|
|
10890
|
+
const flattened = [];
|
|
10891
|
+
if (attachtargets.length > 0) {
|
|
10892
|
+
const targetgatecheck = targetgate({ session, tabid: tabid2, origin, targets: attachtargets, grants, now: Date.now() });
|
|
10893
|
+
if (!targetgatecheck.allowed) throw new Error(targetgatecheck.reason ?? "The attach target stays outside the profiling target gate.");
|
|
10894
|
+
attachtargets.forEach((target, index) => {
|
|
10895
|
+
flattened.push({ kind: target.kind, url: target.url, sessionid: `${sessionid}-${index + 1}`, attachedat: Date.now() });
|
|
10896
|
+
if (target.kind === "serviceworker") {
|
|
10897
|
+
void memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "info", source: "cdp", message: `Service worker target ${target.url} attached through the page controller state; the worker console and network flow into the run timeline as derived entries because no debugger permission exists in the manifest.` });
|
|
10898
|
+
}
|
|
10899
|
+
});
|
|
10900
|
+
activeprofiletargets.set(plan.id, { runid: plan.id, targets: flattened });
|
|
10901
|
+
}
|
|
10004
10902
|
await memory.setcdpsession(record2);
|
|
10005
10903
|
await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "attach", domains: domains.length }, Date.now()));
|
|
10006
|
-
await audit("debugger", `Attached the devtools session ${sessionid} to the run tab ${tabid2} of ${origin} with the reviewed domains ${domains.join(", ")} enabled and the debugger version ${cdpderivation}; the teardown plan reverts ${teardown?.revertsteps.length ?? 0} step${(teardown?.revertsteps.length ?? 0) === 1 ? "" : "s"} with the ${teardown?.resumepolicy ?? "ask"} resume policy
|
|
10904
|
+
await audit("debugger", `Attached the devtools session ${sessionid} to the run tab ${tabid2} of ${origin} with the reviewed domains ${domains.join(", ")} enabled and the debugger version ${cdpderivation}; the teardown plan reverts ${teardown?.revertsteps.length ?? 0} step${(teardown?.revertsteps.length ?? 0) === 1 ? "" : "s"} with the ${teardown?.resumepolicy ?? "ask"} resume policy.${flattened.length > 0 ? ` The attach flattened ${flattened.length} sub session${flattened.length === 1 ? "" : "s"} for nested target access of ${flattened.map((target) => `${target.kind} ${target.url}`).join(", ")}.` : ""}`, extra);
|
|
10007
10905
|
await refreshbadge();
|
|
10008
|
-
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, session: record2, cdp: { sessionid, state: "attached", commandids: [] } } };
|
|
10906
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, session: record2, ...flattened.length > 0 ? { targets: flattened } : {}, cdp: { sessionid, state: "attached", commandids: [] } } };
|
|
10009
10907
|
}
|
|
10010
10908
|
if (step.kind === "detachcdp") {
|
|
10011
10909
|
if (!active) throw new Error("No attached devtools session covers this run.");
|
|
@@ -10147,6 +11045,275 @@ async function executecdpstep(step, session, plan, tabid2, origin) {
|
|
|
10147
11045
|
}
|
|
10148
11046
|
return { ok: false, summary: "The devtools step is not part of the instrumented family." };
|
|
10149
11047
|
}
|
|
11048
|
+
var activeprofiletargets = /* @__PURE__ */ new Map();
|
|
11049
|
+
var activememorytrackers = /* @__PURE__ */ new Map();
|
|
11050
|
+
var lastheapshots = /* @__PURE__ */ new Map();
|
|
11051
|
+
function profiletargetsof(options) {
|
|
11052
|
+
const single = attachtargetof(options.target);
|
|
11053
|
+
const listed = Array.isArray(options.attachtargets) ? options.attachtargets.flatMap((target) => {
|
|
11054
|
+
const parsed = attachtargetof(target);
|
|
11055
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
11056
|
+
}) : [];
|
|
11057
|
+
return [...single !== void 0 ? [single] : [], ...listed];
|
|
11058
|
+
}
|
|
11059
|
+
async function stopprofileinstrumentsforrun(runid, reason) {
|
|
11060
|
+
const targets = activeprofiletargets.get(runid);
|
|
11061
|
+
if (targets) {
|
|
11062
|
+
activeprofiletargets.delete(runid);
|
|
11063
|
+
await audit("profile", `Detached ${targets.targets.length} profiling target${targets.targets.length === 1 ? "" : "s"} at ${reason}; the flattened sub sessions close with the parent session.`, { planid: runid }).catch(() => void 0);
|
|
11064
|
+
}
|
|
11065
|
+
const tracker = activememorytrackers.get(runid);
|
|
11066
|
+
if (tracker) {
|
|
11067
|
+
activememorytrackers.delete(runid);
|
|
11068
|
+
const samples = await memory.listgrowsamples(runid).catch(() => []);
|
|
11069
|
+
if (samples.length > 0) {
|
|
11070
|
+
const trend = growthtrend({ runid, samples, slope: tracker.slope, now: Date.now() });
|
|
11071
|
+
await memory.settrend(trend).catch(() => void 0);
|
|
11072
|
+
}
|
|
11073
|
+
await audit("profile", `Closed the memory growth tracker of ${samples.length} sample${samples.length === 1 ? "" : "s"} at ${reason}; the computed trend stays stored with its flagged steps.`, { planid: runid }).catch(() => void 0);
|
|
11074
|
+
}
|
|
11075
|
+
lastheapshots.delete(runid);
|
|
11076
|
+
}
|
|
11077
|
+
async function sampleheapforstep(tracker, stepid, tabid2, origin, plan) {
|
|
11078
|
+
const now = Date.now();
|
|
11079
|
+
if (now - tracker.lastsampleat < tracker.interval) return;
|
|
11080
|
+
const samplestep = { id: stepid, kind: "heapshot", summary: "Heap sample beside the step", risk: "read" };
|
|
11081
|
+
const output = await dispatchpagestep(samplestep, tabid2, origin, plan);
|
|
11082
|
+
const details = output?.details;
|
|
11083
|
+
if (!output?.ok || typeof details?.usedbytes !== "number") return;
|
|
11084
|
+
const sample = growsampleof({ id: randomid(), runid: tracker.runid, stepid, usedbytes: details.usedbytes, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, now });
|
|
11085
|
+
await memory.addgrowsample(sample);
|
|
11086
|
+
tracker.lastsampleat = now;
|
|
11087
|
+
const samples = await memory.listgrowsamples(tracker.runid);
|
|
11088
|
+
const trend = growthtrend({ runid: tracker.runid, samples, slope: tracker.slope, now });
|
|
11089
|
+
await memory.settrend(trend);
|
|
11090
|
+
if (trend.flaggedsteps.includes(stepid)) await memory.addtimelineentry({ id: randomid(), runid: tracker.runid, stepid, time: now, level: "warn", source: "longtask", message: `Heap growth of step ${stepid} exceeds the reviewed slope of ${tracker.slope} bytes per millisecond; the step is flagged in the memory trend.` });
|
|
11091
|
+
}
|
|
11092
|
+
async function executeprofilestep(step, session, plan, tabid2, origin) {
|
|
11093
|
+
const options = stepoptions2(step);
|
|
11094
|
+
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
11095
|
+
const targets = profiletargetsof(options);
|
|
11096
|
+
const grants = await memory.getdebuggergrants();
|
|
11097
|
+
const targetgatecheck = targetgate({ session, tabid: tabid2, origin, targets, grants, now: Date.now() });
|
|
11098
|
+
if (!targetgatecheck.allowed) {
|
|
11099
|
+
const consent = debuggerconsentcovers(origin, [], grants);
|
|
11100
|
+
if (!consent.allowed) {
|
|
11101
|
+
const pending = grants.find((grant) => grant.origin === origin && grant.approved === void 0);
|
|
11102
|
+
if (!pending) {
|
|
11103
|
+
const record2 = { id: randomid(), prompt: `Profiling instrumentation on ${origin} for run ${plan.id} through the performance timeline buffers and the injected probes of the page bridge.`, origin, domains: [], consentedat: Date.now() };
|
|
11104
|
+
await memory.setdebuggergrant(record2);
|
|
11105
|
+
await refreshbadge();
|
|
11106
|
+
}
|
|
11107
|
+
throw new Error(`${consent.reason} The prompt is open in the review panel with the profiling derivation shown; approve it and run the step again.`);
|
|
11108
|
+
}
|
|
11109
|
+
throw new Error(targetgatecheck.reason ?? "The profiling step stays outside the profiling target gate.");
|
|
11110
|
+
}
|
|
11111
|
+
if (step.kind === "measureflow") {
|
|
11112
|
+
const spec = flowspecof(options.flow);
|
|
11113
|
+
if (!spec) throw new Error("A reviewed flow spec is required.");
|
|
11114
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The flow measurement returned no result." };
|
|
11115
|
+
if (!output.ok) return output;
|
|
11116
|
+
const entries = detailarray(output.details, "entries").flatMap((entry) => {
|
|
11117
|
+
if (!entry || typeof entry !== "object") return [];
|
|
11118
|
+
const record2 = entry;
|
|
11119
|
+
if (typeof record2.name !== "string" || typeof record2.type !== "string" || typeof record2.start !== "number" || typeof record2.duration !== "number") return [];
|
|
11120
|
+
return [{ name: record2.name, type: record2.type, start: record2.start, duration: record2.duration }];
|
|
11121
|
+
});
|
|
11122
|
+
const metrics = measure({ runid: plan.id, stepid: step.id, spec, entries, now: Date.now() });
|
|
11123
|
+
for (const metric of metrics) await memory.addflowmetric(metric);
|
|
11124
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "flow", metrics: metrics.length, recordids: metrics.map((metric) => metric.id) }, Date.now()));
|
|
11125
|
+
await audit("profile", `Measured the flow ${spec.prefix} of ${origin} over ${spec.steps.length} step${spec.steps.length === 1 ? "" : "s"} with ${metrics.length} metric${metrics.length === 1 ? "" : "s"} of the reviewed set ${spec.metrics.join(", ")}; the marks and performance buffers derive the durations because no debugger permission exists in the manifest.`, extra);
|
|
11126
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, flow: { metrics: metrics.map((metric) => ({ name: metric.name, duration: metric.duration, steps: metric.steps })) }, profile: { metrics: metrics.length, samples: 0 } } };
|
|
11127
|
+
}
|
|
11128
|
+
if (step.kind === "heapshot") {
|
|
11129
|
+
const heap = options.heap && typeof options.heap === "object" && !Array.isArray(options.heap) ? options.heap : {};
|
|
11130
|
+
const interval = typeof heap.interval === "number" ? heap.interval : void 0;
|
|
11131
|
+
if (!heapintervalallowed(lastheapshots.get(plan.id), interval, Date.now())) throw new Error(`The reviewed heap snapshot interval of ${interval} milliseconds has not elapsed since the last snapshot of this run; the frequency bound stays the user choice only.`);
|
|
11132
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The heap snapshot returned no result." };
|
|
11133
|
+
if (!output.ok) return output;
|
|
11134
|
+
const details = output.details;
|
|
11135
|
+
const record2 = heapsnap({ id: randomid(), runid: plan.id, stepid: step.id, origin, usedbytes: typeof details.usedbytes === "number" ? details.usedbytes : 0, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, nodecount: typeof details.nodecount === "number" ? details.nodecount : 0, now: Date.now() });
|
|
11136
|
+
lastheapshots.set(plan.id, record2.capturedat);
|
|
11137
|
+
await memory.setheaprecord(record2);
|
|
11138
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "heap", nodes: record2.nodecount, bytes: record2.bytesize, recordids: [record2.id] }, Date.now()));
|
|
11139
|
+
await audit("profile", `Captured the on demand heap snapshot ${record2.id} of ${origin} with ${record2.bytesize} used bytes and ${record2.nodecount} dom nodes${interval !== void 0 ? ` under the user chosen interval of ${interval} milliseconds` : ""}; the bytes derive from the page performance memory buffer because no heap profiler exists without the debugger permission.`, extra);
|
|
11140
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, heapid: record2.id, heap: record2, profile: { metrics: 0, samples: 0 } } };
|
|
11141
|
+
}
|
|
11142
|
+
if (step.kind === "trackmemory") {
|
|
11143
|
+
const growth = options.growth && typeof options.growth === "object" && !Array.isArray(options.growth) ? options.growth : void 0;
|
|
11144
|
+
const slope = typeof growth?.slope === "number" ? growth.slope : void 0;
|
|
11145
|
+
if (growth === void 0 || slope === void 0) throw new Error("The reviewed growth slope is required.");
|
|
11146
|
+
const interval = typeof growth.interval === "number" ? growth.interval : 0;
|
|
11147
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The memory sample returned no result." };
|
|
11148
|
+
if (!output.ok) return output;
|
|
11149
|
+
const details = output.details;
|
|
11150
|
+
const sample = growsampleof({ id: randomid(), runid: plan.id, stepid: step.id, usedbytes: typeof details.usedbytes === "number" ? details.usedbytes : 0, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, now: Date.now() });
|
|
11151
|
+
await memory.addgrowsample(sample);
|
|
11152
|
+
activememorytrackers.set(plan.id, { runid: plan.id, slope, interval, lastsampleat: sample.at });
|
|
11153
|
+
const samples = await memory.listgrowsamples(plan.id);
|
|
11154
|
+
const trend = growthtrend({ runid: plan.id, samples, slope, now: Date.now() });
|
|
11155
|
+
await memory.settrend(trend);
|
|
11156
|
+
if (trend.flaggedsteps.length > 0) await memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "warn", source: "longtask", message: `Memory growth tracking flagged ${trend.flaggedsteps.length} step${trend.flaggedsteps.length === 1 ? "" : "s"} above the reviewed slope of ${slope} bytes per millisecond: ${trend.flaggedsteps.join(", ")}.` });
|
|
11157
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "memory", samples: trend.samples, flagged: trend.flaggedsteps.length, recordids: [sample.id] }, Date.now()));
|
|
11158
|
+
await audit("profile", `Started the memory growth tracking of ${origin} with the reviewed slope of ${slope} bytes per millisecond and ${trend.samples} stored sample${trend.samples === 1 ? "" : "s"}${trend.flaggedsteps.length > 0 ? `; the steps ${trend.flaggedsteps.join(", ")} exceed the slope` : ""}; a sample is taken beside every following step of the run.`, extra);
|
|
11159
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, trend, profile: { metrics: 0, samples: trend.samples } } };
|
|
11160
|
+
}
|
|
11161
|
+
if (step.kind === "profilecpu") {
|
|
11162
|
+
const duration = options.profile && typeof options.profile === "object" && !Array.isArray(options.profile) ? options.profile.duration : void 0;
|
|
11163
|
+
if (typeof duration !== "number") throw new Error("The reviewed cpu profile duration is required.");
|
|
11164
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The cpu profile returned no result." };
|
|
11165
|
+
if (!output.ok) return output;
|
|
11166
|
+
const samples = detailarray(output.details, "samples").flatMap((sample) => {
|
|
11167
|
+
if (!sample || typeof sample !== "object") return [];
|
|
11168
|
+
const record3 = sample;
|
|
11169
|
+
if (typeof record3.name !== "string" || typeof record3.time !== "number") return [];
|
|
11170
|
+
return [{ name: record3.name, time: record3.time }];
|
|
11171
|
+
});
|
|
11172
|
+
const record2 = cpusnap({ id: randomid(), runid: plan.id, stepid: step.id, origin, duration, samples, now: Date.now() });
|
|
11173
|
+
await memory.setcpuprofile(record2);
|
|
11174
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "cpu", samples: record2.samplecount, recordids: [record2.id] }, Date.now()));
|
|
11175
|
+
await audit("profile", `Profiled the cpu window of ${duration} milliseconds of ${origin} with ${record2.samplecount} sample${record2.samplecount === 1 ? "" : "s"} and ${record2.hotfunctions.length} hot function${record2.hotfunctions.length === 1 ? "" : "s"}${record2.hotfunctions.length > 0 ? ` led by ${record2.hotfunctions[0]}` : ""}; the samples derive from the long task attribution and event timing buffers because no sampling profiler exists without the debugger permission.`, extra);
|
|
11176
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, profileid: record2.id, cpu: { duration: record2.duration, samplecount: record2.samplecount, hotfunctions: record2.hotfunctions }, profile: { metrics: 0, samples: record2.samplecount } } };
|
|
11177
|
+
}
|
|
11178
|
+
if (step.kind === "watchshifts") {
|
|
11179
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The layout shift watch returned no result." };
|
|
11180
|
+
if (!output.ok) return output;
|
|
11181
|
+
const entries = [];
|
|
11182
|
+
for (const shift of detailarray(output.details, "shifts")) {
|
|
11183
|
+
const parsed = shiftentryof({ ...shift && typeof shift === "object" && !Array.isArray(shift) ? shift : {}, id: randomid(), runid: plan.id, stepid: step.id, at: Date.now() });
|
|
11184
|
+
if (parsed !== void 0) entries.push(parsed);
|
|
11185
|
+
}
|
|
11186
|
+
for (const entry of entries) await memory.addshiftentry(entry);
|
|
11187
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "shift", events: entries.length, recordids: entries.map((entry) => entry.id) }, Date.now()));
|
|
11188
|
+
await audit("profile", `Watched the layout shifts of ${origin} for the reviewed window and recorded ${entries.length} shift${entries.length === 1 ? "" : "s"} with scores and impacted selectors from the performance layout-shift buffer.`, extra);
|
|
11189
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, shifts: entries, profile: { metrics: 0, samples: 0 } } };
|
|
11190
|
+
}
|
|
11191
|
+
if (step.kind === "traceload") {
|
|
11192
|
+
const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
|
|
11193
|
+
const categories = trace !== void 0 && Array.isArray(trace.categories) ? trace.categories.filter((category) => typeof category === "string") : [];
|
|
11194
|
+
if (trace === void 0 || categories.length === 0) throw new Error("The reviewed trace categories are required.");
|
|
11195
|
+
const exporttarget = trace.exporttarget === "download" ? "download" : "memory";
|
|
11196
|
+
const started = Date.now();
|
|
11197
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The trace record returned no result." };
|
|
11198
|
+
if (!output.ok) return output;
|
|
11199
|
+
const events = detailarray(output.details, "events").flatMap((event) => {
|
|
11200
|
+
if (!event || typeof event !== "object") return [];
|
|
11201
|
+
const record3 = event;
|
|
11202
|
+
if (typeof record3.name !== "string" || typeof record3.category !== "string" || typeof record3.offset !== "number") return [];
|
|
11203
|
+
return [{ name: record3.name, category: record3.category, offset: record3.offset }];
|
|
11204
|
+
});
|
|
11205
|
+
const record2 = tracestart({ id: randomid(), runid: plan.id, stepid: step.id, origin, categories, now: started });
|
|
11206
|
+
const file = tracetofile(record2, events);
|
|
11207
|
+
const ceiling = traceceilingof(await memory.getsettings());
|
|
11208
|
+
if (ceiling !== void 0 && file.bytesize > ceiling) throw new Error(`The derived trace file of ${file.bytesize} bytes exceeds the user configured trace byte ceiling of ${ceiling} bytes; review a wider ceiling or a narrower category list.`);
|
|
11209
|
+
const ended = { ...record2, endedat: Date.now(), bytesize: file.bytesize, events: file.events };
|
|
11210
|
+
await memory.settracerecord(ended);
|
|
11211
|
+
await memory.settracefile(record2.id, file.content);
|
|
11212
|
+
let exported = false;
|
|
11213
|
+
if (exporttarget === "download") {
|
|
11214
|
+
const granted = await chrome.permissions.contains({ permissions: ["downloads"] }).catch(() => false);
|
|
11215
|
+
if (!granted) throw new Error("The trace export needs the downloads capability; request it from the review panel.");
|
|
11216
|
+
const dataurl = `data:application/json;base64,${btoa(file.content)}`;
|
|
11217
|
+
await chrome.downloads.download({ url: dataurl, filename: `devthink-trace-${record2.id}.json` });
|
|
11218
|
+
exported = true;
|
|
11219
|
+
await memory.settracerecord({ ...ended, exportedat: Date.now() });
|
|
11220
|
+
}
|
|
11221
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "trace", events: file.events, bytes: file.bytesize, recordids: [record2.id] }, Date.now()));
|
|
11222
|
+
await audit("profile", `Recorded the trace ${record2.id} of ${origin} with the reviewed categories ${categories.join(", ")} over ${file.events} event${file.events === 1 ? "" : "s"} and ${file.bytesize} bytes${exported ? " exported through the reviewed download flow" : " kept in memory"}; the file derives from the performance timeline entries and is not the devtools binary trace format.`, extra);
|
|
11223
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, traceid: record2.id, trace: { categories, events: file.events, bytesize: file.bytesize, exported }, profile: { metrics: 0, samples: 0 } } };
|
|
11224
|
+
}
|
|
11225
|
+
if (step.kind === "annotatetrace") {
|
|
11226
|
+
const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
|
|
11227
|
+
const traceid = typeof trace?.traceid === "string" ? trace.traceid : "";
|
|
11228
|
+
const stored = traceid ? (await memory.gettracerecords()).find((item) => item.id === traceid && item.runid === plan.id) : void 0;
|
|
11229
|
+
if (!stored) throw new Error(`No stored trace of this run matches ${traceid || "the given id"}.`);
|
|
11230
|
+
const annotations = (Array.isArray(options.annotations) ? options.annotations : []).flatMap((input) => {
|
|
11231
|
+
const parsed = annotationof(input);
|
|
11232
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
11233
|
+
});
|
|
11234
|
+
if (annotations.length === 0) throw new Error("Exported traces carry their step annotations: at least one reviewed annotation is required.");
|
|
11235
|
+
const timeline = await memory.listtimeline({ runid: plan.id });
|
|
11236
|
+
const annotated = annotatetrace({ trace: stored, annotations, timeline: timeline.map((entry) => ({ stepid: entry.stepid, time: entry.time })), now: Date.now() });
|
|
11237
|
+
await memory.settracerecord(annotated);
|
|
11238
|
+
const content = await memory.gettracefile(traceid);
|
|
11239
|
+
if (content !== void 0) {
|
|
11240
|
+
try {
|
|
11241
|
+
const replay = replaytrace(content);
|
|
11242
|
+
const updated = tracetofile(annotated, replay.events.map((event) => ({ name: event.name, category: event.category, offset: event.offset })));
|
|
11243
|
+
await memory.settracefile(traceid, updated.content);
|
|
11244
|
+
await memory.settracerecord({ ...annotated, bytesize: updated.bytesize });
|
|
11245
|
+
} catch {
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "annotate", events: annotated.annotations.length, recordids: [traceid] }, Date.now()));
|
|
11249
|
+
await audit("profile", `Annotated the trace ${traceid} with ${annotated.annotations.length} step annotation${annotated.annotations.length === 1 ? "" : "s"} aligned with the run timeline entries of steps ${annotated.annotations.map((annotation) => annotation.stepid).join(", ")}.`, extra);
|
|
11250
|
+
return { ok: true, summary: `Annotated the stored trace ${traceid} with ${annotated.annotations.length} step annotation${annotated.annotations.length === 1 ? "" : "s"} aligned with the run timeline.`, details: { traceid, annotations: annotated.annotations, profile: { metrics: 0, samples: 0 } } };
|
|
11251
|
+
}
|
|
11252
|
+
if (step.kind === "replaytrace") {
|
|
11253
|
+
const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
|
|
11254
|
+
const traceid = typeof trace?.traceid === "string" ? trace.traceid : "";
|
|
11255
|
+
const stored = traceid ? (await memory.gettracerecords()).find((item) => item.id === traceid) : void 0;
|
|
11256
|
+
if (!stored) throw new Error(`No stored trace matches ${traceid || "the given id"}.`);
|
|
11257
|
+
const content = await memory.gettracefile(traceid);
|
|
11258
|
+
const replay = content !== void 0 ? replaytrace(content) : { traceid, runid: stored.runid, categories: Object.fromEntries(stored.categories.map((category) => [category, 0])), events: [], annotations: stored.annotations };
|
|
11259
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "replay", events: replay.events.length, recordids: [traceid] }, Date.now()));
|
|
11260
|
+
await audit("profile", `Replayed the stored trace ${traceid} of ${stored.runid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step${content === void 0 ? "; the heavy file bytes had expired after the retention window so the replay renders the surviving metadata and annotations only" : ""}.`, extra);
|
|
11261
|
+
return { ok: true, summary: `Replayed the stored trace ${traceid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step.`, details: { traceid, replay, ...content === void 0 ? { fileexpired: true } : {}, profile: { metrics: 0, samples: 0 } } };
|
|
11262
|
+
}
|
|
11263
|
+
if (step.kind === "capturesourcemaps") {
|
|
11264
|
+
const consents = await memory.getsourcemapconsents();
|
|
11265
|
+
const consent = sourcemapconsentcovers(origin, consents);
|
|
11266
|
+
if (!consent.allowed) {
|
|
11267
|
+
const pending = consents.find((item) => item.origin === origin && item.approved === void 0);
|
|
11268
|
+
if (!pending) {
|
|
11269
|
+
const record2 = { id: randomid(), prompt: `Source map capture on ${origin} for run ${plan.id}: the map files of the loaded same origin scripts are fetched and parsed locally.`, origin, consentedat: Date.now() };
|
|
11270
|
+
await memory.setsourcemapconsent(record2);
|
|
11271
|
+
await refreshbadge();
|
|
11272
|
+
}
|
|
11273
|
+
throw new Error(`${consent.reason} The prompt is open in the review panel; approve it and run the step again.`);
|
|
11274
|
+
}
|
|
11275
|
+
const approved = consents.find((item) => item.origin === origin && item.approved === true && item.revokedat === void 0);
|
|
11276
|
+
if (approved) await memory.setsourcemapconsent({ ...approved, usedat: Date.now() });
|
|
11277
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The source map capture returned no result." };
|
|
11278
|
+
if (!output.ok) return output;
|
|
11279
|
+
const scripts = detailarray(output.details, "scripts").flatMap((script) => {
|
|
11280
|
+
if (!script || typeof script !== "object") return [];
|
|
11281
|
+
const record2 = script;
|
|
11282
|
+
if (typeof record2.url !== "string" || typeof record2.mapurl !== "string") return [];
|
|
11283
|
+
return [{ url: record2.url, source: `//# sourceMappingURL=${record2.mapurl}` }];
|
|
11284
|
+
});
|
|
11285
|
+
const refs = capturesourcemaps({ runid: plan.id, stepid: step.id, origin, scripts, now: Date.now() });
|
|
11286
|
+
const parsedmaps = /* @__PURE__ */ new Map();
|
|
11287
|
+
for (const ref of refs) {
|
|
11288
|
+
let parsed = false;
|
|
11289
|
+
try {
|
|
11290
|
+
const response = await fetch(ref.mapurl, { credentials: "omit" });
|
|
11291
|
+
if (response.ok) {
|
|
11292
|
+
const map = await response.json();
|
|
11293
|
+
if (Array.isArray(map.sources) && typeof map.mappings === "string") {
|
|
11294
|
+
parsed = true;
|
|
11295
|
+
parsedmaps.set(ref.scripturl, { sources: map.sources.filter((source) => typeof source === "string"), mappings: map.mappings });
|
|
11296
|
+
}
|
|
11297
|
+
}
|
|
11298
|
+
} catch {
|
|
11299
|
+
}
|
|
11300
|
+
await memory.setsourcemapref({ ...ref, parsed });
|
|
11301
|
+
}
|
|
11302
|
+
const stack = (Array.isArray(options.stack) ? options.stack : []).flatMap((location2) => {
|
|
11303
|
+
if (!location2 || typeof location2 !== "object") return [];
|
|
11304
|
+
const record2 = location2;
|
|
11305
|
+
if (typeof record2.url !== "string" || typeof record2.line !== "number") return [];
|
|
11306
|
+
const map = parsedmaps.get(record2.url);
|
|
11307
|
+
if (map === void 0) return [];
|
|
11308
|
+
const rewritten = rewritesourcelocation({ url: record2.url, line: record2.line, ...typeof record2.column === "number" ? { column: record2.column } : {} }, map);
|
|
11309
|
+
return rewritten !== void 0 ? [{ from: `${record2.url}:${record2.line}`, to: `${rewritten.url}:${rewritten.line}` }] : [];
|
|
11310
|
+
});
|
|
11311
|
+
await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "sourcemap", events: refs.length, recordids: refs.map((ref) => ref.id) }, Date.now()));
|
|
11312
|
+
await audit("profile", `Captured ${refs.length} source map reference${refs.length === 1 ? "" : "s"} of ${origin} with ${[...parsedmaps.keys()].length} parsed map${[...parsedmaps.keys()].length === 1 ? "" : "s"}${stack.length > 0 ? ` and rewrote ${stack.length} stack location${stack.length === 1 ? "" : "s"}` : ""}; the script sources stayed in the page bridge and only the map urls and parsed state entered the record.`, extra);
|
|
11313
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, sourcemaps: refs.map((ref) => ({ scripturl: ref.scripturl, mapurl: ref.mapurl, parsed: ref.parsed })), ...stack.length > 0 ? { rewritten: stack } : {}, profile: { metrics: 0, samples: 0 } } };
|
|
11314
|
+
}
|
|
11315
|
+
return { ok: false, summary: "The profiling step is not part of the instrumented family." };
|
|
11316
|
+
}
|
|
10150
11317
|
var activerules = /* @__PURE__ */ new Map();
|
|
10151
11318
|
var activeauthflows = /* @__PURE__ */ new Map();
|
|
10152
11319
|
function rulesetof(runid) {
|
|
@@ -10541,11 +11708,116 @@ async function refreshbadge() {
|
|
|
10541
11708
|
const observedrequests = (await memory.getexchanges()).length;
|
|
10542
11709
|
const livechannels = (await memory.getchannels()).filter((channel) => channel.state === "open" || channel.state === "connecting").length;
|
|
10543
11710
|
const activerulescount = [...activerules.values()].reduce((total2, ruleset) => total2 + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0);
|
|
11711
|
+
const locationprompts = (await memory.getlocationconsents()).filter((consent) => consent.approved === void 0).length;
|
|
11712
|
+
const emulatedlayers = [...activeemulation.values()].reduce((total2, state) => total2 + activelayers(state).length, 0);
|
|
10544
11713
|
const tasktabs2 = new Set(badges.map((badge) => badge.tabid)).size;
|
|
10545
|
-
const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + debuggerprompts + observedrequests + livechannels + activerulescount;
|
|
11714
|
+
const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + debuggerprompts + locationprompts + emulatedlayers + observedrequests + livechannels + activerulescount;
|
|
10546
11715
|
await chrome.action.setBadgeText({ text: total > 0 ? String(total) : "" }).catch(() => {
|
|
10547
11716
|
});
|
|
10548
11717
|
}
|
|
11718
|
+
var activeemulation = /* @__PURE__ */ new Map();
|
|
11719
|
+
async function loademulationstate(runid) {
|
|
11720
|
+
const existing = activeemulation.get(runid);
|
|
11721
|
+
if (existing) return existing;
|
|
11722
|
+
const stored = await memory.getemulationstate(runid);
|
|
11723
|
+
if (stored) activeemulation.set(runid, stored);
|
|
11724
|
+
return stored;
|
|
11725
|
+
}
|
|
11726
|
+
async function revertemulationforrun(runid, reason, tabid2) {
|
|
11727
|
+
const state = await loademulationstate(runid);
|
|
11728
|
+
if (!state) return;
|
|
11729
|
+
const now = Date.now();
|
|
11730
|
+
const outcome = revertalllayers(state, now);
|
|
11731
|
+
const target = tabid2 ?? state.tabid;
|
|
11732
|
+
if (outcome.reverted.length > 0 && target !== void 0) {
|
|
11733
|
+
for (const layer of outcome.reverted) {
|
|
11734
|
+
await chrome.scripting.executeScript({ target: { tabId: target }, func: (family, prior) => {
|
|
11735
|
+
const bridge = globalThis.devthinkbridge;
|
|
11736
|
+
if (bridge) bridge.revertemulationlayer(family, prior);
|
|
11737
|
+
}, args: [layer.family, layer.prior] }).catch(() => {
|
|
11738
|
+
});
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11741
|
+
activeemulation.set(runid, outcome.state);
|
|
11742
|
+
await memory.setemulationstate(outcome.state);
|
|
11743
|
+
const session = await memory.getsession();
|
|
11744
|
+
for (const layer of outcome.reverted) {
|
|
11745
|
+
await audit("emulation", `Reverted the ${layer.family} layer ${layer.name} of run ${runid} on ${reason}; the prior state${layer.prior !== void 0 ? " restored exactly" : " needed no page state"} through the revert plan of ${layer.revertplan.join(", ")}.`, { ...session ? { sessionid: session.id } : {}, planid: runid, stepid: layer.stepid });
|
|
11746
|
+
}
|
|
11747
|
+
if (outcome.reverted.length > 0) {
|
|
11748
|
+
const plan = await memory.getplan();
|
|
11749
|
+
if (plan) await memory.setprogress(recordemulation(await memory.getprogress(), plan.id, outcome.reverted[0]?.stepid ?? "", { applied: [], reverted: outcome.reverted.map((layer) => layer.name), reason: `Emulation reverted on ${reason}` }, now));
|
|
11750
|
+
await refreshbadge();
|
|
11751
|
+
}
|
|
11752
|
+
}
|
|
11753
|
+
async function executeemulationstep(step, session, plan, tabid2, origin) {
|
|
11754
|
+
const options = stepoptions2(step);
|
|
11755
|
+
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
11756
|
+
const revertplan = revertplanof(options.revertplan) ?? [];
|
|
11757
|
+
const family = familyofkind(step.kind) ?? "device";
|
|
11758
|
+
const state = await loademulationstate(plan.id) ?? emulationstateof({ runid: plan.id, tabid: tabid2, origin, now: Date.now() });
|
|
11759
|
+
const stacked = activelayers(state).filter((layer2) => layer2.family === family).length;
|
|
11760
|
+
const stackgate2 = emulationstackallowed(plan, step.kind, stacked);
|
|
11761
|
+
if (!stackgate2.allowed) throw new Error(stackgate2.reason);
|
|
11762
|
+
if (step.kind === "emulatelocate") {
|
|
11763
|
+
const preset = locationpresetof(options.location);
|
|
11764
|
+
if (!preset) throw new Error("A reviewed location preset is required before the location override applies.");
|
|
11765
|
+
const consents = await memory.getlocationconsents();
|
|
11766
|
+
const consentgate = locationconsentgate(origin, preset.latitude, preset.longitude, consents);
|
|
11767
|
+
if (!consentgate.allowed) {
|
|
11768
|
+
const pending = consents.find((consent) => consent.origin === origin && consent.approved === void 0 && consent.latitude === preset.latitude && consent.longitude === preset.longitude);
|
|
11769
|
+
if (!pending) {
|
|
11770
|
+
await memory.setlocationconsent({ id: randomid(), prompt: `Location override of ${preset.latitude}, ${preset.longitude} on ${origin} for run ${plan.id} through a page-injected geolocation override; the true browser location stays untouched.`, origin, latitude: preset.latitude, longitude: preset.longitude, consentedat: Date.now() });
|
|
11771
|
+
await refreshbadge();
|
|
11772
|
+
}
|
|
11773
|
+
throw new Error(`${consentgate.reason} The prompt is open in the review panel with the coordinates shown; approve it and run the step again.`);
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
if (step.kind === "blackboxscripts") {
|
|
11777
|
+
const rules = (Array.isArray(options.rules) ? options.rules : []).flatMap((rule) => {
|
|
11778
|
+
const parsed = blackboxruleof(rule);
|
|
11779
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
11780
|
+
});
|
|
11781
|
+
if (rules.length === 0) throw new Error("A reviewed non-empty blackbox rule list is required.");
|
|
11782
|
+
const output2 = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The blackbox registration returned no result." };
|
|
11783
|
+
if (!output2.ok) return output2;
|
|
11784
|
+
await memory.setblackboxrules(origin, rules);
|
|
11785
|
+
const name2 = `${rules.length} blackbox rule${rules.length === 1 ? "" : "s"}`;
|
|
11786
|
+
const layer2 = newlayer({ id: randomid(), runid: plan.id, stepid: step.id, family: "blackbox", name: name2, originscope: origin, revertplan, at: Date.now() });
|
|
11787
|
+
const updated2 = applylayer(state, layer2, Date.now());
|
|
11788
|
+
activeemulation.set(plan.id, updated2);
|
|
11789
|
+
await memory.setemulationstate(updated2);
|
|
11790
|
+
await memory.setprogress(recordemulation(await memory.getprogress(), plan.id, step.id, { applied: [name2], reverted: [], reason: "Blackbox rules registered" }, Date.now()));
|
|
11791
|
+
await audit("emulation", `Marked ${rules.flatMap((rule) => rule.urlpatterns).length} third party url pattern${rules.flatMap((rule) => rule.urlpatterns).length === 1 ? "" : "s"} as blackboxed in the traces of ${origin} with the ${rules.map((rule) => rule.tracescope).join(", ")} scope${revertplan.length > 0 ? ` and the revert plan of ${revertplan.join(", ")}` : ""}; the rules stay read only trace shaping.`, extra);
|
|
11792
|
+
await refreshbadge();
|
|
11793
|
+
return { ok: true, summary: output2.summary, details: { ...output2.details ?? {}, emulation: { applied: [name2], reverted: [] } } };
|
|
11794
|
+
}
|
|
11795
|
+
const priorpermission = step.kind === "overridepermission" ? await chrome.scripting.executeScript({ target: { tabId: tabid2 }, func: (name2) => navigator.permissions?.query({ name: name2 }).then((status) => status.state).catch(() => "prompt"), args: [permissiongrantof(options.permission)?.name ?? ""] }).then((result) => result[0]?.result).catch(() => "prompt") : void 0;
|
|
11796
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The emulation step returned no result." };
|
|
11797
|
+
if (!output.ok) return output;
|
|
11798
|
+
const prior = output.details?.prior;
|
|
11799
|
+
let name = family;
|
|
11800
|
+
if (step.kind === "emulatedevice") name = devicepresetof(options.device)?.name ?? "device";
|
|
11801
|
+
if (step.kind === "emulatenetwork") name = networkpresetof(options.network)?.name ?? "network";
|
|
11802
|
+
if (step.kind === "emulatelocate") name = locationpresetof(options.location)?.name ?? "location";
|
|
11803
|
+
if (step.kind === "setuseragent") name = agentpresetof(options.agent)?.name ?? "agent";
|
|
11804
|
+
if (step.kind === "overridepermission") name = `${permissiongrantof(options.permission)?.name ?? "permission"} ${permissiongrantof(options.permission)?.state ?? ""}`.trim();
|
|
11805
|
+
const layer = newlayer({ id: randomid(), runid: plan.id, stepid: step.id, family, name, originscope: origin, revertplan, ...prior !== void 0 ? { prior } : {}, at: Date.now() });
|
|
11806
|
+
const updated = applylayer(state, layer, Date.now());
|
|
11807
|
+
activeemulation.set(plan.id, updated);
|
|
11808
|
+
await memory.setemulationstate(updated);
|
|
11809
|
+
if (step.kind === "overridepermission") {
|
|
11810
|
+
const grant = permissiongrantof(options.permission);
|
|
11811
|
+
if (grant) await memory.addpermissionoverride({ id: layer.id, runid: plan.id, stepid: step.id, origin, name: grant.name, state: grant.state, priorstate: priorpermission === "granted" || priorpermission === "denied" || priorpermission === "prompt" ? priorpermission : "prompt", appliedat: Date.now() });
|
|
11812
|
+
}
|
|
11813
|
+
if (step.kind === "emulatedevice" && options.reload === true) await chrome.tabs.reload(tabid2).catch(() => {
|
|
11814
|
+
});
|
|
11815
|
+
const grade = step.kind === "overridepermission" ? ` graded ${permissiongrade(permissiongrantof(options.permission)?.name ?? "")} by the reviewed permission name` : "";
|
|
11816
|
+
await memory.setprogress(recordemulation(await memory.getprogress(), plan.id, step.id, { applied: [name], reverted: [], reason: `Emulation layer applied${grade}` }, Date.now()));
|
|
11817
|
+
await audit("emulation", `Applied the ${family} layer ${name} of run ${plan.id} on ${origin}${grade}${prior !== void 0 ? " with the prior page state captured for the exact revert" : ""} and the revert plan of ${revertplan.join(", ")}; the mask is a page-injected override through the scripting api because no debugger or platform permission exists in the manifest.`, extra);
|
|
11818
|
+
await refreshbadge();
|
|
11819
|
+
return { ok: true, summary: output.summary, details: { ...output.details ?? {}, emulation: { applied: [name], reverted: [] } } };
|
|
11820
|
+
}
|
|
10549
11821
|
async function executestep(stepid) {
|
|
10550
11822
|
const session = await memory.getsession();
|
|
10551
11823
|
const plan = await memory.getplan();
|
|
@@ -10616,6 +11888,12 @@ async function executestep(stepid) {
|
|
|
10616
11888
|
} else if (iscdpkind(step.kind)) {
|
|
10617
11889
|
if (!session || !plan || plan.state !== "approved") throw new Error("Devtools protocol kinds refuse to run outside an approved session plan.");
|
|
10618
11890
|
output = await executecdpstep(step, session, plan, tab.id, origin);
|
|
11891
|
+
} else if (isprofilekind(step.kind)) {
|
|
11892
|
+
if (!session || !plan || plan.state !== "approved") throw new Error("Profiling kinds refuse to run outside an approved session plan.");
|
|
11893
|
+
output = await executeprofilestep(step, session, plan, tab.id, origin);
|
|
11894
|
+
} else if (isemulationkind(step.kind)) {
|
|
11895
|
+
if (!session || !plan || plan.state !== "approved") throw new Error("Emulation kinds refuse to run outside an approved session plan.");
|
|
11896
|
+
output = await executeemulationstep(step, session, plan, tab.id, origin);
|
|
10619
11897
|
} else {
|
|
10620
11898
|
if (step.target && freshcheckkinds.has(step.kind)) {
|
|
10621
11899
|
const fresh = await snapshot(tab.id);
|
|
@@ -10662,6 +11940,9 @@ async function executestep(stepid) {
|
|
|
10662
11940
|
const completed = watchwindow ? recordwatchcompletion(base, plan.id, stepid, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, stepid, Date.now());
|
|
10663
11941
|
const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
|
|
10664
11942
|
await memory.setprogress(tracked);
|
|
11943
|
+
const tracker = activememorytrackers.get(plan.id);
|
|
11944
|
+
if (tracker) await sampleheapforstep(tracker, stepid, tab.id, origin, plan).catch(() => {
|
|
11945
|
+
});
|
|
10665
11946
|
await updatetaskbadges(plan, tracked);
|
|
10666
11947
|
await refreshbadge();
|
|
10667
11948
|
if (iscomplete(tracked, plan) && plan.state === "approved") {
|
|
@@ -10675,6 +11956,10 @@ async function executestep(stepid) {
|
|
|
10675
11956
|
});
|
|
10676
11957
|
await detachcdpforrun(plan.id, "plan completion").catch(() => {
|
|
10677
11958
|
});
|
|
11959
|
+
await stopprofileinstrumentsforrun(plan.id, "plan completion").catch(() => {
|
|
11960
|
+
});
|
|
11961
|
+
await revertemulationforrun(plan.id, "plan completion").catch(() => {
|
|
11962
|
+
});
|
|
10678
11963
|
const done = { ...plan, state: "completed", completedat: Date.now() };
|
|
10679
11964
|
await memory.setplan(done);
|
|
10680
11965
|
await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
|
|
@@ -10851,7 +12136,7 @@ async function handlerequest(message, sender) {
|
|
|
10851
12136
|
const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
|
|
10852
12137
|
const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
|
|
10853
12138
|
const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
|
|
10854
|
-
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, socketsactive: activesockets.size, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
|
|
12139
|
+
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
|
|
10855
12140
|
}
|
|
10856
12141
|
case "capabilities":
|
|
10857
12142
|
return refreshcapabilities();
|
|
@@ -11569,6 +12854,103 @@ async function handlerequest(message, sender) {
|
|
|
11569
12854
|
await refreshbadge();
|
|
11570
12855
|
return { revoked, tokenids };
|
|
11571
12856
|
}
|
|
12857
|
+
case "emulationreport": {
|
|
12858
|
+
const plan = await memory.getplan();
|
|
12859
|
+
const storedemulation = plan ? await loademulationstate(plan.id) : void 0;
|
|
12860
|
+
return emulationreport({ ...storedemulation !== void 0 ? { state: storedemulation } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() });
|
|
12861
|
+
}
|
|
12862
|
+
case "revertemulation": {
|
|
12863
|
+
const plan = await memory.getplan();
|
|
12864
|
+
if (!plan) throw new Error("No plan is available for an emulation revert.");
|
|
12865
|
+
const state = await loademulationstate(plan.id);
|
|
12866
|
+
if (!state || activelayers(state).length === 0) throw new Error("No active emulation layer covers this run.");
|
|
12867
|
+
await revertemulationforrun(plan.id, "review panel demand");
|
|
12868
|
+
return { reverted: true, layers: await memory.listlayers(plan.id) };
|
|
12869
|
+
}
|
|
12870
|
+
case "restoreemulation": {
|
|
12871
|
+
const plan = await memory.getplan();
|
|
12872
|
+
if (!plan) throw new Error("No plan is available for an emulation restore.");
|
|
12873
|
+
const stored = await memory.getemulationstate(plan.id);
|
|
12874
|
+
if (!stored || stored.layers.length === 0) throw new Error("No stored emulation layer history covers this run.");
|
|
12875
|
+
activeemulation.set(plan.id, stored);
|
|
12876
|
+
await refreshbadge();
|
|
12877
|
+
await audit("emulation", `Restored the stored emulation state of run ${plan.id} with ${activelayers(stored).length} active layer${activelayers(stored).length === 1 ? "" : "s"} on user demand after the service worker restart; the layer history stayed persisted through the run record.`, { planid: plan.id });
|
|
12878
|
+
return { restored: true, layers: stored.layers };
|
|
12879
|
+
}
|
|
12880
|
+
case "approvelocationconsent": {
|
|
12881
|
+
const inputapprove = message;
|
|
12882
|
+
const records = await memory.getlocationconsents();
|
|
12883
|
+
const record2 = records.find((item) => item.id === inputapprove.id);
|
|
12884
|
+
if (!record2) throw new Error("No location consent prompt matches the id.");
|
|
12885
|
+
const decided = { ...record2, approved: true, usedat: Date.now() };
|
|
12886
|
+
await memory.setlocationconsent(decided);
|
|
12887
|
+
await audit("consent", `Location override consent on ${record2.origin} for ${record2.latitude}, ${record2.longitude} approved from the review panel; the decision persists for those coordinates of that origin.`, { planid: record2.id });
|
|
12888
|
+
await refreshbadge();
|
|
12889
|
+
return { approved: true, origin: record2.origin, latitude: record2.latitude, longitude: record2.longitude };
|
|
12890
|
+
}
|
|
12891
|
+
case "setdevicepreset": {
|
|
12892
|
+
const inputpreset = message;
|
|
12893
|
+
const preset = devicepresetof(inputpreset.device);
|
|
12894
|
+
if (!preset) throw new Error("A reviewed device preset needs a name, positive integer width and height and a positive pixel ratio.");
|
|
12895
|
+
await memory.setdevicepreset(preset);
|
|
12896
|
+
await audit("emulation", `Stored the device preset ${preset.name} of ${preset.width} by ${preset.height} css pixels with pixel ratio ${preset.pixelratio} and the ${preset.mobile ? "mobile" : "desktop"} hint in the user curated library.`, {});
|
|
12897
|
+
return preset;
|
|
12898
|
+
}
|
|
12899
|
+
case "setnetworkpreset": {
|
|
12900
|
+
const inputpreset = message;
|
|
12901
|
+
const preset = networkpresetof(inputpreset.network);
|
|
12902
|
+
if (!preset) throw new Error("A reviewed network preset needs a name and zero or positive latency, download and upload bounds.");
|
|
12903
|
+
await memory.setnetworkpreset(preset);
|
|
12904
|
+
await audit("emulation", `Stored the network preset ${preset.name} with ${preset.latency} milliseconds latency, ${preset.download} and ${preset.upload} kilobit per second bounds${preset.offline ? " and the offline flag" : ""} in the user curated library.`, {});
|
|
12905
|
+
return preset;
|
|
12906
|
+
}
|
|
12907
|
+
case "setlocationpreset": {
|
|
12908
|
+
const inputpreset = message;
|
|
12909
|
+
const preset = locationpresetof(inputpreset.location);
|
|
12910
|
+
if (!preset) throw new Error("A reviewed location preset needs a name, a latitude inside -90 and 90, a longitude inside -180 and 180 and a zero or positive accuracy radius.");
|
|
12911
|
+
await memory.setlocationpreset(preset);
|
|
12912
|
+
await audit("emulation", `Stored the location preset ${preset.name} of ${preset.latitude}, ${preset.longitude} with the ${preset.accuracy} meter accuracy radius in the user curated library.`, {});
|
|
12913
|
+
return preset;
|
|
12914
|
+
}
|
|
12915
|
+
case "setagentpreset": {
|
|
12916
|
+
const inputpreset = message;
|
|
12917
|
+
const preset = agentpresetof(inputpreset.agent);
|
|
12918
|
+
if (!preset) throw new Error("A reviewed agent preset needs a user agent string of the reviewed grammar, a platform and a non-empty brand list.");
|
|
12919
|
+
await memory.setagentpreset(preset);
|
|
12920
|
+
await audit("emulation", `Stored the agent preset ${preset.name} with platform ${preset.platform} and ${preset.brands.length} brand${preset.brands.length === 1 ? "" : "s"} in the user curated library.`, {});
|
|
12921
|
+
return preset;
|
|
12922
|
+
}
|
|
12923
|
+
case "exportpresets": {
|
|
12924
|
+
const session = await memory.getsession();
|
|
12925
|
+
if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Preset exports stay behind the consent gate of an active session.");
|
|
12926
|
+
const granted = await chrome.permissions.contains({ permissions: ["downloads"] }).catch(() => false);
|
|
12927
|
+
if (!granted) throw new Error("The preset export needs the downloads capability; request it from the review panel.");
|
|
12928
|
+
const file = exportpresetlibrary({ devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), now: Date.now() });
|
|
12929
|
+
const dataurl = `data:application/json;base64,${btoa(JSON.stringify(file, null, 2))}`;
|
|
12930
|
+
await chrome.downloads.download({ url: dataurl, filename: `devthink-presets-${Date.now()}.json` });
|
|
12931
|
+
await audit("emulation", `The review panel exported the versioned preset library of ${file.devices.length + file.networks.length + file.locations.length + file.agents.length} preset${file.devices.length + file.networks.length + file.locations.length + file.agents.length === 1 ? "" : "s"} through the reviewed download flow.`, { sessionid: session.id });
|
|
12932
|
+
return { exported: file.devices.length + file.networks.length + file.locations.length + file.agents.length, version: file.version };
|
|
12933
|
+
}
|
|
12934
|
+
case "importpresets": {
|
|
12935
|
+
const inputimport = message;
|
|
12936
|
+
const library = importpresetlibrary(inputimport.file);
|
|
12937
|
+
if (!library) throw new Error("The reviewed preset file carries no valid preset of any family; the import is refused.");
|
|
12938
|
+
for (const preset of library.devices) await memory.setdevicepreset(preset);
|
|
12939
|
+
for (const preset of library.networks) await memory.setnetworkpreset(preset);
|
|
12940
|
+
for (const preset of library.locations) await memory.setlocationpreset(preset);
|
|
12941
|
+
for (const preset of library.agents) await memory.setagentpreset(preset);
|
|
12942
|
+
const session = await memory.getsession();
|
|
12943
|
+
await audit("emulation", `Imported the reviewed preset library version ${library.version} with ${library.devices.length} device, ${library.networks.length} network, ${library.locations.length} location and ${library.agents.length} agent preset${library.devices.length + library.networks.length + library.locations.length + library.agents.length === 1 ? "" : "s"} through review.`, { ...session ? { sessionid: session.id } : {} });
|
|
12944
|
+
return { imported: library.devices.length + library.networks.length + library.locations.length + library.agents.length, version: library.version };
|
|
12945
|
+
}
|
|
12946
|
+
case "setemulationretention": {
|
|
12947
|
+
const inputretention = message;
|
|
12948
|
+
const settings = await memory.getsettings();
|
|
12949
|
+
const retention = typeof inputretention.retention === "number" && Number.isInteger(inputretention.retention) && inputretention.retention >= 0 ? inputretention.retention : void 0;
|
|
12950
|
+
await memory.setsettings({ ...settings, ...retention !== void 0 ? { emulationretention: retention } : {} });
|
|
12951
|
+
await audit("configure", `The user set the reverted emulation layer state retention to ${retention === void 0 ? "keep every prior state" : retention} layer${retention === 1 ? "" : "s"}; the layer history itself always survives.`);
|
|
12952
|
+
return { emulationretention: retention };
|
|
12953
|
+
}
|
|
11572
12954
|
case "revertproxyroute": {
|
|
11573
12955
|
const inputrevert = message;
|
|
11574
12956
|
const plan = await memory.getplan();
|
|
@@ -11687,6 +13069,74 @@ async function handlerequest(message, sender) {
|
|
|
11687
13069
|
if (!plan) throw new Error("No plan is available for a devtools protocol envelope.");
|
|
11688
13070
|
return cdpreport({ sessions: await memory.getcdpsessions(), commands: await memory.getcdpcommands(), events: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watches: await memory.getwatchexpressions(), overrides: await memory.getscriptoverrides(), grants: await memory.getdebuggergrants() });
|
|
11689
13071
|
}
|
|
13072
|
+
case "profilereport": {
|
|
13073
|
+
return profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() });
|
|
13074
|
+
}
|
|
13075
|
+
case "approvesourcemapconsent": {
|
|
13076
|
+
const inputapprove = message;
|
|
13077
|
+
const records = await memory.getsourcemapconsents();
|
|
13078
|
+
const record2 = records.find((item) => item.id === inputapprove.id);
|
|
13079
|
+
if (!record2) throw new Error("No source map capture prompt matches the id.");
|
|
13080
|
+
const decided = { ...record2, approved: true, usedat: Date.now() };
|
|
13081
|
+
await memory.setsourcemapconsent(decided);
|
|
13082
|
+
await audit("consent", `Source map capture on ${record2.origin} approved from the review panel; the decision covers the map files of that origin only.`, { stepid: record2.id });
|
|
13083
|
+
await refreshbadge();
|
|
13084
|
+
return { approved: true, origin: record2.origin };
|
|
13085
|
+
}
|
|
13086
|
+
case "revokesourcemapconsent": {
|
|
13087
|
+
const session = await memory.getsession();
|
|
13088
|
+
const origin = session?.origin;
|
|
13089
|
+
if (!origin) throw new Error("No active session origin covers a source map consent revoke.");
|
|
13090
|
+
const revoked = await memory.revokesourcemapconsents(origin, Date.now());
|
|
13091
|
+
await audit("consent", `The user revoked ${revoked} source map capture consent record${revoked === 1 ? "" : "s"} of ${origin}; the next capture needs a new reviewed prompt.`, { ...session ? { sessionid: session.id } : {} });
|
|
13092
|
+
await refreshbadge();
|
|
13093
|
+
return { revoked };
|
|
13094
|
+
}
|
|
13095
|
+
case "exporttrace": {
|
|
13096
|
+
const session = await memory.getsession();
|
|
13097
|
+
if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Trace exports stay behind the consent gate of an active session.");
|
|
13098
|
+
const inputtrace = message;
|
|
13099
|
+
const traceid = inputtrace.traceid?.trim();
|
|
13100
|
+
if (!traceid) throw new Error("Trace exports need the stored trace id.");
|
|
13101
|
+
const content = await memory.gettracefile(traceid);
|
|
13102
|
+
if (content === void 0) throw new Error("The trace file bytes expired after the retention window; only the metadata survives.");
|
|
13103
|
+
const granted = await chrome.permissions.contains({ permissions: ["downloads"] }).catch(() => false);
|
|
13104
|
+
if (!granted) throw new Error("The trace export needs the downloads capability; request it from the review panel.");
|
|
13105
|
+
const dataurl = `data:application/json;base64,${btoa(content)}`;
|
|
13106
|
+
await chrome.downloads.download({ url: dataurl, filename: `devthink-trace-${traceid}.json` });
|
|
13107
|
+
const stored = (await memory.gettracerecords()).find((trace) => trace.id === traceid);
|
|
13108
|
+
if (stored) await memory.settracerecord({ ...stored, exportedat: Date.now() });
|
|
13109
|
+
await audit("export", `The review panel exported the trace ${traceid} through the reviewed download flow with its ${stored?.annotations.length ?? 0} step annotation${(stored?.annotations.length ?? 0) === 1 ? "" : "s"}.`, { ...session ? { sessionid: session.id } : {} });
|
|
13110
|
+
return { exported: true, traceid };
|
|
13111
|
+
}
|
|
13112
|
+
case "tracereplay": {
|
|
13113
|
+
const inputreplay = message;
|
|
13114
|
+
const traceid = inputreplay.traceid?.trim();
|
|
13115
|
+
if (!traceid) throw new Error("The trace replay needs the stored trace id.");
|
|
13116
|
+
const stored = (await memory.gettracerecords()).find((trace) => trace.id === traceid);
|
|
13117
|
+
if (!stored) throw new Error(`No stored trace matches ${traceid}.`);
|
|
13118
|
+
const content = await memory.gettracefile(traceid);
|
|
13119
|
+
if (content === void 0) throw new Error("The trace file bytes expired after the retention window; only the metadata survives.");
|
|
13120
|
+
const replay = replaytrace(content);
|
|
13121
|
+
await audit("profile", `The review panel replayed the stored trace ${traceid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step.`, { planid: stored.runid });
|
|
13122
|
+
return replay;
|
|
13123
|
+
}
|
|
13124
|
+
case "setprofileretention": {
|
|
13125
|
+
const inputretention = message;
|
|
13126
|
+
const settings = await memory.getsettings();
|
|
13127
|
+
const retention = typeof inputretention.retention === "number" && Number.isInteger(inputretention.retention) && inputretention.retention >= 0 ? inputretention.retention : void 0;
|
|
13128
|
+
await memory.setsettings({ ...settings, ...retention !== void 0 ? { profileretention: retention } : {} });
|
|
13129
|
+
await audit("configure", `The user set the profile retention to ${retention === void 0 ? "keep every heavy artifact" : `${retention} millisecond${retention === 1 ? "" : "s"}`}; the byte and node counts, sample counts, hot functions and step annotations always survive.`);
|
|
13130
|
+
return { profileretention: retention };
|
|
13131
|
+
}
|
|
13132
|
+
case "settraceceiling": {
|
|
13133
|
+
const inputceiling = message;
|
|
13134
|
+
const settings = await memory.getsettings();
|
|
13135
|
+
const ceiling = typeof inputceiling.ceiling === "number" && Number.isInteger(inputceiling.ceiling) && inputceiling.ceiling >= 0 ? inputceiling.ceiling : void 0;
|
|
13136
|
+
await memory.setsettings({ ...settings, ...ceiling !== void 0 ? { traceceiling: ceiling } : {} });
|
|
13137
|
+
await audit("configure", `The user set the trace byte ceiling to ${ceiling === void 0 ? "no ceiling" : `${ceiling} byte${ceiling === 1 ? "" : "s"}`}; an absent value never refuses a trace export.`);
|
|
13138
|
+
return { traceceiling: ceiling };
|
|
13139
|
+
}
|
|
11690
13140
|
case "stop": {
|
|
11691
13141
|
const session = await memory.getsession();
|
|
11692
13142
|
for (const [id, controller] of [...activefetches.entries()]) {
|
|
@@ -11710,16 +13160,28 @@ async function handlerequest(message, sender) {
|
|
|
11710
13160
|
});
|
|
11711
13161
|
await detachcdpforrun(stoppedplan.id, "run cancel").catch(() => {
|
|
11712
13162
|
});
|
|
13163
|
+
await stopprofileinstrumentsforrun(stoppedplan.id, "run cancel").catch(() => {
|
|
13164
|
+
});
|
|
13165
|
+
await revertemulationforrun(stoppedplan.id, "run cancel").catch(() => {
|
|
13166
|
+
});
|
|
11713
13167
|
} else {
|
|
11714
13168
|
await closechannelsforrun("none").catch(() => {
|
|
11715
13169
|
});
|
|
11716
13170
|
await revertcontrolsforrun("none", "run cancel").catch(() => {
|
|
11717
13171
|
});
|
|
13172
|
+
await revertemulationforrun("none", "run cancel").catch(() => {
|
|
13173
|
+
});
|
|
11718
13174
|
}
|
|
11719
13175
|
for (const [runid, active] of [...activecdpsessions.entries()]) {
|
|
11720
13176
|
active.cancelled = true;
|
|
11721
13177
|
await detachcdpforrun(runid, "run cancel").catch(() => {
|
|
11722
13178
|
});
|
|
13179
|
+
await stopprofileinstrumentsforrun(runid, "run cancel").catch(() => {
|
|
13180
|
+
});
|
|
13181
|
+
}
|
|
13182
|
+
for (const runid of [...activeemulation.keys()]) {
|
|
13183
|
+
await revertemulationforrun(runid, "run cancel").catch(() => {
|
|
13184
|
+
});
|
|
11723
13185
|
}
|
|
11724
13186
|
for (const [id, active] of [...activerecordings.entries()]) {
|
|
11725
13187
|
const finished = finishrecording(active.record, Date.now());
|
|
@@ -11751,6 +13213,12 @@ async function reconcilewatches() {
|
|
|
11751
13213
|
}
|
|
11752
13214
|
reconcilewatches().catch(() => {
|
|
11753
13215
|
});
|
|
13216
|
+
async function restoreemulationstate() {
|
|
13217
|
+
const plan = await memory.getplan();
|
|
13218
|
+
if (plan) await loademulationstate(plan.id);
|
|
13219
|
+
}
|
|
13220
|
+
restoreemulationstate().catch(() => {
|
|
13221
|
+
});
|
|
11754
13222
|
chrome.runtime.onConnect.addListener((port) => {
|
|
11755
13223
|
if (port.name !== "devthinksidepanel" || port.sender?.id !== chrome.runtime.id || !port.sender.url?.startsWith(chrome.runtime.getURL(""))) return port.disconnect();
|
|
11756
13224
|
port.onMessage.addListener((message) => {
|