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