This commit is contained in:
Nicholas42 2026-01-07 16:43:09 -08:00 committed by GitHub
commit a90e723169
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 6 deletions

View File

@ -832,6 +832,14 @@ class AbstractElements:
"""
raise NotImplementedError
def find_xpath(self, xpath: str, callback: _MultiCallback) -> None:
"""Find all HTML elements matching the given XPath.
Args:
xpath: The XPath to search for.
callback: The callback to be called when the search finished.
"""
class AbstractAudio(QObject):

View File

@ -804,6 +804,11 @@ class WebEngineElements(browsertab.AbstractElements):
js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb)
def find_xpath(self, xpath, callback):
js_code = javascript.assemble('webelem', 'find_xpath', xpath)
js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb)
class WebEngineAudio(browsertab.AbstractAudio):

View File

@ -14,6 +14,9 @@ env:
browser: true
es6: true
parserOptions:
ecmaVersion: 11
extends:
"eslint:all"
@ -34,7 +37,7 @@ rules:
max-statements: ["error", {"max": 40}]
quotes: ["error", "double", {"avoidEscape": true}]
object-property-newline: ["error", {"allowMultiplePropertiesPerLine": true}]
comma-dangle: ["error", "always-multiline"]
comma-dangle: ["error", {"arrays": "always-multiline", "objects": "always-multiline"}]
no-magic-numbers: "off"
no-undefined: "off"
wrap-iife: ["error", "inside"]

View File

@ -165,10 +165,10 @@ window._qutebrowser.webelem = (function() {
let rect = add_offset_rect(elem.getBoundingClientRect(), offset_rect);
if (!rect ||
rect.top > window.innerHeight ||
rect.bottom < 0 ||
rect.left > window.innerWidth ||
rect.right < 0) {
rect.top > window.innerHeight ||
rect.bottom < 0 ||
rect.left > window.innerWidth ||
rect.right < 0) {
return false;
}
@ -257,6 +257,30 @@ window._qutebrowser.webelem = (function() {
return null;
}
funcs.find_xpath = (xpath) => {
function finder(win) {
const doc = win.document;
const xpath_result = doc.evaluate(xpath, doc, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
const out = [];
for (let i = 0; i < xpath_result.snapshotLength; i++) {
out.push(serialize_elem(xpath_result.snapshotItem(i)));
}
if (out.length > 0) {
return out;
}
return null;
}
const direct = finder(window) ?? [];
const in_frames = run_frames(finder) ?? [];
return {"success": true, "result": [...direct, ...in_frames]};
};
funcs.find_id = (id) => {
const elem = document.getElementById(id);
if (elem) {
@ -320,7 +344,7 @@ window._qutebrowser.webelem = (function() {
(frame) => {
// Subtract offsets due to being in an iframe
const frame_offset_rect =
frame.frameElement.getBoundingClientRect();
frame.frameElement.getBoundingClientRect();
return serialize_elem(frame.document.
elementFromPoint(x - frame_offset_rect.left,
y - frame_offset_rect.top), frame);
@ -332,6 +356,27 @@ window._qutebrowser.webelem = (function() {
return serialize_elem(elem);
};
function *traverse_dom(start_node) {
let cur_node = start_node;
while (cur_node !== null) {
yield cur_node;
if (cur_node.children.length > 0) {
cur_node = cur_node.children[0];
continue; // eslint-disable-line no-continue
}
if (iframe_same_domain(cur_node.contentWindow)) {
yield* traverse_dom(cur_node.document);
}
while (cur_node !== null && cur_node.nextSibling === null) {
cur_node = cur_node.parentNode;
}
cur_node = cur_node?.nextElementSibling;
}
}
// Function for returning a selection or focus to python (so we can click
// it). If nothing is selected but there is something focused, returns
// "focused"