agent-zero/lib/browser/extract_dom.js
Alessandro d1827e6c66
Some checks are pending
Build And Publish Docker Images / plan (push) Waiting to run
Build And Publish Docker Images / build (push) Blocked by required conditions
Refactor: use user locale for time displays
Add user-configurable timezone and 12/24-hour preferences, then wire them through settings, runtime snapshots, scheduler payloads, wait handling, notifications, backups, memory, plugin metadata, and frontend formatters.

Keep UTC as the boundary for absolute instants while serializing user-facing dates in the configured or browser-resolved timezone. Preserve scheduler wall-clock inputs in the selected timezone, propagate TZ into desktop/runtime process environments, and restart active desktop sessions when the runtime timezone changes.

Cover the risky paths with timezone regression tests for settings normalization, auto and fixed timezone resolution, scheduler round-trips, memory timestamp conversion, and desktop timezone sync.
2026-05-21 15:26:00 +02:00

166 lines
4.2 KiB
JavaScript

function extractDOM([
selectorLabel = "",
selectorName = "data-a0sel3ct0r",
guidName = "data-a0gu1d",
]) {
let elementCounter = 0;
const now = new Date();
const time = [
String(now.getHours()).padStart(2, "0"),
String(now.getMinutes()).padStart(2, "0"),
String(now.getSeconds()).padStart(2, "0"),
String(now.getMilliseconds()).padStart(3, "0"),
].join("");
const ignoredTags = [
"style",
"script",
"meta",
"link",
"svg",
"noscript",
"path",
];
// Convert number to base64 and trim unnecessary chars
function toBase64(num) {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let result = "";
do {
result = chars[num & 63] + result;
num = num >> 6;
} while (num > 0);
return result;
}
function isElementVisible(element) {
// Return true for non-element nodes
if (element.nodeType !== Node.ELEMENT_NODE) {
return true;
}
const computedStyle = window.getComputedStyle(element);
// Check if element is hidden via CSS
if (
computedStyle.display === "none" ||
computedStyle.visibility === "hidden" ||
computedStyle.opacity === "0"
) {
return false;
}
// Check for hidden input type
if (element.tagName === "INPUT" && element.type === "hidden") {
return false;
}
// Check for hidden attribute
if (
element.hasAttribute("hidden") ||
element.getAttribute("aria-hidden") === "true"
) {
return false;
}
return true;
}
function convertAttribute(tag, attr) {
let out = {
name: attr.name,
value:
typeof attr.value == "string" ? attr.value : JSON.stringify(attr.value),
};
//excluded attributes
if (["srcset"].includes(out.name)) return null;
if (out.name.startsWith("data-") && out.name != selectorName) return null;
if (out.name == "src" && out.value.startsWith("data:"))
out.value = "data...";
return out;
}
function traverseNodes(node, depth = 0, visited = new Set()) {
// Safety checks
if (!node) return "";
if (depth > 1000) return "<!-- Max depth exceeded -->";
const guid = node.getAttribute?.(guidName);
if (guid && visited.has(guid)) {
return `<!-- Circular reference detected at guid: ${guid} -->`;
}
let content = "";
const tagName = node.tagName ? node.tagName.toLowerCase() : "";
// Skip ignored tags
if (tagName && ignoredTags.includes(tagName)) {
return "";
}
if (node.nodeType === Node.ELEMENT_NODE) {
// Add unique ID to the actual DOM element
if (tagName) {
const no = elementCounter++;
const selector = `${no}${selectorLabel}`;
const guid = `${time}-${selector}`;
node.setAttribute(selectorName, selector);
node.setAttribute(guidName, guid);
visited.add(guid);
}
content += `<${tagName}`;
// Add invisible attribute if element is not visible
if (!isElementVisible(node)) {
content += " invisible";
}
for (let attr of node.attributes) {
const out = convertAttribute(tagName, attr);
if (out) content += ` ${out.name}="${out.value}"`;
}
content += ">";
// Handle iframes
if (tagName === "iframe") {
try {
const frameId = elementCounter++;
node.setAttribute(selectorName, frameId);
content += `<!-- IFrame Content Placeholder ${frameId} -->`;
} catch (e) {
console.warn("Error marking iframe:", e);
}
}
if (node.shadowRoot) {
content += "<!-- Shadow DOM Start -->";
for (let shadowChild of node.shadowRoot.childNodes) {
content += traverseNodes(shadowChild, depth + 1, visited);
}
content += "<!-- Shadow DOM End -->";
}
for (let child of node.childNodes) {
content += traverseNodes(child, depth + 1, visited);
}
content += `</${tagName}>`;
} else if (node.nodeType === Node.TEXT_NODE) {
content += node.textContent;
} else if (node.nodeType === Node.COMMENT_NODE) {
content += `<!--${node.textContent}-->`;
}
return content;
}
const fullHTML = traverseNodes(document.documentElement);
return fullHTML;
}