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