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