@quanta-intellect/vessel-browser 0.1.13 → 0.1.15
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 +1 -1
- package/out/main/index.js +1630 -498
- package/out/preload/content-script.js +303 -19
- package/out/preload/index.js +32 -1
- package/out/renderer/assets/{index-DiB_DxLD.js → index-DSaws_sH.js} +429 -410
- package/out/renderer/index.html +2 -1
- package/package.json +1 -1
|
@@ -2123,6 +2123,21 @@ function generateStableSelector(el) {
|
|
|
2123
2123
|
}
|
|
2124
2124
|
return uniqueSelector(document2, parts.join(" > ")) || parts.join(" > ");
|
|
2125
2125
|
}
|
|
2126
|
+
function looksLikeCorrectOption(value) {
|
|
2127
|
+
const text = getTrimmedText(value);
|
|
2128
|
+
if (!text) return void 0;
|
|
2129
|
+
if (/\b(correct|right choice|this is correct|correct answer|pick this|select this|choose this|right answer)\b/i.test(
|
|
2130
|
+
text
|
|
2131
|
+
)) {
|
|
2132
|
+
return true;
|
|
2133
|
+
}
|
|
2134
|
+
if (/\b(wrong|incorrect|not this|don't pick|do not pick|bad option|decoy)\b/i.test(
|
|
2135
|
+
text
|
|
2136
|
+
)) {
|
|
2137
|
+
return false;
|
|
2138
|
+
}
|
|
2139
|
+
return void 0;
|
|
2140
|
+
}
|
|
2126
2141
|
let elementIndex = 0;
|
|
2127
2142
|
const elementSelectors = {};
|
|
2128
2143
|
let indexedElements = /* @__PURE__ */ new WeakMap();
|
|
@@ -2135,7 +2150,8 @@ function collectShadowRoots(root) {
|
|
|
2135
2150
|
const shadowRoots = [];
|
|
2136
2151
|
let walked = 0;
|
|
2137
2152
|
const walk = (node, depth) => {
|
|
2138
|
-
if (depth > MAX_SHADOW_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS)
|
|
2153
|
+
if (depth > MAX_SHADOW_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS)
|
|
2154
|
+
return;
|
|
2139
2155
|
const tw = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
|
|
2140
2156
|
let el = tw.nextNode();
|
|
2141
2157
|
while (el && walked < MAX_WALK_ELEMENTS && shadowRoots.length < MAX_SHADOW_HOSTS) {
|
|
@@ -2306,6 +2322,192 @@ function getOverlayType(el) {
|
|
|
2306
2322
|
}
|
|
2307
2323
|
return "overlay";
|
|
2308
2324
|
}
|
|
2325
|
+
function touchesViewportEdge(rect) {
|
|
2326
|
+
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
2327
|
+
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || 0;
|
|
2328
|
+
const edgePadding = 24;
|
|
2329
|
+
return rect.left <= edgePadding || rect.top <= edgePadding || rect.right >= viewportWidth - edgePadding || rect.bottom >= viewportHeight - edgePadding;
|
|
2330
|
+
}
|
|
2331
|
+
function hasFixedAncestor(el) {
|
|
2332
|
+
let current = el.parentElement;
|
|
2333
|
+
while (current && current !== document.body) {
|
|
2334
|
+
const position = window.getComputedStyle(current).position;
|
|
2335
|
+
if (position === "fixed" || position === "sticky") return true;
|
|
2336
|
+
current = current.parentElement;
|
|
2337
|
+
}
|
|
2338
|
+
return false;
|
|
2339
|
+
}
|
|
2340
|
+
function getEffectiveZIndex(el, style = window.getComputedStyle(el)) {
|
|
2341
|
+
const own = parseZIndex(style);
|
|
2342
|
+
if (own > 0) return own;
|
|
2343
|
+
let current = el.parentElement;
|
|
2344
|
+
while (current && current !== document.body) {
|
|
2345
|
+
const parentZ = parseZIndex(window.getComputedStyle(current));
|
|
2346
|
+
if (parentZ > 0) return parentZ;
|
|
2347
|
+
current = current.parentElement;
|
|
2348
|
+
}
|
|
2349
|
+
return 0;
|
|
2350
|
+
}
|
|
2351
|
+
function looksLikeDrawer(el, style, rect, areaRatio) {
|
|
2352
|
+
if (rect.width < 220 || rect.height < 160 || areaRatio < 0.08) return false;
|
|
2353
|
+
if (!touchesViewportEdge(rect)) return false;
|
|
2354
|
+
if (style.position === "fixed" || style.position === "sticky") {
|
|
2355
|
+
return getEffectiveZIndex(el, style) >= 5;
|
|
2356
|
+
}
|
|
2357
|
+
if (style.position === "absolute" && hasFixedAncestor(el)) {
|
|
2358
|
+
return getEffectiveZIndex(el, style) >= 5;
|
|
2359
|
+
}
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
function looksLikeCartConfirmation(el) {
|
|
2363
|
+
const text = (el.textContent || "").slice(0, 500).toLowerCase();
|
|
2364
|
+
const signals = [
|
|
2365
|
+
"added to cart",
|
|
2366
|
+
"added to bag",
|
|
2367
|
+
"added to basket",
|
|
2368
|
+
"added to your cart",
|
|
2369
|
+
"added to your bag",
|
|
2370
|
+
"added to your basket"
|
|
2371
|
+
];
|
|
2372
|
+
return signals.some((signal) => text.includes(signal));
|
|
2373
|
+
}
|
|
2374
|
+
function getControlTextData(el) {
|
|
2375
|
+
if (el instanceof HTMLInputElement && (el.type === "radio" || el.type === "checkbox")) {
|
|
2376
|
+
const label = getInputLabel(el);
|
|
2377
|
+
if (label) return { text: label, source: "label" };
|
|
2378
|
+
}
|
|
2379
|
+
const aria = getTrimmedText(el.getAttribute("aria-label"));
|
|
2380
|
+
if (aria) return { text: aria, source: "aria-label" };
|
|
2381
|
+
const textContent = getTrimmedText(el.textContent);
|
|
2382
|
+
if (textContent) return { text: textContent, source: "textContent" };
|
|
2383
|
+
if (el instanceof HTMLInputElement) {
|
|
2384
|
+
const value = getTrimmedText(el.value) || getTrimmedText(el.getAttribute("value"));
|
|
2385
|
+
if (value) return { text: value, source: "value" };
|
|
2386
|
+
}
|
|
2387
|
+
const valueAttr = getTrimmedText(el.getAttribute("value"));
|
|
2388
|
+
if (valueAttr) return { text: valueAttr, source: "value" };
|
|
2389
|
+
const title = getTrimmedText(el.getAttribute("title"));
|
|
2390
|
+
if (title) return { text: title, source: "title" };
|
|
2391
|
+
return {};
|
|
2392
|
+
}
|
|
2393
|
+
function getOverlayActionKind(el, label) {
|
|
2394
|
+
const lower = label.toLowerCase();
|
|
2395
|
+
const attrText = [
|
|
2396
|
+
el.getAttribute("id"),
|
|
2397
|
+
typeof el.className === "string" ? el.className : "",
|
|
2398
|
+
el.getAttribute("name"),
|
|
2399
|
+
el.getAttribute("title")
|
|
2400
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2401
|
+
if (el.getAttribute("role") === "radio" || el instanceof HTMLInputElement && el.type === "radio") {
|
|
2402
|
+
return "radio";
|
|
2403
|
+
}
|
|
2404
|
+
if (/close|dismiss|skip|cancel|not now|maybe later|no thanks|reject|decline/.test(
|
|
2405
|
+
lower
|
|
2406
|
+
) || /modal-close|overlay-close/.test(attrText)) {
|
|
2407
|
+
return "dismiss";
|
|
2408
|
+
}
|
|
2409
|
+
if (/accept|agree|allow/.test(lower) && /cookie|consent|privacy|gdpr|onetrust|cookiebot/.test(
|
|
2410
|
+
`${lower} ${attrText}`
|
|
2411
|
+
)) {
|
|
2412
|
+
return "accept";
|
|
2413
|
+
}
|
|
2414
|
+
if (/submit|continue|next|confirm|done|ok|start|proceed/.test(lower)) {
|
|
2415
|
+
return "submit";
|
|
2416
|
+
}
|
|
2417
|
+
return "action";
|
|
2418
|
+
}
|
|
2419
|
+
function getOverlayActionPriority(action) {
|
|
2420
|
+
switch (action.kind) {
|
|
2421
|
+
case "dismiss":
|
|
2422
|
+
return 40;
|
|
2423
|
+
case "accept":
|
|
2424
|
+
return 35;
|
|
2425
|
+
case "submit":
|
|
2426
|
+
return 30;
|
|
2427
|
+
case "radio":
|
|
2428
|
+
return 20;
|
|
2429
|
+
default:
|
|
2430
|
+
return 10;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function collectOverlayRadioOptions(root) {
|
|
2434
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2435
|
+
const options = [];
|
|
2436
|
+
root.querySelectorAll('[role="radio"], input[type="radio"]').forEach((node) => {
|
|
2437
|
+
if (!(node instanceof HTMLElement) || !isElementVisible(node)) return;
|
|
2438
|
+
const data = getControlTextData(node);
|
|
2439
|
+
if (!data.text) return;
|
|
2440
|
+
const selector = generateSelector(node);
|
|
2441
|
+
const key = selector || data.text;
|
|
2442
|
+
if (seen.has(key)) return;
|
|
2443
|
+
seen.add(key);
|
|
2444
|
+
const checked = node.getAttribute("aria-checked") === "true" || (node instanceof HTMLInputElement ? node.checked : false);
|
|
2445
|
+
options.push({
|
|
2446
|
+
label: data.text.slice(0, 100),
|
|
2447
|
+
selector,
|
|
2448
|
+
checked,
|
|
2449
|
+
labelSource: data.source,
|
|
2450
|
+
looksCorrect: looksLikeCorrectOption(data.text)
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
return options.slice(0, 8);
|
|
2454
|
+
}
|
|
2455
|
+
function collectOverlayActions(root) {
|
|
2456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2457
|
+
const actions = [];
|
|
2458
|
+
root.querySelectorAll(
|
|
2459
|
+
'button, [role="button"], a[href], input[type="button"], input[type="submit"], [role="radio"], input[type="radio"]'
|
|
2460
|
+
).forEach((node) => {
|
|
2461
|
+
if (!(node instanceof HTMLElement) || !isElementVisible(node)) return;
|
|
2462
|
+
const selector = generateSelector(node);
|
|
2463
|
+
if (!selector || seen.has(selector)) return;
|
|
2464
|
+
let data = getControlTextData(node);
|
|
2465
|
+
if (!data.text) {
|
|
2466
|
+
const attrText = [
|
|
2467
|
+
node.id,
|
|
2468
|
+
typeof node.className === "string" ? node.className : ""
|
|
2469
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2470
|
+
if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(
|
|
2471
|
+
attrText
|
|
2472
|
+
)) {
|
|
2473
|
+
data = {
|
|
2474
|
+
text: attrText.includes("accept") ? "Accept cookies" : attrText.includes("reject") ? "Reject cookies" : attrText.includes("close") ? "Close" : "Consent button",
|
|
2475
|
+
source: "fallback"
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (!data.text) return;
|
|
2480
|
+
seen.add(selector);
|
|
2481
|
+
actions.push({
|
|
2482
|
+
label: data.text.slice(0, 100),
|
|
2483
|
+
selector,
|
|
2484
|
+
kind: getOverlayActionKind(node, data.text),
|
|
2485
|
+
disabled: isElementDisabled(node)
|
|
2486
|
+
});
|
|
2487
|
+
});
|
|
2488
|
+
return actions.sort((a, b) => getOverlayActionPriority(b) - getOverlayActionPriority(a)).slice(0, 10);
|
|
2489
|
+
}
|
|
2490
|
+
function getOverlayMessage(el) {
|
|
2491
|
+
const heading = el.querySelector("h1, h2, h3, h4, h5, h6");
|
|
2492
|
+
return getTrimmedText(heading?.textContent)?.slice(0, 160) || getNodeTextByIds(el.getAttribute("aria-describedby"))?.slice(0, 160) || getTrimmedText(el.textContent)?.slice(0, 160);
|
|
2493
|
+
}
|
|
2494
|
+
function classifyOverlayKind(args) {
|
|
2495
|
+
const haystack = [
|
|
2496
|
+
args.node.id,
|
|
2497
|
+
typeof args.node.className === "string" ? args.node.className : "",
|
|
2498
|
+
args.node.getAttribute("role"),
|
|
2499
|
+
args.node.getAttribute("aria-label"),
|
|
2500
|
+
args.node.textContent
|
|
2501
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
2502
|
+
if (/cookie|consent|privacy|gdpr|onetrust|cookiebot|trustarc/.test(haystack)) {
|
|
2503
|
+
return "cookie_consent";
|
|
2504
|
+
}
|
|
2505
|
+
if (args.cartConfirm) return "cart_confirmation";
|
|
2506
|
+
if (args.radioOptions.length > 0) return "selection_modal";
|
|
2507
|
+
if (args.drawerLike) return "drawer";
|
|
2508
|
+
if (/alert|warning|notice|success|error/.test(haystack)) return "alert";
|
|
2509
|
+
return "overlay";
|
|
2510
|
+
}
|
|
2309
2511
|
function detectOverlays() {
|
|
2310
2512
|
if (!document.body) return [];
|
|
2311
2513
|
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
@@ -2320,23 +2522,39 @@ function detectOverlays() {
|
|
|
2320
2522
|
if (style.pointerEvents === "none") return;
|
|
2321
2523
|
const rect = node.getBoundingClientRect();
|
|
2322
2524
|
if (!isInViewportRect(rect)) return;
|
|
2323
|
-
const position = style.position;
|
|
2324
|
-
const zIndex = parseZIndex(style);
|
|
2325
|
-
const areaRatio = rect.width * rect.height / viewportArea;
|
|
2326
2525
|
const overlayType = getOverlayType(node);
|
|
2327
2526
|
const dialogLike = overlayType === "dialog" || overlayType === "modal";
|
|
2328
|
-
const
|
|
2329
|
-
|
|
2527
|
+
const areaRatio = rect.width * rect.height / viewportArea;
|
|
2528
|
+
const drawerLike = looksLikeDrawer(node, style, rect, areaRatio);
|
|
2529
|
+
const cartConfirm = !dialogLike && !drawerLike && (style.position === "fixed" || style.position === "sticky" || style.position === "absolute") && rect.width >= 160 && rect.height >= 100 && looksLikeCartConfirmation(node);
|
|
2530
|
+
const blockingSurface = dialogLike || drawerLike || cartConfirm || (style.position === "fixed" || style.position === "sticky") && parseZIndex(style) >= 10 && areaRatio >= 0.3 && getViewportCenterCoverage(rect);
|
|
2531
|
+
if (!blockingSurface && overlayType !== "dialog" && overlayType !== "modal") {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const actions = collectOverlayActions(node);
|
|
2535
|
+
const radioOptions = collectOverlayRadioOptions(node);
|
|
2330
2536
|
seen.add(node);
|
|
2331
2537
|
overlays.push({
|
|
2332
2538
|
element: node,
|
|
2333
2539
|
type: overlayType ?? "overlay",
|
|
2540
|
+
kind: classifyOverlayKind({
|
|
2541
|
+
node,
|
|
2542
|
+
drawerLike,
|
|
2543
|
+
cartConfirm,
|
|
2544
|
+
radioOptions
|
|
2545
|
+
}),
|
|
2334
2546
|
role: getTrimmedText(node.getAttribute("role")) || void 0,
|
|
2335
2547
|
label: getOverlayLabel(node),
|
|
2336
2548
|
selector: generateSelector(node),
|
|
2337
2549
|
text: getTrimmedText(node.textContent)?.slice(0, 160),
|
|
2338
|
-
|
|
2339
|
-
|
|
2550
|
+
message: getOverlayMessage(node),
|
|
2551
|
+
blocksInteraction: blockingSurface,
|
|
2552
|
+
dismissSelector: actions.find((action) => action.kind === "dismiss")?.selector,
|
|
2553
|
+
acceptSelector: actions.find((action) => action.kind === "accept")?.selector,
|
|
2554
|
+
submitSelector: actions.find((action) => action.kind === "submit")?.selector,
|
|
2555
|
+
actions,
|
|
2556
|
+
radioOptions,
|
|
2557
|
+
zIndex: parseZIndex(style)
|
|
2340
2558
|
});
|
|
2341
2559
|
});
|
|
2342
2560
|
return overlays.sort((a, b) => {
|
|
@@ -2499,6 +2717,46 @@ function getInputLabel(el) {
|
|
|
2499
2717
|
}
|
|
2500
2718
|
return getTrimmedText(el.getAttribute("aria-label")) || getNodeTextByIds(el.getAttribute("aria-labelledby")) || getTrimmedText(el.getAttribute("placeholder")) || void 0;
|
|
2501
2719
|
}
|
|
2720
|
+
function getInputLabelWithSource(el) {
|
|
2721
|
+
if (el.id) {
|
|
2722
|
+
const label = document.querySelector(
|
|
2723
|
+
`label[for="${escapeSelectorValue(el.id)}"]`
|
|
2724
|
+
);
|
|
2725
|
+
const text = getTrimmedText(label?.textContent);
|
|
2726
|
+
if (text) return { label: text, source: "label" };
|
|
2727
|
+
}
|
|
2728
|
+
const parentLabel = el.closest("label");
|
|
2729
|
+
if (parentLabel) {
|
|
2730
|
+
const clone = parentLabel.cloneNode(true);
|
|
2731
|
+
clone.querySelectorAll("input, select, textarea").forEach((input) => {
|
|
2732
|
+
input.remove();
|
|
2733
|
+
});
|
|
2734
|
+
const text = getTrimmedText(clone.textContent);
|
|
2735
|
+
if (text) return { label: text, source: "label" };
|
|
2736
|
+
}
|
|
2737
|
+
const ariaLabel = getTrimmedText(el.getAttribute("aria-label"));
|
|
2738
|
+
if (ariaLabel) return { label: ariaLabel, source: "aria-label" };
|
|
2739
|
+
const labelledBy = getNodeTextByIds(el.getAttribute("aria-labelledby"));
|
|
2740
|
+
if (labelledBy) return { label: labelledBy, source: "label" };
|
|
2741
|
+
const placeholder = getTrimmedText(el.getAttribute("placeholder"));
|
|
2742
|
+
if (placeholder) return { label: placeholder, source: "placeholder" };
|
|
2743
|
+
return {};
|
|
2744
|
+
}
|
|
2745
|
+
function getButtonTextWithSource(el) {
|
|
2746
|
+
const textContent = getTrimmedText(el.textContent);
|
|
2747
|
+
if (textContent) return { text: textContent, source: "text" };
|
|
2748
|
+
const value = el instanceof HTMLInputElement || el instanceof HTMLButtonElement ? getTrimmedText(el.value) : getTrimmedText(el.getAttribute("value"));
|
|
2749
|
+
if (value) return { text: value, source: "value" };
|
|
2750
|
+
const ariaLabel = getTrimmedText(el.getAttribute("aria-label"));
|
|
2751
|
+
if (ariaLabel) return { text: ariaLabel, source: "aria-label" };
|
|
2752
|
+
return { text: "Button", source: "text" };
|
|
2753
|
+
}
|
|
2754
|
+
function getParentOverlaySelector(el) {
|
|
2755
|
+
const overlay = activeOverlays.find(
|
|
2756
|
+
(candidate) => candidate.element === el || candidate.element.contains(el) || el instanceof HTMLElement && el.contains(candidate.element)
|
|
2757
|
+
);
|
|
2758
|
+
return overlay?.selector;
|
|
2759
|
+
}
|
|
2502
2760
|
function getElementRole(el) {
|
|
2503
2761
|
return getTrimmedText(el.getAttribute("role")) || (el.tagName.toLowerCase() === "a" ? "link" : el.tagName.toLowerCase() === "button" ? "button" : void 0);
|
|
2504
2762
|
}
|
|
@@ -2534,6 +2792,7 @@ function getAriaBoolean(el, attr) {
|
|
|
2534
2792
|
function buildBaseMetadata(el) {
|
|
2535
2793
|
return {
|
|
2536
2794
|
context: getElementContext(el),
|
|
2795
|
+
parentOverlay: getParentOverlaySelector(el),
|
|
2537
2796
|
selector: generateSelector(el),
|
|
2538
2797
|
index: assignIndex(el),
|
|
2539
2798
|
role: getElementRole(el),
|
|
@@ -2608,12 +2867,15 @@ function extractInteractiveElements() {
|
|
|
2608
2867
|
deepQuerySelectorAll(
|
|
2609
2868
|
'button, [role="button"], input[type="submit"], input[type="button"]'
|
|
2610
2869
|
).forEach((btn) => {
|
|
2611
|
-
const
|
|
2612
|
-
const
|
|
2870
|
+
const { text, source } = getButtonTextWithSource(btn);
|
|
2871
|
+
const role = getElementRole(btn);
|
|
2613
2872
|
elements.push({
|
|
2614
2873
|
type: "button",
|
|
2615
|
-
text: text
|
|
2616
|
-
|
|
2874
|
+
text: text?.slice(0, 100),
|
|
2875
|
+
labelSource: source,
|
|
2876
|
+
...buildBaseMetadata(btn),
|
|
2877
|
+
role,
|
|
2878
|
+
looksCorrect: role === "radio" ? looksLikeCorrectOption(text) : void 0
|
|
2617
2879
|
});
|
|
2618
2880
|
});
|
|
2619
2881
|
deepQuerySelectorAll("a[href]").forEach((link) => {
|
|
@@ -2635,15 +2897,24 @@ function extractInteractiveElements() {
|
|
|
2635
2897
|
).forEach((input) => {
|
|
2636
2898
|
const element = input;
|
|
2637
2899
|
const tag = input.tagName.toLowerCase();
|
|
2900
|
+
const label = getInputLabelWithSource(element);
|
|
2901
|
+
const role = getElementRole(input);
|
|
2902
|
+
const radioText = role === "radio" || element instanceof HTMLInputElement && element.type === "radio" ? getTrimmedText(
|
|
2903
|
+
element.getAttribute("value") || element.getAttribute("aria-label") || label.label
|
|
2904
|
+
) : void 0;
|
|
2638
2905
|
elements.push({
|
|
2639
2906
|
type: tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input",
|
|
2640
|
-
label:
|
|
2907
|
+
label: label.label?.slice(0, 100),
|
|
2908
|
+
labelSource: label.source,
|
|
2641
2909
|
inputType: element.getAttribute("type") || void 0,
|
|
2642
2910
|
placeholder: element.getAttribute("placeholder") || void 0,
|
|
2643
2911
|
required: element.hasAttribute("required") || void 0,
|
|
2644
2912
|
value: getElementValue(element),
|
|
2645
2913
|
options: element instanceof HTMLSelectElement ? getSelectOptions(element) : void 0,
|
|
2646
2914
|
...buildBaseMetadata(input),
|
|
2915
|
+
role,
|
|
2916
|
+
text: radioText?.slice(0, 100),
|
|
2917
|
+
looksCorrect: radioText || label.label ? looksLikeCorrectOption(radioText || label.label) : void 0,
|
|
2647
2918
|
...getFieldMetadata(element)
|
|
2648
2919
|
});
|
|
2649
2920
|
});
|
|
@@ -2666,15 +2937,24 @@ function extractForms() {
|
|
|
2666
2937
|
).forEach((input) => {
|
|
2667
2938
|
const element = input;
|
|
2668
2939
|
const tag = input.tagName.toLowerCase();
|
|
2940
|
+
const label = getInputLabelWithSource(element);
|
|
2941
|
+
const role = getElementRole(input);
|
|
2942
|
+
const radioText = role === "radio" || element instanceof HTMLInputElement && element.type === "radio" ? getTrimmedText(
|
|
2943
|
+
element.getAttribute("value") || element.getAttribute("aria-label") || label.label
|
|
2944
|
+
) : void 0;
|
|
2669
2945
|
fields.push({
|
|
2670
2946
|
type: tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input",
|
|
2671
|
-
label:
|
|
2947
|
+
label: label.label?.slice(0, 100),
|
|
2948
|
+
labelSource: label.source,
|
|
2672
2949
|
inputType: element.getAttribute("type") || void 0,
|
|
2673
2950
|
placeholder: element.getAttribute("placeholder") || void 0,
|
|
2674
2951
|
required: element.hasAttribute("required") || void 0,
|
|
2675
2952
|
value: getElementValue(element),
|
|
2676
2953
|
options: element instanceof HTMLSelectElement ? getSelectOptions(element) : void 0,
|
|
2677
2954
|
...buildBaseMetadata(input),
|
|
2955
|
+
role,
|
|
2956
|
+
text: radioText?.slice(0, 100),
|
|
2957
|
+
looksCorrect: radioText || label.label ? looksLikeCorrectOption(radioText || label.label) : void 0,
|
|
2678
2958
|
...getFieldMetadata(element)
|
|
2679
2959
|
});
|
|
2680
2960
|
});
|
|
@@ -2683,11 +2963,11 @@ function extractForms() {
|
|
|
2683
2963
|
"button, input[type='submit'], input[type='image']"
|
|
2684
2964
|
)
|
|
2685
2965
|
).filter((control) => isSubmitControlForForm(control, form)).forEach((btn) => {
|
|
2686
|
-
const
|
|
2687
|
-
const text = btn.textContent?.trim() || input.value || btn.getAttribute("aria-label") || "Submit";
|
|
2966
|
+
const { text, source } = getButtonTextWithSource(btn);
|
|
2688
2967
|
fields.push({
|
|
2689
2968
|
type: "button",
|
|
2690
|
-
text: text
|
|
2969
|
+
text: text?.slice(0, 100),
|
|
2970
|
+
labelSource: source,
|
|
2691
2971
|
...buildBaseMetadata(btn)
|
|
2692
2972
|
});
|
|
2693
2973
|
});
|
|
@@ -2729,7 +3009,9 @@ function extractLandmarks() {
|
|
|
2729
3009
|
}
|
|
2730
3010
|
function extractJsonLd() {
|
|
2731
3011
|
const results = [];
|
|
2732
|
-
const scripts = document.querySelectorAll(
|
|
3012
|
+
const scripts = document.querySelectorAll(
|
|
3013
|
+
'script[type="application/ld+json"]'
|
|
3014
|
+
);
|
|
2733
3015
|
for (const script of scripts) {
|
|
2734
3016
|
try {
|
|
2735
3017
|
const parsed = JSON.parse(script.textContent || "");
|
|
@@ -2828,7 +3110,9 @@ function extractRdfa() {
|
|
|
2828
3110
|
}
|
|
2829
3111
|
function withHighlightLabelsRemoved(read) {
|
|
2830
3112
|
const labels = Array.from(
|
|
2831
|
-
document.querySelectorAll(
|
|
3113
|
+
document.querySelectorAll(
|
|
3114
|
+
".__vessel-highlight-label[data-vessel-highlight]"
|
|
3115
|
+
)
|
|
2832
3116
|
).filter((node) => node instanceof HTMLElement);
|
|
2833
3117
|
const removed = labels.map((label) => {
|
|
2834
3118
|
const parent = label.parentNode;
|
package/out/preload/index.js
CHANGED
|
@@ -63,6 +63,17 @@ const Channels = {
|
|
|
63
63
|
// DevTools panel
|
|
64
64
|
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
65
65
|
DEVTOOLS_PANEL_STATE: "devtools-panel:state",
|
|
66
|
+
DEVTOOLS_PANEL_RESIZE: "devtools-panel:resize",
|
|
67
|
+
// Find in page
|
|
68
|
+
FIND_IN_PAGE_START: "find:start",
|
|
69
|
+
FIND_IN_PAGE_NEXT: "find:next",
|
|
70
|
+
FIND_IN_PAGE_STOP: "find:stop",
|
|
71
|
+
FIND_IN_PAGE_RESULT: "find:result",
|
|
72
|
+
// Browsing history
|
|
73
|
+
HISTORY_GET: "history:get",
|
|
74
|
+
HISTORY_SEARCH: "history:search",
|
|
75
|
+
HISTORY_CLEAR: "history:clear",
|
|
76
|
+
HISTORY_UPDATE: "history:update",
|
|
66
77
|
// Window controls
|
|
67
78
|
WINDOW_MINIMIZE: "window:minimize",
|
|
68
79
|
WINDOW_MAXIMIZE: "window:maximize",
|
|
@@ -185,13 +196,33 @@ const api = {
|
|
|
185
196
|
},
|
|
186
197
|
devtoolsPanel: {
|
|
187
198
|
toggle: () => electron.ipcRenderer.invoke(Channels.DEVTOOLS_PANEL_TOGGLE),
|
|
188
|
-
resize: (height) => electron.ipcRenderer.invoke(
|
|
199
|
+
resize: (height) => electron.ipcRenderer.invoke(Channels.DEVTOOLS_PANEL_RESIZE, height),
|
|
189
200
|
onStateUpdate: (cb) => {
|
|
190
201
|
const handler = (_, state) => cb(state);
|
|
191
202
|
electron.ipcRenderer.on(Channels.DEVTOOLS_PANEL_STATE, handler);
|
|
192
203
|
return () => electron.ipcRenderer.removeListener(Channels.DEVTOOLS_PANEL_STATE, handler);
|
|
193
204
|
}
|
|
194
205
|
},
|
|
206
|
+
find: {
|
|
207
|
+
start: (text, options) => electron.ipcRenderer.invoke(Channels.FIND_IN_PAGE_START, text, options),
|
|
208
|
+
next: (forward) => electron.ipcRenderer.invoke(Channels.FIND_IN_PAGE_NEXT, forward),
|
|
209
|
+
stop: (action) => electron.ipcRenderer.invoke(Channels.FIND_IN_PAGE_STOP, action),
|
|
210
|
+
onResult: (cb) => {
|
|
211
|
+
const handler = (_, result) => cb(result);
|
|
212
|
+
electron.ipcRenderer.on(Channels.FIND_IN_PAGE_RESULT, handler);
|
|
213
|
+
return () => electron.ipcRenderer.removeListener(Channels.FIND_IN_PAGE_RESULT, handler);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
history: {
|
|
217
|
+
get: () => electron.ipcRenderer.invoke(Channels.HISTORY_GET),
|
|
218
|
+
search: (query) => electron.ipcRenderer.invoke(Channels.HISTORY_SEARCH, query),
|
|
219
|
+
clear: () => electron.ipcRenderer.invoke(Channels.HISTORY_CLEAR),
|
|
220
|
+
onUpdate: (cb) => {
|
|
221
|
+
const handler = (_, state) => cb(state);
|
|
222
|
+
electron.ipcRenderer.on(Channels.HISTORY_UPDATE, handler);
|
|
223
|
+
return () => electron.ipcRenderer.removeListener(Channels.HISTORY_UPDATE, handler);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
195
226
|
window: {
|
|
196
227
|
minimize: () => electron.ipcRenderer.invoke(Channels.WINDOW_MINIMIZE),
|
|
197
228
|
maximize: () => electron.ipcRenderer.invoke(Channels.WINDOW_MAXIMIZE),
|