Improve some webdriver conformance tests results (#36673)

These changes allow test_dom_token_list from
/execute_script/collections.py to pass, and various tests in
/execute_script/arguments.py to expose new failures.

Testing: Not run in CI yet, but verified results from
tests/wpt/tests/webdriver/tests/classic/{execute_script,execute_async_script}
locally.
Fixes: #35738

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-05-07 03:22:29 -04:00 committed by GitHub
parent a18c6e2c78
commit f47e69c112
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 128 additions and 165 deletions

View file

@ -65,6 +65,7 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan;
use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
use script_bindings::interfaces::WindowHelpers;
use script_bindings::root::Root;
use script_layout_interface::{
FragmentType, Layout, PendingImageState, QueryMsg, Reflow, ReflowGoal, ReflowRequest,
TrustedNodeAddress, combine_id_with_fragment_type,
@ -146,6 +147,7 @@ use crate::dom::performance::Performance;
use crate::dom::promise::Promise;
use crate::dom::screen::Screen;
use crate::dom::selection::Selection;
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::storage::Storage;
#[cfg(feature = "bluetooth")]
use crate::dom::testrunner::TestRunner;
@ -165,7 +167,7 @@ use crate::script_runtime::{CanGc, JSContext, Runtime};
use crate::script_thread::ScriptThread;
use crate::timers::{IsInterval, TimerCallback};
use crate::unminify::unminified_path;
use crate::webdriver_handlers::jsval_to_webdriver;
use crate::webdriver_handlers::{find_node_by_unique_id_in_document, jsval_to_webdriver};
use crate::{fetch, window_named_properties};
/// A callback to call when a response comes back from the `ImageCache`.
@ -1394,6 +1396,30 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
}
}
fn WebdriverElement(&self, id: DOMString) -> Option<DomRoot<Element>> {
find_node_by_unique_id_in_document(&self.Document(), id.into())
.ok()
.and_then(Root::downcast)
}
fn WebdriverFrame(&self, id: DOMString) -> Option<DomRoot<Element>> {
find_node_by_unique_id_in_document(&self.Document(), id.into())
.ok()
.and_then(Root::downcast::<HTMLIFrameElement>)
.map(Root::upcast::<Element>)
}
fn WebdriverWindow(&self, _id: DOMString) -> Option<DomRoot<Window>> {
warn!("Window references are not supported in webdriver yet");
None
}
fn WebdriverShadowRoot(&self, id: DOMString) -> Option<DomRoot<ShadowRoot>> {
find_node_by_unique_id_in_document(&self.Document(), id.into())
.ok()
.and_then(Root::downcast)
}
// https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
fn GetComputedStyle(
&self,

View file

@ -53,6 +53,7 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
@ -77,12 +78,27 @@ fn find_node_by_unique_id(
pipeline: PipelineId,
node_id: String,
) -> Result<DomRoot<Node>, ErrorStatus> {
match documents.find_document(pipeline).and_then(|document| {
document
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
.find(|node| node.unique_id() == node_id)
}) {
match documents.find_document(pipeline) {
Some(doc) => find_node_by_unique_id_in_document(&doc, node_id),
None => {
if ScriptThread::has_node_id(&node_id) {
Err(ErrorStatus::StaleElementReference)
} else {
Err(ErrorStatus::NoSuchElement)
}
},
}
}
pub(crate) fn find_node_by_unique_id_in_document(
document: &Document,
node_id: String,
) -> Result<DomRoot<Node>, ErrorStatus> {
match document
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
.find(|node| node.unique_id() == node_id)
{
Some(node) => Ok(node),
None => {
if ScriptThread::has_node_id(&node_id) {

View file

@ -532,7 +532,7 @@ where
/// Returns whether `value` is an array-like object (Array, FileList,
/// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection,
/// NodeList).
/// NodeList, DOMTokenList).
///
/// # Safety
/// `cx` must point to a valid, non-null JSContext.
@ -548,6 +548,10 @@ pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: Handl
_ => return false,
};
// TODO: HTMLAllCollection
if root_from_object::<D::DOMTokenList>(object, cx).is_ok() {
return true;
}
if root_from_object::<D::FileList>(object, cx).is_ok() {
return true;
}

View file

@ -150,6 +150,10 @@ partial interface Window {
undefined webdriverCallback(optional any result);
undefined webdriverException(optional any result);
undefined webdriverTimeout();
Element? webdriverElement(DOMString id);
Element? webdriverFrame(DOMString id);
Window? webdriverWindow(DOMString id);
ShadowRoot? webdriverShadowRoot(DOMString id);
};
// https://html.spec.whatwg.org/multipage/#dom-sessionstorage

View file

@ -1915,6 +1915,15 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
}
}
/// <https://w3c.github.io/webdriver/#dfn-web-element-identifier>
const ELEMENT_IDENTIFIER: &str = "element-6066-11e4-a52e-4f735466cecf";
/// <https://w3c.github.io/webdriver/#dfn-web-frame-identifier>
const FRAME_IDENTIFIER: &str = "frame-075b-4da1-b6ba-e579c2d3230a";
/// <https://w3c.github.io/webdriver/#dfn-web-window-identifier>
const WINDOW_IDENTIFIER: &str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
/// <https://w3c.github.io/webdriver/#dfn-shadow-root-identifier>
const SHADOW_ROOT_IDENTIFIER: &str = "shadow-6066-11e4-a52e-4f735466cecf";
fn webdriver_value_to_js_argument(v: &Value) -> String {
match v {
Value::String(s) => format!("\"{}\"", s),
@ -1929,6 +1938,22 @@ fn webdriver_value_to_js_argument(v: &Value) -> String {
format!("[{}]", elems.join(", "))
},
Value::Object(map) => {
let key = map.keys().next().map(String::as_str);
match (key, map.values().next()) {
(Some(ELEMENT_IDENTIFIER), Some(id)) => {
return format!("window.webdriverElement({})", id);
},
(Some(FRAME_IDENTIFIER), Some(id)) => {
return format!("window.webdriverFrame({})", id);
},
(Some(WINDOW_IDENTIFIER), Some(id)) => {
return format!("window.webdriverWindow({})", id);
},
(Some(SHADOW_ROOT_IDENTIFIER), Some(id)) => {
return format!("window.webdriverShadowRoot({})", id);
},
_ => {},
}
let elems = map
.iter()
.map(|(k, v)| format!("{}: {}", k, webdriver_value_to_js_argument(v)))