1use std::collections::{HashMap, HashSet};
6use std::ffi::CString;
7use std::ptr::NonNull;
8
9use cookie::Cookie;
10use embedder_traits::{
11 CustomHandlersAutomationMode, JSValue, JavaScriptEvaluationError,
12 JavaScriptEvaluationResultSerializationError, WebDriverFrameId, WebDriverJSResult,
13 WebDriverLoadStatus,
14};
15use euclid::default::{Point2D, Rect, Size2D};
16use hyper_serde::Serde;
17use js::context::JSContext;
18use js::conversions::jsstr_to_string;
19use js::jsapi::{
20 self, GetPropertyKeys, HandleValueArray, JS_GetOwnPropertyDescriptorById, JS_GetPropertyById,
21 JS_IsExceptionPending, JSAutoRealm, JSObject, JSType, PropertyDescriptor,
22};
23use js::jsval::UndefinedValue;
24use js::realm::CurrentRealm;
25use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue};
26use js::rust::{Handle, HandleObject, HandleValue, IdVector, ToString};
27use net_traits::CookieSource::{HTTP, NonHTTP};
28use net_traits::CoreResourceMsg::{DeleteCookie, DeleteCookies, GetCookiesForUrl, SetCookieForUrl};
29use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
30use script_bindings::conversions::is_array_like;
31use script_bindings::num::Finite;
32use script_bindings::reflector::DomObject;
33use script_bindings::settings_stack::run_a_script;
34use servo_base::generic_channel::{self, GenericOneshotSender, GenericSend, GenericSender};
35use servo_base::id::{BrowsingContextId, PipelineId};
36use webdriver::error::ErrorStatus;
37
38use crate::DomTypeHolder;
39use crate::document_collection::DocumentCollection;
40use crate::dom::attr::is_boolean_attribute;
41use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
42use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
43use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
44use crate::dom::bindings::codegen::Bindings::ElementBinding::{
45 ElementMethods, ScrollIntoViewOptions, ScrollLogicalPosition,
46};
47use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
48use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
49use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
50use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
51use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
52use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
53use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
54use crate::dom::bindings::codegen::Bindings::WindowBinding::{
55 ScrollBehavior, ScrollOptions, WindowMethods,
56};
57use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
58use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
59 XPathResultConstants, XPathResultMethods,
60};
61use crate::dom::bindings::codegen::UnionTypes::BooleanOrScrollIntoViewOptions;
62use crate::dom::bindings::conversions::{
63 ConversionBehavior, ConversionResult, FromJSValConvertible, get_property, get_property_jsval,
64 jsid_to_string, root_from_object,
65};
66use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
67use crate::dom::bindings::inheritance::Castable;
68use crate::dom::bindings::reflector::DomGlobal;
69use crate::dom::bindings::root::DomRoot;
70use crate::dom::bindings::str::DOMString;
71use crate::dom::document::Document;
72use crate::dom::domrect::DOMRect;
73use crate::dom::element::Element;
74use crate::dom::eventtarget::EventTarget;
75use crate::dom::globalscope::GlobalScope;
76use crate::dom::html::htmlbodyelement::HTMLBodyElement;
77use crate::dom::html::htmldatalistelement::HTMLDataListElement;
78use crate::dom::html::htmlelement::HTMLElement;
79use crate::dom::html::htmlformelement::FormControl;
80use crate::dom::html::htmliframeelement::HTMLIFrameElement;
81use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
82use crate::dom::html::htmloptionelement::HTMLOptionElement;
83use crate::dom::html::htmlselectelement::HTMLSelectElement;
84use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
85use crate::dom::html::input_element::HTMLInputElement;
86use crate::dom::input_element::input_type::InputType;
87use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
88use crate::dom::nodelist::NodeList;
89use crate::dom::types::ShadowRoot;
90use crate::dom::validitystate::ValidationFlags;
91use crate::dom::window::Window;
92use crate::dom::xmlserializer::XMLSerializer;
93use crate::realms::{InRealm, enter_auto_realm, enter_realm};
94use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
95use crate::script_thread::ScriptThread;
96
97fn is_stale(element: &Element) -> bool {
99 !element.owner_document().is_active() || !element.is_connected()
102}
103
104fn is_detached(shadow_root: &ShadowRoot) -> bool {
106 !shadow_root.owner_document().is_active() || is_stale(&shadow_root.Host())
109}
110
111fn is_disabled(element: &Element) -> bool {
113 if element.is::<HTMLOptionElement>() || element.is::<HTMLOptGroupElement>() {
115 let disabled = element
117 .upcast::<Node>()
118 .inclusive_ancestors(ShadowIncluding::No)
119 .any(|node| {
120 if node.is::<HTMLOptGroupElement>() || node.is::<HTMLSelectElement>() {
121 node.downcast::<Element>().unwrap().is_actually_disabled()
124 } else {
125 false
126 }
127 });
128
129 if disabled {
134 return true;
135 }
136 }
137 element.is_actually_disabled()
139}
140
141pub(crate) fn handle_get_known_window(
142 documents: &DocumentCollection,
143 pipeline: PipelineId,
144 webview_id: String,
145 reply: GenericSender<Result<(), ErrorStatus>>,
146) {
147 if reply
148 .send(
149 documents
150 .find_window(pipeline)
151 .map_or(Err(ErrorStatus::NoSuchWindow), |window| {
152 let window_proxy = window.window_proxy();
153 if window_proxy.browsing_context_id() != window_proxy.webview_id() ||
155 window_proxy.webview_id().to_string() != webview_id
156 {
157 Err(ErrorStatus::NoSuchWindow)
158 } else {
159 Ok(())
160 }
161 }),
162 )
163 .is_err()
164 {
165 error!("Webdriver get known window reply failed");
166 }
167}
168
169pub(crate) fn handle_get_known_shadow_root(
170 documents: &DocumentCollection,
171 pipeline: PipelineId,
172 shadow_root_id: String,
173 reply: GenericSender<Result<(), ErrorStatus>>,
174) {
175 let result = get_known_shadow_root(documents, pipeline, shadow_root_id).map(|_| ());
176 if reply.send(result).is_err() {
177 error!("Webdriver get known shadow root reply failed");
178 }
179}
180
181fn get_known_shadow_root(
183 documents: &DocumentCollection,
184 pipeline: PipelineId,
185 node_id: String,
186) -> Result<DomRoot<ShadowRoot>, ErrorStatus> {
187 let doc = documents
188 .find_document(pipeline)
189 .ok_or(ErrorStatus::NoSuchWindow)?;
190 if !ScriptThread::has_node_id(pipeline, &node_id) {
193 return Err(ErrorStatus::NoSuchShadowRoot);
194 }
195
196 let node = find_node_by_unique_id_in_document(&doc, node_id);
199
200 if let Some(ref node) = node &&
203 !node.is::<ShadowRoot>()
204 {
205 return Err(ErrorStatus::NoSuchShadowRoot);
206 }
207
208 let Some(node) = node else {
210 return Err(ErrorStatus::DetachedShadowRoot);
211 };
212
213 let shadow_root = DomRoot::downcast::<ShadowRoot>(node).unwrap();
217 if is_detached(&shadow_root) {
218 return Err(ErrorStatus::DetachedShadowRoot);
219 }
220 Ok(shadow_root)
222}
223
224pub(crate) fn handle_get_known_element(
225 documents: &DocumentCollection,
226 pipeline: PipelineId,
227 element_id: String,
228 reply: GenericSender<Result<(), ErrorStatus>>,
229) {
230 let result = get_known_element(documents, pipeline, element_id).map(|_| ());
231 if reply.send(result).is_err() {
232 error!("Webdriver get known element reply failed");
233 }
234}
235
236fn get_known_element(
238 documents: &DocumentCollection,
239 pipeline: PipelineId,
240 node_id: String,
241) -> Result<DomRoot<Element>, ErrorStatus> {
242 let doc = documents
243 .find_document(pipeline)
244 .ok_or(ErrorStatus::NoSuchWindow)?;
245 if !ScriptThread::has_node_id(pipeline, &node_id) {
248 return Err(ErrorStatus::NoSuchElement);
249 }
250 let node = find_node_by_unique_id_in_document(&doc, node_id);
253
254 if let Some(ref node) = node &&
257 !node.is::<Element>()
258 {
259 return Err(ErrorStatus::NoSuchElement);
260 }
261 let Some(node) = node else {
263 return Err(ErrorStatus::StaleElementReference);
264 };
265 let element = DomRoot::downcast::<Element>(node).unwrap();
267 if is_stale(&element) {
268 return Err(ErrorStatus::StaleElementReference);
269 }
270 Ok(element)
272}
273
274pub(crate) fn find_node_by_unique_id_in_document(
276 document: &Document,
277 node_id: String,
278) -> Option<DomRoot<Node>> {
279 let pipeline = document.window().pipeline_id();
280 document
281 .upcast::<Node>()
282 .traverse_preorder(ShadowIncluding::Yes)
283 .find(|node| node.unique_id(pipeline) == node_id)
284}
285
286fn matching_links(
288 links: &NodeList,
289 link_text: String,
290 partial: bool,
291) -> impl Iterator<Item = String> + '_ {
292 links
293 .iter()
294 .filter(move |node| {
295 let content = node
296 .downcast::<HTMLElement>()
297 .map(|element| element.InnerText())
298 .map_or("".to_owned(), String::from)
299 .trim()
300 .to_owned();
301 if partial {
302 content.contains(&link_text)
303 } else {
304 content == link_text
305 }
306 })
307 .map(|node| node.unique_id(node.owner_doc().window().pipeline_id()))
308}
309
310fn all_matching_links(
311 root_node: &Node,
312 link_text: String,
313 partial: bool,
314) -> Result<Vec<String>, ErrorStatus> {
315 root_node
319 .query_selector_all(DOMString::from("a"))
320 .map_err(|_| ErrorStatus::InvalidSelector)
321 .map(|nodes| matching_links(&nodes, link_text, partial).collect())
322}
323
324#[expect(unsafe_code)]
325fn object_has_to_json_property(
326 cx: SafeJSContext,
327 global_scope: &GlobalScope,
328 object: HandleObject,
329) -> bool {
330 let name = CString::new("toJSON").unwrap();
331 let mut found = false;
332 if unsafe { JS_HasOwnProperty(*cx, object, name.as_ptr(), &mut found) } && found {
333 rooted!(in(*cx) let mut value = UndefinedValue());
334 let result = unsafe { JS_GetProperty(*cx, object, name.as_ptr(), value.handle_mut()) };
335 if !result {
336 throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::deprecated_note());
337 false
338 } else {
339 result && unsafe { JS_TypeOfValue(*cx, value.handle()) } == JSType::JSTYPE_FUNCTION
340 }
341 } else if unsafe { JS_IsExceptionPending(*cx) } {
342 throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::deprecated_note());
343 false
344 } else {
345 false
346 }
347}
348
349#[expect(unsafe_code)]
350fn is_arguments_object(cx: SafeJSContext, value: HandleValue) -> bool {
352 rooted!(in(*cx) let class_name = unsafe { ToString(*cx, value) });
353 let Some(class_name) = NonNull::new(class_name.get()) else {
354 return false;
355 };
356 let class_name = unsafe { jsstr_to_string(*cx, class_name) };
357 class_name == "[object Arguments]"
358}
359
360#[derive(Clone, Eq, Hash, PartialEq)]
361struct HashableJSVal(u64);
362
363impl From<HandleValue<'_>> for HashableJSVal {
364 fn from(v: HandleValue<'_>) -> HashableJSVal {
365 HashableJSVal(v.get().asBits_)
366 }
367}
368
369pub(crate) fn jsval_to_webdriver(
371 cx: &mut CurrentRealm,
372 global_scope: &GlobalScope,
373 val: HandleValue,
374) -> WebDriverJSResult {
375 run_a_script::<DomTypeHolder, _>(global_scope, || {
376 let mut seen = HashSet::new();
377 let result = jsval_to_webdriver_inner(cx.into(), global_scope, val, &mut seen);
378
379 let in_realm_proof = cx.into();
380 let in_realm = InRealm::Already(&in_realm_proof);
381
382 if result.is_err() {
383 report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
384 }
385 result
386 })
387}
388
389#[expect(unsafe_code)]
390fn jsval_to_webdriver_inner(
392 cx: SafeJSContext,
393 global_scope: &GlobalScope,
394 val: HandleValue,
395 seen: &mut HashSet<HashableJSVal>,
396) -> WebDriverJSResult {
397 let _ac = enter_realm(global_scope);
398 if val.get().is_undefined() {
399 Ok(JSValue::Undefined)
400 } else if val.get().is_null() {
401 Ok(JSValue::Null)
402 } else if val.get().is_boolean() {
403 Ok(JSValue::Boolean(val.get().to_boolean()))
404 } else if val.get().is_number() {
405 Ok(JSValue::Number(val.to_number()))
406 } else if val.get().is_string() {
407 let string = NonNull::new(val.to_string()).expect("Should have a non-Null String");
408 let string = unsafe { jsstr_to_string(*cx, string) };
409 Ok(JSValue::String(string))
410 } else if val.get().is_object() {
411 rooted!(in(*cx) let object = match unsafe { FromJSValConvertible::from_jsval(*cx, val, ())}.unwrap() {
412 ConversionResult::Success(object) => object,
413 _ => unreachable!(),
414 });
415 let _ac = JSAutoRealm::new(*cx, *object);
416
417 if let Ok(element) = unsafe { root_from_object::<Element>(*object, *cx) } {
418 if is_stale(&element) {
420 Err(JavaScriptEvaluationError::SerializationError(
421 JavaScriptEvaluationResultSerializationError::StaleElementReference,
422 ))
423 } else {
424 Ok(JSValue::Element(
425 element
426 .upcast::<Node>()
427 .unique_id(element.owner_window().pipeline_id()),
428 ))
429 }
430 } else if let Ok(shadow_root) = unsafe { root_from_object::<ShadowRoot>(*object, *cx) } {
431 if is_detached(&shadow_root) {
433 Err(JavaScriptEvaluationError::SerializationError(
434 JavaScriptEvaluationResultSerializationError::DetachedShadowRoot,
435 ))
436 } else {
437 Ok(JSValue::ShadowRoot(
438 shadow_root
439 .upcast::<Node>()
440 .unique_id(shadow_root.owner_window().pipeline_id()),
441 ))
442 }
443 } else if let Ok(window) = unsafe { root_from_object::<Window>(*object, *cx) } {
444 let window_proxy = window.window_proxy();
445 if window_proxy.is_browsing_context_discarded() {
446 Err(JavaScriptEvaluationError::SerializationError(
447 JavaScriptEvaluationResultSerializationError::StaleElementReference,
448 ))
449 } else if window_proxy.browsing_context_id() == window_proxy.webview_id() {
450 Ok(JSValue::Window(window.webview_id().to_string()))
451 } else {
452 Ok(JSValue::Frame(
453 window_proxy.browsing_context_id().to_string(),
454 ))
455 }
456 } else if object_has_to_json_property(cx, global_scope, object.handle()) {
457 let name = CString::new("toJSON").unwrap();
458 rooted!(in(*cx) let mut value = UndefinedValue());
459 let call_result = unsafe {
460 JS_CallFunctionName(
461 *cx,
462 object.handle(),
463 name.as_ptr(),
464 &HandleValueArray::empty(),
465 value.handle_mut(),
466 )
467 };
468
469 if call_result {
470 Ok(jsval_to_webdriver_inner(
471 cx,
472 global_scope,
473 value.handle(),
474 seen,
475 )?)
476 } else {
477 throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::deprecated_note());
478 Err(JavaScriptEvaluationError::SerializationError(
479 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
480 ))
481 }
482 } else {
483 clone_an_object(cx, global_scope, val, seen, object.handle())
484 }
485 } else {
486 Err(JavaScriptEvaluationError::SerializationError(
487 JavaScriptEvaluationResultSerializationError::UnknownType,
488 ))
489 }
490}
491
492#[expect(unsafe_code)]
493fn clone_an_object(
495 cx: SafeJSContext,
496 global_scope: &GlobalScope,
497 val: HandleValue,
498 seen: &mut HashSet<HashableJSVal>,
499 object_handle: Handle<'_, *mut JSObject>,
500) -> WebDriverJSResult {
501 let hashable = val.into();
502 if seen.contains(&hashable) {
504 return Err(JavaScriptEvaluationError::SerializationError(
505 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
506 ));
507 }
508 seen.insert(hashable.clone());
510
511 let return_val = if unsafe {
512 is_array_like::<crate::DomTypeHolder>(*cx, val) || is_arguments_object(cx, val)
513 } {
514 let mut result: Vec<JSValue> = Vec::new();
515
516 let get_property_result =
517 get_property::<u32>(cx, object_handle, c"length", ConversionBehavior::Default);
518 let length = match get_property_result {
519 Ok(length) => match length {
520 Some(length) => length,
521 _ => {
522 return Err(JavaScriptEvaluationError::SerializationError(
523 JavaScriptEvaluationResultSerializationError::UnknownType,
524 ));
525 },
526 },
527 Err(error) => {
528 throw_dom_exception(cx, global_scope, error, CanGc::deprecated_note());
529 return Err(JavaScriptEvaluationError::SerializationError(
530 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
531 ));
532 },
533 };
534 for i in 0..length {
536 rooted!(in(*cx) let mut item = UndefinedValue());
537 let cname = CString::new(i.to_string()).unwrap();
538 let get_property_result =
539 get_property_jsval(cx, object_handle, &cname, item.handle_mut());
540 match get_property_result {
541 Ok(_) => {
542 let conversion_result =
543 jsval_to_webdriver_inner(cx, global_scope, item.handle(), seen);
544 match conversion_result {
545 Ok(converted_item) => result.push(converted_item),
546 err @ Err(_) => return err,
547 }
548 },
549 Err(error) => {
550 throw_dom_exception(cx, global_scope, error, CanGc::deprecated_note());
551 return Err(JavaScriptEvaluationError::SerializationError(
552 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
553 ));
554 },
555 }
556 }
557 Ok(JSValue::Array(result))
558 } else {
559 let mut result = HashMap::new();
560
561 let mut ids = unsafe { IdVector::new(*cx) };
562 let succeeded = unsafe {
563 GetPropertyKeys(
564 *cx,
565 object_handle.into(),
566 jsapi::JSITER_OWNONLY,
567 ids.handle_mut(),
568 )
569 };
570 if !succeeded {
571 return Err(JavaScriptEvaluationError::SerializationError(
572 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
573 ));
574 }
575 for id in ids.iter() {
576 rooted!(in(*cx) let id = *id);
577 rooted!(in(*cx) let mut desc = PropertyDescriptor::default());
578
579 let mut is_none = false;
580 let succeeded = unsafe {
581 JS_GetOwnPropertyDescriptorById(
582 *cx,
583 object_handle.into(),
584 id.handle().into(),
585 desc.handle_mut().into(),
586 &mut is_none,
587 )
588 };
589 if !succeeded {
590 return Err(JavaScriptEvaluationError::SerializationError(
591 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
592 ));
593 }
594
595 rooted!(in(*cx) let mut property = UndefinedValue());
596 let succeeded = unsafe {
597 JS_GetPropertyById(
598 *cx,
599 object_handle.into(),
600 id.handle().into(),
601 property.handle_mut().into(),
602 )
603 };
604 if !succeeded {
605 return Err(JavaScriptEvaluationError::SerializationError(
606 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
607 ));
608 }
609
610 if !property.is_undefined() {
611 let name = unsafe { jsid_to_string(*cx, id.handle()) };
612 let Some(name) = name else {
613 return Err(JavaScriptEvaluationError::SerializationError(
614 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
615 ));
616 };
617
618 let value = jsval_to_webdriver_inner(cx, global_scope, property.handle(), seen)?;
619 result.insert(name.into(), value);
620 }
621 }
622 Ok(JSValue::Object(result))
623 };
624 seen.remove(&hashable);
626 return_val
628}
629
630pub(crate) fn handle_execute_async_script(
631 window: Option<DomRoot<Window>>,
632 eval: String,
633 reply: GenericSender<WebDriverJSResult>,
634 cx: &mut JSContext,
635) {
636 match window {
637 Some(window) => {
638 let reply_sender = reply.clone();
639 window.set_webdriver_script_chan(Some(reply));
640
641 let global_scope = window.as_global_scope();
642
643 let mut realm = enter_auto_realm(cx, global_scope);
644 let mut realm = realm.current_realm();
645 if let Err(error) = global_scope.evaluate_js_on_global(
646 &mut realm,
647 eval.into(),
648 "",
649 None, None,
651 ) {
652 reply_sender.send(Err(error)).unwrap_or_else(|error| {
653 error!("ExecuteAsyncScript Failed to send reply: {error}");
654 });
655 }
656 },
657 None => {
658 reply
659 .send(Err(JavaScriptEvaluationError::DocumentNotFound))
660 .unwrap_or_else(|error| {
661 error!("ExecuteAsyncScript Failed to send reply: {error}");
662 });
663 },
664 }
665}
666
667pub(crate) fn handle_get_parent_frame_id(
669 documents: &DocumentCollection,
670 pipeline: PipelineId,
671 reply: GenericSender<Result<BrowsingContextId, ErrorStatus>>,
672) {
673 reply
676 .send(
677 documents
678 .find_window(pipeline)
679 .and_then(|window| {
680 window
681 .window_proxy()
682 .parent()
683 .map(|parent| parent.browsing_context_id())
684 })
685 .ok_or(ErrorStatus::NoSuchWindow),
686 )
687 .unwrap();
688}
689
690pub(crate) fn handle_get_browsing_context_id(
692 documents: &DocumentCollection,
693 pipeline: PipelineId,
694 webdriver_frame_id: WebDriverFrameId,
695 reply: GenericSender<Result<BrowsingContextId, ErrorStatus>>,
696) {
697 reply
698 .send(match webdriver_frame_id {
699 WebDriverFrameId::Short(id) => {
700 documents
703 .find_document(pipeline)
704 .ok_or(ErrorStatus::NoSuchWindow)
705 .and_then(|document| {
706 document
707 .iframes()
708 .iter()
709 .nth(id as usize)
710 .and_then(|iframe| iframe.browsing_context_id())
711 .ok_or(ErrorStatus::NoSuchFrame)
712 })
713 },
714 WebDriverFrameId::Element(element_id) => {
715 get_known_element(documents, pipeline, element_id).and_then(|element| {
716 element
717 .downcast::<HTMLIFrameElement>()
718 .and_then(|element| element.browsing_context_id())
719 .ok_or(ErrorStatus::NoSuchFrame)
720 })
721 },
722 })
723 .unwrap();
724}
725
726fn get_element_in_view_center_point(cx: &mut JSContext, element: &Element) -> Option<Point2D<i64>> {
728 let doc = element.owner_document();
729 element.GetClientRects(cx).first().map(|rectangle| {
732 let x = rectangle.X();
733 let y = rectangle.Y();
734 let width = rectangle.Width();
735 let height = rectangle.Height();
736 debug!(
737 "get_element_in_view_center_point: Element rectangle at \
738 (x: {x}, y: {y}, width: {width}, height: {height})",
739 );
740 let window = doc.window();
741 let left = (x.min(x + width)).max(0.0);
743 let right = f64::min(window.InnerWidth() as f64, x.max(x + width));
745 let top = (y.min(y + height)).max(0.0);
747 let bottom = f64::min(window.InnerHeight() as f64, y.max(y + height));
750 debug!(
751 "get_element_in_view_center_point: Computed rectangle is \
752 (left: {left}, right: {right}, top: {top}, bottom: {bottom})",
753 );
754 let center_x = ((left + right) / 2.0).floor() as i64;
756 let center_y = ((top + bottom) / 2.0).floor() as i64;
758
759 debug!(
760 "get_element_in_view_center_point: Element center point at ({center_x}, {center_y})",
761 );
762 Point2D::new(center_x, center_y)
764 })
765}
766
767pub(crate) fn handle_get_element_in_view_center_point(
768 cx: &mut JSContext,
769 documents: &DocumentCollection,
770 pipeline: PipelineId,
771 element_id: String,
772 reply: GenericOneshotSender<Result<Option<(i64, i64)>, ErrorStatus>>,
773) {
774 reply
775 .send(
776 get_known_element(documents, pipeline, element_id).map(|element| {
777 get_element_in_view_center_point(cx, &element).map(|point| (point.x, point.y))
778 }),
779 )
780 .unwrap();
781}
782
783fn retrieve_document_and_check_root_existence(
784 documents: &DocumentCollection,
785 pipeline: PipelineId,
786) -> Result<DomRoot<Document>, ErrorStatus> {
787 let document = documents
788 .find_document(pipeline)
789 .ok_or(ErrorStatus::NoSuchWindow)?;
790
791 if document.GetDocumentElement().is_none() {
796 Err(ErrorStatus::NoSuchElement)
797 } else {
798 Ok(document)
799 }
800}
801
802pub(crate) fn handle_find_elements_css_selector(
803 documents: &DocumentCollection,
804 pipeline: PipelineId,
805 selector: String,
806 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
807) {
808 match retrieve_document_and_check_root_existence(documents, pipeline) {
809 Ok(document) => reply
810 .send(
811 document
812 .QuerySelectorAll(DOMString::from(selector))
813 .map_err(|_| ErrorStatus::InvalidSelector)
814 .map(|nodes| {
815 nodes
816 .iter()
817 .map(|x| x.upcast::<Node>().unique_id(pipeline))
818 .collect()
819 }),
820 )
821 .unwrap(),
822 Err(error) => reply.send(Err(error)).unwrap(),
823 }
824}
825
826pub(crate) fn handle_find_elements_link_text(
827 documents: &DocumentCollection,
828 pipeline: PipelineId,
829 selector: String,
830 partial: bool,
831 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
832) {
833 match retrieve_document_and_check_root_existence(documents, pipeline) {
834 Ok(document) => reply
835 .send(all_matching_links(
836 document.upcast::<Node>(),
837 selector,
838 partial,
839 ))
840 .unwrap(),
841 Err(error) => reply.send(Err(error)).unwrap(),
842 }
843}
844
845pub(crate) fn handle_find_elements_tag_name(
846 cx: &mut JSContext,
847 documents: &DocumentCollection,
848 pipeline: PipelineId,
849 selector: String,
850 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
851) {
852 match retrieve_document_and_check_root_existence(documents, pipeline) {
853 Ok(document) => reply
854 .send(Ok(document
855 .GetElementsByTagName(cx, DOMString::from(selector))
856 .elements_iter()
857 .map(|x| x.upcast::<Node>().unique_id(pipeline))
858 .collect::<Vec<String>>()))
859 .unwrap(),
860 Err(error) => reply.send(Err(error)).unwrap(),
861 }
862}
863
864fn find_elements_xpath_strategy(
866 cx: &mut JSContext,
867 document: &Document,
868 start_node: &Node,
869 selector: String,
870 pipeline: PipelineId,
871) -> Result<Vec<String>, ErrorStatus> {
872 let evaluate_result = match document.Evaluate(
877 DOMString::from(selector),
878 start_node,
879 None,
880 XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
881 None,
882 CanGc::from_cx(cx),
883 ) {
884 Ok(res) => res,
885 Err(_) => return Err(ErrorStatus::InvalidSelector),
886 };
887 let length = match evaluate_result.GetSnapshotLength() {
893 Ok(len) => len,
894 Err(_) => return Err(ErrorStatus::InvalidSelector),
895 };
896
897 let mut result = Vec::new();
899
900 for index in 0..length {
902 let node = match evaluate_result.SnapshotItem(index) {
905 Ok(node) => node.expect(
906 "Node should always exist as ORDERED_NODE_SNAPSHOT_TYPE \
907 gives static result and we verified the length!",
908 ),
909 Err(_) => return Err(ErrorStatus::InvalidSelector),
910 };
911
912 if !node.is::<Element>() {
914 return Err(ErrorStatus::InvalidSelector);
915 }
916
917 result.push(node.unique_id(pipeline));
919 }
920 Ok(result)
922}
923
924pub(crate) fn handle_find_elements_xpath_selector(
925 cx: &mut JSContext,
926 documents: &DocumentCollection,
927 pipeline: PipelineId,
928 selector: String,
929 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
930) {
931 match retrieve_document_and_check_root_existence(documents, pipeline) {
932 Ok(document) => reply
933 .send(find_elements_xpath_strategy(
934 cx,
935 &document,
936 document.upcast::<Node>(),
937 selector,
938 pipeline,
939 ))
940 .unwrap(),
941 Err(error) => reply.send(Err(error)).unwrap(),
942 }
943}
944
945pub(crate) fn handle_find_element_elements_css_selector(
946 documents: &DocumentCollection,
947 pipeline: PipelineId,
948 element_id: String,
949 selector: String,
950 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
951) {
952 reply
953 .send(
954 get_known_element(documents, pipeline, element_id).and_then(|element| {
955 element
956 .upcast::<Node>()
957 .query_selector_all(DOMString::from(selector))
958 .map_err(|_| ErrorStatus::InvalidSelector)
959 .map(|nodes| {
960 nodes
961 .iter()
962 .map(|x| x.upcast::<Node>().unique_id(pipeline))
963 .collect()
964 })
965 }),
966 )
967 .unwrap();
968}
969
970pub(crate) fn handle_find_element_elements_link_text(
971 documents: &DocumentCollection,
972 pipeline: PipelineId,
973 element_id: String,
974 selector: String,
975 partial: bool,
976 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
977) {
978 reply
979 .send(
980 get_known_element(documents, pipeline, element_id).and_then(|element| {
981 all_matching_links(element.upcast::<Node>(), selector.clone(), partial)
982 }),
983 )
984 .unwrap();
985}
986
987pub(crate) fn handle_find_element_elements_tag_name(
988 cx: &mut JSContext,
989 documents: &DocumentCollection,
990 pipeline: PipelineId,
991 element_id: String,
992 selector: String,
993 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
994) {
995 reply
996 .send(
997 get_known_element(documents, pipeline, element_id).map(|element| {
998 element
999 .GetElementsByTagName(cx, DOMString::from(selector))
1000 .elements_iter()
1001 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1002 .collect::<Vec<String>>()
1003 }),
1004 )
1005 .unwrap();
1006}
1007
1008pub(crate) fn handle_find_element_elements_xpath_selector(
1009 cx: &mut JSContext,
1010 documents: &DocumentCollection,
1011 pipeline: PipelineId,
1012 element_id: String,
1013 selector: String,
1014 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1015) {
1016 reply
1017 .send(
1018 get_known_element(documents, pipeline, element_id).and_then(|element| {
1019 find_elements_xpath_strategy(
1020 cx,
1021 &documents
1022 .find_document(pipeline)
1023 .expect("Document existence guaranteed by `get_known_element`"),
1024 element.upcast::<Node>(),
1025 selector,
1026 pipeline,
1027 )
1028 }),
1029 )
1030 .unwrap();
1031}
1032
1033pub(crate) fn handle_find_shadow_elements_css_selector(
1035 documents: &DocumentCollection,
1036 pipeline: PipelineId,
1037 shadow_root_id: String,
1038 selector: String,
1039 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1040) {
1041 reply
1042 .send(
1043 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1044 shadow_root
1045 .upcast::<Node>()
1046 .query_selector_all(DOMString::from(selector))
1047 .map_err(|_| ErrorStatus::InvalidSelector)
1048 .map(|nodes| {
1049 nodes
1050 .iter()
1051 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1052 .collect()
1053 })
1054 }),
1055 )
1056 .unwrap();
1057}
1058
1059pub(crate) fn handle_find_shadow_elements_link_text(
1060 documents: &DocumentCollection,
1061 pipeline: PipelineId,
1062 shadow_root_id: String,
1063 selector: String,
1064 partial: bool,
1065 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1066) {
1067 reply
1068 .send(
1069 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1070 all_matching_links(shadow_root.upcast::<Node>(), selector.clone(), partial)
1071 }),
1072 )
1073 .unwrap();
1074}
1075
1076pub(crate) fn handle_find_shadow_elements_tag_name(
1077 documents: &DocumentCollection,
1078 pipeline: PipelineId,
1079 shadow_root_id: String,
1080 selector: String,
1081 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1082) {
1083 reply
1089 .send(
1090 get_known_shadow_root(documents, pipeline, shadow_root_id).map(|shadow_root| {
1091 shadow_root
1092 .upcast::<Node>()
1093 .query_selector_all(DOMString::from(selector))
1094 .map(|nodes| {
1095 nodes
1096 .iter()
1097 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1098 .collect()
1099 })
1100 .unwrap_or_default()
1101 }),
1102 )
1103 .unwrap();
1104}
1105
1106pub(crate) fn handle_find_shadow_elements_xpath_selector(
1107 cx: &mut JSContext,
1108 documents: &DocumentCollection,
1109 pipeline: PipelineId,
1110 shadow_root_id: String,
1111 selector: String,
1112 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1113) {
1114 reply
1115 .send(
1116 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1117 find_elements_xpath_strategy(
1118 cx,
1119 &documents
1120 .find_document(pipeline)
1121 .expect("Document existence guaranteed by `get_known_shadow_root`"),
1122 shadow_root.upcast::<Node>(),
1123 selector,
1124 pipeline,
1125 )
1126 }),
1127 )
1128 .unwrap();
1129}
1130
1131pub(crate) fn handle_get_element_shadow_root(
1133 documents: &DocumentCollection,
1134 pipeline: PipelineId,
1135 element_id: String,
1136 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1137) {
1138 reply
1139 .send(
1140 get_known_element(documents, pipeline, element_id).map(|element| {
1141 element
1142 .shadow_root()
1143 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1144 }),
1145 )
1146 .unwrap();
1147}
1148
1149impl Element {
1150 fn is_keyboard_interactable(&self) -> bool {
1152 self.is_focusable_area() || self.is::<HTMLBodyElement>() || self.is_document_element()
1153 }
1154}
1155
1156fn handle_send_keys_file(
1157 file_input: &HTMLInputElement,
1158 text: &str,
1159 reply_sender: GenericSender<Result<bool, ErrorStatus>>,
1160) {
1161 let files: Vec<DOMString> = text
1166 .split("\n")
1167 .filter_map(|string| {
1168 if string.is_empty() {
1169 None
1170 } else {
1171 Some(string.into())
1172 }
1173 })
1174 .collect();
1175
1176 if files.is_empty() {
1178 let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1179 return;
1180 }
1181
1182 if !file_input.Multiple() && files.len() > 1 {
1186 let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1187 return;
1188 }
1189
1190 file_input.select_files_for_webdriver(files, reply_sender);
1199}
1200
1201fn handle_send_keys_non_typeable(
1203 cx: &mut JSContext,
1204 input_element: &HTMLInputElement,
1205 text: &str,
1206) -> Result<bool, ErrorStatus> {
1207 if !input_element.is_mutable() {
1214 return Err(ErrorStatus::ElementNotInteractable);
1215 }
1216
1217 if let Err(error) = input_element.SetValue(cx, text.into()) {
1219 error!(
1220 "Failed to set value on non-typeable input element: {:?}",
1221 error
1222 );
1223 return Err(ErrorStatus::UnknownError);
1224 }
1225
1226 if input_element
1228 .Validity(cx)
1229 .invalid_flags()
1230 .contains(ValidationFlags::BAD_INPUT)
1231 {
1232 return Err(ErrorStatus::InvalidArgument);
1233 }
1234
1235 Ok(false)
1238}
1239
1240pub(crate) fn handle_will_send_keys(
1246 cx: &mut JSContext,
1247 documents: &DocumentCollection,
1248 pipeline: PipelineId,
1249 element_id: String,
1250 text: String,
1251 strict_file_interactability: bool,
1252 reply: GenericSender<Result<bool, ErrorStatus>>,
1253) {
1254 let element = match get_known_element(documents, pipeline, element_id) {
1256 Ok(element) => element,
1257 Err(error) => {
1258 let _ = reply.send(Err(error));
1259 return;
1260 },
1261 };
1262
1263 let input_element = element.downcast::<HTMLInputElement>();
1264 let mut element_has_focus = false;
1265
1266 let is_file_input =
1269 input_element.is_some_and(|e| matches!(*e.input_type(), InputType::File(_)));
1270
1271 if !is_file_input || strict_file_interactability {
1273 scroll_into_view(cx, &element, documents, &pipeline);
1275
1276 if !element.is_keyboard_interactable() {
1282 let _ = reply.send(Err(ErrorStatus::ElementNotInteractable));
1283 return;
1284 }
1285
1286 let Some(html_element) = element.downcast::<HTMLElement>() else {
1289 let _ = reply.send(Err(ErrorStatus::UnknownError));
1290 return;
1291 };
1292
1293 if !element.is_active_element() {
1294 html_element.Focus(
1295 cx,
1296 &FocusOptions {
1297 preventScroll: true,
1298 },
1299 );
1300 } else {
1301 element_has_focus = element.focus_state();
1302 }
1303 }
1304
1305 if let Some(input_element) = input_element {
1306 if is_file_input {
1308 handle_send_keys_file(input_element, &text, reply);
1309 return;
1310 }
1311
1312 if input_element.is_nontypeable() {
1314 let _ = reply.send(handle_send_keys_non_typeable(cx, input_element, &text));
1315 return;
1316 }
1317 }
1318
1319 if !element_has_focus {
1327 if let Some(input_element) = input_element {
1328 let length = input_element.Value().len() as u32;
1329 let _ = input_element.SetSelectionRange(length, length, None);
1330 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1331 let length = textarea_element.Value().len() as u32;
1332 let _ = textarea_element.SetSelectionRange(length, length, None);
1333 }
1334 }
1335
1336 let _ = reply.send(Ok(true));
1337}
1338
1339pub(crate) fn handle_get_active_element(
1340 documents: &DocumentCollection,
1341 pipeline: PipelineId,
1342 reply: GenericSender<Option<String>>,
1343) {
1344 reply
1345 .send(
1346 documents
1347 .find_document(pipeline)
1348 .and_then(|document| document.GetActiveElement())
1349 .map(|element| element.upcast::<Node>().unique_id(pipeline)),
1350 )
1351 .unwrap();
1352}
1353
1354pub(crate) fn handle_get_computed_role(
1355 documents: &DocumentCollection,
1356 pipeline: PipelineId,
1357 node_id: String,
1358 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1359) {
1360 reply
1361 .send(
1362 get_known_element(documents, pipeline, node_id)
1363 .map(|element| element.GetRole().map(String::from)),
1367 )
1368 .unwrap();
1369}
1370
1371pub(crate) fn handle_get_page_source(
1372 cx: &mut JSContext,
1373 documents: &DocumentCollection,
1374 pipeline: PipelineId,
1375 reply: GenericSender<Result<String, ErrorStatus>>,
1376) {
1377 reply
1378 .send(
1379 documents
1380 .find_document(pipeline)
1381 .ok_or(ErrorStatus::UnknownError)
1382 .and_then(|document| match document.GetDocumentElement() {
1383 Some(element) => match element.outer_html(cx) {
1384 Ok(source) => Ok(source.to_string()),
1385 Err(_) => {
1386 match XMLSerializer::new(document.window(), None, CanGc::from_cx(cx))
1387 .SerializeToString(element.upcast::<Node>())
1388 {
1389 Ok(source) => Ok(source.to_string()),
1390 Err(_) => Err(ErrorStatus::UnknownError),
1391 }
1392 },
1393 },
1394 None => Err(ErrorStatus::UnknownError),
1395 }),
1396 )
1397 .unwrap();
1398}
1399
1400pub(crate) fn handle_get_cookies(
1401 documents: &DocumentCollection,
1402 pipeline: PipelineId,
1403 reply: GenericSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1404) {
1405 reply
1406 .send(
1407 match documents.find_document(pipeline) {
1409 Some(document) => {
1410 let url = document.url();
1411 let (sender, receiver) = generic_channel::channel().unwrap();
1412 let _ = document
1413 .window()
1414 .as_global_scope()
1415 .resource_threads()
1416 .send(GetCookiesForUrl(url, sender, NonHTTP));
1417 Ok(receiver.recv().unwrap())
1418 },
1419 None => Ok(Vec::new()),
1420 },
1421 )
1422 .unwrap();
1423}
1424
1425pub(crate) fn handle_get_cookie(
1427 documents: &DocumentCollection,
1428 pipeline: PipelineId,
1429 name: String,
1430 reply: GenericSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1431) {
1432 reply
1433 .send(
1434 match documents.find_document(pipeline) {
1436 Some(document) => {
1437 let url = document.url();
1438 let (sender, receiver) = generic_channel::channel().unwrap();
1439 let _ = document
1440 .window()
1441 .as_global_scope()
1442 .resource_threads()
1443 .send(GetCookiesForUrl(url, sender, NonHTTP));
1444 let cookies = receiver.recv().unwrap();
1445 Ok(cookies
1446 .into_iter()
1447 .filter(|cookie| cookie.name() == &*name)
1448 .collect())
1449 },
1450 None => Ok(Vec::new()),
1451 },
1452 )
1453 .unwrap();
1454}
1455
1456pub(crate) fn handle_add_cookie(
1458 documents: &DocumentCollection,
1459 pipeline: PipelineId,
1460 cookie: Cookie<'static>,
1461 reply: GenericSender<Result<(), ErrorStatus>>,
1462) {
1463 let document = match documents.find_document(pipeline) {
1465 Some(document) => document,
1466 None => {
1467 return reply.send(Err(ErrorStatus::NoSuchWindow)).unwrap();
1468 },
1469 };
1470 let url = document.url();
1471 let method = if cookie.http_only().unwrap_or(false) {
1472 HTTP
1473 } else {
1474 NonHTTP
1475 };
1476
1477 let domain = cookie.domain().map(ToOwned::to_owned);
1478 reply
1480 .send(match (document.is_cookie_averse(), domain) {
1481 (true, _) => Err(ErrorStatus::InvalidCookieDomain),
1484 (false, Some(ref domain)) if url.host_str().is_some_and(|host| host == domain) => {
1485 let _ = document
1486 .window()
1487 .as_global_scope()
1488 .resource_threads()
1489 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1490 Ok(())
1491 },
1492 (false, Some(_)) => Err(ErrorStatus::InvalidCookieDomain),
1495 (false, None) => {
1496 let _ = document
1497 .window()
1498 .as_global_scope()
1499 .resource_threads()
1500 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1501 Ok(())
1502 },
1503 })
1504 .unwrap();
1505}
1506
1507pub(crate) fn handle_delete_cookies(
1509 documents: &DocumentCollection,
1510 pipeline: PipelineId,
1511 reply: GenericSender<Result<(), ErrorStatus>>,
1512) {
1513 let document = match documents.find_document(pipeline) {
1514 Some(document) => document,
1515 None => {
1516 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1517 },
1518 };
1519 let url = document.url();
1520 document
1521 .window()
1522 .as_global_scope()
1523 .resource_threads()
1524 .send(DeleteCookies(Some(url), None))
1525 .unwrap();
1526 reply.send(Ok(())).unwrap();
1527}
1528
1529pub(crate) fn handle_delete_cookie(
1531 documents: &DocumentCollection,
1532 pipeline: PipelineId,
1533 name: String,
1534 reply: GenericSender<Result<(), ErrorStatus>>,
1535) {
1536 let document = match documents.find_document(pipeline) {
1537 Some(document) => document,
1538 None => {
1539 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1540 },
1541 };
1542 let url = document.url();
1543 document
1544 .window()
1545 .as_global_scope()
1546 .resource_threads()
1547 .send(DeleteCookie(url, name))
1548 .unwrap();
1549 reply.send(Ok(())).unwrap();
1550}
1551
1552pub(crate) fn handle_get_title(
1553 documents: &DocumentCollection,
1554 pipeline: PipelineId,
1555 reply: GenericSender<String>,
1556) {
1557 reply
1558 .send(
1559 documents
1561 .find_document(pipeline)
1562 .map(|document| String::from(document.Title()))
1563 .unwrap_or_default(),
1564 )
1565 .unwrap();
1566}
1567
1568fn calculate_absolute_position(
1570 documents: &DocumentCollection,
1571 pipeline: &PipelineId,
1572 rect: &DOMRect,
1573) -> Result<(f64, f64), ErrorStatus> {
1574 let document = match documents.find_document(*pipeline) {
1579 Some(document) => document,
1580 None => return Err(ErrorStatus::UnknownError),
1581 };
1582 let win = match document.GetDefaultView() {
1583 Some(win) => win,
1584 None => return Err(ErrorStatus::UnknownError),
1585 };
1586
1587 let x = win.ScrollX() as f64 + rect.X();
1589 let y = win.ScrollY() as f64 + rect.Y();
1590
1591 Ok((x, y))
1592}
1593
1594pub(crate) fn handle_get_rect(
1596 cx: &mut JSContext,
1597 documents: &DocumentCollection,
1598 pipeline: PipelineId,
1599 element_id: String,
1600 reply: GenericSender<Result<Rect<f64>, ErrorStatus>>,
1601) {
1602 reply
1603 .send(
1604 get_known_element(documents, pipeline, element_id).and_then(|element| {
1605 let rect = element.GetBoundingClientRect(cx);
1609 let (x, y) = calculate_absolute_position(documents, &pipeline, &rect)?;
1610
1611 Ok(Rect::new(
1613 Point2D::new(x, y),
1614 Size2D::new(rect.Width(), rect.Height()),
1615 ))
1616 }),
1617 )
1618 .unwrap();
1619}
1620
1621pub(crate) fn handle_scroll_and_get_bounding_client_rect(
1622 cx: &mut JSContext,
1623 documents: &DocumentCollection,
1624 pipeline: PipelineId,
1625 element_id: String,
1626 reply: GenericSender<Result<Rect<f32>, ErrorStatus>>,
1627) {
1628 reply
1629 .send(
1630 get_known_element(documents, pipeline, element_id).map(|element| {
1631 scroll_into_view(cx, &element, documents, &pipeline);
1632
1633 let rect = element.GetBoundingClientRect(cx);
1634 Rect::new(
1635 Point2D::new(rect.X() as f32, rect.Y() as f32),
1636 Size2D::new(rect.Width() as f32, rect.Height() as f32),
1637 )
1638 }),
1639 )
1640 .unwrap();
1641}
1642
1643pub(crate) fn handle_get_text(
1645 documents: &DocumentCollection,
1646 pipeline: PipelineId,
1647 node_id: String,
1648 reply: GenericSender<Result<String, ErrorStatus>>,
1649) {
1650 reply
1651 .send(
1652 get_known_element(documents, pipeline, node_id).map(|element| {
1653 element
1654 .downcast::<HTMLElement>()
1655 .map(|htmlelement| htmlelement.InnerText().to_string())
1656 .unwrap_or_else(|| {
1657 element
1658 .upcast::<Node>()
1659 .GetTextContent()
1660 .map_or("".to_owned(), String::from)
1661 })
1662 }),
1663 )
1664 .unwrap();
1665}
1666
1667pub(crate) fn handle_get_name(
1668 documents: &DocumentCollection,
1669 pipeline: PipelineId,
1670 node_id: String,
1671 reply: GenericSender<Result<String, ErrorStatus>>,
1672) {
1673 reply
1674 .send(
1675 get_known_element(documents, pipeline, node_id)
1676 .map(|element| String::from(element.TagName())),
1677 )
1678 .unwrap();
1679}
1680
1681pub(crate) fn handle_get_attribute(
1682 cx: &mut JSContext,
1683 documents: &DocumentCollection,
1684 pipeline: PipelineId,
1685 node_id: String,
1686 name: String,
1687 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1688) {
1689 reply
1690 .send(
1691 get_known_element(documents, pipeline, node_id).map(|element| {
1692 if is_boolean_attribute(&name) {
1693 if element.HasAttribute(cx, DOMString::from(name)) {
1694 Some(String::from("true"))
1695 } else {
1696 None
1697 }
1698 } else {
1699 element
1700 .GetAttribute(cx, DOMString::from(name))
1701 .map(String::from)
1702 }
1703 }),
1704 )
1705 .unwrap();
1706}
1707
1708pub(crate) fn handle_get_property(
1709 documents: &DocumentCollection,
1710 pipeline: PipelineId,
1711 node_id: String,
1712 name: String,
1713 reply: GenericSender<Result<JSValue, ErrorStatus>>,
1714 cx: &mut JSContext,
1715) {
1716 reply
1717 .send(
1718 get_known_element(documents, pipeline, node_id).map(|element| {
1719 let document = documents.find_document(pipeline).unwrap();
1720
1721 let Ok(cname) = CString::new(name) else {
1722 return JSValue::Undefined;
1723 };
1724
1725 let mut realm = enter_auto_realm(cx, &*document);
1726 let cx = &mut realm.current_realm();
1727
1728 rooted!(&in(cx) let mut property = UndefinedValue());
1729 match get_property_jsval(
1730 cx.into(),
1731 element.reflector().get_jsobject(),
1732 &cname,
1733 property.handle_mut(),
1734 ) {
1735 Ok(_) => match jsval_to_webdriver(cx, &element.global(), property.handle()) {
1736 Ok(property) => property,
1737 Err(_) => JSValue::Undefined,
1738 },
1739 Err(error) => {
1740 throw_dom_exception(
1741 cx.into(),
1742 &element.global(),
1743 error,
1744 CanGc::from_cx(cx),
1745 );
1746 JSValue::Undefined
1747 },
1748 }
1749 }),
1750 )
1751 .unwrap();
1752}
1753
1754pub(crate) fn handle_get_css(
1755 documents: &DocumentCollection,
1756 pipeline: PipelineId,
1757 node_id: String,
1758 name: String,
1759 reply: GenericSender<Result<String, ErrorStatus>>,
1760) {
1761 reply
1762 .send(
1763 get_known_element(documents, pipeline, node_id).map(|element| {
1764 let window = element.owner_window();
1765 String::from(
1766 window
1767 .GetComputedStyle(&element, None)
1768 .GetPropertyValue(DOMString::from(name)),
1769 )
1770 }),
1771 )
1772 .unwrap();
1773}
1774
1775pub(crate) fn handle_get_url(
1776 documents: &DocumentCollection,
1777 pipeline: PipelineId,
1778 reply: GenericSender<String>,
1779) {
1780 reply
1781 .send(
1782 documents
1784 .find_document(pipeline)
1785 .map(|document| document.url().into_string())
1786 .unwrap_or_else(|| "about:blank".to_string()),
1787 )
1788 .unwrap();
1789}
1790
1791fn element_is_mutable_form_control(element: &Element) -> bool {
1793 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1794 input_element.is_mutable() &&
1795 matches!(
1796 *input_element.input_type(),
1797 InputType::Text(_) |
1798 InputType::Search(_) |
1799 InputType::Url(_) |
1800 InputType::Tel(_) |
1801 InputType::Email(_) |
1802 InputType::Password(_) |
1803 InputType::Date(_) |
1804 InputType::Month(_) |
1805 InputType::Week(_) |
1806 InputType::Time(_) |
1807 InputType::DatetimeLocal(_) |
1808 InputType::Number(_) |
1809 InputType::Range(_) |
1810 InputType::Color(_) |
1811 InputType::File(_)
1812 )
1813 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1814 textarea_element.is_mutable()
1815 } else {
1816 false
1817 }
1818}
1819
1820fn clear_a_resettable_element(cx: &mut JSContext, element: &Element) -> Result<(), ErrorStatus> {
1822 let html_element = element
1823 .downcast::<HTMLElement>()
1824 .ok_or(ErrorStatus::UnknownError)?;
1825
1826 if html_element.is_candidate_for_constraint_validation() {
1829 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1830 if input_element.Value().is_empty() {
1831 return Ok(());
1832 }
1833 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() &&
1834 textarea_element.Value().is_empty()
1835 {
1836 return Ok(());
1837 }
1838 }
1839
1840 html_element.Focus(
1842 cx,
1843 &FocusOptions {
1844 preventScroll: true,
1845 },
1846 );
1847
1848 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1850 input_element.clear(cx);
1851 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1852 textarea_element.clear();
1853 } else {
1854 unreachable!("We have confirm previously that element is mutable form control");
1855 }
1856
1857 let event_target = element.upcast::<EventTarget>();
1858 event_target.fire_bubbling_event(cx, atom!("input"));
1859 event_target.fire_bubbling_event(cx, atom!("change"));
1860
1861 html_element.Blur(cx);
1863
1864 Ok(())
1865}
1866
1867pub(crate) fn handle_element_clear(
1869 cx: &mut JSContext,
1870 documents: &DocumentCollection,
1871 pipeline: PipelineId,
1872 element_id: String,
1873 reply: GenericSender<Result<(), ErrorStatus>>,
1874) {
1875 reply
1876 .send(
1877 get_known_element(documents, pipeline, element_id).and_then(|element| {
1878 if !element_is_mutable_form_control(&element) {
1882 return Err(ErrorStatus::InvalidElementState);
1883 }
1884
1885 scroll_into_view(cx, &element, documents, &pipeline);
1887
1888 if !element.is_keyboard_interactable() {
1894 return Err(ErrorStatus::ElementNotInteractable);
1895 }
1896
1897 let paint_tree = get_element_pointer_interactable_paint_tree(
1898 cx,
1899 &element,
1900 &documents
1901 .find_document(pipeline)
1902 .expect("Document existence guaranteed by `get_known_element`"),
1903 );
1904 if !is_element_in_view(&element, &paint_tree) {
1905 return Err(ErrorStatus::ElementNotInteractable);
1906 }
1907
1908 clear_a_resettable_element(cx, &element)
1911 }),
1912 )
1913 .unwrap();
1914}
1915
1916fn get_option_parent(node: &Node) -> Option<DomRoot<Element>> {
1917 let mut candidate_select = None;
1925
1926 for ancestor in node.ancestors() {
1927 if ancestor.is::<HTMLDataListElement>() {
1928 return Some(DomRoot::downcast::<Element>(ancestor).unwrap());
1929 } else if candidate_select.is_none() && ancestor.is::<HTMLSelectElement>() {
1930 candidate_select = Some(ancestor);
1931 }
1932 }
1933
1934 candidate_select.map(|ancestor| DomRoot::downcast::<Element>(ancestor).unwrap())
1935}
1936
1937fn get_container(element: &Element) -> Option<DomRoot<Element>> {
1939 if element.is::<HTMLOptionElement>() {
1940 return get_option_parent(element.upcast::<Node>());
1941 }
1942 if element.is::<HTMLOptGroupElement>() {
1943 return get_option_parent(element.upcast::<Node>())
1944 .or_else(|| Some(DomRoot::from_ref(element)));
1945 }
1946 Some(DomRoot::from_ref(element))
1947}
1948
1949pub(crate) fn handle_element_click(
1951 cx: &mut JSContext,
1952 documents: &DocumentCollection,
1953 pipeline: PipelineId,
1954 element_id: String,
1955 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1956) {
1957 reply
1958 .send(
1959 get_known_element(documents, pipeline, element_id).and_then(|element| {
1961 if let Some(input_element) = element.downcast::<HTMLInputElement>() &&
1964 matches!(*input_element.input_type(), InputType::File(_))
1965 {
1966 return Err(ErrorStatus::InvalidArgument);
1967 }
1968
1969 let Some(container) = get_container(&element) else {
1970 return Err(ErrorStatus::UnknownError);
1971 };
1972
1973 scroll_into_view(cx, &container, documents, &pipeline);
1975
1976 let paint_tree = get_element_pointer_interactable_paint_tree(
1979 cx,
1980 &container,
1981 &documents
1982 .find_document(pipeline)
1983 .expect("Document existence guaranteed by `get_known_element`"),
1984 );
1985
1986 if !is_element_in_view(&container, &paint_tree) {
1987 return Err(ErrorStatus::ElementNotInteractable);
1988 }
1989
1990 if !container
1997 .upcast::<Node>()
1998 .is_shadow_including_inclusive_ancestor_of(paint_tree[0].upcast::<Node>())
1999 {
2000 return Err(ErrorStatus::ElementClickIntercepted);
2001 }
2002
2003 match element.downcast::<HTMLOptionElement>() {
2005 Some(option_element) => {
2006 let event_target = container.upcast::<EventTarget>();
2008 event_target.fire_event(cx, atom!("mouseover"));
2009 event_target.fire_event(cx, atom!("mousemove"));
2010 event_target.fire_event(cx, atom!("mousedown"));
2011
2012 match container.downcast::<HTMLElement>() {
2014 Some(html_element) => {
2015 html_element.Focus(
2016 cx,
2017 &FocusOptions {
2018 preventScroll: true,
2019 },
2020 );
2021 },
2022 None => return Err(ErrorStatus::UnknownError),
2023 }
2024
2025 if !is_disabled(&element) {
2027 event_target.fire_event(cx, atom!("input"));
2029
2030 let previous_selectedness = option_element.Selected();
2032
2033 match container.downcast::<HTMLSelectElement>() {
2035 Some(select_element) => {
2036 if select_element.Multiple() {
2037 option_element.SetSelected(cx, !option_element.Selected());
2038 }
2039 },
2040 None => option_element.SetSelected(cx, true),
2041 }
2042
2043 if !previous_selectedness {
2045 event_target.fire_event(cx, atom!("change"));
2046 }
2047 }
2048
2049 event_target.fire_event(cx, atom!("mouseup"));
2051 event_target.fire_event(cx, atom!("click"));
2052
2053 Ok(None)
2054 },
2055 None => Ok(Some(element.upcast::<Node>().unique_id(pipeline))),
2056 }
2057 }),
2058 )
2059 .unwrap();
2060}
2061
2062fn is_element_in_view(element: &Element, paint_tree: &[DomRoot<Element>]) -> bool {
2064 if !paint_tree.contains(&DomRoot::from_ref(element)) {
2067 return false;
2068 }
2069 use style::computed_values::pointer_events::T as PointerEvents;
2070 element
2074 .style()
2075 .is_none_or(|style| style.get_inherited_ui().pointer_events != PointerEvents::None)
2076}
2077
2078fn get_element_pointer_interactable_paint_tree(
2080 cx: &mut JSContext,
2081 element: &Element,
2082 document: &Document,
2083) -> Vec<DomRoot<Element>> {
2084 if !element.is_connected() {
2087 return Vec::new();
2088 }
2089
2090 get_element_in_view_center_point(cx, element).map_or(Vec::new(), |center_point| {
2096 document.ElementsFromPoint(
2097 Finite::wrap(center_point.x as f64),
2098 Finite::wrap(center_point.y as f64),
2099 )
2100 })
2101}
2102
2103pub(crate) fn handle_is_enabled(
2105 documents: &DocumentCollection,
2106 pipeline: PipelineId,
2107 element_id: String,
2108 reply: GenericSender<Result<bool, ErrorStatus>>,
2109) {
2110 reply
2111 .send(
2112 get_known_element(documents, pipeline, element_id).map(|element| {
2114 let document = documents.find_document(pipeline).unwrap();
2116
2117 if document.is_html_document() || document.is_xhtml_document() {
2123 !is_disabled(&element)
2124 } else {
2125 false
2126 }
2127 }),
2128 )
2129 .unwrap();
2130}
2131
2132pub(crate) fn handle_is_selected(
2133 documents: &DocumentCollection,
2134 pipeline: PipelineId,
2135 element_id: String,
2136 reply: GenericSender<Result<bool, ErrorStatus>>,
2137) {
2138 reply
2139 .send(
2140 get_known_element(documents, pipeline, element_id).and_then(|element| {
2141 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
2142 Ok(input_element.Checked())
2143 } else if let Some(option_element) = element.downcast::<HTMLOptionElement>() {
2144 Ok(option_element.Selected())
2145 } else if element.is::<HTMLElement>() {
2146 Ok(false) } else {
2148 Err(ErrorStatus::UnknownError)
2149 }
2150 }),
2151 )
2152 .unwrap();
2153}
2154
2155pub(crate) fn handle_add_load_status_sender(
2156 documents: &DocumentCollection,
2157 pipeline: PipelineId,
2158 reply: GenericSender<WebDriverLoadStatus>,
2159) {
2160 if let Some(document) = documents.find_document(pipeline) {
2161 let window = document.window();
2162 window.set_webdriver_load_status_sender(Some(reply));
2163 }
2164}
2165
2166pub(crate) fn handle_remove_load_status_sender(
2167 documents: &DocumentCollection,
2168 pipeline: PipelineId,
2169) {
2170 if let Some(document) = documents.find_document(pipeline) {
2171 let window = document.window();
2172 window.set_webdriver_load_status_sender(None);
2173 }
2174}
2175
2176fn scroll_into_view(
2178 cx: &mut JSContext,
2179 element: &Element,
2180 documents: &DocumentCollection,
2181 pipeline: &PipelineId,
2182) {
2183 let paint_tree = get_element_pointer_interactable_paint_tree(
2185 cx,
2186 element,
2187 &documents
2188 .find_document(*pipeline)
2189 .expect("Document existence guaranteed by `get_known_element`"),
2190 );
2191 if is_element_in_view(element, &paint_tree) {
2192 return;
2193 }
2194
2195 let options = BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions(ScrollIntoViewOptions {
2200 parent: ScrollOptions {
2201 behavior: ScrollBehavior::Instant,
2202 },
2203 block: ScrollLogicalPosition::End,
2204 inline: ScrollLogicalPosition::Nearest,
2205 container: Default::default(),
2206 });
2207 element.ScrollIntoView(options);
2209}
2210
2211pub(crate) fn set_protocol_handler_automation_mode(
2212 documents: &DocumentCollection,
2213 pipeline: PipelineId,
2214 mode: CustomHandlersAutomationMode,
2215) {
2216 if let Some(document) = documents.find_document(pipeline) {
2217 document.set_protocol_handler_automation_mode(mode);
2218 }
2219}