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