script/dom/
windowproxy.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::ptr;
7use std::rc::Rc;
8
9use base::generic_channel;
10use base::generic_channel::GenericSend;
11use base::id::{BrowsingContextId, PipelineId, WebViewId};
12use constellation_traits::{
13    AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
14    ScriptToConstellationMessage,
15};
16use content_security_policy::sandboxing_directive::SandboxingFlagSet;
17use dom_struct::dom_struct;
18use html5ever::local_name;
19use indexmap::map::IndexMap;
20use ipc_channel::ipc;
21use js::JSCLASS_IS_GLOBAL;
22use js::glue::{
23    CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
24    ProxyTraps, SetProxyReservedSlot,
25};
26use js::jsapi::{
27    GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
28    HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
29    JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
30    JS_HasPropertyById, JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSObject,
31    JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer, MutableHandle as RawMutableHandle,
32    MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
33    ObjectOpResult, PropertyDescriptor,
34};
35use js::jsval::{NullValue, PrivateValue, UndefinedValue};
36use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
37use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
38use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
39use net_traits::request::Referrer;
40use script_bindings::reflector::MutDomObject;
41use script_traits::NewPipelineInfo;
42use serde::{Deserialize, Serialize};
43use servo_url::{ImmutableOrigin, ServoUrl};
44use storage_traits::webstorage_thread::WebStorageThreadMsg;
45use style::attr::parse_integer;
46
47use crate::dom::bindings::cell::DomRefCell;
48use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
49use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
50use crate::dom::bindings::inheritance::Castable;
51use crate::dom::bindings::proxyhandler::set_property_descriptor;
52use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
53use crate::dom::bindings::root::{Dom, DomRoot};
54use crate::dom::bindings::str::{DOMString, USVString};
55use crate::dom::bindings::trace::JSTraceable;
56use crate::dom::bindings::utils::get_array_index_from_id;
57use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
58use crate::dom::document::Document;
59use crate::dom::element::Element;
60use crate::dom::globalscope::GlobalScope;
61use crate::dom::window::Window;
62use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
63use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
64use crate::script_thread::{ScriptThread, with_script_thread};
65use crate::script_window_proxies::ScriptWindowProxies;
66
67#[dom_struct]
68// NOTE: the browsing context for a window is managed in two places:
69// here, in script, but also in the constellation. The constellation
70// manages the session history, which in script is accessed through
71// History objects, messaging the constellation.
72pub(crate) struct WindowProxy {
73    /// The JS WindowProxy object.
74    /// Unlike other reflectors, we mutate this field because
75    /// we have to brain-transplant the reflector when the WindowProxy
76    /// changes Window.
77    reflector: Reflector,
78
79    /// The id of the browsing context.
80    /// In the case that this is a nested browsing context, this is the id
81    /// of the container.
82    #[no_trace]
83    browsing_context_id: BrowsingContextId,
84
85    // https://html.spec.whatwg.org/multipage/#opener-browsing-context
86    #[no_trace]
87    opener: Option<BrowsingContextId>,
88
89    /// The frame id of the top-level ancestor browsing context.
90    /// In the case that this is a top-level window, this is our id.
91    #[no_trace]
92    webview_id: WebViewId,
93
94    /// The name of the browsing context (sometimes, but not always,
95    /// equal to the name of a container element)
96    name: DomRefCell<DOMString>,
97    /// The pipeline id of the currently active document.
98    /// May be None, when the currently active document is in another script thread.
99    /// We do not try to keep the pipeline id for documents in other threads,
100    /// as this would require the constellation notifying many script threads about
101    /// the change, which could be expensive.
102    #[no_trace]
103    currently_active: Cell<Option<PipelineId>>,
104
105    /// Has the browsing context been discarded?
106    discarded: Cell<bool>,
107
108    /// Has the browsing context been disowned?
109    disowned: Cell<bool>,
110
111    /// <https://html.spec.whatwg.org/multipage/#is-closing>
112    is_closing: Cell<bool>,
113
114    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
115    /// this will be the [`Element`] of the `<iframe>` element in the realm of the
116    /// parent page. Otherwise, it is `None`.
117    frame_element: Option<Dom<Element>>,
118
119    /// The parent browsing context's window proxy, if this is a nested browsing context
120    parent: Option<Dom<WindowProxy>>,
121
122    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
123    delaying_load_events_mode: Cell<bool>,
124
125    /// The creator browsing context's url.
126    #[no_trace]
127    creator_url: Option<ServoUrl>,
128
129    /// The creator browsing context's origin.
130    #[no_trace]
131    creator_origin: Option<ImmutableOrigin>,
132
133    /// The window proxies the script thread knows.
134    #[conditional_malloc_size_of]
135    script_window_proxies: Rc<ScriptWindowProxies>,
136}
137
138impl WindowProxy {
139    fn new_inherited(
140        browsing_context_id: BrowsingContextId,
141        webview_id: WebViewId,
142        currently_active: Option<PipelineId>,
143        frame_element: Option<&Element>,
144        parent: Option<&WindowProxy>,
145        opener: Option<BrowsingContextId>,
146        creator: CreatorBrowsingContextInfo,
147    ) -> WindowProxy {
148        let name = frame_element.map_or(DOMString::new(), |e| {
149            e.get_string_attribute(&local_name!("name"))
150        });
151        WindowProxy {
152            reflector: Reflector::new(),
153            browsing_context_id,
154            webview_id,
155            name: DomRefCell::new(name),
156            currently_active: Cell::new(currently_active),
157            discarded: Cell::new(false),
158            disowned: Cell::new(false),
159            is_closing: Cell::new(false),
160            frame_element: frame_element.map(Dom::from_ref),
161            parent: parent.map(Dom::from_ref),
162            delaying_load_events_mode: Cell::new(false),
163            opener,
164            creator_url: creator.url,
165            creator_origin: creator.origin,
166            script_window_proxies: ScriptThread::window_proxies(),
167        }
168    }
169
170    #[expect(unsafe_code)]
171    pub(crate) fn new(
172        window: &Window,
173        browsing_context_id: BrowsingContextId,
174        webview_id: WebViewId,
175        frame_element: Option<&Element>,
176        parent: Option<&WindowProxy>,
177        opener: Option<BrowsingContextId>,
178        creator: CreatorBrowsingContextInfo,
179    ) -> DomRoot<WindowProxy> {
180        unsafe {
181            let handler = window.windowproxy_handler();
182
183            let cx = GlobalScope::get_cx();
184            let window_jsobject = window.reflector().get_jsobject();
185            assert!(!window_jsobject.get().is_null());
186            assert_ne!(
187                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
188                0
189            );
190            let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
191
192            // Create a new window proxy.
193            rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
194            assert!(!js_proxy.is_null());
195
196            // Create a new browsing context.
197
198            let current = Some(window.upcast::<GlobalScope>().pipeline_id());
199            let window_proxy = Box::new(WindowProxy::new_inherited(
200                browsing_context_id,
201                webview_id,
202                current,
203                frame_element,
204                parent,
205                opener,
206                creator,
207            ));
208
209            // The window proxy owns the browsing context.
210            // When we finalize the window proxy, it drops the browsing context it owns.
211            SetProxyReservedSlot(
212                js_proxy.get(),
213                0,
214                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
215            );
216
217            // Notify the JS engine about the new window proxy binding.
218            SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
219
220            // Set the reflector.
221            debug!(
222                "Initializing reflector of {:p} to {:p}.",
223                window_proxy,
224                js_proxy.get()
225            );
226            window_proxy
227                .reflector
228                .init_reflector::<WindowProxy>(js_proxy.get());
229            DomRoot::from_ref(&*Box::into_raw(window_proxy))
230        }
231    }
232
233    #[expect(unsafe_code)]
234    pub(crate) fn new_dissimilar_origin(
235        global_to_clone_from: &GlobalScope,
236        browsing_context_id: BrowsingContextId,
237        webview_id: WebViewId,
238        parent: Option<&WindowProxy>,
239        opener: Option<BrowsingContextId>,
240        creator: CreatorBrowsingContextInfo,
241    ) -> DomRoot<WindowProxy> {
242        unsafe {
243            let handler = WindowProxyHandler::x_origin_proxy_handler();
244
245            let cx = GlobalScope::get_cx();
246
247            // Create a new browsing context.
248            let window_proxy = Box::new(WindowProxy::new_inherited(
249                browsing_context_id,
250                webview_id,
251                None,
252                None,
253                parent,
254                opener,
255                creator,
256            ));
257
258            // Create a new dissimilar-origin window.
259            let window = DissimilarOriginWindow::new(global_to_clone_from, &window_proxy);
260            let window_jsobject = window.reflector().get_jsobject();
261            assert!(!window_jsobject.get().is_null());
262            assert_ne!(
263                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
264                0
265            );
266            let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
267
268            // Create a new window proxy.
269            rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
270            assert!(!js_proxy.is_null());
271
272            // The window proxy owns the browsing context.
273            // When we finalize the window proxy, it drops the browsing context it owns.
274            SetProxyReservedSlot(
275                js_proxy.get(),
276                0,
277                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
278            );
279
280            // Notify the JS engine about the new window proxy binding.
281            SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
282
283            // Set the reflector.
284            debug!(
285                "Initializing reflector of {:p} to {:p}.",
286                window_proxy,
287                js_proxy.get()
288            );
289            window_proxy
290                .reflector
291                .init_reflector::<WindowProxy>(js_proxy.get());
292            DomRoot::from_ref(&*Box::into_raw(window_proxy))
293        }
294    }
295
296    /// <https://html.spec.whatwg.org/multipage/#auxiliary-browsing-context>
297    fn create_auxiliary_browsing_context(
298        &self,
299        name: DOMString,
300        noopener: bool,
301    ) -> Option<DomRoot<WindowProxy>> {
302        let (response_sender, response_receiver) = ipc::channel().unwrap();
303        let window = self
304            .currently_active
305            .get()
306            .and_then(ScriptThread::find_document)
307            .map(|doc| DomRoot::from_ref(doc.window()))
308            .unwrap();
309
310        let document = self
311            .currently_active
312            .get()
313            .and_then(ScriptThread::find_document)
314            .expect("A WindowProxy creating an auxiliary to have an active document");
315        let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
316        let load_data = LoadData::new(
317            LoadOrigin::Script(document.origin().snapshot()),
318            blank_url,
319            Some(document.base_url()),
320            // This has the effect of ensuring that the new `about:blank` URL has the
321            // same origin as the `Document` that is creating the new browsing context.
322            Some(window.pipeline_id()),
323            document.global().get_referrer(),
324            document.get_referrer_policy(),
325            None, // Doesn't inherit secure context
326            None,
327            false,
328            // There are no sandboxing restrictions when creating auxiliary browsing contexts.
329            SandboxingFlagSet::empty(),
330        );
331        let load_info = AuxiliaryWebViewCreationRequest {
332            load_data: load_data.clone(),
333            opener_webview_id: window.webview_id(),
334            opener_pipeline_id: self.currently_active.get().unwrap(),
335            response_sender,
336        };
337        let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
338        window.send_to_constellation(constellation_msg);
339
340        let response = response_receiver.recv().unwrap()?;
341        let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
342        let new_pipeline_info = NewPipelineInfo {
343            parent_info: None,
344            new_pipeline_id: response.new_pipeline_id,
345            browsing_context_id: new_browsing_context_id,
346            webview_id: response.new_webview_id,
347            opener: Some(self.browsing_context_id),
348            load_data,
349            viewport_details: window.viewport_details(),
350            user_content_manager_id: response.user_content_manager_id,
351            // Use the current `WebView`'s theme initially, but the embedder may
352            // change this later.
353            theme: window.theme(),
354        };
355
356        with_script_thread(|script_thread| {
357            script_thread.spawn_pipeline(new_pipeline_info);
358        });
359
360        let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
361            .and_then(|doc| doc.browsing_context())?;
362        if name.to_lowercase() != "_blank" {
363            new_window_proxy.set_name(name);
364        }
365        if noopener {
366            new_window_proxy.disown();
367        } else {
368            // After creating a new auxiliary browsing context and document,
369            // the session storage is copied over.
370            // See https://html.spec.whatwg.org/multipage/#the-sessionstorage-attribute
371
372            let (sender, receiver) = generic_channel::channel().unwrap();
373
374            let msg = WebStorageThreadMsg::Clone {
375                sender,
376                src: window.window_proxy().webview_id(),
377                dest: response.new_webview_id,
378            };
379
380            GenericSend::send(document.global().storage_threads(), msg).unwrap();
381            receiver.recv().unwrap();
382        }
383        Some(new_window_proxy)
384    }
385
386    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
387    pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
388        self.delaying_load_events_mode.get()
389    }
390
391    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
392    pub(crate) fn start_delaying_load_events_mode(&self) {
393        self.delaying_load_events_mode.set(true);
394    }
395
396    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
397    pub(crate) fn stop_delaying_load_events_mode(&self) {
398        self.delaying_load_events_mode.set(false);
399        if let Some(document) = self.document() {
400            if !document.loader().events_inhibited() {
401                ScriptThread::mark_document_with_no_blocked_loads(&document);
402            }
403        }
404    }
405
406    // https://html.spec.whatwg.org/multipage/#disowned-its-opener
407    pub(crate) fn disown(&self) {
408        self.disowned.set(true);
409    }
410
411    /// <https://html.spec.whatwg.org/multipage/#dom-window-close>
412    /// Step 3.1, set BCs `is_closing` to true.
413    pub(crate) fn close(&self) {
414        self.is_closing.set(true);
415    }
416
417    /// <https://html.spec.whatwg.org/multipage/#is-closing>
418    pub(crate) fn is_closing(&self) -> bool {
419        self.is_closing.get()
420    }
421
422    #[expect(unsafe_code)]
423    // https://html.spec.whatwg.org/multipage/#dom-opener
424    pub(crate) fn opener(
425        &self,
426        cx: *mut JSContext,
427        in_realm_proof: InRealm,
428        mut retval: MutableHandleValue,
429    ) {
430        if self.disowned.get() {
431            return retval.set(NullValue());
432        }
433        let opener_id = match self.opener {
434            Some(opener_browsing_context_id) => opener_browsing_context_id,
435            None => return retval.set(NullValue()),
436        };
437        let parent_browsing_context = self.parent.as_deref();
438        let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
439            Some(window_proxy) => window_proxy,
440            None => {
441                let sender_pipeline_id = self.currently_active().unwrap();
442                match ScriptThread::get_top_level_for_browsing_context(
443                    self.webview_id(),
444                    sender_pipeline_id,
445                    opener_id,
446                ) {
447                    Some(opener_top_id) => {
448                        let global_to_clone_from =
449                            unsafe { GlobalScope::from_context(cx, in_realm_proof) };
450                        let creator =
451                            CreatorBrowsingContextInfo::from(parent_browsing_context, None);
452                        WindowProxy::new_dissimilar_origin(
453                            &global_to_clone_from,
454                            opener_id,
455                            opener_top_id,
456                            None,
457                            None,
458                            creator,
459                        )
460                    },
461                    None => return retval.set(NullValue()),
462                }
463            },
464        };
465        if opener_proxy.is_browsing_context_discarded() {
466            return retval.set(NullValue());
467        }
468        unsafe { opener_proxy.to_jsval(cx, retval) };
469    }
470
471    // https://html.spec.whatwg.org/multipage/#window-open-steps
472    pub(crate) fn open(
473        &self,
474        url: USVString,
475        target: DOMString,
476        features: DOMString,
477        can_gc: CanGc,
478    ) -> Fallible<Option<DomRoot<WindowProxy>>> {
479        // Note: this does not map to the spec,
480        // but it does prevent a panic at the constellation because the browsing context
481        // has already been discarded.
482        // See issue: #39716 for the original problem,
483        // and https://github.com/whatwg/html/issues/11797 for a discussion at the level of the spec.
484        if self.discarded.get() {
485            return Ok(None);
486        }
487        // Step 5. If target is the empty string, then set target to "_blank".
488        let non_empty_target = if target.is_empty() {
489            DOMString::from("_blank")
490        } else {
491            target
492        };
493        // Step 6. Let tokenizedFeatures be the result of tokenizing features.
494        let tokenized_features = tokenize_open_features(features);
495        // Step 7 - 8.
496        // If tokenizedFeatures["noreferrer"] exists, then set noreferrer to
497        // the result of parsing tokenizedFeatures["noreferrer"] as a boolean feature.
498        let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
499
500        // Step 9. Let noopener be the result of getting noopener for window
501        // open with sourceDocument, tokenizedFeatures, and urlRecord.
502        let noopener = if noreferrer {
503            true
504        } else {
505            parse_open_feature_boolean(&tokenized_features, "noopener")
506        };
507        // (TODO) Step 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"].
508
509        // (TODO) Step 11. Let referrerPolicy be the empty string.
510        // (TODO) Step 12. If noreferrer is true, then set referrerPolicy to "no-referrer".
511
512        // Step 13 - 14
513        // Let targetNavigable and windowType be the result of applying the rules for
514        // choosing a navigable given target, sourceDocument's node navigable, and noopener.
515        // If targetNavigable is null, then return null.
516        let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
517            (Some(chosen), new) => (chosen, new),
518            (None, _) => return Ok(None),
519        };
520        // TODO Step 15.2, Set up browsing context features for targetNavigable's
521        // active browsing context given tokenizedFeatures.
522        let target_document = match chosen.document() {
523            Some(target_document) => target_document,
524            None => return Ok(None),
525        };
526        let has_trustworthy_ancestor_origin = if new {
527            target_document.has_trustworthy_ancestor_or_current_origin()
528        } else {
529            false
530        };
531        let target_window = target_document.window();
532        // Step 15.3 and 15.4 will have happened elsewhere,
533        // since we've created a new browsing context and loaded it with about:blank.
534        if !url.is_empty() {
535            let existing_document = self
536                .currently_active
537                .get()
538                .and_then(ScriptThread::find_document)
539                .unwrap();
540            let url = match existing_document.url().join(&url) {
541                Ok(url) => url,
542                Err(_) => return Err(Error::Syntax(None)),
543            };
544            let referrer = if noreferrer {
545                Referrer::NoReferrer
546            } else {
547                target_window.as_global_scope().get_referrer()
548            };
549            // Propagate CSP list and about-base-url from opener to new document
550            let csp_list = existing_document.get_csp_list();
551            target_document.set_csp_list(csp_list);
552
553            // Step 15.5 Otherwise, navigate targetNavigable to urlRecord using sourceDocument,
554            // with referrerPolicy set to referrerPolicy and exceptionsEnabled set to true.
555            // FIXME: referrerPolicy may not be used properly here. exceptionsEnabled not used.
556            let mut load_data = LoadData::new(
557                LoadOrigin::Script(existing_document.origin().snapshot()),
558                url,
559                target_document.about_base_url(),
560                Some(target_window.pipeline_id()),
561                referrer,
562                target_document.get_referrer_policy(),
563                Some(target_window.as_global_scope().is_secure_context()),
564                Some(target_document.insecure_requests_policy()),
565                has_trustworthy_ancestor_origin,
566                target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
567            );
568
569            // Handle javascript: URLs specially to report CSP violations to the source window
570            // https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
571            if load_data.url.scheme() == "javascript" {
572                let existing_global = existing_document.global();
573
574                // Check CSP and report violations to the source (existing) window
575                if !ScriptThread::can_navigate_to_javascript_url(
576                    &existing_global,
577                    target_window.as_global_scope(),
578                    &mut load_data,
579                    None,
580                    can_gc,
581                ) {
582                    // CSP blocked the navigation, don't proceed
583                    return Ok(target_document.browsing_context());
584                }
585            }
586
587            let history_handling = if new {
588                NavigationHistoryBehavior::Replace
589            } else {
590                NavigationHistoryBehavior::Push
591            };
592            target_window.load_url(history_handling, false, load_data, can_gc);
593        }
594        // Step 17 (Dis-owning has been done in create_auxiliary_browsing_context).
595        if noopener {
596            return Ok(None);
597        }
598        // Step 18
599        Ok(target_document.browsing_context())
600    }
601
602    // https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
603    pub(crate) fn choose_browsing_context(
604        &self,
605        name: DOMString,
606        noopener: bool,
607    ) -> (Option<DomRoot<WindowProxy>>, bool) {
608        match name.to_lowercase().as_ref() {
609            "" | "_self" => {
610                // Step 3.
611                (Some(DomRoot::from_ref(self)), false)
612            },
613            "_parent" => {
614                // Step 4
615                if let Some(parent) = self.parent() {
616                    return (Some(DomRoot::from_ref(parent)), false);
617                }
618                (None, false)
619            },
620            "_top" => {
621                // Step 5
622                (Some(DomRoot::from_ref(self.top())), false)
623            },
624            "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
625            _ => {
626                // Step 6.
627                // TODO: expand the search to all 'familiar' bc,
628                // including auxiliaries familiar by way of their opener.
629                // See https://html.spec.whatwg.org/multipage/#familiar-with
630                match ScriptThread::find_window_proxy_by_name(&name) {
631                    Some(proxy) => (Some(proxy), false),
632                    None => (self.create_auxiliary_browsing_context(name, noopener), true),
633                }
634            },
635        }
636    }
637
638    pub(crate) fn is_auxiliary(&self) -> bool {
639        self.opener.is_some()
640    }
641
642    pub(crate) fn discard_browsing_context(&self) {
643        self.discarded.set(true);
644    }
645
646    pub(crate) fn is_browsing_context_discarded(&self) -> bool {
647        self.discarded.get()
648    }
649
650    pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
651        self.browsing_context_id
652    }
653
654    pub(crate) fn webview_id(&self) -> WebViewId {
655        self.webview_id
656    }
657
658    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
659    /// this will return an [`Element`] of the `<iframe>` element in the realm of the parent
660    /// page.
661    pub(crate) fn frame_element(&self) -> Option<&Element> {
662        self.frame_element.as_deref()
663    }
664
665    pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
666        self.currently_active
667            .get()
668            .and_then(ScriptThread::find_document)
669    }
670
671    pub(crate) fn parent(&self) -> Option<&WindowProxy> {
672        self.parent.as_deref()
673    }
674
675    pub(crate) fn top(&self) -> &WindowProxy {
676        let mut result = self;
677        while let Some(parent) = result.parent() {
678            result = parent;
679        }
680        result
681    }
682
683    /// Run [the focusing steps] with this browsing context.
684    ///
685    /// [the focusing steps]: https://html.spec.whatwg.org/multipage/#focusing-steps
686    pub fn focus(&self) {
687        debug!(
688            "Requesting the constellation to initiate a focus operation for \
689            browsing context {}",
690            self.browsing_context_id()
691        );
692        self.global()
693            .script_to_constellation_chan()
694            .send(ScriptToConstellationMessage::FocusRemoteDocument(
695                self.browsing_context_id(),
696            ))
697            .unwrap();
698    }
699
700    #[expect(unsafe_code)]
701    /// Change the Window that this WindowProxy resolves to.
702    // TODO: support setting the window proxy to a dummy value,
703    // to handle the case when the active document is in another script thread.
704    fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
705        unsafe {
706            debug!("Setting window of {:p}.", self);
707
708            let cx = GlobalScope::get_cx();
709            let window_jsobject = window.reflector().get_jsobject();
710            let old_js_proxy = self.reflector.get_jsobject();
711            assert!(!window_jsobject.get().is_null());
712            assert_ne!(
713                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
714                0
715            );
716            let _ac = enter_realm(window);
717
718            // The old window proxy no longer owns this browsing context.
719            SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
720
721            // Brain transplant the window proxy. Brain transplantation is
722            // usually done to move a window proxy between compartments, but
723            // that's not what we are doing here. We need to do this just
724            // because we want to replace the wrapper's `ProxyTraps`, but we
725            // don't want to update its identity.
726            rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
727            // Explicitly set this slot to a null pointer in case a GC occurs before we
728            // are ready to set it to a real value.
729            SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
730            debug!(
731                "Transplanting proxy from {:p} to {:p}.",
732                old_js_proxy.get(),
733                new_js_proxy.get()
734            );
735            rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
736            debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
737
738            // Transfer ownership of this browsing context from the old window proxy to the new one.
739            SetProxyReservedSlot(
740                new_js_proxy.get(),
741                0,
742                &PrivateValue(self as *const _ as *const libc::c_void),
743            );
744
745            // Notify the JS engine about the new window proxy binding.
746            SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
747
748            // Update the reflector.
749            debug!(
750                "Setting reflector of {:p} to {:p}.",
751                self,
752                new_js_proxy.get()
753            );
754            self.reflector.rootable().set(new_js_proxy.get());
755        }
756    }
757
758    pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
759        if let Some(pipeline_id) = self.currently_active() {
760            if pipeline_id == window.pipeline_id() {
761                return debug!(
762                    "Attempt to set the currently active window to the currently active window."
763                );
764            }
765        }
766
767        let global_scope = window.as_global_scope();
768        self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
769        self.currently_active.set(Some(global_scope.pipeline_id()));
770    }
771
772    pub(crate) fn unset_currently_active(&self, can_gc: CanGc) {
773        if self.currently_active().is_none() {
774            return debug!(
775                "Attempt to unset the currently active window on a windowproxy that does not have one."
776            );
777        }
778        let globalscope = self.global();
779        let window = DissimilarOriginWindow::new(&globalscope, self);
780        self.set_window(
781            window.upcast(),
782            WindowProxyHandler::x_origin_proxy_handler(),
783            can_gc,
784        );
785        self.currently_active.set(None);
786    }
787
788    pub(crate) fn currently_active(&self) -> Option<PipelineId> {
789        self.currently_active.get()
790    }
791
792    pub(crate) fn get_name(&self) -> DOMString {
793        self.name.borrow().clone()
794    }
795
796    pub(crate) fn set_name(&self, name: DOMString) {
797        *self.name.borrow_mut() = name;
798    }
799}
800
801/// A browsing context can have a creator browsing context, the browsing context that
802/// was responsible for its creation. If a browsing context has a parent browsing context,
803/// then that is its creator browsing context. Otherwise, if the browsing context has an
804/// opener browsing context, then that is its creator browsing context. Otherwise, the
805/// browsing context has no creator browsing context.
806///
807/// If a browsing context A has a creator browsing context, then the Document that was the
808/// active document of that creator browsing context at the time A was created is the creator
809/// Document.
810///
811/// See: <https://html.spec.whatwg.org/multipage/#creating-browsing-contexts>
812#[derive(Debug, Deserialize, Serialize)]
813pub(crate) struct CreatorBrowsingContextInfo {
814    /// Creator document URL.
815    url: Option<ServoUrl>,
816
817    /// Creator document origin.
818    origin: Option<ImmutableOrigin>,
819}
820
821impl CreatorBrowsingContextInfo {
822    pub(crate) fn from(
823        parent: Option<&WindowProxy>,
824        opener: Option<&WindowProxy>,
825    ) -> CreatorBrowsingContextInfo {
826        let creator = match (parent, opener) {
827            (Some(parent), _) => parent.document(),
828            (None, Some(opener)) => opener.document(),
829            (None, None) => None,
830        };
831
832        let url = creator.as_deref().map(|document| document.url());
833        let origin = creator
834            .as_deref()
835            .map(|document| document.origin().immutable().clone());
836
837        CreatorBrowsingContextInfo { url, origin }
838    }
839}
840
841/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize>
842fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
843    let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
844    // Step 1
845    let mut tokenized_features = IndexMap::new();
846    // Step 2
847    let features = features.str();
848    let mut iter = features.chars();
849    let mut cur = iter.next();
850
851    // Step 3
852    while cur.is_some() {
853        // Step 3.1 & 3.2
854        let mut name = String::new();
855        let mut value = String::new();
856        // Step 3.3
857        while let Some(cur_char) = cur {
858            if !is_feature_sep(cur_char) {
859                break;
860            }
861            cur = iter.next();
862        }
863        // Step 3.4
864        while let Some(cur_char) = cur {
865            if is_feature_sep(cur_char) {
866                break;
867            }
868            name.push(cur_char.to_ascii_lowercase());
869            cur = iter.next();
870        }
871        // Step 3.5
872        let normalized_name = String::from(match name.as_ref() {
873            "screenx" => "left",
874            "screeny" => "top",
875            "innerwidth" => "width",
876            "innerheight" => "height",
877            _ => name.as_ref(),
878        });
879        // Step 3.6
880        while let Some(cur_char) = cur {
881            if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
882                break;
883            }
884            cur = iter.next();
885        }
886        // Step 3.7
887        if cur.is_some() && is_feature_sep(cur.unwrap()) {
888            // Step 3.7.1
889            while let Some(cur_char) = cur {
890                if !is_feature_sep(cur_char) || cur_char == ',' {
891                    break;
892                }
893                cur = iter.next();
894            }
895            // Step 3.7.2
896            while let Some(cur_char) = cur {
897                if is_feature_sep(cur_char) {
898                    break;
899                }
900                value.push(cur_char.to_ascii_lowercase());
901                cur = iter.next();
902            }
903        }
904        // Step 3.8
905        if !name.is_empty() {
906            tokenized_features.insert(normalized_name, value);
907        }
908    }
909    // Step 4
910    tokenized_features
911}
912
913/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean>
914fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
915    if let Some(value) = tokenized_features.get(name) {
916        // Step 1 & 2
917        if value.is_empty() || value == "yes" {
918            return true;
919        }
920        // Step 3 & 4
921        if let Ok(int) = parse_integer(value.chars()) {
922            return int != 0;
923        }
924    }
925    // Step 5
926    false
927}
928
929// This is only called from extern functions,
930// there's no use using the lifetimed handles here.
931// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
932#[expect(unsafe_code)]
933#[expect(non_snake_case)]
934unsafe fn GetSubframeWindowProxy(
935    cx: *mut JSContext,
936    proxy: RawHandleObject,
937    id: RawHandleId,
938) -> Option<(DomRoot<WindowProxy>, u32)> {
939    let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
940    if let Some(index) = index {
941        let mut slot = UndefinedValue();
942        unsafe { GetProxyPrivate(*proxy, &mut slot) };
943        rooted!(in(cx) let target = slot.to_object());
944        let script_window_proxies = ScriptThread::window_proxies();
945        if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
946            let browsing_context_id = win.window_proxy().browsing_context_id();
947            let (result_sender, result_receiver) = ipc::channel().unwrap();
948
949            let _ = win.as_global_scope().script_to_constellation_chan().send(
950                ScriptToConstellationMessage::GetChildBrowsingContextId(
951                    browsing_context_id,
952                    index as usize,
953                    result_sender,
954                ),
955            );
956            return result_receiver
957                .recv()
958                .ok()
959                .and_then(|maybe_bcid| maybe_bcid)
960                .and_then(|id| script_window_proxies.find_window_proxy(id))
961                .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
962        } else if let Ok(win) =
963            root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
964        {
965            let browsing_context_id = win.window_proxy().browsing_context_id();
966            let (result_sender, result_receiver) = ipc::channel().unwrap();
967
968            let _ = win.global().script_to_constellation_chan().send(
969                ScriptToConstellationMessage::GetChildBrowsingContextId(
970                    browsing_context_id,
971                    index as usize,
972                    result_sender,
973                ),
974            );
975            return result_receiver
976                .recv()
977                .ok()
978                .and_then(|maybe_bcid| maybe_bcid)
979                .and_then(|id| script_window_proxies.find_window_proxy(id))
980                .map(|proxy| (proxy, JSPROP_READONLY as u32));
981        }
982    }
983
984    None
985}
986
987#[expect(unsafe_code)]
988unsafe extern "C" fn get_own_property_descriptor(
989    cx: *mut JSContext,
990    proxy: RawHandleObject,
991    id: RawHandleId,
992    desc: RawMutableHandle<PropertyDescriptor>,
993    is_none: *mut bool,
994) -> bool {
995    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
996    if let Some((window, attrs)) = window {
997        rooted!(in(cx) let mut val = UndefinedValue());
998        unsafe { window.to_jsval(cx, val.handle_mut()) };
999        set_property_descriptor(
1000            unsafe { MutableHandle::from_raw(desc) },
1001            val.handle(),
1002            attrs,
1003            unsafe { &mut *is_none },
1004        );
1005        return true;
1006    }
1007
1008    let mut slot = UndefinedValue();
1009    unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1010    rooted!(in(cx) let target = slot.to_object());
1011    unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1012}
1013
1014#[expect(unsafe_code)]
1015unsafe extern "C" fn define_property(
1016    cx: *mut JSContext,
1017    proxy: RawHandleObject,
1018    id: RawHandleId,
1019    desc: RawHandle<PropertyDescriptor>,
1020    res: *mut ObjectOpResult,
1021) -> bool {
1022    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1023        // Spec says to Reject whether this is a supported index or not,
1024        // since we have no indexed setter or indexed creator.  That means
1025        // throwing in strict mode (FIXME: Bug 828137), doing nothing in
1026        // non-strict mode.
1027        unsafe {
1028            (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1029        }
1030        return true;
1031    }
1032
1033    let mut slot = UndefinedValue();
1034    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1035    rooted!(in(cx) let target = slot.to_object());
1036    unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1037}
1038
1039#[expect(unsafe_code)]
1040unsafe extern "C" fn has(
1041    cx: *mut JSContext,
1042    proxy: RawHandleObject,
1043    id: RawHandleId,
1044    bp: *mut bool,
1045) -> bool {
1046    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1047    if window.is_some() {
1048        unsafe { *bp = true };
1049        return true;
1050    }
1051
1052    let mut slot = UndefinedValue();
1053    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1054    rooted!(in(cx) let target = slot.to_object());
1055    let mut found = false;
1056    if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1057        return false;
1058    }
1059
1060    unsafe { *bp = found };
1061    true
1062}
1063
1064#[expect(unsafe_code)]
1065unsafe extern "C" fn get(
1066    cx: *mut JSContext,
1067    proxy: RawHandleObject,
1068    receiver: RawHandleValue,
1069    id: RawHandleId,
1070    vp: RawMutableHandleValue,
1071) -> bool {
1072    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1073    if let Some((window, _attrs)) = window {
1074        unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1075        return true;
1076    }
1077
1078    let mut slot = UndefinedValue();
1079    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1080    rooted!(in(cx) let target = slot.to_object());
1081    unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1082}
1083
1084#[expect(unsafe_code)]
1085unsafe extern "C" fn set(
1086    cx: *mut JSContext,
1087    proxy: RawHandleObject,
1088    id: RawHandleId,
1089    v: RawHandleValue,
1090    receiver: RawHandleValue,
1091    res: *mut ObjectOpResult,
1092) -> bool {
1093    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1094        // Reject (which means throw if and only if strict) the set.
1095        unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1096        return true;
1097    }
1098
1099    let mut slot = UndefinedValue();
1100    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1101    rooted!(in(cx) let target = slot.to_object());
1102    unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1103}
1104
1105#[expect(unsafe_code)]
1106unsafe extern "C" fn get_prototype_if_ordinary(
1107    _: *mut JSContext,
1108    _: RawHandleObject,
1109    is_ordinary: *mut bool,
1110    _: RawMutableHandleObject,
1111) -> bool {
1112    // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
1113    //
1114    //   https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof
1115    //
1116    // We nonetheless can implement it with a static [[Prototype]], because
1117    // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
1118    // all non-ordinary behavior.
1119    //
1120    // But from a spec point of view, it's the exact same object in both cases --
1121    // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
1122    // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
1123    // usually means ordinary.
1124    unsafe { *is_ordinary = false };
1125    true
1126}
1127
1128static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1129    // TODO: These traps should change their behavior depending on
1130    //       `IsPlatformObjectSameOrigin(this.[[Window]])`
1131    enter: None,
1132    getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1133    defineProperty: Some(define_property),
1134    ownPropertyKeys: None,
1135    delete_: None,
1136    enumerate: None,
1137    getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1138    getPrototype: None, // TODO: return `null` if cross origin-domain
1139    setPrototype: None,
1140    setImmutablePrototype: None,
1141    preventExtensions: None,
1142    isExtensible: None,
1143    has: Some(has),
1144    get: Some(get),
1145    set: Some(set),
1146    call: None,
1147    construct: None,
1148    hasOwn: None,
1149    getOwnEnumerablePropertyKeys: None,
1150    nativeCall: None,
1151    objectClassIs: None,
1152    className: None,
1153    fun_toString: None,
1154    boxedValue_unbox: None,
1155    defaultValue: None,
1156    trace: Some(trace),
1157    finalize: Some(finalize),
1158    objectMoved: None,
1159    isCallable: None,
1160    isConstructor: None,
1161};
1162
1163/// Proxy handler for a WindowProxy.
1164/// Has ownership of the inner pointer and deallocates it when it is no longer needed.
1165pub(crate) struct WindowProxyHandler(*const libc::c_void);
1166
1167impl MallocSizeOf for WindowProxyHandler {
1168    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1169        // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs.
1170        0
1171    }
1172}
1173
1174// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1175#[expect(unsafe_code)]
1176unsafe impl Send for WindowProxyHandler {}
1177// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1178#[expect(unsafe_code)]
1179unsafe impl Sync for WindowProxyHandler {}
1180
1181#[expect(unsafe_code)]
1182impl WindowProxyHandler {
1183    fn new(traps: &ProxyTraps) -> Self {
1184        // Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak.
1185        let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1186        assert!(!ptr.is_null());
1187        Self(ptr)
1188    }
1189
1190    /// Returns a single, shared WindowProxyHandler that contains XORIGIN_PROXY_TRAPS.
1191    pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1192        use std::sync::OnceLock;
1193        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1194        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1195        /// it is safe to free it.
1196        /// Sharing a single instance should be fine because all methods on this pointer in C++
1197        /// are const and don't modify its internal state.
1198        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1199        SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1200    }
1201
1202    /// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS.
1203    pub(crate) fn proxy_handler() -> &'static Self {
1204        use std::sync::OnceLock;
1205        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1206        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1207        /// it is safe to free it.
1208        /// Sharing a single instance should be fine because all methods on this pointer in C++
1209        /// are const and don't modify its internal state.
1210        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1211        SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1212    }
1213
1214    /// Creates a new WindowProxy object on the C++ side and returns the pointer to it.
1215    /// The pointer should be owned by the GC.
1216    pub(crate) fn new_window_proxy(
1217        &self,
1218        cx: &crate::script_runtime::JSContext,
1219        window_jsobject: js::gc::HandleObject,
1220    ) -> *mut JSObject {
1221        let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1222        assert!(!obj.is_null());
1223        obj
1224    }
1225}
1226
1227#[expect(unsafe_code)]
1228impl Drop for WindowProxyHandler {
1229    fn drop(&mut self) {
1230        // Safety: Pointer is allocated by corresponding C++ function, owned by this
1231        // struct and not accessible from outside.
1232        unsafe {
1233            DeleteWrapperProxyHandler(self.0);
1234        }
1235    }
1236}
1237
1238// The proxy traps for cross-origin windows.
1239// These traps often throw security errors, and only pass on calls to methods
1240// defined in the DissimilarOriginWindow IDL.
1241
1242// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls
1243//       to this function should be replaced with those to
1244//       `report_cross_origin_denial`.
1245#[expect(unsafe_code)]
1246fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1247    if !unsafe { JS_IsExceptionPending(*cx) } {
1248        let global = unsafe { GlobalScope::from_context(*cx, realm) };
1249        throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1250    }
1251    false
1252}
1253
1254#[expect(unsafe_code)]
1255unsafe extern "C" fn has_xorigin(
1256    cx: *mut JSContext,
1257    proxy: RawHandleObject,
1258    id: RawHandleId,
1259    bp: *mut bool,
1260) -> bool {
1261    let mut slot = UndefinedValue();
1262    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1263    rooted!(in(cx) let target = slot.to_object());
1264    let mut found = false;
1265    unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1266    if found {
1267        unsafe { *bp = true };
1268        true
1269    } else {
1270        let cx = unsafe { SafeJSContext::from_ptr(cx) };
1271        let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1272        throw_security_error(cx, InRealm::Already(&in_realm_proof))
1273    }
1274}
1275
1276#[expect(unsafe_code)]
1277unsafe extern "C" fn get_xorigin(
1278    cx: *mut JSContext,
1279    proxy: RawHandleObject,
1280    receiver: RawHandleValue,
1281    id: RawHandleId,
1282    vp: RawMutableHandleValue,
1283) -> bool {
1284    let mut found = false;
1285    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1286    found && unsafe { get(cx, proxy, receiver, id, vp) }
1287}
1288
1289#[expect(unsafe_code)]
1290unsafe extern "C" fn set_xorigin(
1291    cx: *mut JSContext,
1292    _: RawHandleObject,
1293    _: RawHandleId,
1294    _: RawHandleValue,
1295    _: RawHandleValue,
1296    _: *mut ObjectOpResult,
1297) -> bool {
1298    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1299    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1300    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1301}
1302
1303#[expect(unsafe_code)]
1304unsafe extern "C" fn delete_xorigin(
1305    cx: *mut JSContext,
1306    _: RawHandleObject,
1307    _: RawHandleId,
1308    _: *mut ObjectOpResult,
1309) -> bool {
1310    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1311    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1312    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1313}
1314
1315#[expect(unsafe_code)]
1316#[expect(non_snake_case)]
1317unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1318    cx: *mut JSContext,
1319    proxy: RawHandleObject,
1320    id: RawHandleId,
1321    desc: RawMutableHandle<PropertyDescriptor>,
1322    is_none: *mut bool,
1323) -> bool {
1324    let mut found = false;
1325    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1326    found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1327}
1328
1329#[expect(unsafe_code)]
1330#[expect(non_snake_case)]
1331unsafe extern "C" fn defineProperty_xorigin(
1332    cx: *mut JSContext,
1333    _: RawHandleObject,
1334    _: RawHandleId,
1335    _: RawHandle<PropertyDescriptor>,
1336    _: *mut ObjectOpResult,
1337) -> bool {
1338    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1339    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1340    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1341}
1342
1343#[expect(unsafe_code)]
1344#[expect(non_snake_case)]
1345unsafe extern "C" fn preventExtensions_xorigin(
1346    cx: *mut JSContext,
1347    _: RawHandleObject,
1348    _: *mut ObjectOpResult,
1349) -> bool {
1350    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1351    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1352    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1353}
1354
1355static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1356    enter: None,
1357    getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1358    defineProperty: Some(defineProperty_xorigin),
1359    ownPropertyKeys: None,
1360    delete_: Some(delete_xorigin),
1361    enumerate: None,
1362    getPrototypeIfOrdinary: None,
1363    getPrototype: None,
1364    setPrototype: None,
1365    setImmutablePrototype: None,
1366    preventExtensions: Some(preventExtensions_xorigin),
1367    isExtensible: None,
1368    has: Some(has_xorigin),
1369    get: Some(get_xorigin),
1370    set: Some(set_xorigin),
1371    call: None,
1372    construct: None,
1373    hasOwn: Some(has_xorigin),
1374    getOwnEnumerablePropertyKeys: None,
1375    nativeCall: None,
1376    objectClassIs: None,
1377    className: None,
1378    fun_toString: None,
1379    boxedValue_unbox: None,
1380    defaultValue: None,
1381    trace: Some(trace),
1382    finalize: Some(finalize),
1383    objectMoved: None,
1384    isCallable: None,
1385    isConstructor: None,
1386};
1387
1388// How WindowProxy objects are garbage collected.
1389
1390#[expect(unsafe_code)]
1391unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1392    let mut slot = UndefinedValue();
1393    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1394    let this = slot.to_private() as *mut WindowProxy;
1395    if this.is_null() {
1396        // GC during obj creation or after transplanting.
1397        return;
1398    }
1399    let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1400    debug!(
1401        "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1402        this, jsobject, obj
1403    );
1404    let _ = unsafe { Box::from_raw(this) };
1405}
1406
1407#[expect(unsafe_code)]
1408unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1409    let mut slot = UndefinedValue();
1410    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1411    let this = slot.to_private() as *const WindowProxy;
1412    if this.is_null() {
1413        // GC during obj creation or after transplanting.
1414        return;
1415    }
1416    unsafe { (*this).trace(trc) };
1417}