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