@wenathlan/extension 1.1.34 → 1.1.35
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 +5 -4
- package/dist/index.js +315 -4
- package/dist/index.js.map +2 -2
- package/dist/memory.d.ts +53 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +8 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +23 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +146 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1164 -11
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +331 -12
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +12 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +189 -8
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnOEjO8Z0PDgQyfvawGcaO2j+o0GLCFTLNj7TkYC/Avo9l2NenMRq7gp90Nfd7E9MViv/OMcCKSYZ5unv12QPRtv31C+a5UQWDFAOP/cH5mwMd6hsayElrSoW8ta+FwFqmr9dIFkn7cQEU3YhZr4Gcbs+ycUHOxVgDA4NBKB0rQ6e9VW5LvTw0isRYUrqM+M72vKxHk9zUIYYn/LGPvottKBYi2GLr0PHSeC2UE+Shmq7vcFIXj6hDjvD4kLJ5sKoUllEcZ1TPuBcnHUQ9ndKA5iktXDQOIJCUJmi7a0YJ2PGg7fvpYfT9k0ai/qZ+pIoRfoOEwE01bPoDn7NjeYnNQIDAQAB",
|
|
4
4
|
"name": "Devthink",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.35",
|
|
6
6
|
"description": "A consent-first bridge for reviewed browser-agent tasks.",
|
|
7
7
|
"permissions": [
|
|
8
8
|
"activeTab",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
(() => {
|
|
3
3
|
// policy.ts
|
|
4
|
-
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"]);
|
|
4
|
+
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"]);
|
|
5
5
|
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
|
|
6
|
-
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"]);
|
|
6
|
+
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"]);
|
|
7
7
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
8
8
|
function parseoptions(step) {
|
|
9
9
|
if (step.options === void 0) return {};
|
|
@@ -1135,6 +1135,323 @@
|
|
|
1135
1135
|
return { ok: false, summary: "Unsupported interaction action." };
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
// extension/pagenav.ts
|
|
1139
|
+
function loadphase(readystate) {
|
|
1140
|
+
if (readystate === "interactive") return "interactive";
|
|
1141
|
+
if (readystate === "complete") return "complete";
|
|
1142
|
+
return "loading";
|
|
1143
|
+
}
|
|
1144
|
+
function parseurlpattern(step, key = "urlpattern") {
|
|
1145
|
+
let options = {};
|
|
1146
|
+
try {
|
|
1147
|
+
options = parseoptions(step);
|
|
1148
|
+
} catch {
|
|
1149
|
+
options = {};
|
|
1150
|
+
}
|
|
1151
|
+
const value = options[key];
|
|
1152
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1153
|
+
const pattern = value;
|
|
1154
|
+
if (typeof pattern.url !== "string" || !pattern.url) return null;
|
|
1155
|
+
const mode = pattern.mode === "exact" || pattern.mode === "host" || pattern.mode === "pattern" ? pattern.mode : "prefix";
|
|
1156
|
+
const query = {};
|
|
1157
|
+
if (pattern.query && typeof pattern.query === "object" && !Array.isArray(pattern.query)) {
|
|
1158
|
+
for (const [name, item] of Object.entries(pattern.query)) if (typeof item === "string") query[name] = item;
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
mode,
|
|
1162
|
+
url: pattern.url,
|
|
1163
|
+
...Object.keys(query).length > 0 ? { query } : {},
|
|
1164
|
+
...typeof pattern.fragment === "string" && pattern.fragment ? { fragment: pattern.fragment } : {}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function segmentmatches(pattern, actual) {
|
|
1168
|
+
if (pattern === "*" || pattern === "**") return true;
|
|
1169
|
+
if (!pattern.includes("*")) return pattern === actual;
|
|
1170
|
+
const parts = pattern.split("*");
|
|
1171
|
+
let index = 0;
|
|
1172
|
+
for (let position = 0; position < parts.length; position += 1) {
|
|
1173
|
+
const part = parts[position];
|
|
1174
|
+
if (part === "") continue;
|
|
1175
|
+
const found = actual.indexOf(part, index);
|
|
1176
|
+
if (found < 0) return false;
|
|
1177
|
+
if (position === 0 && found !== 0) return false;
|
|
1178
|
+
index = found + part.length;
|
|
1179
|
+
}
|
|
1180
|
+
const last = parts[parts.length - 1];
|
|
1181
|
+
return last === "" || actual.endsWith(last);
|
|
1182
|
+
}
|
|
1183
|
+
function urlmatches(url, pattern) {
|
|
1184
|
+
let parsed;
|
|
1185
|
+
try {
|
|
1186
|
+
parsed = new URL(url);
|
|
1187
|
+
} catch {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
let expected;
|
|
1191
|
+
try {
|
|
1192
|
+
expected = new URL(pattern.url);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
if (pattern.mode === "exact" && parsed.toString() !== expected.toString()) return false;
|
|
1197
|
+
if (pattern.mode === "prefix" && !parsed.toString().startsWith(pattern.url)) return false;
|
|
1198
|
+
if (pattern.mode === "host" && parsed.origin !== expected.origin) return false;
|
|
1199
|
+
if (pattern.mode === "pattern") {
|
|
1200
|
+
if (parsed.origin !== expected.origin) return false;
|
|
1201
|
+
const expectedsegments = expected.pathname.split("/").filter((segment) => segment !== "");
|
|
1202
|
+
const actualsegments = parsed.pathname.split("/").filter((segment) => segment !== "");
|
|
1203
|
+
if (expectedsegments.includes("**")) {
|
|
1204
|
+
const cut = expectedsegments.indexOf("**");
|
|
1205
|
+
const head = expectedsegments.slice(0, cut);
|
|
1206
|
+
const tail = expectedsegments.slice(cut + 1);
|
|
1207
|
+
if (actualsegments.length < head.length + tail.length) return false;
|
|
1208
|
+
if (!head.every((segment, position) => segmentmatches(segment, actualsegments[position] ?? ""))) return false;
|
|
1209
|
+
if (!tail.every((segment, position) => segmentmatches(segment, actualsegments[actualsegments.length - tail.length + position] ?? ""))) return false;
|
|
1210
|
+
} else if (expectedsegments.length !== actualsegments.length || !expectedsegments.every((segment, position) => segmentmatches(segment, actualsegments[position] ?? ""))) return false;
|
|
1211
|
+
}
|
|
1212
|
+
const values = parsed.searchParams;
|
|
1213
|
+
for (const [name, value] of Object.entries(pattern.query ?? {})) {
|
|
1214
|
+
if (!values.has(name)) return false;
|
|
1215
|
+
if (value !== "*" && values.get(name) !== value) return false;
|
|
1216
|
+
}
|
|
1217
|
+
if (pattern.fragment !== void 0 && parsed.hash.slice(1) !== pattern.fragment) return false;
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
function readquery(url) {
|
|
1221
|
+
const values = {};
|
|
1222
|
+
try {
|
|
1223
|
+
for (const [name, value] of new URL(url).searchParams.entries()) values[name] = value;
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
return values;
|
|
1227
|
+
}
|
|
1228
|
+
function rewritequeryurl(url, set, remove) {
|
|
1229
|
+
const parsed = new URL(url);
|
|
1230
|
+
const before = readquery(url);
|
|
1231
|
+
for (const name of remove) parsed.searchParams.delete(name);
|
|
1232
|
+
for (const [name, value] of Object.entries(set)) parsed.searchParams.set(name, value);
|
|
1233
|
+
parsed.hash = "";
|
|
1234
|
+
return { url: parsed.toString(), before, after: readquery(parsed.toString()), set: Object.keys(set), removed: [...remove] };
|
|
1235
|
+
}
|
|
1236
|
+
function fragmenturl(url, fragment) {
|
|
1237
|
+
const parsed = new URL(url);
|
|
1238
|
+
parsed.hash = fragment.replace(/^#/, "");
|
|
1239
|
+
return parsed.toString();
|
|
1240
|
+
}
|
|
1241
|
+
function matchlinktext(links, text) {
|
|
1242
|
+
const wanted = text.trim().toLowerCase();
|
|
1243
|
+
return links.filter((link) => link.text.trim().toLowerCase() === wanted);
|
|
1244
|
+
}
|
|
1245
|
+
function matchlinkfragment(links, fragment) {
|
|
1246
|
+
const wanted = fragment.trim().replace(/^#/, "");
|
|
1247
|
+
return links.filter((link) => {
|
|
1248
|
+
try {
|
|
1249
|
+
return new URL(link.href, "https://example.invalid").hash.replace(/^#/, "") === wanted;
|
|
1250
|
+
} catch {
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function spauroutechanged(previousurl, currenturl) {
|
|
1256
|
+
if (previousurl === currenturl) return false;
|
|
1257
|
+
try {
|
|
1258
|
+
return new URL(previousurl).origin === new URL(currenturl).origin;
|
|
1259
|
+
} catch {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function wait2(ms) {
|
|
1264
|
+
return new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
1265
|
+
}
|
|
1266
|
+
function stepoptions(step) {
|
|
1267
|
+
try {
|
|
1268
|
+
return parseoptions(step);
|
|
1269
|
+
} catch {
|
|
1270
|
+
return {};
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function collectlinks(root) {
|
|
1274
|
+
return [...root.querySelectorAll("a[href]")].map((element) => ({
|
|
1275
|
+
text: element.textContent?.trim() ?? "",
|
|
1276
|
+
href: element instanceof HTMLAnchorElement ? element.href : element.getAttribute("href") ?? "",
|
|
1277
|
+
selector: element.getAttribute("href") ?? ""
|
|
1278
|
+
}));
|
|
1279
|
+
}
|
|
1280
|
+
function resolvelink(step, root) {
|
|
1281
|
+
const options = stepoptions(step);
|
|
1282
|
+
const allowedorigins = Array.isArray(options.allowedorigins) ? options.allowedorigins.filter((item) => typeof item === "string") : [];
|
|
1283
|
+
const links = collectlinks(root);
|
|
1284
|
+
const matches = options.fragment === true ? matchlinkfragment(links, step.value ?? "") : matchlinktext(links, step.value ?? "");
|
|
1285
|
+
if (matches.length === 0) return { ok: false, summary: `No link matches the reviewed reference "${step.value ?? ""}".` };
|
|
1286
|
+
if (matches.length > 1) return { ok: false, summary: `The reviewed link reference matched ${matches.length} links; review a unique one.` };
|
|
1287
|
+
const element = [...root.querySelectorAll("a[href]")].find((candidate) => (candidate instanceof HTMLAnchorElement ? candidate.href : candidate.getAttribute("href") ?? "") === matches[0]?.href);
|
|
1288
|
+
if (!(element instanceof HTMLAnchorElement)) return { ok: false, summary: "The reviewed link is no longer available." };
|
|
1289
|
+
if (allowedorigins.length > 0) {
|
|
1290
|
+
let origin = "";
|
|
1291
|
+
try {
|
|
1292
|
+
origin = new URL(element.href).origin;
|
|
1293
|
+
} catch {
|
|
1294
|
+
origin = "";
|
|
1295
|
+
}
|
|
1296
|
+
if (!allowedorigins.includes(origin)) return { ok: false, summary: `The reviewed link leaves the session origin grants for ${origin}.` };
|
|
1297
|
+
}
|
|
1298
|
+
return { ok: true, element };
|
|
1299
|
+
}
|
|
1300
|
+
async function runwaitload(step) {
|
|
1301
|
+
const options = stepoptions(step);
|
|
1302
|
+
const timeout = typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0 ? options.timeout : 0;
|
|
1303
|
+
const started = Date.now();
|
|
1304
|
+
for (; ; ) {
|
|
1305
|
+
const phase = loadphase(document.readyState);
|
|
1306
|
+
if (phase === "complete") return { ok: true, summary: `The page load event fired and the document is complete after ${Date.now() - started} milliseconds.`, details: { phase, readystate: document.readyState, waited: Date.now() - started } };
|
|
1307
|
+
if (timeout > 0 && Date.now() - started >= timeout) return { ok: false, summary: `The page did not reach the complete load phase within the reviewed timeout of ${timeout} milliseconds.`, details: { phase, readystate: document.readyState, waited: Date.now() - started } };
|
|
1308
|
+
await wait2(50);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
async function runwaiturl(step) {
|
|
1312
|
+
const options = stepoptions(step);
|
|
1313
|
+
const pattern = parseurlpattern(step);
|
|
1314
|
+
if (!pattern) return { ok: false, summary: "A reviewed urlpattern is required." };
|
|
1315
|
+
const timeout = typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0 ? options.timeout : 0;
|
|
1316
|
+
const poll2 = typeof options.poll === "number" && Number.isFinite(options.poll) && options.poll > 0 ? options.poll : 100;
|
|
1317
|
+
const started = Date.now();
|
|
1318
|
+
for (; ; ) {
|
|
1319
|
+
if (urlmatches(location.href, pattern)) return { ok: true, summary: `The url matched the reviewed ${pattern.mode} pattern after ${Date.now() - started} milliseconds.`, details: { url: location.href, mode: pattern.mode, waited: Date.now() - started } };
|
|
1320
|
+
if (timeout > 0 && Date.now() - started >= timeout) return { ok: false, summary: `The url did not match the reviewed ${pattern.mode} pattern within the reviewed timeout of ${timeout} milliseconds.`, details: { url: location.href, mode: pattern.mode, waited: Date.now() - started } };
|
|
1321
|
+
await wait2(poll2);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async function runfollowlink(step, root) {
|
|
1325
|
+
const resolution = resolvelink(step, root);
|
|
1326
|
+
if (!resolution.ok) return { ok: false, summary: resolution.summary };
|
|
1327
|
+
const href = resolution.element.href;
|
|
1328
|
+
resolution.element.click();
|
|
1329
|
+
return { ok: true, summary: `Followed the reviewed link to ${href}.`, details: { href } };
|
|
1330
|
+
}
|
|
1331
|
+
async function runspanav(step, root) {
|
|
1332
|
+
const options = stepoptions(step);
|
|
1333
|
+
const resolution = resolvelink(step, root);
|
|
1334
|
+
if (!resolution.ok) return { ok: false, summary: resolution.summary };
|
|
1335
|
+
const timeout = typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0 ? options.timeout : 0;
|
|
1336
|
+
const pattern = parseurlpattern(step, "routepattern");
|
|
1337
|
+
const before = location.href;
|
|
1338
|
+
resolution.element.click();
|
|
1339
|
+
const started = Date.now();
|
|
1340
|
+
for (; ; ) {
|
|
1341
|
+
const changed = spauroutechanged(before, location.href);
|
|
1342
|
+
const matched = pattern ? urlmatches(location.href, pattern) : changed;
|
|
1343
|
+
if (matched) return { ok: true, summary: `The single page app route changed to ${location.href} without a reload.`, details: { from: before, to: location.href, waited: Date.now() - started } };
|
|
1344
|
+
if (timeout > 0 && Date.now() - started >= timeout) return { ok: false, summary: `The single page app route did not change within the reviewed timeout of ${timeout} milliseconds.`, details: { from: before, to: location.href, waited: Date.now() - started } };
|
|
1345
|
+
await wait2(50);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
async function runspawait(step) {
|
|
1349
|
+
const options = stepoptions(step);
|
|
1350
|
+
const timeout = typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0 ? options.timeout : 0;
|
|
1351
|
+
const poll2 = typeof options.poll === "number" && Number.isFinite(options.poll) && options.poll > 0 ? options.poll : 100;
|
|
1352
|
+
const pattern = parseurlpattern(step);
|
|
1353
|
+
const before = location.href;
|
|
1354
|
+
const started = Date.now();
|
|
1355
|
+
let detected = false;
|
|
1356
|
+
const onroute = () => {
|
|
1357
|
+
if (spauroutechanged(before, location.href)) detected = true;
|
|
1358
|
+
};
|
|
1359
|
+
window.addEventListener("popstate", onroute);
|
|
1360
|
+
window.addEventListener("hashchange", onroute);
|
|
1361
|
+
try {
|
|
1362
|
+
for (; ; ) {
|
|
1363
|
+
if (pattern ? urlmatches(location.href, pattern) : detected || spauroutechanged(before, location.href)) {
|
|
1364
|
+
return { ok: true, summary: `The single page app url changed to ${location.href} without a reload.`, details: { from: before, to: location.href, waited: Date.now() - started } };
|
|
1365
|
+
}
|
|
1366
|
+
if (timeout > 0 && Date.now() - started >= timeout) return { ok: false, summary: `The single page app url did not change within the reviewed timeout of ${timeout} milliseconds.`, details: { from: before, to: location.href, waited: Date.now() - started } };
|
|
1367
|
+
await wait2(poll2);
|
|
1368
|
+
}
|
|
1369
|
+
} finally {
|
|
1370
|
+
window.removeEventListener("popstate", onroute);
|
|
1371
|
+
window.removeEventListener("hashchange", onroute);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
function runrewritequery(step) {
|
|
1375
|
+
const options = stepoptions(step);
|
|
1376
|
+
const set = {};
|
|
1377
|
+
if (options.set && typeof options.set === "object" && !Array.isArray(options.set)) {
|
|
1378
|
+
for (const [name, value] of Object.entries(options.set)) if (typeof value === "string") set[name] = value;
|
|
1379
|
+
}
|
|
1380
|
+
const remove = Array.isArray(options.remove) ? options.remove.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1381
|
+
const outcome = rewritequeryurl(location.href, set, remove);
|
|
1382
|
+
history.pushState(history.state, document.title, outcome.url);
|
|
1383
|
+
return { ok: true, summary: `Rewrote ${outcome.set.length + outcome.removed.length} query parameter${outcome.set.length + outcome.removed.length === 1 ? "" : "s"}; the url is now ${outcome.url}.`, details: { url: outcome.url, before: outcome.before, after: outcome.after, set: outcome.set, removed: outcome.removed } };
|
|
1384
|
+
}
|
|
1385
|
+
async function runsetfragment(step) {
|
|
1386
|
+
const fragment = (step.value ?? "").replace(/^#/, "");
|
|
1387
|
+
if (!fragment) return { ok: false, summary: "A reviewed fragment is required." };
|
|
1388
|
+
const url = fragmenturl(location.href, fragment);
|
|
1389
|
+
history.pushState(history.state, document.title, url);
|
|
1390
|
+
const anchor = document.getElementById(fragment);
|
|
1391
|
+
anchor?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1392
|
+
return { ok: true, summary: `Set the url fragment to ${fragment} and scrolled to its anchor.`, details: { url, fragment, anchored: Boolean(anchor) } };
|
|
1393
|
+
}
|
|
1394
|
+
function runstopnav() {
|
|
1395
|
+
window.stop();
|
|
1396
|
+
return { ok: true, summary: "Stopped the pending navigation of the page." };
|
|
1397
|
+
}
|
|
1398
|
+
function runprefetch(step) {
|
|
1399
|
+
const options = stepoptions(step);
|
|
1400
|
+
const urls = Array.isArray(options.urls) ? options.urls.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1401
|
+
if (urls.length === 0) return { ok: false, summary: "A reviewed list of prefetch urls is required." };
|
|
1402
|
+
for (const url of urls) {
|
|
1403
|
+
const hint = document.createElement("link");
|
|
1404
|
+
hint.rel = "prefetch";
|
|
1405
|
+
hint.href = url;
|
|
1406
|
+
document.head.append(hint);
|
|
1407
|
+
}
|
|
1408
|
+
return { ok: true, summary: `Queued ${urls.length} prefetch hint${urls.length === 1 ? "" : "s"}.`, details: { urls } };
|
|
1409
|
+
}
|
|
1410
|
+
function runpreconnect(step) {
|
|
1411
|
+
const options = stepoptions(step);
|
|
1412
|
+
const origins = Array.isArray(options.origins) ? options.origins.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1413
|
+
if (origins.length === 0) return { ok: false, summary: "A reviewed list of preconnect origins is required." };
|
|
1414
|
+
for (const origin of origins) {
|
|
1415
|
+
const hint = document.createElement("link");
|
|
1416
|
+
hint.rel = "preconnect";
|
|
1417
|
+
hint.href = origin;
|
|
1418
|
+
document.head.append(hint);
|
|
1419
|
+
}
|
|
1420
|
+
return { ok: true, summary: `Opened ${origins.length} preconnect hint${origins.length === 1 ? "" : "s"}.`, details: { origins } };
|
|
1421
|
+
}
|
|
1422
|
+
function runprintpdf() {
|
|
1423
|
+
window.print();
|
|
1424
|
+
return { ok: true, summary: "Sent the page to the browser print pipeline." };
|
|
1425
|
+
}
|
|
1426
|
+
function runpagenav(step, root = document) {
|
|
1427
|
+
switch (step.kind) {
|
|
1428
|
+
case "waitload":
|
|
1429
|
+
return runwaitload(step);
|
|
1430
|
+
case "waiturl":
|
|
1431
|
+
return runwaiturl(step);
|
|
1432
|
+
case "followlink":
|
|
1433
|
+
return runfollowlink(step, root);
|
|
1434
|
+
case "spanav":
|
|
1435
|
+
return runspanav(step, root);
|
|
1436
|
+
case "spawait":
|
|
1437
|
+
return runspawait(step);
|
|
1438
|
+
case "rewritequery":
|
|
1439
|
+
return runrewritequery(step);
|
|
1440
|
+
case "setfragment":
|
|
1441
|
+
return runsetfragment(step);
|
|
1442
|
+
case "stopnav":
|
|
1443
|
+
return runstopnav();
|
|
1444
|
+
case "prefetch":
|
|
1445
|
+
return runprefetch(step);
|
|
1446
|
+
case "preconnect":
|
|
1447
|
+
return runpreconnect(step);
|
|
1448
|
+
case "printpdf":
|
|
1449
|
+
return runprintpdf();
|
|
1450
|
+
default:
|
|
1451
|
+
return { ok: false, summary: "Unsupported navigation action." };
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1138
1455
|
// extension/pagedialogs.ts
|
|
1139
1456
|
function harvestdialoglog(root) {
|
|
1140
1457
|
const raw = root.documentElement.dataset.devthinkdialoglog;
|
|
@@ -1405,7 +1722,7 @@
|
|
|
1405
1722
|
return { index, origin, sameorigin: origin !== "" && origin === location.origin, width: Math.round(rect.width), height: Math.round(rect.height) };
|
|
1406
1723
|
});
|
|
1407
1724
|
}
|
|
1408
|
-
function
|
|
1725
|
+
function stepoptions2(step) {
|
|
1409
1726
|
try {
|
|
1410
1727
|
return parseoptions(step);
|
|
1411
1728
|
} catch {
|
|
@@ -1449,7 +1766,7 @@
|
|
|
1449
1766
|
return { ok: true, summary: `Detected page language ${outcome.language || "unknown"} from the ${outcome.source} signal.`, details: { language: outcome.language, source: outcome.source } };
|
|
1450
1767
|
}
|
|
1451
1768
|
case "detectlanguage": {
|
|
1452
|
-
const options =
|
|
1769
|
+
const options = stepoptions2(step);
|
|
1453
1770
|
const text = typeof options.text === "string" && options.text ? options.text : target?.textContent ?? root.body?.innerText ?? "";
|
|
1454
1771
|
const routed = taglanguage(clean(text));
|
|
1455
1772
|
return { ok: routed.language !== "", summary: routed.language ? `Detected language ${routed.language} for the extracted text.` : "The extracted text language is undetermined.", details: { language: routed.language, routed } };
|
|
@@ -1878,7 +2195,7 @@
|
|
|
1878
2195
|
if (shape.index > 0) candidates.push({ selector: `${shape.tag}:nth-of-type(${shape.index})`, strategy: "structural", score: 40 });
|
|
1879
2196
|
return candidates.sort((left, right) => right.score - left.score);
|
|
1880
2197
|
}
|
|
1881
|
-
function
|
|
2198
|
+
function wait3(ms) {
|
|
1882
2199
|
return new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
1883
2200
|
}
|
|
1884
2201
|
function quietruleof(step) {
|
|
@@ -1912,7 +2229,7 @@
|
|
|
1912
2229
|
}
|
|
1913
2230
|
});
|
|
1914
2231
|
for (const scope of roots) observer.observe(scope, { childList: true, attributes: true, characterData: true, subtree: true });
|
|
1915
|
-
await
|
|
2232
|
+
await wait3(options.lifetime);
|
|
1916
2233
|
observer.disconnect();
|
|
1917
2234
|
const batches = batchmutations(collected, options.poll);
|
|
1918
2235
|
return { ok: true, summary: `Watched ${collected.length} mutation${collected.length === 1 ? "" : "s"} in ${batches.length} batch${batches.length === 1 ? "" : "es"} for the reviewed lifetime of ${options.lifetime} milliseconds.`, details: { events: collected, batches: batches.length, watchid: options.watchid, lifetime: options.lifetime, scopes: options.scopes ?? [] } };
|
|
@@ -1929,7 +2246,7 @@
|
|
|
1929
2246
|
const onblur = record("blur");
|
|
1930
2247
|
root.addEventListener("focusin", onfocus, true);
|
|
1931
2248
|
root.addEventListener("focusout", onblur, true);
|
|
1932
|
-
await
|
|
2249
|
+
await wait3(options.lifetime);
|
|
1933
2250
|
root.removeEventListener("focusin", onfocus, true);
|
|
1934
2251
|
root.removeEventListener("focusout", onblur, true);
|
|
1935
2252
|
return { ok: true, summary: `Watched ${collected.length} focus change${collected.length === 1 ? "" : "s"} for the reviewed lifetime of ${options.lifetime} milliseconds.`, details: { events: collected, watchid: options.watchid, lifetime: options.lifetime } };
|
|
@@ -1944,7 +2261,7 @@
|
|
|
1944
2261
|
for (const report of bannermatches(collectbannercandidates(root), at)) {
|
|
1945
2262
|
if (!seen.has(report.selector)) seen.set(report.selector, report);
|
|
1946
2263
|
}
|
|
1947
|
-
await
|
|
2264
|
+
await wait3(options.poll);
|
|
1948
2265
|
}
|
|
1949
2266
|
const reports = [...seen.values()];
|
|
1950
2267
|
return { ok: true, summary: `Watched for consent banners for the reviewed lifetime of ${options.lifetime} milliseconds and observed ${reports.length} banner${reports.length === 1 ? "" : "s"}.`, details: { banners: reports, watchid: options.watchid, lifetime: options.lifetime } };
|
|
@@ -1963,7 +2280,7 @@
|
|
|
1963
2280
|
const latest = samples[samples.length - 1];
|
|
1964
2281
|
if (latest && latest.quietfor >= rule.idle) break;
|
|
1965
2282
|
if (timeout > 0 && now - started >= timeout) break;
|
|
1966
|
-
await
|
|
2283
|
+
await wait3(poll2);
|
|
1967
2284
|
}
|
|
1968
2285
|
const outcome = quietresolution(samples, rule.idle, timeout);
|
|
1969
2286
|
return {
|
|
@@ -2042,7 +2359,7 @@
|
|
|
2042
2359
|
}
|
|
2043
2360
|
|
|
2044
2361
|
// extension/pagebridge.ts
|
|
2045
|
-
function
|
|
2362
|
+
function stepoptions3(step) {
|
|
2046
2363
|
if (!step.options) return {};
|
|
2047
2364
|
try {
|
|
2048
2365
|
const parsed = JSON.parse(step.options);
|
|
@@ -2147,6 +2464,7 @@
|
|
|
2147
2464
|
var observationkinds = /* @__PURE__ */ new Set(["a11ytree", "readvisible", "readertree", "readoutline", "readselection", "readopengraph", "readlang", "detectlanguage", "listshadow", "listframes"]);
|
|
2148
2465
|
var detectionkinds = /* @__PURE__ */ new Set(["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection", "readscrollpos"]);
|
|
2149
2466
|
var watchstepkinds = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"]);
|
|
2467
|
+
var navstepkinds = /* @__PURE__ */ new Set(["waitload", "waiturl", "followlink", "spanav", "spawait", "rewritequery", "setfragment", "stopnav", "prefetch", "preconnect", "printpdf"]);
|
|
2150
2468
|
async function performstep(step, expectedorigin, rootdocument = document) {
|
|
2151
2469
|
if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
|
|
2152
2470
|
if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
|
|
@@ -2173,8 +2491,9 @@
|
|
|
2173
2491
|
history.forward();
|
|
2174
2492
|
return { ok: true, summary: "History forward requested." };
|
|
2175
2493
|
}
|
|
2494
|
+
if (navstepkinds.has(step.kind)) return await runpagenav(step, rootdocument);
|
|
2176
2495
|
if (step.kind === "scrollpage") {
|
|
2177
|
-
const options =
|
|
2496
|
+
const options = stepoptions3(step);
|
|
2178
2497
|
window.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
|
|
2179
2498
|
return { ok: true, summary: "Window scrolled by the reviewed amounts." };
|
|
2180
2499
|
}
|
|
@@ -2203,7 +2522,7 @@
|
|
|
2203
2522
|
else {
|
|
2204
2523
|
if (!element) return { ok: false, summary: "Action target is no longer available." };
|
|
2205
2524
|
if (step.kind === "scrollby") {
|
|
2206
|
-
const options =
|
|
2525
|
+
const options = stepoptions3(step);
|
|
2207
2526
|
element.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
|
|
2208
2527
|
result = { ok: true, summary: "Container scrolled by the reviewed amounts." };
|
|
2209
2528
|
} else if (step.kind === "focus") {
|