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