sentienceapi 0.90.16__py3-none-any.whl → 0.98.0__py3-none-any.whl
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.
Potentially problematic release.
This version of sentienceapi might be problematic. Click here for more details.
- sentience/__init__.py +120 -6
- sentience/_extension_loader.py +156 -1
- sentience/action_executor.py +217 -0
- sentience/actions.py +758 -30
- sentience/agent.py +806 -293
- sentience/agent_config.py +3 -0
- sentience/agent_runtime.py +840 -0
- sentience/asserts/__init__.py +70 -0
- sentience/asserts/expect.py +621 -0
- sentience/asserts/query.py +383 -0
- sentience/async_api.py +89 -1141
- sentience/backends/__init__.py +137 -0
- sentience/backends/actions.py +372 -0
- sentience/backends/browser_use_adapter.py +241 -0
- sentience/backends/cdp_backend.py +393 -0
- sentience/backends/exceptions.py +211 -0
- sentience/backends/playwright_backend.py +194 -0
- sentience/backends/protocol.py +216 -0
- sentience/backends/sentience_context.py +469 -0
- sentience/backends/snapshot.py +483 -0
- sentience/base_agent.py +95 -0
- sentience/browser.py +678 -39
- sentience/browser_evaluator.py +299 -0
- sentience/canonicalization.py +207 -0
- sentience/cloud_tracing.py +507 -42
- sentience/constants.py +6 -0
- sentience/conversational_agent.py +77 -43
- sentience/cursor_policy.py +142 -0
- sentience/element_filter.py +136 -0
- sentience/expect.py +98 -2
- sentience/extension/background.js +56 -185
- sentience/extension/content.js +150 -287
- sentience/extension/injected_api.js +1088 -1368
- sentience/extension/manifest.json +1 -1
- sentience/extension/pkg/sentience_core.d.ts +22 -22
- sentience/extension/pkg/sentience_core.js +275 -433
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/release.json +47 -47
- sentience/failure_artifacts.py +241 -0
- sentience/formatting.py +9 -53
- sentience/inspector.py +183 -1
- sentience/integrations/__init__.py +6 -0
- sentience/integrations/langchain/__init__.py +12 -0
- sentience/integrations/langchain/context.py +18 -0
- sentience/integrations/langchain/core.py +326 -0
- sentience/integrations/langchain/tools.py +180 -0
- sentience/integrations/models.py +46 -0
- sentience/integrations/pydanticai/__init__.py +15 -0
- sentience/integrations/pydanticai/deps.py +20 -0
- sentience/integrations/pydanticai/toolset.py +468 -0
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +765 -66
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +595 -3
- sentience/ordinal.py +280 -0
- sentience/overlay.py +109 -2
- sentience/protocols.py +228 -0
- sentience/query.py +67 -5
- sentience/read.py +95 -3
- sentience/recorder.py +223 -3
- sentience/schemas/trace_v1.json +128 -9
- sentience/screenshot.py +48 -2
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +599 -55
- sentience/snapshot_diff.py +126 -0
- sentience/text_search.py +120 -5
- sentience/trace_event_builder.py +148 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/index_schema.py +95 -7
- sentience/trace_indexing/indexer.py +105 -48
- sentience/tracer_factory.py +120 -9
- sentience/tracing.py +172 -8
- sentience/utils/__init__.py +40 -0
- sentience/utils/browser.py +46 -0
- sentience/{utils.py → utils/element.py} +3 -42
- sentience/utils/formatting.py +59 -0
- sentience/verification.py +618 -0
- sentience/visual_agent.py +2058 -0
- sentience/wait.py +68 -2
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/METADATA +199 -40
- sentienceapi-0.98.0.dist-info/RECORD +92 -0
- sentience/extension/test-content.js +0 -4
- sentienceapi-0.90.16.dist-info/RECORD +0 -50
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/WHEEL +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/entry_points.txt +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-APACHE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-MIT +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.98.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1473 +1,1193 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const check = setInterval(() => {
|
|
13
|
-
extId = getExtensionId();
|
|
14
|
-
if (extId) { clearInterval(check); resolve(); }
|
|
15
|
-
}, 50);
|
|
16
|
-
setTimeout(() => resolve(), 5000); // Max 5s wait
|
|
17
|
-
});
|
|
1
|
+
!function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
function getAllElements(root = document) {
|
|
4
|
+
const elements = [], filter = {
|
|
5
|
+
acceptNode: node => [ "SCRIPT", "STYLE", "NOSCRIPT", "META", "LINK", "HEAD" ].includes(node.tagName) || node.parentNode && "SVG" === node.parentNode.tagName && "SVG" !== node.tagName ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
|
|
6
|
+
}, walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter);
|
|
7
|
+
for (;walker.nextNode(); ) {
|
|
8
|
+
const node = walker.currentNode;
|
|
9
|
+
node.isConnected && (elements.push(node), node.shadowRoot && elements.push(...getAllElements(node.shadowRoot)));
|
|
10
|
+
}
|
|
11
|
+
return elements;
|
|
18
12
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
const CAPTCHA_TEXT_KEYWORDS = [ "verify you are human", "captcha", "human verification", "unusual traffic", "are you a robot", "security check", "prove you are human", "bot detection", "automated access" ], CAPTCHA_URL_HINTS = [ "captcha", "challenge", "verify" ], CAPTCHA_IFRAME_HINTS = {
|
|
14
|
+
recaptcha: [ "recaptcha", "google.com/recaptcha" ],
|
|
15
|
+
hcaptcha: [ "hcaptcha.com" ],
|
|
16
|
+
turnstile: [ "challenges.cloudflare.com", "turnstile" ],
|
|
17
|
+
arkose: [ "arkoselabs.com", "funcaptcha.com", "client-api.arkoselabs.com" ],
|
|
18
|
+
awswaf: [ "amazonaws.com/captcha", "awswaf.com" ]
|
|
19
|
+
}, CAPTCHA_SCRIPT_HINTS = {
|
|
20
|
+
recaptcha: [ "recaptcha" ],
|
|
21
|
+
hcaptcha: [ "hcaptcha" ],
|
|
22
|
+
turnstile: [ "turnstile", "challenges.cloudflare.com" ],
|
|
23
|
+
arkose: [ "arkoselabs", "funcaptcha" ],
|
|
24
|
+
awswaf: [ "captcha.awswaf", "awswaf-captcha" ]
|
|
25
|
+
}, CAPTCHA_CONTAINER_SELECTORS = [ {
|
|
26
|
+
selector: ".g-recaptcha",
|
|
27
|
+
provider: "recaptcha"
|
|
28
|
+
}, {
|
|
29
|
+
selector: "#g-recaptcha",
|
|
30
|
+
provider: "recaptcha"
|
|
31
|
+
}, {
|
|
32
|
+
selector: "[data-sitekey]",
|
|
33
|
+
provider: "unknown"
|
|
34
|
+
}, {
|
|
35
|
+
selector: 'iframe[title*="recaptcha" i]',
|
|
36
|
+
provider: "recaptcha"
|
|
37
|
+
}, {
|
|
38
|
+
selector: ".h-captcha",
|
|
39
|
+
provider: "hcaptcha"
|
|
40
|
+
}, {
|
|
41
|
+
selector: "#h-captcha",
|
|
42
|
+
provider: "hcaptcha"
|
|
43
|
+
}, {
|
|
44
|
+
selector: 'iframe[title*="hcaptcha" i]',
|
|
45
|
+
provider: "hcaptcha"
|
|
46
|
+
}, {
|
|
47
|
+
selector: ".cf-turnstile",
|
|
48
|
+
provider: "turnstile"
|
|
49
|
+
}, {
|
|
50
|
+
selector: "[data-cf-turnstile-sitekey]",
|
|
51
|
+
provider: "turnstile"
|
|
52
|
+
}, {
|
|
53
|
+
selector: 'iframe[src*="challenges.cloudflare.com"]',
|
|
54
|
+
provider: "turnstile"
|
|
55
|
+
}, {
|
|
56
|
+
selector: "#FunCaptcha",
|
|
57
|
+
provider: "arkose"
|
|
58
|
+
}, {
|
|
59
|
+
selector: ".funcaptcha",
|
|
60
|
+
provider: "arkose"
|
|
61
|
+
}, {
|
|
62
|
+
selector: "[data-arkose-public-key]",
|
|
63
|
+
provider: "arkose"
|
|
64
|
+
}, {
|
|
65
|
+
selector: 'iframe[src*="arkoselabs"]',
|
|
66
|
+
provider: "arkose"
|
|
67
|
+
}, {
|
|
68
|
+
selector: "#captcha-container",
|
|
69
|
+
provider: "awswaf"
|
|
70
|
+
}, {
|
|
71
|
+
selector: "[data-awswaf-captcha]",
|
|
72
|
+
provider: "awswaf"
|
|
73
|
+
}, {
|
|
74
|
+
selector: 'iframe[title*="captcha" i]',
|
|
75
|
+
provider: "unknown"
|
|
76
|
+
} ];
|
|
77
|
+
function addEvidence(list, value) {
|
|
78
|
+
value && (list.length >= 5 || list.push(value));
|
|
23
79
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
80
|
+
function truncateText(text, maxLen) {
|
|
81
|
+
return text ? text.length <= maxLen ? text : text.slice(0, maxLen) : "";
|
|
82
|
+
}
|
|
83
|
+
function matchHints(value, hints) {
|
|
84
|
+
const lower = String(value || "").toLowerCase();
|
|
85
|
+
return !!lower && hints.some(hint => lower.includes(hint));
|
|
86
|
+
}
|
|
87
|
+
function detectCaptcha() {
|
|
88
|
+
const evidence = {
|
|
89
|
+
text_hits: [],
|
|
90
|
+
selector_hits: [],
|
|
91
|
+
iframe_src_hits: [],
|
|
92
|
+
url_hits: []
|
|
93
|
+
};
|
|
94
|
+
let hasIframeHit = !1, hasContainerHit = !1, hasScriptHit = !1, hasKeywordHit = !1, hasUrlHit = !1;
|
|
95
|
+
const providerSignals = {
|
|
96
|
+
recaptcha: 0,
|
|
97
|
+
hcaptcha: 0,
|
|
98
|
+
turnstile: 0,
|
|
99
|
+
arkose: 0,
|
|
100
|
+
awswaf: 0
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const iframes = document.querySelectorAll("iframe");
|
|
104
|
+
for (const iframe of iframes) {
|
|
105
|
+
const src = iframe.getAttribute("src") || "", title = iframe.getAttribute("title") || "";
|
|
106
|
+
if (src) for (const [provider, hints] of Object.entries(CAPTCHA_IFRAME_HINTS)) matchHints(src, hints) && (hasIframeHit = !0,
|
|
107
|
+
providerSignals[provider] += 1, addEvidence(evidence.iframe_src_hits, truncateText(src, 120)));
|
|
108
|
+
if (title && matchHints(title, [ "captcha", "recaptcha" ]) && (hasContainerHit = !0,
|
|
109
|
+
addEvidence(evidence.selector_hits, 'iframe[title*="captcha"]')), evidence.iframe_src_hits.length >= 5) break;
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {}
|
|
112
|
+
try {
|
|
113
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
114
|
+
for (const script of scripts) {
|
|
115
|
+
const src = script.getAttribute("src") || "";
|
|
116
|
+
if (src) {
|
|
117
|
+
for (const [provider, hints] of Object.entries(CAPTCHA_SCRIPT_HINTS)) matchHints(src, hints) && (hasScriptHit = !0,
|
|
118
|
+
providerSignals[provider] += 1, addEvidence(evidence.selector_hits, `script[src*="${hints[0]}"]`));
|
|
119
|
+
if (evidence.selector_hits.length >= 5) break;
|
|
38
120
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {}
|
|
123
|
+
for (const {selector: selector, provider: provider} of CAPTCHA_CONTAINER_SELECTORS) try {
|
|
124
|
+
document.querySelector(selector) && (hasContainerHit = !0, addEvidence(evidence.selector_hits, selector),
|
|
125
|
+
"unknown" !== provider && (providerSignals[provider] += 1));
|
|
126
|
+
} catch (e) {}
|
|
127
|
+
const textSnippet = function() {
|
|
128
|
+
try {
|
|
129
|
+
const candidates = document.querySelectorAll("h1, h2, h3, h4, p, label, button, form, div, span");
|
|
130
|
+
let combined = "", count = 0;
|
|
131
|
+
for (const node of candidates) {
|
|
132
|
+
if (count >= 30 || combined.length >= 2e3) break;
|
|
133
|
+
if (!node || "string" != typeof node.innerText) continue;
|
|
134
|
+
if (!node.offsetWidth && !node.offsetHeight && !node.getClientRects().length) continue;
|
|
135
|
+
const text = node.innerText.replace(/\s+/g, " ").trim();
|
|
136
|
+
text && (combined += `${text} `, count += 1);
|
|
42
137
|
}
|
|
43
|
-
return
|
|
138
|
+
if (combined = combined.trim(), combined) return truncateText(combined, 2e3);
|
|
139
|
+
} catch (e) {}
|
|
140
|
+
try {
|
|
141
|
+
let bodyText = document.body?.innerText || "";
|
|
142
|
+
return !bodyText && document.body?.textContent && (bodyText = document.body.textContent),
|
|
143
|
+
truncateText(bodyText.replace(/\s+/g, " ").trim(), 2e3);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
return "";
|
|
44
146
|
}
|
|
147
|
+
}();
|
|
148
|
+
if (textSnippet) {
|
|
149
|
+
const lowerText = textSnippet.toLowerCase();
|
|
150
|
+
for (const keyword of CAPTCHA_TEXT_KEYWORDS) lowerText.includes(keyword) && (hasKeywordHit = !0,
|
|
151
|
+
addEvidence(evidence.text_hits, keyword));
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const lowerUrl = (window.location?.href || "").toLowerCase();
|
|
155
|
+
for (const hint of CAPTCHA_URL_HINTS) lowerUrl.includes(hint) && (hasUrlHit = !0,
|
|
156
|
+
addEvidence(evidence.url_hits, hint));
|
|
157
|
+
} catch (e) {}
|
|
158
|
+
let confidence = 0;
|
|
159
|
+
hasIframeHit && (confidence += .7), hasContainerHit && (confidence += .5), hasScriptHit && (confidence += .5),
|
|
160
|
+
hasKeywordHit && (confidence += .3), hasUrlHit && (confidence += .2), confidence = Math.min(1, confidence),
|
|
161
|
+
hasIframeHit && (confidence = Math.max(confidence, .8)), !hasKeywordHit || hasIframeHit || hasContainerHit || hasScriptHit || hasUrlHit || (confidence = Math.min(confidence, .4));
|
|
162
|
+
const detected = confidence >= .7;
|
|
163
|
+
let providerHint = null;
|
|
164
|
+
return providerSignals.recaptcha > 0 ? providerHint = "recaptcha" : providerSignals.hcaptcha > 0 ? providerHint = "hcaptcha" : providerSignals.turnstile > 0 ? providerHint = "turnstile" : providerSignals.arkose > 0 ? providerHint = "arkose" : providerSignals.awswaf > 0 ? providerHint = "awswaf" : detected && (providerHint = "unknown"),
|
|
165
|
+
{
|
|
166
|
+
detected: detected,
|
|
167
|
+
provider_hint: providerHint,
|
|
168
|
+
confidence: confidence,
|
|
169
|
+
evidence: evidence
|
|
45
170
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
171
|
+
}
|
|
172
|
+
const DEFAULT_INFERENCE_CONFIG = {
|
|
173
|
+
allowedTags: [ "label", "span", "div" ],
|
|
174
|
+
allowedRoles: [],
|
|
175
|
+
allowedClassPatterns: [],
|
|
176
|
+
maxParentDepth: 2,
|
|
177
|
+
maxSiblingDistance: 1,
|
|
178
|
+
requireSameContainer: !0,
|
|
179
|
+
containerTags: [ "form", "fieldset", "div" ],
|
|
180
|
+
methods: {
|
|
181
|
+
explicitLabel: !0,
|
|
182
|
+
ariaLabelledby: !0,
|
|
183
|
+
parentTraversal: !0,
|
|
184
|
+
siblingProximity: !0
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function isInferenceSource(el, config) {
|
|
188
|
+
if (!el || !el.tagName) return !1;
|
|
189
|
+
const tag = el.tagName.toLowerCase(), role = el.getAttribute ? el.getAttribute("role") : "", className = ((el.className || "") + "").toLowerCase();
|
|
190
|
+
if (config.allowedTags.includes(tag)) return !0;
|
|
191
|
+
if (config.allowedRoles.length > 0 && role && config.allowedRoles.includes(role)) return !0;
|
|
192
|
+
if (config.allowedClassPatterns.length > 0) for (const pattern of config.allowedClassPatterns) if (className.includes(pattern.toLowerCase())) return !0;
|
|
193
|
+
return !1;
|
|
194
|
+
}
|
|
195
|
+
function isInSameValidContainer(element, candidate, limits) {
|
|
196
|
+
if (!element || !candidate) return !1;
|
|
197
|
+
if (limits.requireSameContainer) {
|
|
198
|
+
const commonParent = function(el1, el2) {
|
|
199
|
+
if (!el1 || !el2) return null;
|
|
200
|
+
const doc = "undefined" != typeof global && global.document || "undefined" != typeof window && window.document || "undefined" != typeof document && document || null, parents1 = [];
|
|
201
|
+
let current = el1;
|
|
202
|
+
for (;current && (parents1.push(current), current.parentElement) && (!doc || current !== doc.body && current !== doc.documentElement); ) current = current.parentElement;
|
|
203
|
+
for (current = el2; current; ) {
|
|
204
|
+
if (-1 !== parents1.indexOf(current)) return current;
|
|
205
|
+
if (!current.parentElement) break;
|
|
206
|
+
if (doc && (current === doc.body || current === doc.documentElement)) break;
|
|
207
|
+
current = current.parentElement;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}(element, candidate);
|
|
211
|
+
if (!commonParent) return !1;
|
|
212
|
+
if (!function(el, validTags) {
|
|
213
|
+
if (!el || !el.tagName) return !1;
|
|
214
|
+
const tag = el.tagName.toLowerCase();
|
|
215
|
+
let className = "";
|
|
216
|
+
try {
|
|
217
|
+
className = (el.className || "") + "";
|
|
218
|
+
} catch (e) {
|
|
219
|
+
className = "";
|
|
220
|
+
}
|
|
221
|
+
return validTags.includes(tag) || className.toLowerCase().includes("form") || className.toLowerCase().includes("field");
|
|
222
|
+
}(commonParent, limits.containerTags)) return !1;
|
|
223
|
+
}
|
|
224
|
+
return !0;
|
|
225
|
+
}
|
|
226
|
+
function getInferredLabel(el, options = {}) {
|
|
227
|
+
if (!el) return null;
|
|
228
|
+
const {enableInference: enableInference = !0, inferenceConfig: inferenceConfig = {}} = options;
|
|
229
|
+
if (!enableInference) return null;
|
|
230
|
+
const ariaLabel = el.getAttribute ? el.getAttribute("aria-label") : null, hasAriaLabel = ariaLabel && ariaLabel.trim(), hasInputValue = "INPUT" === el.tagName && (el.value || el.placeholder), hasImgAlt = "IMG" === el.tagName && el.alt;
|
|
231
|
+
let innerTextValue = "";
|
|
232
|
+
try {
|
|
233
|
+
innerTextValue = el.innerText || "";
|
|
234
|
+
} catch (e) {
|
|
235
|
+
innerTextValue = "";
|
|
236
|
+
}
|
|
237
|
+
const hasInnerText = "INPUT" !== el.tagName && "IMG" !== el.tagName && innerTextValue && innerTextValue.trim();
|
|
238
|
+
if (hasAriaLabel || hasInputValue || hasImgAlt || hasInnerText) return null;
|
|
239
|
+
const config = function(userConfig = {}) {
|
|
240
|
+
return {
|
|
241
|
+
...DEFAULT_INFERENCE_CONFIG,
|
|
242
|
+
...userConfig,
|
|
243
|
+
methods: {
|
|
244
|
+
...DEFAULT_INFERENCE_CONFIG.methods,
|
|
245
|
+
...userConfig.methods || {}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}(inferenceConfig);
|
|
249
|
+
if (config.methods.explicitLabel && el.labels && el.labels.length > 0) {
|
|
250
|
+
const label = el.labels[0];
|
|
251
|
+
if (isInferenceSource(label, config)) {
|
|
252
|
+
const text = (label.innerText || "").trim();
|
|
253
|
+
if (text) return {
|
|
254
|
+
text: text,
|
|
255
|
+
source: "explicit_label"
|
|
256
|
+
};
|
|
53
257
|
}
|
|
54
258
|
}
|
|
55
|
-
|
|
259
|
+
if (config.methods.ariaLabelledby && el.hasAttribute && el.hasAttribute("aria-labelledby")) {
|
|
260
|
+
const labelIdsAttr = el.getAttribute("aria-labelledby");
|
|
261
|
+
if (labelIdsAttr) {
|
|
262
|
+
const labelIds = labelIdsAttr.split(/\s+/).filter(id => id.trim()), labelTexts = [], doc = (() => "undefined" != typeof global && global.document ? global.document : "undefined" != typeof window && window.document ? window.document : "undefined" != typeof document ? document : null)();
|
|
263
|
+
if (doc && doc.getElementById) for (const labelId of labelIds) {
|
|
264
|
+
if (!labelId.trim()) continue;
|
|
265
|
+
let labelEl = null;
|
|
266
|
+
try {
|
|
267
|
+
labelEl = doc.getElementById(labelId);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (labelEl) {
|
|
272
|
+
let text = "";
|
|
273
|
+
try {
|
|
274
|
+
if (text = (labelEl.innerText || "").trim(), !text && labelEl.textContent && (text = labelEl.textContent.trim()),
|
|
275
|
+
!text && labelEl.getAttribute) {
|
|
276
|
+
const ariaLabel = labelEl.getAttribute("aria-label");
|
|
277
|
+
ariaLabel && (text = ariaLabel.trim());
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
text && labelTexts.push(text);
|
|
283
|
+
}
|
|
284
|
+
} else ;
|
|
285
|
+
if (labelTexts.length > 0) return {
|
|
286
|
+
text: labelTexts.join(" "),
|
|
287
|
+
source: "aria_labelledby"
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (config.methods.parentTraversal) {
|
|
292
|
+
let parent = el.parentElement, depth = 0;
|
|
293
|
+
for (;parent && depth < config.maxParentDepth; ) {
|
|
294
|
+
if (isInferenceSource(parent, config)) {
|
|
295
|
+
const text = (parent.innerText || "").trim();
|
|
296
|
+
if (text) return {
|
|
297
|
+
text: text,
|
|
298
|
+
source: "parent_label"
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
parent = parent.parentElement, depth++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (config.methods.siblingProximity) {
|
|
305
|
+
const prev = el.previousElementSibling;
|
|
306
|
+
if (prev && isInferenceSource(prev, config) && isInSameValidContainer(el, prev, {
|
|
307
|
+
requireSameContainer: config.requireSameContainer,
|
|
308
|
+
containerTags: config.containerTags
|
|
309
|
+
})) {
|
|
310
|
+
const text = (prev.innerText || "").trim();
|
|
311
|
+
if (text) return {
|
|
312
|
+
text: text,
|
|
313
|
+
source: "sibling_label"
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function normalizeNearbyText(text) {
|
|
320
|
+
return text ? text.replace(/\s+/g, " ").trim() : "";
|
|
321
|
+
}
|
|
322
|
+
function isInteractableElement(el) {
|
|
323
|
+
if (!el || !el.tagName) return !1;
|
|
324
|
+
const tag = el.tagName.toLowerCase(), role = el.getAttribute ? el.getAttribute("role") : null, hasTabIndex = !!el.hasAttribute && el.hasAttribute("tabindex"), hasHref = "A" === el.tagName && !!el.hasAttribute && el.hasAttribute("href");
|
|
325
|
+
if ([ "button", "input", "textarea", "select", "option", "details", "summary", "a" ].includes(tag)) return !("a" === tag && !hasHref);
|
|
326
|
+
if (role && [ "button", "link", "tab", "menuitem", "checkbox", "radio", "switch", "slider", "combobox", "textbox", "searchbox", "spinbutton" ].includes(role.toLowerCase())) return !0;
|
|
327
|
+
if (hasTabIndex) return !0;
|
|
328
|
+
if (el.onclick || el.onkeydown || el.onkeypress || el.onkeyup) return !0;
|
|
329
|
+
if (el.getAttribute) {
|
|
330
|
+
if (el.getAttribute("onclick") || el.getAttribute("onkeydown") || el.getAttribute("onkeypress") || el.getAttribute("onkeyup")) return !0;
|
|
331
|
+
}
|
|
332
|
+
return !1;
|
|
56
333
|
}
|
|
57
|
-
|
|
58
|
-
// --- HELPER: Smart Text Extractor ---
|
|
59
334
|
function getText(el) {
|
|
60
|
-
if (el.getAttribute(
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
335
|
+
if (el.getAttribute("aria-label")) return el.getAttribute("aria-label");
|
|
336
|
+
if ("INPUT" === el.tagName) {
|
|
337
|
+
const t = el.getAttribute && el.getAttribute("type") || el.type || "";
|
|
338
|
+
return "password" === String(t).toLowerCase() ? el.placeholder || "" : el.value || el.placeholder || "";
|
|
339
|
+
}
|
|
340
|
+
return "IMG" === el.tagName ? el.alt || "" : (el.innerText || "").replace(/\s+/g, " ").trim().substring(0, 100);
|
|
64
341
|
}
|
|
65
|
-
|
|
66
|
-
// --- HELPER: Safe Class Name Extractor (Handles SVGAnimatedString) ---
|
|
67
342
|
function getClassName(el) {
|
|
68
|
-
if (!el || !el.className) return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Handle SVGAnimatedString (SVG elements)
|
|
74
|
-
if (typeof el.className === 'object') {
|
|
75
|
-
if ('baseVal' in el.className && typeof el.className.baseVal === 'string') {
|
|
76
|
-
return el.className.baseVal;
|
|
77
|
-
}
|
|
78
|
-
if ('animVal' in el.className && typeof el.className.animVal === 'string') {
|
|
79
|
-
return el.className.animVal;
|
|
80
|
-
}
|
|
81
|
-
// Fallback: convert to string
|
|
343
|
+
if (!el || !el.className) return "";
|
|
344
|
+
if ("string" == typeof el.className) return el.className;
|
|
345
|
+
if ("object" == typeof el.className) {
|
|
346
|
+
if ("baseVal" in el.className && "string" == typeof el.className.baseVal) return el.className.baseVal;
|
|
347
|
+
if ("animVal" in el.className && "string" == typeof el.className.animVal) return el.className.animVal;
|
|
82
348
|
try {
|
|
83
349
|
return String(el.className);
|
|
84
350
|
} catch (e) {
|
|
85
|
-
return
|
|
351
|
+
return "";
|
|
86
352
|
}
|
|
87
353
|
}
|
|
88
|
-
|
|
89
|
-
return '';
|
|
354
|
+
return "";
|
|
90
355
|
}
|
|
91
|
-
|
|
92
|
-
// --- HELPER: Paranoid String Converter (Handles SVGAnimatedString) ---
|
|
93
356
|
function toSafeString(value) {
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// 2. Handle SVG objects (SVGAnimatedString, SVGAnimatedNumber, etc.)
|
|
100
|
-
if (typeof value === 'object') {
|
|
101
|
-
// Try extracting baseVal (standard SVG property)
|
|
102
|
-
if ('baseVal' in value && typeof value.baseVal === 'string') {
|
|
103
|
-
return value.baseVal;
|
|
104
|
-
}
|
|
105
|
-
// Try animVal as fallback
|
|
106
|
-
if ('animVal' in value && typeof value.animVal === 'string') {
|
|
107
|
-
return value.animVal;
|
|
108
|
-
}
|
|
109
|
-
// Fallback: Force to string (prevents WASM crash even if data is less useful)
|
|
110
|
-
// This prevents the "Invalid Type" crash, even if the data is "[object SVGAnimatedString]"
|
|
357
|
+
if (null == value) return null;
|
|
358
|
+
if ("string" == typeof value) return value;
|
|
359
|
+
if ("object" == typeof value) {
|
|
360
|
+
if ("baseVal" in value && "string" == typeof value.baseVal) return value.baseVal;
|
|
361
|
+
if ("animVal" in value && "string" == typeof value.animVal) return value.animVal;
|
|
111
362
|
try {
|
|
112
363
|
return String(value);
|
|
113
364
|
} catch (e) {
|
|
114
365
|
return null;
|
|
115
366
|
}
|
|
116
367
|
}
|
|
117
|
-
|
|
118
|
-
// 3. Last resort cast for primitives
|
|
119
368
|
try {
|
|
120
369
|
return String(value);
|
|
121
370
|
} catch (e) {
|
|
122
371
|
return null;
|
|
123
372
|
}
|
|
124
373
|
}
|
|
125
|
-
|
|
126
|
-
// --- HELPER: Get SVG Fill/Stroke Color ---
|
|
127
|
-
// For SVG elements, get the fill or stroke color (SVGs use fill/stroke, not backgroundColor)
|
|
128
374
|
function getSVGColor(el) {
|
|
129
|
-
if (!el || el.tagName
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Try fill first (most common for SVG icons)
|
|
134
|
-
const fill = style.fill;
|
|
135
|
-
if (fill && fill !== 'none' && fill !== 'transparent' && fill !== 'rgba(0, 0, 0, 0)') {
|
|
136
|
-
// Convert fill to rgb() format if needed
|
|
375
|
+
if (!el || "SVG" !== el.tagName) return null;
|
|
376
|
+
const style = window.getComputedStyle(el), fill = style.fill;
|
|
377
|
+
if (fill && "none" !== fill && "transparent" !== fill && "rgba(0, 0, 0, 0)" !== fill) {
|
|
137
378
|
const rgbaMatch = fill.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
138
379
|
if (rgbaMatch) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
142
|
-
}
|
|
143
|
-
} else if (fill.startsWith('rgb(')) {
|
|
144
|
-
return fill;
|
|
145
|
-
}
|
|
380
|
+
if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
381
|
+
} else if (fill.startsWith("rgb(")) return fill;
|
|
146
382
|
}
|
|
147
|
-
|
|
148
|
-
// Fallback to stroke if fill is not available
|
|
149
383
|
const stroke = style.stroke;
|
|
150
|
-
if (stroke &&
|
|
384
|
+
if (stroke && "none" !== stroke && "transparent" !== stroke && "rgba(0, 0, 0, 0)" !== stroke) {
|
|
151
385
|
const rgbaMatch = stroke.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
152
386
|
if (rgbaMatch) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
156
|
-
}
|
|
157
|
-
} else if (stroke.startsWith('rgb(')) {
|
|
158
|
-
return stroke;
|
|
159
|
-
}
|
|
387
|
+
if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
388
|
+
} else if (stroke.startsWith("rgb(")) return stroke;
|
|
160
389
|
}
|
|
161
|
-
|
|
162
390
|
return null;
|
|
163
391
|
}
|
|
164
|
-
|
|
165
|
-
// --- HELPER: Get Effective Background Color ---
|
|
166
|
-
// Traverses up the DOM tree to find the nearest non-transparent background color
|
|
167
|
-
// For SVGs, also checks fill/stroke properties
|
|
168
|
-
// This handles rgba(0,0,0,0) and transparent values that browsers commonly return
|
|
169
|
-
function getEffectiveBackgroundColor(el) {
|
|
170
|
-
if (!el) return null;
|
|
171
|
-
|
|
172
|
-
// For SVG elements, use fill/stroke instead of backgroundColor
|
|
173
|
-
if (el.tagName === 'SVG') {
|
|
174
|
-
const svgColor = getSVGColor(el);
|
|
175
|
-
if (svgColor) return svgColor;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
let current = el;
|
|
179
|
-
const maxDepth = 10; // Prevent infinite loops
|
|
180
|
-
let depth = 0;
|
|
181
|
-
|
|
182
|
-
while (current && depth < maxDepth) {
|
|
183
|
-
const style = window.getComputedStyle(current);
|
|
184
|
-
|
|
185
|
-
// For SVG elements in the tree, also check fill/stroke
|
|
186
|
-
if (current.tagName === 'SVG') {
|
|
187
|
-
const svgColor = getSVGColor(current);
|
|
188
|
-
if (svgColor) return svgColor;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const bgColor = style.backgroundColor;
|
|
192
|
-
|
|
193
|
-
if (bgColor && bgColor !== 'transparent' && bgColor !== 'rgba(0, 0, 0, 0)') {
|
|
194
|
-
// Check if it's rgba with alpha < 1 (semi-transparent)
|
|
195
|
-
const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
196
|
-
if (rgbaMatch) {
|
|
197
|
-
const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0;
|
|
198
|
-
// If alpha is high enough (>= 0.9), consider it opaque enough
|
|
199
|
-
if (alpha >= 0.9) {
|
|
200
|
-
// Convert to rgb() format for Gateway compatibility
|
|
201
|
-
return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
202
|
-
}
|
|
203
|
-
// If semi-transparent, continue up the tree
|
|
204
|
-
} else if (bgColor.startsWith('rgb(')) {
|
|
205
|
-
// Already in rgb() format, use it
|
|
206
|
-
return bgColor;
|
|
207
|
-
} else {
|
|
208
|
-
// Named color or other format, return as-is
|
|
209
|
-
return bgColor;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Move up the DOM tree
|
|
214
|
-
current = current.parentElement;
|
|
215
|
-
depth++;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Fallback: return null if nothing found
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// --- HELPER: Viewport Check ---
|
|
223
|
-
function isInViewport(rect) {
|
|
224
|
-
return (
|
|
225
|
-
rect.top < window.innerHeight && rect.bottom > 0 &&
|
|
226
|
-
rect.left < window.innerWidth && rect.right > 0
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// --- HELPER: Occlusion Check (Optimized to avoid layout thrashing) ---
|
|
231
|
-
// Only checks occlusion for elements likely to be occluded (high z-index, positioned)
|
|
232
|
-
// This avoids forced reflow for most elements, dramatically improving performance
|
|
233
|
-
function isOccluded(el, rect, style) {
|
|
234
|
-
// Fast path: Skip occlusion check for most elements
|
|
235
|
-
// Only check for elements that are likely to be occluded (overlays, modals, tooltips)
|
|
236
|
-
const zIndex = parseInt(style.zIndex, 10);
|
|
237
|
-
const position = style.position;
|
|
238
|
-
|
|
239
|
-
// Skip occlusion check for normal flow elements (vast majority)
|
|
240
|
-
// Only check for positioned elements or high z-index (likely overlays)
|
|
241
|
-
if (position === 'static' && (isNaN(zIndex) || zIndex <= 10)) {
|
|
242
|
-
return false; // Assume not occluded for performance
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// For positioned/high z-index elements, do the expensive check
|
|
246
|
-
const cx = rect.x + rect.width / 2;
|
|
247
|
-
const cy = rect.y + rect.height / 2;
|
|
248
|
-
|
|
249
|
-
if (cx < 0 || cx > window.innerWidth || cy < 0 || cy > window.innerHeight) return false;
|
|
250
|
-
|
|
251
|
-
const topEl = document.elementFromPoint(cx, cy);
|
|
252
|
-
if (!topEl) return false;
|
|
253
|
-
|
|
254
|
-
return !(el === topEl || el.contains(topEl) || topEl.contains(el));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// --- HELPER: Screenshot Bridge ---
|
|
258
|
-
function captureScreenshot(options) {
|
|
259
|
-
return new Promise(resolve => {
|
|
260
|
-
const requestId = Math.random().toString(36).substring(7);
|
|
261
|
-
const listener = (e) => {
|
|
262
|
-
if (e.data.type === 'SENTIENCE_SCREENSHOT_RESULT' && e.data.requestId === requestId) {
|
|
263
|
-
window.removeEventListener('message', listener);
|
|
264
|
-
resolve(e.data.screenshot);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
window.addEventListener('message', listener);
|
|
268
|
-
window.postMessage({ type: 'SENTIENCE_SCREENSHOT_REQUEST', requestId, options }, '*');
|
|
269
|
-
setTimeout(() => {
|
|
270
|
-
window.removeEventListener('message', listener);
|
|
271
|
-
resolve(null);
|
|
272
|
-
}, 10000); // 10s timeout
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// --- HELPER: Snapshot Processing Bridge (NEW!) ---
|
|
277
|
-
function processSnapshotInBackground(rawData, options) {
|
|
278
|
-
return new Promise((resolve, reject) => {
|
|
279
|
-
const requestId = Math.random().toString(36).substring(7);
|
|
280
|
-
const TIMEOUT_MS = 25000; // 25 seconds (longer than content.js timeout)
|
|
281
|
-
let resolved = false;
|
|
282
|
-
|
|
283
|
-
const timeout = setTimeout(() => {
|
|
284
|
-
if (!resolved) {
|
|
285
|
-
resolved = true;
|
|
286
|
-
window.removeEventListener('message', listener);
|
|
287
|
-
reject(new Error('WASM processing timeout - extension may be unresponsive. Try reloading the extension.'));
|
|
288
|
-
}
|
|
289
|
-
}, TIMEOUT_MS);
|
|
290
|
-
|
|
291
|
-
const listener = (e) => {
|
|
292
|
-
if (e.data.type === 'SENTIENCE_SNAPSHOT_RESULT' && e.data.requestId === requestId) {
|
|
293
|
-
if (resolved) return; // Already handled
|
|
294
|
-
resolved = true;
|
|
295
|
-
clearTimeout(timeout);
|
|
296
|
-
window.removeEventListener('message', listener);
|
|
297
|
-
|
|
298
|
-
if (e.data.error) {
|
|
299
|
-
reject(new Error(e.data.error));
|
|
300
|
-
} else {
|
|
301
|
-
resolve({
|
|
302
|
-
elements: e.data.elements,
|
|
303
|
-
raw_elements: e.data.raw_elements,
|
|
304
|
-
duration: e.data.duration
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
window.addEventListener('message', listener);
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
window.postMessage({
|
|
314
|
-
type: 'SENTIENCE_SNAPSHOT_REQUEST',
|
|
315
|
-
requestId,
|
|
316
|
-
rawData,
|
|
317
|
-
options
|
|
318
|
-
}, '*');
|
|
319
|
-
} catch (error) {
|
|
320
|
-
if (!resolved) {
|
|
321
|
-
resolved = true;
|
|
322
|
-
clearTimeout(timeout);
|
|
323
|
-
window.removeEventListener('message', listener);
|
|
324
|
-
reject(new Error(`Failed to send snapshot request: ${error.message}`));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// --- HELPER: Raw HTML Extractor (unchanged) ---
|
|
331
392
|
function getRawHTML(root) {
|
|
332
|
-
const sourceRoot = root || document.body;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
unwantedTags.forEach(tag => {
|
|
337
|
-
const elements = clone.querySelectorAll(tag);
|
|
338
|
-
elements.forEach(el => {
|
|
339
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
393
|
+
const sourceRoot = root || document.body, clone = sourceRoot.cloneNode(!0);
|
|
394
|
+
[ "nav", "footer", "header", "script", "style", "noscript", "iframe", "svg" ].forEach(tag => {
|
|
395
|
+
clone.querySelectorAll(tag).forEach(el => {
|
|
396
|
+
el.parentNode && el.parentNode.removeChild(el);
|
|
340
397
|
});
|
|
341
398
|
});
|
|
342
|
-
|
|
343
|
-
// Remove invisible elements
|
|
344
|
-
const invisibleSelectors = [];
|
|
345
|
-
const walker = document.createTreeWalker(sourceRoot, NodeFilter.SHOW_ELEMENT, null, false);
|
|
399
|
+
const invisibleSelectors = [], walker = document.createTreeWalker(sourceRoot, NodeFilter.SHOW_ELEMENT, null, !1);
|
|
346
400
|
let node;
|
|
347
|
-
|
|
401
|
+
for (;node = walker.nextNode(); ) {
|
|
348
402
|
const tag = node.tagName.toLowerCase();
|
|
349
|
-
if (
|
|
350
|
-
|
|
403
|
+
if ("head" === tag || "title" === tag) continue;
|
|
351
404
|
const style = window.getComputedStyle(node);
|
|
352
|
-
if (style.display
|
|
353
|
-
(node.offsetWidth === 0 && node.offsetHeight === 0)) {
|
|
405
|
+
if ("none" === style.display || "hidden" === style.visibility || 0 === node.offsetWidth && 0 === node.offsetHeight) {
|
|
354
406
|
let selector = tag;
|
|
355
|
-
if (node.id) {
|
|
356
|
-
selector = `#${node.id}`;
|
|
357
|
-
} else if (node.className && typeof node.className === 'string') {
|
|
407
|
+
if (node.id) selector = `#${node.id}`; else if (node.className && "string" == typeof node.className) {
|
|
358
408
|
const classes = node.className.trim().split(/\s+/).filter(c => c);
|
|
359
|
-
|
|
360
|
-
selector = `${tag}.${classes.join('.')}`;
|
|
361
|
-
}
|
|
409
|
+
classes.length > 0 && (selector = `${tag}.${classes.join(".")}`);
|
|
362
410
|
}
|
|
363
411
|
invisibleSelectors.push(selector);
|
|
364
412
|
}
|
|
365
413
|
}
|
|
366
|
-
|
|
367
414
|
invisibleSelectors.forEach(selector => {
|
|
368
415
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
416
|
+
clone.querySelectorAll(selector).forEach(el => {
|
|
417
|
+
el.parentNode && el.parentNode.removeChild(el);
|
|
372
418
|
});
|
|
373
|
-
} catch (e) {
|
|
374
|
-
// Invalid selector, skip
|
|
375
|
-
}
|
|
419
|
+
} catch (e) {}
|
|
376
420
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('#')) {
|
|
383
|
-
try {
|
|
384
|
-
link.setAttribute('href', new URL(href, document.baseURI).href);
|
|
385
|
-
} catch (e) {}
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
const images = clone.querySelectorAll('img[src]');
|
|
390
|
-
images.forEach(img => {
|
|
391
|
-
const src = img.getAttribute('src');
|
|
392
|
-
if (src && !src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('data:')) {
|
|
393
|
-
try {
|
|
394
|
-
img.setAttribute('src', new URL(src, document.baseURI).href);
|
|
395
|
-
} catch (e) {}
|
|
396
|
-
}
|
|
421
|
+
clone.querySelectorAll("a[href]").forEach(link => {
|
|
422
|
+
const href = link.getAttribute("href");
|
|
423
|
+
if (href && !href.startsWith("http://") && !href.startsWith("https://") && !href.startsWith("#")) try {
|
|
424
|
+
link.setAttribute("href", new URL(href, document.baseURI).href);
|
|
425
|
+
} catch (e) {}
|
|
397
426
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const rawHTML = getRawHTML(root);
|
|
405
|
-
const tempDiv = document.createElement('div');
|
|
406
|
-
tempDiv.innerHTML = rawHTML;
|
|
407
|
-
|
|
408
|
-
let markdown = '';
|
|
409
|
-
let insideLink = false;
|
|
410
|
-
|
|
411
|
-
function walk(node) {
|
|
412
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
413
|
-
const text = node.textContent.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ');
|
|
414
|
-
if (text.trim()) markdown += text;
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
419
|
-
|
|
420
|
-
const tag = node.tagName.toLowerCase();
|
|
421
|
-
|
|
422
|
-
// Prefix
|
|
423
|
-
if (tag === 'h1') markdown += '\n# ';
|
|
424
|
-
if (tag === 'h2') markdown += '\n## ';
|
|
425
|
-
if (tag === 'h3') markdown += '\n### ';
|
|
426
|
-
if (tag === 'li') markdown += '\n- ';
|
|
427
|
-
if (!insideLink && (tag === 'p' || tag === 'div' || tag === 'br')) markdown += '\n';
|
|
428
|
-
if (tag === 'strong' || tag === 'b') markdown += '**';
|
|
429
|
-
if (tag === 'em' || tag === 'i') markdown += '_';
|
|
430
|
-
if (tag === 'a') {
|
|
431
|
-
markdown += '[';
|
|
432
|
-
insideLink = true;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Children
|
|
436
|
-
if (node.shadowRoot) {
|
|
437
|
-
Array.from(node.shadowRoot.childNodes).forEach(walk);
|
|
438
|
-
} else {
|
|
439
|
-
node.childNodes.forEach(walk);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Suffix
|
|
443
|
-
if (tag === 'a') {
|
|
444
|
-
const href = node.getAttribute('href');
|
|
445
|
-
if (href) markdown += `](${href})`;
|
|
446
|
-
else markdown += ']';
|
|
447
|
-
insideLink = false;
|
|
448
|
-
}
|
|
449
|
-
if (tag === 'strong' || tag === 'b') markdown += '**';
|
|
450
|
-
if (tag === 'em' || tag === 'i') markdown += '_';
|
|
451
|
-
if (!insideLink && (tag === 'h1' || tag === 'h2' || tag === 'h3' || tag === 'p' || tag === 'div')) markdown += '\n';
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
walk(tempDiv);
|
|
455
|
-
return markdown.replace(/\n{3,}/g, '\n\n').trim();
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// --- HELPER: Text Extractor (unchanged) ---
|
|
459
|
-
function convertToText(root) {
|
|
460
|
-
let text = '';
|
|
461
|
-
function walk(node) {
|
|
462
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
463
|
-
text += node.textContent;
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
467
|
-
const tag = node.tagName.toLowerCase();
|
|
468
|
-
if (['nav', 'footer', 'header', 'script', 'style', 'noscript', 'iframe', 'svg'].includes(tag)) return;
|
|
469
|
-
|
|
470
|
-
const style = window.getComputedStyle(node);
|
|
471
|
-
if (style.display === 'none' || style.visibility === 'hidden') return;
|
|
472
|
-
|
|
473
|
-
const isBlock = style.display === 'block' || style.display === 'flex' || node.tagName === 'P' || node.tagName === 'DIV';
|
|
474
|
-
if (isBlock) text += ' ';
|
|
475
|
-
|
|
476
|
-
if (node.shadowRoot) {
|
|
477
|
-
Array.from(node.shadowRoot.childNodes).forEach(walk);
|
|
478
|
-
} else {
|
|
479
|
-
node.childNodes.forEach(walk);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (isBlock) text += '\n';
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
walk(root || document.body);
|
|
486
|
-
return text.replace(/\n{3,}/g, '\n\n').trim();
|
|
427
|
+
return clone.querySelectorAll("img[src]").forEach(img => {
|
|
428
|
+
const src = img.getAttribute("src");
|
|
429
|
+
if (src && !src.startsWith("http://") && !src.startsWith("https://") && !src.startsWith("data:")) try {
|
|
430
|
+
img.setAttribute("src", new URL(src, document.baseURI).href);
|
|
431
|
+
} catch (e) {}
|
|
432
|
+
}), clone.innerHTML;
|
|
487
433
|
}
|
|
488
|
-
|
|
489
|
-
// --- HELPER: Clean null/undefined fields ---
|
|
490
434
|
function cleanElement(obj) {
|
|
491
|
-
if (Array.isArray(obj))
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
if (obj !== null && typeof obj === 'object') {
|
|
435
|
+
if (Array.isArray(obj)) return obj.map(cleanElement);
|
|
436
|
+
if (null !== obj && "object" == typeof obj) {
|
|
495
437
|
const cleaned = {};
|
|
496
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if (Object.keys(deepClean).length > 0) {
|
|
501
|
-
cleaned[key] = deepClean;
|
|
502
|
-
}
|
|
503
|
-
} else {
|
|
504
|
-
cleaned[key] = value;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
438
|
+
for (const [key, value] of Object.entries(obj)) if (null != value) if ("object" == typeof value) {
|
|
439
|
+
const deepClean = cleanElement(value);
|
|
440
|
+
Object.keys(deepClean).length > 0 && (cleaned[key] = deepClean);
|
|
441
|
+
} else cleaned[key] = value;
|
|
508
442
|
return cleaned;
|
|
509
443
|
}
|
|
510
444
|
return obj;
|
|
511
445
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
x: Math.round(rect.x),
|
|
522
|
-
y: Math.round(rect.y),
|
|
523
|
-
width: Math.round(rect.width),
|
|
524
|
-
height: Math.round(rect.height)
|
|
525
|
-
},
|
|
526
|
-
styles: {
|
|
527
|
-
cursor: style.cursor || null,
|
|
528
|
-
backgroundColor: style.backgroundColor || null,
|
|
529
|
-
color: style.color || null,
|
|
530
|
-
fontWeight: style.fontWeight || null,
|
|
531
|
-
fontSize: style.fontSize || null,
|
|
532
|
-
display: style.display || null,
|
|
533
|
-
position: style.position || null,
|
|
534
|
-
zIndex: style.zIndex || null,
|
|
535
|
-
opacity: style.opacity || null,
|
|
536
|
-
visibility: style.visibility || null
|
|
537
|
-
},
|
|
538
|
-
attributes: {
|
|
539
|
-
role: el.getAttribute('role') || null,
|
|
540
|
-
type: el.getAttribute('type') || null,
|
|
541
|
-
ariaLabel: el.getAttribute('aria-label') || null,
|
|
542
|
-
id: el.id || null,
|
|
543
|
-
className: el.className || null
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// --- HELPER: Generate Unique CSS Selector (for Golden Set) ---
|
|
549
|
-
function getUniqueSelector(el) {
|
|
550
|
-
if (!el || !el.tagName) return '';
|
|
551
|
-
|
|
552
|
-
// If element has a unique ID, use it
|
|
553
|
-
if (el.id) {
|
|
554
|
-
return `#${el.id}`;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Try data attributes or aria-label for uniqueness
|
|
558
|
-
for (const attr of el.attributes) {
|
|
559
|
-
if (attr.name.startsWith('data-') || attr.name === 'aria-label') {
|
|
560
|
-
const value = attr.value ? attr.value.replace(/"/g, '\\"') : '';
|
|
561
|
-
return `${el.tagName.toLowerCase()}[${attr.name}="${value}"]`;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Build path with classes and nth-child for uniqueness
|
|
566
|
-
const path = [];
|
|
567
|
-
let current = el;
|
|
568
|
-
|
|
569
|
-
while (current && current !== document.body && current !== document.documentElement) {
|
|
570
|
-
let selector = current.tagName.toLowerCase();
|
|
571
|
-
|
|
572
|
-
// If current element has ID, use it and stop
|
|
573
|
-
if (current.id) {
|
|
574
|
-
selector = `#${current.id}`;
|
|
575
|
-
path.unshift(selector);
|
|
576
|
-
break;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Add class if available
|
|
580
|
-
if (current.className && typeof current.className === 'string') {
|
|
581
|
-
const classes = current.className.trim().split(/\s+/).filter(c => c);
|
|
582
|
-
if (classes.length > 0) {
|
|
583
|
-
// Use first class for simplicity
|
|
584
|
-
selector += `.${classes[0]}`;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Add nth-of-type if needed for uniqueness
|
|
589
|
-
if (current.parentElement) {
|
|
590
|
-
const siblings = Array.from(current.parentElement.children);
|
|
591
|
-
const sameTagSiblings = siblings.filter(s => s.tagName === current.tagName);
|
|
592
|
-
const index = sameTagSiblings.indexOf(current);
|
|
593
|
-
if (index > 0 || sameTagSiblings.length > 1) {
|
|
594
|
-
selector += `:nth-of-type(${index + 1})`;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
path.unshift(selector);
|
|
599
|
-
current = current.parentElement;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return path.join(' > ') || el.tagName.toLowerCase();
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// --- HELPER: Wait for DOM Stability (SPA Hydration) ---
|
|
606
|
-
// Waits for the DOM to stabilize before taking a snapshot
|
|
607
|
-
// Useful for React/Vue apps that render empty skeletons before hydration
|
|
608
|
-
async function waitForStability(options = {}) {
|
|
609
|
-
const {
|
|
610
|
-
minNodeCount = 500,
|
|
611
|
-
quietPeriod = 200, // milliseconds
|
|
612
|
-
maxWait = 5000 // maximum wait time
|
|
613
|
-
} = options;
|
|
614
|
-
|
|
615
|
-
const startTime = Date.now();
|
|
616
|
-
|
|
617
|
-
return new Promise((resolve) => {
|
|
618
|
-
// Check if DOM already has enough nodes
|
|
619
|
-
const nodeCount = document.querySelectorAll('*').length;
|
|
620
|
-
if (nodeCount >= minNodeCount) {
|
|
621
|
-
// DOM seems ready, but wait for quiet period to ensure stability
|
|
622
|
-
let lastChange = Date.now();
|
|
623
|
-
const observer = new MutationObserver(() => {
|
|
624
|
-
lastChange = Date.now();
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
observer.observe(document.body, {
|
|
628
|
-
childList: true,
|
|
629
|
-
subtree: true,
|
|
630
|
-
attributes: false
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
const checkStable = () => {
|
|
634
|
-
const timeSinceLastChange = Date.now() - lastChange;
|
|
635
|
-
const totalWait = Date.now() - startTime;
|
|
636
|
-
|
|
637
|
-
if (timeSinceLastChange >= quietPeriod) {
|
|
638
|
-
observer.disconnect();
|
|
639
|
-
resolve();
|
|
640
|
-
} else if (totalWait >= maxWait) {
|
|
641
|
-
observer.disconnect();
|
|
642
|
-
console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway');
|
|
643
|
-
resolve();
|
|
644
|
-
} else {
|
|
645
|
-
setTimeout(checkStable, 50);
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
checkStable();
|
|
650
|
-
} else {
|
|
651
|
-
// DOM doesn't have enough nodes yet, wait for them
|
|
652
|
-
const observer = new MutationObserver(() => {
|
|
653
|
-
const currentCount = document.querySelectorAll('*').length;
|
|
654
|
-
const totalWait = Date.now() - startTime;
|
|
655
|
-
|
|
656
|
-
if (currentCount >= minNodeCount) {
|
|
657
|
-
observer.disconnect();
|
|
658
|
-
// Now wait for quiet period
|
|
446
|
+
async function snapshot(options = {}) {
|
|
447
|
+
try {
|
|
448
|
+
!1 !== options.waitForStability && await async function(options = {}) {
|
|
449
|
+
const {minNodeCount: minNodeCount = 500, quietPeriod: quietPeriod = 200, maxWait: maxWait = 5e3} = options, startTime = Date.now();
|
|
450
|
+
try {
|
|
451
|
+
window.__sentience_lastMutationTs = performance.now();
|
|
452
|
+
} catch (e) {}
|
|
453
|
+
return new Promise(resolve => {
|
|
454
|
+
if (document.querySelectorAll("*").length >= minNodeCount) {
|
|
659
455
|
let lastChange = Date.now();
|
|
660
|
-
const
|
|
456
|
+
const observer = new MutationObserver(() => {
|
|
661
457
|
lastChange = Date.now();
|
|
458
|
+
try {
|
|
459
|
+
window.__sentience_lastMutationTs = performance.now();
|
|
460
|
+
} catch (e) {}
|
|
662
461
|
});
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
attributes: false
|
|
462
|
+
observer.observe(document.body, {
|
|
463
|
+
childList: !0,
|
|
464
|
+
subtree: !0,
|
|
465
|
+
attributes: !1
|
|
668
466
|
});
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if (timeSinceLastChange >= quietPeriod) {
|
|
675
|
-
quietObserver.disconnect();
|
|
676
|
-
resolve();
|
|
677
|
-
} else if (totalWait >= maxWait) {
|
|
678
|
-
quietObserver.disconnect();
|
|
679
|
-
console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway');
|
|
680
|
-
resolve();
|
|
681
|
-
} else {
|
|
682
|
-
setTimeout(checkQuiet, 50);
|
|
683
|
-
}
|
|
467
|
+
const checkStable = () => {
|
|
468
|
+
const timeSinceLastChange = Date.now() - lastChange, totalWait = Date.now() - startTime;
|
|
469
|
+
timeSinceLastChange >= quietPeriod || totalWait >= maxWait ? (observer.disconnect(),
|
|
470
|
+
resolve()) : setTimeout(checkStable, 50);
|
|
684
471
|
};
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
472
|
+
checkStable();
|
|
473
|
+
} else {
|
|
474
|
+
const observer = new MutationObserver(() => {
|
|
475
|
+
const currentCount = document.querySelectorAll("*").length, totalWait = Date.now() - startTime;
|
|
476
|
+
try {
|
|
477
|
+
window.__sentience_lastMutationTs = performance.now();
|
|
478
|
+
} catch (e) {}
|
|
479
|
+
if (currentCount >= minNodeCount) {
|
|
480
|
+
observer.disconnect();
|
|
481
|
+
let lastChange = Date.now();
|
|
482
|
+
const quietObserver = new MutationObserver(() => {
|
|
483
|
+
lastChange = Date.now();
|
|
484
|
+
try {
|
|
485
|
+
window.__sentience_lastMutationTs = performance.now();
|
|
486
|
+
} catch (e) {}
|
|
487
|
+
});
|
|
488
|
+
quietObserver.observe(document.body, {
|
|
489
|
+
childList: !0,
|
|
490
|
+
subtree: !0,
|
|
491
|
+
attributes: !1
|
|
492
|
+
});
|
|
493
|
+
const checkQuiet = () => {
|
|
494
|
+
const timeSinceLastChange = Date.now() - lastChange, totalWait = Date.now() - startTime;
|
|
495
|
+
timeSinceLastChange >= quietPeriod || totalWait >= maxWait ? (quietObserver.disconnect(),
|
|
496
|
+
resolve()) : setTimeout(checkQuiet, 50);
|
|
497
|
+
};
|
|
498
|
+
checkQuiet();
|
|
499
|
+
} else totalWait >= maxWait && (observer.disconnect(), resolve());
|
|
500
|
+
});
|
|
501
|
+
observer.observe(document.body, {
|
|
502
|
+
childList: !0,
|
|
503
|
+
subtree: !0,
|
|
504
|
+
attributes: !1
|
|
505
|
+
}), setTimeout(() => {
|
|
506
|
+
observer.disconnect(), resolve();
|
|
507
|
+
}, maxWait);
|
|
691
508
|
}
|
|
692
509
|
});
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (iframes.length === 0) {
|
|
725
|
-
return iframeData;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
console.log(`[SentienceAPI] Found ${iframes.length} iframe(s), requesting snapshots...`);
|
|
729
|
-
// Request snapshot from each iframe
|
|
730
|
-
const iframePromises = iframes.map((iframe, idx) => {
|
|
731
|
-
// OPTIMIZATION: Skip common ad domains to save time
|
|
732
|
-
const src = iframe.src || '';
|
|
733
|
-
if (src.includes('doubleclick') || src.includes('googleadservices') || src.includes('ads system')) {
|
|
734
|
-
console.log(`[SentienceAPI] Skipping ad iframe: ${src.substring(0, 30)}...`);
|
|
735
|
-
return Promise.resolve(null);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return new Promise((resolve) => {
|
|
739
|
-
const requestId = `iframe-${idx}-${Date.now()}`;
|
|
740
|
-
|
|
741
|
-
// 1. EXTENDED TIMEOUT (Handle slow children)
|
|
742
|
-
const timeout = setTimeout(() => {
|
|
743
|
-
console.warn(`[SentienceAPI] ⚠️ Iframe ${idx} snapshot TIMEOUT (id: ${requestId})`);
|
|
744
|
-
resolve(null);
|
|
745
|
-
}, 5000); // Increased to 5s to handle slow processing
|
|
746
|
-
|
|
747
|
-
// 2. ROBUST LISTENER with debugging
|
|
748
|
-
const listener = (event) => {
|
|
749
|
-
// Debug: Log all SENTIENCE_IFRAME_SNAPSHOT_RESPONSE messages to see what's happening
|
|
750
|
-
if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE') {
|
|
751
|
-
// Only log if it's not our request (for debugging)
|
|
752
|
-
if (event.data?.requestId !== requestId) {
|
|
753
|
-
// console.log(`[SentienceAPI] Received response for different request: ${event.data.requestId} (expected: ${requestId})`);
|
|
754
|
-
}
|
|
510
|
+
}(options.waitForStability || {});
|
|
511
|
+
const rawData = [];
|
|
512
|
+
window.sentience_registry = [];
|
|
513
|
+
getAllElements().forEach((el, idx) => {
|
|
514
|
+
if (!el.getBoundingClientRect) return;
|
|
515
|
+
const rect = el.getBoundingClientRect();
|
|
516
|
+
if (rect.width < 5 || rect.height < 5) return;
|
|
517
|
+
const tagName = el.tagName.toLowerCase();
|
|
518
|
+
if ("span" === tagName) {
|
|
519
|
+
if (el.closest("a")) return;
|
|
520
|
+
const childLink = el.querySelector("a[href]");
|
|
521
|
+
if (childLink && childLink.href) return;
|
|
522
|
+
options.debug && el.className && el.className.includes("titleline");
|
|
523
|
+
}
|
|
524
|
+
window.sentience_registry[idx] = el;
|
|
525
|
+
const inputType = "input" === tagName ? toSafeString(el.getAttribute && el.getAttribute("type") || el.type || null) : null, isPasswordInput = inputType && "password" === inputType.toLowerCase(), semanticText = function(el, options = {}) {
|
|
526
|
+
if (!el) return {
|
|
527
|
+
text: "",
|
|
528
|
+
source: null
|
|
529
|
+
};
|
|
530
|
+
const explicitAriaLabel = el.getAttribute ? el.getAttribute("aria-label") : null;
|
|
531
|
+
if (explicitAriaLabel && explicitAriaLabel.trim()) return {
|
|
532
|
+
text: explicitAriaLabel.trim(),
|
|
533
|
+
source: "explicit_aria_label"
|
|
534
|
+
};
|
|
535
|
+
if ("INPUT" === el.tagName) {
|
|
536
|
+
const t = el.getAttribute && el.getAttribute("type") || el.type || "", isPassword = "password" === String(t).toLowerCase(), value = (isPassword ? el.placeholder || "" : el.value || el.placeholder || "").trim();
|
|
537
|
+
if (value) return {
|
|
538
|
+
text: value,
|
|
539
|
+
source: isPassword ? "input_placeholder" : "input_value"
|
|
540
|
+
};
|
|
755
541
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
542
|
+
if ("IMG" === el.tagName) {
|
|
543
|
+
const alt = (el.alt || "").trim();
|
|
544
|
+
if (alt) return {
|
|
545
|
+
text: alt,
|
|
546
|
+
source: "img_alt"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const innerText = (el.innerText || "").trim();
|
|
550
|
+
if (innerText) return {
|
|
551
|
+
text: innerText.substring(0, 100),
|
|
552
|
+
source: "inner_text"
|
|
553
|
+
};
|
|
554
|
+
const inferred = getInferredLabel(el, {
|
|
555
|
+
enableInference: !1 !== options.enableInference,
|
|
556
|
+
inferenceConfig: options.inferenceConfig
|
|
557
|
+
});
|
|
558
|
+
return inferred || {
|
|
559
|
+
text: "",
|
|
560
|
+
source: null
|
|
561
|
+
};
|
|
562
|
+
}(el, {
|
|
563
|
+
enableInference: !1 !== options.enableInference,
|
|
564
|
+
inferenceConfig: options.inferenceConfig
|
|
565
|
+
}), textVal = semanticText.text || getText(el), inferredRole = function(el, options = {}) {
|
|
566
|
+
const {enableInference: enableInference = !0} = options;
|
|
567
|
+
if (!enableInference) return null;
|
|
568
|
+
if (!isInteractableElement(el)) return null;
|
|
569
|
+
const hasAriaLabel = el.getAttribute ? el.getAttribute("aria-label") : null, hasExplicitRole = el.getAttribute ? el.getAttribute("role") : null;
|
|
570
|
+
if (hasAriaLabel || hasExplicitRole) return null;
|
|
571
|
+
const tag = el.tagName.toLowerCase();
|
|
572
|
+
return [ "button", "a", "input", "textarea", "select", "option" ].includes(tag) ? null : el.onclick || el.getAttribute && el.getAttribute("onclick") || el.onkeydown || el.onkeypress || el.onkeyup || el.getAttribute && (el.getAttribute("onkeydown") || el.getAttribute("onkeypress") || el.getAttribute("onkeyup")) || el.hasAttribute && el.hasAttribute("tabindex") && ("div" === tag || "span" === tag) ? "button" : null;
|
|
573
|
+
}(el, {
|
|
574
|
+
enableInference: !1 !== options.enableInference,
|
|
575
|
+
inferenceConfig: options.inferenceConfig
|
|
576
|
+
}), inView = function(rect) {
|
|
577
|
+
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
578
|
+
}(rect), style = window.getComputedStyle(el), occluded = !!inView && function(el, rect, style) {
|
|
579
|
+
const zIndex = parseInt(style.zIndex, 10);
|
|
580
|
+
if ("static" === style.position && (isNaN(zIndex) || zIndex <= 10)) return !1;
|
|
581
|
+
const cx = rect.x + rect.width / 2, cy = rect.y + rect.height / 2;
|
|
582
|
+
if (cx < 0 || cx > window.innerWidth || cy < 0 || cy > window.innerHeight) return !1;
|
|
583
|
+
const topEl = document.elementFromPoint(cx, cy);
|
|
584
|
+
return !!topEl && !(el === topEl || el.contains(topEl) || topEl.contains(el));
|
|
585
|
+
}(el, rect, style), effectiveBgColor = function(el) {
|
|
586
|
+
if (!el) return null;
|
|
587
|
+
if ("SVG" === el.tagName) {
|
|
588
|
+
const svgColor = getSVGColor(el);
|
|
589
|
+
if (svgColor) return svgColor;
|
|
590
|
+
}
|
|
591
|
+
let current = el, depth = 0;
|
|
592
|
+
for (;current && depth < 10; ) {
|
|
593
|
+
const style = window.getComputedStyle(current);
|
|
594
|
+
if ("SVG" === current.tagName) {
|
|
595
|
+
const svgColor = getSVGColor(current);
|
|
596
|
+
if (svgColor) return svgColor;
|
|
775
597
|
}
|
|
598
|
+
const bgColor = style.backgroundColor;
|
|
599
|
+
if (bgColor && "transparent" !== bgColor && "rgba(0, 0, 0, 0)" !== bgColor) {
|
|
600
|
+
const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
601
|
+
if (!rgbaMatch) return bgColor.startsWith("rgb("), bgColor;
|
|
602
|
+
if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`;
|
|
603
|
+
}
|
|
604
|
+
current = current.parentElement, depth++;
|
|
776
605
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
// 3. SEND REQUEST with error handling
|
|
606
|
+
return null;
|
|
607
|
+
}(el);
|
|
608
|
+
let safeValue = null, valueRedacted = null;
|
|
782
609
|
try {
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
requestId: requestId,
|
|
788
|
-
options: {
|
|
789
|
-
...options,
|
|
790
|
-
collectIframes: true // Enable recursion for nested iframes
|
|
791
|
-
}
|
|
792
|
-
}, '*'); // Use '*' for cross-origin, but browser will enforce same-origin policy
|
|
793
|
-
} else {
|
|
794
|
-
console.warn(`[SentienceAPI] Iframe ${idx} contentWindow is inaccessible (Cross-Origin?)`);
|
|
795
|
-
clearTimeout(timeout);
|
|
796
|
-
window.removeEventListener('message', listener);
|
|
797
|
-
resolve(null);
|
|
610
|
+
if (void 0 !== el.value || el.getAttribute && null !== el.getAttribute("value")) if (isPasswordInput) safeValue = null,
|
|
611
|
+
valueRedacted = "true"; else {
|
|
612
|
+
const rawValue = void 0 !== el.value ? String(el.value) : String(el.getAttribute("value"));
|
|
613
|
+
safeValue = rawValue.length > 200 ? rawValue.substring(0, 200) : rawValue, valueRedacted = "false";
|
|
798
614
|
}
|
|
799
|
-
} catch (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
iframeData.set(iframes[idx], result.data);
|
|
815
|
-
console.log(`[SentienceAPI] ✓ Collected snapshot from iframe ${idx}`);
|
|
816
|
-
} else if (result && result.error) {
|
|
817
|
-
console.warn(`[SentienceAPI] Iframe ${idx} snapshot error:`, result.error);
|
|
818
|
-
} else if (!result) {
|
|
819
|
-
console.warn(`[SentienceAPI] Iframe ${idx} returned no data (timeout or error)`);
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
return iframeData;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// --- HELPER: Handle Iframe Snapshot Request (for child frames) ---
|
|
827
|
-
// When a parent frame requests snapshot, this handler responds with local snapshot
|
|
828
|
-
// NOTE: Recursion is safe because querySelectorAll('iframe') only finds direct children.
|
|
829
|
-
// Iframe A can ask Iframe B, but won't go back up to parent (no circular dependency risk).
|
|
830
|
-
function setupIframeSnapshotHandler() {
|
|
831
|
-
window.addEventListener('message', async (event) => {
|
|
832
|
-
// Security: only respond to snapshot requests from parent frames
|
|
833
|
-
if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_REQUEST') {
|
|
834
|
-
const { requestId, options } = event.data;
|
|
835
|
-
|
|
836
|
-
try {
|
|
837
|
-
// Generate snapshot for this iframe's content
|
|
838
|
-
// Allow recursive collection - querySelectorAll('iframe') only finds direct children,
|
|
839
|
-
// so Iframe A will ask Iframe B, but won't go back up to parent (safe recursion)
|
|
840
|
-
// waitForStability: false makes performance better - i.e. don't wait for children frames
|
|
841
|
-
const snapshotOptions = { ...options, collectIframes: true, waitForStability: options.waitForStability === false ? false : false };
|
|
842
|
-
const snapshot = await window.sentience.snapshot(snapshotOptions);
|
|
843
|
-
|
|
844
|
-
// Send response back to parent
|
|
845
|
-
if (event.source && event.source.postMessage) {
|
|
846
|
-
event.source.postMessage({
|
|
847
|
-
type: 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE',
|
|
848
|
-
requestId: requestId,
|
|
849
|
-
snapshot: snapshot,
|
|
850
|
-
error: null
|
|
851
|
-
}, '*');
|
|
615
|
+
} catch (e) {}
|
|
616
|
+
const accessibleName = toSafeString(function(el) {
|
|
617
|
+
if (!el || !el.getAttribute) return "";
|
|
618
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
619
|
+
if (ariaLabel && ariaLabel.trim()) return ariaLabel.trim().substring(0, 200);
|
|
620
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
621
|
+
if (labelledBy && labelledBy.trim()) {
|
|
622
|
+
const ids = labelledBy.split(/\s+/).filter(id => id.trim()), texts = [];
|
|
623
|
+
for (const id of ids) try {
|
|
624
|
+
const ref = document.getElementById(id);
|
|
625
|
+
if (!ref) continue;
|
|
626
|
+
const txt = (ref.innerText || ref.textContent || ref.getAttribute?.("aria-label") || "").toString().trim();
|
|
627
|
+
txt && texts.push(txt);
|
|
628
|
+
} catch (e) {}
|
|
629
|
+
if (texts.length > 0) return texts.join(" ").substring(0, 200);
|
|
852
630
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
631
|
+
try {
|
|
632
|
+
if (el.labels && el.labels.length > 0) {
|
|
633
|
+
const t = (el.labels[0].innerText || el.labels[0].textContent || "").toString().trim();
|
|
634
|
+
if (t) return t.substring(0, 200);
|
|
635
|
+
}
|
|
636
|
+
} catch (e) {}
|
|
637
|
+
try {
|
|
638
|
+
const parentLabel = el.closest && el.closest("label");
|
|
639
|
+
if (parentLabel) {
|
|
640
|
+
const t = (parentLabel.innerText || parentLabel.textContent || "").toString().trim();
|
|
641
|
+
if (t) return t.substring(0, 200);
|
|
642
|
+
}
|
|
643
|
+
} catch (e) {}
|
|
644
|
+
const tag = (el.tagName || "").toUpperCase();
|
|
645
|
+
if ("INPUT" === tag || "TEXTAREA" === tag) {
|
|
646
|
+
const ph = (el.getAttribute("placeholder") || "").toString().trim();
|
|
647
|
+
if (ph) return ph.substring(0, 200);
|
|
862
648
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
in_viewport: inView,
|
|
938
|
-
is_occluded: occluded
|
|
939
|
-
});
|
|
649
|
+
const title = el.getAttribute("title");
|
|
650
|
+
return title && title.trim() ? title.trim().substring(0, 200) : "";
|
|
651
|
+
}(el) || null), nearbyText = isInteractableElement(el) ? function(el, options = {}) {
|
|
652
|
+
if (!el) return null;
|
|
653
|
+
const maxLen = "number" == typeof options.maxLen ? options.maxLen : 80, ownText = normalizeNearbyText(el.innerText || ""), candidates = [], collect = node => {
|
|
654
|
+
if (!node) return;
|
|
655
|
+
let text = "";
|
|
656
|
+
try {
|
|
657
|
+
text = normalizeNearbyText(node.innerText || node.textContent || "");
|
|
658
|
+
} catch (e) {
|
|
659
|
+
text = "";
|
|
660
|
+
}
|
|
661
|
+
text && text !== ownText && candidates.push(text);
|
|
662
|
+
};
|
|
663
|
+
if (collect(el.previousElementSibling), collect(el.nextElementSibling), 0 === candidates.length && el.parentElement) {
|
|
664
|
+
let parentText = "";
|
|
665
|
+
try {
|
|
666
|
+
parentText = normalizeNearbyText(el.parentElement.innerText || "");
|
|
667
|
+
} catch (e) {
|
|
668
|
+
parentText = "";
|
|
669
|
+
}
|
|
670
|
+
parentText && parentText !== ownText && parentText.length <= 120 && candidates.push(parentText);
|
|
671
|
+
}
|
|
672
|
+
if (0 === candidates.length) return null;
|
|
673
|
+
let text = candidates[0];
|
|
674
|
+
return text.length > maxLen && (text = text.slice(0, maxLen).trim()), text || null;
|
|
675
|
+
}(el, {
|
|
676
|
+
maxLen: 80
|
|
677
|
+
}) : null;
|
|
678
|
+
rawData.push({
|
|
679
|
+
id: idx,
|
|
680
|
+
tag: tagName,
|
|
681
|
+
rect: {
|
|
682
|
+
x: rect.x,
|
|
683
|
+
y: rect.y,
|
|
684
|
+
width: rect.width,
|
|
685
|
+
height: rect.height
|
|
686
|
+
},
|
|
687
|
+
styles: {
|
|
688
|
+
display: toSafeString(style.display),
|
|
689
|
+
visibility: toSafeString(style.visibility),
|
|
690
|
+
opacity: toSafeString(style.opacity),
|
|
691
|
+
z_index: toSafeString(style.zIndex || "auto"),
|
|
692
|
+
position: toSafeString(style.position),
|
|
693
|
+
bg_color: toSafeString(effectiveBgColor || style.backgroundColor),
|
|
694
|
+
color: toSafeString(style.color),
|
|
695
|
+
cursor: toSafeString(style.cursor),
|
|
696
|
+
font_weight: toSafeString(style.fontWeight),
|
|
697
|
+
font_size: toSafeString(style.fontSize)
|
|
698
|
+
},
|
|
699
|
+
attributes: {
|
|
700
|
+
role: toSafeString(el.getAttribute("role")),
|
|
701
|
+
type_: toSafeString(el.getAttribute("type")),
|
|
702
|
+
input_type: inputType,
|
|
703
|
+
aria_label: "explicit_aria_label" === semanticText?.source ? semanticText.text : toSafeString(el.getAttribute("aria-label")),
|
|
704
|
+
name: accessibleName,
|
|
705
|
+
inferred_label: semanticText?.source && ![ "explicit_aria_label", "input_value", "img_alt", "inner_text" ].includes(semanticText.source) ? toSafeString(semanticText.text) : null,
|
|
706
|
+
label_source: semanticText?.source || null,
|
|
707
|
+
inferred_role: inferredRole ? toSafeString(inferredRole) : null,
|
|
708
|
+
nearby_text: toSafeString(nearbyText),
|
|
709
|
+
href: toSafeString(el.href || el.getAttribute("href") || el.closest && el.closest("a")?.href || null),
|
|
710
|
+
class: toSafeString(getClassName(el)),
|
|
711
|
+
value: null !== safeValue ? toSafeString(safeValue) : null,
|
|
712
|
+
value_redacted: valueRedacted,
|
|
713
|
+
checked: void 0 !== el.checked ? String(el.checked) : null,
|
|
714
|
+
disabled: void 0 !== el.disabled ? String(el.disabled) : null,
|
|
715
|
+
aria_checked: toSafeString(el.getAttribute("aria-checked")),
|
|
716
|
+
aria_disabled: toSafeString(el.getAttribute("aria-disabled")),
|
|
717
|
+
aria_expanded: toSafeString(el.getAttribute("aria-expanded"))
|
|
718
|
+
},
|
|
719
|
+
text: toSafeString(textVal),
|
|
720
|
+
in_viewport: inView,
|
|
721
|
+
is_occluded: occluded,
|
|
722
|
+
scroll_y: window.scrollY
|
|
940
723
|
});
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const iframeSrc = iframeEl.src || iframeEl.getAttribute('src') || '';
|
|
971
|
-
let isSameOrigin = false;
|
|
972
|
-
try {
|
|
973
|
-
// Try to access contentWindow to check if same-origin
|
|
974
|
-
isSameOrigin = iframeEl.contentWindow !== null;
|
|
975
|
-
} catch (e) {
|
|
976
|
-
isSameOrigin = false;
|
|
724
|
+
});
|
|
725
|
+
const allRawElements = [ ...rawData ];
|
|
726
|
+
let totalIframeElements = 0;
|
|
727
|
+
if (!1 !== options.collectIframes) try {
|
|
728
|
+
const iframeSnapshots = await async function(options = {}) {
|
|
729
|
+
const iframeData = new Map, iframes = Array.from(document.querySelectorAll("iframe"));
|
|
730
|
+
if (0 === iframes.length) return iframeData;
|
|
731
|
+
const iframePromises = iframes.map((iframe, idx) => {
|
|
732
|
+
const src = iframe.src || "";
|
|
733
|
+
return src.includes("doubleclick") || src.includes("googleadservices") || src.includes("ads system") ? Promise.resolve(null) : new Promise(resolve => {
|
|
734
|
+
const requestId = `iframe-${idx}-${Date.now()}`, timeout = setTimeout(() => {
|
|
735
|
+
resolve(null);
|
|
736
|
+
}, 5e3), listener = event => {
|
|
737
|
+
"SENTIENCE_IFRAME_SNAPSHOT_RESPONSE" === event.data?.type && event.data, "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE" === event.data?.type && event.data?.requestId === requestId && (clearTimeout(timeout),
|
|
738
|
+
window.removeEventListener("message", listener), event.data.error ? resolve(null) : (event.data.snapshot,
|
|
739
|
+
resolve({
|
|
740
|
+
iframe: iframe,
|
|
741
|
+
data: event.data.snapshot,
|
|
742
|
+
error: null
|
|
743
|
+
})));
|
|
744
|
+
};
|
|
745
|
+
window.addEventListener("message", listener);
|
|
746
|
+
try {
|
|
747
|
+
iframe.contentWindow ? iframe.contentWindow.postMessage({
|
|
748
|
+
type: "SENTIENCE_IFRAME_SNAPSHOT_REQUEST",
|
|
749
|
+
requestId: requestId,
|
|
750
|
+
options: {
|
|
751
|
+
...options,
|
|
752
|
+
collectIframes: !0
|
|
977
753
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
754
|
+
}, "*") : (clearTimeout(timeout), window.removeEventListener("message", listener),
|
|
755
|
+
resolve(null));
|
|
756
|
+
} catch (error) {
|
|
757
|
+
clearTimeout(timeout), window.removeEventListener("message", listener), resolve(null);
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
return (await Promise.all(iframePromises)).forEach((result, idx) => {
|
|
762
|
+
result && result.data && !result.error ? iframeData.set(iframes[idx], result.data) : result && result.error;
|
|
763
|
+
}), iframeData;
|
|
764
|
+
}(options);
|
|
765
|
+
iframeSnapshots.size > 0 && iframeSnapshots.forEach((iframeSnapshot, iframeEl) => {
|
|
766
|
+
if (iframeSnapshot && iframeSnapshot.raw_elements) {
|
|
767
|
+
iframeSnapshot.raw_elements.length;
|
|
768
|
+
const iframeRect = iframeEl.getBoundingClientRect(), offset = {
|
|
769
|
+
x: iframeRect.x,
|
|
770
|
+
y: iframeRect.y
|
|
771
|
+
}, iframeSrc = iframeEl.src || iframeEl.getAttribute("src") || "";
|
|
772
|
+
let isSameOrigin = !1;
|
|
773
|
+
try {
|
|
774
|
+
isSameOrigin = null !== iframeEl.contentWindow;
|
|
775
|
+
} catch (e) {
|
|
776
|
+
isSameOrigin = !1;
|
|
777
|
+
}
|
|
778
|
+
const adjustedElements = iframeSnapshot.raw_elements.map(el => {
|
|
779
|
+
const adjusted = {
|
|
780
|
+
...el
|
|
781
|
+
};
|
|
782
|
+
return adjusted.rect && (adjusted.rect = {
|
|
783
|
+
...adjusted.rect,
|
|
784
|
+
x: adjusted.rect.x + offset.x,
|
|
785
|
+
y: adjusted.rect.y + offset.y
|
|
786
|
+
}), adjusted.iframe_context = {
|
|
787
|
+
src: iframeSrc,
|
|
788
|
+
is_same_origin: isSameOrigin
|
|
789
|
+
}, adjusted;
|
|
790
|
+
});
|
|
791
|
+
allRawElements.push(...adjustedElements), totalIframeElements += adjustedElements.length;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
} catch (error) {}
|
|
795
|
+
const processed = await function(rawData, options) {
|
|
796
|
+
return new Promise((resolve, reject) => {
|
|
797
|
+
const requestId = Math.random().toString(36).substring(7);
|
|
798
|
+
let resolved = !1;
|
|
799
|
+
const timeout = setTimeout(() => {
|
|
800
|
+
resolved || (resolved = !0, window.removeEventListener("message", listener), reject(new Error("WASM processing timeout - extension may be unresponsive. Try reloading the extension.")));
|
|
801
|
+
}, 25e3), listener = e => {
|
|
802
|
+
if ("SENTIENCE_SNAPSHOT_RESULT" === e.data.type && e.data.requestId === requestId) {
|
|
803
|
+
if (resolved) return;
|
|
804
|
+
resolved = !0, clearTimeout(timeout), window.removeEventListener("message", listener),
|
|
805
|
+
e.data.error ? reject(new Error(e.data.error)) : resolve({
|
|
806
|
+
elements: e.data.elements,
|
|
807
|
+
raw_elements: e.data.raw_elements,
|
|
808
|
+
duration: e.data.duration
|
|
1005
809
|
});
|
|
1006
|
-
|
|
1007
|
-
// console.log(`[SentienceAPI] Merged ${iframeSnapshots.size} iframe(s). Total elements: ${allRawElements.length} (${rawData.length} main + ${totalIframeElements} iframe)`);
|
|
1008
810
|
}
|
|
811
|
+
};
|
|
812
|
+
window.addEventListener("message", listener);
|
|
813
|
+
try {
|
|
814
|
+
window.postMessage({
|
|
815
|
+
type: "SENTIENCE_SNAPSHOT_REQUEST",
|
|
816
|
+
requestId: requestId,
|
|
817
|
+
rawData: rawData,
|
|
818
|
+
options: options
|
|
819
|
+
}, "*");
|
|
1009
820
|
} catch (error) {
|
|
1010
|
-
|
|
821
|
+
resolved || (resolved = !0, clearTimeout(timeout), window.removeEventListener("message", listener),
|
|
822
|
+
reject(new Error(`Failed to send snapshot request: ${error.message}`)));
|
|
1011
823
|
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const iframeCount = totalIframeElements || 0;
|
|
1042
|
-
|
|
1043
|
-
console.log(`[SentienceAPI] ✓ Complete: ${totalCount} Smart Elements, ${totalRaw} Raw Elements (includes ${iframeCount} from iframes) (WASM took ${processed.duration?.toFixed(1)}ms)`);
|
|
1044
|
-
|
|
1045
|
-
return {
|
|
1046
|
-
status: "success",
|
|
1047
|
-
url: window.location.href,
|
|
1048
|
-
viewport: {
|
|
1049
|
-
width: window.innerWidth,
|
|
1050
|
-
height: window.innerHeight
|
|
824
|
+
});
|
|
825
|
+
}(allRawElements, options);
|
|
826
|
+
if (!processed || !processed.elements) throw new Error("WASM processing returned invalid result");
|
|
827
|
+
let screenshot = null;
|
|
828
|
+
options.screenshot && (screenshot = await function(options) {
|
|
829
|
+
return new Promise(resolve => {
|
|
830
|
+
const requestId = Math.random().toString(36).substring(7), listener = e => {
|
|
831
|
+
"SENTIENCE_SCREENSHOT_RESULT" === e.data.type && e.data.requestId === requestId && (window.removeEventListener("message", listener),
|
|
832
|
+
resolve(e.data.screenshot));
|
|
833
|
+
};
|
|
834
|
+
window.addEventListener("message", listener), window.postMessage({
|
|
835
|
+
type: "SENTIENCE_SCREENSHOT_REQUEST",
|
|
836
|
+
requestId: requestId,
|
|
837
|
+
options: options
|
|
838
|
+
}, "*"), setTimeout(() => {
|
|
839
|
+
window.removeEventListener("message", listener), resolve(null);
|
|
840
|
+
}, 1e4);
|
|
841
|
+
});
|
|
842
|
+
}(options.screenshot));
|
|
843
|
+
const cleanedElements = cleanElement(processed.elements), cleanedRawElements = cleanElement(processed.raw_elements);
|
|
844
|
+
cleanedElements.length, cleanedRawElements.length;
|
|
845
|
+
let diagnostics;
|
|
846
|
+
try {
|
|
847
|
+
const lastMutationTs = window.__sentience_lastMutationTs, now = performance.now(), quietMs = "number" == typeof lastMutationTs && Number.isFinite(lastMutationTs) ? Math.max(0, now - lastMutationTs) : null, nodeCount = document.querySelectorAll("*").length;
|
|
848
|
+
diagnostics = {
|
|
849
|
+
metrics: {
|
|
850
|
+
ready_state: document.readyState || null,
|
|
851
|
+
quiet_ms: quietMs,
|
|
852
|
+
node_count: nodeCount
|
|
1051
853
|
},
|
|
1052
|
-
|
|
1053
|
-
raw_elements: cleanedRawElements,
|
|
1054
|
-
screenshot: screenshot
|
|
854
|
+
captcha: detectCaptcha()
|
|
1055
855
|
};
|
|
1056
|
-
} catch (
|
|
1057
|
-
console.error('[SentienceAPI] snapshot() failed:', error);
|
|
1058
|
-
console.error('[SentienceAPI] Error stack:', error.stack);
|
|
1059
|
-
return {
|
|
1060
|
-
status: "error",
|
|
1061
|
-
error: error.message || 'Unknown error',
|
|
1062
|
-
stack: error.stack
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
},
|
|
1066
|
-
|
|
1067
|
-
// 2. Read Content (unchanged)
|
|
1068
|
-
read: (options = {}) => {
|
|
1069
|
-
const format = options.format || 'raw';
|
|
1070
|
-
let content;
|
|
1071
|
-
|
|
1072
|
-
if (format === 'raw') {
|
|
1073
|
-
content = getRawHTML(document.body);
|
|
1074
|
-
} else if (format === 'markdown') {
|
|
1075
|
-
content = convertToMarkdown(document.body);
|
|
1076
|
-
} else {
|
|
1077
|
-
content = convertToText(document.body);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
856
|
+
} catch (e) {}
|
|
1080
857
|
return {
|
|
1081
858
|
status: "success",
|
|
1082
859
|
url: window.location.href,
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
860
|
+
viewport: {
|
|
861
|
+
width: window.innerWidth,
|
|
862
|
+
height: window.innerHeight
|
|
863
|
+
},
|
|
864
|
+
elements: cleanedElements,
|
|
865
|
+
raw_elements: cleanedRawElements,
|
|
866
|
+
screenshot: screenshot,
|
|
867
|
+
diagnostics: diagnostics
|
|
1086
868
|
};
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const results = [];
|
|
1107
|
-
const searchText = caseSensitive ? text : text.toLowerCase();
|
|
1108
|
-
|
|
1109
|
-
// Helper function to find text in a single text node
|
|
1110
|
-
function findInTextNode(textNode) {
|
|
1111
|
-
const nodeText = textNode.nodeValue;
|
|
1112
|
-
const searchableText = caseSensitive ? nodeText : nodeText.toLowerCase();
|
|
1113
|
-
|
|
1114
|
-
let startIndex = 0;
|
|
1115
|
-
while (startIndex < nodeText.length && results.length < maxResults) {
|
|
1116
|
-
const foundIndex = searchableText.indexOf(searchText, startIndex);
|
|
1117
|
-
|
|
1118
|
-
if (foundIndex === -1) break;
|
|
1119
|
-
|
|
1120
|
-
// Check whole word matching if required
|
|
1121
|
-
if (wholeWord) {
|
|
1122
|
-
const before = foundIndex > 0 ? nodeText[foundIndex - 1] : ' ';
|
|
1123
|
-
const after = foundIndex + text.length < nodeText.length
|
|
1124
|
-
? nodeText[foundIndex + text.length]
|
|
1125
|
-
: ' ';
|
|
1126
|
-
|
|
1127
|
-
// Check if surrounded by word boundaries
|
|
1128
|
-
if (!/\s/.test(before) || !/\s/.test(after)) {
|
|
1129
|
-
startIndex = foundIndex + 1;
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
try {
|
|
1135
|
-
// Create range for this occurrence
|
|
1136
|
-
const range = document.createRange();
|
|
1137
|
-
range.setStart(textNode, foundIndex);
|
|
1138
|
-
range.setEnd(textNode, foundIndex + text.length);
|
|
1139
|
-
|
|
1140
|
-
const rect = range.getBoundingClientRect();
|
|
1141
|
-
|
|
1142
|
-
// Only include visible rectangles
|
|
1143
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
1144
|
-
results.push({
|
|
1145
|
-
text: nodeText.substring(foundIndex, foundIndex + text.length),
|
|
1146
|
-
rect: {
|
|
1147
|
-
x: rect.left + window.scrollX,
|
|
1148
|
-
y: rect.top + window.scrollY,
|
|
1149
|
-
width: rect.width,
|
|
1150
|
-
height: rect.height,
|
|
1151
|
-
left: rect.left + window.scrollX,
|
|
1152
|
-
top: rect.top + window.scrollY,
|
|
1153
|
-
right: rect.right + window.scrollX,
|
|
1154
|
-
bottom: rect.bottom + window.scrollY
|
|
1155
|
-
},
|
|
1156
|
-
viewport_rect: {
|
|
1157
|
-
x: rect.left,
|
|
1158
|
-
y: rect.top,
|
|
1159
|
-
width: rect.width,
|
|
1160
|
-
height: rect.height
|
|
1161
|
-
},
|
|
1162
|
-
context: {
|
|
1163
|
-
before: nodeText.substring(Math.max(0, foundIndex - 20), foundIndex),
|
|
1164
|
-
after: nodeText.substring(foundIndex + text.length, Math.min(nodeText.length, foundIndex + text.length + 20))
|
|
1165
|
-
},
|
|
1166
|
-
in_viewport: (
|
|
1167
|
-
rect.top >= 0 &&
|
|
1168
|
-
rect.left >= 0 &&
|
|
1169
|
-
rect.bottom <= window.innerHeight &&
|
|
1170
|
-
rect.right <= window.innerWidth
|
|
1171
|
-
)
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
} catch (e) {
|
|
1175
|
-
console.warn('[SentienceAPI] Failed to get rect for text:', e);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
startIndex = foundIndex + 1;
|
|
869
|
+
} catch (error) {
|
|
870
|
+
return {
|
|
871
|
+
status: "error",
|
|
872
|
+
error: error.message || "Unknown error",
|
|
873
|
+
stack: error.stack
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function read(options = {}) {
|
|
878
|
+
const format = options.format || "raw";
|
|
879
|
+
let content;
|
|
880
|
+
return content = "raw" === format ? getRawHTML(document.body) : "markdown" === format ? function(root) {
|
|
881
|
+
const rawHTML = getRawHTML(root), tempDiv = document.createElement("div");
|
|
882
|
+
tempDiv.innerHTML = rawHTML;
|
|
883
|
+
let markdown = "", insideLink = !1;
|
|
884
|
+
return function walk(node) {
|
|
885
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
886
|
+
const text = node.textContent.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ");
|
|
887
|
+
return void (text.trim() && (markdown += text));
|
|
1179
888
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
{
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const parent = node.parentElement;
|
|
1190
|
-
if (!parent) return NodeFilter.FILTER_REJECT;
|
|
1191
|
-
|
|
1192
|
-
const tagName = parent.tagName.toLowerCase();
|
|
1193
|
-
if (tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
|
|
1194
|
-
return NodeFilter.FILTER_REJECT;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// Skip whitespace-only nodes
|
|
1198
|
-
if (!node.nodeValue || node.nodeValue.trim().length === 0) {
|
|
1199
|
-
return NodeFilter.FILTER_REJECT;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
// Check if element is visible
|
|
1203
|
-
const computedStyle = window.getComputedStyle(parent);
|
|
1204
|
-
if (computedStyle.display === 'none' ||
|
|
1205
|
-
computedStyle.visibility === 'hidden' ||
|
|
1206
|
-
computedStyle.opacity === '0') {
|
|
1207
|
-
return NodeFilter.FILTER_REJECT;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
1211
|
-
}
|
|
889
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
890
|
+
const tag = node.tagName.toLowerCase();
|
|
891
|
+
if ("h1" === tag && (markdown += "\n# "), "h2" === tag && (markdown += "\n## "),
|
|
892
|
+
"h3" === tag && (markdown += "\n### "), "li" === tag && (markdown += "\n- "), insideLink || "p" !== tag && "div" !== tag && "br" !== tag || (markdown += "\n"),
|
|
893
|
+
"strong" !== tag && "b" !== tag || (markdown += "**"), "em" !== tag && "i" !== tag || (markdown += "_"),
|
|
894
|
+
"a" === tag && (markdown += "[", insideLink = !0), node.shadowRoot ? Array.from(node.shadowRoot.childNodes).forEach(walk) : node.childNodes.forEach(walk),
|
|
895
|
+
"a" === tag) {
|
|
896
|
+
const href = node.getAttribute("href");
|
|
897
|
+
markdown += href ? `](${href})` : "]", insideLink = !1;
|
|
1212
898
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
899
|
+
"strong" !== tag && "b" !== tag || (markdown += "**"), "em" !== tag && "i" !== tag || (markdown += "_"),
|
|
900
|
+
insideLink || "h1" !== tag && "h2" !== tag && "h3" !== tag && "p" !== tag && "div" !== tag || (markdown += "\n");
|
|
901
|
+
}(tempDiv), markdown.replace(/\n{3,}/g, "\n\n").trim();
|
|
902
|
+
}(document.body) : function(root) {
|
|
903
|
+
let text = "";
|
|
904
|
+
return function walk(node) {
|
|
905
|
+
if (node.nodeType !== Node.TEXT_NODE) {
|
|
906
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
907
|
+
const tag = node.tagName.toLowerCase();
|
|
908
|
+
if ([ "nav", "footer", "header", "script", "style", "noscript", "iframe", "svg" ].includes(tag)) return;
|
|
909
|
+
const style = window.getComputedStyle(node);
|
|
910
|
+
if ("none" === style.display || "hidden" === style.visibility) return;
|
|
911
|
+
const isBlock = "block" === style.display || "flex" === style.display || "P" === node.tagName || "DIV" === node.tagName;
|
|
912
|
+
isBlock && (text += " "), node.shadowRoot ? Array.from(node.shadowRoot.childNodes).forEach(walk) : node.childNodes.forEach(walk),
|
|
913
|
+
isBlock && (text += "\n");
|
|
914
|
+
}
|
|
915
|
+
} else text += node.textContent;
|
|
916
|
+
}(root || document.body), text.replace(/\n{3,}/g, "\n\n").trim();
|
|
917
|
+
}(document.body), {
|
|
918
|
+
status: "success",
|
|
919
|
+
url: window.location.href,
|
|
920
|
+
format: format,
|
|
921
|
+
content: content,
|
|
922
|
+
length: content.length
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
function findTextRect(options = {}) {
|
|
926
|
+
const {text: text, containerElement: containerElement = document.body, caseSensitive: caseSensitive = !1, wholeWord: wholeWord = !1, maxResults: maxResults = 10} = options;
|
|
927
|
+
if (!text || 0 === text.trim().length) return {
|
|
928
|
+
status: "error",
|
|
929
|
+
error: "Text parameter is required"
|
|
930
|
+
};
|
|
931
|
+
const results = [], searchText = caseSensitive ? text : text.toLowerCase();
|
|
932
|
+
function findInTextNode(textNode) {
|
|
933
|
+
const nodeText = textNode.nodeValue, searchableText = caseSensitive ? nodeText : nodeText.toLowerCase();
|
|
934
|
+
let startIndex = 0;
|
|
935
|
+
for (;startIndex < nodeText.length && results.length < maxResults; ) {
|
|
936
|
+
const foundIndex = searchableText.indexOf(searchText, startIndex);
|
|
937
|
+
if (-1 === foundIndex) break;
|
|
938
|
+
if (wholeWord) {
|
|
939
|
+
const before = foundIndex > 0 ? nodeText[foundIndex - 1] : " ", after = foundIndex + text.length < nodeText.length ? nodeText[foundIndex + text.length] : " ";
|
|
940
|
+
if (!/\s/.test(before) || !/\s/.test(after)) {
|
|
941
|
+
startIndex = foundIndex + 1;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
1233
944
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
return () => {}; // Return no-op cleanup function
|
|
945
|
+
try {
|
|
946
|
+
const range = document.createRange();
|
|
947
|
+
range.setStart(textNode, foundIndex), range.setEnd(textNode, foundIndex + text.length);
|
|
948
|
+
const rect = range.getBoundingClientRect();
|
|
949
|
+
rect.width > 0 && rect.height > 0 && results.push({
|
|
950
|
+
text: nodeText.substring(foundIndex, foundIndex + text.length),
|
|
951
|
+
rect: {
|
|
952
|
+
x: rect.left + window.scrollX,
|
|
953
|
+
y: rect.top + window.scrollY,
|
|
954
|
+
width: rect.width,
|
|
955
|
+
height: rect.height,
|
|
956
|
+
left: rect.left + window.scrollX,
|
|
957
|
+
top: rect.top + window.scrollY,
|
|
958
|
+
right: rect.right + window.scrollX,
|
|
959
|
+
bottom: rect.bottom + window.scrollY
|
|
960
|
+
},
|
|
961
|
+
viewport_rect: {
|
|
962
|
+
x: rect.left,
|
|
963
|
+
y: rect.top,
|
|
964
|
+
width: rect.width,
|
|
965
|
+
height: rect.height
|
|
966
|
+
},
|
|
967
|
+
context: {
|
|
968
|
+
before: nodeText.substring(Math.max(0, foundIndex - 20), foundIndex),
|
|
969
|
+
after: nodeText.substring(foundIndex + text.length, Math.min(nodeText.length, foundIndex + text.length + 20))
|
|
970
|
+
},
|
|
971
|
+
in_viewport: rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth
|
|
972
|
+
});
|
|
973
|
+
} catch (e) {}
|
|
974
|
+
startIndex = foundIndex + 1;
|
|
1265
975
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
highlightBox = document.createElement('div');
|
|
1277
|
-
highlightBox.id = 'sentience-highlight-box';
|
|
1278
|
-
highlightBox.style.cssText = `
|
|
1279
|
-
position: fixed;
|
|
1280
|
-
pointer-events: none;
|
|
1281
|
-
z-index: 2147483647;
|
|
1282
|
-
border: 2px solid ${highlightColor};
|
|
1283
|
-
background: rgba(255, 0, 0, 0.1);
|
|
1284
|
-
display: none;
|
|
1285
|
-
transition: all 0.1s ease;
|
|
1286
|
-
box-sizing: border-box;
|
|
1287
|
-
`;
|
|
1288
|
-
document.body.appendChild(highlightBox);
|
|
976
|
+
}
|
|
977
|
+
const walker = document.createTreeWalker(containerElement, NodeFilter.SHOW_TEXT, {
|
|
978
|
+
acceptNode(node) {
|
|
979
|
+
const parent = node.parentElement;
|
|
980
|
+
if (!parent) return NodeFilter.FILTER_REJECT;
|
|
981
|
+
const tagName = parent.tagName.toLowerCase();
|
|
982
|
+
if ("script" === tagName || "style" === tagName || "noscript" === tagName) return NodeFilter.FILTER_REJECT;
|
|
983
|
+
if (!node.nodeValue || 0 === node.nodeValue.trim().length) return NodeFilter.FILTER_REJECT;
|
|
984
|
+
const computedStyle = window.getComputedStyle(parent);
|
|
985
|
+
return "none" === computedStyle.display || "hidden" === computedStyle.visibility || "0" === computedStyle.opacity ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
|
|
1289
986
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
`;
|
|
1306
|
-
document.body.appendChild(recordingIndicator);
|
|
987
|
+
});
|
|
988
|
+
let currentNode;
|
|
989
|
+
for (;(currentNode = walker.nextNode()) && results.length < maxResults; ) findInTextNode(currentNode);
|
|
990
|
+
return {
|
|
991
|
+
status: "success",
|
|
992
|
+
query: text,
|
|
993
|
+
case_sensitive: caseSensitive,
|
|
994
|
+
whole_word: wholeWord,
|
|
995
|
+
matches: results.length,
|
|
996
|
+
results: results,
|
|
997
|
+
viewport: {
|
|
998
|
+
width: window.innerWidth,
|
|
999
|
+
height: window.innerHeight,
|
|
1000
|
+
scroll_x: window.scrollX,
|
|
1001
|
+
scroll_y: window.scrollY
|
|
1307
1002
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
id: sentienceId,
|
|
1352
|
-
selector: selector,
|
|
1353
|
-
role: role,
|
|
1354
|
-
text: text.substring(0, 50)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function click(id) {
|
|
1006
|
+
const el = window.sentience_registry[id];
|
|
1007
|
+
return !!el && (el.click(), el.focus(), !0);
|
|
1008
|
+
}
|
|
1009
|
+
function startRecording(options = {}) {
|
|
1010
|
+
const {highlightColor: highlightColor = "#ff0000", successColor: successColor = "#00ff00", autoDisableTimeout: autoDisableTimeout = 18e5, keyboardShortcut: keyboardShortcut = "Ctrl+Shift+I"} = options;
|
|
1011
|
+
if (!window.sentience_registry || 0 === window.sentience_registry.length) return alert("Registry empty. Run `await window.sentience.snapshot()` first!"),
|
|
1012
|
+
() => {};
|
|
1013
|
+
window.sentience_registry_map = new Map, window.sentience_registry.forEach((el, idx) => {
|
|
1014
|
+
el && window.sentience_registry_map.set(el, idx);
|
|
1015
|
+
});
|
|
1016
|
+
let highlightBox = document.getElementById("sentience-highlight-box");
|
|
1017
|
+
highlightBox || (highlightBox = document.createElement("div"), highlightBox.id = "sentience-highlight-box",
|
|
1018
|
+
highlightBox.style.cssText = `\n position: fixed;\n pointer-events: none;\n z-index: 2147483647;\n border: 2px solid ${highlightColor};\n background: rgba(255, 0, 0, 0.1);\n display: none;\n transition: all 0.1s ease;\n box-sizing: border-box;\n `,
|
|
1019
|
+
document.body.appendChild(highlightBox));
|
|
1020
|
+
let recordingIndicator = document.getElementById("sentience-recording-indicator");
|
|
1021
|
+
recordingIndicator || (recordingIndicator = document.createElement("div"), recordingIndicator.id = "sentience-recording-indicator",
|
|
1022
|
+
recordingIndicator.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: ${highlightColor};\n z-index: 2147483646;\n pointer-events: none;\n `,
|
|
1023
|
+
document.body.appendChild(recordingIndicator)), recordingIndicator.style.display = "block";
|
|
1024
|
+
const mouseOverHandler = e => {
|
|
1025
|
+
const el = e.target;
|
|
1026
|
+
if (!el || el === highlightBox || el === recordingIndicator) return;
|
|
1027
|
+
const rect = el.getBoundingClientRect();
|
|
1028
|
+
highlightBox.style.display = "block", highlightBox.style.top = rect.top + window.scrollY + "px",
|
|
1029
|
+
highlightBox.style.left = rect.left + window.scrollX + "px", highlightBox.style.width = rect.width + "px",
|
|
1030
|
+
highlightBox.style.height = rect.height + "px";
|
|
1031
|
+
}, clickHandler = e => {
|
|
1032
|
+
e.preventDefault(), e.stopPropagation();
|
|
1033
|
+
const el = e.target;
|
|
1034
|
+
if (!el || el === highlightBox || el === recordingIndicator) return;
|
|
1035
|
+
const sentienceId = window.sentience_registry_map.get(el);
|
|
1036
|
+
if (void 0 === sentienceId) return void alert("Element not in registry. Run `await window.sentience.snapshot()` first!");
|
|
1037
|
+
const rawData = function(el) {
|
|
1038
|
+
const style = window.getComputedStyle(el), rect = el.getBoundingClientRect();
|
|
1039
|
+
return {
|
|
1040
|
+
tag: el.tagName,
|
|
1041
|
+
rect: {
|
|
1042
|
+
x: Math.round(rect.x),
|
|
1043
|
+
y: Math.round(rect.y),
|
|
1044
|
+
width: Math.round(rect.width),
|
|
1045
|
+
height: Math.round(rect.height)
|
|
1355
1046
|
},
|
|
1356
|
-
|
|
1047
|
+
styles: {
|
|
1048
|
+
cursor: style.cursor || null,
|
|
1049
|
+
backgroundColor: style.backgroundColor || null,
|
|
1050
|
+
color: style.color || null,
|
|
1051
|
+
fontWeight: style.fontWeight || null,
|
|
1052
|
+
fontSize: style.fontSize || null,
|
|
1053
|
+
display: style.display || null,
|
|
1054
|
+
position: style.position || null,
|
|
1055
|
+
zIndex: style.zIndex || null,
|
|
1056
|
+
opacity: style.opacity || null,
|
|
1057
|
+
visibility: style.visibility || null
|
|
1058
|
+
},
|
|
1059
|
+
attributes: {
|
|
1060
|
+
role: el.getAttribute("role") || null,
|
|
1061
|
+
type: el.getAttribute("type") || null,
|
|
1062
|
+
ariaLabel: el.getAttribute("aria-label") || null,
|
|
1063
|
+
id: el.id || null,
|
|
1064
|
+
className: el.className || null
|
|
1065
|
+
}
|
|
1357
1066
|
};
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
// Flash green to indicate success
|
|
1365
|
-
highlightBox.style.border = `2px solid ${successColor}`;
|
|
1366
|
-
highlightBox.style.background = 'rgba(0, 255, 0, 0.2)';
|
|
1367
|
-
setTimeout(() => {
|
|
1368
|
-
highlightBox.style.border = `2px solid ${highlightColor}`;
|
|
1369
|
-
highlightBox.style.background = 'rgba(255, 0, 0, 0.1)';
|
|
1370
|
-
}, 500);
|
|
1371
|
-
}).catch(err => {
|
|
1372
|
-
console.error("❌ Failed to copy to clipboard:", err);
|
|
1373
|
-
alert("Failed to copy to clipboard. Check console for JSON.");
|
|
1374
|
-
});
|
|
1375
|
-
};
|
|
1376
|
-
|
|
1377
|
-
// Auto-disable timeout
|
|
1378
|
-
let timeoutId = null;
|
|
1379
|
-
|
|
1380
|
-
// Cleanup function to stop recording (defined before use)
|
|
1381
|
-
const stopRecording = () => {
|
|
1382
|
-
document.removeEventListener('mouseover', mouseOverHandler, true);
|
|
1383
|
-
document.removeEventListener('click', clickHandler, true);
|
|
1384
|
-
document.removeEventListener('keydown', keyboardHandler, true);
|
|
1385
|
-
|
|
1386
|
-
if (timeoutId) {
|
|
1387
|
-
clearTimeout(timeoutId);
|
|
1388
|
-
timeoutId = null;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
if (highlightBox) {
|
|
1392
|
-
highlightBox.style.display = 'none';
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
if (recordingIndicator) {
|
|
1396
|
-
recordingIndicator.style.display = 'none';
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// Clean up registry map (optional, but good practice)
|
|
1400
|
-
if (window.sentience_registry_map) {
|
|
1401
|
-
window.sentience_registry_map.clear();
|
|
1067
|
+
}(el), selector = function(el) {
|
|
1068
|
+
if (!el || !el.tagName) return "";
|
|
1069
|
+
if (el.id) return `#${el.id}`;
|
|
1070
|
+
for (const attr of el.attributes) if (attr.name.startsWith("data-") || "aria-label" === attr.name) {
|
|
1071
|
+
const value = attr.value ? attr.value.replace(/"/g, '\\"') : "";
|
|
1072
|
+
return `${el.tagName.toLowerCase()}[${attr.name}="${value}"]`;
|
|
1402
1073
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1074
|
+
const path = [];
|
|
1075
|
+
let current = el;
|
|
1076
|
+
for (;current && current !== document.body && current !== document.documentElement; ) {
|
|
1077
|
+
let selector = current.tagName.toLowerCase();
|
|
1078
|
+
if (current.id) {
|
|
1079
|
+
selector = `#${current.id}`, path.unshift(selector);
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
if (current.className && "string" == typeof current.className) {
|
|
1083
|
+
const classes = current.className.trim().split(/\s+/).filter(c => c);
|
|
1084
|
+
classes.length > 0 && (selector += `.${classes[0]}`);
|
|
1085
|
+
}
|
|
1086
|
+
if (current.parentElement) {
|
|
1087
|
+
const sameTagSiblings = Array.from(current.parentElement.children).filter(s => s.tagName === current.tagName), index = sameTagSiblings.indexOf(current);
|
|
1088
|
+
(index > 0 || sameTagSiblings.length > 1) && (selector += `:nth-of-type(${index + 1})`);
|
|
1089
|
+
}
|
|
1090
|
+
path.unshift(selector), current = current.parentElement;
|
|
1418
1091
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1092
|
+
return path.join(" > ") || el.tagName.toLowerCase();
|
|
1093
|
+
}(el), role = el.getAttribute("role") || el.tagName.toLowerCase(), text = getText(el), snippet = {
|
|
1094
|
+
task: `Interact with ${text.substring(0, 20)}${text.length > 20 ? "..." : ""}`,
|
|
1095
|
+
url: window.location.href,
|
|
1096
|
+
timestamp: (new Date).toISOString(),
|
|
1097
|
+
target_criteria: {
|
|
1098
|
+
id: sentienceId,
|
|
1099
|
+
selector: selector,
|
|
1100
|
+
role: role,
|
|
1101
|
+
text: text.substring(0, 50)
|
|
1102
|
+
},
|
|
1103
|
+
debug_snapshot: rawData
|
|
1104
|
+
}, jsonString = JSON.stringify(snippet, null, 2);
|
|
1105
|
+
navigator.clipboard.writeText(jsonString).then(() => {
|
|
1106
|
+
highlightBox.style.border = `2px solid ${successColor}`, highlightBox.style.background = "rgba(0, 255, 0, 0.2)",
|
|
1107
|
+
setTimeout(() => {
|
|
1108
|
+
highlightBox.style.border = `2px solid ${highlightColor}`, highlightBox.style.background = "rgba(255, 0, 0, 0.1)";
|
|
1109
|
+
}, 500);
|
|
1110
|
+
}).catch(err => {
|
|
1111
|
+
alert("Failed to copy to clipboard. Check console for JSON.");
|
|
1112
|
+
});
|
|
1113
|
+
};
|
|
1114
|
+
let timeoutId = null;
|
|
1115
|
+
const stopRecording = () => {
|
|
1116
|
+
document.removeEventListener("mouseover", mouseOverHandler, !0), document.removeEventListener("click", clickHandler, !0),
|
|
1117
|
+
document.removeEventListener("keydown", keyboardHandler, !0), timeoutId && (clearTimeout(timeoutId),
|
|
1118
|
+
timeoutId = null), highlightBox && (highlightBox.style.display = "none"), recordingIndicator && (recordingIndicator.style.display = "none"),
|
|
1119
|
+
window.sentience_registry_map && window.sentience_registry_map.clear(), window.sentience_stopRecording === stopRecording && delete window.sentience_stopRecording;
|
|
1120
|
+
}, keyboardHandler = e => {
|
|
1121
|
+
(e.ctrlKey || e.metaKey) && e.shiftKey && "I" === e.key && (e.preventDefault(),
|
|
1122
|
+
stopRecording());
|
|
1123
|
+
};
|
|
1124
|
+
return document.addEventListener("mouseover", mouseOverHandler, !0), document.addEventListener("click", clickHandler, !0),
|
|
1125
|
+
document.addEventListener("keydown", keyboardHandler, !0), autoDisableTimeout > 0 && (timeoutId = setTimeout(() => {
|
|
1126
|
+
stopRecording();
|
|
1127
|
+
}, autoDisableTimeout)), window.sentience_stopRecording = stopRecording, stopRecording;
|
|
1128
|
+
}
|
|
1129
|
+
function showOverlay(elements, targetElementId = null) {
|
|
1130
|
+
elements && Array.isArray(elements) && window.postMessage({
|
|
1131
|
+
type: "SENTIENCE_SHOW_OVERLAY",
|
|
1454
1132
|
elements: elements,
|
|
1455
1133
|
targetElementId: targetElementId,
|
|
1456
1134
|
timestamp: Date.now()
|
|
1457
|
-
},
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1135
|
+
}, "*");
|
|
1136
|
+
}
|
|
1137
|
+
function showGrid(grids, targetGridId = null) {
|
|
1138
|
+
grids && Array.isArray(grids) && window.postMessage({
|
|
1139
|
+
type: "SENTIENCE_SHOW_GRID_OVERLAY",
|
|
1140
|
+
grids: grids,
|
|
1141
|
+
targetGridId: targetGridId,
|
|
1142
|
+
timestamp: Date.now()
|
|
1143
|
+
}, "*");
|
|
1144
|
+
}
|
|
1145
|
+
function clearOverlay() {
|
|
1466
1146
|
window.postMessage({
|
|
1467
|
-
type:
|
|
1468
|
-
},
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1147
|
+
type: "SENTIENCE_CLEAR_OVERLAY"
|
|
1148
|
+
}, "*");
|
|
1149
|
+
}
|
|
1150
|
+
(async () => {
|
|
1151
|
+
const getExtensionId = () => document.documentElement.dataset.sentienceExtensionId;
|
|
1152
|
+
let extId = getExtensionId();
|
|
1153
|
+
extId || await new Promise(resolve => {
|
|
1154
|
+
const check = setInterval(() => {
|
|
1155
|
+
extId = getExtensionId(), extId && (clearInterval(check), resolve());
|
|
1156
|
+
}, 50);
|
|
1157
|
+
setTimeout(() => resolve(), 5e3);
|
|
1158
|
+
}), extId && (window.sentience_registry = [], window.sentience = {
|
|
1159
|
+
snapshot: snapshot,
|
|
1160
|
+
read: read,
|
|
1161
|
+
findTextRect: findTextRect,
|
|
1162
|
+
click: click,
|
|
1163
|
+
startRecording: startRecording,
|
|
1164
|
+
showOverlay: showOverlay,
|
|
1165
|
+
showGrid: showGrid,
|
|
1166
|
+
clearOverlay: clearOverlay
|
|
1167
|
+
}, window.sentience_iframe_handler_setup || (window.addEventListener("message", async event => {
|
|
1168
|
+
if ("SENTIENCE_IFRAME_SNAPSHOT_REQUEST" === event.data?.type) {
|
|
1169
|
+
const {requestId: requestId, options: options} = event.data;
|
|
1170
|
+
try {
|
|
1171
|
+
const snapshotOptions = {
|
|
1172
|
+
...options,
|
|
1173
|
+
collectIframes: !0,
|
|
1174
|
+
waitForStability: (options.waitForStability, !1)
|
|
1175
|
+
}, snapshot = await window.sentience.snapshot(snapshotOptions);
|
|
1176
|
+
event.source && event.source.postMessage && event.source.postMessage({
|
|
1177
|
+
type: "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE",
|
|
1178
|
+
requestId: requestId,
|
|
1179
|
+
snapshot: snapshot,
|
|
1180
|
+
error: null
|
|
1181
|
+
}, "*");
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
event.source && event.source.postMessage && event.source.postMessage({
|
|
1184
|
+
type: "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE",
|
|
1185
|
+
requestId: requestId,
|
|
1186
|
+
snapshot: null,
|
|
1187
|
+
error: error.message
|
|
1188
|
+
}, "*");
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}), window.sentience_iframe_handler_setup = !0));
|
|
1192
|
+
})();
|
|
1193
|
+
}();
|