init
This commit is contained in:
43
browser_env/scripts/__init__.py
Normal file
43
browser_env/scripts/__init__.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
rootdir = Path(__file__).parent
|
||||
|
||||
# marker, gpt-4v-act style
|
||||
with open(os.path.join(rootdir, 'local_marker.js'), 'r') as f:
|
||||
local_marker_script = f.read()
|
||||
|
||||
with open(os.path.join(rootdir, 'mix_marker.js'), 'r') as f:
|
||||
mix_marker_script = f.read()
|
||||
|
||||
with open(os.path.join(rootdir, 'get_data.js'), 'r') as f:
|
||||
get_rect_script = f.read()
|
||||
|
||||
# canva handler
|
||||
with open(os.path.join(rootdir, 'canva_handler.js'), 'r') as f:
|
||||
canva_handler_script = f.read()
|
||||
|
||||
# draw label on page
|
||||
with open(os.path.join(rootdir, 'label_marker.js'), 'r') as f:
|
||||
label_marker_script = f.read()
|
||||
|
||||
# get text from page
|
||||
with open(os.path.join(rootdir, 'get_text.js'), 'r') as f:
|
||||
get_text_script = f.read()
|
||||
|
||||
# remove label draw on page
|
||||
remove_label_mark_script = """
|
||||
() => {
|
||||
document.querySelectorAll(".our-dom-marker").forEach(item => {
|
||||
document.body.removeChild(item);
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
remove_id_script = """
|
||||
() => {
|
||||
Array.from(document.getElementsByClassName('possible-clickable-element')).forEach((element) => {
|
||||
element.classList.remove('possible-clickable-element');
|
||||
element.removeAttribute('data-testid');
|
||||
});
|
||||
}
|
||||
"""
|
||||
44
browser_env/scripts/canva_handler.js
Normal file
44
browser_env/scripts/canva_handler.js
Normal file
@@ -0,0 +1,44 @@
|
||||
() => {
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll("canvas")
|
||||
);
|
||||
|
||||
var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||
|
||||
items = items.map(element => {
|
||||
// var img = element.toDataURL("image/png");
|
||||
var bb = element.getClientRects();
|
||||
var rect = {
|
||||
left: -1,
|
||||
top: -1,
|
||||
right: -1,
|
||||
bottom: -1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
if (bb.length > 0) {
|
||||
bb = bb[0];
|
||||
rect = {
|
||||
left: Math.max(0, bb.left),
|
||||
top: Math.max(0, bb.top),
|
||||
right: Math.min(vw, bb.right),
|
||||
bottom: Math.min(vh, bb.bottom)
|
||||
};
|
||||
rect = {
|
||||
...rect,
|
||||
width: rect.right - rect.left,
|
||||
height: rect.bottom - rect.top
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
rects: rect,
|
||||
tag: element.tagName.toLowerCase?.() || "",
|
||||
text: element.textContent.trim().replace(/\s{2,}/g, ' '),
|
||||
// img: img
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
73
browser_env/scripts/get_data.js
Normal file
73
browser_env/scripts/get_data.js
Normal file
@@ -0,0 +1,73 @@
|
||||
(packet) => {
|
||||
function int2str(index) {
|
||||
var str = "";
|
||||
while (index >= 0) {
|
||||
str = String.fromCharCode(65 + index % 26) + str;
|
||||
index = Math.floor(index / 26) - 1;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
selector = packet.selector
|
||||
index = packet.startIndex
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll(selector)
|
||||
);
|
||||
|
||||
var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||
|
||||
items = items.filter(
|
||||
x => !items.some(y => x.contains(y) && !(x == y))
|
||||
).map(element => {
|
||||
var bb = element.getClientRects();
|
||||
var rect = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
var keep = false;
|
||||
var text = "", id = -1;
|
||||
if (bb.length > 0) {
|
||||
bb = bb[0];
|
||||
rect = {
|
||||
left: Math.max(0, bb.left),
|
||||
top: Math.max(0, bb.top),
|
||||
right: Math.min(vw, bb.right),
|
||||
bottom: Math.min(vh, bb.bottom)
|
||||
};
|
||||
rect = {
|
||||
...rect,
|
||||
width: rect.right - rect.left,
|
||||
height: rect.bottom - rect.top
|
||||
};
|
||||
if (rect.width > 0 || rect.height > 0) {
|
||||
keep = true;
|
||||
if (index >= 0) {
|
||||
id = int2str(index++);
|
||||
element.setAttribute("data-testid", id);
|
||||
}
|
||||
var childNodes = element.childNodes;
|
||||
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
if (childNodes[i].nodeType == Node.TEXT_NODE) {
|
||||
text += childNodes[i].textContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
keep: true,
|
||||
id,
|
||||
rects: rect,
|
||||
tag: element.tagName.toLowerCase?.() || "",
|
||||
text,//: element.innerText?.trim().replace(/\s{2,}/g, " ") || ""
|
||||
};
|
||||
}).filter(x => x.keep);
|
||||
|
||||
return [items, index];
|
||||
}
|
||||
48
browser_env/scripts/get_text.js
Normal file
48
browser_env/scripts/get_text.js
Normal file
@@ -0,0 +1,48 @@
|
||||
() => {
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll("*")
|
||||
);
|
||||
|
||||
var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||
const ignoreTags = ["script", "html"];
|
||||
items = items.map(element => {
|
||||
const tag = element.tagName.toLowerCase?.() || "";
|
||||
var bb = element.getClientRects();
|
||||
var keep = false;
|
||||
var text = '';
|
||||
|
||||
const domId = element.getAttribute('data-testid');
|
||||
var id = domId? parseInt(domId): "-";
|
||||
|
||||
if (bb.length > 0) {
|
||||
bb = bb[0];
|
||||
var width = Math.min(vw, bb.right) - Math.max(0, bb.left);
|
||||
var height = Math.min(vh, bb.bottom) - Math.max(0, bb.top);
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
keep = true;
|
||||
var childNodes = element.childNodes;
|
||||
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
if (childNodes[i].nodeType == Node.TEXT_NODE) {
|
||||
text += childNodes[i].textContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text = text.trim().replace(/\s{2,}/g, ' ');
|
||||
if (ignoreTags.includes(tag)) keep = false;
|
||||
if (id == "-" && text.length == 0) keep = false;
|
||||
|
||||
return {
|
||||
keep,
|
||||
tag,
|
||||
id,
|
||||
text, //:element.innerText?.trim().replace(/\s{2,}/g, " ") || ""
|
||||
};
|
||||
}).filter(x => x.keep);
|
||||
|
||||
return items;
|
||||
}
|
||||
65
browser_env/scripts/label_marker.js
Normal file
65
browser_env/scripts/label_marker.js
Normal file
@@ -0,0 +1,65 @@
|
||||
(items) => {
|
||||
function getRandomColor() {
|
||||
var letters = '0123456789ABCDEF';
|
||||
var color = '#';
|
||||
for (var i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
items.filter(
|
||||
item => item.id != ""
|
||||
).forEach((item) => {
|
||||
const bbox = item.rects;
|
||||
const id_string = `dom-marker-id-${index}`;
|
||||
|
||||
index = item.id;
|
||||
|
||||
outerElement = document.createElement("div");
|
||||
outerElement.classList.add("our-dom-marker");
|
||||
// var borderColor = getRandomColor();
|
||||
var borderColor = "#FFFF00";
|
||||
outerElement.style.outline = `2px dashed ${borderColor}`;
|
||||
outerElement.style.position = "fixed";
|
||||
outerElement.style.left = bbox.left - 2 + "px";
|
||||
outerElement.style.top = bbox.top - 2 + "px";
|
||||
outerElement.style.width = bbox.width + 4 + "px";
|
||||
outerElement.style.height = bbox.height + 4 + "px";
|
||||
outerElement.style.pointerEvents = "none";
|
||||
outerElement.style.boxSizing = "border-box";
|
||||
outerElement.style.zIndex = 2147483647;
|
||||
|
||||
innerElement = document.createElement("div");
|
||||
innerElement.classList.add("our-dom-marker");
|
||||
innerElement.style.outline = `2px dashed #222288`;
|
||||
innerElement.style.position = "fixed";
|
||||
innerElement.style.left = bbox.left + "px";
|
||||
innerElement.style.top = bbox.top + "px";
|
||||
innerElement.style.width = bbox.width + "px";
|
||||
innerElement.style.height = bbox.height + "px";
|
||||
innerElement.style.pointerEvents = "none";
|
||||
innerElement.style.boxSizing = "border-box";
|
||||
innerElement.style.zIndex = 2147483647;
|
||||
|
||||
// Add floating label at the corner
|
||||
var label = document.createElement("span");
|
||||
var topPosition = 25;
|
||||
if (bbox.top < 25) topPosition = bbox.top;
|
||||
label.textContent = index;
|
||||
label.style.position = "absolute";
|
||||
label.style.top = `-${topPosition}px`;
|
||||
label.style.left = "0px";
|
||||
label.style.background = borderColor;
|
||||
label.style.color = "black";
|
||||
label.style.padding = "2px 4px";
|
||||
label.style.fontSize = "16px";
|
||||
label.style.borderRadius = "2px";
|
||||
label.style.fontWeight = "bold";
|
||||
outerElement.appendChild(label);
|
||||
|
||||
document.body.appendChild(outerElement);
|
||||
document.body.appendChild(innerElement);
|
||||
})
|
||||
return items;
|
||||
}
|
||||
46
browser_env/scripts/local_marker.js
Normal file
46
browser_env/scripts/local_marker.js
Normal file
@@ -0,0 +1,46 @@
|
||||
() => {
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll('*')
|
||||
).map((element) => {
|
||||
var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||
|
||||
var rects = [...element.getClientRects()].filter(bb => {
|
||||
var center_x = bb.left + bb.width / 2;
|
||||
var center_y = bb.top + bb.height / 2;
|
||||
var elAtCenter = document.elementFromPoint(center_x, center_y);
|
||||
|
||||
return elAtCenter === element || element.contains(elAtCenter)
|
||||
}).map(bb => {
|
||||
const rect = {
|
||||
left: Math.max(0, bb.left),
|
||||
top: Math.max(0, bb.top),
|
||||
right: Math.min(vw, bb.right),
|
||||
bottom: Math.min(vh, bb.bottom)
|
||||
};
|
||||
return {
|
||||
...rect,
|
||||
width: rect.right - rect.left,
|
||||
height: rect.bottom - rect.top
|
||||
}
|
||||
});
|
||||
|
||||
var area = rects.reduce((acc, rect) => acc + rect.width * rect.height, 0);
|
||||
|
||||
return {
|
||||
element: element,
|
||||
include: window.getComputedStyle(element).cursor == "pointer",
|
||||
area,
|
||||
rects,
|
||||
text: element.textContent.trim().replace(/\s{2,}/g, ' '),
|
||||
};
|
||||
}).filter(item =>
|
||||
item.include && (item.area >= 20)
|
||||
)
|
||||
|
||||
items = items.filter(x => !items.some(y => x.element.contains(y.element) && !(x == y)))
|
||||
|
||||
items.forEach(item => {
|
||||
item.element.classList.add('possible-clickable-element');
|
||||
});
|
||||
}
|
||||
148
browser_env/scripts/mix_marker.js
Normal file
148
browser_env/scripts/mix_marker.js
Normal file
@@ -0,0 +1,148 @@
|
||||
() => {
|
||||
var items = Array.prototype.slice.call(
|
||||
document.querySelectorAll('*')
|
||||
).map(function(element) {
|
||||
var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
|
||||
|
||||
var rects = [...element.getClientRects()].filter(bb => {
|
||||
var center_x = bb.left + bb.width / 2;
|
||||
var center_y = bb.top + bb.height / 2;
|
||||
var elAtCenter = document.elementFromPoint(center_x, center_y);
|
||||
|
||||
if (!elAtCenter) return false;
|
||||
return elAtCenter === element || element.contains(elAtCenter)
|
||||
}).map(bb => {
|
||||
const rect = {
|
||||
left: Math.max(0, bb.left),
|
||||
top: Math.max(0, bb.top),
|
||||
right: Math.min(vw, bb.right),
|
||||
bottom: Math.min(vh, bb.bottom)
|
||||
};
|
||||
return {
|
||||
...rect,
|
||||
width: rect.right - rect.left,
|
||||
height: rect.bottom - rect.top
|
||||
}
|
||||
});
|
||||
// var rects = [];
|
||||
var area = rects.reduce((acc, rect) => acc + rect.width * rect.height, 0);
|
||||
|
||||
const tagName = element.tagName.toLowerCase?.() || "";
|
||||
let isClickable = ((element.onclick != null) || window.getComputedStyle(element).cursor == "pointer");
|
||||
|
||||
// Insert area elements that provide click functionality to an img.
|
||||
if (tagName === "img") {
|
||||
let mapName = element.getAttribute("usemap");
|
||||
if (mapName) {
|
||||
const imgClientRects = element.getClientRects();
|
||||
mapName = mapName.replace(/^#/, "").replace('"', '\\"');
|
||||
const map = document.querySelector(`map[name=\"${mapName}\"]`);
|
||||
if (map && (imgClientRects.length > 0)) isClickable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isClickable) {
|
||||
const role = element.getAttribute("role");
|
||||
const clickableRoles = [
|
||||
"button",
|
||||
"tab",
|
||||
"link",
|
||||
"checkbox",
|
||||
"menuitem",
|
||||
"menuitemcheckbox",
|
||||
"menuitemradio",
|
||||
"radio",
|
||||
];
|
||||
if (role != null && clickableRoles.includes(role.toLowerCase())) {
|
||||
isClickable = true;
|
||||
} else {
|
||||
const contentEditable = element.getAttribute("contentEditable");
|
||||
if (
|
||||
contentEditable != null &&
|
||||
["", "contenteditable", "true"].includes(contentEditable.toLowerCase())
|
||||
) {
|
||||
isClickable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for jsaction event listeners on the element.
|
||||
if (!isClickable && element.hasAttribute("jsaction")) {
|
||||
const jsactionRules = element.getAttribute("jsaction").split(";");
|
||||
for (let jsactionRule of jsactionRules) {
|
||||
const ruleSplit = jsactionRule.trim().split(":");
|
||||
if ((ruleSplit.length >= 1) && (ruleSplit.length <= 2)) {
|
||||
const [eventType, namespace, actionName] = ruleSplit.length === 1
|
||||
? ["click", ...ruleSplit[0].trim().split("."), "_"]
|
||||
: [ruleSplit[0], ...ruleSplit[1].trim().split("."), "_"];
|
||||
if (!isClickable) {
|
||||
isClickable = (eventType === "click") && (namespace !== "none") && (actionName !== "_");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isClickable) {
|
||||
const clickableTags = [
|
||||
"input",
|
||||
"textarea",
|
||||
"select",
|
||||
"button",
|
||||
"a",
|
||||
"iframe",
|
||||
"video",
|
||||
"object",
|
||||
"embed",
|
||||
"details"
|
||||
];
|
||||
isClickable = clickableTags.includes(tagName);
|
||||
}
|
||||
|
||||
if (!isClickable) {
|
||||
if (tagName === "label")
|
||||
isClickable = (element.control != null) && !element.control.disabled;
|
||||
else if (tagName === "img")
|
||||
isClickable = ["zoom-in", "zoom-out"].includes(element.style.cursor);
|
||||
}
|
||||
|
||||
// An element with a class name containing the text "button" might be clickable. However, real
|
||||
// clickables are often wrapped in elements with such class names. So, when we find clickables
|
||||
// based only on their class name, we mark them as unreliable.
|
||||
const className = element.getAttribute("class");
|
||||
if (!isClickable && className && className.toLowerCase().includes("button")) {
|
||||
isClickable = true;
|
||||
}
|
||||
|
||||
// Elements with tabindex are sometimes useful, but usually not. We can treat them as second
|
||||
// class citizens when it improves UX, so take special note of them.
|
||||
const tabIndexValue = element.getAttribute("tabindex");
|
||||
const tabIndex = tabIndexValue ? parseInt(tabIndexValue) : -1;
|
||||
if (!isClickable && !(tabIndex < 0) && !isNaN(tabIndex)) {
|
||||
isClickable = true;
|
||||
}
|
||||
|
||||
const idValue = element.getAttribute("id");
|
||||
const id = idValue ? idValue.toLowerCase() : "";
|
||||
if (isClickable && area == 0) {
|
||||
const textValue = element.textContent.trim().replace(/\s{2,}/g, ' ');
|
||||
clickable_msg = `${tagName}[id=${id}] ${isClickable} (${area}) ${textValue}`
|
||||
}
|
||||
|
||||
return {
|
||||
element: element,
|
||||
include: isClickable,
|
||||
area,
|
||||
rects,
|
||||
text: element.textContent.trim().replace(/\s{2,}/g, ' ')
|
||||
};
|
||||
}).filter(item =>
|
||||
item.include && (item.area >= 1)
|
||||
);
|
||||
|
||||
items = items.filter(x => !items.some(y => x.element.contains(y.element) && !(x == y)))
|
||||
|
||||
items.forEach(item => {
|
||||
item.element.classList.add('possible-clickable-element');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user