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