Skip to main content

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