From c4e81a7d0090bc1c13f3ed3f8da30a5821e627d7 Mon Sep 17 00:00:00 2001 From: rcholic Date: Sat, 10 Jan 2026 20:02:22 +0000 Subject: [PATCH] chore: sync extension files from sentience-chrome v2.3.0 --- sentience/extension/background.js | 306 +- sentience/extension/content.js | 449 +-- sentience/extension/injected_api.js | 2957 +++++------------ sentience/extension/manifest.json | 2 +- sentience/extension/pkg/sentience_core.js | 586 ++-- .../extension/pkg/sentience_core_bg.wasm | Bin 107941 -> 107013 bytes sentience/extension/release.json | 60 +- 7 files changed, 1286 insertions(+), 3074 deletions(-) diff --git a/sentience/extension/background.js b/sentience/extension/background.js index 10bff46..2923f55 100644 --- a/sentience/extension/background.js +++ b/sentience/extension/background.js @@ -1,240 +1,104 @@ import init, { analyze_page_with_options, analyze_page, prune_for_api } from "../pkg/sentience_core.js"; -// background.js - Service Worker with WASM (CSP-Immune!) -// This runs in an isolated environment, completely immune to page CSP policies +let wasmReady = !1, wasmInitPromise = null; - -console.log('[Sentience Background] Initializing...'); - -// Global WASM initialization state -let wasmReady = false; -let wasmInitPromise = null; - -/** - * Initialize WASM module - called once on service worker startup - * Uses static imports (not dynamic import()) which is required for Service Workers - */ async function initWASM() { - if (wasmReady) return; - if (wasmInitPromise) return wasmInitPromise; + if (!wasmReady) return wasmInitPromise || (wasmInitPromise = (async () => { + try { + globalThis.js_click_element = () => {}, await init(), wasmReady = !0; + } catch (error) { + throw error; + } + })(), wasmInitPromise); +} - wasmInitPromise = (async () => { +async function handleScreenshotCapture(_tabId, options = {}) { try { - console.log('[Sentience Background] Loading WASM module...'); - - // Define the js_click_element function that WASM expects - // In Service Workers, use 'globalThis' instead of 'window' - // In background context, we can't actually click, so we log a warning - globalThis.js_click_element = () => { - console.warn('[Sentience Background] js_click_element called in background (ignored)'); - }; - - // Initialize WASM - this calls the init() function from the static import - // The init() function handles fetching and instantiating the .wasm file - await init(); - - wasmReady = true; - console.log('[Sentience Background] ✓ WASM ready!'); - console.log( - '[Sentience Background] Available functions: analyze_page, analyze_page_with_options, prune_for_api' - ); + const {format: format = "png", quality: quality = 90} = options; + return await chrome.tabs.captureVisibleTab(null, { + format: format, + quality: quality + }); } catch (error) { - console.error('[Sentience Background] WASM initialization failed:', error); - throw error; + throw new Error(`Failed to capture screenshot: ${error.message}`); } - })(); - - return wasmInitPromise; } -// Initialize WASM on service worker startup -initWASM().catch((err) => { - console.error('[Sentience Background] Failed to initialize WASM:', err); -}); - -/** - * Message handler for all extension communication - * Includes global error handling to prevent extension crashes - */ -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - // Global error handler to prevent extension crashes - try { - // Handle screenshot requests (existing functionality) - if (request.action === 'captureScreenshot') { - handleScreenshotCapture(sender.tab.id, request.options) - .then((screenshot) => { - sendResponse({ success: true, screenshot }); - }) - .catch((error) => { - console.error('[Sentience Background] Screenshot capture failed:', error); - sendResponse({ - success: false, - error: error.message || 'Screenshot capture failed', - }); - }); - return true; // Async response - } - - // Handle WASM processing requests (NEW!) - if (request.action === 'processSnapshot') { - handleSnapshotProcessing(request.rawData, request.options) - .then((result) => { - sendResponse({ success: true, result }); - }) - .catch((error) => { - console.error('[Sentience Background] Snapshot processing failed:', error); - sendResponse({ - success: false, - error: error.message || 'Snapshot processing failed', - }); - }); - return true; // Async response - } - - // Unknown action - console.warn('[Sentience Background] Unknown action:', request.action); - sendResponse({ success: false, error: 'Unknown action' }); - return false; - } catch (error) { - // Catch any synchronous errors that might crash the extension - console.error('[Sentience Background] Fatal error in message handler:', error); - try { - sendResponse({ - success: false, - error: `Fatal error: ${error.message || 'Unknown error'}`, - }); - } catch (e) { - // If sendResponse already called, ignore - } - return false; - } -}); - -/** - * Handle screenshot capture (existing functionality) - */ -async function handleScreenshotCapture(_tabId, options = {}) { - try { - const { format = 'png', quality = 90 } = options; - - const dataUrl = await chrome.tabs.captureVisibleTab(null, { - format, - quality, - }); - - console.log( - `[Sentience Background] Screenshot captured: ${format}, size: ${dataUrl.length} bytes` - ); - return dataUrl; - } catch (error) { - console.error('[Sentience Background] Screenshot error:', error); - throw new Error(`Failed to capture screenshot: ${error.message}`); - } -} - -/** - * Handle snapshot processing with WASM (NEW!) - * This is where the magic happens - completely CSP-immune! - * Includes safeguards to prevent crashes and hangs. - * - * @param {Array} rawData - Raw element data from injected_api.js - * @param {Object} options - Snapshot options (limit, filter, etc.) - * @returns {Promise} Processed snapshot result - */ async function handleSnapshotProcessing(rawData, options = {}) { - const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs - const startTime = performance.now(); - - try { - // Safety check: limit element count to prevent hangs - if (!Array.isArray(rawData)) { - throw new Error('rawData must be an array'); - } - - if (rawData.length > MAX_ELEMENTS) { - console.warn( - `[Sentience Background] ⚠️ Large dataset: ${rawData.length} elements. Limiting to ${MAX_ELEMENTS} to prevent hangs.` - ); - rawData = rawData.slice(0, MAX_ELEMENTS); - } - - // Ensure WASM is initialized - await initWASM(); - if (!wasmReady) { - throw new Error('WASM module not initialized'); - } - - console.log( - `[Sentience Background] Processing ${rawData.length} elements with options:`, - options - ); - - // Run WASM processing using the imported functions directly - // Wrap in try-catch with timeout protection - let analyzedElements; + const startTime = performance.now(); try { - // Use a timeout wrapper to prevent infinite hangs - const wasmPromise = new Promise((resolve, reject) => { + if (!Array.isArray(rawData)) throw new Error("rawData must be an array"); + if (rawData.length > 1e4 && (rawData = rawData.slice(0, 1e4)), await initWASM(), + !wasmReady) throw new Error("WASM module not initialized"); + let analyzedElements, prunedRawData; try { - let result; - if (options.limit || options.filter) { - result = analyze_page_with_options(rawData, options); - } else { - result = analyze_page(rawData); - } - resolve(result); + const wasmPromise = new Promise((resolve, reject) => { + try { + let result; + result = options.limit || options.filter ? analyze_page_with_options(rawData, options) : analyze_page(rawData), + resolve(result); + } catch (e) { + reject(e); + } + }); + analyzedElements = await Promise.race([ wasmPromise, new Promise((_, reject) => setTimeout(() => reject(new Error("WASM processing timeout (>18s)")), 18e3)) ]); } catch (e) { - reject(e); + const errorMsg = e.message || "Unknown WASM error"; + throw new Error(`WASM analyze_page failed: ${errorMsg}`); } - }); - - // Add timeout protection (18 seconds - less than content.js timeout) - analyzedElements = await Promise.race([ - wasmPromise, - new Promise((_, reject) => - setTimeout(() => reject(new Error('WASM processing timeout (>18s)')), 18000) - ), - ]); - } catch (e) { - const errorMsg = e.message || 'Unknown WASM error'; - console.error(`[Sentience Background] WASM analyze_page failed: ${errorMsg}`, e); - throw new Error(`WASM analyze_page failed: ${errorMsg}`); + try { + prunedRawData = prune_for_api(rawData); + } catch (e) { + prunedRawData = rawData; + } + performance.now(); + return { + elements: analyzedElements, + raw_elements: prunedRawData + }; + } catch (error) { + performance.now(); + throw error; } +} - // Prune elements for API (prevents 413 errors on large sites) - let prunedRawData; +initWASM().catch(err => {}), chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { try { - prunedRawData = prune_for_api(rawData); - } catch (e) { - console.warn('[Sentience Background] prune_for_api failed, using original data:', e); - prunedRawData = rawData; + return "captureScreenshot" === request.action ? (handleScreenshotCapture(sender.tab.id, request.options).then(screenshot => { + sendResponse({ + success: !0, + screenshot: screenshot + }); + }).catch(error => { + sendResponse({ + success: !1, + error: error.message || "Screenshot capture failed" + }); + }), !0) : "processSnapshot" === request.action ? (handleSnapshotProcessing(request.rawData, request.options).then(result => { + sendResponse({ + success: !0, + result: result + }); + }).catch(error => { + sendResponse({ + success: !1, + error: error.message || "Snapshot processing failed" + }); + }), !0) : (sendResponse({ + success: !1, + error: "Unknown action" + }), !1); + } catch (error) { + try { + sendResponse({ + success: !1, + error: `Fatal error: ${error.message || "Unknown error"}` + }); + } catch (e) {} + return !1; } - - const duration = performance.now() - startTime; - console.log( - `[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned (${duration.toFixed(1)}ms)` - ); - - return { - elements: analyzedElements, - raw_elements: prunedRawData, - }; - } catch (error) { - const duration = performance.now() - startTime; - console.error(`[Sentience Background] Processing error after ${duration.toFixed(1)}ms:`, error); - throw error; - } -} - -console.log('[Sentience Background] Service worker ready'); - -// Global error handlers to prevent extension crashes -self.addEventListener('error', (event) => { - console.error('[Sentience Background] Global error caught:', event.error); - event.preventDefault(); // Prevent extension crash -}); - -self.addEventListener('unhandledrejection', (event) => { - console.error('[Sentience Background] Unhandled promise rejection:', event.reason); - event.preventDefault(); // Prevent extension crash -}); +}), self.addEventListener("error", event => { + event.preventDefault(); +}), self.addEventListener("unhandledrejection", event => { + event.preventDefault(); +}); \ No newline at end of file diff --git a/sentience/extension/content.js b/sentience/extension/content.js index 931ef5a..e94cde1 100644 --- a/sentience/extension/content.js +++ b/sentience/extension/content.js @@ -1,329 +1,126 @@ -// Sentience Chrome Extension - Content Script -// Auto-generated from modular source -(function () { - 'use strict'; - - // content.js - ISOLATED WORLD (Bridge between Main World and Background) - console.log('[Sentience Bridge] Loaded.'); - - // Detect if we're in a child frame (for iframe support) - const isChildFrame = window !== window.top; - if (isChildFrame) { - console.log('[Sentience Bridge] Running in child frame:', window.location.href); - } - - // 1. Pass Extension ID to Main World (So API knows where to find resources) - document.documentElement.dataset.sentienceExtensionId = chrome.runtime.id; - - // 2. Message Router - Handles all communication between page and background - window.addEventListener('message', (event) => { - // Security check: only accept messages from same window - if (event.source !== window) return; - - // Route different message types - switch (event.data.type) { - case 'SENTIENCE_SCREENSHOT_REQUEST': - handleScreenshotRequest(event.data); - break; - - case 'SENTIENCE_SNAPSHOT_REQUEST': - handleSnapshotRequest(event.data); - break; - - case 'SENTIENCE_SHOW_OVERLAY': - handleShowOverlay(event.data); - break; - - case 'SENTIENCE_CLEAR_OVERLAY': - handleClearOverlay(); - break; - } - }); - - /** - * Handle screenshot requests (existing functionality) - */ - function handleScreenshotRequest(data) { - chrome.runtime.sendMessage({ action: 'captureScreenshot', options: data.options }, (response) => { - window.postMessage( - { - type: 'SENTIENCE_SCREENSHOT_RESULT', - requestId: data.requestId, - screenshot: response?.success ? response.screenshot : null, - error: response?.error, - }, - '*' - ); - }); - } - - /** - * Handle snapshot processing requests (NEW!) - * Sends raw DOM data to background worker for WASM processing - * Includes timeout protection to prevent extension crashes - */ - function handleSnapshotRequest(data) { - const startTime = performance.now(); - const TIMEOUT_MS = 20000; // 20 seconds (longer than injected_api timeout) - let responded = false; - - // Timeout protection: if background doesn't respond, send error - const timeoutId = setTimeout(() => { - if (!responded) { - responded = true; - const duration = performance.now() - startTime; - console.error(`[Sentience Bridge] ⚠️ WASM processing timeout after ${duration.toFixed(1)}ms`); - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_RESULT', - requestId: data.requestId, - error: 'WASM processing timeout - background script may be unresponsive', - duration, - }, - '*' - ); - } - }, TIMEOUT_MS); - - try { - chrome.runtime.sendMessage( - { - action: 'processSnapshot', - rawData: data.rawData, - options: data.options, - }, - (response) => { - if (responded) return; // Already responded via timeout - responded = true; - clearTimeout(timeoutId); - - const duration = performance.now() - startTime; - - // Handle Chrome extension errors (e.g., background script crashed) - if (chrome.runtime.lastError) { - console.error( - '[Sentience Bridge] Chrome runtime error:', - chrome.runtime.lastError.message - ); - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_RESULT', - requestId: data.requestId, - error: `Chrome runtime error: ${chrome.runtime.lastError.message}`, - duration, - }, - '*' - ); - return; - } - - if (response?.success) { - console.log(`[Sentience Bridge] ✓ WASM processing complete in ${duration.toFixed(1)}ms`); - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_RESULT', - requestId: data.requestId, - elements: response.result.elements, - raw_elements: response.result.raw_elements, - duration, - }, - '*' - ); - } else { - console.error('[Sentience Bridge] WASM processing failed:', response?.error); - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_RESULT', - requestId: data.requestId, - error: response?.error || 'Processing failed', - duration, - }, - '*' - ); - } +!function() { + "use strict"; + window, window.top; + document.documentElement.dataset.sentienceExtensionId = chrome.runtime.id, window.addEventListener("message", event => { + var data; + if (event.source === window) switch (event.data.type) { + case "SENTIENCE_SCREENSHOT_REQUEST": + data = event.data, chrome.runtime.sendMessage({ + action: "captureScreenshot", + options: data.options + }, response => { + window.postMessage({ + type: "SENTIENCE_SCREENSHOT_RESULT", + requestId: data.requestId, + screenshot: response?.success ? response.screenshot : null, + error: response?.error + }, "*"); + }); + break; + + case "SENTIENCE_SNAPSHOT_REQUEST": + !function(data) { + const startTime = performance.now(); + let responded = !1; + const timeoutId = setTimeout(() => { + if (!responded) { + responded = !0; + const duration = performance.now() - startTime; + window.postMessage({ + type: "SENTIENCE_SNAPSHOT_RESULT", + requestId: data.requestId, + error: "WASM processing timeout - background script may be unresponsive", + duration: duration + }, "*"); + } + }, 2e4); + try { + chrome.runtime.sendMessage({ + action: "processSnapshot", + rawData: data.rawData, + options: data.options + }, response => { + if (responded) return; + responded = !0, clearTimeout(timeoutId); + const duration = performance.now() - startTime; + chrome.runtime.lastError ? window.postMessage({ + type: "SENTIENCE_SNAPSHOT_RESULT", + requestId: data.requestId, + error: `Chrome runtime error: ${chrome.runtime.lastError.message}`, + duration: duration + }, "*") : response?.success ? window.postMessage({ + type: "SENTIENCE_SNAPSHOT_RESULT", + requestId: data.requestId, + elements: response.result.elements, + raw_elements: response.result.raw_elements, + duration: duration + }, "*") : window.postMessage({ + type: "SENTIENCE_SNAPSHOT_RESULT", + requestId: data.requestId, + error: response?.error || "Processing failed", + duration: duration + }, "*"); + }); + } catch (error) { + if (!responded) { + responded = !0, clearTimeout(timeoutId); + const duration = performance.now() - startTime; + window.postMessage({ + type: "SENTIENCE_SNAPSHOT_RESULT", + requestId: data.requestId, + error: `Failed to send message: ${error.message}`, + duration: duration + }, "*"); + } + } + }(event.data); + break; + + case "SENTIENCE_SHOW_OVERLAY": + !function(data) { + const {elements: elements, targetElementId: targetElementId} = data; + if (!elements || !Array.isArray(elements)) return; + removeOverlay(); + const host = document.createElement("div"); + host.id = OVERLAY_HOST_ID, host.style.cssText = "\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n width: 100vw !important;\n height: 100vh !important;\n pointer-events: none !important;\n z-index: 2147483647 !important;\n margin: 0 !important;\n padding: 0 !important;\n ", + document.body.appendChild(host); + const shadow = host.attachShadow({ + mode: "closed" + }), maxImportance = Math.max(...elements.map(e => e.importance || 0), 1); + elements.forEach(element => { + const bbox = element.bbox; + if (!bbox) return; + const isTarget = element.id === targetElementId, isPrimary = element.visual_cues?.is_primary || !1, importance = element.importance || 0; + let color; + color = isTarget ? "#FF0000" : isPrimary ? "#0066FF" : "#00FF00"; + const importanceRatio = maxImportance > 0 ? importance / maxImportance : .5, borderOpacity = isTarget ? 1 : isPrimary ? .9 : Math.max(.4, .5 + .5 * importanceRatio), fillOpacity = .2 * borderOpacity, borderWidth = isTarget ? 2 : isPrimary ? 1.5 : Math.max(.5, Math.round(2 * importanceRatio)), hexOpacity = Math.round(255 * fillOpacity).toString(16).padStart(2, "0"), box = document.createElement("div"); + if (box.style.cssText = `\n position: absolute;\n left: ${bbox.x}px;\n top: ${bbox.y}px;\n width: ${bbox.width}px;\n height: ${bbox.height}px;\n border: ${borderWidth}px solid ${color};\n background-color: ${color}${hexOpacity};\n box-sizing: border-box;\n opacity: ${borderOpacity};\n pointer-events: none;\n `, + importance > 0 || isPrimary) { + const badge = document.createElement("span"); + badge.textContent = isPrimary ? `⭐${importance}` : `${importance}`, badge.style.cssText = `\n position: absolute;\n top: -18px;\n left: 0;\n background: ${color};\n color: white;\n font-size: 11px;\n font-weight: bold;\n padding: 2px 6px;\n font-family: Arial, sans-serif;\n border-radius: 3px;\n opacity: 0.95;\n white-space: nowrap;\n pointer-events: none;\n `, + box.appendChild(badge); + } + if (isTarget) { + const targetIndicator = document.createElement("span"); + targetIndicator.textContent = "🎯", targetIndicator.style.cssText = "\n position: absolute;\n top: -18px;\n right: 0;\n font-size: 16px;\n pointer-events: none;\n ", + box.appendChild(targetIndicator); + } + shadow.appendChild(box); + }), overlayTimeout = setTimeout(() => { + removeOverlay(); + }, 5e3); + }(event.data); + break; + + case "SENTIENCE_CLEAR_OVERLAY": + removeOverlay(); } - ); - } catch (error) { - if (!responded) { - responded = true; - clearTimeout(timeoutId); - const duration = performance.now() - startTime; - console.error('[Sentience Bridge] Exception sending message:', error); - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_RESULT', - requestId: data.requestId, - error: `Failed to send message: ${error.message}`, - duration, - }, - '*' - ); - } - } - } - - // ============================================================================ - // Visual Overlay - Shadow DOM Implementation - // ============================================================================ - - const OVERLAY_HOST_ID = 'sentience-overlay-host'; - let overlayTimeout = null; - - /** - * Show visual overlay highlighting elements using Shadow DOM - * @param {Object} data - Message data with elements and targetElementId - */ - function handleShowOverlay(data) { - const { elements, targetElementId } = data; - - if (!elements || !Array.isArray(elements)) { - console.warn('[Sentience Bridge] showOverlay: elements must be an array'); - return; - } - - removeOverlay(); - - // Create host with Shadow DOM for CSS isolation - const host = document.createElement('div'); - host.id = OVERLAY_HOST_ID; - host.style.cssText = ` - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100vw !important; - height: 100vh !important; - pointer-events: none !important; - z-index: 2147483647 !important; - margin: 0 !important; - padding: 0 !important; - `; - document.body.appendChild(host); - - // Attach shadow root (closed mode for security and CSS isolation) - const shadow = host.attachShadow({ mode: 'closed' }); - - // Calculate max importance for scaling - const maxImportance = Math.max(...elements.map((e) => e.importance || 0), 1); - - elements.forEach((element) => { - const bbox = element.bbox; - if (!bbox) return; - - const isTarget = element.id === targetElementId; - const isPrimary = element.visual_cues?.is_primary || false; - const importance = element.importance || 0; - - // Color: Red (target), Blue (primary), Green (regular) - let color; - if (isTarget) color = '#FF0000'; - else if (isPrimary) color = '#0066FF'; - else color = '#00FF00'; - - // Scale opacity and border width based on importance - const importanceRatio = maxImportance > 0 ? importance / maxImportance : 0.5; - const borderOpacity = isTarget - ? 1.0 - : isPrimary - ? 0.9 - : Math.max(0.4, 0.5 + importanceRatio * 0.5); - const fillOpacity = borderOpacity * 0.2; - const borderWidth = isTarget - ? 2 - : isPrimary - ? 1.5 - : Math.max(0.5, Math.round(importanceRatio * 2)); - - // Convert fill opacity to hex for background-color - const hexOpacity = Math.round(fillOpacity * 255) - .toString(16) - .padStart(2, '0'); - - // Create box with semi-transparent fill - const box = document.createElement('div'); - box.style.cssText = ` - position: absolute; - left: ${bbox.x}px; - top: ${bbox.y}px; - width: ${bbox.width}px; - height: ${bbox.height}px; - border: ${borderWidth}px solid ${color}; - background-color: ${color}${hexOpacity}; - box-sizing: border-box; - opacity: ${borderOpacity}; - pointer-events: none; - `; - - // Add badge showing importance score - if (importance > 0 || isPrimary) { - const badge = document.createElement('span'); - badge.textContent = isPrimary ? `⭐${importance}` : `${importance}`; - badge.style.cssText = ` - position: absolute; - top: -18px; - left: 0; - background: ${color}; - color: white; - font-size: 11px; - font-weight: bold; - padding: 2px 6px; - font-family: Arial, sans-serif; - border-radius: 3px; - opacity: 0.95; - white-space: nowrap; - pointer-events: none; - `; - box.appendChild(badge); - } - - // Add target emoji for target element - if (isTarget) { - const targetIndicator = document.createElement('span'); - targetIndicator.textContent = '🎯'; - targetIndicator.style.cssText = ` - position: absolute; - top: -18px; - right: 0; - font-size: 16px; - pointer-events: none; - `; - box.appendChild(targetIndicator); - } - - shadow.appendChild(box); }); - - console.log(`[Sentience Bridge] Overlay shown for ${elements.length} elements`); - - // Auto-remove after 5 seconds - overlayTimeout = setTimeout(() => { - removeOverlay(); - console.log('[Sentience Bridge] Overlay auto-cleared after 5 seconds'); - }, 5000); - } - - /** - * Clear overlay manually - */ - function handleClearOverlay() { - removeOverlay(); - console.log('[Sentience Bridge] Overlay cleared manually'); - } - - /** - * Remove overlay from DOM - */ - function removeOverlay() { - const existing = document.getElementById(OVERLAY_HOST_ID); - if (existing) { - existing.remove(); - } - - if (overlayTimeout) { - clearTimeout(overlayTimeout); - overlayTimeout = null; + const OVERLAY_HOST_ID = "sentience-overlay-host"; + let overlayTimeout = null; + function removeOverlay() { + const existing = document.getElementById(OVERLAY_HOST_ID); + existing && existing.remove(), overlayTimeout && (clearTimeout(overlayTimeout), + overlayTimeout = null); } - } - - // console.log('[Sentience Bridge] Ready - Extension ID:', chrome.runtime.id); - -})(); +}(); \ No newline at end of file diff --git a/sentience/extension/injected_api.js b/sentience/extension/injected_api.js index f334e0c..80b3404 100644 --- a/sentience/extension/injected_api.js +++ b/sentience/extension/injected_api.js @@ -1,2142 +1,899 @@ -// Sentience Chrome Extension - Injected API -// Auto-generated from modular source -(function () { - 'use strict'; - - // utils.js - Helper Functions (CSP-Resistant) - // All utility functions needed for DOM data collection - - // --- HELPER: Deep Walker with Native Filter --- - function getAllElements(root = document) { - const elements = []; - const filter = { - acceptNode(node) { - // Skip metadata and script/style tags - if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'HEAD'].includes(node.tagName)) { - return NodeFilter.FILTER_REJECT; +!function() { + "use strict"; + function getAllElements(root = document) { + const elements = [], filter = { + 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 + }, walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter); + for (;walker.nextNode(); ) { + const node = walker.currentNode; + node.isConnected && (elements.push(node), node.shadowRoot && elements.push(...getAllElements(node.shadowRoot))); } - // Skip deep SVG children - if (node.parentNode && node.parentNode.tagName === 'SVG' && node.tagName !== 'SVG') { - return NodeFilter.FILTER_REJECT; + return elements; + } + const DEFAULT_INFERENCE_CONFIG = { + allowedTags: [ "label", "span", "div" ], + allowedRoles: [], + allowedClassPatterns: [], + maxParentDepth: 2, + maxSiblingDistance: 1, + requireSameContainer: !0, + containerTags: [ "form", "fieldset", "div" ], + methods: { + explicitLabel: !0, + ariaLabelledby: !0, + parentTraversal: !0, + siblingProximity: !0 } - return NodeFilter.FILTER_ACCEPT; - }, }; - - const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter); - while (walker.nextNode()) { - const node = walker.currentNode; - if (node.isConnected) { - elements.push(node); - if (node.shadowRoot) elements.push(...getAllElements(node.shadowRoot)); - } - } - return elements; - } - - // ============================================================================ - // LABEL INFERENCE SYSTEM - // ============================================================================ - - // Default inference configuration (conservative - Stage 1 equivalent) - const DEFAULT_INFERENCE_CONFIG = { - // Allowed tag names that can be inference sources - allowedTags: ['label', 'span', 'div'], - - // Allowed ARIA roles that can be inference sources - allowedRoles: [], - - // Class name patterns (substring match, case-insensitive) - allowedClassPatterns: [], - - // DOM tree traversal limits - maxParentDepth: 2, // Max 2 levels up DOM tree - maxSiblingDistance: 1, // Only immediate previous/next sibling - - // Container requirements (no distance-based checks) - requireSameContainer: true, // Must share common parent - containerTags: ['form', 'fieldset', 'div'], - - // Enable/disable specific inference methods - methods: { - explicitLabel: true, // el.labels API - ariaLabelledby: true, // aria-labelledby attribute - parentTraversal: true, // Check parent/grandparent - siblingProximity: true, // Check preceding sibling (same container only) - }, - }; - - // Merge user config with defaults - function mergeInferenceConfig(userConfig = {}) { - return { - ...DEFAULT_INFERENCE_CONFIG, - ...userConfig, - methods: { - ...DEFAULT_INFERENCE_CONFIG.methods, - ...(userConfig.methods || {}), - }, - }; - } - - // Check if element matches inference source criteria - function isInferenceSource(el, config) { - if (!el || !el.tagName) return false; - - const tag = el.tagName.toLowerCase(); - const role = el.getAttribute ? el.getAttribute('role') : ''; - const className = ((el.className || '') + '').toLowerCase(); - - // Check tag name - if (config.allowedTags.includes(tag)) { - return true; - } - - // Check role - if (config.allowedRoles.length > 0 && role && config.allowedRoles.includes(role)) { - return true; - } - - // Check class patterns - if (config.allowedClassPatterns.length > 0) { - for (const pattern of config.allowedClassPatterns) { - if (className.includes(pattern.toLowerCase())) { - return true; - } - } - } - - return false; - } - - // Helper: Find common parent element - function findCommonParent(el1, el2) { - if (!el1 || !el2) return null; - - // Get document reference safely for stopping conditions - // eslint-disable-next-line no-undef - const doc = - (typeof global !== 'undefined' && global.document) || - (typeof window !== 'undefined' && window.document) || - (typeof document !== 'undefined' && document) || - null; - - const parents1 = []; - let current = el1; - // Collect all parents (including el1 itself) - while (current) { - parents1.push(current); - // Stop if no parent - if (!current.parentElement) { - break; - } - // Stop at body or documentElement if they exist - if (doc && (current === doc.body || current === doc.documentElement)) { - break; - } - current = current.parentElement; - } - - // Check if el2 or any of its parents are in parents1 - current = el2; - while (current) { - // Use indexOf for more reliable comparison (handles object identity) - if (parents1.indexOf(current) !== -1) { - return current; - } - // Stop if no parent - if (!current.parentElement) { - break; - } - // Stop at body or documentElement if they exist - if (doc && (current === doc.body || current === doc.documentElement)) { - break; - } - current = current.parentElement; - } - - return null; - } - - // Helper: Check if element is a valid container - function isValidContainer(el, validTags) { - if (!el || !el.tagName) return false; - const tag = el.tagName.toLowerCase(); - // Handle both string and object className - let className = ''; - try { - className = (el.className || '') + ''; - } catch (e) { - className = ''; - } - return ( - validTags.includes(tag) || - className.toLowerCase().includes('form') || - className.toLowerCase().includes('field') - ); - } - - // Helper: Check container requirements (no distance-based checks) - function isInSameValidContainer(element, candidate, limits) { - if (!element || !candidate) return false; - - // Check same container requirement - if (limits.requireSameContainer) { - const commonParent = findCommonParent(element, candidate); - if (!commonParent) { - return false; - } - // Check if common parent is a valid container - if (!isValidContainer(commonParent, limits.containerTags)) { - return false; - } - } - - return true; - } - - // Main inference function - function getInferredLabel(el, options = {}) { - if (!el) return null; - - const { - enableInference = true, - inferenceConfig = {}, // User-provided config, merged with defaults - } = options; - - if (!enableInference) return null; - - // OPTIMIZATION: If element already has text or aria-label, skip inference entirely - // Check this BEFORE checking labels, so we don't infer if element already has text - // Note: For INPUT elements, we check value/placeholder, not innerText - // For IMG elements, we check alt, not innerText - // For other elements, innerText is considered explicit text - const ariaLabel = el.getAttribute ? el.getAttribute('aria-label') : null; - const hasAriaLabel = ariaLabel && ariaLabel.trim(); - const hasInputValue = el.tagName === 'INPUT' && (el.value || el.placeholder); - const hasImgAlt = el.tagName === 'IMG' && el.alt; - // For non-input/img elements, check innerText - but only if it's not empty - // Access innerText safely - it might be a getter or property - let innerTextValue = ''; - try { - innerTextValue = el.innerText || ''; - } catch (e) { - // If innerText access fails, treat as empty - innerTextValue = ''; - } - const hasInnerText = - el.tagName !== 'INPUT' && el.tagName !== 'IMG' && innerTextValue && innerTextValue.trim(); - - if (hasAriaLabel || hasInputValue || hasImgAlt || hasInnerText) { - return null; - } - - // Merge config with defaults - const config = mergeInferenceConfig(inferenceConfig); - - // Method 1: Explicit label association (el.labels API) - if (config.methods.explicitLabel && el.labels && el.labels.length > 0) { - const label = el.labels[0]; - if (isInferenceSource(label, config)) { - const text = (label.innerText || '').trim(); - if (text) { - return { - text, - source: 'explicit_label', - }; - } - } - } - - // Method 2: aria-labelledby (supports space-separated list of IDs) - // NOTE: aria-labelledby is an EXPLICIT reference, so it should work with ANY element - // regardless of inference source criteria. The config only controls whether this method runs. - if (config.methods.ariaLabelledby && el.hasAttribute && el.hasAttribute('aria-labelledby')) { - const labelIdsAttr = el.getAttribute('aria-labelledby'); - if (labelIdsAttr) { - // Split by whitespace to support multiple IDs (space-separated list) - const labelIds = labelIdsAttr.split(/\s+/).filter((id) => id.trim()); - const labelTexts = []; - - // Helper function to get document.getElementById from available contexts - const getDocument = () => { - // eslint-disable-next-line no-undef - if (typeof global !== 'undefined' && global.document) { - // eslint-disable-next-line no-undef - return global.document; - } - if (typeof window !== 'undefined' && window.document) { - return window.document; - } - if (typeof document !== 'undefined') { - return document; - } - return null; - }; - - const doc = getDocument(); - if (!doc || !doc.getElementById) ; else { - // Process each ID in the space-separated list - for (const labelId of labelIds) { - if (!labelId.trim()) continue; - - let labelEl = null; - try { - labelEl = doc.getElementById(labelId); - } catch (e) { - // If getElementById fails, skip this ID - continue; - } - - // aria-labelledby is an explicit reference - use ANY element, not just those matching inference criteria - // This follows ARIA spec: aria-labelledby can reference any element in the document - if (labelEl) { - // Extract text from the referenced element - let text = ''; - try { - // Try innerText first (preferred for visible text) - text = (labelEl.innerText || '').trim(); - // Fallback to textContent if innerText is empty - if (!text && labelEl.textContent) { - text = labelEl.textContent.trim(); + function isInferenceSource(el, config) { + if (!el || !el.tagName) return !1; + const tag = el.tagName.toLowerCase(), role = el.getAttribute ? el.getAttribute("role") : "", className = ((el.className || "") + "").toLowerCase(); + if (config.allowedTags.includes(tag)) return !0; + if (config.allowedRoles.length > 0 && role && config.allowedRoles.includes(role)) return !0; + if (config.allowedClassPatterns.length > 0) for (const pattern of config.allowedClassPatterns) if (className.includes(pattern.toLowerCase())) return !0; + return !1; + } + function isInSameValidContainer(element, candidate, limits) { + if (!element || !candidate) return !1; + if (limits.requireSameContainer) { + const commonParent = function(el1, el2) { + if (!el1 || !el2) return null; + const doc = "undefined" != typeof global && global.document || "undefined" != typeof window && window.document || "undefined" != typeof document && document || null, parents1 = []; + let current = el1; + for (;current && (parents1.push(current), current.parentElement) && (!doc || current !== doc.body && current !== doc.documentElement); ) current = current.parentElement; + for (current = el2; current; ) { + if (-1 !== parents1.indexOf(current)) return current; + if (!current.parentElement) break; + if (doc && (current === doc.body || current === doc.documentElement)) break; + current = current.parentElement; } - // Fallback to aria-label if available - if (!text && labelEl.getAttribute) { - const ariaLabel = labelEl.getAttribute('aria-label'); - if (ariaLabel) { - text = ariaLabel.trim(); - } + return null; + }(element, candidate); + if (!commonParent) return !1; + if (!function(el, validTags) { + if (!el || !el.tagName) return !1; + const tag = el.tagName.toLowerCase(); + let className = ""; + try { + className = (el.className || "") + ""; + } catch (e) { + className = ""; } - } catch (e) { - // If text extraction fails, skip this element - continue; - } - - if (text) { - labelTexts.push(text); - } - } - } - } - - // Combine multiple label texts (space-separated) - if (labelTexts.length > 0) { - return { - text: labelTexts.join(' '), - source: 'aria_labelledby', - }; + return validTags.includes(tag) || className.toLowerCase().includes("form") || className.toLowerCase().includes("field"); + }(commonParent, limits.containerTags)) return !1; } - } - } - - // Method 3: Parent/grandparent traversal - if (config.methods.parentTraversal) { - let parent = el.parentElement; - let depth = 0; - while (parent && depth < config.maxParentDepth) { - if (isInferenceSource(parent, config)) { - const text = (parent.innerText || '').trim(); - if (text) { - return { - text, - source: 'parent_label', - }; - } + return !0; + } + function getInferredLabel(el, options = {}) { + if (!el) return null; + const {enableInference: enableInference = !0, inferenceConfig: inferenceConfig = {}} = options; + if (!enableInference) return null; + 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; + let innerTextValue = ""; + try { + innerTextValue = el.innerText || ""; + } catch (e) { + innerTextValue = ""; } - parent = parent.parentElement; - depth++; - } - } - - // Method 4: Preceding sibling (no distance-based checks, only DOM structure) - if (config.methods.siblingProximity) { - const prev = el.previousElementSibling; - if (prev && isInferenceSource(prev, config)) { - // Only check if they're in the same valid container (no pixel distance) - if ( - isInSameValidContainer(el, prev, { - requireSameContainer: config.requireSameContainer, - containerTags: config.containerTags, - }) - ) { - const text = (prev.innerText || '').trim(); - if (text) { + const hasInnerText = "INPUT" !== el.tagName && "IMG" !== el.tagName && innerTextValue && innerTextValue.trim(); + if (hasAriaLabel || hasInputValue || hasImgAlt || hasInnerText) return null; + const config = function(userConfig = {}) { return { - text, - source: 'sibling_label', + ...DEFAULT_INFERENCE_CONFIG, + ...userConfig, + methods: { + ...DEFAULT_INFERENCE_CONFIG.methods, + ...userConfig.methods || {} + } }; - } + }(inferenceConfig); + if (config.methods.explicitLabel && el.labels && el.labels.length > 0) { + const label = el.labels[0]; + if (isInferenceSource(label, config)) { + const text = (label.innerText || "").trim(); + if (text) return { + text: text, + source: "explicit_label" + }; + } } - } - } - - return null; - } - - // Helper: Check if element is interactable (should have role inferred) - function isInteractableElement(el) { - if (!el || !el.tagName) return false; - - const tag = el.tagName.toLowerCase(); - const role = el.getAttribute ? el.getAttribute('role') : null; - const hasTabIndex = el.hasAttribute ? el.hasAttribute('tabindex') : false; - const hasHref = el.tagName === 'A' && (el.hasAttribute ? el.hasAttribute('href') : false); - - // Native interactive elements - const interactiveTags = [ - 'button', - 'input', - 'textarea', - 'select', - 'option', - 'details', - 'summary', - 'a', - ]; - if (interactiveTags.includes(tag)) { - // For , only if it has href - if (tag === 'a' && !hasHref) return false; - return true; - } - - // Elements with explicit interactive roles - const interactiveRoles = [ - 'button', - 'link', - 'tab', - 'menuitem', - 'checkbox', - 'radio', - 'switch', - 'slider', - 'combobox', - 'textbox', - 'searchbox', - 'spinbutton', - ]; - if (role && interactiveRoles.includes(role.toLowerCase())) { - return true; - } - - // Focusable elements (tabindex makes them interactive) - if (hasTabIndex) { - return true; - } - - // Elements with event handlers (custom interactive elements) - if (el.onclick || el.onkeydown || el.onkeypress || el.onkeyup) { - return true; - } - - // Check for inline event handlers via attributes - if (el.getAttribute) { - const hasInlineHandler = - el.getAttribute('onclick') || - el.getAttribute('onkeydown') || - el.getAttribute('onkeypress') || - el.getAttribute('onkeyup'); - if (hasInlineHandler) { - return true; - } - } - - return false; - } - - // Helper: Infer ARIA role for interactable elements - function getInferredRole(el, options = {}) { - const { - enableInference = true, - // inferenceConfig reserved for future extensibility - } = options; - - if (!enableInference) return null; - - // Only infer roles for interactable elements - if (!isInteractableElement(el)) { - return null; - } - - // CRITICAL: Only infer if element has NO aria-label AND NO explicit role - const hasAriaLabel = el.getAttribute ? el.getAttribute('aria-label') : null; - const hasExplicitRole = el.getAttribute ? el.getAttribute('role') : null; - - if (hasAriaLabel || hasExplicitRole) { - return null; // Skip inference if element already has aria-label or role - } - - // If element is native semantic HTML, it already has a role - const tag = el.tagName.toLowerCase(); - const semanticTags = ['button', 'a', 'input', 'textarea', 'select', 'option']; - if (semanticTags.includes(tag)) { - return null; // Native HTML already has role - } - - // Infer role based on element behavior or context - // Check for click handlers first (most common) - if (el.onclick || (el.getAttribute && el.getAttribute('onclick'))) { - return 'button'; - } - - // Check for keyboard handlers - if ( - el.onkeydown || - el.onkeypress || - el.onkeyup || - (el.getAttribute && - (el.getAttribute('onkeydown') || el.getAttribute('onkeypress') || el.getAttribute('onkeyup'))) - ) { - return 'button'; // Default to button for keyboard-interactive elements - } - - // Focusable div/span likely needs a role - if (el.hasAttribute && el.hasAttribute('tabindex') && (tag === 'div' || tag === 'span')) { - return 'button'; // Default assumption for focusable elements - } - - return null; - } - - // --- HELPER: Smart Text Extractor --- - function getText(el) { - if (el.getAttribute('aria-label')) return el.getAttribute('aria-label'); - if (el.tagName === 'INPUT') return el.value || el.placeholder || ''; - if (el.tagName === 'IMG') return el.alt || ''; - return (el.innerText || '').replace(/\s+/g, ' ').trim().substring(0, 100); - } - - // Enhanced semantic text extractor with inference support - function getSemanticText(el, options = {}) { - if (!el) { - return { - text: '', - source: null, - }; - } - - // First check explicit aria-label (highest priority) - const explicitAriaLabel = el.getAttribute ? el.getAttribute('aria-label') : null; - if (explicitAriaLabel && explicitAriaLabel.trim()) { - return { - text: explicitAriaLabel.trim(), - source: 'explicit_aria_label', - }; - } - - // Check for existing text (visible text, input value, etc.) - // This matches the existing getText() logic - if (el.tagName === 'INPUT') { - const value = (el.value || el.placeholder || '').trim(); - if (value) { - return { - text: value, - source: 'input_value', - }; - } - } - - if (el.tagName === 'IMG') { - const alt = (el.alt || '').trim(); - if (alt) { - return { - text: alt, - source: 'img_alt', - }; - } - } - - const innerText = (el.innerText || '').trim(); - if (innerText) { - return { - text: innerText.substring(0, 100), // Match existing getText() limit - source: 'inner_text', - }; - } - - // Only try inference if we have NO explicit text/label - // Pass inferenceConfig from options to getInferredLabel - const inferred = getInferredLabel(el, { - enableInference: options.enableInference !== false, - inferenceConfig: options.inferenceConfig, // Pass config through - }); - if (inferred) { - return inferred; - } - - // Fallback: return empty with no source - return { - text: '', - source: null, - }; - } - - // --- HELPER: Safe Class Name Extractor (Handles SVGAnimatedString) --- - function getClassName(el) { - if (!el || !el.className) return ''; - - // Handle string (HTML elements) - if (typeof el.className === 'string') return el.className; - - // Handle SVGAnimatedString (SVG elements) - if (typeof el.className === 'object') { - if ('baseVal' in el.className && typeof el.className.baseVal === 'string') { - return el.className.baseVal; - } - if ('animVal' in el.className && typeof el.className.animVal === 'string') { - return el.className.animVal; - } - // Fallback: convert to string - try { - return String(el.className); - } catch (e) { - return ''; - } - } - - return ''; - } - - // --- HELPER: Paranoid String Converter (Handles SVGAnimatedString) --- - function toSafeString(value) { - if (value === null || value === undefined) return null; - - // 1. If it's already a primitive string, return it - if (typeof value === 'string') return value; - - // 2. Handle SVG objects (SVGAnimatedString, SVGAnimatedNumber, etc.) - if (typeof value === 'object') { - // Try extracting baseVal (standard SVG property) - if ('baseVal' in value && typeof value.baseVal === 'string') { - return value.baseVal; - } - // Try animVal as fallback - if ('animVal' in value && typeof value.animVal === 'string') { - return value.animVal; - } - // Fallback: Force to string (prevents WASM crash even if data is less useful) - // This prevents the "Invalid Type" crash, even if the data is "[object SVGAnimatedString]" - try { - return String(value); - } catch (e) { - return null; - } - } - - // 3. Last resort cast for primitives - try { - return String(value); - } catch (e) { - return null; - } - } - - // --- HELPER: Get SVG Fill/Stroke Color --- - // For SVG elements, get the fill or stroke color (SVGs use fill/stroke, not backgroundColor) - function getSVGColor(el) { - if (!el || el.tagName !== 'SVG') return null; - - const style = window.getComputedStyle(el); - - // Try fill first (most common for SVG icons) - const fill = style.fill; - if (fill && fill !== 'none' && fill !== 'transparent' && fill !== 'rgba(0, 0, 0, 0)') { - // Convert fill to rgb() format if needed - const rgbaMatch = fill.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); - if (rgbaMatch) { - const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; - if (alpha >= 0.9) { - return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + if (config.methods.ariaLabelledby && el.hasAttribute && el.hasAttribute("aria-labelledby")) { + const labelIdsAttr = el.getAttribute("aria-labelledby"); + if (labelIdsAttr) { + 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)(); + if (doc && doc.getElementById) for (const labelId of labelIds) { + if (!labelId.trim()) continue; + let labelEl = null; + try { + labelEl = doc.getElementById(labelId); + } catch (e) { + continue; + } + if (labelEl) { + let text = ""; + try { + if (text = (labelEl.innerText || "").trim(), !text && labelEl.textContent && (text = labelEl.textContent.trim()), + !text && labelEl.getAttribute) { + const ariaLabel = labelEl.getAttribute("aria-label"); + ariaLabel && (text = ariaLabel.trim()); + } + } catch (e) { + continue; + } + text && labelTexts.push(text); + } + } else ; + if (labelTexts.length > 0) return { + text: labelTexts.join(" "), + source: "aria_labelledby" + }; + } } - } else if (fill.startsWith('rgb(')) { - return fill; - } - } - - // Fallback to stroke if fill is not available - const stroke = style.stroke; - if (stroke && stroke !== 'none' && stroke !== 'transparent' && stroke !== 'rgba(0, 0, 0, 0)') { - const rgbaMatch = stroke.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); - if (rgbaMatch) { - const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; - if (alpha >= 0.9) { - return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + if (config.methods.parentTraversal) { + let parent = el.parentElement, depth = 0; + for (;parent && depth < config.maxParentDepth; ) { + if (isInferenceSource(parent, config)) { + const text = (parent.innerText || "").trim(); + if (text) return { + text: text, + source: "parent_label" + }; + } + parent = parent.parentElement, depth++; + } } - } else if (stroke.startsWith('rgb(')) { - return stroke; - } - } - - return null; - } - - // --- HELPER: Get Effective Background Color --- - // Traverses up the DOM tree to find the nearest non-transparent background color - // For SVGs, also checks fill/stroke properties - // This handles rgba(0,0,0,0) and transparent values that browsers commonly return - function getEffectiveBackgroundColor(el) { - if (!el) return null; - - // For SVG elements, use fill/stroke instead of backgroundColor - if (el.tagName === 'SVG') { - const svgColor = getSVGColor(el); - if (svgColor) return svgColor; - } - - let current = el; - const maxDepth = 10; // Prevent infinite loops - let depth = 0; - - while (current && depth < maxDepth) { - const style = window.getComputedStyle(current); - - // For SVG elements in the tree, also check fill/stroke - if (current.tagName === 'SVG') { - const svgColor = getSVGColor(current); - if (svgColor) return svgColor; - } - - const bgColor = style.backgroundColor; - - if (bgColor && bgColor !== 'transparent' && bgColor !== 'rgba(0, 0, 0, 0)') { - // Check if it's rgba with alpha < 1 (semi-transparent) - const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); - if (rgbaMatch) { - const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; - // If alpha is high enough (>= 0.9), consider it opaque enough - if (alpha >= 0.9) { - // Convert to rgb() format for Gateway compatibility - return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; - } - // If semi-transparent, continue up the tree - } else if (bgColor.startsWith('rgb(')) { - // Already in rgb() format, use it - return bgColor; - } else { - // Named color or other format, return as-is - return bgColor; + if (config.methods.siblingProximity) { + const prev = el.previousElementSibling; + if (prev && isInferenceSource(prev, config) && isInSameValidContainer(el, prev, { + requireSameContainer: config.requireSameContainer, + containerTags: config.containerTags + })) { + const text = (prev.innerText || "").trim(); + if (text) return { + text: text, + source: "sibling_label" + }; + } } - } - - // Move up the DOM tree - current = current.parentElement; - depth++; + return null; } - - // Fallback: return null if nothing found - return null; - } - - // --- HELPER: Viewport Check --- - function isInViewport(rect) { - return ( - rect.top < window.innerHeight && - rect.bottom > 0 && - rect.left < window.innerWidth && - rect.right > 0 - ); - } - - // --- HELPER: Occlusion Check (Optimized to avoid layout thrashing) --- - // Only checks occlusion for elements likely to be occluded (high z-index, positioned) - // This avoids forced reflow for most elements, dramatically improving performance - function isOccluded(el, rect, style) { - // Fast path: Skip occlusion check for most elements - // Only check for elements that are likely to be occluded (overlays, modals, tooltips) - const zIndex = parseInt(style.zIndex, 10); - const position = style.position; - - // Skip occlusion check for normal flow elements (vast majority) - // Only check for positioned elements or high z-index (likely overlays) - if (position === 'static' && (isNaN(zIndex) || zIndex <= 10)) { - return false; // Assume not occluded for performance + function getText(el) { + return el.getAttribute("aria-label") ? el.getAttribute("aria-label") : "INPUT" === el.tagName ? el.value || el.placeholder || "" : "IMG" === el.tagName ? el.alt || "" : (el.innerText || "").replace(/\s+/g, " ").trim().substring(0, 100); } - - // For positioned/high z-index elements, do the expensive check - const cx = rect.x + rect.width / 2; - const cy = rect.y + rect.height / 2; - - if (cx < 0 || cx > window.innerWidth || cy < 0 || cy > window.innerHeight) return false; - - const topEl = document.elementFromPoint(cx, cy); - if (!topEl) return false; - - return !(el === topEl || el.contains(topEl) || topEl.contains(el)); - } - - // --- HELPER: Screenshot Bridge --- - function captureScreenshot(options) { - return new Promise((resolve) => { - const requestId = Math.random().toString(36).substring(7); - const listener = (e) => { - if (e.data.type === 'SENTIENCE_SCREENSHOT_RESULT' && e.data.requestId === requestId) { - window.removeEventListener('message', listener); - resolve(e.data.screenshot); - } - }; - window.addEventListener('message', listener); - window.postMessage({ type: 'SENTIENCE_SCREENSHOT_REQUEST', requestId, options }, '*'); - setTimeout(() => { - window.removeEventListener('message', listener); - resolve(null); - }, 10000); // 10s timeout - }); - } - - // --- HELPER: Snapshot Processing Bridge (NEW!) --- - function processSnapshotInBackground(rawData, options) { - return new Promise((resolve, reject) => { - const requestId = Math.random().toString(36).substring(7); - const TIMEOUT_MS = 25000; // 25 seconds (longer than content.js timeout) - let resolved = false; - - const timeout = setTimeout(() => { - if (!resolved) { - resolved = true; - window.removeEventListener('message', listener); - reject( - new Error( - 'WASM processing timeout - extension may be unresponsive. Try reloading the extension.' - ) - ); - } - }, TIMEOUT_MS); - - const listener = (e) => { - if (e.data.type === 'SENTIENCE_SNAPSHOT_RESULT' && e.data.requestId === requestId) { - if (resolved) return; // Already handled - resolved = true; - clearTimeout(timeout); - window.removeEventListener('message', listener); - - if (e.data.error) { - reject(new Error(e.data.error)); - } else { - resolve({ - elements: e.data.elements, - raw_elements: e.data.raw_elements, - duration: e.data.duration, - }); - } - } - }; - - window.addEventListener('message', listener); - - try { - window.postMessage( - { - type: 'SENTIENCE_SNAPSHOT_REQUEST', - requestId, - rawData, - options, - }, - '*' - ); - } catch (error) { - if (!resolved) { - resolved = true; - clearTimeout(timeout); - window.removeEventListener('message', listener); - reject(new Error(`Failed to send snapshot request: ${error.message}`)); - } - } - }); - } - - // --- HELPER: Raw HTML Extractor (unchanged) --- - function getRawHTML(root) { - const sourceRoot = root || document.body; - const clone = sourceRoot.cloneNode(true); - - const unwantedTags = ['nav', 'footer', 'header', 'script', 'style', 'noscript', 'iframe', 'svg']; - unwantedTags.forEach((tag) => { - const elements = clone.querySelectorAll(tag); - elements.forEach((el) => { - if (el.parentNode) el.parentNode.removeChild(el); - }); - }); - - // Remove invisible elements - const invisibleSelectors = []; - const walker = document.createTreeWalker(sourceRoot, NodeFilter.SHOW_ELEMENT, null, false); - let node; - while ((node = walker.nextNode())) { - const tag = node.tagName.toLowerCase(); - if (tag === 'head' || tag === 'title') continue; - - const style = window.getComputedStyle(node); - if ( - style.display === 'none' || - style.visibility === 'hidden' || - (node.offsetWidth === 0 && node.offsetHeight === 0) - ) { - let selector = tag; - if (node.id) { - selector = `#${node.id}`; - } else if (node.className && typeof node.className === 'string') { - const classes = node.className - .trim() - .split(/\s+/) - .filter((c) => c); - if (classes.length > 0) { - selector = `${tag}.${classes.join('.')}`; - } + function getClassName(el) { + if (!el || !el.className) return ""; + if ("string" == typeof el.className) return el.className; + if ("object" == typeof el.className) { + if ("baseVal" in el.className && "string" == typeof el.className.baseVal) return el.className.baseVal; + if ("animVal" in el.className && "string" == typeof el.className.animVal) return el.className.animVal; + try { + return String(el.className); + } catch (e) { + return ""; + } } - invisibleSelectors.push(selector); - } - } - - invisibleSelectors.forEach((selector) => { - try { - const elements = clone.querySelectorAll(selector); - elements.forEach((el) => { - if (el.parentNode) el.parentNode.removeChild(el); - }); - } catch (e) { - // Invalid selector, skip - } - }); - - // Resolve relative URLs - const links = clone.querySelectorAll('a[href]'); - links.forEach((link) => { - const href = link.getAttribute('href'); - if ( - href && - !href.startsWith('http://') && - !href.startsWith('https://') && - !href.startsWith('#') - ) { - try { - link.setAttribute('href', new URL(href, document.baseURI).href); - } catch (e) { - // Ignore invalid URLs + return ""; + } + function toSafeString(value) { + if (null == value) return null; + if ("string" == typeof value) return value; + if ("object" == typeof value) { + if ("baseVal" in value && "string" == typeof value.baseVal) return value.baseVal; + if ("animVal" in value && "string" == typeof value.animVal) return value.animVal; + try { + return String(value); + } catch (e) { + return null; + } } - } - }); - - const images = clone.querySelectorAll('img[src]'); - images.forEach((img) => { - const src = img.getAttribute('src'); - if ( - src && - !src.startsWith('http://') && - !src.startsWith('https://') && - !src.startsWith('data:') - ) { try { - img.setAttribute('src', new URL(src, document.baseURI).href); + return String(value); } catch (e) { - // Ignore invalid URLs + return null; } - } - }); - - return clone.innerHTML; - } - - // --- HELPER: Markdown Converter (unchanged) --- - function convertToMarkdown(root) { - const rawHTML = getRawHTML(root); - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = rawHTML; - - let markdown = ''; - let insideLink = false; - - function walk(node) { - if (node.nodeType === Node.TEXT_NODE) { - const text = node.textContent.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' '); - if (text.trim()) markdown += text; - return; - } - - if (node.nodeType !== Node.ELEMENT_NODE) return; - - const tag = node.tagName.toLowerCase(); - - // Prefix - if (tag === 'h1') markdown += '\n# '; - if (tag === 'h2') markdown += '\n## '; - if (tag === 'h3') markdown += '\n### '; - if (tag === 'li') markdown += '\n- '; - if (!insideLink && (tag === 'p' || tag === 'div' || tag === 'br')) markdown += '\n'; - if (tag === 'strong' || tag === 'b') markdown += '**'; - if (tag === 'em' || tag === 'i') markdown += '_'; - if (tag === 'a') { - markdown += '['; - insideLink = true; - } - - // Children - if (node.shadowRoot) { - Array.from(node.shadowRoot.childNodes).forEach(walk); - } else { - node.childNodes.forEach(walk); - } - - // Suffix - if (tag === 'a') { - const href = node.getAttribute('href'); - if (href) markdown += `](${href})`; - else markdown += ']'; - insideLink = false; - } - if (tag === 'strong' || tag === 'b') markdown += '**'; - if (tag === 'em' || tag === 'i') markdown += '_'; - if ( - !insideLink && - (tag === 'h1' || tag === 'h2' || tag === 'h3' || tag === 'p' || tag === 'div') - ) - markdown += '\n'; } - - walk(tempDiv); - return markdown.replace(/\n{3,}/g, '\n\n').trim(); - } - - // --- HELPER: Text Extractor (unchanged) --- - function convertToText(root) { - let text = ''; - function walk(node) { - if (node.nodeType === Node.TEXT_NODE) { - text += node.textContent; - return; - } - if (node.nodeType === Node.ELEMENT_NODE) { - const tag = node.tagName.toLowerCase(); - if (['nav', 'footer', 'header', 'script', 'style', 'noscript', 'iframe', 'svg'].includes(tag)) - return; - - const style = window.getComputedStyle(node); - if (style.display === 'none' || style.visibility === 'hidden') return; - - const isBlock = - style.display === 'block' || - style.display === 'flex' || - node.tagName === 'P' || - node.tagName === 'DIV'; - if (isBlock) text += ' '; - - if (node.shadowRoot) { - Array.from(node.shadowRoot.childNodes).forEach(walk); - } else { - node.childNodes.forEach(walk); + function getSVGColor(el) { + if (!el || "SVG" !== el.tagName) return null; + const style = window.getComputedStyle(el), fill = style.fill; + if (fill && "none" !== fill && "transparent" !== fill && "rgba(0, 0, 0, 0)" !== fill) { + const rgbaMatch = fill.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (rgbaMatch) { + if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } else if (fill.startsWith("rgb(")) return fill; } - - if (isBlock) text += '\n'; - } - } - walk(root || document.body); - return text.replace(/\n{3,}/g, '\n\n').trim(); - } - - // --- HELPER: Clean null/undefined fields --- - function cleanElement(obj) { - if (Array.isArray(obj)) { - return obj.map(cleanElement); - } - if (obj !== null && typeof obj === 'object') { - const cleaned = {}; - for (const [key, value] of Object.entries(obj)) { - if (value !== null && value !== undefined) { - if (typeof value === 'object') { - const deepClean = cleanElement(value); - if (Object.keys(deepClean).length > 0) { - cleaned[key] = deepClean; - } - } else { - cleaned[key] = value; - } + const stroke = style.stroke; + if (stroke && "none" !== stroke && "transparent" !== stroke && "rgba(0, 0, 0, 0)" !== stroke) { + const rgbaMatch = stroke.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (rgbaMatch) { + if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } else if (stroke.startsWith("rgb(")) return stroke; } - } - return cleaned; - } - return obj; - } - - // --- HELPER: Extract Raw Element Data (for Golden Set) --- - function extractRawElementData(el) { - const style = window.getComputedStyle(el); - const rect = el.getBoundingClientRect(); - - return { - tag: el.tagName, - rect: { - x: Math.round(rect.x), - y: Math.round(rect.y), - width: Math.round(rect.width), - height: Math.round(rect.height), - }, - styles: { - cursor: style.cursor || null, - backgroundColor: style.backgroundColor || null, - color: style.color || null, - fontWeight: style.fontWeight || null, - fontSize: style.fontSize || null, - display: style.display || null, - position: style.position || null, - zIndex: style.zIndex || null, - opacity: style.opacity || null, - visibility: style.visibility || null, - }, - attributes: { - role: el.getAttribute('role') || null, - type: el.getAttribute('type') || null, - ariaLabel: el.getAttribute('aria-label') || null, - id: el.id || null, - className: el.className || null, - }, - }; - } - - // --- HELPER: Generate Unique CSS Selector (for Golden Set) --- - function getUniqueSelector(el) { - if (!el || !el.tagName) return ''; - - // If element has a unique ID, use it - if (el.id) { - return `#${el.id}`; - } - - // Try data attributes or aria-label for uniqueness - for (const attr of el.attributes) { - if (attr.name.startsWith('data-') || attr.name === 'aria-label') { - const value = attr.value ? attr.value.replace(/"/g, '\\"') : ''; - return `${el.tagName.toLowerCase()}[${attr.name}="${value}"]`; - } - } - - // Build path with classes and nth-child for uniqueness - const path = []; - let current = el; - - while (current && current !== document.body && current !== document.documentElement) { - let selector = current.tagName.toLowerCase(); - - // If current element has ID, use it and stop - if (current.id) { - selector = `#${current.id}`; - path.unshift(selector); - break; - } - - // Add class if available - if (current.className && typeof current.className === 'string') { - const classes = current.className - .trim() - .split(/\s+/) - .filter((c) => c); - if (classes.length > 0) { - // Use first class for simplicity - selector += `.${classes[0]}`; - } - } - - // Add nth-of-type if needed for uniqueness - if (current.parentElement) { - const siblings = Array.from(current.parentElement.children); - const sameTagSiblings = siblings.filter((s) => s.tagName === current.tagName); - const index = sameTagSiblings.indexOf(current); - if (index > 0 || sameTagSiblings.length > 1) { - selector += `:nth-of-type(${index + 1})`; - } - } - - path.unshift(selector); - current = current.parentElement; + return null; } - - return path.join(' > ') || el.tagName.toLowerCase(); - } - - // --- HELPER: Wait for DOM Stability (SPA Hydration) --- - // Waits for the DOM to stabilize before taking a snapshot - // Useful for React/Vue apps that render empty skeletons before hydration - async function waitForStability(options = {}) { - const { - minNodeCount = 500, - quietPeriod = 200, // milliseconds - maxWait = 5000, // maximum wait time - } = options; - - const startTime = Date.now(); - - return new Promise((resolve) => { - // Check if DOM already has enough nodes - const nodeCount = document.querySelectorAll('*').length; - if (nodeCount >= minNodeCount) { - // DOM seems ready, but wait for quiet period to ensure stability - let lastChange = Date.now(); - const observer = new MutationObserver(() => { - lastChange = Date.now(); - }); - - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: false, - }); - - const checkStable = () => { - const timeSinceLastChange = Date.now() - lastChange; - const totalWait = Date.now() - startTime; - - if (timeSinceLastChange >= quietPeriod) { - observer.disconnect(); - resolve(); - } else if (totalWait >= maxWait) { - observer.disconnect(); - console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway'); - resolve(); - } else { - setTimeout(checkStable, 50); - } - }; - - checkStable(); - } else { - // DOM doesn't have enough nodes yet, wait for them - const observer = new MutationObserver(() => { - const currentCount = document.querySelectorAll('*').length; - const totalWait = Date.now() - startTime; - - if (currentCount >= minNodeCount) { - observer.disconnect(); - // Now wait for quiet period - let lastChange = Date.now(); - const quietObserver = new MutationObserver(() => { - lastChange = Date.now(); - }); - - quietObserver.observe(document.body, { - childList: true, - subtree: true, - attributes: false, + function getRawHTML(root) { + const sourceRoot = root || document.body, clone = sourceRoot.cloneNode(!0); + [ "nav", "footer", "header", "script", "style", "noscript", "iframe", "svg" ].forEach(tag => { + clone.querySelectorAll(tag).forEach(el => { + el.parentNode && el.parentNode.removeChild(el); }); - - const checkQuiet = () => { - const timeSinceLastChange = Date.now() - lastChange; - const totalWait = Date.now() - startTime; - - if (timeSinceLastChange >= quietPeriod) { - quietObserver.disconnect(); - resolve(); - } else if (totalWait >= maxWait) { - quietObserver.disconnect(); - console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway'); - resolve(); - } else { - setTimeout(checkQuiet, 50); - } - }; - - checkQuiet(); - } else if (totalWait >= maxWait) { - observer.disconnect(); - console.warn('[SentienceAPI] DOM node count timeout - proceeding anyway'); - resolve(); - } }); - - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: false, - }); - - // Timeout fallback - setTimeout(() => { - observer.disconnect(); - console.warn('[SentienceAPI] DOM stability max wait reached - proceeding'); - resolve(); - }, maxWait); - } - }); - } - - // --- HELPER: Collect Iframe Snapshots (Frame Stitching) --- - // Recursively collects snapshot data from all child iframes - // This enables detection of elements inside iframes (e.g., Stripe forms) - // - // NOTE: Cross-origin iframes cannot be accessed due to browser security (Same-Origin Policy). - // Only same-origin iframes will return snapshot data. Cross-origin iframes will be skipped - // with a warning. For cross-origin iframes, users must manually switch frames using - // Playwright's page.frame() API. - async function collectIframeSnapshots(options = {}) { - const iframeData = new Map(); // Map of iframe element -> snapshot data - - // Find all iframe elements in current document - const iframes = Array.from(document.querySelectorAll('iframe')); - - if (iframes.length === 0) { - return iframeData; - } - - console.log(`[SentienceAPI] Found ${iframes.length} iframe(s), requesting snapshots...`); - // Request snapshot from each iframe - const iframePromises = iframes.map((iframe, idx) => { - // OPTIMIZATION: Skip common ad domains to save time - const src = iframe.src || ''; - if ( - src.includes('doubleclick') || - src.includes('googleadservices') || - src.includes('ads system') - ) { - console.log(`[SentienceAPI] Skipping ad iframe: ${src.substring(0, 30)}...`); - return Promise.resolve(null); - } - - return new Promise((resolve) => { - const requestId = `iframe-${idx}-${Date.now()}`; - - // 1. EXTENDED TIMEOUT (Handle slow children) - const timeout = setTimeout(() => { - console.warn(`[SentienceAPI] ⚠️ Iframe ${idx} snapshot TIMEOUT (id: ${requestId})`); - resolve(null); - }, 5000); // Increased to 5s to handle slow processing - - // 2. ROBUST LISTENER with debugging - const listener = (event) => { - // Debug: Log all SENTIENCE_IFRAME_SNAPSHOT_RESPONSE messages to see what's happening - if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE') { - // Only log if it's not our request (for debugging) - if (event.data?.requestId !== requestId) ; - } - - // Check if this is the response we're waiting for - if ( - event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE' && - event.data?.requestId === requestId - ) { - clearTimeout(timeout); - window.removeEventListener('message', listener); - - if (event.data.error) { - console.warn(`[SentienceAPI] Iframe ${idx} returned error:`, event.data.error); - resolve(null); - } else { - const elementCount = event.data.snapshot?.raw_elements?.length || 0; - console.log( - `[SentienceAPI] ✓ Received ${elementCount} elements from Iframe ${idx} (id: ${requestId})` - ); - resolve({ - iframe, - data: event.data.snapshot, - error: null, - }); + const invisibleSelectors = [], walker = document.createTreeWalker(sourceRoot, NodeFilter.SHOW_ELEMENT, null, !1); + let node; + for (;node = walker.nextNode(); ) { + const tag = node.tagName.toLowerCase(); + if ("head" === tag || "title" === tag) continue; + const style = window.getComputedStyle(node); + if ("none" === style.display || "hidden" === style.visibility || 0 === node.offsetWidth && 0 === node.offsetHeight) { + let selector = tag; + if (node.id) selector = `#${node.id}`; else if (node.className && "string" == typeof node.className) { + const classes = node.className.trim().split(/\s+/).filter(c => c); + classes.length > 0 && (selector = `${tag}.${classes.join(".")}`); + } + invisibleSelectors.push(selector); } - } - }; - - window.addEventListener('message', listener); - - // 3. SEND REQUEST with error handling - try { - if (iframe.contentWindow) { - // console.log(`[SentienceAPI] Sending request to Iframe ${idx} (id: ${requestId})`); - iframe.contentWindow.postMessage( - { - type: 'SENTIENCE_IFRAME_SNAPSHOT_REQUEST', - requestId, - options: { - ...options, - collectIframes: true, // Enable recursion for nested iframes - }, - }, - '*' - ); // Use '*' for cross-origin, but browser will enforce same-origin policy - } else { - console.warn( - `[SentienceAPI] Iframe ${idx} contentWindow is inaccessible (Cross-Origin?)` - ); - clearTimeout(timeout); - window.removeEventListener('message', listener); - resolve(null); - } - } catch (error) { - console.error(`[SentienceAPI] Failed to postMessage to Iframe ${idx}:`, error); - clearTimeout(timeout); - window.removeEventListener('message', listener); - resolve(null); } - }); - }); - - // Wait for all iframe responses - const results = await Promise.all(iframePromises); - - // Store iframe data - results.forEach((result, idx) => { - if (result && result.data && !result.error) { - iframeData.set(iframes[idx], result.data); - console.log(`[SentienceAPI] ✓ Collected snapshot from iframe ${idx}`); - } else if (result && result.error) { - console.warn(`[SentienceAPI] Iframe ${idx} snapshot error:`, result.error); - } else if (!result) { - console.warn(`[SentienceAPI] Iframe ${idx} returned no data (timeout or error)`); - } - }); - - return iframeData; - } - - // --- HELPER: Handle Iframe Snapshot Request (for child frames) --- - // When a parent frame requests snapshot, this handler responds with local snapshot - // NOTE: Recursion is safe because querySelectorAll('iframe') only finds direct children. - // Iframe A can ask Iframe B, but won't go back up to parent (no circular dependency risk). - function setupIframeSnapshotHandler() { - window.addEventListener('message', async (event) => { - // Security: only respond to snapshot requests from parent frames - if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_REQUEST') { - const { requestId, options } = event.data; - - try { - // Generate snapshot for this iframe's content - // Allow recursive collection - querySelectorAll('iframe') only finds direct children, - // so Iframe A will ask Iframe B, but won't go back up to parent (safe recursion) - // waitForStability: false makes performance better - i.e. don't wait for children frames - const snapshotOptions = { - ...options, - collectIframes: true, - waitForStability: options.waitForStability === false ? false : false, - }; - const snapshot = await window.sentience.snapshot(snapshotOptions); - - // Send response back to parent - if (event.source && event.source.postMessage) { - event.source.postMessage( - { - type: 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE', - requestId, - snapshot, - error: null, - }, - '*' - ); - } - } catch (error) { - // Send error response - if (event.source && event.source.postMessage) { - event.source.postMessage( - { - type: 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE', - requestId, - snapshot: null, - error: error.message, - }, - '*' - ); - } - } - } - }); - } - - // snapshot.js - Snapshot Method (Main DOM Collection Logic) - - // 1. Geometry snapshot (NEW ARCHITECTURE - No WASM in Main World!) - async function snapshot(options = {}) { - try { - // Step 0: Wait for DOM stability if requested (for SPA hydration) - if (options.waitForStability !== false) { - await waitForStability(options.waitForStability || {}); - } - - // Step 1: Collect raw DOM data (Main World - CSP can't block this!) - const rawData = []; - window.sentience_registry = []; - - const nodes = getAllElements(); - - nodes.forEach((el, idx) => { - if (!el.getBoundingClientRect) return; - const rect = el.getBoundingClientRect(); - if (rect.width < 5 || rect.height < 5) return; - - window.sentience_registry[idx] = el; - - // Use getSemanticText for inference support (falls back to getText if no inference) - const semanticText = getSemanticText(el, { - enableInference: options.enableInference !== false, // Default: true - inferenceConfig: options.inferenceConfig, // Pass configurable inference settings - }); - const textVal = semanticText.text || getText(el); // Fallback to getText for backward compat - - // Infer role for interactable elements (only if no aria-label and no explicit role) - const inferredRole = getInferredRole(el, { - enableInference: options.enableInference !== false, - inferenceConfig: options.inferenceConfig, + invisibleSelectors.forEach(selector => { + try { + clone.querySelectorAll(selector).forEach(el => { + el.parentNode && el.parentNode.removeChild(el); + }); + } catch (e) {} }); - const inView = isInViewport(rect); - - // Get computed style once (needed for both occlusion check and data collection) - const style = window.getComputedStyle(el); - - // Only check occlusion for elements likely to be occluded (optimized) - // This avoids layout thrashing for the vast majority of elements - const occluded = inView ? isOccluded(el, rect, style) : false; - - // Get effective background color (traverses DOM to find non-transparent color) - const effectiveBgColor = getEffectiveBackgroundColor(el); - - rawData.push({ - id: idx, - tag: el.tagName.toLowerCase(), - rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, - styles: { - display: toSafeString(style.display), - visibility: toSafeString(style.visibility), - opacity: toSafeString(style.opacity), - z_index: toSafeString(style.zIndex || 'auto'), - position: toSafeString(style.position), - bg_color: toSafeString(effectiveBgColor || style.backgroundColor), - color: toSafeString(style.color), - cursor: toSafeString(style.cursor), - font_weight: toSafeString(style.fontWeight), - font_size: toSafeString(style.fontSize), - }, - attributes: { - role: toSafeString(el.getAttribute('role')), - type_: toSafeString(el.getAttribute('type')), - aria_label: - semanticText?.source === 'explicit_aria_label' - ? semanticText.text - : toSafeString(el.getAttribute('aria-label')), // Keep original for backward compat - inferred_label: - semanticText?.source && - !['explicit_aria_label', 'input_value', 'img_alt', 'inner_text'].includes( - semanticText.source - ) - ? toSafeString(semanticText.text) - : null, - label_source: semanticText?.source || null, // Track source for gateway - inferred_role: inferredRole ? toSafeString(inferredRole) : null, // Inferred role for interactable elements - href: toSafeString(el.href || el.getAttribute('href') || null), - class: toSafeString(getClassName(el)), - // Capture dynamic input state (not just initial attributes) - value: - el.value !== undefined - ? toSafeString(el.value) - : toSafeString(el.getAttribute('value')), - checked: el.checked !== undefined ? String(el.checked) : null, - }, - text: toSafeString(textVal), - in_viewport: inView, - is_occluded: occluded, - // Phase 1: Pass scroll position for doc_y computation in WASM - scroll_y: window.scrollY, + clone.querySelectorAll("a[href]").forEach(link => { + const href = link.getAttribute("href"); + if (href && !href.startsWith("http://") && !href.startsWith("https://") && !href.startsWith("#")) try { + link.setAttribute("href", new URL(href, document.baseURI).href); + } catch (e) {} }); - }); - - console.log(`[SentienceAPI] Collected ${rawData.length} elements from main frame`); - - // Step 1.5: Collect iframe snapshots and FLATTEN immediately - // "Flatten Early" architecture: Merge iframe elements into main array before WASM - // This allows WASM to process all elements uniformly (no recursion needed) - const allRawElements = [...rawData]; // Start with main frame elements - let totalIframeElements = 0; - - if (options.collectIframes !== false) { + return clone.querySelectorAll("img[src]").forEach(img => { + const src = img.getAttribute("src"); + if (src && !src.startsWith("http://") && !src.startsWith("https://") && !src.startsWith("data:")) try { + img.setAttribute("src", new URL(src, document.baseURI).href); + } catch (e) {} + }), clone.innerHTML; + } + function cleanElement(obj) { + if (Array.isArray(obj)) return obj.map(cleanElement); + if (null !== obj && "object" == typeof obj) { + const cleaned = {}; + for (const [key, value] of Object.entries(obj)) if (null != value) if ("object" == typeof value) { + const deepClean = cleanElement(value); + Object.keys(deepClean).length > 0 && (cleaned[key] = deepClean); + } else cleaned[key] = value; + return cleaned; + } + return obj; + } + async function snapshot(options = {}) { try { - console.log(`[SentienceAPI] Starting iframe collection...`); - const iframeSnapshots = await collectIframeSnapshots(options); - console.log( - `[SentienceAPI] Iframe collection complete. Received ${iframeSnapshots.size} snapshot(s)` - ); - - if (iframeSnapshots.size > 0) { - // FLATTEN IMMEDIATELY: Don't nest them. Just append them with coordinate translation. - iframeSnapshots.forEach((iframeSnapshot, iframeEl) => { - // Debug: Log structure to verify data is correct - // console.log(`[SentienceAPI] Processing iframe snapshot:`, iframeSnapshot); - - if (iframeSnapshot && iframeSnapshot.raw_elements) { - const rawElementsCount = iframeSnapshot.raw_elements.length; - console.log( - `[SentienceAPI] Processing ${rawElementsCount} elements from iframe (src: ${iframeEl.src || 'unknown'})` - ); - // Get iframe's bounding rect (offset for coordinate translation) - const iframeRect = iframeEl.getBoundingClientRect(); - const offset = { x: iframeRect.x, y: iframeRect.y }; - - // Get iframe context for frame switching (Playwright needs this) - const iframeSrc = iframeEl.src || iframeEl.getAttribute('src') || ''; - let isSameOrigin = false; - try { - // Try to access contentWindow to check if same-origin - isSameOrigin = iframeEl.contentWindow !== null; - } catch (e) { - isSameOrigin = false; - } - - // Adjust coordinates and add iframe context to each element - const adjustedElements = iframeSnapshot.raw_elements.map((el) => { - const adjusted = { ...el }; - - // Adjust rect coordinates to parent viewport - if (adjusted.rect) { - adjusted.rect = { - ...adjusted.rect, - x: adjusted.rect.x + offset.x, - y: adjusted.rect.y + offset.y, + !1 !== options.waitForStability && await async function(options = {}) { + const {minNodeCount: minNodeCount = 500, quietPeriod: quietPeriod = 200, maxWait: maxWait = 5e3} = options, startTime = Date.now(); + return new Promise(resolve => { + if (document.querySelectorAll("*").length >= minNodeCount) { + let lastChange = Date.now(); + const observer = new MutationObserver(() => { + lastChange = Date.now(); + }); + observer.observe(document.body, { + childList: !0, + subtree: !0, + attributes: !1 + }); + const checkStable = () => { + const timeSinceLastChange = Date.now() - lastChange, totalWait = Date.now() - startTime; + timeSinceLastChange >= quietPeriod || totalWait >= maxWait ? (observer.disconnect(), + resolve()) : setTimeout(checkStable, 50); + }; + checkStable(); + } else { + const observer = new MutationObserver(() => { + const currentCount = document.querySelectorAll("*").length, totalWait = Date.now() - startTime; + if (currentCount >= minNodeCount) { + observer.disconnect(); + let lastChange = Date.now(); + const quietObserver = new MutationObserver(() => { + lastChange = Date.now(); + }); + quietObserver.observe(document.body, { + childList: !0, + subtree: !0, + attributes: !1 + }); + const checkQuiet = () => { + const timeSinceLastChange = Date.now() - lastChange, totalWait = Date.now() - startTime; + timeSinceLastChange >= quietPeriod || totalWait >= maxWait ? (quietObserver.disconnect(), + resolve()) : setTimeout(checkQuiet, 50); + }; + checkQuiet(); + } else totalWait >= maxWait && (observer.disconnect(), resolve()); + }); + observer.observe(document.body, { + childList: !0, + subtree: !0, + attributes: !1 + }), setTimeout(() => { + observer.disconnect(), resolve(); + }, maxWait); + } + }); + }(options.waitForStability || {}); + const rawData = []; + window.sentience_registry = []; + getAllElements().forEach((el, idx) => { + if (!el.getBoundingClientRect) return; + const rect = el.getBoundingClientRect(); + if (rect.width < 5 || rect.height < 5) return; + window.sentience_registry[idx] = el; + const semanticText = function(el, options = {}) { + if (!el) return { + text: "", + source: null }; - } - - // Add iframe context so agents can switch frames in Playwright - adjusted.iframe_context = { - src: iframeSrc, - is_same_origin: isSameOrigin, - }; - - return adjusted; + const explicitAriaLabel = el.getAttribute ? el.getAttribute("aria-label") : null; + if (explicitAriaLabel && explicitAriaLabel.trim()) return { + text: explicitAriaLabel.trim(), + source: "explicit_aria_label" + }; + if ("INPUT" === el.tagName) { + const value = (el.value || el.placeholder || "").trim(); + if (value) return { + text: value, + source: "input_value" + }; + } + if ("IMG" === el.tagName) { + const alt = (el.alt || "").trim(); + if (alt) return { + text: alt, + source: "img_alt" + }; + } + const innerText = (el.innerText || "").trim(); + if (innerText) return { + text: innerText.substring(0, 100), + source: "inner_text" + }; + const inferred = getInferredLabel(el, { + enableInference: !1 !== options.enableInference, + inferenceConfig: options.inferenceConfig + }); + return inferred || { + text: "", + source: null + }; + }(el, { + enableInference: !1 !== options.enableInference, + inferenceConfig: options.inferenceConfig + }), textVal = semanticText.text || getText(el), inferredRole = function(el, options = {}) { + const {enableInference: enableInference = !0} = options; + if (!enableInference) return null; + if (!function(el) { + if (!el || !el.tagName) return !1; + 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"); + return [ "button", "input", "textarea", "select", "option", "details", "summary", "a" ].includes(tag) ? !("a" === tag && !hasHref) : !(!role || ![ "button", "link", "tab", "menuitem", "checkbox", "radio", "switch", "slider", "combobox", "textbox", "searchbox", "spinbutton" ].includes(role.toLowerCase())) || (!!hasTabIndex || (!!(el.onclick || el.onkeydown || el.onkeypress || el.onkeyup) || !(!el.getAttribute || !(el.getAttribute("onclick") || el.getAttribute("onkeydown") || el.getAttribute("onkeypress") || el.getAttribute("onkeyup"))))); + }(el)) return null; + const hasAriaLabel = el.getAttribute ? el.getAttribute("aria-label") : null, hasExplicitRole = el.getAttribute ? el.getAttribute("role") : null; + if (hasAriaLabel || hasExplicitRole) return null; + const tag = el.tagName.toLowerCase(); + 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; + }(el, { + enableInference: !1 !== options.enableInference, + inferenceConfig: options.inferenceConfig + }), inView = function(rect) { + return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0; + }(rect), style = window.getComputedStyle(el), occluded = !!inView && function(el, rect, style) { + const zIndex = parseInt(style.zIndex, 10); + if ("static" === style.position && (isNaN(zIndex) || zIndex <= 10)) return !1; + const cx = rect.x + rect.width / 2, cy = rect.y + rect.height / 2; + if (cx < 0 || cx > window.innerWidth || cy < 0 || cy > window.innerHeight) return !1; + const topEl = document.elementFromPoint(cx, cy); + return !!topEl && !(el === topEl || el.contains(topEl) || topEl.contains(el)); + }(el, rect, style), effectiveBgColor = function(el) { + if (!el) return null; + if ("SVG" === el.tagName) { + const svgColor = getSVGColor(el); + if (svgColor) return svgColor; + } + let current = el, depth = 0; + for (;current && depth < 10; ) { + const style = window.getComputedStyle(current); + if ("SVG" === current.tagName) { + const svgColor = getSVGColor(current); + if (svgColor) return svgColor; + } + const bgColor = style.backgroundColor; + if (bgColor && "transparent" !== bgColor && "rgba(0, 0, 0, 0)" !== bgColor) { + const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (!rgbaMatch) return bgColor.startsWith("rgb("), bgColor; + if ((rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1) >= .9) return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } + current = current.parentElement, depth++; + } + return null; + }(el); + rawData.push({ + id: idx, + tag: el.tagName.toLowerCase(), + rect: { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + }, + styles: { + display: toSafeString(style.display), + visibility: toSafeString(style.visibility), + opacity: toSafeString(style.opacity), + z_index: toSafeString(style.zIndex || "auto"), + position: toSafeString(style.position), + bg_color: toSafeString(effectiveBgColor || style.backgroundColor), + color: toSafeString(style.color), + cursor: toSafeString(style.cursor), + font_weight: toSafeString(style.fontWeight), + font_size: toSafeString(style.fontSize) + }, + attributes: { + role: toSafeString(el.getAttribute("role")), + type_: toSafeString(el.getAttribute("type")), + aria_label: "explicit_aria_label" === semanticText?.source ? semanticText.text : toSafeString(el.getAttribute("aria-label")), + inferred_label: semanticText?.source && ![ "explicit_aria_label", "input_value", "img_alt", "inner_text" ].includes(semanticText.source) ? toSafeString(semanticText.text) : null, + label_source: semanticText?.source || null, + inferred_role: inferredRole ? toSafeString(inferredRole) : null, + href: toSafeString(el.href || el.getAttribute("href") || null), + class: toSafeString(getClassName(el)), + value: void 0 !== el.value ? toSafeString(el.value) : toSafeString(el.getAttribute("value")), + checked: void 0 !== el.checked ? String(el.checked) : null + }, + text: toSafeString(textVal), + in_viewport: inView, + is_occluded: occluded, + scroll_y: window.scrollY }); - - // Append flattened iframe elements to main array - allRawElements.push(...adjustedElements); - totalIframeElements += adjustedElements.length; - } }); - - // console.log(`[SentienceAPI] Merged ${iframeSnapshots.size} iframe(s). Total elements: ${allRawElements.length} (${rawData.length} main + ${totalIframeElements} iframe)`); - } + const allRawElements = [ ...rawData ]; + let totalIframeElements = 0; + if (!1 !== options.collectIframes) try { + const iframeSnapshots = await async function(options = {}) { + const iframeData = new Map, iframes = Array.from(document.querySelectorAll("iframe")); + if (0 === iframes.length) return iframeData; + const iframePromises = iframes.map((iframe, idx) => { + const src = iframe.src || ""; + return src.includes("doubleclick") || src.includes("googleadservices") || src.includes("ads system") ? Promise.resolve(null) : new Promise(resolve => { + const requestId = `iframe-${idx}-${Date.now()}`, timeout = setTimeout(() => { + resolve(null); + }, 5e3), listener = event => { + "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE" === event.data?.type && event.data, "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE" === event.data?.type && event.data?.requestId === requestId && (clearTimeout(timeout), + window.removeEventListener("message", listener), event.data.error ? resolve(null) : (event.data.snapshot, + resolve({ + iframe: iframe, + data: event.data.snapshot, + error: null + }))); + }; + window.addEventListener("message", listener); + try { + iframe.contentWindow ? iframe.contentWindow.postMessage({ + type: "SENTIENCE_IFRAME_SNAPSHOT_REQUEST", + requestId: requestId, + options: { + ...options, + collectIframes: !0 + } + }, "*") : (clearTimeout(timeout), window.removeEventListener("message", listener), + resolve(null)); + } catch (error) { + clearTimeout(timeout), window.removeEventListener("message", listener), resolve(null); + } + }); + }); + return (await Promise.all(iframePromises)).forEach((result, idx) => { + result && result.data && !result.error ? iframeData.set(iframes[idx], result.data) : result && result.error; + }), iframeData; + }(options); + iframeSnapshots.size > 0 && iframeSnapshots.forEach((iframeSnapshot, iframeEl) => { + if (iframeSnapshot && iframeSnapshot.raw_elements) { + iframeSnapshot.raw_elements.length; + const iframeRect = iframeEl.getBoundingClientRect(), offset = { + x: iframeRect.x, + y: iframeRect.y + }, iframeSrc = iframeEl.src || iframeEl.getAttribute("src") || ""; + let isSameOrigin = !1; + try { + isSameOrigin = null !== iframeEl.contentWindow; + } catch (e) { + isSameOrigin = !1; + } + const adjustedElements = iframeSnapshot.raw_elements.map(el => { + const adjusted = { + ...el + }; + return adjusted.rect && (adjusted.rect = { + ...adjusted.rect, + x: adjusted.rect.x + offset.x, + y: adjusted.rect.y + offset.y + }), adjusted.iframe_context = { + src: iframeSrc, + is_same_origin: isSameOrigin + }, adjusted; + }); + allRawElements.push(...adjustedElements), totalIframeElements += adjustedElements.length; + } + }); + } catch (error) {} + const processed = await function(rawData, options) { + return new Promise((resolve, reject) => { + const requestId = Math.random().toString(36).substring(7); + let resolved = !1; + const timeout = setTimeout(() => { + resolved || (resolved = !0, window.removeEventListener("message", listener), reject(new Error("WASM processing timeout - extension may be unresponsive. Try reloading the extension."))); + }, 25e3), listener = e => { + if ("SENTIENCE_SNAPSHOT_RESULT" === e.data.type && e.data.requestId === requestId) { + if (resolved) return; + resolved = !0, clearTimeout(timeout), window.removeEventListener("message", listener), + e.data.error ? reject(new Error(e.data.error)) : resolve({ + elements: e.data.elements, + raw_elements: e.data.raw_elements, + duration: e.data.duration + }); + } + }; + window.addEventListener("message", listener); + try { + window.postMessage({ + type: "SENTIENCE_SNAPSHOT_REQUEST", + requestId: requestId, + rawData: rawData, + options: options + }, "*"); + } catch (error) { + resolved || (resolved = !0, clearTimeout(timeout), window.removeEventListener("message", listener), + reject(new Error(`Failed to send snapshot request: ${error.message}`))); + } + }); + }(allRawElements, options); + if (!processed || !processed.elements) throw new Error("WASM processing returned invalid result"); + let screenshot = null; + options.screenshot && (screenshot = await function(options) { + return new Promise(resolve => { + const requestId = Math.random().toString(36).substring(7), listener = e => { + "SENTIENCE_SCREENSHOT_RESULT" === e.data.type && e.data.requestId === requestId && (window.removeEventListener("message", listener), + resolve(e.data.screenshot)); + }; + window.addEventListener("message", listener), window.postMessage({ + type: "SENTIENCE_SCREENSHOT_REQUEST", + requestId: requestId, + options: options + }, "*"), setTimeout(() => { + window.removeEventListener("message", listener), resolve(null); + }, 1e4); + }); + }(options.screenshot)); + const cleanedElements = cleanElement(processed.elements), cleanedRawElements = cleanElement(processed.raw_elements); + cleanedElements.length, cleanedRawElements.length; + return { + status: "success", + url: window.location.href, + viewport: { + width: window.innerWidth, + height: window.innerHeight + }, + elements: cleanedElements, + raw_elements: cleanedRawElements, + screenshot: screenshot + }; } catch (error) { - console.warn('[SentienceAPI] Iframe collection failed:', error); + return { + status: "error", + error: error.message || "Unknown error", + stack: error.stack + }; } - } - - // Step 2: Send EVERYTHING to WASM (One giant flat list) - // Now WASM prunes iframe elements and main elements in one pass! - // No recursion needed - everything is already flat - console.log( - `[SentienceAPI] Sending ${allRawElements.length} total elements to WASM (${rawData.length} main + ${totalIframeElements} iframe)` - ); - const processed = await processSnapshotInBackground(allRawElements, options); - - if (!processed || !processed.elements) { - throw new Error('WASM processing returned invalid result'); - } - - // Step 3: Capture screenshot if requested - let screenshot = null; - if (options.screenshot) { - screenshot = await captureScreenshot(options.screenshot); - } - - // Step 4: Clean and return - const cleanedElements = cleanElement(processed.elements); - const cleanedRawElements = cleanElement(processed.raw_elements); - - // FIXED: Removed undefined 'totalIframeRawElements' - // FIXED: Logic updated for "Flatten Early" architecture. - // processed.elements ALREADY contains the merged iframe elements, - // so we simply use .length. No addition needed. - - const totalCount = cleanedElements.length; - const totalRaw = cleanedRawElements.length; - const iframeCount = totalIframeElements || 0; - - console.log( - `[SentienceAPI] ✓ Complete: ${totalCount} Smart Elements, ${totalRaw} Raw Elements (includes ${iframeCount} from iframes) (WASM took ${processed.duration?.toFixed(1)}ms)` - ); - - return { - status: 'success', - url: window.location.href, - viewport: { - width: window.innerWidth, - height: window.innerHeight, - }, - elements: cleanedElements, - raw_elements: cleanedRawElements, - screenshot, - }; - } catch (error) { - console.error('[SentienceAPI] snapshot() failed:', error); - console.error('[SentienceAPI] Error stack:', error.stack); - return { - status: 'error', - error: error.message || 'Unknown error', - stack: error.stack, - }; - } - } - - // read.js - Content Reading Methods - - // 2. Read Content (unchanged) - function read(options = {}) { - const format = options.format || 'raw'; - let content; - - if (format === 'raw') { - content = getRawHTML(document.body); - } else if (format === 'markdown') { - content = convertToMarkdown(document.body); - } else { - content = convertToText(document.body); - } - - return { - status: 'success', - url: window.location.href, - format, - content, - length: content.length, - }; - } - - // 2b. Find Text Rectangle - Get exact pixel coordinates of specific text - function findTextRect(options = {}) { - const { - text, - containerElement = document.body, - caseSensitive = false, - wholeWord = false, - maxResults = 10, - } = options; - - if (!text || text.trim().length === 0) { - return { - status: 'error', - error: 'Text parameter is required', - }; } - - const results = []; - const searchText = caseSensitive ? text : text.toLowerCase(); - - // Helper function to find text in a single text node - function findInTextNode(textNode) { - const nodeText = textNode.nodeValue; - const searchableText = caseSensitive ? nodeText : nodeText.toLowerCase(); - - let startIndex = 0; - while (startIndex < nodeText.length && results.length < maxResults) { - const foundIndex = searchableText.indexOf(searchText, startIndex); - - if (foundIndex === -1) break; - - // Check whole word matching if required - if (wholeWord) { - const before = foundIndex > 0 ? nodeText[foundIndex - 1] : ' '; - const after = - foundIndex + text.length < nodeText.length ? nodeText[foundIndex + text.length] : ' '; - - // Check if surrounded by word boundaries - if (!/\s/.test(before) || !/\s/.test(after)) { - startIndex = foundIndex + 1; - continue; - } - } - - try { - // Create range for this occurrence - const range = document.createRange(); - range.setStart(textNode, foundIndex); - range.setEnd(textNode, foundIndex + text.length); - - const rect = range.getBoundingClientRect(); - - // Only include visible rectangles - if (rect.width > 0 && rect.height > 0) { - results.push({ - text: nodeText.substring(foundIndex, foundIndex + text.length), - rect: { - x: rect.left + window.scrollX, - y: rect.top + window.scrollY, - width: rect.width, - height: rect.height, - left: rect.left + window.scrollX, - top: rect.top + window.scrollY, - right: rect.right + window.scrollX, - bottom: rect.bottom + window.scrollY, - }, - viewport_rect: { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }, - context: { - before: nodeText.substring(Math.max(0, foundIndex - 20), foundIndex), - after: nodeText.substring( - foundIndex + text.length, - Math.min(nodeText.length, foundIndex + text.length + 20) - ), - }, - in_viewport: - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth, - }); - } - } catch (e) { - console.warn('[SentienceAPI] Failed to get rect for text:', e); - } - - startIndex = foundIndex + 1; - } + function read(options = {}) { + const format = options.format || "raw"; + let content; + return content = "raw" === format ? getRawHTML(document.body) : "markdown" === format ? function(root) { + const rawHTML = getRawHTML(root), tempDiv = document.createElement("div"); + tempDiv.innerHTML = rawHTML; + let markdown = "", insideLink = !1; + return function walk(node) { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent.replace(/[\r\n]+/g, " ").replace(/\s+/g, " "); + return void (text.trim() && (markdown += text)); + } + if (node.nodeType !== Node.ELEMENT_NODE) return; + const tag = node.tagName.toLowerCase(); + if ("h1" === tag && (markdown += "\n# "), "h2" === tag && (markdown += "\n## "), + "h3" === tag && (markdown += "\n### "), "li" === tag && (markdown += "\n- "), insideLink || "p" !== tag && "div" !== tag && "br" !== tag || (markdown += "\n"), + "strong" !== tag && "b" !== tag || (markdown += "**"), "em" !== tag && "i" !== tag || (markdown += "_"), + "a" === tag && (markdown += "[", insideLink = !0), node.shadowRoot ? Array.from(node.shadowRoot.childNodes).forEach(walk) : node.childNodes.forEach(walk), + "a" === tag) { + const href = node.getAttribute("href"); + markdown += href ? `](${href})` : "]", insideLink = !1; + } + "strong" !== tag && "b" !== tag || (markdown += "**"), "em" !== tag && "i" !== tag || (markdown += "_"), + insideLink || "h1" !== tag && "h2" !== tag && "h3" !== tag && "p" !== tag && "div" !== tag || (markdown += "\n"); + }(tempDiv), markdown.replace(/\n{3,}/g, "\n\n").trim(); + }(document.body) : function(root) { + let text = ""; + return function walk(node) { + if (node.nodeType !== Node.TEXT_NODE) { + if (node.nodeType === Node.ELEMENT_NODE) { + const tag = node.tagName.toLowerCase(); + if ([ "nav", "footer", "header", "script", "style", "noscript", "iframe", "svg" ].includes(tag)) return; + const style = window.getComputedStyle(node); + if ("none" === style.display || "hidden" === style.visibility) return; + const isBlock = "block" === style.display || "flex" === style.display || "P" === node.tagName || "DIV" === node.tagName; + isBlock && (text += " "), node.shadowRoot ? Array.from(node.shadowRoot.childNodes).forEach(walk) : node.childNodes.forEach(walk), + isBlock && (text += "\n"); + } + } else text += node.textContent; + }(root || document.body), text.replace(/\n{3,}/g, "\n\n").trim(); + }(document.body), { + status: "success", + url: window.location.href, + format: format, + content: content, + length: content.length + }; } - - // Tree walker to find all text nodes - const walker = document.createTreeWalker(containerElement, NodeFilter.SHOW_TEXT, { - acceptNode(node) { - // Skip script, style, and empty text nodes - const parent = node.parentElement; - if (!parent) return NodeFilter.FILTER_REJECT; - - const tagName = parent.tagName.toLowerCase(); - if (tagName === 'script' || tagName === 'style' || tagName === 'noscript') { - return NodeFilter.FILTER_REJECT; - } - - // Skip whitespace-only nodes - if (!node.nodeValue || node.nodeValue.trim().length === 0) { - return NodeFilter.FILTER_REJECT; - } - - // Check if element is visible - const computedStyle = window.getComputedStyle(parent); - if ( - computedStyle.display === 'none' || - computedStyle.visibility === 'hidden' || - computedStyle.opacity === '0' - ) { - return NodeFilter.FILTER_REJECT; + function findTextRect(options = {}) { + const {text: text, containerElement: containerElement = document.body, caseSensitive: caseSensitive = !1, wholeWord: wholeWord = !1, maxResults: maxResults = 10} = options; + if (!text || 0 === text.trim().length) return { + status: "error", + error: "Text parameter is required" + }; + const results = [], searchText = caseSensitive ? text : text.toLowerCase(); + function findInTextNode(textNode) { + const nodeText = textNode.nodeValue, searchableText = caseSensitive ? nodeText : nodeText.toLowerCase(); + let startIndex = 0; + for (;startIndex < nodeText.length && results.length < maxResults; ) { + const foundIndex = searchableText.indexOf(searchText, startIndex); + if (-1 === foundIndex) break; + if (wholeWord) { + const before = foundIndex > 0 ? nodeText[foundIndex - 1] : " ", after = foundIndex + text.length < nodeText.length ? nodeText[foundIndex + text.length] : " "; + if (!/\s/.test(before) || !/\s/.test(after)) { + startIndex = foundIndex + 1; + continue; + } + } + try { + const range = document.createRange(); + range.setStart(textNode, foundIndex), range.setEnd(textNode, foundIndex + text.length); + const rect = range.getBoundingClientRect(); + rect.width > 0 && rect.height > 0 && results.push({ + text: nodeText.substring(foundIndex, foundIndex + text.length), + rect: { + x: rect.left + window.scrollX, + y: rect.top + window.scrollY, + width: rect.width, + height: rect.height, + left: rect.left + window.scrollX, + top: rect.top + window.scrollY, + right: rect.right + window.scrollX, + bottom: rect.bottom + window.scrollY + }, + viewport_rect: { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + }, + context: { + before: nodeText.substring(Math.max(0, foundIndex - 20), foundIndex), + after: nodeText.substring(foundIndex + text.length, Math.min(nodeText.length, foundIndex + text.length + 20)) + }, + in_viewport: rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth + }); + } catch (e) {} + startIndex = foundIndex + 1; + } } - - return NodeFilter.FILTER_ACCEPT; - }, - }); - - // Walk through all text nodes - let currentNode; - while ((currentNode = walker.nextNode()) && results.length < maxResults) { - findInTextNode(currentNode); - } - - return { - status: 'success', - query: text, - case_sensitive: caseSensitive, - whole_word: wholeWord, - matches: results.length, - results, - viewport: { - width: window.innerWidth, - height: window.innerHeight, - scroll_x: window.scrollX, - scroll_y: window.scrollY, - }, - }; - } - - // click.js - Click Action Method - - // 3. Click Action (unchanged) - function click(id) { - const el = window.sentience_registry[id]; - if (el) { - el.click(); - el.focus(); - return true; - } - return false; - } - - // registry.js - Inspector Mode / Golden Set Collection - - // 4. Inspector Mode: Start Recording for Golden Set Collection - function startRecording(options = {}) { - const { - highlightColor = '#ff0000', - successColor = '#00ff00', - autoDisableTimeout = 30 * 60 * 1000, // 30 minutes default - keyboardShortcut = 'Ctrl+Shift+I', - } = options; - - console.log( - '🔴 [Sentience] Recording Mode STARTED. Click an element to copy its Ground Truth JSON.' - ); - console.log(` Press ${keyboardShortcut} or call stopRecording() to stop.`); - - // Validate registry is populated - if (!window.sentience_registry || window.sentience_registry.length === 0) { - console.warn( - '⚠️ Registry empty. Call `await window.sentience.snapshot()` first to populate registry.' - ); - alert('Registry empty. Run `await window.sentience.snapshot()` first!'); - return () => {}; // Return no-op cleanup function - } - - // Create reverse mapping for O(1) lookup (fixes registry lookup bug) - window.sentience_registry_map = new Map(); - window.sentience_registry.forEach((el, idx) => { - if (el) window.sentience_registry_map.set(el, idx); - }); - - // Create highlight box overlay - let highlightBox = document.getElementById('sentience-highlight-box'); - if (!highlightBox) { - highlightBox = document.createElement('div'); - highlightBox.id = 'sentience-highlight-box'; - highlightBox.style.cssText = ` - position: fixed; - pointer-events: none; - z-index: 2147483647; - border: 2px solid ${highlightColor}; - background: rgba(255, 0, 0, 0.1); - display: none; - transition: all 0.1s ease; - box-sizing: border-box; - `; - document.body.appendChild(highlightBox); - } - - // Create visual indicator (red border on page when recording) - let recordingIndicator = document.getElementById('sentience-recording-indicator'); - if (!recordingIndicator) { - recordingIndicator = document.createElement('div'); - recordingIndicator.id = 'sentience-recording-indicator'; - recordingIndicator.style.cssText = ` - position: fixed; - top: 0; - left: 0; - right: 0; - height: 3px; - background: ${highlightColor}; - z-index: 2147483646; - pointer-events: none; - `; - document.body.appendChild(recordingIndicator); - } - recordingIndicator.style.display = 'block'; - - // Hover handler (visual feedback) - const mouseOverHandler = (e) => { - const el = e.target; - if (!el || el === highlightBox || el === recordingIndicator) return; - - const rect = el.getBoundingClientRect(); - highlightBox.style.display = 'block'; - highlightBox.style.top = rect.top + window.scrollY + 'px'; - highlightBox.style.left = rect.left + window.scrollX + 'px'; - highlightBox.style.width = rect.width + 'px'; - highlightBox.style.height = rect.height + 'px'; - }; - - // Click handler (capture ground truth data) - const clickHandler = (e) => { - e.preventDefault(); - e.stopPropagation(); - - const el = e.target; - if (!el || el === highlightBox || el === recordingIndicator) return; - - // Use Map for reliable O(1) lookup - const sentienceId = window.sentience_registry_map.get(el); - if (sentienceId === undefined) { - console.warn('⚠️ Element not found in Sentience Registry. Did you run snapshot() first?'); - alert('Element not in registry. Run `await window.sentience.snapshot()` first!'); - return; - } - - // Extract raw data (ground truth + raw signals, NOT model outputs) - const rawData = extractRawElementData(el); - const selector = getUniqueSelector(el); - const role = el.getAttribute('role') || el.tagName.toLowerCase(); - const text = getText(el); - - // Build golden set JSON (ground truth + raw signals only) - const snippet = { - task: `Interact with ${text.substring(0, 20)}${text.length > 20 ? '...' : ''}`, - url: window.location.href, - timestamp: new Date().toISOString(), - target_criteria: { - id: sentienceId, - selector, - role, - text: text.substring(0, 50), - }, - debug_snapshot: rawData, - }; - - // Copy to clipboard - const jsonString = JSON.stringify(snippet, null, 2); - navigator.clipboard - .writeText(jsonString) - .then(() => { - console.log('✅ Copied Ground Truth to clipboard:', snippet); - - // Flash green to indicate success - highlightBox.style.border = `2px solid ${successColor}`; - highlightBox.style.background = 'rgba(0, 255, 0, 0.2)'; - setTimeout(() => { - highlightBox.style.border = `2px solid ${highlightColor}`; - highlightBox.style.background = 'rgba(255, 0, 0, 0.1)'; - }, 500); - }) - .catch((err) => { - console.error('❌ Failed to copy to clipboard:', err); - alert('Failed to copy to clipboard. Check console for JSON.'); + const walker = document.createTreeWalker(containerElement, NodeFilter.SHOW_TEXT, { + acceptNode(node) { + const parent = node.parentElement; + if (!parent) return NodeFilter.FILTER_REJECT; + const tagName = parent.tagName.toLowerCase(); + if ("script" === tagName || "style" === tagName || "noscript" === tagName) return NodeFilter.FILTER_REJECT; + if (!node.nodeValue || 0 === node.nodeValue.trim().length) return NodeFilter.FILTER_REJECT; + const computedStyle = window.getComputedStyle(parent); + return "none" === computedStyle.display || "hidden" === computedStyle.visibility || "0" === computedStyle.opacity ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; + } }); - }; - - // Auto-disable timeout - let timeoutId = null; - - // Cleanup function to stop recording (defined before use) - const stopRecording = () => { - document.removeEventListener('mouseover', mouseOverHandler, true); - document.removeEventListener('click', clickHandler, true); - document.removeEventListener('keydown', keyboardHandler, true); - - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - - if (highlightBox) { - highlightBox.style.display = 'none'; - } - - if (recordingIndicator) { - recordingIndicator.style.display = 'none'; - } - - // Clean up registry map (optional, but good practice) - if (window.sentience_registry_map) { - window.sentience_registry_map.clear(); - } - - // Remove global reference - if (window.sentience_stopRecording === stopRecording) { - delete window.sentience_stopRecording; - } - - console.log('⚪ [Sentience] Recording Mode STOPPED.'); - }; - - // Keyboard shortcut handler (defined after stopRecording) - const keyboardHandler = (e) => { - // Ctrl+Shift+I or Cmd+Shift+I - if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'I') { - e.preventDefault(); - stopRecording(); - } - }; - - // Attach event listeners (use capture phase to intercept early) - document.addEventListener('mouseover', mouseOverHandler, true); - document.addEventListener('click', clickHandler, true); - document.addEventListener('keydown', keyboardHandler, true); - - // Set up auto-disable timeout - if (autoDisableTimeout > 0) { - timeoutId = setTimeout(() => { - console.log('⏰ [Sentience] Recording Mode auto-disabled after timeout.'); - stopRecording(); - }, autoDisableTimeout); - } - - // Store stop function globally for keyboard shortcut access - window.sentience_stopRecording = stopRecording; - - return stopRecording; - } - - // overlay.js - Visual Overlay Methods - - /** - * Show overlay highlighting specific elements with Shadow DOM - * @param {Array} elements - List of elements with bbox, importance, visual_cues - * @param {number} targetElementId - Optional ID of target element (shown in red) - */ - function showOverlay(elements, targetElementId = null) { - if (!elements || !Array.isArray(elements)) { - console.warn('[Sentience] showOverlay: elements must be an array'); - return; - } - - window.postMessage( - { - type: 'SENTIENCE_SHOW_OVERLAY', - elements, - targetElementId, - timestamp: Date.now(), - }, - '*' - ); - - console.log(`[Sentience] Overlay requested for ${elements.length} elements`); - } - - /** - * Clear overlay manually - */ - function clearOverlay() { - window.postMessage( - { - type: 'SENTIENCE_CLEAR_OVERLAY', - }, - '*' - ); - console.log('[Sentience] Overlay cleared'); - } - - // index.js - Main Entry Point for Injected API - // This script ONLY collects raw DOM data and sends it to background for processing - - - (async () => { - // console.log('[SentienceAPI] Initializing (CSP-Resistant Mode)...'); - - // Wait for Extension ID from content.js - const getExtensionId = () => document.documentElement.dataset.sentienceExtensionId; - let extId = getExtensionId(); - - if (!extId) { - await new Promise((resolve) => { - const check = setInterval(() => { - extId = getExtensionId(); - if (extId) { - clearInterval(check); - resolve(); - } - }, 50); - setTimeout(() => resolve(), 5000); // Max 5s wait - }); - } - - if (!extId) { - console.error('[SentienceAPI] Failed to get extension ID'); - return; + let currentNode; + for (;(currentNode = walker.nextNode()) && results.length < maxResults; ) findInTextNode(currentNode); + return { + status: "success", + query: text, + case_sensitive: caseSensitive, + whole_word: wholeWord, + matches: results.length, + results: results, + viewport: { + width: window.innerWidth, + height: window.innerHeight, + scroll_x: window.scrollX, + scroll_y: window.scrollY + } + }; } - - // console.log('[SentienceAPI] Extension ID:', extId); - - // Registry for click actions (still needed for click() function) - window.sentience_registry = []; - - // --- GLOBAL API --- - window.sentience = { - snapshot, - read, - findTextRect, - click, - startRecording, - showOverlay, - clearOverlay, - }; - - // Setup iframe handler when script loads (only once) - if (!window.sentience_iframe_handler_setup) { - setupIframeSnapshotHandler(); - window.sentience_iframe_handler_setup = true; + function click(id) { + const el = window.sentience_registry[id]; + return !!el && (el.click(), el.focus(), !0); } - - console.log('[SentienceAPI] ✓ Ready! (CSP-Resistant - WASM runs in background)'); - })(); - -})(); + function startRecording(options = {}) { + const {highlightColor: highlightColor = "#ff0000", successColor: successColor = "#00ff00", autoDisableTimeout: autoDisableTimeout = 18e5, keyboardShortcut: keyboardShortcut = "Ctrl+Shift+I"} = options; + if (!window.sentience_registry || 0 === window.sentience_registry.length) return alert("Registry empty. Run `await window.sentience.snapshot()` first!"), + () => {}; + window.sentience_registry_map = new Map, window.sentience_registry.forEach((el, idx) => { + el && window.sentience_registry_map.set(el, idx); + }); + let highlightBox = document.getElementById("sentience-highlight-box"); + highlightBox || (highlightBox = document.createElement("div"), highlightBox.id = "sentience-highlight-box", + 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 `, + document.body.appendChild(highlightBox)); + let recordingIndicator = document.getElementById("sentience-recording-indicator"); + recordingIndicator || (recordingIndicator = document.createElement("div"), recordingIndicator.id = "sentience-recording-indicator", + 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 `, + document.body.appendChild(recordingIndicator)), recordingIndicator.style.display = "block"; + const mouseOverHandler = e => { + const el = e.target; + if (!el || el === highlightBox || el === recordingIndicator) return; + const rect = el.getBoundingClientRect(); + highlightBox.style.display = "block", highlightBox.style.top = rect.top + window.scrollY + "px", + highlightBox.style.left = rect.left + window.scrollX + "px", highlightBox.style.width = rect.width + "px", + highlightBox.style.height = rect.height + "px"; + }, clickHandler = e => { + e.preventDefault(), e.stopPropagation(); + const el = e.target; + if (!el || el === highlightBox || el === recordingIndicator) return; + const sentienceId = window.sentience_registry_map.get(el); + if (void 0 === sentienceId) return void alert("Element not in registry. Run `await window.sentience.snapshot()` first!"); + const rawData = function(el) { + const style = window.getComputedStyle(el), rect = el.getBoundingClientRect(); + return { + tag: el.tagName, + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + }, + styles: { + cursor: style.cursor || null, + backgroundColor: style.backgroundColor || null, + color: style.color || null, + fontWeight: style.fontWeight || null, + fontSize: style.fontSize || null, + display: style.display || null, + position: style.position || null, + zIndex: style.zIndex || null, + opacity: style.opacity || null, + visibility: style.visibility || null + }, + attributes: { + role: el.getAttribute("role") || null, + type: el.getAttribute("type") || null, + ariaLabel: el.getAttribute("aria-label") || null, + id: el.id || null, + className: el.className || null + } + }; + }(el), selector = function(el) { + if (!el || !el.tagName) return ""; + if (el.id) return `#${el.id}`; + for (const attr of el.attributes) if (attr.name.startsWith("data-") || "aria-label" === attr.name) { + const value = attr.value ? attr.value.replace(/"/g, '\\"') : ""; + return `${el.tagName.toLowerCase()}[${attr.name}="${value}"]`; + } + const path = []; + let current = el; + for (;current && current !== document.body && current !== document.documentElement; ) { + let selector = current.tagName.toLowerCase(); + if (current.id) { + selector = `#${current.id}`, path.unshift(selector); + break; + } + if (current.className && "string" == typeof current.className) { + const classes = current.className.trim().split(/\s+/).filter(c => c); + classes.length > 0 && (selector += `.${classes[0]}`); + } + if (current.parentElement) { + const sameTagSiblings = Array.from(current.parentElement.children).filter(s => s.tagName === current.tagName), index = sameTagSiblings.indexOf(current); + (index > 0 || sameTagSiblings.length > 1) && (selector += `:nth-of-type(${index + 1})`); + } + path.unshift(selector), current = current.parentElement; + } + return path.join(" > ") || el.tagName.toLowerCase(); + }(el), role = el.getAttribute("role") || el.tagName.toLowerCase(), text = getText(el), snippet = { + task: `Interact with ${text.substring(0, 20)}${text.length > 20 ? "..." : ""}`, + url: window.location.href, + timestamp: (new Date).toISOString(), + target_criteria: { + id: sentienceId, + selector: selector, + role: role, + text: text.substring(0, 50) + }, + debug_snapshot: rawData + }, jsonString = JSON.stringify(snippet, null, 2); + navigator.clipboard.writeText(jsonString).then(() => { + highlightBox.style.border = `2px solid ${successColor}`, highlightBox.style.background = "rgba(0, 255, 0, 0.2)", + setTimeout(() => { + highlightBox.style.border = `2px solid ${highlightColor}`, highlightBox.style.background = "rgba(255, 0, 0, 0.1)"; + }, 500); + }).catch(err => { + alert("Failed to copy to clipboard. Check console for JSON."); + }); + }; + let timeoutId = null; + const stopRecording = () => { + document.removeEventListener("mouseover", mouseOverHandler, !0), document.removeEventListener("click", clickHandler, !0), + document.removeEventListener("keydown", keyboardHandler, !0), timeoutId && (clearTimeout(timeoutId), + timeoutId = null), highlightBox && (highlightBox.style.display = "none"), recordingIndicator && (recordingIndicator.style.display = "none"), + window.sentience_registry_map && window.sentience_registry_map.clear(), window.sentience_stopRecording === stopRecording && delete window.sentience_stopRecording; + }, keyboardHandler = e => { + (e.ctrlKey || e.metaKey) && e.shiftKey && "I" === e.key && (e.preventDefault(), + stopRecording()); + }; + return document.addEventListener("mouseover", mouseOverHandler, !0), document.addEventListener("click", clickHandler, !0), + document.addEventListener("keydown", keyboardHandler, !0), autoDisableTimeout > 0 && (timeoutId = setTimeout(() => { + stopRecording(); + }, autoDisableTimeout)), window.sentience_stopRecording = stopRecording, stopRecording; + } + function showOverlay(elements, targetElementId = null) { + elements && Array.isArray(elements) && window.postMessage({ + type: "SENTIENCE_SHOW_OVERLAY", + elements: elements, + targetElementId: targetElementId, + timestamp: Date.now() + }, "*"); + } + function clearOverlay() { + window.postMessage({ + type: "SENTIENCE_CLEAR_OVERLAY" + }, "*"); + } + (async () => { + const getExtensionId = () => document.documentElement.dataset.sentienceExtensionId; + let extId = getExtensionId(); + extId || await new Promise(resolve => { + const check = setInterval(() => { + extId = getExtensionId(), extId && (clearInterval(check), resolve()); + }, 50); + setTimeout(() => resolve(), 5e3); + }), extId && (window.sentience_registry = [], window.sentience = { + snapshot: snapshot, + read: read, + findTextRect: findTextRect, + click: click, + startRecording: startRecording, + showOverlay: showOverlay, + clearOverlay: clearOverlay + }, window.sentience_iframe_handler_setup || (window.addEventListener("message", async event => { + if ("SENTIENCE_IFRAME_SNAPSHOT_REQUEST" === event.data?.type) { + const {requestId: requestId, options: options} = event.data; + try { + const snapshotOptions = { + ...options, + collectIframes: !0, + waitForStability: (options.waitForStability, !1) + }, snapshot = await window.sentience.snapshot(snapshotOptions); + event.source && event.source.postMessage && event.source.postMessage({ + type: "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE", + requestId: requestId, + snapshot: snapshot, + error: null + }, "*"); + } catch (error) { + event.source && event.source.postMessage && event.source.postMessage({ + type: "SENTIENCE_IFRAME_SNAPSHOT_RESPONSE", + requestId: requestId, + snapshot: null, + error: error.message + }, "*"); + } + } + }), window.sentience_iframe_handler_setup = !0)); + })(); +}(); \ No newline at end of file diff --git a/sentience/extension/manifest.json b/sentience/extension/manifest.json index 456c1f5..4eaf8b2 100644 --- a/sentience/extension/manifest.json +++ b/sentience/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Sentience Semantic Visual Grounding Extractor", - "version": "2.2.0", + "version": "2.3.0", "description": "Extract semantic visual grounding data from web pages", "permissions": ["activeTab", "scripting"], "host_permissions": [""], diff --git a/sentience/extension/pkg/sentience_core.js b/sentience/extension/pkg/sentience_core.js index b232d13..ecba479 100644 --- a/sentience/extension/pkg/sentience_core.js +++ b/sentience/extension/pkg/sentience_core.js @@ -1,112 +1,70 @@ let wasm; function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); + heap_next === heap.length && heap.push(heap.length + 1); const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; + return heap_next = heap[idx], heap[idx] = obj, idx; } function debugString(val) { - // primitive types const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { + if ("number" == type || "boolean" == type || null == val) return `${val}`; + if ("string" == type) return `"${val}"`; + if ("symbol" == type) { const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } + return null == description ? "Symbol" : `Symbol(${description})`; } - if (type == 'function') { + if ("function" == type) { const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } + return "string" == typeof name && name.length > 0 ? `Function(${name})` : "Function"; } - // objects if (Array.isArray(val)) { const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; + let debug = "["; + length > 0 && (debug += debugString(val[0])); + for (let i = 1; i < length; i++) debug += ", " + debugString(val[i]); + return debug += "]", debug; } - // Test for built-in const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); + if (!(builtInMatches && builtInMatches.length > 1)) return toString.call(val); + if (className = builtInMatches[1], "Object" == className) try { + return "Object(" + JSON.stringify(val) + ")"; + } catch (_) { + return "Object"; } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; + return val instanceof Error ? `${val.name}: ${val.message}\n${val.stack}` : className; } function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; + idx < 132 || (heap[idx] = heap_next, heap_next = idx); } function getArrayU8FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); + return ptr >>>= 0, getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); } let cachedDataViewMemory0 = null; + function getDataViewMemory0() { - if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { - cachedDataViewMemory0 = new DataView(wasm.memory.buffer); - } - return cachedDataViewMemory0; + return (null === cachedDataViewMemory0 || !0 === cachedDataViewMemory0.buffer.detached || void 0 === cachedDataViewMemory0.buffer.detached && cachedDataViewMemory0.buffer !== wasm.memory.buffer) && (cachedDataViewMemory0 = new DataView(wasm.memory.buffer)), + cachedDataViewMemory0; } function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return decodeText(ptr, len); + return decodeText(ptr >>>= 0, len); } let cachedUint8ArrayMemory0 = null; + function getUint8ArrayMemory0() { - if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { - cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8ArrayMemory0; + return null !== cachedUint8ArrayMemory0 && 0 !== cachedUint8ArrayMemory0.byteLength || (cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer)), + cachedUint8ArrayMemory0; } -function getObject(idx) { return heap[idx]; } +function getObject(idx) { + return heap[idx]; +} function handleError(f, args) { try { @@ -116,414 +74,250 @@ function handleError(f, args) { } } -let heap = new Array(128).fill(undefined); -heap.push(undefined, null, true, false); +let heap = new Array(128).fill(void 0); + +heap.push(void 0, null, !0, !1); let heap_next = heap.length; function isLikeNone(x) { - return x === undefined || x === null; + return null == x; } function passStringToWasm0(arg, malloc, realloc) { - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; + if (void 0 === realloc) { + const buf = cachedTextEncoder.encode(arg), ptr = malloc(buf.length, 1) >>> 0; + return getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf), WASM_VECTOR_LEN = buf.length, + ptr; } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - + let len = arg.length, ptr = malloc(len, 1) >>> 0; const mem = getUint8ArrayMemory0(); - let offset = 0; - - for (; offset < len; offset++) { + for (;offset < len; offset++) { const code = arg.charCodeAt(offset); - if (code > 0x7F) break; + if (code > 127) break; mem[ptr + offset] = code; } if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + 0 !== offset && (arg = arg.slice(offset)), ptr = realloc(ptr, len, len = offset + 3 * arg.length, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); - const ret = cachedTextEncoder.encodeInto(arg, view); - - offset += ret.written; - ptr = realloc(ptr, len, offset, 1) >>> 0; + offset += cachedTextEncoder.encodeInto(arg, view).written, ptr = realloc(ptr, len, offset, 1) >>> 0; } - - WASM_VECTOR_LEN = offset; - return ptr; + return WASM_VECTOR_LEN = offset, ptr; } function takeObject(idx) { const ret = getObject(idx); - dropObject(idx); - return ret; + return dropObject(idx), ret; } -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +let cachedTextDecoder = new TextDecoder("utf-8", { + ignoreBOM: !0, + fatal: !0 +}); + cachedTextDecoder.decode(); + const MAX_SAFARI_DECODE_BYTES = 2146435072; + let numBytesDecoded = 0; + function decodeText(ptr, len) { - numBytesDecoded += len; - if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { - cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - cachedTextDecoder.decode(); - numBytesDecoded = len; - } - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); + return numBytesDecoded += len, numBytesDecoded >= MAX_SAFARI_DECODE_BYTES && (cachedTextDecoder = new TextDecoder("utf-8", { + ignoreBOM: !0, + fatal: !0 + }), cachedTextDecoder.decode(), numBytesDecoded = len), cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); } -const cachedTextEncoder = new TextEncoder(); +const cachedTextEncoder = new TextEncoder; -if (!('encodeInto' in cachedTextEncoder)) { - cachedTextEncoder.encodeInto = function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; - } -} +"encodeInto" in cachedTextEncoder || (cachedTextEncoder.encodeInto = function(arg, view) { + const buf = cachedTextEncoder.encode(arg); + return view.set(buf), { + read: arg.length, + written: buf.length + }; +}); let WASM_VECTOR_LEN = 0; -/** - * @param {any} val - * @returns {any} - */ export function analyze_page(val) { - const ret = wasm.analyze_page(addHeapObject(val)); - return takeObject(ret); + return takeObject(wasm.analyze_page(addHeapObject(val))); } -/** - * @param {any} val - * @param {any} options - * @returns {any} - */ export function analyze_page_with_options(val, options) { - const ret = wasm.analyze_page_with_options(addHeapObject(val), addHeapObject(options)); - return takeObject(ret); + return takeObject(wasm.analyze_page_with_options(addHeapObject(val), addHeapObject(options))); } -/** - * @param {any} _raw_elements - */ export function decide_and_act(_raw_elements) { wasm.decide_and_act(addHeapObject(_raw_elements)); } -/** - * Prune raw elements before sending to API - * This is a "dumb" filter that reduces payload size without leaking proprietary IP - * Filters out: tiny elements, invisible elements, non-interactive wrapper divs - * Amazon: 5000-6000 elements -> ~200-400 elements (~95% reduction) - * @param {any} val - * @returns {any} - */ export function prune_for_api(val) { - const ret = wasm.prune_for_api(addHeapObject(val)); - return takeObject(ret); + return takeObject(wasm.prune_for_api(addHeapObject(val))); } -const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']); +const EXPECTED_RESPONSE_TYPES = new Set([ "basic", "cors", "default" ]); async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - } catch (e) { - const validResponse = module.ok && EXPECTED_RESPONSE_TYPES.has(module.type); - - if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); - - } else { - throw e; - } - } + if ("function" == typeof Response && module instanceof Response) { + if ("function" == typeof WebAssembly.instantiateStreaming) try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + if (!(module.ok && EXPECTED_RESPONSE_TYPES.has(module.type)) || "application/wasm" === module.headers.get("Content-Type")) throw e; } - const bytes = await module.arrayBuffer(); return await WebAssembly.instantiate(bytes, imports); - } else { + } + { const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - } else { - return instance; - } + return instance instanceof WebAssembly.Instance ? { + instance: instance, + module: module + } : instance; } } function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - imports.wbg.__wbg_Error_52673b7de5a0ca89 = function(arg0, arg1) { - const ret = Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_Number_2d1dcfcf4ec51736 = function(arg0) { - const ret = Number(getObject(arg0)); - return ret; - }; - imports.wbg.__wbg___wbindgen_bigint_get_as_i64_6e32f5e6aff02e1d = function(arg0, arg1) { - const v = getObject(arg1); - const ret = typeof(v) === 'bigint' ? v : undefined; - getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); - }; - imports.wbg.__wbg___wbindgen_boolean_get_dea25b33882b895b = function(arg0) { - const v = getObject(arg0); - const ret = typeof(v) === 'boolean' ? v : undefined; - return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0; - }; - imports.wbg.__wbg___wbindgen_debug_string_adfb662ae34724b6 = function(arg0, arg1) { - const ret = debugString(getObject(arg1)); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg___wbindgen_in_0d3e1e8f0c669317 = function(arg0, arg1) { - const ret = getObject(arg0) in getObject(arg1); - return ret; - }; - imports.wbg.__wbg___wbindgen_is_bigint_0e1a2e3f55cfae27 = function(arg0) { - const ret = typeof(getObject(arg0)) === 'bigint'; - return ret; - }; - imports.wbg.__wbg___wbindgen_is_function_8d400b8b1af978cd = function(arg0) { - const ret = typeof(getObject(arg0)) === 'function'; - return ret; - }; - imports.wbg.__wbg___wbindgen_is_object_ce774f3490692386 = function(arg0) { + const imports = { + wbg: {} + }; + return imports.wbg.__wbg_Error_52673b7de5a0ca89 = function(arg0, arg1) { + return addHeapObject(Error(getStringFromWasm0(arg0, arg1))); + }, imports.wbg.__wbg_Number_2d1dcfcf4ec51736 = function(arg0) { + return Number(getObject(arg0)); + }, imports.wbg.__wbg___wbindgen_bigint_get_as_i64_6e32f5e6aff02e1d = function(arg0, arg1) { + const v = getObject(arg1), ret = "bigint" == typeof v ? v : void 0; + getDataViewMemory0().setBigInt64(arg0 + 8, isLikeNone(ret) ? BigInt(0) : ret, !0), + getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), !0); + }, imports.wbg.__wbg___wbindgen_boolean_get_dea25b33882b895b = function(arg0) { + const v = getObject(arg0), ret = "boolean" == typeof v ? v : void 0; + return isLikeNone(ret) ? 16777215 : ret ? 1 : 0; + }, imports.wbg.__wbg___wbindgen_debug_string_adfb662ae34724b6 = function(arg0, arg1) { + const ptr1 = passStringToWasm0(debugString(getObject(arg1)), wasm.__wbindgen_export, wasm.__wbindgen_export2), len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4, len1, !0), getDataViewMemory0().setInt32(arg0 + 0, ptr1, !0); + }, imports.wbg.__wbg___wbindgen_in_0d3e1e8f0c669317 = function(arg0, arg1) { + return getObject(arg0) in getObject(arg1); + }, imports.wbg.__wbg___wbindgen_is_bigint_0e1a2e3f55cfae27 = function(arg0) { + return "bigint" == typeof getObject(arg0); + }, imports.wbg.__wbg___wbindgen_is_function_8d400b8b1af978cd = function(arg0) { + return "function" == typeof getObject(arg0); + }, imports.wbg.__wbg___wbindgen_is_object_ce774f3490692386 = function(arg0) { const val = getObject(arg0); - const ret = typeof(val) === 'object' && val !== null; - return ret; - }; - imports.wbg.__wbg___wbindgen_is_undefined_f6b95eab589e0269 = function(arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbg___wbindgen_jsval_eq_b6101cc9cef1fe36 = function(arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1); - return ret; - }; - imports.wbg.__wbg___wbindgen_jsval_loose_eq_766057600fdd1b0d = function(arg0, arg1) { - const ret = getObject(arg0) == getObject(arg1); - return ret; - }; - imports.wbg.__wbg___wbindgen_number_get_9619185a74197f95 = function(arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof(obj) === 'number' ? obj : undefined; - getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); - }; - imports.wbg.__wbg___wbindgen_string_get_a2a31e16edf96e42 = function(arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof(obj) === 'string' ? obj : undefined; - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); - var len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg___wbindgen_throw_dd24417ed36fc46e = function(arg0, arg1) { + return "object" == typeof val && null !== val; + }, imports.wbg.__wbg___wbindgen_is_undefined_f6b95eab589e0269 = function(arg0) { + return void 0 === getObject(arg0); + }, imports.wbg.__wbg___wbindgen_jsval_eq_b6101cc9cef1fe36 = function(arg0, arg1) { + return getObject(arg0) === getObject(arg1); + }, imports.wbg.__wbg___wbindgen_jsval_loose_eq_766057600fdd1b0d = function(arg0, arg1) { + return getObject(arg0) == getObject(arg1); + }, imports.wbg.__wbg___wbindgen_number_get_9619185a74197f95 = function(arg0, arg1) { + const obj = getObject(arg1), ret = "number" == typeof obj ? obj : void 0; + getDataViewMemory0().setFloat64(arg0 + 8, isLikeNone(ret) ? 0 : ret, !0), getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), !0); + }, imports.wbg.__wbg___wbindgen_string_get_a2a31e16edf96e42 = function(arg0, arg1) { + const obj = getObject(arg1), ret = "string" == typeof obj ? obj : void 0; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2), len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4, len1, !0), getDataViewMemory0().setInt32(arg0 + 0, ptr1, !0); + }, imports.wbg.__wbg___wbindgen_throw_dd24417ed36fc46e = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_call_abb4ff46ce38be40 = function() { return handleError(function (arg0, arg1) { - const ret = getObject(arg0).call(getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_done_62ea16af4ce34b24 = function(arg0) { - const ret = getObject(arg0).done; - return ret; - }; - imports.wbg.__wbg_error_7bc7d576a6aaf855 = function(arg0) { - console.error(getObject(arg0)); - }; - imports.wbg.__wbg_get_6b7bd52aca3f9671 = function(arg0, arg1) { - const ret = getObject(arg0)[arg1 >>> 0]; - return addHeapObject(ret); - }; - imports.wbg.__wbg_get_af9dab7e9603ea93 = function() { return handleError(function (arg0, arg1) { - const ret = Reflect.get(getObject(arg0), getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_get_with_ref_key_1dc361bd10053bfe = function(arg0, arg1) { - const ret = getObject(arg0)[getObject(arg1)]; - return addHeapObject(ret); - }; - imports.wbg.__wbg_instanceof_ArrayBuffer_f3320d2419cd0355 = function(arg0) { + }, imports.wbg.__wbg_call_abb4ff46ce38be40 = function() { + return handleError(function(arg0, arg1) { + return addHeapObject(getObject(arg0).call(getObject(arg1))); + }, arguments); + }, imports.wbg.__wbg_done_62ea16af4ce34b24 = function(arg0) { + return getObject(arg0).done; + }, imports.wbg.__wbg_error_7bc7d576a6aaf855 = function(arg0) {}, imports.wbg.__wbg_get_6b7bd52aca3f9671 = function(arg0, arg1) { + return addHeapObject(getObject(arg0)[arg1 >>> 0]); + }, imports.wbg.__wbg_get_af9dab7e9603ea93 = function() { + return handleError(function(arg0, arg1) { + return addHeapObject(Reflect.get(getObject(arg0), getObject(arg1))); + }, arguments); + }, imports.wbg.__wbg_get_with_ref_key_1dc361bd10053bfe = function(arg0, arg1) { + return addHeapObject(getObject(arg0)[getObject(arg1)]); + }, imports.wbg.__wbg_instanceof_ArrayBuffer_f3320d2419cd0355 = function(arg0) { let result; try { result = getObject(arg0) instanceof ArrayBuffer; } catch (_) { - result = false; + result = !1; } - const ret = result; - return ret; - }; - imports.wbg.__wbg_instanceof_Uint8Array_da54ccc9d3e09434 = function(arg0) { + return result; + }, imports.wbg.__wbg_instanceof_Uint8Array_da54ccc9d3e09434 = function(arg0) { let result; try { result = getObject(arg0) instanceof Uint8Array; } catch (_) { - result = false; + result = !1; } - const ret = result; - return ret; - }; - imports.wbg.__wbg_isArray_51fd9e6422c0a395 = function(arg0) { - const ret = Array.isArray(getObject(arg0)); - return ret; - }; - imports.wbg.__wbg_isSafeInteger_ae7d3f054d55fa16 = function(arg0) { - const ret = Number.isSafeInteger(getObject(arg0)); - return ret; - }; - imports.wbg.__wbg_iterator_27b7c8b35ab3e86b = function() { - const ret = Symbol.iterator; - return addHeapObject(ret); - }; - imports.wbg.__wbg_js_click_element_2fe1e774f3d232c7 = function(arg0) { + return result; + }, imports.wbg.__wbg_isArray_51fd9e6422c0a395 = function(arg0) { + return Array.isArray(getObject(arg0)); + }, imports.wbg.__wbg_isSafeInteger_ae7d3f054d55fa16 = function(arg0) { + return Number.isSafeInteger(getObject(arg0)); + }, imports.wbg.__wbg_iterator_27b7c8b35ab3e86b = function() { + return addHeapObject(Symbol.iterator); + }, imports.wbg.__wbg_js_click_element_2fe1e774f3d232c7 = function(arg0) { js_click_element(arg0); - }; - imports.wbg.__wbg_length_22ac23eaec9d8053 = function(arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_length_d45040a40c570362 = function(arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_new_1ba21ce319a06297 = function() { - const ret = new Object(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_25f239778d6112b9 = function() { - const ret = new Array(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_6421f6084cc5bc5a = function(arg0) { - const ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_next_138a17bbf04e926c = function(arg0) { - const ret = getObject(arg0).next; - return addHeapObject(ret); - }; - imports.wbg.__wbg_next_3cfe5c0fe2a4cc53 = function() { return handleError(function (arg0) { - const ret = getObject(arg0).next(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_prototypesetcall_dfe9b766cdc1f1fd = function(arg0, arg1, arg2) { + }, imports.wbg.__wbg_length_22ac23eaec9d8053 = function(arg0) { + return getObject(arg0).length; + }, imports.wbg.__wbg_length_d45040a40c570362 = function(arg0) { + return getObject(arg0).length; + }, imports.wbg.__wbg_new_1ba21ce319a06297 = function() { + return addHeapObject(new Object); + }, imports.wbg.__wbg_new_25f239778d6112b9 = function() { + return addHeapObject(new Array); + }, imports.wbg.__wbg_new_6421f6084cc5bc5a = function(arg0) { + return addHeapObject(new Uint8Array(getObject(arg0))); + }, imports.wbg.__wbg_next_138a17bbf04e926c = function(arg0) { + return addHeapObject(getObject(arg0).next); + }, imports.wbg.__wbg_next_3cfe5c0fe2a4cc53 = function() { + return handleError(function(arg0) { + return addHeapObject(getObject(arg0).next()); + }, arguments); + }, imports.wbg.__wbg_prototypesetcall_dfe9b766cdc1f1fd = function(arg0, arg1, arg2) { Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2)); - }; - imports.wbg.__wbg_set_3f1d0b984ed272ed = function(arg0, arg1, arg2) { + }, imports.wbg.__wbg_set_3f1d0b984ed272ed = function(arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2); - }; - imports.wbg.__wbg_set_7df433eea03a5c14 = function(arg0, arg1, arg2) { + }, imports.wbg.__wbg_set_7df433eea03a5c14 = function(arg0, arg1, arg2) { getObject(arg0)[arg1 >>> 0] = takeObject(arg2); - }; - imports.wbg.__wbg_value_57b7b035e117f7ee = function(arg0) { - const ret = getObject(arg0).value; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) { - // Cast intrinsic for `Ref(String) -> Externref`. - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_cast_4625c577ab2ec9ee = function(arg0) { - // Cast intrinsic for `U64 -> Externref`. - const ret = BigInt.asUintN(64, arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) { - // Cast intrinsic for `F64 -> Externref`. - const ret = arg0; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + }, imports.wbg.__wbg_value_57b7b035e117f7ee = function(arg0) { + return addHeapObject(getObject(arg0).value); + }, imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) { + return addHeapObject(getStringFromWasm0(arg0, arg1)); + }, imports.wbg.__wbindgen_cast_4625c577ab2ec9ee = function(arg0) { + return addHeapObject(BigInt.asUintN(64, arg0)); + }, imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) { + return addHeapObject(arg0); + }, imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + return addHeapObject(getObject(arg0)); + }, imports.wbg.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); - }; - - return imports; + }, imports; } function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedDataViewMemory0 = null; - cachedUint8ArrayMemory0 = null; - - - - return wasm; + return wasm = instance.exports, __wbg_init.__wbindgen_wasm_module = module, cachedDataViewMemory0 = null, + cachedUint8ArrayMemory0 = null, wasm; } function initSync(module) { - if (wasm !== undefined) return wasm; - - - if (typeof module !== 'undefined') { - if (Object.getPrototypeOf(module) === Object.prototype) { - ({module} = module) - } else { - console.warn('using deprecated parameters for `initSync()`; pass a single object instead') - } - } - + if (void 0 !== wasm) return wasm; + void 0 !== module && Object.getPrototypeOf(module) === Object.prototype && ({module: module} = module); const imports = __wbg_get_imports(); - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } - const instance = new WebAssembly.Instance(module, imports); - return __wbg_finalize_init(instance, module); + module instanceof WebAssembly.Module || (module = new WebAssembly.Module(module)); + return __wbg_finalize_init(new WebAssembly.Instance(module, imports), module); } async function __wbg_init(module_or_path) { - if (wasm !== undefined) return wasm; - - - if (typeof module_or_path !== 'undefined') { - if (Object.getPrototypeOf(module_or_path) === Object.prototype) { - ({module_or_path} = module_or_path) - } else { - console.warn('using deprecated parameters for the initialization function; pass a single object instead') - } - } - - if (typeof module_or_path === 'undefined') { - module_or_path = new URL('sentience_core_bg.wasm', import.meta.url); - } + if (void 0 !== wasm) return wasm; + void 0 !== module_or_path && Object.getPrototypeOf(module_or_path) === Object.prototype && ({module_or_path: module_or_path} = module_or_path), + void 0 === module_or_path && (module_or_path = new URL("sentience_core_bg.wasm", import.meta.url)); const imports = __wbg_get_imports(); - - if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { - module_or_path = fetch(module_or_path); - } - - const { instance, module } = await __wbg_load(await module_or_path, imports); - + ("string" == typeof module_or_path || "function" == typeof Request && module_or_path instanceof Request || "function" == typeof URL && module_or_path instanceof URL) && (module_or_path = fetch(module_or_path)); + const {instance: instance, module: module} = await __wbg_load(await module_or_path, imports); return __wbg_finalize_init(instance, module); } export { initSync }; -export default __wbg_init; + +export default __wbg_init; \ No newline at end of file diff --git a/sentience/extension/pkg/sentience_core_bg.wasm b/sentience/extension/pkg/sentience_core_bg.wasm index b001b68c9613422d2d6fd7c88ee28e062472894e..78c777c2a681bc1d308e3b5752a9929a758c8958 100644 GIT binary patch delta 22952 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 C`gex_ delta 22922 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 diff --git a/sentience/extension/release.json b/sentience/extension/release.json index c5e81b7..973b49c 100644 --- a/sentience/extension/release.json +++ b/sentience/extension/release.json @@ -1,9 +1,9 @@ { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/274400382", - "assets_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/274400382/assets", - "upload_url": "https://uploads.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/274400382/assets{?name,label}", - "html_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/tag/v2.2.0", - "id": 274400382, + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/275742777", + "assets_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/275742777/assets", + "upload_url": "https://uploads.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/275742777/assets{?name,label}", + "html_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/tag/v2.3.0", + "id": 275742777, "author": { "login": "rcholic", "id": 135060, @@ -25,21 +25,21 @@ "user_view_type": "public", "site_admin": false }, - "node_id": "RE_kwDOQshiJ84QWwR-", - "tag_name": "v2.2.0", + "node_id": "RE_kwDOQshiJ84Qb4A5", + "tag_name": "v2.3.0", "target_commitish": "main", - "name": "Release v2.2.0", + "name": "Release v2.3.0", "draft": false, "immutable": false, "prerelease": false, - "created_at": "2026-01-06T03:10:35Z", - "updated_at": "2026-01-06T03:16:45Z", - "published_at": "2026-01-06T03:16:10Z", + "created_at": "2026-01-10T19:58:22Z", + "updated_at": "2026-01-10T19:59:50Z", + "published_at": "2026-01-10T19:59:16Z", "assets": [ { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/336823100", - "id": 336823100, - "node_id": "RA_kwDOQshiJ84UE4M8", + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/338832607", + "id": 338832607, + "node_id": "RA_kwDOQshiJ84UMizf", "name": "extension-files.tar.gz", "label": "", "uploader": { @@ -65,17 +65,17 @@ }, "content_type": "application/gzip", "state": "uploaded", - "size": 72250, - "digest": "sha256:adb68bd89b417f23f32c029c6cf045cc3677588e6a7760b7c8d0deb7e2601dd1", - "download_count": 7, - "created_at": "2026-01-06T03:16:44Z", - "updated_at": "2026-01-06T03:16:45Z", - "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.2.0/extension-files.tar.gz" + "size": 73652, + "digest": "sha256:8b2c505a4d0943365b683bb791356cfd33398499039f6a1e9cb1ade4e4d95949", + "download_count": 0, + "created_at": "2026-01-10T19:59:50Z", + "updated_at": "2026-01-10T19:59:50Z", + "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.3.0/extension-files.tar.gz" }, { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/336823099", - "id": 336823099, - "node_id": "RA_kwDOQshiJ84UE4M7", + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/338832606", + "id": 338832606, + "node_id": "RA_kwDOQshiJ84UMize", "name": "extension-package.zip", "label": "", "uploader": { @@ -101,15 +101,15 @@ }, "content_type": "application/zip", "state": "uploaded", - "size": 73962, - "digest": "sha256:7483812c016842fb02add2d6c8d887e321cb9eb89030fee016cf4ea9f812f4bf", + "size": 75735, + "digest": "sha256:892f9a843859bdba36d8aea8bbe727c07ebeea17d925b785cbbc0b33c8aa16c4", "download_count": 0, - "created_at": "2026-01-06T03:16:44Z", - "updated_at": "2026-01-06T03:16:45Z", - "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.2.0/extension-package.zip" + "created_at": "2026-01-10T19:59:50Z", + "updated_at": "2026-01-10T19:59:50Z", + "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.3.0/extension-package.zip" } ], - "tarball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/tarball/v2.2.0", - "zipball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/zipball/v2.2.0", + "tarball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/tarball/v2.3.0", + "zipball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/zipball/v2.3.0", "body": "" }