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