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