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    /// Run [the focusing steps] with this browsing context.
700    ///
701    /// [the focusing steps]: https://html.spec.whatwg.org/multipage/#focusing-steps
702    pub fn focus(&self) {
703        debug!(
704            "Requesting the constellation to initiate a focus operation for \
705            browsing context {}",
706            self.browsing_context_id()
707        );
708        self.global()
709            .script_to_constellation_chan()
710            .send(ScriptToConstellationMessage::FocusRemoteDocument(
711                self.browsing_context_id(),
712            ))
713            .unwrap();
714    }
715
716    pub fn document_origin(&self) -> Option<String> {
717        let pipeline_id = self.currently_active()?;
718        let (result_sender, result_receiver) = generic_channel::channel().unwrap();
719        self.global()
720            .script_to_constellation_chan()
721            .send(ScriptToConstellationMessage::GetDocumentOrigin(
722                pipeline_id,
723                result_sender,
724            ))
725            .ok()?;
726        result_receiver.recv().ok()?
727    }
728
729    #[expect(unsafe_code)]
730    /// Change the Window that this WindowProxy resolves to.
731    // TODO: support setting the window proxy to a dummy value,
732    // to handle the case when the active document is in another script thread.
733    fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
734        unsafe {
735            debug!("Setting window of {:p}.", self);
736
737            let cx = GlobalScope::get_cx();
738            let window_jsobject = window.reflector().get_jsobject();
739            let old_js_proxy = self.reflector.get_jsobject();
740            assert!(!window_jsobject.get().is_null());
741            assert_ne!(
742                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
743                0
744            );
745            let _ac = enter_realm(window);
746
747            // The old window proxy no longer owns this browsing context.
748            SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
749
750            // Brain transplant the window proxy. Brain transplantation is
751            // usually done to move a window proxy between compartments, but
752            // that's not what we are doing here. We need to do this just
753            // because we want to replace the wrapper's `ProxyTraps`, but we
754            // don't want to update its identity.
755            rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
756            // Explicitly set this slot to a null pointer in case a GC occurs before we
757            // are ready to set it to a real value.
758            SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
759            debug!(
760                "Transplanting proxy from {:p} to {:p}.",
761                old_js_proxy.get(),
762                new_js_proxy.get()
763            );
764            rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
765            debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
766
767            // Transfer ownership of this browsing context from the old window proxy to the new one.
768            SetProxyReservedSlot(
769                new_js_proxy.get(),
770                0,
771                &PrivateValue(self as *const _ as *const libc::c_void),
772            );
773
774            // Notify the JS engine about the new window proxy binding.
775            SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
776
777            // Update the reflector.
778            debug!(
779                "Setting reflector of {:p} to {:p}.",
780                self,
781                new_js_proxy.get()
782            );
783            self.reflector.rootable().set(new_js_proxy.get());
784        }
785    }
786
787    pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
788        if let Some(pipeline_id) = self.currently_active() {
789            if pipeline_id == window.pipeline_id() {
790                return debug!(
791                    "Attempt to set the currently active window to the currently active window."
792                );
793            }
794        }
795
796        let global_scope = window.as_global_scope();
797        self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
798        self.currently_active.set(Some(global_scope.pipeline_id()));
799    }
800
801    pub(crate) fn unset_currently_active(&self, cx: &mut js::context::JSContext) {
802        if self.currently_active().is_none() {
803            return debug!(
804                "Attempt to unset the currently active window on a windowproxy that does not have one."
805            );
806        }
807        let globalscope = self.global();
808        let window = DissimilarOriginWindow::new(cx, &globalscope, self);
809        self.set_window(
810            window.upcast(),
811            WindowProxyHandler::x_origin_proxy_handler(),
812            CanGc::from_cx(cx),
813        );
814        self.currently_active.set(None);
815    }
816
817    pub(crate) fn currently_active(&self) -> Option<PipelineId> {
818        self.currently_active.get()
819    }
820
821    pub(crate) fn get_name(&self) -> DOMString {
822        self.name.borrow().clone()
823    }
824
825    pub(crate) fn set_name(&self, name: DOMString) {
826        *self.name.borrow_mut() = name;
827    }
828}
829
830/// A browsing context can have a creator browsing context, the browsing context that
831/// was responsible for its creation. If a browsing context has a parent browsing context,
832/// then that is its creator browsing context. Otherwise, if the browsing context has an
833/// opener browsing context, then that is its creator browsing context. Otherwise, the
834/// browsing context has no creator browsing context.
835///
836/// If a browsing context A has a creator browsing context, then the Document that was the
837/// active document of that creator browsing context at the time A was created is the creator
838/// Document.
839///
840/// See: <https://html.spec.whatwg.org/multipage/#creating-browsing-contexts>
841#[derive(Debug, Deserialize, Serialize)]
842pub(crate) struct CreatorBrowsingContextInfo {
843    /// Creator document URL.
844    url: Option<ServoUrl>,
845
846    /// Creator document origin.
847    origin: Option<ImmutableOrigin>,
848}
849
850impl CreatorBrowsingContextInfo {
851    pub(crate) fn from(
852        parent: Option<&WindowProxy>,
853        opener: Option<&WindowProxy>,
854    ) -> CreatorBrowsingContextInfo {
855        let creator = match (parent, opener) {
856            (Some(parent), _) => parent.document(),
857            (None, Some(opener)) => opener.document(),
858            (None, None) => None,
859        };
860
861        let url = creator.as_deref().map(|document| document.url());
862        let origin = creator
863            .as_deref()
864            .map(|document| document.origin().immutable().clone());
865
866        CreatorBrowsingContextInfo { url, origin }
867    }
868}
869
870/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize>
871fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
872    let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
873    // Step 1
874    let mut tokenized_features = IndexMap::new();
875    // Step 2
876    let features = features.str();
877    let mut iter = features.chars();
878    let mut cur = iter.next();
879
880    // Step 3
881    while cur.is_some() {
882        // Step 3.1 & 3.2
883        let mut name = String::new();
884        let mut value = String::new();
885        // Step 3.3
886        while let Some(cur_char) = cur {
887            if !is_feature_sep(cur_char) {
888                break;
889            }
890            cur = iter.next();
891        }
892        // Step 3.4
893        while let Some(cur_char) = cur {
894            if is_feature_sep(cur_char) {
895                break;
896            }
897            name.push(cur_char.to_ascii_lowercase());
898            cur = iter.next();
899        }
900        // Step 3.5
901        let normalized_name = String::from(match name.as_ref() {
902            "screenx" => "left",
903            "screeny" => "top",
904            "innerwidth" => "width",
905            "innerheight" => "height",
906            _ => name.as_ref(),
907        });
908        // Step 3.6
909        while let Some(cur_char) = cur {
910            if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
911                break;
912            }
913            cur = iter.next();
914        }
915        // Step 3.7
916        if cur.is_some() && is_feature_sep(cur.unwrap()) {
917            // Step 3.7.1
918            while let Some(cur_char) = cur {
919                if !is_feature_sep(cur_char) || cur_char == ',' {
920                    break;
921                }
922                cur = iter.next();
923            }
924            // Step 3.7.2
925            while let Some(cur_char) = cur {
926                if is_feature_sep(cur_char) {
927                    break;
928                }
929                value.push(cur_char.to_ascii_lowercase());
930                cur = iter.next();
931            }
932        }
933        // Step 3.8
934        if !name.is_empty() {
935            tokenized_features.insert(normalized_name, value);
936        }
937    }
938    // Step 4
939    tokenized_features
940}
941
942/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean>
943fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
944    if let Some(value) = tokenized_features.get(name) {
945        // Step 1 & 2
946        if value.is_empty() || value == "yes" {
947            return true;
948        }
949        // Step 3 & 4
950        if let Ok(int) = parse_integer(value.chars()) {
951            return int != 0;
952        }
953    }
954    // Step 5
955    false
956}
957
958// This is only called from extern functions,
959// there's no use using the lifetimed handles here.
960// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
961#[expect(unsafe_code)]
962#[expect(non_snake_case)]
963unsafe fn GetSubframeWindowProxy(
964    cx: *mut JSContext,
965    proxy: RawHandleObject,
966    id: RawHandleId,
967) -> Option<(DomRoot<WindowProxy>, u32)> {
968    let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
969    if let Some(index) = index {
970        let mut slot = UndefinedValue();
971        unsafe { GetProxyPrivate(*proxy, &mut slot) };
972        rooted!(in(cx) let target = slot.to_object());
973        let script_window_proxies = ScriptThread::window_proxies();
974        if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
975            let browsing_context_id = win.window_proxy().browsing_context_id();
976            let (result_sender, result_receiver) = generic_channel::channel().unwrap();
977
978            let _ = win.as_global_scope().script_to_constellation_chan().send(
979                ScriptToConstellationMessage::GetChildBrowsingContextId(
980                    browsing_context_id,
981                    index as usize,
982                    result_sender,
983                ),
984            );
985            return result_receiver
986                .recv()
987                .ok()
988                .and_then(|maybe_bcid| maybe_bcid)
989                .and_then(|id| script_window_proxies.find_window_proxy(id))
990                .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
991        } else if let Ok(win) =
992            root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
993        {
994            let browsing_context_id = win.window_proxy().browsing_context_id();
995            let (result_sender, result_receiver) = generic_channel::channel().unwrap();
996
997            let _ = win.global().script_to_constellation_chan().send(
998                ScriptToConstellationMessage::GetChildBrowsingContextId(
999                    browsing_context_id,
1000                    index as usize,
1001                    result_sender,
1002                ),
1003            );
1004            return result_receiver
1005                .recv()
1006                .ok()
1007                .and_then(|maybe_bcid| maybe_bcid)
1008                .and_then(|id| script_window_proxies.find_window_proxy(id))
1009                .map(|proxy| (proxy, JSPROP_READONLY as u32));
1010        }
1011    }
1012
1013    None
1014}
1015
1016#[expect(unsafe_code)]
1017unsafe extern "C" fn get_own_property_descriptor(
1018    cx: *mut JSContext,
1019    proxy: RawHandleObject,
1020    id: RawHandleId,
1021    desc: RawMutableHandle<PropertyDescriptor>,
1022    is_none: *mut bool,
1023) -> bool {
1024    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1025    if let Some((window, attrs)) = window {
1026        rooted!(in(cx) let mut val = UndefinedValue());
1027        unsafe { window.to_jsval(cx, val.handle_mut()) };
1028        set_property_descriptor(
1029            unsafe { MutableHandle::from_raw(desc) },
1030            val.handle(),
1031            attrs,
1032            unsafe { &mut *is_none },
1033        );
1034        return true;
1035    }
1036
1037    let mut slot = UndefinedValue();
1038    unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1039    rooted!(in(cx) let target = slot.to_object());
1040    unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1041}
1042
1043#[expect(unsafe_code)]
1044unsafe extern "C" fn define_property(
1045    cx: *mut JSContext,
1046    proxy: RawHandleObject,
1047    id: RawHandleId,
1048    desc: RawHandle<PropertyDescriptor>,
1049    res: *mut ObjectOpResult,
1050) -> bool {
1051    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1052        // Spec says to Reject whether this is a supported index or not,
1053        // since we have no indexed setter or indexed creator.  That means
1054        // throwing in strict mode (FIXME: Bug 828137), doing nothing in
1055        // non-strict mode.
1056        unsafe {
1057            (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1058        }
1059        return true;
1060    }
1061
1062    let mut slot = UndefinedValue();
1063    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1064    rooted!(in(cx) let target = slot.to_object());
1065    unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1066}
1067
1068#[expect(unsafe_code)]
1069unsafe extern "C" fn has(
1070    cx: *mut JSContext,
1071    proxy: RawHandleObject,
1072    id: RawHandleId,
1073    bp: *mut bool,
1074) -> bool {
1075    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1076    if window.is_some() {
1077        unsafe { *bp = true };
1078        return true;
1079    }
1080
1081    let mut slot = UndefinedValue();
1082    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1083    rooted!(in(cx) let target = slot.to_object());
1084    let mut found = false;
1085    if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1086        return false;
1087    }
1088
1089    unsafe { *bp = found };
1090    true
1091}
1092
1093#[expect(unsafe_code)]
1094unsafe extern "C" fn get(
1095    cx: *mut JSContext,
1096    proxy: RawHandleObject,
1097    receiver: RawHandleValue,
1098    id: RawHandleId,
1099    vp: RawMutableHandleValue,
1100) -> bool {
1101    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1102    if let Some((window, _attrs)) = window {
1103        unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1104        return true;
1105    }
1106
1107    let mut slot = UndefinedValue();
1108    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1109    rooted!(in(cx) let target = slot.to_object());
1110    unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1111}
1112
1113#[expect(unsafe_code)]
1114unsafe extern "C" fn set(
1115    cx: *mut JSContext,
1116    proxy: RawHandleObject,
1117    id: RawHandleId,
1118    v: RawHandleValue,
1119    receiver: RawHandleValue,
1120    res: *mut ObjectOpResult,
1121) -> bool {
1122    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1123        // Reject (which means throw if and only if strict) the set.
1124        unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1125        return true;
1126    }
1127
1128    let mut slot = UndefinedValue();
1129    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1130    rooted!(in(cx) let target = slot.to_object());
1131    unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1132}
1133
1134#[expect(unsafe_code)]
1135unsafe extern "C" fn get_prototype_if_ordinary(
1136    _: *mut JSContext,
1137    _: RawHandleObject,
1138    is_ordinary: *mut bool,
1139    _: RawMutableHandleObject,
1140) -> bool {
1141    // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
1142    //
1143    //   https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof
1144    //
1145    // We nonetheless can implement it with a static [[Prototype]], because
1146    // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
1147    // all non-ordinary behavior.
1148    //
1149    // But from a spec point of view, it's the exact same object in both cases --
1150    // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
1151    // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
1152    // usually means ordinary.
1153    unsafe { *is_ordinary = false };
1154    true
1155}
1156
1157static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1158    // TODO: These traps should change their behavior depending on
1159    //       `IsPlatformObjectSameOrigin(this.[[Window]])`
1160    enter: None,
1161    getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1162    defineProperty: Some(define_property),
1163    ownPropertyKeys: None,
1164    delete_: None,
1165    enumerate: None,
1166    getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1167    getPrototype: None, // TODO: return `null` if cross origin-domain
1168    setPrototype: None,
1169    setImmutablePrototype: None,
1170    preventExtensions: None,
1171    isExtensible: None,
1172    has: Some(has),
1173    get: Some(get),
1174    set: Some(set),
1175    call: None,
1176    construct: None,
1177    hasOwn: None,
1178    getOwnEnumerablePropertyKeys: None,
1179    nativeCall: None,
1180    objectClassIs: None,
1181    className: None,
1182    fun_toString: None,
1183    boxedValue_unbox: None,
1184    defaultValue: None,
1185    trace: Some(trace),
1186    finalize: Some(finalize),
1187    objectMoved: None,
1188    isCallable: None,
1189    isConstructor: None,
1190};
1191
1192/// Proxy handler for a WindowProxy.
1193/// Has ownership of the inner pointer and deallocates it when it is no longer needed.
1194pub(crate) struct WindowProxyHandler(*const libc::c_void);
1195
1196impl MallocSizeOf for WindowProxyHandler {
1197    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1198        // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs.
1199        0
1200    }
1201}
1202
1203// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1204#[expect(unsafe_code)]
1205unsafe impl Send for WindowProxyHandler {}
1206// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1207#[expect(unsafe_code)]
1208unsafe impl Sync for WindowProxyHandler {}
1209
1210#[expect(unsafe_code)]
1211impl WindowProxyHandler {
1212    fn new(traps: &ProxyTraps) -> Self {
1213        // Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak.
1214        let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1215        assert!(!ptr.is_null());
1216        Self(ptr)
1217    }
1218
1219    /// Returns a single, shared WindowProxyHandler that contains XORIGIN_PROXY_TRAPS.
1220    pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1221        use std::sync::OnceLock;
1222        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1223        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1224        /// it is safe to free it.
1225        /// Sharing a single instance should be fine because all methods on this pointer in C++
1226        /// are const and don't modify its internal state.
1227        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1228        SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1229    }
1230
1231    /// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS.
1232    pub(crate) fn proxy_handler() -> &'static Self {
1233        use std::sync::OnceLock;
1234        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1235        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1236        /// it is safe to free it.
1237        /// Sharing a single instance should be fine because all methods on this pointer in C++
1238        /// are const and don't modify its internal state.
1239        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1240        SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1241    }
1242
1243    /// Creates a new WindowProxy object on the C++ side and returns the pointer to it.
1244    /// The pointer should be owned by the GC.
1245    pub(crate) fn new_window_proxy(
1246        &self,
1247        cx: &crate::script_runtime::JSContext,
1248        window_jsobject: js::gc::HandleObject,
1249    ) -> *mut JSObject {
1250        let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1251        assert!(!obj.is_null());
1252        obj
1253    }
1254}
1255
1256#[expect(unsafe_code)]
1257impl Drop for WindowProxyHandler {
1258    fn drop(&mut self) {
1259        // Safety: Pointer is allocated by corresponding C++ function, owned by this
1260        // struct and not accessible from outside.
1261        unsafe {
1262            DeleteWrapperProxyHandler(self.0);
1263        }
1264    }
1265}
1266
1267// The proxy traps for cross-origin windows.
1268// These traps often throw security errors, and only pass on calls to methods
1269// defined in the DissimilarOriginWindow IDL.
1270
1271// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls
1272//       to this function should be replaced with those to
1273//       `report_cross_origin_denial`.
1274#[expect(unsafe_code)]
1275fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1276    if !unsafe { JS_IsExceptionPending(*cx) } {
1277        let global = unsafe { GlobalScope::from_context(*cx, realm) };
1278        throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1279    }
1280    false
1281}
1282
1283#[expect(unsafe_code)]
1284unsafe extern "C" fn has_xorigin(
1285    cx: *mut JSContext,
1286    proxy: RawHandleObject,
1287    id: RawHandleId,
1288    bp: *mut bool,
1289) -> bool {
1290    let mut slot = UndefinedValue();
1291    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1292    rooted!(in(cx) let target = slot.to_object());
1293    let mut found = false;
1294    unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1295    if found {
1296        unsafe { *bp = true };
1297        true
1298    } else {
1299        let cx = unsafe { SafeJSContext::from_ptr(cx) };
1300        let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1301        throw_security_error(cx, InRealm::Already(&in_realm_proof))
1302    }
1303}
1304
1305#[expect(unsafe_code)]
1306unsafe extern "C" fn get_xorigin(
1307    cx: *mut JSContext,
1308    proxy: RawHandleObject,
1309    receiver: RawHandleValue,
1310    id: RawHandleId,
1311    vp: RawMutableHandleValue,
1312) -> bool {
1313    let mut found = false;
1314    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1315    found && unsafe { get(cx, proxy, receiver, id, vp) }
1316}
1317
1318#[expect(unsafe_code)]
1319unsafe extern "C" fn set_xorigin(
1320    cx: *mut JSContext,
1321    _: RawHandleObject,
1322    _: RawHandleId,
1323    _: RawHandleValue,
1324    _: RawHandleValue,
1325    _: *mut ObjectOpResult,
1326) -> bool {
1327    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1328    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1329    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1330}
1331
1332#[expect(unsafe_code)]
1333unsafe extern "C" fn delete_xorigin(
1334    cx: *mut JSContext,
1335    _: RawHandleObject,
1336    _: RawHandleId,
1337    _: *mut ObjectOpResult,
1338) -> bool {
1339    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1340    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1341    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1342}
1343
1344#[expect(unsafe_code)]
1345#[expect(non_snake_case)]
1346unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1347    cx: *mut JSContext,
1348    proxy: RawHandleObject,
1349    id: RawHandleId,
1350    desc: RawMutableHandle<PropertyDescriptor>,
1351    is_none: *mut bool,
1352) -> bool {
1353    let mut found = false;
1354    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1355    found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1356}
1357
1358#[expect(unsafe_code)]
1359#[expect(non_snake_case)]
1360unsafe extern "C" fn defineProperty_xorigin(
1361    cx: *mut JSContext,
1362    _: RawHandleObject,
1363    _: RawHandleId,
1364    _: RawHandle<PropertyDescriptor>,
1365    _: *mut ObjectOpResult,
1366) -> bool {
1367    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1368    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1369    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1370}
1371
1372#[expect(unsafe_code)]
1373#[expect(non_snake_case)]
1374unsafe extern "C" fn preventExtensions_xorigin(
1375    cx: *mut JSContext,
1376    _: RawHandleObject,
1377    _: *mut ObjectOpResult,
1378) -> bool {
1379    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1380    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1381    throw_security_error(cx, InRealm::Already(&in_realm_proof))
1382}
1383
1384static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1385    enter: None,
1386    getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1387    defineProperty: Some(defineProperty_xorigin),
1388    ownPropertyKeys: None,
1389    delete_: Some(delete_xorigin),
1390    enumerate: None,
1391    getPrototypeIfOrdinary: None,
1392    getPrototype: None,
1393    setPrototype: None,
1394    setImmutablePrototype: None,
1395    preventExtensions: Some(preventExtensions_xorigin),
1396    isExtensible: None,
1397    has: Some(has_xorigin),
1398    get: Some(get_xorigin),
1399    set: Some(set_xorigin),
1400    call: None,
1401    construct: None,
1402    hasOwn: Some(has_xorigin),
1403    getOwnEnumerablePropertyKeys: None,
1404    nativeCall: None,
1405    objectClassIs: None,
1406    className: None,
1407    fun_toString: None,
1408    boxedValue_unbox: None,
1409    defaultValue: None,
1410    trace: Some(trace),
1411    finalize: Some(finalize),
1412    objectMoved: None,
1413    isCallable: None,
1414    isConstructor: None,
1415};
1416
1417// How WindowProxy objects are garbage collected.
1418
1419#[expect(unsafe_code)]
1420unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1421    let mut slot = UndefinedValue();
1422    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1423    let this = slot.to_private() as *mut WindowProxy;
1424    if this.is_null() {
1425        // GC during obj creation or after transplanting.
1426        return;
1427    }
1428    unsafe {
1429        (*this).reflector.drop_memory(&*this);
1430        let jsobject = (*this).reflector.get_jsobject().get();
1431        debug!(
1432            "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1433            this, jsobject, obj
1434        );
1435        let _ = Box::from_raw(this);
1436    }
1437}
1438
1439#[expect(unsafe_code)]
1440unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1441    let mut slot = UndefinedValue();
1442    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1443    let this = slot.to_private() as *const WindowProxy;
1444    if this.is_null() {
1445        // GC during obj creation or after transplanting.
1446        return;
1447    }
1448    unsafe { (*this).trace(trc) };
1449}