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