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