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