From 0329f335206bf3b48abf3b42f02e92f262194152 Mon Sep 17 00:00:00 2001 From: Euclid Ye Date: Fri, 27 Jun 2025 16:45:58 +0800 Subject: [PATCH] [WebDriver] Reuse "Find Elements" handler in "Find element" (#37737) All "Find Element ..." in [spec](https://w3c.github.io/webdriver/#find-element-from-element) has exactly same step has "Find Elements ...", except they extract the first element if there is any. We now reuse the handler instead of running numerous repetitive steps, same as what we did for ["Find Element from Shadow Root"](https://w3c.github.io/webdriver/#find-element-from-shadow-root) in #37578. This reduces binary size by 98KB for Release profile in Windows and improves maintainability. Testing: `./mach test-wpt -r .\tests\wpt\tests\webdriver\tests\classic\find_element* --product servodriver` --------- Signed-off-by: Euclid Ye --- components/script/script_thread.rs | 60 ----------- components/script/webdriver_handlers.rs | 137 ------------------------ components/shared/embedder/webdriver.rs | 19 ---- components/webdriver_server/lib.rs | 132 ++++------------------- 4 files changed, 23 insertions(+), 325 deletions(-) diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index d109e956165..3547f5b9693 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -2247,33 +2247,6 @@ impl ScriptThread { WebDriverScriptCommand::DeleteCookie(name, reply) => { webdriver_handlers::handle_delete_cookie(&documents, pipeline_id, name, reply) }, - WebDriverScriptCommand::FindElementCSSSelector(selector, reply) => { - webdriver_handlers::handle_find_element_css_selector( - &documents, - pipeline_id, - selector, - reply, - ) - }, - WebDriverScriptCommand::FindElementLinkText(selector, partial, reply) => { - webdriver_handlers::handle_find_element_link_text( - &documents, - pipeline_id, - selector, - partial, - reply, - can_gc, - ) - }, - WebDriverScriptCommand::FindElementTagName(selector, reply) => { - webdriver_handlers::handle_find_element_tag_name( - &documents, - pipeline_id, - selector, - reply, - can_gc, - ) - }, WebDriverScriptCommand::FindElementsCSSSelector(selector, reply) => { webdriver_handlers::handle_find_elements_css_selector( &documents, @@ -2301,39 +2274,6 @@ impl ScriptThread { can_gc, ) }, - WebDriverScriptCommand::FindElementElementCSSSelector(selector, element_id, reply) => { - webdriver_handlers::handle_find_element_element_css_selector( - &documents, - pipeline_id, - element_id, - selector, - reply, - ) - }, - WebDriverScriptCommand::FindElementElementLinkText( - selector, - element_id, - partial, - reply, - ) => webdriver_handlers::handle_find_element_element_link_text( - &documents, - pipeline_id, - element_id, - selector, - partial, - reply, - can_gc, - ), - WebDriverScriptCommand::FindElementElementTagName(selector, element_id, reply) => { - webdriver_handlers::handle_find_element_element_tag_name( - &documents, - pipeline_id, - element_id, - selector, - reply, - can_gc, - ) - }, WebDriverScriptCommand::FindElementElementsCSSSelector(selector, element_id, reply) => { webdriver_handlers::handle_find_element_elements_css_selector( &documents, diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index d36b6605f70..b219da74b77 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -217,25 +217,6 @@ fn all_matching_links( .map(|nodes| matching_links(&nodes, link_text, partial, can_gc).collect()) } -fn first_matching_link( - root_node: &Node, - link_text: String, - partial: bool, - can_gc: CanGc, -) -> Result, ErrorStatus> { - // - // Step 7.2. If a DOMException, SyntaxError, XPathException, or other error occurs - // during the execution of the element location strategy, return error invalid selector. - root_node - .query_selector_all(DOMString::from("a")) - .map_err(|_| ErrorStatus::InvalidSelector) - .map(|nodes| { - matching_links(&nodes, link_text, partial, can_gc) - .take(1) - .next() - }) -} - #[allow(unsafe_code)] unsafe fn object_has_to_json_property( cx: *mut JSContext, @@ -714,65 +695,6 @@ fn retrieve_document_and_check_root_existence( } } -pub(crate) fn handle_find_element_css_selector( - documents: &DocumentCollection, - pipeline: PipelineId, - selector: String, - reply: IpcSender, ErrorStatus>>, -) { - match retrieve_document_and_check_root_existence(documents, pipeline) { - Ok(document) => reply - .send( - document - .QuerySelector(DOMString::from(selector)) - .map_err(|_| ErrorStatus::InvalidSelector) - .map(|element| element.map(|x| x.upcast::().unique_id(pipeline))), - ) - .unwrap(), - Err(error) => reply.send(Err(error)).unwrap(), - } -} - -pub(crate) fn handle_find_element_link_text( - documents: &DocumentCollection, - pipeline: PipelineId, - selector: String, - partial: bool, - reply: IpcSender, ErrorStatus>>, - can_gc: CanGc, -) { - match retrieve_document_and_check_root_existence(documents, pipeline) { - Ok(document) => reply - .send(first_matching_link( - document.upcast::(), - selector.clone(), - partial, - can_gc, - )) - .unwrap(), - Err(error) => reply.send(Err(error)).unwrap(), - } -} - -pub(crate) fn handle_find_element_tag_name( - documents: &DocumentCollection, - pipeline: PipelineId, - selector: String, - reply: IpcSender, ErrorStatus>>, - can_gc: CanGc, -) { - match retrieve_document_and_check_root_existence(documents, pipeline) { - Ok(document) => reply - .send(Ok(document - .GetElementsByTagName(DOMString::from(selector), can_gc) - .elements_iter() - .next() - .map(|element| element.upcast::().unique_id(pipeline)))) - .unwrap(), - Err(error) => reply.send(Err(error)).unwrap(), - } -} - pub(crate) fn handle_find_elements_css_selector( documents: &DocumentCollection, pipeline: PipelineId, @@ -837,65 +759,6 @@ pub(crate) fn handle_find_elements_tag_name( } } -pub(crate) fn handle_find_element_element_css_selector( - documents: &DocumentCollection, - pipeline: PipelineId, - element_id: String, - selector: String, - reply: IpcSender, ErrorStatus>>, -) { - reply - .send( - get_known_element(documents, pipeline, element_id).and_then(|element| { - element - .upcast::() - .query_selector(DOMString::from(selector)) - .map_err(|_| ErrorStatus::InvalidSelector) - .map(|element| element.map(|x| x.upcast::().unique_id(pipeline))) - }), - ) - .unwrap(); -} - -pub(crate) fn handle_find_element_element_link_text( - documents: &DocumentCollection, - pipeline: PipelineId, - element_id: String, - selector: String, - partial: bool, - reply: IpcSender, ErrorStatus>>, - can_gc: CanGc, -) { - reply - .send( - get_known_element(documents, pipeline, element_id).and_then(|element| { - first_matching_link(element.upcast::(), selector.clone(), partial, can_gc) - }), - ) - .unwrap(); -} - -pub(crate) fn handle_find_element_element_tag_name( - documents: &DocumentCollection, - pipeline: PipelineId, - element_id: String, - selector: String, - reply: IpcSender, ErrorStatus>>, - can_gc: CanGc, -) { - reply - .send( - get_known_element(documents, pipeline, element_id).map(|element| { - element - .GetElementsByTagName(DOMString::from(selector), can_gc) - .elements_iter() - .next() - .map(|x| x.upcast::().unique_id(pipeline)) - }), - ) - .unwrap(); -} - pub(crate) fn handle_find_element_elements_css_selector( documents: &DocumentCollection, pipeline: PipelineId, diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 181abe8b62c..ea4b9eebd55 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -131,28 +131,9 @@ pub enum WebDriverScriptCommand { DeleteCookie(String, IpcSender>), ExecuteScript(String, IpcSender), ExecuteAsyncScript(String, IpcSender), - FindElementCSSSelector(String, IpcSender, ErrorStatus>>), - FindElementLinkText(String, bool, IpcSender, ErrorStatus>>), - FindElementTagName(String, IpcSender, ErrorStatus>>), FindElementsCSSSelector(String, IpcSender, ErrorStatus>>), FindElementsLinkText(String, bool, IpcSender, ErrorStatus>>), FindElementsTagName(String, IpcSender, ErrorStatus>>), - FindElementElementCSSSelector( - String, - String, - IpcSender, ErrorStatus>>, - ), - FindElementElementLinkText( - String, - String, - bool, - IpcSender, ErrorStatus>>, - ), - FindElementElementTagName( - String, - String, - IpcSender, ErrorStatus>>, - ), FindElementElementsCSSSelector(String, String, IpcSender, ErrorStatus>>), FindElementElementsLinkText( String, diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index e6e0cbfa4fb..003964ea5c2 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -970,53 +970,11 @@ impl Handler { &self, parameters: &LocatorParameters, ) -> WebDriverResult { - // Step 4. If selector is undefined, return error with error code invalid argument. - if parameters.value.is_empty() { - return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")); - } - let (sender, receiver) = ipc::channel().unwrap(); - - match parameters.using { - LocatorStrategy::CSSSelector => { - let cmd = WebDriverScriptCommand::FindElementCSSSelector( - parameters.value.clone(), - sender, - ); - self.browsing_context_script_command::(cmd)?; - }, - LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => { - let cmd = WebDriverScriptCommand::FindElementLinkText( - parameters.value.clone(), - parameters.using == LocatorStrategy::PartialLinkText, - sender, - ); - self.browsing_context_script_command::(cmd)?; - }, - LocatorStrategy::TagName => { - let cmd = - WebDriverScriptCommand::FindElementTagName(parameters.value.clone(), sender); - self.browsing_context_script_command::(cmd)?; - }, - _ => { - return Err(WebDriverError::new( - ErrorStatus::UnsupportedOperation, - "Unsupported locator strategy", - )); - }, - } - + // Step 1 - 9. + let res = self.handle_find_elements(parameters)?; // Step 10. If result is empty, return error with error code no such element. // Otherwise, return the first element of result. - match wait_for_script_response(receiver)? { - Ok(value) => match value { - Some(value) => { - let value_resp = serde_json::to_value(WebElement(value)).unwrap(); - Ok(WebDriverResponse::Generic(ValueResponse(value_resp))) - }, - None => Err(WebDriverError::new(ErrorStatus::NoSuchElement, "")), - }, - Err(error) => Err(WebDriverError::new(error, "")), - } + unwrap_first_element_response(res) } /// @@ -1234,62 +1192,16 @@ impl Handler { } /// - fn handle_find_element_element( + fn handle_find_element_from_element( &self, element: &WebElement, parameters: &LocatorParameters, ) -> WebDriverResult { - // Step 4. If selector is undefined, return error with error code invalid argument. - if parameters.value.is_empty() { - return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")); - } - let (sender, receiver) = ipc::channel().unwrap(); - - match parameters.using { - LocatorStrategy::CSSSelector => { - let cmd = WebDriverScriptCommand::FindElementElementCSSSelector( - parameters.value.clone(), - element.to_string(), - sender, - ); - self.browsing_context_script_command::(cmd)?; - }, - LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => { - let cmd = WebDriverScriptCommand::FindElementElementLinkText( - parameters.value.clone(), - element.to_string(), - parameters.using == LocatorStrategy::PartialLinkText, - sender, - ); - self.browsing_context_script_command::(cmd)?; - }, - LocatorStrategy::TagName => { - let cmd = WebDriverScriptCommand::FindElementElementTagName( - parameters.value.clone(), - element.to_string(), - sender, - ); - self.browsing_context_script_command::(cmd)?; - }, - _ => { - return Err(WebDriverError::new( - ErrorStatus::UnsupportedOperation, - "Unsupported locator strategy", - )); - }, - } + // Step 1 - 8. + let res = self.handle_find_elements_from_element(element, parameters)?; // Step 9. If result is empty, return error with error code no such element. // Otherwise, return the first element of result. - match wait_for_script_response(receiver)? { - Ok(value) => match value { - Some(value) => { - let value_resp = serde_json::to_value(WebElement(value))?; - Ok(WebDriverResponse::Generic(ValueResponse(value_resp))) - }, - None => Err(WebDriverError::new(ErrorStatus::NoSuchElement, "")), - }, - Err(error) => Err(WebDriverError::new(error, "")), - } + unwrap_first_element_response(res) } /// @@ -1418,22 +1330,11 @@ impl Handler { shadow_root: &ShadowRoot, parameters: &LocatorParameters, ) -> WebDriverResult { + // Step 1 - 8. let res = self.handle_find_elements_from_shadow_root(shadow_root, parameters)?; // Step 9. If result is empty, return error with error code no such element. // Otherwise, return the first element of result. - if let WebDriverResponse::Generic(ValueResponse(values)) = res { - let arr = values.as_array().unwrap(); - if let Some(first) = arr.first() { - Ok(WebDriverResponse::Generic(ValueResponse(first.clone()))) - } else { - Err(WebDriverError::new(ErrorStatus::NoSuchElement, "")) - } - } else { - Err(WebDriverError::new( - ErrorStatus::UnknownError, - "Unexpected response", - )) - } + unwrap_first_element_response(res) } fn handle_get_shadow_root(&self, element: WebElement) -> WebDriverResult { @@ -2255,7 +2156,7 @@ impl WebDriverHandler for Handler { WebDriverCommand::FindElement(ref parameters) => self.handle_find_element(parameters), WebDriverCommand::FindElements(ref parameters) => self.handle_find_elements(parameters), WebDriverCommand::FindElementElement(ref element, ref parameters) => { - self.handle_find_element_element(element, parameters) + self.handle_find_element_from_element(element, parameters) }, WebDriverCommand::FindElementElements(ref element, ref parameters) => { self.handle_find_elements_from_element(element, parameters) @@ -2380,3 +2281,16 @@ where .recv() .map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, "")) } + +fn unwrap_first_element_response(res: WebDriverResponse) -> WebDriverResult { + if let WebDriverResponse::Generic(ValueResponse(values)) = res { + let arr = values.as_array().unwrap(); + if let Some(first) = arr.first() { + Ok(WebDriverResponse::Generic(ValueResponse(first.clone()))) + } else { + Err(WebDriverError::new(ErrorStatus::NoSuchElement, "")) + } + } else { + unreachable!() + } +}