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