Files
servo/components/script/dom/dissimilaroriginwindow.rs
Martin Robinson 718c8913af script/constellation: Rename and consolidate cross-Document focus messaging (#44020)
There are two times that Servo needs to ask other `Document`s to either
focus or blur.

- During processing of the "focusing steps". When a new element gains
  focus this may cause focus to be lost or gained in parent `<iframe>`s.
- When calling `focus()` on a DOM Window from another origin.

In both of these cases we need to request that a `Document` gain or lose
focus via the Constellation, but in the second case we may have a
`BrowsingContextId` of the `<iframe>` gaining focus and a
`FocusSequence`. This change splits those cases into two kinds of
messages.

Finally, run the entire focusing steps when calling `window.focus()`
instead of going to the constellation immediately. This will be
important in a followup changes where messaging order is made more
consistent.

Testing: This should not change behavior so is covered by existing
tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2026-04-09 08:59:47 +00:00

261 lines
9.2 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::context::JSContext;
use js::jsapi::{Heap, JSObject};
use js::jsval::UndefinedValue;
use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue, MutableHandleValue};
use servo_base::id::PipelineId;
use servo_constellation_traits::{ScriptToConstellationMessage, StructuredSerializedData};
use servo_url::ServoUrl;
use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding;
use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowPostMessageOptions;
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::bindings::structuredclone;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::dissimilaroriginlocation::DissimilarOriginLocation;
use crate::dom::globalscope::GlobalScope;
use crate::dom::windowproxy::WindowProxy;
use crate::script_runtime::CanGc;
/// Represents a dissimilar-origin `Window` that exists in another script thread.
///
/// Since the `Window` is in a different script thread, we cannot access it
/// directly, but some of its accessors (for example `window.parent`)
/// still need to function.
///
/// In `windowproxy.rs`, we create a custom window proxy for these windows,
/// that throws security exceptions for most accessors. This is not a replacement
/// for XOWs, but provides belt-and-braces security.
#[dom_struct]
pub(crate) struct DissimilarOriginWindow {
/// The global for this window.
globalscope: GlobalScope,
/// The window proxy for this window.
window_proxy: Dom<WindowProxy>,
/// The location of this window, initialized lazily.
location: MutNullableDom<DissimilarOriginLocation>,
}
impl DissimilarOriginWindow {
pub(crate) fn new(
cx: &mut js::context::JSContext,
global_to_clone_from: &GlobalScope,
window_proxy: &WindowProxy,
) -> DomRoot<Self> {
let win = Box::new(Self {
globalscope: GlobalScope::new_inherited(
PipelineId::new(),
global_to_clone_from.devtools_chan().cloned(),
global_to_clone_from.mem_profiler_chan().clone(),
global_to_clone_from.time_profiler_chan().clone(),
global_to_clone_from.script_to_constellation_chan().clone(),
global_to_clone_from.script_to_embedder_chan().clone(),
global_to_clone_from.resource_threads().clone(),
global_to_clone_from.storage_threads().clone(),
global_to_clone_from.origin().clone(),
global_to_clone_from.creation_url(),
global_to_clone_from.top_level_creation_url().clone(),
#[cfg(feature = "webgpu")]
global_to_clone_from.wgpu_id_hub(),
Some(global_to_clone_from.is_secure_context()),
false,
global_to_clone_from.font_context().cloned(),
),
window_proxy: Dom::from_ref(window_proxy),
location: Default::default(),
});
DissimilarOriginWindowBinding::Wrap::<crate::DomTypeHolder>(cx, win)
}
pub(crate) fn window_proxy(&self) -> DomRoot<WindowProxy> {
DomRoot::from_ref(&*self.window_proxy)
}
}
impl DissimilarOriginWindowMethods<crate::DomTypeHolder> for DissimilarOriginWindow {
/// <https://html.spec.whatwg.org/multipage/#dom-window>
fn Window(&self) -> DomRoot<WindowProxy> {
self.window_proxy()
}
/// <https://html.spec.whatwg.org/multipage/#dom-self>
fn Self_(&self) -> DomRoot<WindowProxy> {
self.window_proxy()
}
/// <https://html.spec.whatwg.org/multipage/#dom-frames>
fn Frames(&self) -> DomRoot<WindowProxy> {
self.window_proxy()
}
/// <https://html.spec.whatwg.org/multipage/#dom-parent>
fn GetParent(&self) -> Option<DomRoot<WindowProxy>> {
// Steps 1-3.
if self.window_proxy.is_browsing_context_discarded() {
return None;
}
// Step 4.
if let Some(parent) = self.window_proxy.parent() {
return Some(DomRoot::from_ref(parent));
}
// Step 5.
Some(DomRoot::from_ref(&*self.window_proxy))
}
/// <https://html.spec.whatwg.org/multipage/#dom-top>
fn GetTop(&self) -> Option<DomRoot<WindowProxy>> {
// Steps 1-3.
if self.window_proxy.is_browsing_context_discarded() {
return None;
}
// Steps 4-5.
Some(DomRoot::from_ref(self.window_proxy.top()))
}
/// <https://html.spec.whatwg.org/multipage/#dom-length>
fn Length(&self) -> u32 {
// TODO: Implement x-origin length
0
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-close>
fn Close(&self) {
// TODO: Implement x-origin close
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-closed>
fn Closed(&self) -> bool {
// TODO: Implement x-origin close
false
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage>
fn PostMessage(
&self,
cx: &mut JSContext,
message: HandleValue,
target_origin: USVString,
transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
) -> ErrorResult {
self.post_message_impl(&target_origin, cx, message, transfer)
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage-options>
fn PostMessage_(
&self,
cx: &mut JSContext,
message: HandleValue,
options: RootedTraceableBox<WindowPostMessageOptions>,
) -> ErrorResult {
let mut rooted = CustomAutoRooter::new(
options
.parent
.transfer
.iter()
.map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
.collect(),
);
#[expect(unsafe_code)]
let transfer = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
self.post_message_impl(&options.targetOrigin, cx, message, transfer)
}
/// <https://html.spec.whatwg.org/multipage/#dom-opener>
fn Opener(&self, _: &mut JSContext, mut retval: MutableHandleValue) {
// TODO: Implement x-origin opener
retval.set(UndefinedValue());
}
/// <https://html.spec.whatwg.org/multipage/#dom-opener>
fn SetOpener(&self, _: &mut JSContext, _: HandleValue) {
// TODO: Implement x-origin opener
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-blur>
fn Blur(&self) {
// > User agents are encouraged to ignore calls to this `blur()` method
// > entirely.
}
/// <https://html.spec.whatwg.org/multipage/#dom-window-focus>
fn Focus(&self) {
let browsing_context_id = self.window_proxy.browsing_context_id();
debug!("Initiating a focus operation for {browsing_context_id:?}");
self.globalscope
.script_to_constellation_chan()
.send(ScriptToConstellationMessage::FocusRemoteBrowsingContext(
browsing_context_id,
))
.unwrap();
}
/// <https://html.spec.whatwg.org/multipage/#dom-location>
fn Location(&self, can_gc: CanGc) -> DomRoot<DissimilarOriginLocation> {
self.location
.or_init(|| DissimilarOriginLocation::new(self, can_gc))
}
}
impl DissimilarOriginWindow {
/// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
fn post_message_impl(
&self,
target_origin: &USVString,
cx: &mut JSContext,
message: HandleValue,
transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
) -> ErrorResult {
// Step 6-7.
let data = structuredclone::write(cx.into(), message, Some(transfer))?;
self.post_message(target_origin, data)
}
/// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
pub(crate) fn post_message(
&self,
target_origin: &USVString,
data: StructuredSerializedData,
) -> ErrorResult {
// Step 1.
let target = self.window_proxy.browsing_context_id();
// Step 2.
let incumbent = match GlobalScope::incumbent() {
None => panic!("postMessage called with no incumbent global"),
Some(incumbent) => incumbent,
};
let source_origin = incumbent.origin().immutable().clone();
// Step 3-5.
let target_origin = match target_origin.0[..].as_ref() {
"*" => None,
"/" => Some(source_origin.clone()),
url => match ServoUrl::parse(url) {
Ok(url) => Some(url.origin()),
Err(_) => return Err(Error::Syntax(None)),
},
};
let msg = ScriptToConstellationMessage::PostMessage {
target,
source: incumbent.pipeline_id(),
source_origin,
target_origin,
data,
};
// Step 8
let _ = incumbent.script_to_constellation_chan().send(msg);
Ok(())
}
}