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