1use std::collections::{HashMap, HashSet};
6use std::ffi::CString;
7use std::ptr::NonNull;
8
9use cookie::Cookie;
10use embedder_traits::{
11 CustomHandlersAutomationMode, JSValue, JavaScriptEvaluationError,
12 JavaScriptEvaluationResultSerializationError, WebDriverFrameId, WebDriverJSResult,
13 WebDriverLoadStatus,
14};
15use euclid::default::{Point2D, Rect, Size2D};
16use hyper_serde::Serde;
17use js::context::JSContext;
18use js::conversions::jsstr_to_string;
19use js::jsapi::{
20 self, GetPropertyKeys, HandleValueArray, JS_GetOwnPropertyDescriptorById, JS_GetPropertyById,
21 JS_IsExceptionPending, JSAutoRealm, JSObject, JSType, PropertyDescriptor,
22};
23use js::jsval::UndefinedValue;
24use js::realm::CurrentRealm;
25use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue};
26use js::rust::{Handle, HandleObject, HandleValue, IdVector, ToString};
27use net_traits::CookieSource::{HTTP, NonHTTP};
28use net_traits::CoreResourceMsg::{
29 DeleteCookie, DeleteCookies, GetCookiesDataForUrl, SetCookieForUrl,
30};
31use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
32use script_bindings::conversions::is_array_like;
33use script_bindings::num::Finite;
34use script_bindings::settings_stack::run_a_script;
35use servo_base::generic_channel::{self, GenericOneshotSender, GenericSend, GenericSender};
36use servo_base::id::{BrowsingContextId, PipelineId};
37use webdriver::error::ErrorStatus;
38
39use crate::DomTypeHolder;
40use crate::document_collection::DocumentCollection;
41use crate::dom::attr::is_boolean_attribute;
42use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
43use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
44use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
45use crate::dom::bindings::codegen::Bindings::ElementBinding::{
46 ElementMethods, ScrollIntoViewOptions, ScrollLogicalPosition,
47};
48use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
49use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
50use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
51use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
52use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
53use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
54use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
55use crate::dom::bindings::codegen::Bindings::WindowBinding::{
56 ScrollBehavior, ScrollOptions, WindowMethods,
57};
58use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
59use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
60 XPathResultConstants, XPathResultMethods,
61};
62use crate::dom::bindings::codegen::UnionTypes::BooleanOrScrollIntoViewOptions;
63use crate::dom::bindings::conversions::{
64 ConversionBehavior, ConversionResult, FromJSValConvertible, get_property, get_property_jsval,
65 jsid_to_string, root_from_object,
66};
67use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::reflector::{DomGlobal, DomObject};
70use crate::dom::bindings::root::DomRoot;
71use crate::dom::bindings::str::DOMString;
72use crate::dom::document::Document;
73use crate::dom::domrect::DOMRect;
74use crate::dom::element::Element;
75use crate::dom::eventtarget::EventTarget;
76use crate::dom::globalscope::GlobalScope;
77use crate::dom::html::htmlbodyelement::HTMLBodyElement;
78use crate::dom::html::htmldatalistelement::HTMLDataListElement;
79use crate::dom::html::htmlelement::HTMLElement;
80use crate::dom::html::htmlformelement::FormControl;
81use crate::dom::html::htmliframeelement::HTMLIFrameElement;
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::html::input_element::HTMLInputElement;
87use crate::dom::input_element::input_type::InputType;
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 =
1270 input_element.is_some_and(|e| matches!(*e.input_type(), InputType::File(_)));
1271
1272 if !is_file_input || strict_file_interactability {
1274 scroll_into_view(&element, documents, &pipeline, can_gc);
1276
1277 if !element.is_keyboard_interactable() {
1283 let _ = reply.send(Err(ErrorStatus::ElementNotInteractable));
1284 return;
1285 }
1286
1287 let Some(html_element) = element.downcast::<HTMLElement>() else {
1290 let _ = reply.send(Err(ErrorStatus::UnknownError));
1291 return;
1292 };
1293
1294 if !element.is_active_element() {
1295 html_element.Focus(
1296 &FocusOptions {
1297 preventScroll: true,
1298 },
1299 can_gc,
1300 );
1301 } else {
1302 element_has_focus = element.focus_state();
1303 }
1304 }
1305
1306 if let Some(input_element) = input_element {
1307 if is_file_input {
1309 handle_send_keys_file(input_element, &text, reply);
1310 return;
1311 }
1312
1313 if input_element.is_nontypeable() {
1315 let _ = reply.send(handle_send_keys_non_typeable(input_element, &text, can_gc));
1316 return;
1317 }
1318 }
1319
1320 if !element_has_focus {
1328 if let Some(input_element) = input_element {
1329 let length = input_element.Value().len() as u32;
1330 let _ = input_element.SetSelectionRange(length, length, None);
1331 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1332 let length = textarea_element.Value().len() as u32;
1333 let _ = textarea_element.SetSelectionRange(length, length, None);
1334 }
1335 }
1336
1337 let _ = reply.send(Ok(true));
1338}
1339
1340pub(crate) fn handle_get_active_element(
1341 documents: &DocumentCollection,
1342 pipeline: PipelineId,
1343 reply: GenericSender<Option<String>>,
1344) {
1345 reply
1346 .send(
1347 documents
1348 .find_document(pipeline)
1349 .and_then(|document| document.GetActiveElement())
1350 .map(|element| element.upcast::<Node>().unique_id(pipeline)),
1351 )
1352 .unwrap();
1353}
1354
1355pub(crate) fn handle_get_computed_role(
1356 documents: &DocumentCollection,
1357 pipeline: PipelineId,
1358 node_id: String,
1359 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1360) {
1361 reply
1362 .send(
1363 get_known_element(documents, pipeline, node_id)
1364 .map(|element| element.GetRole().map(String::from)),
1368 )
1369 .unwrap();
1370}
1371
1372pub(crate) fn handle_get_page_source(
1373 cx: &mut js::context::JSContext,
1374 documents: &DocumentCollection,
1375 pipeline: PipelineId,
1376 reply: GenericSender<Result<String, ErrorStatus>>,
1377) {
1378 reply
1379 .send(
1380 documents
1381 .find_document(pipeline)
1382 .ok_or(ErrorStatus::UnknownError)
1383 .and_then(|document| match document.GetDocumentElement() {
1384 Some(element) => match element.outer_html(cx) {
1385 Ok(source) => Ok(source.to_string()),
1386 Err(_) => {
1387 match XMLSerializer::new(document.window(), None, CanGc::from_cx(cx))
1388 .SerializeToString(element.upcast::<Node>())
1389 {
1390 Ok(source) => Ok(source.to_string()),
1391 Err(_) => Err(ErrorStatus::UnknownError),
1392 }
1393 },
1394 },
1395 None => Err(ErrorStatus::UnknownError),
1396 }),
1397 )
1398 .unwrap();
1399}
1400
1401pub(crate) fn handle_get_cookies(
1402 documents: &DocumentCollection,
1403 pipeline: PipelineId,
1404 reply: GenericSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1405) {
1406 reply
1407 .send(
1408 match documents.find_document(pipeline) {
1410 Some(document) => {
1411 let url = document.url();
1412 let (sender, receiver) = generic_channel::channel().unwrap();
1413 let _ = document
1414 .window()
1415 .as_global_scope()
1416 .resource_threads()
1417 .send(GetCookiesDataForUrl(url, sender, NonHTTP));
1418 Ok(receiver.recv().unwrap())
1419 },
1420 None => Ok(Vec::new()),
1421 },
1422 )
1423 .unwrap();
1424}
1425
1426pub(crate) fn handle_get_cookie(
1428 documents: &DocumentCollection,
1429 pipeline: PipelineId,
1430 name: String,
1431 reply: GenericSender<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) = generic_channel::channel().unwrap();
1440 let _ = document
1441 .window()
1442 .as_global_scope()
1443 .resource_threads()
1444 .send(GetCookiesDataForUrl(url, sender, NonHTTP));
1445 let cookies = receiver.recv().unwrap();
1446 Ok(cookies
1447 .into_iter()
1448 .filter(|cookie| cookie.name() == &*name)
1449 .collect())
1450 },
1451 None => Ok(Vec::new()),
1452 },
1453 )
1454 .unwrap();
1455}
1456
1457pub(crate) fn handle_add_cookie(
1459 documents: &DocumentCollection,
1460 pipeline: PipelineId,
1461 cookie: Cookie<'static>,
1462 reply: GenericSender<Result<(), ErrorStatus>>,
1463) {
1464 let document = match documents.find_document(pipeline) {
1466 Some(document) => document,
1467 None => {
1468 return reply.send(Err(ErrorStatus::NoSuchWindow)).unwrap();
1469 },
1470 };
1471 let url = document.url();
1472 let method = if cookie.http_only().unwrap_or(false) {
1473 HTTP
1474 } else {
1475 NonHTTP
1476 };
1477
1478 let domain = cookie.domain().map(ToOwned::to_owned);
1479 reply
1481 .send(match (document.is_cookie_averse(), domain) {
1482 (true, _) => Err(ErrorStatus::InvalidCookieDomain),
1485 (false, Some(ref domain)) if url.host_str().is_some_and(|host| host == domain) => {
1486 let _ = document
1487 .window()
1488 .as_global_scope()
1489 .resource_threads()
1490 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1491 Ok(())
1492 },
1493 (false, Some(_)) => Err(ErrorStatus::InvalidCookieDomain),
1496 (false, None) => {
1497 let _ = document
1498 .window()
1499 .as_global_scope()
1500 .resource_threads()
1501 .send(SetCookieForUrl(url, Serde(cookie), method, None));
1502 Ok(())
1503 },
1504 })
1505 .unwrap();
1506}
1507
1508pub(crate) fn handle_delete_cookies(
1510 documents: &DocumentCollection,
1511 pipeline: PipelineId,
1512 reply: GenericSender<Result<(), ErrorStatus>>,
1513) {
1514 let document = match documents.find_document(pipeline) {
1515 Some(document) => document,
1516 None => {
1517 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1518 },
1519 };
1520 let url = document.url();
1521 document
1522 .window()
1523 .as_global_scope()
1524 .resource_threads()
1525 .send(DeleteCookies(Some(url), None))
1526 .unwrap();
1527 reply.send(Ok(())).unwrap();
1528}
1529
1530pub(crate) fn handle_delete_cookie(
1532 documents: &DocumentCollection,
1533 pipeline: PipelineId,
1534 name: String,
1535 reply: GenericSender<Result<(), ErrorStatus>>,
1536) {
1537 let document = match documents.find_document(pipeline) {
1538 Some(document) => document,
1539 None => {
1540 return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1541 },
1542 };
1543 let url = document.url();
1544 document
1545 .window()
1546 .as_global_scope()
1547 .resource_threads()
1548 .send(DeleteCookie(url, name))
1549 .unwrap();
1550 reply.send(Ok(())).unwrap();
1551}
1552
1553pub(crate) fn handle_get_title(
1554 documents: &DocumentCollection,
1555 pipeline: PipelineId,
1556 reply: GenericSender<String>,
1557) {
1558 reply
1559 .send(
1560 documents
1562 .find_document(pipeline)
1563 .map(|document| String::from(document.Title()))
1564 .unwrap_or_default(),
1565 )
1566 .unwrap();
1567}
1568
1569fn calculate_absolute_position(
1571 documents: &DocumentCollection,
1572 pipeline: &PipelineId,
1573 rect: &DOMRect,
1574) -> Result<(f64, f64), ErrorStatus> {
1575 let document = match documents.find_document(*pipeline) {
1580 Some(document) => document,
1581 None => return Err(ErrorStatus::UnknownError),
1582 };
1583 let win = match document.GetDefaultView() {
1584 Some(win) => win,
1585 None => return Err(ErrorStatus::UnknownError),
1586 };
1587
1588 let x = win.ScrollX() as f64 + rect.X();
1590 let y = win.ScrollY() as f64 + rect.Y();
1591
1592 Ok((x, y))
1593}
1594
1595pub(crate) fn handle_get_rect(
1597 documents: &DocumentCollection,
1598 pipeline: PipelineId,
1599 element_id: String,
1600 reply: GenericSender<Result<Rect<f64>, ErrorStatus>>,
1601 can_gc: CanGc,
1602) {
1603 reply
1604 .send(
1605 get_known_element(documents, pipeline, element_id).and_then(|element| {
1606 let rect = element.GetBoundingClientRect(can_gc);
1610 let (x, y) = calculate_absolute_position(documents, &pipeline, &rect)?;
1611
1612 Ok(Rect::new(
1614 Point2D::new(x, y),
1615 Size2D::new(rect.Width(), rect.Height()),
1616 ))
1617 }),
1618 )
1619 .unwrap();
1620}
1621
1622pub(crate) fn handle_scroll_and_get_bounding_client_rect(
1623 documents: &DocumentCollection,
1624 pipeline: PipelineId,
1625 element_id: String,
1626 reply: GenericSender<Result<Rect<f32>, ErrorStatus>>,
1627 can_gc: CanGc,
1628) {
1629 reply
1630 .send(
1631 get_known_element(documents, pipeline, element_id).map(|element| {
1632 scroll_into_view(&element, documents, &pipeline, can_gc);
1633
1634 let rect = element.GetBoundingClientRect(can_gc);
1635 Rect::new(
1636 Point2D::new(rect.X() as f32, rect.Y() as f32),
1637 Size2D::new(rect.Width() as f32, rect.Height() as f32),
1638 )
1639 }),
1640 )
1641 .unwrap();
1642}
1643
1644pub(crate) fn handle_get_text(
1646 documents: &DocumentCollection,
1647 pipeline: PipelineId,
1648 node_id: String,
1649 reply: GenericSender<Result<String, ErrorStatus>>,
1650) {
1651 reply
1652 .send(
1653 get_known_element(documents, pipeline, node_id).map(|element| {
1654 element
1655 .downcast::<HTMLElement>()
1656 .map(|htmlelement| htmlelement.InnerText().to_string())
1657 .unwrap_or_else(|| {
1658 element
1659 .upcast::<Node>()
1660 .GetTextContent()
1661 .map_or("".to_owned(), String::from)
1662 })
1663 }),
1664 )
1665 .unwrap();
1666}
1667
1668pub(crate) fn handle_get_name(
1669 documents: &DocumentCollection,
1670 pipeline: PipelineId,
1671 node_id: String,
1672 reply: GenericSender<Result<String, ErrorStatus>>,
1673) {
1674 reply
1675 .send(
1676 get_known_element(documents, pipeline, node_id)
1677 .map(|element| String::from(element.TagName())),
1678 )
1679 .unwrap();
1680}
1681
1682pub(crate) fn handle_get_attribute(
1683 documents: &DocumentCollection,
1684 pipeline: PipelineId,
1685 node_id: String,
1686 name: String,
1687 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1688) {
1689 reply
1690 .send(
1691 get_known_element(documents, pipeline, node_id).map(|element| {
1692 if is_boolean_attribute(&name) {
1693 if element.HasAttribute(DOMString::from(name)) {
1694 Some(String::from("true"))
1695 } else {
1696 None
1697 }
1698 } else {
1699 element
1700 .GetAttribute(DOMString::from(name))
1701 .map(String::from)
1702 }
1703 }),
1704 )
1705 .unwrap();
1706}
1707
1708pub(crate) fn handle_get_property(
1709 documents: &DocumentCollection,
1710 pipeline: PipelineId,
1711 node_id: String,
1712 name: String,
1713 reply: GenericSender<Result<JSValue, ErrorStatus>>,
1714 cx: &mut JSContext,
1715) {
1716 reply
1717 .send(
1718 get_known_element(documents, pipeline, node_id).map(|element| {
1719 let document = documents.find_document(pipeline).unwrap();
1720
1721 let Ok(cname) = CString::new(name) else {
1722 return JSValue::Undefined;
1723 };
1724
1725 let mut realm = enter_auto_realm(cx, &*document);
1726 let cx = &mut realm.current_realm();
1727
1728 rooted!(&in(cx) let mut property = UndefinedValue());
1729 match get_property_jsval(
1730 cx.into(),
1731 element.reflector().get_jsobject(),
1732 &cname,
1733 property.handle_mut(),
1734 ) {
1735 Ok(_) => match jsval_to_webdriver(cx, &element.global(), property.handle()) {
1736 Ok(property) => property,
1737 Err(_) => JSValue::Undefined,
1738 },
1739 Err(error) => {
1740 throw_dom_exception(
1741 cx.into(),
1742 &element.global(),
1743 error,
1744 CanGc::from_cx(cx),
1745 );
1746 JSValue::Undefined
1747 },
1748 }
1749 }),
1750 )
1751 .unwrap();
1752}
1753
1754pub(crate) fn handle_get_css(
1755 documents: &DocumentCollection,
1756 pipeline: PipelineId,
1757 node_id: String,
1758 name: String,
1759 reply: GenericSender<Result<String, ErrorStatus>>,
1760) {
1761 reply
1762 .send(
1763 get_known_element(documents, pipeline, node_id).map(|element| {
1764 let window = element.owner_window();
1765 String::from(
1766 window
1767 .GetComputedStyle(&element, None)
1768 .GetPropertyValue(DOMString::from(name)),
1769 )
1770 }),
1771 )
1772 .unwrap();
1773}
1774
1775pub(crate) fn handle_get_url(
1776 documents: &DocumentCollection,
1777 pipeline: PipelineId,
1778 reply: GenericSender<String>,
1779 _can_gc: CanGc,
1780) {
1781 reply
1782 .send(
1783 documents
1785 .find_document(pipeline)
1786 .map(|document| document.url().into_string())
1787 .unwrap_or_else(|| "about:blank".to_string()),
1788 )
1789 .unwrap();
1790}
1791
1792fn element_is_mutable_form_control(element: &Element) -> bool {
1794 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1795 input_element.is_mutable() &&
1796 matches!(
1797 *input_element.input_type(),
1798 InputType::Text(_) |
1799 InputType::Search(_) |
1800 InputType::Url(_) |
1801 InputType::Tel(_) |
1802 InputType::Email(_) |
1803 InputType::Password(_) |
1804 InputType::Date(_) |
1805 InputType::Month(_) |
1806 InputType::Week(_) |
1807 InputType::Time(_) |
1808 InputType::DatetimeLocal(_) |
1809 InputType::Number(_) |
1810 InputType::Range(_) |
1811 InputType::Color(_) |
1812 InputType::File(_)
1813 )
1814 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1815 textarea_element.is_mutable()
1816 } else {
1817 false
1818 }
1819}
1820
1821fn clear_a_resettable_element(element: &Element, can_gc: CanGc) -> Result<(), ErrorStatus> {
1823 let html_element = element
1824 .downcast::<HTMLElement>()
1825 .ok_or(ErrorStatus::UnknownError)?;
1826
1827 if html_element.is_candidate_for_constraint_validation() {
1830 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1831 if input_element.Value().is_empty() {
1832 return Ok(());
1833 }
1834 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1835 if textarea_element.Value().is_empty() {
1836 return Ok(());
1837 }
1838 }
1839 }
1840
1841 html_element.Focus(
1843 &FocusOptions {
1844 preventScroll: true,
1845 },
1846 can_gc,
1847 );
1848
1849 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1851 input_element.clear(can_gc);
1852 } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1853 textarea_element.clear();
1854 } else {
1855 unreachable!("We have confirm previously that element is mutable form control");
1856 }
1857
1858 let event_target = element.upcast::<EventTarget>();
1859 event_target.fire_bubbling_event(atom!("input"), can_gc);
1860 event_target.fire_bubbling_event(atom!("change"), can_gc);
1861
1862 html_element.Blur(can_gc);
1864
1865 Ok(())
1866}
1867
1868pub(crate) fn handle_element_clear(
1870 documents: &DocumentCollection,
1871 pipeline: PipelineId,
1872 element_id: String,
1873 reply: GenericSender<Result<(), ErrorStatus>>,
1874 can_gc: CanGc,
1875) {
1876 reply
1877 .send(
1878 get_known_element(documents, pipeline, element_id).and_then(|element| {
1879 if !element_is_mutable_form_control(&element) {
1883 return Err(ErrorStatus::InvalidElementState);
1884 }
1885
1886 scroll_into_view(&element, documents, &pipeline, can_gc);
1888
1889 if !element.is_keyboard_interactable() {
1895 return Err(ErrorStatus::ElementNotInteractable);
1896 }
1897
1898 let paint_tree = get_element_pointer_interactable_paint_tree(
1899 &element,
1900 &documents
1901 .find_document(pipeline)
1902 .expect("Document existence guaranteed by `get_known_element`"),
1903 can_gc,
1904 );
1905 if !is_element_in_view(&element, &paint_tree) {
1906 return Err(ErrorStatus::ElementNotInteractable);
1907 }
1908
1909 clear_a_resettable_element(&element, can_gc)
1912 }),
1913 )
1914 .unwrap();
1915}
1916
1917fn get_option_parent(node: &Node) -> Option<DomRoot<Element>> {
1918 let mut candidate_select = None;
1926
1927 for ancestor in node.ancestors() {
1928 if ancestor.is::<HTMLDataListElement>() {
1929 return Some(DomRoot::downcast::<Element>(ancestor).unwrap());
1930 } else if candidate_select.is_none() && ancestor.is::<HTMLSelectElement>() {
1931 candidate_select = Some(ancestor);
1932 }
1933 }
1934
1935 candidate_select.map(|ancestor| DomRoot::downcast::<Element>(ancestor).unwrap())
1936}
1937
1938fn get_container(element: &Element) -> Option<DomRoot<Element>> {
1940 if element.is::<HTMLOptionElement>() {
1941 return get_option_parent(element.upcast::<Node>());
1942 }
1943 if element.is::<HTMLOptGroupElement>() {
1944 return get_option_parent(element.upcast::<Node>())
1945 .or_else(|| Some(DomRoot::from_ref(element)));
1946 }
1947 Some(DomRoot::from_ref(element))
1948}
1949
1950pub(crate) fn handle_element_click(
1952 documents: &DocumentCollection,
1953 pipeline: PipelineId,
1954 element_id: String,
1955 reply: GenericSender<Result<Option<String>, ErrorStatus>>,
1956 can_gc: CanGc,
1957) {
1958 reply
1959 .send(
1960 get_known_element(documents, pipeline, element_id).and_then(|element| {
1962 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1965 if matches!(*input_element.input_type(), InputType::File(_)) {
1966 return Err(ErrorStatus::InvalidArgument);
1967 }
1968 }
1969
1970 let Some(container) = get_container(&element) else {
1971 return Err(ErrorStatus::UnknownError);
1972 };
1973
1974 scroll_into_view(&container, documents, &pipeline, can_gc);
1976
1977 let paint_tree = get_element_pointer_interactable_paint_tree(
1980 &container,
1981 &documents
1982 .find_document(pipeline)
1983 .expect("Document existence guaranteed by `get_known_element`"),
1984 can_gc,
1985 );
1986
1987 if !is_element_in_view(&container, &paint_tree) {
1988 return Err(ErrorStatus::ElementNotInteractable);
1989 }
1990
1991 if !container
1998 .upcast::<Node>()
1999 .is_shadow_including_inclusive_ancestor_of(paint_tree[0].upcast::<Node>())
2000 {
2001 return Err(ErrorStatus::ElementClickIntercepted);
2002 }
2003
2004 match element.downcast::<HTMLOptionElement>() {
2006 Some(option_element) => {
2007 let event_target = container.upcast::<EventTarget>();
2009 event_target.fire_event(atom!("mouseover"), can_gc);
2010 event_target.fire_event(atom!("mousemove"), can_gc);
2011 event_target.fire_event(atom!("mousedown"), can_gc);
2012
2013 match container.downcast::<HTMLElement>() {
2015 Some(html_element) => {
2016 html_element.Focus(
2017 &FocusOptions {
2018 preventScroll: true,
2019 },
2020 can_gc,
2021 );
2022 },
2023 None => return Err(ErrorStatus::UnknownError),
2024 }
2025
2026 if !is_disabled(&element) {
2028 event_target.fire_event(atom!("input"), can_gc);
2030
2031 let previous_selectedness = option_element.Selected();
2033
2034 match container.downcast::<HTMLSelectElement>() {
2036 Some(select_element) => {
2037 if select_element.Multiple() {
2038 option_element
2039 .SetSelected(!option_element.Selected(), can_gc);
2040 }
2041 },
2042 None => option_element.SetSelected(true, can_gc),
2043 }
2044
2045 if !previous_selectedness {
2047 event_target.fire_event(atom!("change"), can_gc);
2048 }
2049 }
2050
2051 event_target.fire_event(atom!("mouseup"), can_gc);
2053 event_target.fire_event(atom!("click"), can_gc);
2054
2055 Ok(None)
2056 },
2057 None => Ok(Some(element.upcast::<Node>().unique_id(pipeline))),
2058 }
2059 }),
2060 )
2061 .unwrap();
2062}
2063
2064fn is_element_in_view(element: &Element, paint_tree: &[DomRoot<Element>]) -> bool {
2066 if !paint_tree.contains(&DomRoot::from_ref(element)) {
2069 return false;
2070 }
2071 use style::computed_values::pointer_events::T as PointerEvents;
2072 element
2076 .style()
2077 .is_none_or(|style| style.get_inherited_ui().pointer_events != PointerEvents::None)
2078}
2079
2080fn get_element_pointer_interactable_paint_tree(
2082 element: &Element,
2083 document: &Document,
2084 can_gc: CanGc,
2085) -> Vec<DomRoot<Element>> {
2086 if !element.is_connected() {
2089 return Vec::new();
2090 }
2091
2092 get_element_in_view_center_point(element, can_gc).map_or(Vec::new(), |center_point| {
2098 document.ElementsFromPoint(
2099 Finite::wrap(center_point.x as f64),
2100 Finite::wrap(center_point.y as f64),
2101 )
2102 })
2103}
2104
2105pub(crate) fn handle_is_enabled(
2107 documents: &DocumentCollection,
2108 pipeline: PipelineId,
2109 element_id: String,
2110 reply: GenericSender<Result<bool, ErrorStatus>>,
2111) {
2112 reply
2113 .send(
2114 get_known_element(documents, pipeline, element_id).map(|element| {
2116 let document = documents.find_document(pipeline).unwrap();
2118
2119 if document.is_html_document() || document.is_xhtml_document() {
2125 !is_disabled(&element)
2126 } else {
2127 false
2128 }
2129 }),
2130 )
2131 .unwrap();
2132}
2133
2134pub(crate) fn handle_is_selected(
2135 documents: &DocumentCollection,
2136 pipeline: PipelineId,
2137 element_id: String,
2138 reply: GenericSender<Result<bool, ErrorStatus>>,
2139) {
2140 reply
2141 .send(
2142 get_known_element(documents, pipeline, element_id).and_then(|element| {
2143 if let Some(input_element) = element.downcast::<HTMLInputElement>() {
2144 Ok(input_element.Checked())
2145 } else if let Some(option_element) = element.downcast::<HTMLOptionElement>() {
2146 Ok(option_element.Selected())
2147 } else if element.is::<HTMLElement>() {
2148 Ok(false) } else {
2150 Err(ErrorStatus::UnknownError)
2151 }
2152 }),
2153 )
2154 .unwrap();
2155}
2156
2157pub(crate) fn handle_add_load_status_sender(
2158 documents: &DocumentCollection,
2159 pipeline: PipelineId,
2160 reply: GenericSender<WebDriverLoadStatus>,
2161) {
2162 if let Some(document) = documents.find_document(pipeline) {
2163 let window = document.window();
2164 window.set_webdriver_load_status_sender(Some(reply));
2165 }
2166}
2167
2168pub(crate) fn handle_remove_load_status_sender(
2169 documents: &DocumentCollection,
2170 pipeline: PipelineId,
2171) {
2172 if let Some(document) = documents.find_document(pipeline) {
2173 let window = document.window();
2174 window.set_webdriver_load_status_sender(None);
2175 }
2176}
2177
2178fn scroll_into_view(
2180 element: &Element,
2181 documents: &DocumentCollection,
2182 pipeline: &PipelineId,
2183 can_gc: CanGc,
2184) {
2185 let paint_tree = get_element_pointer_interactable_paint_tree(
2187 element,
2188 &documents
2189 .find_document(*pipeline)
2190 .expect("Document existence guaranteed by `get_known_element`"),
2191 can_gc,
2192 );
2193 if is_element_in_view(element, &paint_tree) {
2194 return;
2195 }
2196
2197 let options = BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions(ScrollIntoViewOptions {
2202 parent: ScrollOptions {
2203 behavior: ScrollBehavior::Instant,
2204 },
2205 block: ScrollLogicalPosition::End,
2206 inline: ScrollLogicalPosition::Nearest,
2207 container: Default::default(),
2208 });
2209 element.ScrollIntoView(options);
2211}
2212
2213pub(crate) fn set_protocol_handler_automation_mode(
2214 documents: &DocumentCollection,
2215 pipeline: PipelineId,
2216 mode: CustomHandlersAutomationMode,
2217) {
2218 if let Some(document) = documents.find_document(pipeline) {
2219 document.set_protocol_handler_automation_mode(mode);
2220 }
2221}