Skip to main content

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