Skip to main content

script/dom/window/
dissimilaroriginwindow.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 dom_struct::dom_struct;
6use js::context::JSContext;
7use js::jsapi::{Heap, JSObject};
8use js::jsval::UndefinedValue;
9use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue, MutableHandleValue};
10use servo_base::id::PipelineId;
11use servo_constellation_traits::{
12    RemoteFocusOperation, ScriptToConstellationMessage, StructuredSerializedData,
13};
14use servo_url::ServoUrl;
15
16use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding;
17use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods;
18use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowPostMessageOptions;
19use crate::dom::bindings::error::{Error, ErrorResult};
20use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
21use crate::dom::bindings::str::USVString;
22use crate::dom::bindings::structuredclone;
23use crate::dom::bindings::trace::RootedTraceableBox;
24use crate::dom::dissimilaroriginlocation::DissimilarOriginLocation;
25use crate::dom::globalscope::GlobalScope;
26use crate::dom::windowproxy::WindowProxy;
27use crate::script_runtime::CanGc;
28
29/// Represents a dissimilar-origin `Window` that exists in another script thread.
30///
31/// Since the `Window` is in a different script thread, we cannot access it
32/// directly, but some of its accessors (for example `window.parent`)
33/// still need to function.
34///
35/// In `windowproxy.rs`, we create a custom window proxy for these windows,
36/// that throws security exceptions for most accessors. This is not a replacement
37/// for XOWs, but provides belt-and-braces security.
38#[dom_struct]
39pub(crate) struct DissimilarOriginWindow {
40    /// The global for this window.
41    globalscope: GlobalScope,
42
43    /// The window proxy for this window.
44    window_proxy: Dom<WindowProxy>,
45
46    /// The location of this window, initialized lazily.
47    location: MutNullableDom<DissimilarOriginLocation>,
48
49    #[no_trace]
50    pipeline_id: PipelineId,
51}
52
53impl DissimilarOriginWindow {
54    pub(crate) fn new(
55        cx: &mut js::context::JSContext,
56        global_to_clone_from: &GlobalScope,
57        window_proxy: &WindowProxy,
58    ) -> DomRoot<Self> {
59        let win = Box::new(Self {
60            globalscope: GlobalScope::new_inherited(
61                global_to_clone_from.devtools_chan().cloned(),
62                global_to_clone_from.mem_profiler_chan().clone(),
63                global_to_clone_from.time_profiler_chan().clone(),
64                global_to_clone_from.script_to_constellation_chan().sender,
65                global_to_clone_from.script_to_embedder_chan().clone(),
66                global_to_clone_from.resource_threads().clone(),
67                global_to_clone_from.storage_threads().clone(),
68                global_to_clone_from.origin().clone(),
69                global_to_clone_from.creation_url(),
70                global_to_clone_from.top_level_creation_url().clone(),
71                #[cfg(feature = "webgpu")]
72                global_to_clone_from.wgpu_id_hub(),
73                Some(global_to_clone_from.is_secure_context()),
74                false,
75                global_to_clone_from.font_context().cloned(),
76            ),
77            window_proxy: Dom::from_ref(window_proxy),
78            location: Default::default(),
79            pipeline_id: PipelineId::new(),
80        });
81        DissimilarOriginWindowBinding::Wrap::<crate::DomTypeHolder>(cx, win)
82    }
83
84    pub(crate) fn window_proxy(&self) -> DomRoot<WindowProxy> {
85        DomRoot::from_ref(&*self.window_proxy)
86    }
87
88    pub(crate) fn pipeline_id(&self) -> PipelineId {
89        self.pipeline_id
90    }
91}
92
93impl DissimilarOriginWindowMethods<crate::DomTypeHolder> for DissimilarOriginWindow {
94    /// <https://html.spec.whatwg.org/multipage/#dom-window>
95    fn Window(&self) -> DomRoot<WindowProxy> {
96        self.window_proxy()
97    }
98
99    /// <https://html.spec.whatwg.org/multipage/#dom-self>
100    fn Self_(&self) -> DomRoot<WindowProxy> {
101        self.window_proxy()
102    }
103
104    /// <https://html.spec.whatwg.org/multipage/#dom-frames>
105    fn Frames(&self) -> DomRoot<WindowProxy> {
106        self.window_proxy()
107    }
108
109    /// <https://html.spec.whatwg.org/multipage/#dom-parent>
110    fn GetParent(&self) -> Option<DomRoot<WindowProxy>> {
111        // Steps 1-3.
112        if self.window_proxy.is_browsing_context_discarded() {
113            return None;
114        }
115        // Step 4.
116        if let Some(parent) = self.window_proxy.parent() {
117            return Some(DomRoot::from_ref(parent));
118        }
119        // Step 5.
120        Some(DomRoot::from_ref(&*self.window_proxy))
121    }
122
123    /// <https://html.spec.whatwg.org/multipage/#dom-top>
124    fn GetTop(&self) -> Option<DomRoot<WindowProxy>> {
125        // Steps 1-3.
126        if self.window_proxy.is_browsing_context_discarded() {
127            return None;
128        }
129        // Steps 4-5.
130        Some(DomRoot::from_ref(self.window_proxy.top()))
131    }
132
133    /// <https://html.spec.whatwg.org/multipage/#dom-length>
134    fn Length(&self) -> u32 {
135        // TODO: Implement x-origin length
136        0
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-window-close>
140    fn Close(&self) {
141        // TODO: Implement x-origin close
142    }
143
144    /// <https://html.spec.whatwg.org/multipage/#dom-window-closed>
145    fn Closed(&self) -> bool {
146        // TODO: Implement x-origin close
147        false
148    }
149
150    /// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage>
151    fn PostMessage(
152        &self,
153        cx: &mut JSContext,
154        message: HandleValue,
155        target_origin: USVString,
156        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
157    ) -> ErrorResult {
158        self.post_message_impl(&target_origin, cx, message, transfer)
159    }
160
161    /// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage-options>
162    fn PostMessage_(
163        &self,
164        cx: &mut JSContext,
165        message: HandleValue,
166        options: RootedTraceableBox<WindowPostMessageOptions>,
167    ) -> ErrorResult {
168        let mut rooted = CustomAutoRooter::new(
169            options
170                .parent
171                .transfer
172                .iter()
173                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
174                .collect(),
175        );
176        #[expect(unsafe_code)]
177        let transfer = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
178
179        self.post_message_impl(&options.targetOrigin, cx, message, transfer)
180    }
181
182    /// <https://html.spec.whatwg.org/multipage/#dom-opener>
183    fn Opener(&self, _: &mut JSContext, mut retval: MutableHandleValue) {
184        // TODO: Implement x-origin opener
185        retval.set(UndefinedValue());
186    }
187
188    /// <https://html.spec.whatwg.org/multipage/#dom-opener>
189    fn SetOpener(&self, _: &mut JSContext, _: HandleValue) {
190        // TODO: Implement x-origin opener
191    }
192
193    /// <https://html.spec.whatwg.org/multipage/#dom-window-blur>
194    fn Blur(&self) {
195        // > User agents are encouraged to ignore calls to this `blur()` method
196        // > entirely.
197    }
198
199    /// <https://html.spec.whatwg.org/multipage/#dom-window-focus>
200    fn Focus(&self) {
201        let browsing_context_id = self.window_proxy.browsing_context_id();
202        debug!("Initiating a focus operation for {browsing_context_id:?}");
203        let _ = self.globalscope.script_to_constellation_chan().send(
204            ScriptToConstellationMessage::FocusRemoteBrowsingContext(
205                browsing_context_id,
206                RemoteFocusOperation::Viewport,
207            ),
208        );
209    }
210
211    /// <https://html.spec.whatwg.org/multipage/#dom-location>
212    fn Location(&self, can_gc: CanGc) -> DomRoot<DissimilarOriginLocation> {
213        self.location
214            .or_init(|| DissimilarOriginLocation::new(self, can_gc))
215    }
216}
217
218impl DissimilarOriginWindow {
219    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
220    fn post_message_impl(
221        &self,
222        target_origin: &USVString,
223        cx: &mut JSContext,
224        message: HandleValue,
225        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
226    ) -> ErrorResult {
227        // Step 6-7.
228        let data = structuredclone::write(cx, message, Some(transfer))?;
229
230        self.post_message(target_origin, data)
231    }
232
233    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
234    pub(crate) fn post_message(
235        &self,
236        target_origin: &USVString,
237        data: StructuredSerializedData,
238    ) -> ErrorResult {
239        // Step 1.
240        let target = self.window_proxy.browsing_context_id();
241        // Step 2.
242        let incumbent = match GlobalScope::incumbent() {
243            None => panic!("postMessage called with no incumbent global"),
244            Some(incumbent) => incumbent,
245        };
246
247        let source_origin = incumbent.origin().immutable().clone();
248
249        // Step 3-5.
250        let target_origin = match target_origin.0[..].as_ref() {
251            "*" => None,
252            "/" => Some(source_origin.clone()),
253            url => match ServoUrl::parse(url) {
254                Ok(url) => Some(url.origin()),
255                Err(_) => return Err(Error::Syntax(None)),
256            },
257        };
258        let msg = ScriptToConstellationMessage::PostMessage {
259            target,
260            source: incumbent.pipeline_id(),
261            source_origin,
262            target_origin,
263            data,
264        };
265        // Step 8
266        let _ = incumbent.script_to_constellation_chan().send(msg);
267        Ok(())
268    }
269}