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, 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, get_property, get_property_jsval,
63    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_window(
142    documents: &DocumentCollection,
143    pipeline: PipelineId,
144    webview_id: String,
145    reply: IpcSender<Result<(), ErrorStatus>>,
146) {
147    if reply
148        .send(
149            documents
150                .find_window(pipeline)
151                .map_or(Err(ErrorStatus::NoSuchWindow), |window| {
152                    let window_proxy = window.window_proxy();
153                    if window_proxy.browsing_context_id() != window_proxy.webview_id() ||
155                        window_proxy.webview_id().to_string() != webview_id
156                    {
157                        Err(ErrorStatus::NoSuchWindow)
158                    } else {
159                        Ok(())
160                    }
161                }),
162        )
163        .is_err()
164    {
165        error!("Webdriver get known window reply failed");
166    }
167}
168
169pub(crate) fn handle_get_known_shadow_root(
170    documents: &DocumentCollection,
171    pipeline: PipelineId,
172    shadow_root_id: String,
173    reply: IpcSender<Result<(), ErrorStatus>>,
174) {
175    let result = get_known_shadow_root(documents, pipeline, shadow_root_id).map(|_| ());
176    if reply.send(result).is_err() {
177        error!("Webdriver get known shadow root reply failed");
178    }
179}
180
181fn get_known_shadow_root(
183    documents: &DocumentCollection,
184    pipeline: PipelineId,
185    node_id: String,
186) -> Result<DomRoot<ShadowRoot>, ErrorStatus> {
187    let doc = documents
188        .find_document(pipeline)
189        .ok_or(ErrorStatus::NoSuchWindow)?;
190    if !ScriptThread::has_node_id(pipeline, &node_id) {
193        return Err(ErrorStatus::NoSuchShadowRoot);
194    }
195
196    let node = find_node_by_unique_id_in_document(&doc, node_id);
199
200    if let Some(ref node) = node {
203        if !node.is::<ShadowRoot>() {
204            return Err(ErrorStatus::NoSuchShadowRoot);
205        }
206    }
207
208    let Some(node) = node else {
210        return Err(ErrorStatus::DetachedShadowRoot);
211    };
212
213    let shadow_root = DomRoot::downcast::<ShadowRoot>(node).unwrap();
217    if is_detached(&shadow_root) {
218        return Err(ErrorStatus::DetachedShadowRoot);
219    }
220    Ok(shadow_root)
222}
223
224pub(crate) fn handle_get_known_element(
225    documents: &DocumentCollection,
226    pipeline: PipelineId,
227    element_id: String,
228    reply: IpcSender<Result<(), ErrorStatus>>,
229) {
230    let result = get_known_element(documents, pipeline, element_id).map(|_| ());
231    if reply.send(result).is_err() {
232        error!("Webdriver get known element reply failed");
233    }
234}
235
236fn get_known_element(
238    documents: &DocumentCollection,
239    pipeline: PipelineId,
240    node_id: String,
241) -> Result<DomRoot<Element>, ErrorStatus> {
242    let doc = documents
243        .find_document(pipeline)
244        .ok_or(ErrorStatus::NoSuchWindow)?;
245    if !ScriptThread::has_node_id(pipeline, &node_id) {
248        return Err(ErrorStatus::NoSuchElement);
249    }
250    let node = find_node_by_unique_id_in_document(&doc, node_id);
253
254    if let Some(ref node) = node {
257        if !node.is::<Element>() {
258            return Err(ErrorStatus::NoSuchElement);
259        }
260    }
261    let Some(node) = node else {
263        return Err(ErrorStatus::StaleElementReference);
264    };
265    let element = DomRoot::downcast::<Element>(node).unwrap();
267    if is_stale(&element) {
268        return Err(ErrorStatus::StaleElementReference);
269    }
270    Ok(element)
272}
273
274pub(crate) fn find_node_by_unique_id_in_document(
276    document: &Document,
277    node_id: String,
278) -> Option<DomRoot<Node>> {
279    let pipeline = document.window().pipeline_id();
280    document
281        .upcast::<Node>()
282        .traverse_preorder(ShadowIncluding::Yes)
283        .find(|node| node.unique_id(pipeline) == node_id)
284}
285
286fn matching_links(
288    links: &NodeList,
289    link_text: String,
290    partial: bool,
291) -> impl Iterator<Item = String> + '_ {
292    links
293        .iter()
294        .filter(move |node| {
295            let content = node
296                .downcast::<HTMLElement>()
297                .map(|element| element.InnerText())
298                .map_or("".to_owned(), String::from)
299                .trim()
300                .to_owned();
301            if partial {
302                content.contains(&link_text)
303            } else {
304                content == link_text
305            }
306        })
307        .map(|node| node.unique_id(node.owner_doc().window().pipeline_id()))
308}
309
310fn all_matching_links(
311    root_node: &Node,
312    link_text: String,
313    partial: bool,
314) -> Result<Vec<String>, ErrorStatus> {
315    root_node
319        .query_selector_all(DOMString::from("a"))
320        .map_err(|_| ErrorStatus::InvalidSelector)
321        .map(|nodes| matching_links(&nodes, link_text, partial).collect())
322}
323
324#[allow(unsafe_code)]
325fn object_has_to_json_property(
326    cx: SafeJSContext,
327    global_scope: &GlobalScope,
328    object: HandleObject,
329) -> bool {
330    let name = CString::new("toJSON").unwrap();
331    let mut found = false;
332    if unsafe { JS_HasOwnProperty(*cx, object, name.as_ptr(), &mut found) } && found {
333        rooted!(in(*cx) let mut value = UndefinedValue());
334        let result = unsafe { JS_GetProperty(*cx, object, name.as_ptr(), value.handle_mut()) };
335        if !result {
336            throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::note());
337            false
338        } else {
339            result && unsafe { JS_TypeOfValue(*cx, value.handle()) } == JSType::JSTYPE_FUNCTION
340        }
341    } else if unsafe { JS_IsExceptionPending(*cx) } {
342        throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::note());
343        false
344    } else {
345        false
346    }
347}
348
349#[allow(unsafe_code)]
350fn is_arguments_object(cx: SafeJSContext, value: HandleValue) -> bool {
352    rooted!(in(*cx) let class_name = unsafe { ToString(*cx, value) });
353    let Some(class_name) = NonNull::new(class_name.get()) else {
354        return false;
355    };
356    let class_name = unsafe { jsstr_to_string(*cx, class_name) };
357    class_name == "[object Arguments]"
358}
359
360#[derive(Clone, Eq, Hash, PartialEq)]
361struct HashableJSVal(u64);
362
363impl From<HandleValue<'_>> for HashableJSVal {
364    fn from(v: HandleValue<'_>) -> HashableJSVal {
365        HashableJSVal(v.get().asBits_)
366    }
367}
368
369pub(crate) fn jsval_to_webdriver(
371    cx: SafeJSContext,
372    global_scope: &GlobalScope,
373    val: HandleValue,
374    realm: InRealm,
375    can_gc: CanGc,
376) -> WebDriverJSResult {
377    let _aes = AutoEntryScript::new(global_scope);
378    let mut seen = HashSet::new();
379    let result = jsval_to_webdriver_inner(cx, global_scope, val, &mut seen);
380    if result.is_err() {
381        report_pending_exception(cx, true, realm, can_gc);
382    }
383    result
384}
385
386#[allow(unsafe_code)]
387fn jsval_to_webdriver_inner(
389    cx: SafeJSContext,
390    global_scope: &GlobalScope,
391    val: HandleValue,
392    seen: &mut HashSet<HashableJSVal>,
393) -> WebDriverJSResult {
394    let _ac = enter_realm(global_scope);
395    if val.get().is_undefined() {
396        Ok(JSValue::Undefined)
397    } else if val.get().is_null() {
398        Ok(JSValue::Null)
399    } else if val.get().is_boolean() {
400        Ok(JSValue::Boolean(val.get().to_boolean()))
401    } else if val.get().is_number() {
402        Ok(JSValue::Number(val.to_number()))
403    } else if val.get().is_string() {
404        let string = NonNull::new(val.to_string()).expect("Should have a non-Null String");
405        let string = unsafe { jsstr_to_string(*cx, string) };
406        Ok(JSValue::String(string))
407    } else if val.get().is_object() {
408        rooted!(in(*cx) let object = match unsafe { FromJSValConvertible::from_jsval(*cx, val, ())}.unwrap() {
409            ConversionResult::Success(object) => object,
410            _ => unreachable!(),
411        });
412        let _ac = JSAutoRealm::new(*cx, *object);
413
414        if let Ok(element) = unsafe { root_from_object::<Element>(*object, *cx) } {
415            if is_stale(&element) {
417                Err(JavaScriptEvaluationError::SerializationError(
418                    JavaScriptEvaluationResultSerializationError::StaleElementReference,
419                ))
420            } else {
421                Ok(JSValue::Element(
422                    element
423                        .upcast::<Node>()
424                        .unique_id(element.owner_window().pipeline_id()),
425                ))
426            }
427        } else if let Ok(shadow_root) = unsafe { root_from_object::<ShadowRoot>(*object, *cx) } {
428            if is_detached(&shadow_root) {
430                Err(JavaScriptEvaluationError::SerializationError(
431                    JavaScriptEvaluationResultSerializationError::DetachedShadowRoot,
432                ))
433            } else {
434                Ok(JSValue::ShadowRoot(
435                    shadow_root
436                        .upcast::<Node>()
437                        .unique_id(shadow_root.owner_window().pipeline_id()),
438                ))
439            }
440        } else if let Ok(window) = unsafe { root_from_object::<Window>(*object, *cx) } {
441            let window_proxy = window.window_proxy();
442            if window_proxy.is_browsing_context_discarded() {
443                Err(JavaScriptEvaluationError::SerializationError(
444                    JavaScriptEvaluationResultSerializationError::StaleElementReference,
445                ))
446            } else if window_proxy.browsing_context_id() == window_proxy.webview_id() {
447                Ok(JSValue::Window(window.webview_id().to_string()))
448            } else {
449                Ok(JSValue::Frame(
450                    window_proxy.browsing_context_id().to_string(),
451                ))
452            }
453        } else if object_has_to_json_property(cx, global_scope, object.handle()) {
454            let name = CString::new("toJSON").unwrap();
455            rooted!(in(*cx) let mut value = UndefinedValue());
456            let call_result = unsafe {
457                JS_CallFunctionName(
458                    *cx,
459                    object.handle(),
460                    name.as_ptr(),
461                    &HandleValueArray::empty(),
462                    value.handle_mut(),
463                )
464            };
465
466            if call_result {
467                Ok(jsval_to_webdriver_inner(
468                    cx,
469                    global_scope,
470                    value.handle(),
471                    seen,
472                )?)
473            } else {
474                throw_dom_exception(cx, global_scope, Error::JSFailed, CanGc::note());
475                Err(JavaScriptEvaluationError::SerializationError(
476                    JavaScriptEvaluationResultSerializationError::OtherJavaScriptError,
477                ))
478            }
479        } else {
480            clone_an_object(cx, global_scope, val, seen, object.handle())
481        }
482    } else {
483        Err(JavaScriptEvaluationError::SerializationError(
484            JavaScriptEvaluationResultSerializationError::UnknownType,
485        ))
486    }
487}
488
489#[allow(unsafe_code)]
490fn 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                        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 = jsval_to_webdriver_inner(cx, global_scope, property.handle(), seen)?;
615                result.insert(name.into(), value);
616            }
617        }
618        Ok(JSValue::Object(result))
619    };
620    seen.remove(&hashable);
622    return_val
624}
625
626pub(crate) fn handle_execute_script(
627    window: Option<DomRoot<Window>>,
628    eval: String,
629    reply: IpcSender<WebDriverJSResult>,
630    can_gc: CanGc,
631) {
632    match window {
633        Some(window) => {
634            let cx = window.get_cx();
635            let realm = AlreadyInRealm::assert_for_cx(cx);
636            let realm = InRealm::already(&realm);
637
638            rooted!(in(*cx) let mut rval = UndefinedValue());
639            let global = window.as_global_scope();
640            let evaluation_result = global.evaluate_js_on_global_with_result(
641                &eval,
642                rval.handle_mut(),
643                ScriptFetchOptions::default_classic_script(global),
644                global.api_base_url(),
645                can_gc,
646                None, );
648
649            let result = evaluation_result
650                .and_then(|_| jsval_to_webdriver(cx, global, rval.handle(), realm, can_gc));
651
652            reply.send(result).unwrap_or_else(|err| {
653                error!("ExecuteScript Failed to send reply: {err}");
654            });
655        },
656        None => reply
657            .send(Err(JavaScriptEvaluationError::DocumentNotFound))
658            .unwrap_or_else(|err| {
659                error!("ExecuteScript Failed to send reply: {err}");
660            }),
661    }
662}
663
664pub(crate) fn handle_execute_async_script(
665    window: Option<DomRoot<Window>>,
666    eval: String,
667    reply: IpcSender<WebDriverJSResult>,
668    can_gc: CanGc,
669) {
670    match window {
671        Some(window) => {
672            let cx = window.get_cx();
673            let reply_sender = reply.clone();
674            window.set_webdriver_script_chan(Some(reply));
675            rooted!(in(*cx) let mut rval = UndefinedValue());
676
677            let global_scope = window.as_global_scope();
678            if let Err(error) = global_scope.evaluate_js_on_global_with_result(
679                &eval,
680                rval.handle_mut(),
681                ScriptFetchOptions::default_classic_script(global_scope),
682                global_scope.api_base_url(),
683                can_gc,
684                None, ) {
686                reply_sender.send(Err(error)).unwrap_or_else(|error| {
687                    error!("ExecuteAsyncScript Failed to send reply: {error}");
688                });
689            }
690        },
691        None => {
692            reply
693                .send(Err(JavaScriptEvaluationError::DocumentNotFound))
694                .unwrap_or_else(|error| {
695                    error!("ExecuteAsyncScript Failed to send reply: {error}");
696                });
697        },
698    }
699}
700
701pub(crate) fn handle_get_parent_frame_id(
703    documents: &DocumentCollection,
704    pipeline: PipelineId,
705    reply: IpcSender<Result<BrowsingContextId, ErrorStatus>>,
706) {
707    reply
710        .send(
711            documents
712                .find_window(pipeline)
713                .and_then(|window| {
714                    window
715                        .window_proxy()
716                        .parent()
717                        .map(|parent| parent.browsing_context_id())
718                })
719                .ok_or(ErrorStatus::NoSuchWindow),
720        )
721        .unwrap();
722}
723
724pub(crate) fn handle_get_browsing_context_id(
726    documents: &DocumentCollection,
727    pipeline: PipelineId,
728    webdriver_frame_id: WebDriverFrameId,
729    reply: IpcSender<Result<BrowsingContextId, ErrorStatus>>,
730) {
731    reply
732        .send(match webdriver_frame_id {
733            WebDriverFrameId::Short(id) => {
734                documents
737                    .find_document(pipeline)
738                    .ok_or(ErrorStatus::NoSuchWindow)
739                    .and_then(|document| {
740                        document
741                            .iframes()
742                            .iter()
743                            .nth(id as usize)
744                            .and_then(|iframe| iframe.browsing_context_id())
745                            .ok_or(ErrorStatus::NoSuchFrame)
746                    })
747            },
748            WebDriverFrameId::Element(element_id) => {
749                get_known_element(documents, pipeline, element_id).and_then(|element| {
750                    element
751                        .downcast::<HTMLIFrameElement>()
752                        .and_then(|element| element.browsing_context_id())
753                        .ok_or(ErrorStatus::NoSuchFrame)
754                })
755            },
756        })
757        .unwrap();
758}
759
760fn get_element_in_view_center_point(element: &Element, can_gc: CanGc) -> Option<Point2D<i64>> {
762    let doc = element.owner_document();
763    element.GetClientRects(can_gc).first().map(|rectangle| {
766        let x = rectangle.X();
767        let y = rectangle.Y();
768        let width = rectangle.Width();
769        let height = rectangle.Height();
770        debug!(
771            "get_element_in_view_center_point: Element rectangle at \
772            (x: {x}, y: {y}, width: {width}, height: {height})",
773        );
774        let window = doc.window();
775        let left = (x.min(x + width)).max(0.0);
777        let right = f64::min(window.InnerWidth() as f64, x.max(x + width));
779        let top = (y.min(y + height)).max(0.0);
781        let bottom = f64::min(window.InnerHeight() as f64, y.max(y + height));
784        debug!(
785            "get_element_in_view_center_point: Computed rectangle is \
786            (left: {left}, right: {right}, top: {top}, bottom: {bottom})",
787        );
788        let center_x = ((left + right) / 2.0).floor() as i64;
790        let center_y = ((top + bottom) / 2.0).floor() as i64;
792
793        debug!(
794            "get_element_in_view_center_point: Element center point at ({center_x}, {center_y})",
795        );
796        Point2D::new(center_x, center_y)
798    })
799}
800
801pub(crate) fn handle_get_element_in_view_center_point(
802    documents: &DocumentCollection,
803    pipeline: PipelineId,
804    element_id: String,
805    reply: IpcSender<Result<Option<(i64, i64)>, ErrorStatus>>,
806    can_gc: CanGc,
807) {
808    reply
809        .send(
810            get_known_element(documents, pipeline, element_id).map(|element| {
811                get_element_in_view_center_point(&element, can_gc).map(|point| (point.x, point.y))
812            }),
813        )
814        .unwrap();
815}
816
817fn retrieve_document_and_check_root_existence(
818    documents: &DocumentCollection,
819    pipeline: PipelineId,
820) -> Result<DomRoot<Document>, ErrorStatus> {
821    let document = documents
822        .find_document(pipeline)
823        .ok_or(ErrorStatus::NoSuchWindow)?;
824
825    if document.GetDocumentElement().is_none() {
830        Err(ErrorStatus::NoSuchElement)
831    } else {
832        Ok(document)
833    }
834}
835
836pub(crate) fn handle_find_elements_css_selector(
837    documents: &DocumentCollection,
838    pipeline: PipelineId,
839    selector: String,
840    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
841) {
842    match retrieve_document_and_check_root_existence(documents, pipeline) {
843        Ok(document) => reply
844            .send(
845                document
846                    .QuerySelectorAll(DOMString::from(selector))
847                    .map_err(|_| ErrorStatus::InvalidSelector)
848                    .map(|nodes| {
849                        nodes
850                            .iter()
851                            .map(|x| x.upcast::<Node>().unique_id(pipeline))
852                            .collect()
853                    }),
854            )
855            .unwrap(),
856        Err(error) => reply.send(Err(error)).unwrap(),
857    }
858}
859
860pub(crate) fn handle_find_elements_link_text(
861    documents: &DocumentCollection,
862    pipeline: PipelineId,
863    selector: String,
864    partial: bool,
865    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
866) {
867    match retrieve_document_and_check_root_existence(documents, pipeline) {
868        Ok(document) => reply
869            .send(all_matching_links(
870                document.upcast::<Node>(),
871                selector.clone(),
872                partial,
873            ))
874            .unwrap(),
875        Err(error) => reply.send(Err(error)).unwrap(),
876    }
877}
878
879pub(crate) fn handle_find_elements_tag_name(
880    documents: &DocumentCollection,
881    pipeline: PipelineId,
882    selector: String,
883    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
884    can_gc: CanGc,
885) {
886    match retrieve_document_and_check_root_existence(documents, pipeline) {
887        Ok(document) => reply
888            .send(Ok(document
889                .GetElementsByTagName(DOMString::from(selector), can_gc)
890                .elements_iter()
891                .map(|x| x.upcast::<Node>().unique_id(pipeline))
892                .collect::<Vec<String>>()))
893            .unwrap(),
894        Err(error) => reply.send(Err(error)).unwrap(),
895    }
896}
897
898fn find_elements_xpath_strategy(
900    document: &Document,
901    start_node: &Node,
902    selector: String,
903    pipeline: PipelineId,
904    can_gc: CanGc,
905) -> Result<Vec<String>, ErrorStatus> {
906    let evaluate_result = match document.Evaluate(
911        DOMString::from(selector),
912        start_node,
913        None,
914        XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
915        None,
916        can_gc,
917    ) {
918        Ok(res) => res,
919        Err(_) => return Err(ErrorStatus::InvalidSelector),
920    };
921    let length = match evaluate_result.GetSnapshotLength() {
927        Ok(len) => len,
928        Err(_) => return Err(ErrorStatus::InvalidSelector),
929    };
930
931    let mut result = Vec::new();
933
934    for index in 0..length {
936        let node = match evaluate_result.SnapshotItem(index) {
939            Ok(node) => node.expect(
940                "Node should always exist as ORDERED_NODE_SNAPSHOT_TYPE \
941                                gives static result and we verified the length!",
942            ),
943            Err(_) => return Err(ErrorStatus::InvalidSelector),
944        };
945
946        if !node.is::<Element>() {
948            return Err(ErrorStatus::InvalidSelector);
949        }
950
951        result.push(node.unique_id(pipeline));
953    }
954    Ok(result)
956}
957
958pub(crate) fn handle_find_elements_xpath_selector(
959    documents: &DocumentCollection,
960    pipeline: PipelineId,
961    selector: String,
962    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
963    can_gc: CanGc,
964) {
965    match retrieve_document_and_check_root_existence(documents, pipeline) {
966        Ok(document) => reply
967            .send(find_elements_xpath_strategy(
968                &document,
969                document.upcast::<Node>(),
970                selector,
971                pipeline,
972                can_gc,
973            ))
974            .unwrap(),
975        Err(error) => reply.send(Err(error)).unwrap(),
976    }
977}
978
979pub(crate) fn handle_find_element_elements_css_selector(
980    documents: &DocumentCollection,
981    pipeline: PipelineId,
982    element_id: String,
983    selector: String,
984    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
985) {
986    reply
987        .send(
988            get_known_element(documents, pipeline, element_id).and_then(|element| {
989                element
990                    .upcast::<Node>()
991                    .query_selector_all(DOMString::from(selector))
992                    .map_err(|_| ErrorStatus::InvalidSelector)
993                    .map(|nodes| {
994                        nodes
995                            .iter()
996                            .map(|x| x.upcast::<Node>().unique_id(pipeline))
997                            .collect()
998                    })
999            }),
1000        )
1001        .unwrap();
1002}
1003
1004pub(crate) fn handle_find_element_elements_link_text(
1005    documents: &DocumentCollection,
1006    pipeline: PipelineId,
1007    element_id: String,
1008    selector: String,
1009    partial: bool,
1010    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1011) {
1012    reply
1013        .send(
1014            get_known_element(documents, pipeline, element_id).and_then(|element| {
1015                all_matching_links(element.upcast::<Node>(), selector.clone(), partial)
1016            }),
1017        )
1018        .unwrap();
1019}
1020
1021pub(crate) fn handle_find_element_elements_tag_name(
1022    documents: &DocumentCollection,
1023    pipeline: PipelineId,
1024    element_id: String,
1025    selector: String,
1026    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1027    can_gc: CanGc,
1028) {
1029    reply
1030        .send(
1031            get_known_element(documents, pipeline, element_id).map(|element| {
1032                element
1033                    .GetElementsByTagName(DOMString::from(selector), can_gc)
1034                    .elements_iter()
1035                    .map(|x| x.upcast::<Node>().unique_id(pipeline))
1036                    .collect::<Vec<String>>()
1037            }),
1038        )
1039        .unwrap();
1040}
1041
1042pub(crate) fn handle_find_element_elements_xpath_selector(
1043    documents: &DocumentCollection,
1044    pipeline: PipelineId,
1045    element_id: String,
1046    selector: String,
1047    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1048    can_gc: CanGc,
1049) {
1050    reply
1051        .send(
1052            get_known_element(documents, pipeline, element_id).and_then(|element| {
1053                find_elements_xpath_strategy(
1054                    &documents
1055                        .find_document(pipeline)
1056                        .expect("Document existence guaranteed by `get_known_element`"),
1057                    element.upcast::<Node>(),
1058                    selector,
1059                    pipeline,
1060                    can_gc,
1061                )
1062            }),
1063        )
1064        .unwrap();
1065}
1066
1067pub(crate) fn handle_find_shadow_elements_css_selector(
1069    documents: &DocumentCollection,
1070    pipeline: PipelineId,
1071    shadow_root_id: String,
1072    selector: String,
1073    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1074) {
1075    reply
1076        .send(
1077            get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1078                shadow_root
1079                    .upcast::<Node>()
1080                    .query_selector_all(DOMString::from(selector))
1081                    .map_err(|_| ErrorStatus::InvalidSelector)
1082                    .map(|nodes| {
1083                        nodes
1084                            .iter()
1085                            .map(|x| x.upcast::<Node>().unique_id(pipeline))
1086                            .collect()
1087                    })
1088            }),
1089        )
1090        .unwrap();
1091}
1092
1093pub(crate) fn handle_find_shadow_elements_link_text(
1094    documents: &DocumentCollection,
1095    pipeline: PipelineId,
1096    shadow_root_id: String,
1097    selector: String,
1098    partial: bool,
1099    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1100) {
1101    reply
1102        .send(
1103            get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1104                all_matching_links(shadow_root.upcast::<Node>(), selector.clone(), partial)
1105            }),
1106        )
1107        .unwrap();
1108}
1109
1110pub(crate) fn handle_find_shadow_elements_tag_name(
1111    documents: &DocumentCollection,
1112    pipeline: PipelineId,
1113    shadow_root_id: String,
1114    selector: String,
1115    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1116) {
1117    reply
1123        .send(
1124            get_known_shadow_root(documents, pipeline, shadow_root_id).map(|shadow_root| {
1125                shadow_root
1126                    .upcast::<Node>()
1127                    .query_selector_all(DOMString::from(selector))
1128                    .map(|nodes| {
1129                        nodes
1130                            .iter()
1131                            .map(|x| x.upcast::<Node>().unique_id(pipeline))
1132                            .collect()
1133                    })
1134                    .unwrap_or_default()
1135            }),
1136        )
1137        .unwrap();
1138}
1139
1140pub(crate) fn handle_find_shadow_elements_xpath_selector(
1141    documents: &DocumentCollection,
1142    pipeline: PipelineId,
1143    shadow_root_id: String,
1144    selector: String,
1145    reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
1146    can_gc: CanGc,
1147) {
1148    reply
1149        .send(
1150            get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
1151                find_elements_xpath_strategy(
1152                    &documents
1153                        .find_document(pipeline)
1154                        .expect("Document existence guaranteed by `get_known_shadow_root`"),
1155                    shadow_root.upcast::<Node>(),
1156                    selector,
1157                    pipeline,
1158                    can_gc,
1159                )
1160            }),
1161        )
1162        .unwrap();
1163}
1164
1165pub(crate) fn handle_get_element_shadow_root(
1167    documents: &DocumentCollection,
1168    pipeline: PipelineId,
1169    element_id: String,
1170    reply: IpcSender<Result<Option<String>, ErrorStatus>>,
1171) {
1172    reply
1173        .send(
1174            get_known_element(documents, pipeline, element_id).map(|element| {
1175                element
1176                    .shadow_root()
1177                    .map(|x| x.upcast::<Node>().unique_id(pipeline))
1178            }),
1179        )
1180        .unwrap();
1181}
1182
1183fn is_keyboard_interactable(element: &Element) -> bool {
1185    element.is_focusable_area() || element.is::<HTMLBodyElement>() || element.is_document_element()
1186}
1187
1188fn handle_send_keys_file(
1189    file_input: &HTMLInputElement,
1190    text: &str,
1191    reply_sender: IpcSender<Result<bool, ErrorStatus>>,
1192) {
1193    let files: Vec<DOMString> = text
1198        .split("\n")
1199        .filter_map(|string| {
1200            if string.is_empty() {
1201                None
1202            } else {
1203                Some(string.into())
1204            }
1205        })
1206        .collect();
1207
1208    if files.is_empty() {
1210        let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1211        return;
1212    }
1213
1214    if !file_input.Multiple() && files.len() > 1 {
1218        let _ = reply_sender.send(Err(ErrorStatus::InvalidArgument));
1219        return;
1220    }
1221
1222    file_input.select_files_for_webdriver(files, reply_sender);
1231}
1232
1233fn handle_send_keys_non_typeable(
1235    input_element: &HTMLInputElement,
1236    text: &str,
1237    can_gc: CanGc,
1238) -> Result<bool, ErrorStatus> {
1239    if !input_element.is_mutable() {
1246        return Err(ErrorStatus::ElementNotInteractable);
1247    }
1248
1249    if let Err(error) = input_element.SetValue(text.into(), can_gc) {
1251        error!(
1252            "Failed to set value on non-typeable input element: {:?}",
1253            error
1254        );
1255        return Err(ErrorStatus::UnknownError);
1256    }
1257
1258    if input_element
1260        .Validity(can_gc)
1261        .invalid_flags()
1262        .contains(ValidationFlags::BAD_INPUT)
1263    {
1264        return Err(ErrorStatus::InvalidArgument);
1265    }
1266
1267    Ok(false)
1270}
1271
1272pub(crate) fn handle_will_send_keys(
1278    documents: &DocumentCollection,
1279    pipeline: PipelineId,
1280    element_id: String,
1281    text: String,
1282    strict_file_interactability: bool,
1283    reply: IpcSender<Result<bool, ErrorStatus>>,
1284    can_gc: CanGc,
1285) {
1286    let element = match get_known_element(documents, pipeline, element_id) {
1288        Ok(element) => element,
1289        Err(error) => {
1290            let _ = reply.send(Err(error));
1291            return;
1292        },
1293    };
1294
1295    let input_element = element.downcast::<HTMLInputElement>();
1296    let mut element_has_focus = false;
1297
1298    let is_file_input = input_element.is_some_and(|e| e.input_type() == InputType::File);
1301
1302    if !is_file_input || strict_file_interactability {
1304        scroll_into_view(&element, documents, &pipeline, can_gc);
1306
1307        if !is_keyboard_interactable(&element) {
1313            let _ = reply.send(Err(ErrorStatus::ElementNotInteractable));
1314            return;
1315        }
1316
1317        let Some(html_element) = element.downcast::<HTMLElement>() else {
1320            let _ = reply.send(Err(ErrorStatus::UnknownError));
1321            return;
1322        };
1323
1324        if !element.is_active_element() {
1325            html_element.Focus(
1326                &FocusOptions {
1327                    preventScroll: true,
1328                },
1329                can_gc,
1330            );
1331        } else {
1332            element_has_focus = element.focus_state();
1333        }
1334    }
1335
1336    if let Some(input_element) = input_element {
1337        if is_file_input {
1339            handle_send_keys_file(input_element, &text, reply);
1340            return;
1341        }
1342
1343        if input_element.is_nontypeable() {
1345            let _ = reply.send(handle_send_keys_non_typeable(input_element, &text, can_gc));
1346            return;
1347        }
1348    }
1349
1350    if !element_has_focus {
1358        if let Some(input_element) = input_element {
1359            let length = input_element.Value().len() as u32;
1360            let _ = input_element.SetSelectionRange(length, length, None);
1361        } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1362            let length = textarea_element.Value().len() as u32;
1363            let _ = textarea_element.SetSelectionRange(length, length, None);
1364        }
1365    }
1366
1367    let _ = reply.send(Ok(true));
1368}
1369
1370pub(crate) fn handle_get_active_element(
1371    documents: &DocumentCollection,
1372    pipeline: PipelineId,
1373    reply: IpcSender<Option<String>>,
1374) {
1375    reply
1376        .send(
1377            documents
1378                .find_document(pipeline)
1379                .and_then(|document| document.GetActiveElement())
1380                .map(|element| element.upcast::<Node>().unique_id(pipeline)),
1381        )
1382        .unwrap();
1383}
1384
1385pub(crate) fn handle_get_computed_role(
1386    documents: &DocumentCollection,
1387    pipeline: PipelineId,
1388    node_id: String,
1389    reply: IpcSender<Result<Option<String>, ErrorStatus>>,
1390) {
1391    reply
1392        .send(
1393            get_known_element(documents, pipeline, node_id)
1394                .map(|element| element.GetRole().map(String::from)),
1395        )
1396        .unwrap();
1397}
1398
1399pub(crate) fn handle_get_page_source(
1400    documents: &DocumentCollection,
1401    pipeline: PipelineId,
1402    reply: IpcSender<Result<String, ErrorStatus>>,
1403    can_gc: CanGc,
1404) {
1405    reply
1406        .send(
1407            documents
1408                .find_document(pipeline)
1409                .ok_or(ErrorStatus::UnknownError)
1410                .and_then(|document| match document.GetDocumentElement() {
1411                    Some(element) => match element.outer_html(can_gc) {
1412                        Ok(source) => Ok(source.to_string()),
1413                        Err(_) => {
1414                            match XMLSerializer::new(document.window(), None, can_gc)
1415                                .SerializeToString(element.upcast::<Node>())
1416                            {
1417                                Ok(source) => Ok(source.to_string()),
1418                                Err(_) => Err(ErrorStatus::UnknownError),
1419                            }
1420                        },
1421                    },
1422                    None => Err(ErrorStatus::UnknownError),
1423                }),
1424        )
1425        .unwrap();
1426}
1427
1428pub(crate) fn handle_get_cookies(
1429    documents: &DocumentCollection,
1430    pipeline: PipelineId,
1431    reply: IpcSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1432) {
1433    reply
1434        .send(
1435            match documents.find_document(pipeline) {
1437                Some(document) => {
1438                    let url = document.url();
1439                    let (sender, receiver) = ipc::channel().unwrap();
1440                    let _ = document
1441                        .window()
1442                        .as_global_scope()
1443                        .resource_threads()
1444                        .send(GetCookiesDataForUrl(url, sender, NonHTTP));
1445                    Ok(receiver.recv().unwrap())
1446                },
1447                None => Ok(Vec::new()),
1448            },
1449        )
1450        .unwrap();
1451}
1452
1453pub(crate) fn handle_get_cookie(
1455    documents: &DocumentCollection,
1456    pipeline: PipelineId,
1457    name: String,
1458    reply: IpcSender<Result<Vec<Serde<Cookie<'static>>>, ErrorStatus>>,
1459) {
1460    reply
1461        .send(
1462            match documents.find_document(pipeline) {
1464                Some(document) => {
1465                    let url = document.url();
1466                    let (sender, receiver) = ipc::channel().unwrap();
1467                    let _ = document
1468                        .window()
1469                        .as_global_scope()
1470                        .resource_threads()
1471                        .send(GetCookiesDataForUrl(url, sender, NonHTTP));
1472                    let cookies = receiver.recv().unwrap();
1473                    Ok(cookies
1474                        .into_iter()
1475                        .filter(|cookie| cookie.name() == &*name)
1476                        .collect())
1477                },
1478                None => Ok(Vec::new()),
1479            },
1480        )
1481        .unwrap();
1482}
1483
1484pub(crate) fn handle_add_cookie(
1486    documents: &DocumentCollection,
1487    pipeline: PipelineId,
1488    cookie: Cookie<'static>,
1489    reply: IpcSender<Result<(), ErrorStatus>>,
1490) {
1491    let document = match documents.find_document(pipeline) {
1493        Some(document) => document,
1494        None => {
1495            return reply.send(Err(ErrorStatus::UnableToSetCookie)).unwrap();
1496        },
1497    };
1498    let url = document.url();
1499    let method = if cookie.http_only().unwrap_or(false) {
1500        HTTP
1501    } else {
1502        NonHTTP
1503    };
1504
1505    let domain = cookie.domain().map(ToOwned::to_owned);
1506    reply
1507        .send(match (document.is_cookie_averse(), domain) {
1508            (true, _) => Err(ErrorStatus::InvalidCookieDomain),
1509            (false, Some(ref domain)) if url.host_str().is_some_and(|host| host == domain) => {
1510                let _ = document
1511                    .window()
1512                    .as_global_scope()
1513                    .resource_threads()
1514                    .send(SetCookieForUrl(url, Serde(cookie), method));
1515                Ok(())
1516            },
1517            (false, None) => {
1518                let _ = document
1519                    .window()
1520                    .as_global_scope()
1521                    .resource_threads()
1522                    .send(SetCookieForUrl(url, Serde(cookie), method));
1523                Ok(())
1524            },
1525            (_, _) => Err(ErrorStatus::UnableToSetCookie),
1526        })
1527        .unwrap();
1528}
1529
1530pub(crate) fn handle_delete_cookies(
1532    documents: &DocumentCollection,
1533    pipeline: PipelineId,
1534    reply: IpcSender<Result<(), ErrorStatus>>,
1535) {
1536    let document = match documents.find_document(pipeline) {
1537        Some(document) => document,
1538        None => {
1539            return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1540        },
1541    };
1542    let url = document.url();
1543    document
1544        .window()
1545        .as_global_scope()
1546        .resource_threads()
1547        .send(DeleteCookies(url))
1548        .unwrap();
1549    reply.send(Ok(())).unwrap();
1550}
1551
1552pub(crate) fn handle_delete_cookie(
1554    documents: &DocumentCollection,
1555    pipeline: PipelineId,
1556    name: String,
1557    reply: IpcSender<Result<(), ErrorStatus>>,
1558) {
1559    let document = match documents.find_document(pipeline) {
1560        Some(document) => document,
1561        None => {
1562            return reply.send(Err(ErrorStatus::UnknownError)).unwrap();
1563        },
1564    };
1565    let url = document.url();
1566    document
1567        .window()
1568        .as_global_scope()
1569        .resource_threads()
1570        .send(DeleteCookie(url, name))
1571        .unwrap();
1572    reply.send(Ok(())).unwrap();
1573}
1574
1575pub(crate) fn handle_get_title(
1576    documents: &DocumentCollection,
1577    pipeline: PipelineId,
1578    reply: IpcSender<String>,
1579) {
1580    reply
1581        .send(
1582            documents
1584                .find_document(pipeline)
1585                .map(|document| String::from(document.Title()))
1586                .unwrap_or_default(),
1587        )
1588        .unwrap();
1589}
1590
1591fn calculate_absolute_position(
1593    documents: &DocumentCollection,
1594    pipeline: &PipelineId,
1595    rect: &DOMRect,
1596) -> Result<(f64, f64), ErrorStatus> {
1597    let document = match documents.find_document(*pipeline) {
1602        Some(document) => document,
1603        None => return Err(ErrorStatus::UnknownError),
1604    };
1605    let win = match document.GetDefaultView() {
1606        Some(win) => win,
1607        None => return Err(ErrorStatus::UnknownError),
1608    };
1609
1610    let x = win.ScrollX() as f64 + rect.X();
1612    let y = win.ScrollY() as f64 + rect.Y();
1613
1614    Ok((x, y))
1615}
1616
1617pub(crate) fn handle_get_rect(
1619    documents: &DocumentCollection,
1620    pipeline: PipelineId,
1621    element_id: String,
1622    reply: IpcSender<Result<Rect<f64>, ErrorStatus>>,
1623    can_gc: CanGc,
1624) {
1625    reply
1626        .send(
1627            get_known_element(documents, pipeline, element_id).and_then(|element| {
1628                let rect = element.GetBoundingClientRect(can_gc);
1632                let (x, y) = calculate_absolute_position(documents, &pipeline, &rect)?;
1633
1634                Ok(Rect::new(
1636                    Point2D::new(x, y),
1637                    Size2D::new(rect.Width(), rect.Height()),
1638                ))
1639            }),
1640        )
1641        .unwrap();
1642}
1643
1644pub(crate) fn handle_scroll_and_get_bounding_client_rect(
1645    documents: &DocumentCollection,
1646    pipeline: PipelineId,
1647    element_id: String,
1648    reply: IpcSender<Result<Rect<f32>, ErrorStatus>>,
1649    can_gc: CanGc,
1650) {
1651    reply
1652        .send(
1653            get_known_element(documents, pipeline, element_id).map(|element| {
1654                scroll_into_view(&element, documents, &pipeline, can_gc);
1655
1656                let rect = element.GetBoundingClientRect(can_gc);
1657                Rect::new(
1658                    Point2D::new(rect.X() as f32, rect.Y() as f32),
1659                    Size2D::new(rect.Width() as f32, rect.Height() as f32),
1660                )
1661            }),
1662        )
1663        .unwrap();
1664}
1665
1666pub(crate) fn handle_get_text(
1668    documents: &DocumentCollection,
1669    pipeline: PipelineId,
1670    node_id: String,
1671    reply: IpcSender<Result<String, ErrorStatus>>,
1672) {
1673    reply
1674        .send(
1675            get_known_element(documents, pipeline, node_id).map(|element| {
1676                element
1677                    .downcast::<HTMLElement>()
1678                    .map(|htmlelement| htmlelement.InnerText().to_string())
1679                    .unwrap_or_else(|| {
1680                        element
1681                            .upcast::<Node>()
1682                            .GetTextContent()
1683                            .map_or("".to_owned(), String::from)
1684                    })
1685            }),
1686        )
1687        .unwrap();
1688}
1689
1690pub(crate) fn handle_get_name(
1691    documents: &DocumentCollection,
1692    pipeline: PipelineId,
1693    node_id: String,
1694    reply: IpcSender<Result<String, ErrorStatus>>,
1695) {
1696    reply
1697        .send(
1698            get_known_element(documents, pipeline, node_id)
1699                .map(|element| String::from(element.TagName())),
1700        )
1701        .unwrap();
1702}
1703
1704pub(crate) fn handle_get_attribute(
1705    documents: &DocumentCollection,
1706    pipeline: PipelineId,
1707    node_id: String,
1708    name: String,
1709    reply: IpcSender<Result<Option<String>, ErrorStatus>>,
1710) {
1711    reply
1712        .send(
1713            get_known_element(documents, pipeline, node_id).map(|element| {
1714                if is_boolean_attribute(&name) {
1715                    if element.HasAttribute(DOMString::from(name)) {
1716                        Some(String::from("true"))
1717                    } else {
1718                        None
1719                    }
1720                } else {
1721                    element
1722                        .GetAttribute(DOMString::from(name))
1723                        .map(String::from)
1724                }
1725            }),
1726        )
1727        .unwrap();
1728}
1729
1730#[allow(unsafe_code)]
1731pub(crate) fn handle_get_property(
1732    documents: &DocumentCollection,
1733    pipeline: PipelineId,
1734    node_id: String,
1735    name: String,
1736    reply: IpcSender<Result<JSValue, ErrorStatus>>,
1737    can_gc: CanGc,
1738) {
1739    reply
1740        .send(
1741            get_known_element(documents, pipeline, node_id).map(|element| {
1742                let document = documents.find_document(pipeline).unwrap();
1743                let realm = enter_realm(&*document);
1744                let cx = document.window().get_cx();
1745
1746                rooted!(in(*cx) let mut property = UndefinedValue());
1747                match get_property_jsval(
1748                    cx,
1749                    element.reflector().get_jsobject(),
1750                    &name,
1751                    property.handle_mut(),
1752                ) {
1753                    Ok(_) => {
1754                        match jsval_to_webdriver(
1755                            cx,
1756                            &element.global(),
1757                            property.handle(),
1758                            InRealm::entered(&realm),
1759                            can_gc,
1760                        ) {
1761                            Ok(property) => property,
1762                            Err(_) => JSValue::Undefined,
1763                        }
1764                    },
1765                    Err(error) => {
1766                        throw_dom_exception(cx, &element.global(), error, can_gc);
1767                        JSValue::Undefined
1768                    },
1769                }
1770            }),
1771        )
1772        .unwrap();
1773}
1774
1775pub(crate) fn handle_get_css(
1776    documents: &DocumentCollection,
1777    pipeline: PipelineId,
1778    node_id: String,
1779    name: String,
1780    reply: IpcSender<Result<String, ErrorStatus>>,
1781) {
1782    reply
1783        .send(
1784            get_known_element(documents, pipeline, node_id).map(|element| {
1785                let window = element.owner_window();
1786                String::from(
1787                    window
1788                        .GetComputedStyle(&element, None)
1789                        .GetPropertyValue(DOMString::from(name)),
1790                )
1791            }),
1792        )
1793        .unwrap();
1794}
1795
1796pub(crate) fn handle_get_url(
1797    documents: &DocumentCollection,
1798    pipeline: PipelineId,
1799    reply: IpcSender<ServoUrl>,
1800    _can_gc: CanGc,
1801) {
1802    reply
1803        .send(
1804            documents
1806                .find_document(pipeline)
1807                .map(|document| document.url())
1808                .unwrap_or_else(|| ServoUrl::parse("about:blank").expect("infallible")),
1809        )
1810        .unwrap();
1811}
1812
1813fn element_is_mutable_form_control(element: &Element) -> bool {
1815    if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1816        input_element.is_mutable() &&
1817            matches!(
1818                input_element.input_type(),
1819                InputType::Text |
1820                    InputType::Search |
1821                    InputType::Url |
1822                    InputType::Tel |
1823                    InputType::Email |
1824                    InputType::Password |
1825                    InputType::Date |
1826                    InputType::Month |
1827                    InputType::Week |
1828                    InputType::Time |
1829                    InputType::DatetimeLocal |
1830                    InputType::Number |
1831                    InputType::Range |
1832                    InputType::Color |
1833                    InputType::File
1834            )
1835    } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1836        textarea_element.is_mutable()
1837    } else {
1838        false
1839    }
1840}
1841
1842fn clear_a_resettable_element(element: &Element, can_gc: CanGc) -> Result<(), ErrorStatus> {
1844    let html_element = element
1845        .downcast::<HTMLElement>()
1846        .ok_or(ErrorStatus::UnknownError)?;
1847
1848    if html_element.is_candidate_for_constraint_validation() {
1851        if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1852            if input_element.Value().is_empty() {
1853                return Ok(());
1854            }
1855        } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1856            if textarea_element.Value().is_empty() {
1857                return Ok(());
1858            }
1859        }
1860    }
1861
1862    html_element.Focus(
1864        &FocusOptions {
1865            preventScroll: true,
1866        },
1867        can_gc,
1868    );
1869
1870    if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1872        input_element.clear(can_gc);
1873    } else if let Some(textarea_element) = element.downcast::<HTMLTextAreaElement>() {
1874        textarea_element.clear();
1875    } else {
1876        unreachable!("We have confirm previously that element is mutable form control");
1877    }
1878
1879    let event_target = element.upcast::<EventTarget>();
1880    event_target.fire_bubbling_event(atom!("input"), can_gc);
1881    event_target.fire_bubbling_event(atom!("change"), can_gc);
1882
1883    html_element.Blur(can_gc);
1885
1886    Ok(())
1887}
1888
1889pub(crate) fn handle_element_clear(
1891    documents: &DocumentCollection,
1892    pipeline: PipelineId,
1893    element_id: String,
1894    reply: IpcSender<Result<(), ErrorStatus>>,
1895    can_gc: CanGc,
1896) {
1897    reply
1898        .send(
1899            get_known_element(documents, pipeline, element_id).and_then(|element| {
1900                if !element_is_mutable_form_control(&element) {
1904                    return Err(ErrorStatus::InvalidElementState);
1905                }
1906
1907                scroll_into_view(&element, documents, &pipeline, can_gc);
1909
1910                clear_a_resettable_element(&element, can_gc)
1916            }),
1917        )
1918        .unwrap();
1919}
1920
1921fn get_option_parent(node: &Node) -> Option<DomRoot<Element>> {
1922    let mut candidate_select = None;
1930
1931    for ancestor in node.ancestors() {
1932        if ancestor.is::<HTMLDataListElement>() {
1933            return Some(DomRoot::downcast::<Element>(ancestor).unwrap());
1934        } else if candidate_select.is_none() && ancestor.is::<HTMLSelectElement>() {
1935            candidate_select = Some(ancestor);
1936        }
1937    }
1938
1939    candidate_select.map(|ancestor| DomRoot::downcast::<Element>(ancestor).unwrap())
1940}
1941
1942fn get_container(element: &Element) -> Option<DomRoot<Element>> {
1944    if element.is::<HTMLOptionElement>() {
1945        return get_option_parent(element.upcast::<Node>());
1946    }
1947    if element.is::<HTMLOptGroupElement>() {
1948        return get_option_parent(element.upcast::<Node>())
1949            .or_else(|| Some(DomRoot::from_ref(element)));
1950    }
1951    Some(DomRoot::from_ref(element))
1952}
1953
1954pub(crate) fn handle_element_click(
1956    documents: &DocumentCollection,
1957    pipeline: PipelineId,
1958    element_id: String,
1959    reply: IpcSender<Result<Option<String>, ErrorStatus>>,
1960    can_gc: CanGc,
1961) {
1962    reply
1963        .send(
1964            get_known_element(documents, pipeline, element_id).and_then(|element| {
1966                if let Some(input_element) = element.downcast::<HTMLInputElement>() {
1969                    if input_element.input_type() == InputType::File {
1970                        return Err(ErrorStatus::InvalidArgument);
1971                    }
1972                }
1973
1974                let Some(container) = get_container(&element) else {
1975                    return Err(ErrorStatus::UnknownError);
1976                };
1977
1978                scroll_into_view(&container, documents, &pipeline, can_gc);
1980
1981                let paint_tree = get_element_pointer_interactable_paint_tree(
1984                    &container,
1985                    &documents
1986                        .find_document(pipeline)
1987                        .expect("Document existence guaranteed by `get_known_element`"),
1988                    can_gc,
1989                );
1990
1991                if !is_element_in_view(&container, &paint_tree) {
1992                    return Err(ErrorStatus::ElementNotInteractable);
1993                }
1994
1995                if !container
2002                    .upcast::<Node>()
2003                    .is_shadow_including_inclusive_ancestor_of(paint_tree[0].upcast::<Node>())
2004                {
2005                    return Err(ErrorStatus::ElementClickIntercepted);
2006                }
2007
2008                match element.downcast::<HTMLOptionElement>() {
2010                    Some(option_element) => {
2011                        let event_target = container.upcast::<EventTarget>();
2013                        event_target.fire_event(atom!("mouseover"), can_gc);
2014                        event_target.fire_event(atom!("mousemove"), can_gc);
2015                        event_target.fire_event(atom!("mousedown"), can_gc);
2016
2017                        match container.downcast::<HTMLElement>() {
2019                            Some(html_element) => {
2020                                html_element.Focus(
2021                                    &FocusOptions {
2022                                        preventScroll: true,
2023                                    },
2024                                    can_gc,
2025                                );
2026                            },
2027                            None => return Err(ErrorStatus::UnknownError),
2028                        }
2029
2030                        if !is_disabled(&element) {
2032                            event_target.fire_event(atom!("input"), can_gc);
2034
2035                            let previous_selectedness = option_element.Selected();
2037
2038                            match container.downcast::<HTMLSelectElement>() {
2040                                Some(select_element) => {
2041                                    if select_element.Multiple() {
2042                                        option_element.SetSelected(!option_element.Selected());
2043                                    }
2044                                },
2045                                None => option_element.SetSelected(true),
2046                            }
2047
2048                            if !previous_selectedness {
2050                                event_target.fire_event(atom!("change"), can_gc);
2051                            }
2052                        }
2053
2054                        event_target.fire_event(atom!("mouseup"), can_gc);
2056                        event_target.fire_event(atom!("click"), can_gc);
2057
2058                        Ok(None)
2059                    },
2060                    None => Ok(Some(element.upcast::<Node>().unique_id(pipeline))),
2061                }
2062            }),
2063        )
2064        .unwrap();
2065}
2066
2067fn is_element_in_view(element: &Element, paint_tree: &[DomRoot<Element>]) -> bool {
2069    if !paint_tree.contains(&DomRoot::from_ref(element)) {
2072        return false;
2073    }
2074    use style::computed_values::pointer_events::T as PointerEvents;
2075    element
2079        .style()
2080        .is_none_or(|style| style.get_inherited_ui().pointer_events != PointerEvents::None)
2081}
2082
2083fn get_element_pointer_interactable_paint_tree(
2085    element: &Element,
2086    document: &Document,
2087    can_gc: CanGc,
2088) -> Vec<DomRoot<Element>> {
2089    if !element.is_connected() {
2092        return Vec::new();
2093    }
2094
2095    get_element_in_view_center_point(element, can_gc).map_or(Vec::new(), |center_point| {
2101        document.ElementsFromPoint(
2102            Finite::wrap(center_point.x as f64),
2103            Finite::wrap(center_point.y as f64),
2104        )
2105    })
2106}
2107
2108pub(crate) fn handle_is_enabled(
2110    documents: &DocumentCollection,
2111    pipeline: PipelineId,
2112    element_id: String,
2113    reply: IpcSender<Result<bool, ErrorStatus>>,
2114) {
2115    reply
2116        .send(
2117            get_known_element(documents, pipeline, element_id).map(|element| {
2119                let document = documents.find_document(pipeline).unwrap();
2121
2122                if document.is_html_document() || document.is_xhtml_document() {
2128                    !is_disabled(&element)
2129                } else {
2130                    false
2131                }
2132            }),
2133        )
2134        .unwrap();
2135}
2136
2137pub(crate) fn handle_is_selected(
2138    documents: &DocumentCollection,
2139    pipeline: PipelineId,
2140    element_id: String,
2141    reply: IpcSender<Result<bool, ErrorStatus>>,
2142) {
2143    reply
2144        .send(
2145            get_known_element(documents, pipeline, element_id).and_then(|element| {
2146                if let Some(input_element) = element.downcast::<HTMLInputElement>() {
2147                    Ok(input_element.Checked())
2148                } else if let Some(option_element) = element.downcast::<HTMLOptionElement>() {
2149                    Ok(option_element.Selected())
2150                } else if element.is::<HTMLElement>() {
2151                    Ok(false) } else {
2153                    Err(ErrorStatus::UnknownError)
2154                }
2155            }),
2156        )
2157        .unwrap();
2158}
2159
2160pub(crate) fn handle_add_load_status_sender(
2161    documents: &DocumentCollection,
2162    pipeline: PipelineId,
2163    reply: GenericSender<WebDriverLoadStatus>,
2164) {
2165    if let Some(document) = documents.find_document(pipeline) {
2166        let window = document.window();
2167        window.set_webdriver_load_status_sender(Some(reply));
2168    }
2169}
2170
2171pub(crate) fn handle_remove_load_status_sender(
2172    documents: &DocumentCollection,
2173    pipeline: PipelineId,
2174) {
2175    if let Some(document) = documents.find_document(pipeline) {
2176        let window = document.window();
2177        window.set_webdriver_load_status_sender(None);
2178    }
2179}
2180
2181fn scroll_into_view(
2183    element: &Element,
2184    documents: &DocumentCollection,
2185    pipeline: &PipelineId,
2186    can_gc: CanGc,
2187) {
2188    let paint_tree = get_element_pointer_interactable_paint_tree(
2190        element,
2191        &documents
2192            .find_document(*pipeline)
2193            .expect("Document existence guaranteed by `get_known_element`"),
2194        can_gc,
2195    );
2196    if is_element_in_view(element, &paint_tree) {
2197        return;
2198    }
2199
2200    let options = BooleanOrScrollIntoViewOptions::ScrollIntoViewOptions(ScrollIntoViewOptions {
2205        parent: ScrollOptions {
2206            behavior: ScrollBehavior::Instant,
2207        },
2208        block: ScrollLogicalPosition::End,
2209        inline: ScrollLogicalPosition::Nearest,
2210        container: Default::default(),
2211    });
2212    element.ScrollIntoView(options);
2214}