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, NoGC};
18use js::conversions::{FromJSValConvertible, jsstr_to_string};
19use js::jsapi::{HandleValueArray, JSITER_OWNONLY, JSType, PropertyDescriptor};
20use js::jsval::UndefinedValue;
21use js::realm::CurrentRealm;
22use js::rust::wrappers2::{
23 IsArgumentsObject, JS_CallFunctionName, JS_HasOwnProperty, JS_TypeOfValue,
24};
25use js::rust::{HandleObject, HandleValue, IdVector};
26use net_traits::CookieSource::{HTTP, NonHTTP};
27use net_traits::CoreResourceMsg::{DeleteCookie, DeleteCookies, GetCookiesForUrl, SetCookieForUrl};
28use script_bindings::codegen::GenericBindings::PermissionStatusBinding::{
29 PermissionName, PermissionState,
30};
31use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
32use script_bindings::conversions::is_array_like;
33use script_bindings::js::{
34 GetPropertyKeys, JS_GetOwnPropertyDescriptorById, JS_GetProperty, JS_GetPropertyById,
35 JS_IsExceptionPending,
36};
37use script_bindings::num::Finite;
38use script_bindings::reflector::DomObject;
39use script_bindings::settings_stack::run_a_script;
40use servo_base::Epoch;
41use servo_base::generic_channel::{self, GenericOneshotSender, GenericSend, GenericSender};
42use servo_base::id::{BrowsingContextId, PipelineId};
43use servo_config::pref;
44use webdriver::command::SetPermissionState;
45use webdriver::error::ErrorStatus;
46
47use crate::DomTypeHolder;
48use crate::dom::Promise;
49use crate::dom::attr::is_boolean_attribute;
50use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
51use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
52use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
53use crate::dom::bindings::codegen::Bindings::ElementBinding::{
54 ElementMethods, ScrollIntoViewOptions, ScrollLogicalPosition,
55};
56use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
57use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
58use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
59use crate::dom::bindings::codegen::Bindings::HTMLOrSVGOrMathMLElementBinding::FocusOptions;
60use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
61use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
62use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
63use crate::dom::bindings::codegen::Bindings::WindowBinding::{
64 ScrollBehavior, ScrollOptions, WindowMethods,
65};
66use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
67use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
68 XPathResultConstants, XPathResultMethods,
69};
70use crate::dom::bindings::codegen::UnionTypes::BooleanOrScrollIntoViewOptions;
71use crate::dom::bindings::conversions::{
72 ConversionBehavior, ConversionResult, get_property, get_property_jsval, jsid_to_string,
73 root_from_object,
74};
75use crate::dom::bindings::error::{
76 Error, ErrorInfo, javascript_error_info_from_error_info, report_pending_exception,
77 throw_dom_exception,
78};
79use crate::dom::bindings::inheritance::Castable;
80use crate::dom::bindings::reflector::DomGlobal;
81use crate::dom::bindings::root::DomRoot;
82use crate::dom::bindings::str::DOMString;
83use crate::dom::document::Document;
84use crate::dom::domrect::DOMRect;
85use crate::dom::element::Element;
86use crate::dom::eventtarget::EventTarget;
87use crate::dom::globalscope::GlobalScope;
88use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
89use crate::dom::html::form_controls::input_type::InputType;
90use crate::dom::html::htmlbodyelement::HTMLBodyElement;
91use crate::dom::html::htmldatalistelement::HTMLDataListElement;
92use crate::dom::html::htmlelement::HTMLElement;
93use crate::dom::html::htmlformelement::FormControl;
94use crate::dom::html::htmliframeelement::HTMLIFrameElement;
95use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
96use crate::dom::html::htmloptionelement::HTMLOptionElement;
97use crate::dom::html::htmlselectelement::HTMLSelectElement;
98use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
99use crate::dom::iterators::ShadowIncluding;
100use crate::dom::node::{Node, NodeTraits};
101use crate::dom::nodelist::NodeList;
102use crate::dom::promisenativehandler::Callback;
103use crate::dom::types::{PromiseNativeHandler, ShadowRoot};
104use crate::dom::validitystate::ValidationFlags;
105use crate::dom::window::Window;
106use crate::dom::xmlserializer::XMLSerializer;
107use crate::event_loop::document_collection::DocumentCollection;
108use crate::event_loop::script_thread::ScriptThread;
109use crate::realms::enter_auto_realm;
110
111fn is_stale(element: &Element) -> bool {
113 !element.owner_document().is_active() || !element.is_connected()
116}
117
118fn is_detached(shadow_root: &ShadowRoot) -> bool {
120 !shadow_root.owner_document().is_active() || is_stale(&shadow_root.Host())
123}
124
125fn is_disabled(element: &Element) -> bool {
127 if element.is::<HTMLOptionElement>() || element.is::<HTMLOptGroupElement>() {
129 let disabled = element
131 .upcast::<Node>()
132 .inclusive_ancestors(ShadowIncluding::No)
133 .any(|node| {
134 if node.is::<HTMLOptGroupElement>() || node.is::<HTMLSelectElement>() {
135 node.downcast::<Element>().unwrap().is_actually_disabled()
138 } else {
139 false
140 }
141 });
142
143 if disabled {
148 return true;
149 }
150 }
151 element.is_actually_disabled()
153}
154
155pub(crate) fn handle_get_known_window(
156 documents: &DocumentCollection,
157 pipeline: PipelineId,
158 webview_id: String,
159 reply: GenericSender<Result<(), ErrorStatus>>,
160) {
161 if reply
162 .send(
163 documents
164 .find_window(pipeline)
165 .map_or(Err(ErrorStatus::NoSuchWindow), |window| {
166 let window_proxy = window.window_proxy();
167 if window_proxy.browsing_context_id() != window_proxy.webview_id() ||
169 window_proxy.webview_id().to_string() != webview_id
170 {
171 Err(ErrorStatus::NoSuchWindow)
172 } else {
173 Ok(())
174 }
175 }),
176 )
177 .is_err()
178 {
179 error!("Webdriver get known window reply failed");
180 }
181}
182
183pub(crate) fn handle_get_known_shadow_root(
184 documents: &DocumentCollection,
185 pipeline: PipelineId,
186 shadow_root_id: String,
187 reply: GenericSender<Result<(), ErrorStatus>>,
188) {
189 let result = get_known_shadow_root(documents, pipeline, shadow_root_id).map(|_| ());
190 if reply.send(result).is_err() {
191 error!("Webdriver get known shadow root reply failed");
192 }
193}
194
195fn get_known_shadow_root(
197 documents: &DocumentCollection,
198 pipeline: PipelineId,
199 node_id: String,
200) -> Result<DomRoot<ShadowRoot>, ErrorStatus> {
201 let doc = documents
202 .find_document(pipeline)
203 .ok_or(ErrorStatus::NoSuchWindow)?;
204 if !ScriptThread::has_node_id(pipeline, &node_id) {
207 return Err(ErrorStatus::NoSuchShadowRoot);
208 }
209
210 let node = find_node_by_unique_id_in_document(&doc, node_id);
213
214 if let Some(ref node) = node &&
217 !node.is::<ShadowRoot>()
218 {
219 return Err(ErrorStatus::NoSuchShadowRoot);
220 }
221
222 let Some(node) = node else {
224 return Err(ErrorStatus::DetachedShadowRoot);
225 };
226
227 let shadow_root = DomRoot::downcast::<ShadowRoot>(node).unwrap();
231 if is_detached(&shadow_root) {
232 return Err(ErrorStatus::DetachedShadowRoot);
233 }
234 Ok(shadow_root)
236}
237
238pub(crate) fn handle_get_known_element(
239 documents: &DocumentCollection,
240 pipeline: PipelineId,
241 element_id: String,
242 reply: GenericSender<Result<(), ErrorStatus>>,
243) {
244 let result = get_known_element(documents, pipeline, element_id).map(|_| ());
245 if reply.send(result).is_err() {
246 error!("Webdriver get known element reply failed");
247 }
248}
249
250fn get_known_element(
252 documents: &DocumentCollection,
253 pipeline: PipelineId,
254 node_id: String,
255) -> Result<DomRoot<Element>, ErrorStatus> {
256 let doc = documents
257 .find_document(pipeline)
258 .ok_or(ErrorStatus::NoSuchWindow)?;
259 if !ScriptThread::has_node_id(pipeline, &node_id) {
262 return Err(ErrorStatus::NoSuchElement);
263 }
264 let node = find_node_by_unique_id_in_document(&doc, node_id);
267
268 if let Some(ref node) = node &&
271 !node.is::<Element>()
272 {
273 return Err(ErrorStatus::NoSuchElement);
274 }
275 let Some(node) = node else {
277 return Err(ErrorStatus::StaleElementReference);
278 };
279 let element = DomRoot::downcast::<Element>(node).unwrap();
281 if is_stale(&element) {
282 return Err(ErrorStatus::StaleElementReference);
283 }
284 Ok(element)
286}
287
288pub(crate) fn find_node_by_unique_id_in_document(
290 document: &Document,
291 node_id: String,
292) -> Option<DomRoot<Node>> {
293 let pipeline = document.window().pipeline_id();
294 document
295 .upcast::<Node>()
296 .traverse_preorder(ShadowIncluding::Yes)
297 .find(|node| node.unique_id(pipeline) == node_id)
298}
299
300fn matching_links<'a>(
302 cx: &'a NoGC,
303 links: &'a NodeList,
304 link_text: String,
305 partial: bool,
306) -> impl Iterator<Item = String> + 'a {
307 links
308 .iter(cx)
309 .filter(move |node| {
310 let content = node
311 .downcast::<HTMLElement>()
312 .map(|element| element.InnerText())
313 .map_or(String::new(), String::from)
314 .trim()
315 .to_owned();
316 if partial {
317 content.contains(&link_text)
318 } else {
319 content == link_text
320 }
321 })
322 .map(|node| node.unique_id(node.owner_doc().window().pipeline_id()))
323}
324
325fn all_matching_links(
326 cx: &mut JSContext,
327 root_node: &Node,
328 link_text: String,
329 partial: bool,
330) -> Result<Vec<String>, ErrorStatus> {
331 root_node
335 .query_selector_all(cx, DOMString::from_static("a"))
336 .map_err(|_| ErrorStatus::InvalidSelector)
337 .map(|nodes| matching_links(cx, &nodes, link_text, partial).collect())
338}
339
340#[expect(unsafe_code)]
341fn object_has_to_json_property(
342 cx: &mut JSContext,
343 global_scope: &GlobalScope,
344 object: HandleObject,
345) -> bool {
346 let name = CString::new("toJSON").unwrap();
347 let mut found = false;
348 if unsafe { JS_HasOwnProperty(cx, object, name.as_ptr(), &mut found) } && found {
349 rooted!(&in(cx) let mut value = UndefinedValue());
350 let result = JS_GetProperty(cx, object, &name, value.handle_mut());
351 if !result {
352 throw_dom_exception(cx, global_scope, Error::JSFailed);
353 false
354 } else {
355 result && unsafe { JS_TypeOfValue(cx, value.handle()) } == JSType::JSTYPE_FUNCTION
356 }
357 } else if JS_IsExceptionPending(cx) {
358 throw_dom_exception(cx, global_scope, Error::JSFailed);
359 false
360 } else {
361 false
362 }
363}
364
365#[expect(unsafe_code)]
366fn is_arguments_object(object: HandleObject) -> bool {
368 unsafe { IsArgumentsObject(object) }
369}
370
371#[derive(Clone, Eq, Hash, PartialEq)]
372struct HashableJSVal(u64);
373
374impl From<HandleValue<'_>> for HashableJSVal {
375 fn from(v: HandleValue<'_>) -> HashableJSVal {
376 HashableJSVal(v.get().asBits_)
377 }
378}
379
380pub(crate) fn jsval_to_webdriver(
382 cx: &mut CurrentRealm,
383 global_scope: &GlobalScope,
384 val: HandleValue,
385) -> WebDriverJSResult {
386 run_a_script::<DomTypeHolder, _, _>(cx, global_scope, |cx| {
387 let mut seen = HashSet::new();
388 let result = jsval_to_webdriver_inner(cx, global_scope, val, &mut seen);
389
390 if result.is_err() {
391 report_pending_exception(cx);
392 }
393 result
394 })
395}
396
397#[expect(unsafe_code)]
398fn jsval_to_webdriver_inner(
400 cx: &mut CurrentRealm,
401 global_scope: &GlobalScope,
402 val: HandleValue,
403 seen: &mut HashSet<HashableJSVal>,
404) -> WebDriverJSResult {
405 if val.get().is_undefined() {
406 Ok(JSValue::Undefined)
407 } else if val.get().is_null() {
408 Ok(JSValue::Null)
409 } else if val.get().is_boolean() {
410 Ok(JSValue::Boolean(val.get().to_boolean()))
411 } else if val.get().is_number() {
412 Ok(JSValue::Number(val.to_number()))
413 } else if val.get().is_string() {
414 let string = NonNull::new(val.to_string()).expect("Should have a non-Null String");
415 let string = unsafe { jsstr_to_string(cx, string) };
416 Ok(JSValue::String(string))
417 } else if val.get().is_object() {
418 rooted!(&in(cx) let object = match FromJSValConvertible::from_jsval(cx, val, ()).unwrap() {
419 ConversionResult::Success(object) => object,
420 _ => unreachable!(),
421 });
422
423 if let Ok(element) = unsafe { root_from_object::<Element>(cx, *object) } {
424 if is_stale(&element) {
426 Err(JavaScriptEvaluationError::SerializationError(
427 JavaScriptEvaluationResultSerializationError::StaleElementReference,
428 ))
429 } else {
430 Ok(JSValue::Element(
431 element
432 .upcast::<Node>()
433 .unique_id(element.owner_window().pipeline_id()),
434 ))
435 }
436 } else if let Ok(shadow_root) = unsafe { root_from_object::<ShadowRoot>(cx, *object) } {
437 if is_detached(&shadow_root) {
439 Err(JavaScriptEvaluationError::SerializationError(
440 JavaScriptEvaluationResultSerializationError::DetachedShadowRoot,
441 ))
442 } else {
443 Ok(JSValue::ShadowRoot(
444 shadow_root
445 .upcast::<Node>()
446 .unique_id(shadow_root.owner_window().pipeline_id()),
447 ))
448 }
449 } else if let Ok(window) = unsafe { root_from_object::<Window>(cx, *object) } {
450 let window_proxy = window.window_proxy();
451 if window_proxy.is_browsing_context_discarded() {
452 Err(JavaScriptEvaluationError::SerializationError(
453 JavaScriptEvaluationResultSerializationError::StaleElementReference,
454 ))
455 } else if window_proxy.browsing_context_id() == window_proxy.webview_id() {
456 Ok(JSValue::Window(window.webview_id().to_string()))
457 } else {
458 Ok(JSValue::Frame(
459 window_proxy.browsing_context_id().to_string(),
460 ))
461 }
462 } else if object_has_to_json_property(cx, global_scope, object.handle()) {
463 let name = CString::new("toJSON").unwrap();
464 rooted!(&in(cx) let mut value = UndefinedValue());
465 let call_result = unsafe {
466 JS_CallFunctionName(
467 cx,
468 object.handle(),
469 name.as_ptr(),
470 &HandleValueArray::empty(),
471 value.handle_mut(),
472 )
473 };
474
475 if call_result {
476 Ok(jsval_to_webdriver_inner(
477 cx,
478 global_scope,
479 value.handle(),
480 seen,
481 )?)
482 } else {
483 throw_dom_exception(cx, global_scope, Error::JSFailed);
484 Err(JavaScriptEvaluationError::SerializationError(
485 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
486 ))
487 }
488 } else {
489 clone_an_object(cx, global_scope, val, seen, object.handle())
490 }
491 } else {
492 Err(JavaScriptEvaluationError::SerializationError(
493 JavaScriptEvaluationResultSerializationError::UnknownType,
494 ))
495 }
496}
497
498fn clone_an_object(
500 cx: &mut CurrentRealm,
501 global_scope: &GlobalScope,
502 val: HandleValue,
503 seen: &mut HashSet<HashableJSVal>,
504 object_handle: HandleObject,
505) -> WebDriverJSResult {
506 let hashable = val.into();
507 if seen.contains(&hashable) {
509 return Err(JavaScriptEvaluationError::SerializationError(
510 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
511 ));
512 }
513 seen.insert(hashable.clone());
515
516 let return_val = if is_array_like::<crate::DomTypeHolder>(cx, val) ||
517 is_arguments_object(object_handle)
518 {
519 let mut result: Vec<JSValue> = Vec::new();
520
521 let get_property_result =
522 get_property::<u32>(cx, object_handle, c"length", ConversionBehavior::Default);
523 let length = match get_property_result {
524 Ok(length) => match length {
525 Some(length) => length,
526 _ => {
527 return Err(JavaScriptEvaluationError::SerializationError(
528 JavaScriptEvaluationResultSerializationError::UnknownType,
529 ));
530 },
531 },
532 Err(error) => {
533 throw_dom_exception(cx, global_scope, error);
534 return Err(JavaScriptEvaluationError::SerializationError(
535 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
536 ));
537 },
538 };
539 for i in 0..length {
541 rooted!(&in(cx) let mut item = UndefinedValue());
542 let cname = CString::new(i.to_string()).unwrap();
543 let get_property_result =
544 get_property_jsval(cx, object_handle, &cname, item.handle_mut());
545 match get_property_result {
546 Ok(_) => {
547 let converted_item =
548 jsval_to_webdriver_inner(cx, global_scope, item.handle(), seen)?;
549
550 result.push(converted_item);
551 },
552 Err(error) => {
553 throw_dom_exception(cx, global_scope, error);
554 return Err(JavaScriptEvaluationError::SerializationError(
555 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
556 ));
557 },
558 }
559 }
560 Ok(JSValue::Array(result))
561 } else {
562 let mut result = HashMap::new();
563
564 let mut ids = IdVector::new(cx);
565 let succeeded = GetPropertyKeys(cx, object_handle, JSITER_OWNONLY, ids.handle_mut());
566 if !succeeded {
567 return Err(JavaScriptEvaluationError::SerializationError(
568 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
569 ));
570 }
571 for id in ids.iter() {
572 rooted!(&in(cx) let id = *id);
573 rooted!(&in(cx) let mut desc = PropertyDescriptor::default());
574
575 let mut is_none = false;
576 let succeeded = JS_GetOwnPropertyDescriptorById(
577 cx,
578 object_handle,
579 id.handle(),
580 desc.handle_mut(),
581 &mut is_none,
582 );
583 if !succeeded {
584 return Err(JavaScriptEvaluationError::SerializationError(
585 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
586 ));
587 }
588
589 rooted!(&in(cx) let mut property = UndefinedValue());
590 let succeeded =
591 JS_GetPropertyById(cx, object_handle, id.handle(), property.handle_mut());
592 if !succeeded {
593 return Err(JavaScriptEvaluationError::SerializationError(
594 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
595 ));
596 }
597
598 if !property.is_undefined() {
599 let name = jsid_to_string(cx, id.handle());
600 let Some(name) = name else {
601 return Err(JavaScriptEvaluationError::SerializationError(
602 JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
603 ));
604 };
605
606 let value = jsval_to_webdriver_inner(cx, global_scope, property.handle(), seen)?;
607 result.insert(name.into(), value);
608 }
609 }
610 Ok(JSValue::Object(result))
611 };
612 seen.remove(&hashable);
614 return_val
616}
617
618#[derive(MallocSizeOf, JSTraceable)]
619struct WebDriverExecuteScriptFulfillmentHandler {
620 #[no_trace]
621 reply_sender: GenericSender<WebDriverJSResult>,
622}
623
624impl Callback for WebDriverExecuteScriptFulfillmentHandler {
625 fn callback(&self, cx: &mut CurrentRealm, return_value: HandleValue) {
626 let global_scope = GlobalScope::from_current_realm(cx);
627 let result = jsval_to_webdriver(cx, &global_scope, return_value);
628 let _ = self.reply_sender.send(result);
629 }
630}
631
632#[derive(MallocSizeOf, JSTraceable)]
633struct WebDriverExecuteScriptRejectionHandler {
634 #[no_trace]
635 reply_sender: GenericSender<WebDriverJSResult>,
636}
637
638impl Callback for WebDriverExecuteScriptRejectionHandler {
639 fn callback(&self, cx: &mut CurrentRealm, return_value: HandleValue) {
640 let error_info = ErrorInfo::from_value(cx, return_value);
641 let _ = self
642 .reply_sender
643 .send(Err(JavaScriptEvaluationError::EvaluationFailure(Some(
644 javascript_error_info_from_error_info(cx, &error_info, return_value),
645 ))));
646 }
647}
648
649pub(crate) fn handle_execute_script(
650 cx: &mut JSContext,
651 window: Option<DomRoot<Window>>,
652 eval: String,
653 reply_sender: GenericSender<WebDriverJSResult>,
654) {
655 let Some(window) = window else {
656 reply_sender
657 .send(Err(JavaScriptEvaluationError::DocumentNotFound))
658 .unwrap_or_else(|error| {
659 error!("ExecuteAsyncScript Failed to send reply: {error}");
660 });
661 return;
662 };
663
664 let global_scope = window.as_global_scope();
665 let mut realm = enter_auto_realm(cx, global_scope);
666 let mut realm = realm.current_realm();
667 let cx = &mut realm;
668
669 rooted!(&in(cx) let mut return_value = UndefinedValue());
670 if let Err(error) = global_scope.evaluate_js_on_global(
671 cx,
672 eval.into(),
673 "",
674 None, Some(return_value.handle_mut()),
676 ) {
677 reply_sender.send(Err(error)).unwrap_or_else(|error| {
678 error!("ExecuteAsyncScript Failed to send reply: {error}");
679 });
680 return;
681 }
682
683 let promise = Promise::new_resolved(cx, global_scope, return_value.handle());
684 let fulfillment_handler = WebDriverExecuteScriptFulfillmentHandler {
685 reply_sender: reply_sender.clone(),
686 };
687 let rejection_handler = WebDriverExecuteScriptRejectionHandler { reply_sender };
688
689 let handler = PromiseNativeHandler::new(
690 cx,
691 global_scope,
692 Some(Box::new(fulfillment_handler)),
693 Some(Box::new(rejection_handler)),
694 );
695 promise.append_native_handler(cx, &handler);
696}
697
698pub(crate) fn handle_get_parent_frame_id(
700 documents: &DocumentCollection,
701 pipeline: PipelineId,
702 reply: GenericSender<Result<BrowsingContextId, ErrorStatus>>,
703) {
704 reply
707 .send(
708 documents
709 .find_window(pipeline)
710 .and_then(|window| {
711 window
712 .window_proxy()
713 .parent()
714 .map(|parent| parent.browsing_context_id())
715 })
716 .ok_or(ErrorStatus::NoSuchWindow),
717 )
718 .unwrap();
719}
720
721pub(crate) fn handle_get_browsing_context_id(
723 documents: &DocumentCollection,
724 pipeline: PipelineId,
725 webdriver_frame_id: WebDriverFrameId,
726 reply: GenericSender<Result<BrowsingContextId, ErrorStatus>>,
727) {
728 reply
729 .send(match webdriver_frame_id {
730 WebDriverFrameId::Short(id) => {
731 documents
734 .find_document(pipeline)
735 .ok_or(ErrorStatus::NoSuchWindow)
736 .and_then(|document| {
737 document
738 .iframes()
739 .iter()
740 .nth(id as usize)
741 .and_then(|iframe| iframe.browsing_context_id())
742 .ok_or(ErrorStatus::NoSuchFrame)
743 })
744 },
745 WebDriverFrameId::Element(element_id) => {
746 get_known_element(documents, pipeline, element_id).and_then(|element| {
747 element
748 .downcast::<HTMLIFrameElement>()
749 .and_then(|element| element.browsing_context_id())
750 .ok_or(ErrorStatus::NoSuchFrame)
751 })
752 },
753 })
754 .unwrap();
755}
756
757fn get_element_in_view_center_point(cx: &mut JSContext, element: &Element) -> Option<Point2D<i64>> {
759 let doc = element.owner_document();
760 element.GetClientRects(cx).first().map(|rectangle| {
763 let x = rectangle.X();
764 let y = rectangle.Y();
765 let width = rectangle.Width();
766 let height = rectangle.Height();
767 debug!(
768 "get_element_in_view_center_point: Element rectangle at \
769 (x: {x}, y: {y}, width: {width}, height: {height})",
770 );
771 let window = doc.window();
772 let left = (x.min(x + width)).max(0.0);
774 let right = f64::min(window.InnerWidth() as f64, x.max(x + width));
776 let top = (y.min(y + height)).max(0.0);
778 let bottom = f64::min(window.InnerHeight() as f64, y.max(y + height));
781 debug!(
782 "get_element_in_view_center_point: Computed rectangle is \
783 (left: {left}, right: {right}, top: {top}, bottom: {bottom})",
784 );
785 let center_x = ((left + right) / 2.0).floor() as i64;
787 let center_y = ((top + bottom) / 2.0).floor() as i64;
789
790 debug!(
791 "get_element_in_view_center_point: Element center point at ({center_x}, {center_y})",
792 );
793 Point2D::new(center_x, center_y)
795 })
796}
797
798pub(crate) fn handle_get_element_in_view_center_point(
799 cx: &mut JSContext,
800 documents: &DocumentCollection,
801 pipeline: PipelineId,
802 element_id: String,
803 reply: GenericOneshotSender<Result<Option<(i64, i64)>, ErrorStatus>>,
804) {
805 reply
806 .send(
807 get_known_element(documents, pipeline, element_id).map(|element| {
808 get_element_in_view_center_point(cx, &element).map(|point| (point.x, point.y))
809 }),
810 )
811 .unwrap();
812}
813
814fn retrieve_document_and_check_root_existence(
815 documents: &DocumentCollection,
816 pipeline: PipelineId,
817) -> Result<DomRoot<Document>, ErrorStatus> {
818 let document = documents
819 .find_document(pipeline)
820 .ok_or(ErrorStatus::NoSuchWindow)?;
821
822 if document.GetDocumentElement().is_none() {
827 Err(ErrorStatus::NoSuchElement)
828 } else {
829 Ok(document)
830 }
831}
832
833pub(crate) fn handle_find_elements_css_selector(
834 cx: &mut JSContext,
835 documents: &DocumentCollection,
836 pipeline: PipelineId,
837 selector: String,
838 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
839) {
840 match retrieve_document_and_check_root_existence(documents, pipeline) {
841 Ok(document) => reply
842 .send(
843 document
844 .QuerySelectorAll(cx, DOMString::from(selector))
845 .map_err(|_| ErrorStatus::InvalidSelector)
846 .map(|nodes| {
847 nodes
848 .iter(cx)
849 .map(|x| x.upcast::<Node>().unique_id(pipeline))
850 .collect()
851 }),
852 )
853 .unwrap(),
854 Err(error) => reply.send(Err(error)).unwrap(),
855 }
856}
857
858pub(crate) fn handle_find_elements_link_text(
859 cx: &mut JSContext,
860 documents: &DocumentCollection,
861 pipeline: PipelineId,
862 selector: String,
863 partial: bool,
864 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
865) {
866 match retrieve_document_and_check_root_existence(documents, pipeline) {
867 Ok(document) => reply
868 .send(all_matching_links(
869 cx,
870 document.upcast::<Node>(),
871 selector,
872 partial,
873 ))
874 .unwrap(),
875 Err(error) => reply.send(Err(error)).unwrap(),
876 }
877}
878
879pub(crate) fn handle_find_elements_tag_name(
880 cx: &mut JSContext,
881 documents: &DocumentCollection,
882 pipeline: PipelineId,
883 selector: String,
884 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
885) {
886 match retrieve_document_and_check_root_existence(documents, pipeline) {
887 Ok(document) => reply
888 .send(Ok(document
889 .GetElementsByTagName(cx, DOMString::from(selector))
890 .elements_iter(cx.no_gc())
891 .map(|x| x.upcast::<Node>().unique_id(pipeline))
892 .collect::<Vec<String>>()))
893 .unwrap(),
894 Err(error) => reply.send(Err(error)).unwrap(),
895 }
896}
897
898fn find_elements_xpath_strategy(
900 cx: &mut JSContext,
901 document: &Document,
902 start_node: &Node,
903 selector: String,
904 pipeline: PipelineId,
905) -> Result<Vec<String>, ErrorStatus> {
906 let evaluate_result = match document.Evaluate(
911 cx,
912 DOMString::from(selector),
913 start_node,
914 None,
915 XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
916 None,
917 ) {
918 Ok(res) => res,
919 Err(_) => return Err(ErrorStatus::InvalidSelector),
920 };
921 let length = match evaluate_result.GetSnapshotLength() {
927 Ok(len) => len,
928 Err(_) => return Err(ErrorStatus::InvalidSelector),
929 };
930
931 let mut result = Vec::new();
933
934 for index in 0..length {
936 let node = match evaluate_result.SnapshotItem(index) {
939 Ok(node) => node.expect(
940 "Node should always exist as ORDERED_NODE_SNAPSHOT_TYPE \
941 gives static result and we verified the length!",
942 ),
943 Err(_) => return Err(ErrorStatus::InvalidSelector),
944 };
945
946 if !node.is::<Element>() {
948 return Err(ErrorStatus::InvalidSelector);
949 }
950
951 result.push(node.unique_id(pipeline));
953 }
954 Ok(result)
956}
957
958pub(crate) fn handle_find_elements_xpath_selector(
959 cx: &mut JSContext,
960 documents: &DocumentCollection,
961 pipeline: PipelineId,
962 selector: String,
963 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
964) {
965 match retrieve_document_and_check_root_existence(documents, pipeline) {
966 Ok(document) => reply
967 .send(find_elements_xpath_strategy(
968 cx,
969 &document,
970 document.upcast::<Node>(),
971 selector,
972 pipeline,
973 ))
974 .unwrap(),
975 Err(error) => reply.send(Err(error)).unwrap(),
976 }
977}
978
979pub(crate) fn handle_find_element_elements_css_selector(
980 cx: &mut JSContext,
981 documents: &DocumentCollection,
982 pipeline: PipelineId,
983 element_id: String,
984 selector: String,
985 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
986) {
987 reply
988 .send(
989 get_known_element(documents, pipeline, element_id).and_then(|element| {
990 element
991 .upcast::<Node>()
992 .query_selector_all(cx, DOMString::from(selector))
993 .map_err(|_| ErrorStatus::InvalidSelector)
994 .map(|nodes| {
995 nodes
996 .iter(cx)
997 .map(|x| x.upcast::<Node>().unique_id(pipeline))
998 .collect()
999 })
1000 }),
1001 )
1002 .unwrap();
1003}
1004
1005pub(crate) fn handle_find_element_elements_link_text(
1006 cx: &mut JSContext,
1007 documents: &DocumentCollection,
1008 pipeline: PipelineId,
1009 element_id: String,
1010 selector: String,
1011 partial: bool,
1012 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1013) {
1014 reply
1015 .send(
1016 get_known_element(documents, pipeline, element_id).and_then(|element| {
1017 all_matching_links(cx, element.upcast::<Node>(), selector.clone(), partial)
1018 }),
1019 )
1020 .unwrap();
1021}
1022
1023pub(crate) fn handle_find_element_elements_tag_name(
1024 cx: &mut JSContext,
1025 documents: &DocumentCollection,
1026 pipeline: PipelineId,
1027 element_id: String,
1028 selector: String,
1029 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1030) {
1031 reply
1032 .send(
1033 get_known_element(documents, pipeline, element_id).map(|element| {
1034 element
1035 .GetElementsByTagName(cx, DOMString::from(selector))
1036 .elements_iter(cx.no_gc())
1037 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1038 .collect::<Vec<String>>()
1039 }),
1040 )
1041 .unwrap();
1042}
1043
1044pub(crate) fn handle_find_element_elements_xpath_selector(
1045 cx: &mut JSContext,
1046 documents: &DocumentCollection,
1047 pipeline: PipelineId,
1048 element_id: String,
1049 selector: String,
1050 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1051) {
1052 reply
1053 .send(
1054 get_known_element(documents, pipeline, element_id).and_then(|element| {
1055 find_elements_xpath_strategy(
1056 cx,
1057 &documents
1058 .find_document(pipeline)
1059 .expect("Document existence guaranteed by `get_known_element`"),
1060 element.upcast::<Node>(),
1061 selector,
1062 pipeline,
1063 )
1064 }),
1065 )
1066 .unwrap();
1067}
1068
1069pub(crate) fn handle_find_shadow_elements_css_selector(
1071 cx: &mut JSContext,
1072 documents: &DocumentCollection,
1073 pipeline: PipelineId,
1074 shadow_root_id: String,
1075 selector: String,
1076 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1077) {
1078 reply
1079 .send(
1080 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1081 shadow_root
1082 .upcast::<Node>()
1083 .query_selector_all(cx, DOMString::from(selector))
1084 .map_err(|_| ErrorStatus::InvalidSelector)
1085 .map(|nodes| {
1086 nodes
1087 .iter(cx)
1088 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1089 .collect()
1090 })
1091 }),
1092 )
1093 .unwrap();
1094}
1095
1096pub(crate) fn handle_find_shadow_elements_link_text(
1097 cx: &mut JSContext,
1098 documents: &DocumentCollection,
1099 pipeline: PipelineId,
1100 shadow_root_id: String,
1101 selector: String,
1102 partial: bool,
1103 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1104) {
1105 reply
1106 .send(
1107 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1108 all_matching_links(cx, shadow_root.upcast::<Node>(), selector.clone(), partial)
1109 }),
1110 )
1111 .unwrap();
1112}
1113
1114pub(crate) fn handle_find_shadow_elements_tag_name(
1115 cx: &mut JSContext,
1116 documents: &DocumentCollection,
1117 pipeline: PipelineId,
1118 shadow_root_id: String,
1119 selector: String,
1120 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1121) {
1122 reply
1128 .send(
1129 get_known_shadow_root(documents, pipeline, shadow_root_id).map(|shadow_root| {
1130 shadow_root
1131 .upcast::<Node>()
1132 .query_selector_all(cx, DOMString::from(selector))
1133 .map(|nodes| {
1134 nodes
1135 .iter(cx)
1136 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1137 .collect()
1138 })
1139 .unwrap_or_default()
1140 }),
1141 )
1142 .unwrap();
1143}
1144
1145pub(crate) fn handle_find_shadow_elements_xpath_selector(
1146 cx: &mut JSContext,
1147 documents: &DocumentCollection,
1148 pipeline: PipelineId,
1149 shadow_root_id: String,
1150 selector: String,
1151 reply: GenericSender<Result<Vec<String>, ErrorStatus>>,
1152) {
1153 reply
1154 .send(
1155 get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1156 find_elements_xpath_strategy(
1157 cx,
1158 &documents
1159 .find_document(pipeline)
1160 .expect("Document existence guaranteed by `get_known_shadow_root`"),
1161 shadow_root.upcast::<Node>(),
1162 selector,
1163 pipeline,
1164 )
1165 }),
1166 )
1167 .unwrap();
1168}
1169
1170pub(crate) fn handle_get_element_shadow_root(
1172 documents: &DocumentCollection,
1173 pipeline: PipelineId,
1174 element_id: String,
1175 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1176) {
1177 reply
1178 .send(
1179 get_known_element(documents, pipeline, element_id).map(|element| {
1180 element
1181 .shadow_root()
1182 .map(|x| x.upcast::<Node>().unique_id(pipeline))
1183 }),
1184 )
1185 .unwrap();
1186}
1187
1188impl Element {
1189 fn is_keyboard_interactable(&self, no_gc: &NoGC) -> bool {
1191 self.is_focusable_area(no_gc) || self.is::<HTMLBodyElement>() || self.is_document_element()
1192 }
1193}
1194
1195fn handle_send_keys_file(
1196 cx: &mut JSContext,
1197 file_input: &HTMLInputElement,
1198 text: &str,
1199 reply_sender: GenericSender<Result<bool, ErrorStatus>>,
1200) {
1201 let files: Vec<DOMString> = text
1206 .split("\n")
1207 .filter_map(|string| {
1208 if string.is_empty() {
1209 None
1210 } else {
1211 Some(string.into())
1212 }
1213 })
1214 .collect();
1215
1216 if files.is_empty() {
1218 let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1219 return;
1220 }
1221
1222 if !file_input.Multiple() && files.len() > 1 {
1226 let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1227 return;
1228 }
1229
1230 file_input.select_files_for_webdriver(cx, files, reply_sender);
1239}
1240
1241fn handle_send_keys_non_typeable(
1243 cx: &mut JSContext,
1244 input_element: &HTMLInputElement,
1245 text: &str,
1246) -> Result<bool, ErrorStatus> {
1247 if !input_element.is_mutable() {
1254 return Err(ErrorStatus::ElementNotInteractable);
1255 }
1256
1257 if let Err(error) = input_element.SetValue(cx, text.into()) {
1259 error!(
1260 "Failed to set value on non-typeable input element: {:?}",
1261 error
1262 );
1263 return Err(ErrorStatus::UnknownError);
1264 }
1265
1266 if input_element
1268 .Validity(cx)
1269 .invalid_flags()
1270 .contains(ValidationFlags::BAD_INPUT)
1271 {
1272 return Err(ErrorStatus::InvalidArgument);
1273 }
1274
1275 Ok(false)
1278}
1279
1280pub(crate) fn handle_will_send_keys(
1286 cx: &mut JSContext,
1287 documents: &DocumentCollection,
1288 pipeline: PipelineId,
1289 element_id: String,
1290 text: String,
1291 strict_file_interactability: bool,
1292 reply: GenericSender<Result<bool, ErrorStatus>>,
1293) {
1294 let element = match get_known_element(documents, pipeline, element_id) {
1296 Ok(element) => element,
1297 Err(error) => {
1298 let _ = reply.send(Err(error));
1299 return;
1300 },
1301 };
1302
1303 let input_element = element.downcast::<HTMLInputElement>();
1304 let mut element_has_focus = false;
1305
1306 let is_file_input =
1309 input_element.is_some_and(|e| matches!(*e.input_type(), InputType::File(_)));
1310
1311 if !is_file_input || strict_file_interactability {
1313 scroll_into_view(cx, &element);
1315
1316 if !element.is_keyboard_interactable(cx.no_gc()) {
1322 let _ = reply.send(Err(ErrorStatus::ElementNotInteractable));
1323 return;
1324 }
1325
1326 let Some(html_element) = element.downcast::<HTMLElement>() else {
1329 let _ = reply.send(Err(ErrorStatus::UnknownError));
1330 return;
1331 };
1332
1333 if !element.is_active_element() {
1334 html_element.Focus(
1335 cx,
1336 &FocusOptions {
1337 preventScroll: true,
1338 },
1339 );
1340 } else {
1341 element_has_focus = element.focus_state();
1342 }
1343 }
1344
1345 if let Some(input_element) = input_element {
1346 if is_file_input {
1348 handle_send_keys_file(cx, input_element, &text, reply);
1349 return;
1350 }
1351
1352 if input_element.is_nontypeable() {
1354 let _ = reply.send(handle_send_keys_non_typeable(cx, input_element, &text));
1355 return;
1356 }
1357 }
1358
1359 if !element_has_focus {
1367 if let Some(input_element) = input_element {
1368 let length = input_element.Value().len_utf16().0;
1369 let _ = input_element.SetSelectionRange(length, length, None);
1370 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1371 let length = textarea_element.Value().len_utf16().0;
1372 let _ = textarea_element.SetSelectionRange(length, length, None);
1373 }
1374 }
1375
1376 let _ = reply.send(Ok(true));
1377}
1378
1379pub(crate) fn handle_get_active_element(
1380 documents: &DocumentCollection,
1381 pipeline: PipelineId,
1382 reply: GenericSender<Option<String>>,
1383) {
1384 reply
1385 .send(
1386 documents
1387 .find_document(pipeline)
1388 .and_then(|document| document.GetActiveElement())
1389 .map(|element| element.upcast::<Node>().unique_id(pipeline)),
1390 )
1391 .unwrap();
1392}
1393
1394pub(crate) fn handle_get_computed_role(
1395 cx: &mut JSContext,
1396 documents: &DocumentCollection,
1397 pipeline: PipelineId,
1398 node_id: String,
1399 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1400) {
1401 if !pref!(accessibility_enabled) {
1402 return reply.send(Err(ErrorStatus::UnsupportedOperation)).unwrap();
1403 }
1404 reply
1405 .send(
1406 get_known_element(documents, pipeline, node_id).map(|element| {
1407 let document = element.upcast::<Node>().owner_doc();
1408 let window = document.window();
1409 window
1410 .layout()
1411 .set_accessibility_active(true, Epoch::default());
1412 element.get_computed_role(cx).map(String::from)
1413 }),
1414 )
1415 .unwrap();
1416}
1417
1418pub(crate) fn handle_get_page_source(
1419 cx: &mut JSContext,
1420 documents: &DocumentCollection,
1421 pipeline: PipelineId,
1422 reply: GenericSender<Result<String, ErrorStatus>>,
1423) {
1424 reply
1425 .send(
1426 documents
1427 .find_document(pipeline)
1428 .ok_or(ErrorStatus::UnknownError)
1429 .and_then(|document| match document.GetDocumentElement() {
1430 Some(element) => match element.outer_html(cx) {
1431 Ok(source) => Ok(String::from(source)),
1432 Err(_) => {
1433 match XMLSerializer::new(cx, document.window(), None)
1434 .SerializeToString(element.upcast::<Node>())
1435 {
1436 Ok(source) => Ok(String::from(source)),
1437 Err(_) => Err(ErrorStatus::UnknownError),
1438 }
1439 },
1440 },
1441 None => Err(ErrorStatus::UnknownError),
1442 }),
1443 )
1444 .unwrap();
1445}
1446
1447pub(crate) fn handle_get_cookies(
1448 documents: &DocumentCollection,
1449 pipeline: PipelineId,
1450 reply: GenericSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1451) {
1452 reply
1453 .send(
1454 match documents.find_document(pipeline) {
1456 Some(document) => {
1457 let url = document.url();
1458 let (sender, receiver) = generic_channel::channel().unwrap();
1459 let _ = document
1460 .window()
1461 .as_global_scope()
1462 .resource_threads()
1463 .send(GetCookiesForUrl(url, sender, NonHTTP));
1464 Ok(receiver.recv().unwrap())
1465 },
1466 None => Ok(Vec::new()),
1467 },
1468 )
1469 .unwrap();
1470}
1471
1472pub(crate) fn handle_get_cookie(
1474 documents: &DocumentCollection,
1475 pipeline: PipelineId,
1476 name: String,
1477 reply: GenericSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1478) {
1479 reply
1480 .send(
1481 match documents.find_document(pipeline) {
1483 Some(document) => {
1484 let url = document.url();
1485 let (sender, receiver) = generic_channel::channel().unwrap();
1486 let _ = document
1487 .window()
1488 .as_global_scope()
1489 .resource_threads()
1490 .send(GetCookiesForUrl(url, sender, NonHTTP));
1491 let cookies = receiver.recv().unwrap();
1492 Ok(cookies
1493 .into_iter()
1494 .filter(|cookie| cookie.name() == &*name)
1495 .collect())
1496 },
1497 None => Ok(Vec::new()),
1498 },
1499 )
1500 .unwrap();
1501}
1502
1503pub(crate) fn handle_add_cookie(
1505 documents: &DocumentCollection,
1506 pipeline: PipelineId,
1507 cookie: Cookie<'static>,
1508 reply: GenericSender<Result<(), ErrorStatus>>,
1509) {
1510 let document = match documents.find_document(pipeline) {
1512 Some(document) => document,
1513 None => {
1514 return reply.send(Err(ErrorStatus::NoSuchWindow)).unwrap();
1515 },
1516 };
1517 let url = document.url();
1518 let method = if cookie.http_only().unwrap_or(false) {
1519 HTTP
1520 } else {
1521 NonHTTP
1522 };
1523
1524 let domain = cookie.domain().map(ToOwned::to_owned);
1525 reply
1527 .send(match (document.is_cookie_averse(), domain) {
1528 (true, _) => Err(ErrorStatus::InvalidCookieDomain),
1531 (false, Some(ref domain)) if url.host_str().is_some_and(|host| host == domain) => {
1532 let _ = document
1533 .window()
1534 .as_global_scope()
1535 .resource_threads()
1536 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1537 Ok(())
1538 },
1539 (false, Some(_)) => Err(ErrorStatus::InvalidCookieDomain),
1542 (false, None) => {
1543 let _ = document
1544 .window()
1545 .as_global_scope()
1546 .resource_threads()
1547 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1548 Ok(())
1549 },
1550 })
1551 .unwrap();
1552}
1553
1554pub(crate) fn handle_delete_cookies(
1556 documents: &DocumentCollection,
1557 pipeline: PipelineId,
1558 reply: GenericSender<Result<(), ErrorStatus>>,
1559) {
1560 let document = match documents.find_document(pipeline) {
1561 Some(document) => document,
1562 None => {
1563 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1564 },
1565 };
1566 let url = document.url();
1567 document
1568 .window()
1569 .as_global_scope()
1570 .resource_threads()
1571 .send(DeleteCookies(Some(url), None))
1572 .unwrap();
1573 reply.send(Ok(())).unwrap();
1574}
1575
1576pub(crate) fn handle_delete_cookie(
1578 documents: &DocumentCollection,
1579 pipeline: PipelineId,
1580 name: String,
1581 reply: GenericSender<Result<(), ErrorStatus>>,
1582) {
1583 let document = match documents.find_document(pipeline) {
1584 Some(document) => document,
1585 None => {
1586 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1587 },
1588 };
1589 let url = document.url();
1590 document
1591 .window()
1592 .as_global_scope()
1593 .resource_threads()
1594 .send(DeleteCookie(url, name))
1595 .unwrap();
1596 reply.send(Ok(())).unwrap();
1597}
1598
1599pub(crate) fn handle_get_title(
1600 documents: &DocumentCollection,
1601 pipeline: PipelineId,
1602 reply: GenericSender<String>,
1603) {
1604 reply
1605 .send(
1606 documents
1608 .find_document(pipeline)
1609 .map(|document| String::from(document.Title()))
1610 .unwrap_or_default(),
1611 )
1612 .unwrap();
1613}
1614
1615fn calculate_absolute_position(
1617 documents: &DocumentCollection,
1618 pipeline: &PipelineId,
1619 rect: &DOMRect,
1620) -> Result<(f64, f64), ErrorStatus> {
1621 let document = match documents.find_document(*pipeline) {
1626 Some(document) => document,
1627 None => return Err(ErrorStatus::UnknownError),
1628 };
1629 let win = match document.GetDefaultView() {
1630 Some(win) => win,
1631 None => return Err(ErrorStatus::UnknownError),
1632 };
1633
1634 let x = win.ScrollX() as f64 + rect.X();
1636 let y = win.ScrollY() as f64 + rect.Y();
1637
1638 Ok((x, y))
1639}
1640
1641pub(crate) fn handle_get_rect(
1643 cx: &mut JSContext,
1644 documents: &DocumentCollection,
1645 pipeline: PipelineId,
1646 element_id: String,
1647 reply: GenericSender<Result<Rect<f64>, ErrorStatus>>,
1648) {
1649 reply
1650 .send(
1651 get_known_element(documents, pipeline, element_id).and_then(|element| {
1652 let rect = element.GetBoundingClientRect(cx);
1656 let (x, y) = calculate_absolute_position(documents, &pipeline, &rect)?;
1657
1658 Ok(Rect::new(
1660 Point2D::new(x, y),
1661 Size2D::new(rect.Width(), rect.Height()),
1662 ))
1663 }),
1664 )
1665 .unwrap();
1666}
1667
1668pub(crate) fn handle_scroll_and_get_bounding_client_rect(
1669 cx: &mut JSContext,
1670 documents: &DocumentCollection,
1671 pipeline: PipelineId,
1672 element_id: String,
1673 reply: GenericSender<Result<Rect<f32>, ErrorStatus>>,
1674) {
1675 reply
1676 .send(
1677 get_known_element(documents, pipeline, element_id).map(|element| {
1678 scroll_into_view(cx, &element);
1679
1680 let rect = element.GetBoundingClientRect(cx);
1681 Rect::new(
1682 Point2D::new(rect.X() as f32, rect.Y() as f32),
1683 Size2D::new(rect.Width() as f32, rect.Height() as f32),
1684 )
1685 }),
1686 )
1687 .unwrap();
1688}
1689
1690pub(crate) fn handle_get_text(
1692 documents: &DocumentCollection,
1693 pipeline: PipelineId,
1694 node_id: String,
1695 reply: GenericSender<Result<String, ErrorStatus>>,
1696) {
1697 reply
1698 .send(
1699 get_known_element(documents, pipeline, node_id).map(|element| {
1700 element
1701 .downcast::<HTMLElement>()
1702 .map(|htmlelement| String::from(htmlelement.InnerText()))
1703 .unwrap_or_else(|| {
1704 element
1705 .upcast::<Node>()
1706 .GetTextContent()
1707 .map_or(String::new(), String::from)
1708 })
1709 }),
1710 )
1711 .unwrap();
1712}
1713
1714pub(crate) fn handle_get_name(
1716 documents: &DocumentCollection,
1717 pipeline: PipelineId,
1718 node_id: String,
1719 reply: GenericSender<Result<String, ErrorStatus>>,
1720) {
1721 reply
1722 .send(
1723 get_known_element(documents, pipeline, node_id)
1724 .map(|element| element.qualified_name().into_owned()),
1725 )
1726 .unwrap();
1727}
1728
1729pub(crate) fn handle_get_attribute(
1730 cx: &mut JSContext,
1731 documents: &DocumentCollection,
1732 pipeline: PipelineId,
1733 node_id: String,
1734 name: String,
1735 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1736) {
1737 reply
1738 .send(
1739 get_known_element(documents, pipeline, node_id).map(|element| {
1740 if is_boolean_attribute(&name) {
1741 if element.HasAttribute(cx, DOMString::from(name)) {
1742 Some(String::from("true"))
1743 } else {
1744 None
1745 }
1746 } else {
1747 element
1748 .GetAttribute(cx, DOMString::from(name))
1749 .map(String::from)
1750 }
1751 }),
1752 )
1753 .unwrap();
1754}
1755
1756pub(crate) fn handle_get_property(
1757 cx: &mut JSContext,
1758 documents: &DocumentCollection,
1759 pipeline: PipelineId,
1760 node_id: String,
1761 name: String,
1762 reply: GenericSender<Result<JSValue, ErrorStatus>>,
1763) {
1764 reply
1765 .send(
1766 get_known_element(documents, pipeline, node_id).map(|element| {
1767 let document = documents.find_document(pipeline).unwrap();
1768
1769 let Ok(cname) = CString::new(name) else {
1770 return JSValue::Undefined;
1771 };
1772
1773 let mut realm = enter_auto_realm(cx, &*document);
1774 let cx = &mut realm.current_realm();
1775
1776 rooted!(&in(cx) let mut property = UndefinedValue());
1777 match get_property_jsval(
1778 cx,
1779 element.reflector().get_jsobject(),
1780 &cname,
1781 property.handle_mut(),
1782 ) {
1783 Ok(_) => match jsval_to_webdriver(cx, &element.global(), property.handle()) {
1784 Ok(property) => property,
1785 Err(_) => JSValue::Undefined,
1786 },
1787 Err(error) => {
1788 throw_dom_exception(cx, &element.global(), error);
1789 JSValue::Undefined
1790 },
1791 }
1792 }),
1793 )
1794 .unwrap();
1795}
1796
1797pub(crate) fn handle_get_css(
1798 cx: &mut JSContext,
1799 documents: &DocumentCollection,
1800 pipeline: PipelineId,
1801 node_id: String,
1802 name: String,
1803 reply: GenericSender<Result<String, ErrorStatus>>,
1804) {
1805 reply
1806 .send(
1807 get_known_element(documents, pipeline, node_id).map(|element| {
1808 let window = element.owner_window();
1809 String::from(
1810 window
1811 .GetComputedStyle(cx, &element, None)
1812 .GetPropertyValue(DOMString::from(name)),
1813 )
1814 }),
1815 )
1816 .unwrap();
1817}
1818
1819pub(crate) fn handle_get_url(
1820 documents: &DocumentCollection,
1821 pipeline: PipelineId,
1822 reply: GenericSender<String>,
1823) {
1824 reply
1825 .send(
1826 documents
1828 .find_document(pipeline)
1829 .map(|document| document.url().into_string())
1830 .unwrap_or_else(|| "about:blank".to_string()),
1831 )
1832 .unwrap();
1833}
1834
1835fn element_is_mutable_form_control(element: &Element) -> bool {
1837 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1838 input_element.is_mutable() &&
1839 matches!(
1840 *input_element.input_type(),
1841 InputType::Text(_) |
1842 InputType::Search(_) |
1843 InputType::Url(_) |
1844 InputType::Tel(_) |
1845 InputType::Email(_) |
1846 InputType::Password(_) |
1847 InputType::Date(_) |
1848 InputType::Month(_) |
1849 InputType::Week(_) |
1850 InputType::Time(_) |
1851 InputType::DatetimeLocal(_) |
1852 InputType::Number(_) |
1853 InputType::Range(_) |
1854 InputType::Color(_) |
1855 InputType::File(_)
1856 )
1857 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1858 textarea_element.is_mutable()
1859 } else {
1860 false
1861 }
1862}
1863
1864fn clear_a_resettable_element(cx: &mut JSContext, element: &Element) -> Result<(), ErrorStatus> {
1866 let html_element = element
1867 .downcast::<HTMLElement>()
1868 .ok_or(ErrorStatus::UnknownError)?;
1869
1870 if html_element.is_candidate_for_constraint_validation(cx.no_gc()) {
1873 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1874 if input_element.Value().is_empty() {
1875 return Ok(());
1876 }
1877 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() &&
1878 textarea_element.Value().is_empty()
1879 {
1880 return Ok(());
1881 }
1882 }
1883
1884 html_element.Focus(
1886 cx,
1887 &FocusOptions {
1888 preventScroll: true,
1889 },
1890 );
1891
1892 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1894 input_element.clear(cx);
1895 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1896 textarea_element.clear();
1897 } else {
1898 unreachable!("We have confirm previously that element is mutable form control");
1899 }
1900
1901 let event_target = element.upcast::<EventTarget>();
1902 event_target.fire_bubbling_event(cx, atom!("input"));
1903 event_target.fire_bubbling_event(cx, atom!("change"));
1904
1905 html_element.Blur(cx);
1907
1908 Ok(())
1909}
1910
1911pub(crate) fn handle_element_clear(
1913 cx: &mut JSContext,
1914 documents: &DocumentCollection,
1915 pipeline: PipelineId,
1916 element_id: String,
1917 reply: GenericSender<Result<(), ErrorStatus>>,
1918) {
1919 reply
1920 .send(
1921 get_known_element(documents, pipeline, element_id).and_then(|element| {
1922 if !element_is_mutable_form_control(&element) {
1926 return Err(ErrorStatus::InvalidElementState);
1927 }
1928
1929 scroll_into_view(cx, &element);
1931
1932 if !element.is_keyboard_interactable(cx.no_gc()) {
1938 return Err(ErrorStatus::ElementNotInteractable);
1939 }
1940
1941 let paint_tree = get_element_pointer_interactable_paint_tree(cx, &element);
1942 if !is_element_in_view(&element, &paint_tree) {
1943 return Err(ErrorStatus::ElementNotInteractable);
1944 }
1945
1946 clear_a_resettable_element(cx, &element)
1949 }),
1950 )
1951 .unwrap();
1952}
1953
1954fn get_option_parent(node: &Node) -> Option<DomRoot<Element>> {
1955 let mut candidate_select = None;
1963
1964 for ancestor in node.ancestors() {
1965 if ancestor.is::<HTMLDataListElement>() {
1966 return Some(DomRoot::downcast::<Element>(ancestor).unwrap());
1967 } else if candidate_select.is_none() && ancestor.is::<HTMLSelectElement>() {
1968 candidate_select = Some(ancestor);
1969 }
1970 }
1971
1972 candidate_select.map(|ancestor| DomRoot::downcast::<Element>(ancestor).unwrap())
1973}
1974
1975fn get_container(element: &Element) -> Option<DomRoot<Element>> {
1977 if element.is::<HTMLOptionElement>() {
1978 return get_option_parent(element.upcast::<Node>());
1979 }
1980 if element.is::<HTMLOptGroupElement>() {
1981 return get_option_parent(element.upcast::<Node>())
1982 .or_else(|| Some(DomRoot::from_ref(element)));
1983 }
1984 Some(DomRoot::from_ref(element))
1985}
1986
1987pub(crate) fn handle_element_click(
1989 cx: &mut JSContext,
1990 documents: &DocumentCollection,
1991 pipeline: PipelineId,
1992 element_id: String,
1993 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1994) {
1995 reply
1996 .send(
1997 get_known_element(documents, pipeline, element_id).and_then(|element| {
1999 if let Some(input_element) = element.downcast::<HTMLInputElement>() &&
2002 matches!(*input_element.input_type(), InputType::File(_))
2003 {
2004 return Err(ErrorStatus::InvalidArgument);
2005 }
2006
2007 let Some(container) = get_container(&element) else {
2008 return Err(ErrorStatus::UnknownError);
2009 };
2010
2011 scroll_into_view(cx, &container);
2013
2014 let paint_tree = get_element_pointer_interactable_paint_tree(cx, &container);
2017
2018 if !is_element_in_view(&container, &paint_tree) {
2019 return Err(ErrorStatus::ElementNotInteractable);
2020 }
2021
2022 if !container
2029 .upcast::<Node>()
2030 .is_shadow_including_inclusive_ancestor_of(paint_tree[0].upcast::<Node>())
2031 {
2032 return Err(ErrorStatus::ElementClickIntercepted);
2033 }
2034
2035 match element.downcast::<HTMLOptionElement>() {
2037 Some(option_element) => {
2038 let event_target = container.upcast::<EventTarget>();
2040 event_target.fire_event(cx, atom!("mouseover"));
2041 event_target.fire_event(cx, atom!("mousemove"));
2042 event_target.fire_event(cx, atom!("mousedown"));
2043
2044 match container.downcast::<HTMLElement>() {
2046 Some(html_element) => {
2047 html_element.Focus(
2048 cx,
2049 &FocusOptions {
2050 preventScroll: true,
2051 },
2052 );
2053 },
2054 None => return Err(ErrorStatus::UnknownError),
2055 }
2056
2057 if !is_disabled(&element) {
2059 event_target.fire_event(cx, atom!("input"));
2061
2062 let previous_selectedness = option_element.Selected();
2064
2065 match container.downcast::<HTMLSelectElement>() {
2067 Some(select_element) => {
2068 if select_element.Multiple() {
2069 option_element.SetSelected(cx, !option_element.Selected());
2070 }
2071 },
2072 None => option_element.SetSelected(cx, true),
2073 }
2074
2075 if !previous_selectedness {
2077 event_target.fire_event(cx, atom!("change"));
2078 }
2079 }
2080
2081 event_target.fire_event(cx, atom!("mouseup"));
2083 event_target.fire_event(cx, atom!("click"));
2084
2085 Ok(None)
2086 },
2087 None => Ok(Some(element.upcast::<Node>().unique_id(pipeline))),
2088 }
2089 }),
2090 )
2091 .unwrap();
2092}
2093
2094fn is_element_in_view(element: &Element, paint_tree: &[DomRoot<Element>]) -> bool {
2096 if !paint_tree.iter().any(|e| &**e == element) {
2099 return false;
2100 }
2101 use style::computed_values::pointer_events::T as PointerEvents;
2102 element
2106 .style()
2107 .is_none_or(|style| style.get_inherited_ui().pointer_events != PointerEvents::None)
2108}
2109
2110fn get_element_pointer_interactable_paint_tree(
2112 cx: &mut JSContext,
2113 element: &Element,
2114) -> Vec<DomRoot<Element>> {
2115 if !element.is_connected() {
2118 return Vec::new();
2119 }
2120
2121 get_element_in_view_center_point(cx, element).map_or(Vec::new(), |center_point| {
2127 if let Some(shadow_root) = element.containing_shadow_root() {
2128 shadow_root.ElementsFromPoint(
2129 Finite::wrap(center_point.x as f64),
2130 Finite::wrap(center_point.y as f64),
2131 )
2132 } else {
2133 element.owner_document().ElementsFromPoint(
2134 Finite::wrap(center_point.x as f64),
2135 Finite::wrap(center_point.y as f64),
2136 )
2137 }
2138 })
2139}
2140
2141pub(crate) fn handle_is_enabled(
2143 documents: &DocumentCollection,
2144 pipeline: PipelineId,
2145 element_id: String,
2146 reply: GenericSender<Result<bool, ErrorStatus>>,
2147) {
2148 reply
2149 .send(
2150 get_known_element(documents, pipeline, element_id).map(|element| {
2152 let document = documents.find_document(pipeline).unwrap();
2154
2155 if document.is_html_document() || document.is_xhtml_document() {
2161 !is_disabled(&element)
2162 } else {
2163 false
2164 }
2165 }),
2166 )
2167 .unwrap();
2168}
2169
2170pub(crate) fn handle_is_selected(
2171 documents: &DocumentCollection,
2172 pipeline: PipelineId,
2173 element_id: String,
2174 reply: GenericSender<Result<bool, ErrorStatus>>,
2175) {
2176 reply
2177 .send(
2178 get_known_element(documents, pipeline, element_id).and_then(|element| {
2179 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
2180 Ok(input_element.Checked())
2181 } else if let Some(option_element) = element.downcast::<HTMLOptionElement>() {
2182 Ok(option_element.Selected())
2183 } else if element.is::<HTMLElement>() {
2184 Ok(false) } else {
2186 Err(ErrorStatus::UnknownError)
2187 }
2188 }),
2189 )
2190 .unwrap();
2191}
2192
2193pub(crate) fn handle_add_load_status_sender(
2194 documents: &DocumentCollection,
2195 pipeline: PipelineId,
2196 reply: GenericSender<WebDriverLoadStatus>,
2197) {
2198 if let Some(document) = documents.find_document(pipeline) {
2199 let window = document.window();
2200 window.set_webdriver_load_status_sender(Some(reply));
2201 }
2202}
2203
2204pub(crate) fn handle_remove_load_status_sender(
2205 documents: &DocumentCollection,
2206 pipeline: PipelineId,
2207) {
2208 if let Some(document) = documents.find_document(pipeline) {
2209 let window = document.window();
2210 window.set_webdriver_load_status_sender(None);
2211 }
2212}
2213
2214fn scroll_into_view(cx: &mut JSContext, element: &Element) {
2216 let paint_tree = get_element_pointer_interactable_paint_tree(cx, element);
2218 if is_element_in_view(element, &paint_tree) {
2219 return;
2220 }
2221
2222 let options = BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions(ScrollIntoViewOptions {
2227 parent: ScrollOptions {
2228 behavior: ScrollBehavior::Instant,
2229 },
2230 block: ScrollLogicalPosition::End,
2231 inline: ScrollLogicalPosition::Nearest,
2232 container: Default::default(),
2233 });
2234 element.ScrollIntoView(cx, options);
2236}
2237
2238pub(crate) fn set_protocol_handler_automation_mode(
2239 documents: &DocumentCollection,
2240 pipeline: PipelineId,
2241 mode: CustomHandlersAutomationMode,
2242) {
2243 if let Some(document) = documents.find_document(pipeline) {
2244 document.set_protocol_handler_automation_mode(mode);
2245 }
2246}
2247
2248pub(crate) fn set_permission(
2250 documents: &DocumentCollection,
2251 pipeline: PipelineId,
2252 name: String,
2253 state: SetPermissionState,
2254 reply: GenericOneshotSender<Result<(), ErrorStatus>>,
2255) {
2256 let Ok(name) = name.parse::<PermissionName>() else {
2257 if let Err(err) = reply.send(Err(ErrorStatus::InvalidArgument)) {
2258 error!("SetPermission Failed to send reply: {err}");
2259 }
2260 return;
2261 };
2262 let state = match state {
2263 SetPermissionState::Denied => PermissionState::Denied,
2264 SetPermissionState::Granted => PermissionState::Granted,
2265 SetPermissionState::Prompt => PermissionState::Prompt,
2266 };
2267
2268 let Some(global) = documents.find_global(pipeline) else {
2269 if let Err(err) = reply.send(Err(ErrorStatus::NoSuchWindow)) {
2270 error!("SetPermission Failed to send reply: {err}");
2271 }
2272 return;
2273 };
2274
2275 global
2277 .permission_state_invocation_results()
2278 .borrow_mut()
2279 .insert(name, state);
2280
2281 if let Err(err) = reply.send(Ok(())) {
2285 error!("SetPermission Failed to send reply: {err}");
2286 }
2287}