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