///|
fn is_valid_locate_nodes_locator_type(locator_type : String) -> Bool {
locator_type == "css" ||
locator_type == "xpath" ||
locator_type == "innerText" ||
locator_type == "accessibility" ||
locator_type == "context"
}
///|
/// Convert a small XPath subset used by WPT locateNodes tests to CSS.
fn xpath_to_css_selector(xpath : String) -> String? {
let trimmed = xpath.trim().to_owned()
if trimmed == ".//*[name()='circle']" {
return Some("circle")
}
let body = if trimmed.has_prefix(".//") {
trimmed.unsafe_substring(start=3, end=trimmed.length())
} else if trimmed.has_prefix("//") {
trimmed.unsafe_substring(start=2, end=trimmed.length())
} else {
return None
}
if body == "" {
return None
}
match find_substring(body, "[@", 0) {
None => if body.contains("[") { None } else { Some(body) }
Some(predicate_idx) => {
let selector_tag = body.unsafe_substring(start=0, end=predicate_idx)
if selector_tag == "" {
return None
}
let predicate_start = predicate_idx + 2
let predicate_full = body.unsafe_substring(
start=predicate_start,
end=body.length(),
)
if predicate_full == "" || !predicate_full.has_suffix("]") {
return None
}
let predicate = predicate_full.unsafe_substring(
start=0,
end=predicate_full.length() - 1,
)
match find_substring(predicate, "=", 0) {
None => None
Some(eq_idx) => {
let attr_name = predicate.unsafe_substring(start=0, end=eq_idx)
let quoted_value = predicate.unsafe_substring(
start=eq_idx + 1,
end=predicate.length(),
)
if attr_name == "" || quoted_value.length() < 2 {
return None
}
let quote_chars = quoted_value.to_array()
let quote = quote_chars[0]
let end_quote = quote_chars[quote_chars.length() - 1]
if (quote != '\'' && quote != '"') || end_quote != quote {
return None
}
let attr_value = quoted_value.unsafe_substring(
start=1,
end=quoted_value.length() - 1,
)
Some(
selector_tag +
"[" +
attr_name.trim().to_owned() +
"=\"" +
attr_value +
"\"]",
)
}
}
}
}
}
///|
/// Check whether localName should use SVG namespace in BiDi node values.
fn is_svg_local_name(local_name : String) -> Bool {
local_name == "svg" ||
local_name == "circle" ||
local_name == "text" ||
local_name == "path" ||
local_name == "rect" ||
local_name == "ellipse" ||
local_name == "line" ||
local_name == "polyline" ||
local_name == "polygon" ||
local_name == "g" ||
local_name == "defs" ||
local_name == "use"
}
///|
/// Normalize namespaceURI for SVG-like nodes in locateNodes response.
fn normalize_svg_namespace_in_node(node : Json) -> Json {
match node {
Object(node_map) => {
let out_node : Map[String, Json] = {}
for key, value in node_map {
out_node[key] = value
}
match node_map.get("value") {
Some(Object(value_map)) => {
let out_value : Map[String, Json] = {}
for key, value in value_map {
out_value[key] = value
}
let local_name = match value_map.get("localName") {
Some(String(name)) => name
_ => ""
}
let namespace_uri = match value_map.get("namespaceURI") {
Some(String(uri)) => uri
_ => ""
}
if namespace_uri == "http://www.w3.org/1999/xhtml" &&
is_svg_local_name(local_name) {
out_value["namespaceURI"] = Json::string(
"http://www.w3.org/2000/svg",
)
}
out_node["value"] = make_object(out_value)
}
_ => ()
}
make_object(out_node)
}
_ => node
}
}
///|
/// Normalize SVG namespace for all nodes in locateNodes response array.
fn normalize_svg_namespace_in_nodes(nodes : Json) -> Json {
match nodes {
Array(values) => {
let out : Array[Json] = []
for value in values {
out.push(normalize_svg_namespace_in_node(value))
}
Json::array(out)
}
_ => nodes
}
}
///|
/// Encode string array as a JavaScript string-array literal.
fn js_string_array_literal(values : Array[String]) -> String {
let parts : Array[String] = []
for value in values {
parts.push("\"" + escape_js_string(value) + "\"")
}
"[" + parts.join(",") + "]"
}
///|
/// Shared locateNodes JS prelude for resolving start node shared ids.
fn locate_nodes_js_shared_roots_from_ids() -> String {
(
#| const __store = globalThis.__bidiSharedNodeStore || new Map();
#| const __roots = [];
#| if (__ids.length === 0) {
#| if (globalThis.document) __roots.push(globalThis.document);
#| } else {
#| for (const __id of __ids) {
#| const __node = __store.get(__id);
#| if (__node) __roots.push(__node);
#| }
#| }
)
}
///|
/// Shared array-like conversion helper used by locateNodes JS snippets.
fn locate_nodes_js_to_array() -> String {
(
#| const __toArray = (__valueLike) => {
#| if (!__valueLike) return [];
#| if (Array.isArray(__valueLike)) return __valueLike;
#| try {
#| return Array.from(__valueLike);
#| } catch (_e) {}
#| const __out = [];
#| const __len = Number(__valueLike.length || 0);
#| for (let __i = 0; __i < __len; __i++) __out.push(__valueLike[__i]);
#| return __out;
#| };
)
}
///|
/// Shared child collector for element traversal that includes shadow roots.
fn locate_nodes_js_collect_element_children() -> String {
(
#| const __collectChildren = (__node) => {
#| const __out = [];
#| if (!__node) return __out;
#| const __direct = Array.isArray(__node._children)
#| ? __node._children
#| : __toArray(__node.children || __node.childNodes);
#| for (const __child of __direct) {
#| if (__child && __child.nodeType === 1) __out.push(__child);
#| }
#| const __shadow = __node.shadowRoot;
#| if (__shadow) {
#| const __shadowChildren = Array.isArray(__shadow._children)
#| ? __shadow._children
#| : __toArray(__shadow.children || __shadow.childNodes);
#| for (const __child of __shadowChildren) {
#| if (__child && __child.nodeType === 1) __out.push(__child);
#| }
#| }
#| return __out;
#| };
)
}
///|
/// Shared unique result push helper for locateNodes snippets.
fn locate_nodes_js_unique_result_push() -> String {
(
#| const __seen = new Set();
#| const __result = [];
#| const __push = (__node) => {
#| if (!__node || __node.nodeType !== 1) return;
#| if (__seen.has(__node)) return;
#| __seen.add(__node);
#| __result.push(__node);
#| };
)
}
///|
/// Evaluate locateNodes expression and normalize result to nodes array.
fn BidiProtocol::evaluate_locate_expression(
self : BidiProtocol,
ctx_id : String,
expression : String,
serialization_options_json : String,
) -> Json {
set_runtime_context(ctx_id)
set_runtime_context_frames(
ctx_id,
self.context_children.get(ctx_id).unwrap_or([]),
)
self.apply_effective_viewport_to_runtime_context(ctx_id, ctx_id)
let eval_result_json = evaluate_js_with_console(
expression, false, false, false, serialization_options_json,
)
let eval_result = @json.parse(eval_result_json) catch {
_ => return Json::array([])
}
match get_string_field(eval_result, "type") {
Some("success") =>
match get_field(eval_result, "result") {
Some(Object(result_map)) =>
match result_map.get("type") {
Some(String("array")) =>
match result_map.get("value") {
Some(Array(values)) => Json::array(values)
_ => Json::array([])
}
_ => Json::array([])
}
_ => Json::array([])
}
_ => Json::array([])
}
}