mirror of
https://github.com/servo/servo
synced 2026-05-09 00:22:16 +02:00
Reviewable per commits: As noted in https://github.com/servo/servo/pull/42180#discussion_r2749861902 transmuting `&Reflector<AssociatedMemory>` to `&Reflector<()>` will ignore AssociatedMemory information and thus it will not remove extra associated memory. By returning `&Reflector<Self::ReflectorType>` from `DomObject::reflector()` we will preserve this information and thus correctly handle cases with associated memory. We also do not need `overrideMemoryUsage` in bindings.conf anymore. 🎉 Instead of removing associated memory in drop code we should do it as part of finalizers, otherwise we have problems in nested (inherited) structs, where drop is run for both parent and child, but we only added associated memory for child (on init_reflector) which already included size of parent. The only exception here is promise, because it is RCed and not finalized. Testing: Tested locally that it fixes speedometer. Fixes #42269 --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
1418 lines
52 KiB
Rust
1418 lines
52 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 std::cell::Cell;
|
|
use std::ptr;
|
|
use std::rc::Rc;
|
|
|
|
use base::generic_channel;
|
|
use base::generic_channel::GenericSend;
|
|
use base::id::{BrowsingContextId, PipelineId, WebViewId};
|
|
use constellation_traits::{
|
|
AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
|
|
ScriptToConstellationMessage,
|
|
};
|
|
use content_security_policy::sandboxing_directive::SandboxingFlagSet;
|
|
use dom_struct::dom_struct;
|
|
use html5ever::local_name;
|
|
use indexmap::map::IndexMap;
|
|
use ipc_channel::ipc;
|
|
use js::JSCLASS_IS_GLOBAL;
|
|
use js::glue::{
|
|
CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
|
|
ProxyTraps, SetProxyReservedSlot,
|
|
};
|
|
use js::jsapi::{
|
|
GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
|
|
HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
|
|
JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
|
|
JS_HasPropertyById, JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSObject,
|
|
JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer, MutableHandle as RawMutableHandle,
|
|
MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
|
|
ObjectOpResult, PropertyDescriptor,
|
|
};
|
|
use js::jsval::{NullValue, PrivateValue, UndefinedValue};
|
|
use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
|
|
use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
|
|
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
|
use net_traits::request::Referrer;
|
|
use script_bindings::reflector::MutDomObject;
|
|
use script_traits::NewPipelineInfo;
|
|
use serde::{Deserialize, Serialize};
|
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
|
use storage_traits::webstorage_thread::WebStorageThreadMsg;
|
|
use style::attr::parse_integer;
|
|
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
|
|
use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
|
|
use crate::dom::bindings::inheritance::Castable;
|
|
use crate::dom::bindings::proxyhandler::set_property_descriptor;
|
|
use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
|
|
use crate::dom::bindings::root::{Dom, DomRoot};
|
|
use crate::dom::bindings::str::{DOMString, USVString};
|
|
use crate::dom::bindings::trace::JSTraceable;
|
|
use crate::dom::bindings::utils::get_array_index_from_id;
|
|
use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
|
|
use crate::dom::document::Document;
|
|
use crate::dom::element::Element;
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::window::Window;
|
|
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
|
|
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
|
use crate::script_thread::{ScriptThread, with_script_thread};
|
|
use crate::script_window_proxies::ScriptWindowProxies;
|
|
|
|
#[dom_struct]
|
|
// NOTE: the browsing context for a window is managed in two places:
|
|
// here, in script, but also in the constellation. The constellation
|
|
// manages the session history, which in script is accessed through
|
|
// History objects, messaging the constellation.
|
|
pub(crate) struct WindowProxy {
|
|
/// The JS WindowProxy object.
|
|
/// Unlike other reflectors, we mutate this field because
|
|
/// we have to brain-transplant the reflector when the WindowProxy
|
|
/// changes Window.
|
|
reflector: Reflector,
|
|
|
|
/// The id of the browsing context.
|
|
/// In the case that this is a nested browsing context, this is the id
|
|
/// of the container.
|
|
#[no_trace]
|
|
browsing_context_id: BrowsingContextId,
|
|
|
|
// https://html.spec.whatwg.org/multipage/#opener-browsing-context
|
|
#[no_trace]
|
|
opener: Option<BrowsingContextId>,
|
|
|
|
/// The frame id of the top-level ancestor browsing context.
|
|
/// In the case that this is a top-level window, this is our id.
|
|
#[no_trace]
|
|
webview_id: WebViewId,
|
|
|
|
/// The name of the browsing context (sometimes, but not always,
|
|
/// equal to the name of a container element)
|
|
name: DomRefCell<DOMString>,
|
|
/// The pipeline id of the currently active document.
|
|
/// May be None, when the currently active document is in another script thread.
|
|
/// We do not try to keep the pipeline id for documents in other threads,
|
|
/// as this would require the constellation notifying many script threads about
|
|
/// the change, which could be expensive.
|
|
#[no_trace]
|
|
currently_active: Cell<Option<PipelineId>>,
|
|
|
|
/// Has the browsing context been discarded?
|
|
discarded: Cell<bool>,
|
|
|
|
/// Has the browsing context been disowned?
|
|
disowned: Cell<bool>,
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#is-closing>
|
|
is_closing: Cell<bool>,
|
|
|
|
/// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
|
|
/// this will be the [`Element`] of the `<iframe>` element in the realm of the
|
|
/// parent page. Otherwise, it is `None`.
|
|
frame_element: Option<Dom<Element>>,
|
|
|
|
/// The parent browsing context's window proxy, if this is a nested browsing context
|
|
parent: Option<Dom<WindowProxy>>,
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
|
|
delaying_load_events_mode: Cell<bool>,
|
|
|
|
/// The creator browsing context's url.
|
|
#[no_trace]
|
|
creator_url: Option<ServoUrl>,
|
|
|
|
/// The creator browsing context's origin.
|
|
#[no_trace]
|
|
creator_origin: Option<ImmutableOrigin>,
|
|
|
|
/// The window proxies the script thread knows.
|
|
#[conditional_malloc_size_of]
|
|
script_window_proxies: Rc<ScriptWindowProxies>,
|
|
}
|
|
|
|
impl WindowProxy {
|
|
fn new_inherited(
|
|
browsing_context_id: BrowsingContextId,
|
|
webview_id: WebViewId,
|
|
currently_active: Option<PipelineId>,
|
|
frame_element: Option<&Element>,
|
|
parent: Option<&WindowProxy>,
|
|
opener: Option<BrowsingContextId>,
|
|
creator: CreatorBrowsingContextInfo,
|
|
) -> WindowProxy {
|
|
let name = frame_element.map_or(DOMString::new(), |e| {
|
|
e.get_string_attribute(&local_name!("name"))
|
|
});
|
|
WindowProxy {
|
|
reflector: Reflector::new(),
|
|
browsing_context_id,
|
|
webview_id,
|
|
name: DomRefCell::new(name),
|
|
currently_active: Cell::new(currently_active),
|
|
discarded: Cell::new(false),
|
|
disowned: Cell::new(false),
|
|
is_closing: Cell::new(false),
|
|
frame_element: frame_element.map(Dom::from_ref),
|
|
parent: parent.map(Dom::from_ref),
|
|
delaying_load_events_mode: Cell::new(false),
|
|
opener,
|
|
creator_url: creator.url,
|
|
creator_origin: creator.origin,
|
|
script_window_proxies: ScriptThread::window_proxies(),
|
|
}
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
pub(crate) fn new(
|
|
window: &Window,
|
|
browsing_context_id: BrowsingContextId,
|
|
webview_id: WebViewId,
|
|
frame_element: Option<&Element>,
|
|
parent: Option<&WindowProxy>,
|
|
opener: Option<BrowsingContextId>,
|
|
creator: CreatorBrowsingContextInfo,
|
|
) -> DomRoot<WindowProxy> {
|
|
unsafe {
|
|
let handler = window.windowproxy_handler();
|
|
|
|
let cx = GlobalScope::get_cx();
|
|
let window_jsobject = window.reflector().get_jsobject();
|
|
assert!(!window_jsobject.get().is_null());
|
|
assert_ne!(
|
|
((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
|
|
0
|
|
);
|
|
let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
|
|
|
|
// Create a new window proxy.
|
|
rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
|
|
assert!(!js_proxy.is_null());
|
|
|
|
// Create a new browsing context.
|
|
|
|
let current = Some(window.upcast::<GlobalScope>().pipeline_id());
|
|
let window_proxy = Box::new(WindowProxy::new_inherited(
|
|
browsing_context_id,
|
|
webview_id,
|
|
current,
|
|
frame_element,
|
|
parent,
|
|
opener,
|
|
creator,
|
|
));
|
|
|
|
// The window proxy owns the browsing context.
|
|
// When we finalize the window proxy, it drops the browsing context it owns.
|
|
SetProxyReservedSlot(
|
|
js_proxy.get(),
|
|
0,
|
|
&PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
|
|
);
|
|
|
|
// Notify the JS engine about the new window proxy binding.
|
|
SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
|
|
|
|
// Set the reflector.
|
|
debug!(
|
|
"Initializing reflector of {:p} to {:p}.",
|
|
window_proxy,
|
|
js_proxy.get()
|
|
);
|
|
window_proxy
|
|
.reflector
|
|
.init_reflector::<WindowProxy>(js_proxy.get());
|
|
DomRoot::from_ref(&*Box::into_raw(window_proxy))
|
|
}
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
pub(crate) fn new_dissimilar_origin(
|
|
global_to_clone_from: &GlobalScope,
|
|
browsing_context_id: BrowsingContextId,
|
|
webview_id: WebViewId,
|
|
parent: Option<&WindowProxy>,
|
|
opener: Option<BrowsingContextId>,
|
|
creator: CreatorBrowsingContextInfo,
|
|
) -> DomRoot<WindowProxy> {
|
|
unsafe {
|
|
let handler = WindowProxyHandler::x_origin_proxy_handler();
|
|
|
|
let cx = GlobalScope::get_cx();
|
|
|
|
// Create a new browsing context.
|
|
let window_proxy = Box::new(WindowProxy::new_inherited(
|
|
browsing_context_id,
|
|
webview_id,
|
|
None,
|
|
None,
|
|
parent,
|
|
opener,
|
|
creator,
|
|
));
|
|
|
|
// Create a new dissimilar-origin window.
|
|
let window = DissimilarOriginWindow::new(global_to_clone_from, &window_proxy);
|
|
let window_jsobject = window.reflector().get_jsobject();
|
|
assert!(!window_jsobject.get().is_null());
|
|
assert_ne!(
|
|
((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
|
|
0
|
|
);
|
|
let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
|
|
|
|
// Create a new window proxy.
|
|
rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
|
|
assert!(!js_proxy.is_null());
|
|
|
|
// The window proxy owns the browsing context.
|
|
// When we finalize the window proxy, it drops the browsing context it owns.
|
|
SetProxyReservedSlot(
|
|
js_proxy.get(),
|
|
0,
|
|
&PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
|
|
);
|
|
|
|
// Notify the JS engine about the new window proxy binding.
|
|
SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
|
|
|
|
// Set the reflector.
|
|
debug!(
|
|
"Initializing reflector of {:p} to {:p}.",
|
|
window_proxy,
|
|
js_proxy.get()
|
|
);
|
|
window_proxy
|
|
.reflector
|
|
.init_reflector::<WindowProxy>(js_proxy.get());
|
|
DomRoot::from_ref(&*Box::into_raw(window_proxy))
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#auxiliary-browsing-context>
|
|
fn create_auxiliary_browsing_context(
|
|
&self,
|
|
name: DOMString,
|
|
noopener: bool,
|
|
) -> Option<DomRoot<WindowProxy>> {
|
|
let (response_sender, response_receiver) = ipc::channel().unwrap();
|
|
let window = self
|
|
.currently_active
|
|
.get()
|
|
.and_then(ScriptThread::find_document)
|
|
.map(|doc| DomRoot::from_ref(doc.window()))
|
|
.unwrap();
|
|
|
|
let document = self
|
|
.currently_active
|
|
.get()
|
|
.and_then(ScriptThread::find_document)
|
|
.expect("A WindowProxy creating an auxiliary to have an active document");
|
|
let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
|
|
let load_data = LoadData::new(
|
|
LoadOrigin::Script(document.origin().snapshot()),
|
|
blank_url,
|
|
Some(document.base_url()),
|
|
// This has the effect of ensuring that the new `about:blank` URL has the
|
|
// same origin as the `Document` that is creating the new browsing context.
|
|
Some(window.pipeline_id()),
|
|
document.global().get_referrer(),
|
|
document.get_referrer_policy(),
|
|
None, // Doesn't inherit secure context
|
|
None,
|
|
false,
|
|
// There are no sandboxing restrictions when creating auxiliary browsing contexts.
|
|
SandboxingFlagSet::empty(),
|
|
);
|
|
let load_info = AuxiliaryWebViewCreationRequest {
|
|
load_data: load_data.clone(),
|
|
opener_webview_id: window.webview_id(),
|
|
opener_pipeline_id: self.currently_active.get().unwrap(),
|
|
response_sender,
|
|
};
|
|
let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
|
|
window.send_to_constellation(constellation_msg);
|
|
|
|
let response = response_receiver.recv().unwrap()?;
|
|
let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
|
|
let new_pipeline_info = NewPipelineInfo {
|
|
parent_info: None,
|
|
new_pipeline_id: response.new_pipeline_id,
|
|
browsing_context_id: new_browsing_context_id,
|
|
webview_id: response.new_webview_id,
|
|
opener: Some(self.browsing_context_id),
|
|
load_data,
|
|
viewport_details: window.viewport_details(),
|
|
user_content_manager_id: response.user_content_manager_id,
|
|
// Use the current `WebView`'s theme initially, but the embedder may
|
|
// change this later.
|
|
theme: window.theme(),
|
|
};
|
|
|
|
with_script_thread(|script_thread| {
|
|
script_thread.spawn_pipeline(new_pipeline_info);
|
|
});
|
|
|
|
let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
|
|
.and_then(|doc| doc.browsing_context())?;
|
|
if name.to_lowercase() != "_blank" {
|
|
new_window_proxy.set_name(name);
|
|
}
|
|
if noopener {
|
|
new_window_proxy.disown();
|
|
} else {
|
|
// After creating a new auxiliary browsing context and document,
|
|
// the session storage is copied over.
|
|
// See https://html.spec.whatwg.org/multipage/#the-sessionstorage-attribute
|
|
|
|
let (sender, receiver) = generic_channel::channel().unwrap();
|
|
|
|
let msg = WebStorageThreadMsg::Clone {
|
|
sender,
|
|
src: window.window_proxy().webview_id(),
|
|
dest: response.new_webview_id,
|
|
};
|
|
|
|
GenericSend::send(document.global().storage_threads(), msg).unwrap();
|
|
receiver.recv().unwrap();
|
|
}
|
|
Some(new_window_proxy)
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
|
|
pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
|
|
self.delaying_load_events_mode.get()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
|
|
pub(crate) fn start_delaying_load_events_mode(&self) {
|
|
self.delaying_load_events_mode.set(true);
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
|
|
pub(crate) fn stop_delaying_load_events_mode(&self) {
|
|
self.delaying_load_events_mode.set(false);
|
|
if let Some(document) = self.document() {
|
|
if !document.loader().events_inhibited() {
|
|
ScriptThread::mark_document_with_no_blocked_loads(&document);
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#disowned-its-opener
|
|
pub(crate) fn disown(&self) {
|
|
self.disowned.set(true);
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-window-close>
|
|
/// Step 3.1, set BCs `is_closing` to true.
|
|
pub(crate) fn close(&self) {
|
|
self.is_closing.set(true);
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#is-closing>
|
|
pub(crate) fn is_closing(&self) -> bool {
|
|
self.is_closing.get()
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
// https://html.spec.whatwg.org/multipage/#dom-opener
|
|
pub(crate) fn opener(
|
|
&self,
|
|
cx: *mut JSContext,
|
|
in_realm_proof: InRealm,
|
|
mut retval: MutableHandleValue,
|
|
) {
|
|
if self.disowned.get() {
|
|
return retval.set(NullValue());
|
|
}
|
|
let opener_id = match self.opener {
|
|
Some(opener_browsing_context_id) => opener_browsing_context_id,
|
|
None => return retval.set(NullValue()),
|
|
};
|
|
let parent_browsing_context = self.parent.as_deref();
|
|
let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
|
|
Some(window_proxy) => window_proxy,
|
|
None => {
|
|
let sender_pipeline_id = self.currently_active().unwrap();
|
|
match ScriptThread::get_top_level_for_browsing_context(
|
|
self.webview_id(),
|
|
sender_pipeline_id,
|
|
opener_id,
|
|
) {
|
|
Some(opener_top_id) => {
|
|
let global_to_clone_from =
|
|
unsafe { GlobalScope::from_context(cx, in_realm_proof) };
|
|
let creator =
|
|
CreatorBrowsingContextInfo::from(parent_browsing_context, None);
|
|
WindowProxy::new_dissimilar_origin(
|
|
&global_to_clone_from,
|
|
opener_id,
|
|
opener_top_id,
|
|
None,
|
|
None,
|
|
creator,
|
|
)
|
|
},
|
|
None => return retval.set(NullValue()),
|
|
}
|
|
},
|
|
};
|
|
if opener_proxy.is_browsing_context_discarded() {
|
|
return retval.set(NullValue());
|
|
}
|
|
unsafe { opener_proxy.to_jsval(cx, retval) };
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#window-open-steps
|
|
pub(crate) fn open(
|
|
&self,
|
|
url: USVString,
|
|
target: DOMString,
|
|
features: DOMString,
|
|
can_gc: CanGc,
|
|
) -> Fallible<Option<DomRoot<WindowProxy>>> {
|
|
// Note: this does not map to the spec,
|
|
// but it does prevent a panic at the constellation because the browsing context
|
|
// has already been discarded.
|
|
// See issue: #39716 for the original problem,
|
|
// and https://github.com/whatwg/html/issues/11797 for a discussion at the level of the spec.
|
|
if self.discarded.get() {
|
|
return Ok(None);
|
|
}
|
|
// Step 5. If target is the empty string, then set target to "_blank".
|
|
let non_empty_target = if target.is_empty() {
|
|
DOMString::from("_blank")
|
|
} else {
|
|
target
|
|
};
|
|
// Step 6. Let tokenizedFeatures be the result of tokenizing features.
|
|
let tokenized_features = tokenize_open_features(features);
|
|
// Step 7 - 8.
|
|
// If tokenizedFeatures["noreferrer"] exists, then set noreferrer to
|
|
// the result of parsing tokenizedFeatures["noreferrer"] as a boolean feature.
|
|
let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
|
|
|
|
// Step 9. Let noopener be the result of getting noopener for window
|
|
// open with sourceDocument, tokenizedFeatures, and urlRecord.
|
|
let noopener = if noreferrer {
|
|
true
|
|
} else {
|
|
parse_open_feature_boolean(&tokenized_features, "noopener")
|
|
};
|
|
// (TODO) Step 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"].
|
|
|
|
// (TODO) Step 11. Let referrerPolicy be the empty string.
|
|
// (TODO) Step 12. If noreferrer is true, then set referrerPolicy to "no-referrer".
|
|
|
|
// Step 13 - 14
|
|
// Let targetNavigable and windowType be the result of applying the rules for
|
|
// choosing a navigable given target, sourceDocument's node navigable, and noopener.
|
|
// If targetNavigable is null, then return null.
|
|
let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
|
|
(Some(chosen), new) => (chosen, new),
|
|
(None, _) => return Ok(None),
|
|
};
|
|
// TODO Step 15.2, Set up browsing context features for targetNavigable's
|
|
// active browsing context given tokenizedFeatures.
|
|
let target_document = match chosen.document() {
|
|
Some(target_document) => target_document,
|
|
None => return Ok(None),
|
|
};
|
|
let has_trustworthy_ancestor_origin = if new {
|
|
target_document.has_trustworthy_ancestor_or_current_origin()
|
|
} else {
|
|
false
|
|
};
|
|
let target_window = target_document.window();
|
|
// Step 15.3 and 15.4 will have happened elsewhere,
|
|
// since we've created a new browsing context and loaded it with about:blank.
|
|
if !url.is_empty() {
|
|
let existing_document = self
|
|
.currently_active
|
|
.get()
|
|
.and_then(ScriptThread::find_document)
|
|
.unwrap();
|
|
let url = match existing_document.url().join(&url) {
|
|
Ok(url) => url,
|
|
Err(_) => return Err(Error::Syntax(None)),
|
|
};
|
|
let referrer = if noreferrer {
|
|
Referrer::NoReferrer
|
|
} else {
|
|
target_window.as_global_scope().get_referrer()
|
|
};
|
|
// Propagate CSP list and about-base-url from opener to new document
|
|
let csp_list = existing_document.get_csp_list();
|
|
target_document.set_csp_list(csp_list);
|
|
|
|
// Step 15.5 Otherwise, navigate targetNavigable to urlRecord using sourceDocument,
|
|
// with referrerPolicy set to referrerPolicy and exceptionsEnabled set to true.
|
|
// FIXME: referrerPolicy may not be used properly here. exceptionsEnabled not used.
|
|
let mut load_data = LoadData::new(
|
|
LoadOrigin::Script(existing_document.origin().snapshot()),
|
|
url,
|
|
target_document.about_base_url(),
|
|
Some(target_window.pipeline_id()),
|
|
referrer,
|
|
target_document.get_referrer_policy(),
|
|
Some(target_window.as_global_scope().is_secure_context()),
|
|
Some(target_document.insecure_requests_policy()),
|
|
has_trustworthy_ancestor_origin,
|
|
target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
|
|
);
|
|
|
|
// Handle javascript: URLs specially to report CSP violations to the source window
|
|
// https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
|
|
if load_data.url.scheme() == "javascript" {
|
|
let existing_global = existing_document.global();
|
|
|
|
// Check CSP and report violations to the source (existing) window
|
|
if !ScriptThread::can_navigate_to_javascript_url(
|
|
&existing_global,
|
|
target_window.as_global_scope(),
|
|
&mut load_data,
|
|
None,
|
|
can_gc,
|
|
) {
|
|
// CSP blocked the navigation, don't proceed
|
|
return Ok(target_document.browsing_context());
|
|
}
|
|
}
|
|
|
|
let history_handling = if new {
|
|
NavigationHistoryBehavior::Replace
|
|
} else {
|
|
NavigationHistoryBehavior::Push
|
|
};
|
|
target_window.load_url(history_handling, false, load_data, can_gc);
|
|
}
|
|
// Step 17 (Dis-owning has been done in create_auxiliary_browsing_context).
|
|
if noopener {
|
|
return Ok(None);
|
|
}
|
|
// Step 18
|
|
Ok(target_document.browsing_context())
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
|
pub(crate) fn choose_browsing_context(
|
|
&self,
|
|
name: DOMString,
|
|
noopener: bool,
|
|
) -> (Option<DomRoot<WindowProxy>>, bool) {
|
|
match name.to_lowercase().as_ref() {
|
|
"" | "_self" => {
|
|
// Step 3.
|
|
(Some(DomRoot::from_ref(self)), false)
|
|
},
|
|
"_parent" => {
|
|
// Step 4
|
|
if let Some(parent) = self.parent() {
|
|
return (Some(DomRoot::from_ref(parent)), false);
|
|
}
|
|
(None, false)
|
|
},
|
|
"_top" => {
|
|
// Step 5
|
|
(Some(DomRoot::from_ref(self.top())), false)
|
|
},
|
|
"_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
|
|
_ => {
|
|
// Step 6.
|
|
// TODO: expand the search to all 'familiar' bc,
|
|
// including auxiliaries familiar by way of their opener.
|
|
// See https://html.spec.whatwg.org/multipage/#familiar-with
|
|
match ScriptThread::find_window_proxy_by_name(&name) {
|
|
Some(proxy) => (Some(proxy), false),
|
|
None => (self.create_auxiliary_browsing_context(name, noopener), true),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_auxiliary(&self) -> bool {
|
|
self.opener.is_some()
|
|
}
|
|
|
|
pub(crate) fn discard_browsing_context(&self) {
|
|
self.discarded.set(true);
|
|
}
|
|
|
|
pub(crate) fn is_browsing_context_discarded(&self) -> bool {
|
|
self.discarded.get()
|
|
}
|
|
|
|
pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
|
|
self.browsing_context_id
|
|
}
|
|
|
|
pub(crate) fn webview_id(&self) -> WebViewId {
|
|
self.webview_id
|
|
}
|
|
|
|
/// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
|
|
/// this will return an [`Element`] of the `<iframe>` element in the realm of the parent
|
|
/// page.
|
|
pub(crate) fn frame_element(&self) -> Option<&Element> {
|
|
self.frame_element.as_deref()
|
|
}
|
|
|
|
pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
|
|
self.currently_active
|
|
.get()
|
|
.and_then(ScriptThread::find_document)
|
|
}
|
|
|
|
pub(crate) fn parent(&self) -> Option<&WindowProxy> {
|
|
self.parent.as_deref()
|
|
}
|
|
|
|
pub(crate) fn top(&self) -> &WindowProxy {
|
|
let mut result = self;
|
|
while let Some(parent) = result.parent() {
|
|
result = parent;
|
|
}
|
|
result
|
|
}
|
|
|
|
/// Run [the focusing steps] with this browsing context.
|
|
///
|
|
/// [the focusing steps]: https://html.spec.whatwg.org/multipage/#focusing-steps
|
|
pub fn focus(&self) {
|
|
debug!(
|
|
"Requesting the constellation to initiate a focus operation for \
|
|
browsing context {}",
|
|
self.browsing_context_id()
|
|
);
|
|
self.global()
|
|
.script_to_constellation_chan()
|
|
.send(ScriptToConstellationMessage::FocusRemoteDocument(
|
|
self.browsing_context_id(),
|
|
))
|
|
.unwrap();
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
/// Change the Window that this WindowProxy resolves to.
|
|
// TODO: support setting the window proxy to a dummy value,
|
|
// to handle the case when the active document is in another script thread.
|
|
fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
|
|
unsafe {
|
|
debug!("Setting window of {:p}.", self);
|
|
|
|
let cx = GlobalScope::get_cx();
|
|
let window_jsobject = window.reflector().get_jsobject();
|
|
let old_js_proxy = self.reflector.get_jsobject();
|
|
assert!(!window_jsobject.get().is_null());
|
|
assert_ne!(
|
|
((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
|
|
0
|
|
);
|
|
let _ac = enter_realm(window);
|
|
|
|
// The old window proxy no longer owns this browsing context.
|
|
SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
|
|
|
|
// Brain transplant the window proxy. Brain transplantation is
|
|
// usually done to move a window proxy between compartments, but
|
|
// that's not what we are doing here. We need to do this just
|
|
// because we want to replace the wrapper's `ProxyTraps`, but we
|
|
// don't want to update its identity.
|
|
rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
|
|
// Explicitly set this slot to a null pointer in case a GC occurs before we
|
|
// are ready to set it to a real value.
|
|
SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
|
|
debug!(
|
|
"Transplanting proxy from {:p} to {:p}.",
|
|
old_js_proxy.get(),
|
|
new_js_proxy.get()
|
|
);
|
|
rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
|
|
debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
|
|
|
|
// Transfer ownership of this browsing context from the old window proxy to the new one.
|
|
SetProxyReservedSlot(
|
|
new_js_proxy.get(),
|
|
0,
|
|
&PrivateValue(self as *const _ as *const libc::c_void),
|
|
);
|
|
|
|
// Notify the JS engine about the new window proxy binding.
|
|
SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
|
|
|
|
// Update the reflector.
|
|
debug!(
|
|
"Setting reflector of {:p} to {:p}.",
|
|
self,
|
|
new_js_proxy.get()
|
|
);
|
|
self.reflector.rootable().set(new_js_proxy.get());
|
|
}
|
|
}
|
|
|
|
pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
|
|
if let Some(pipeline_id) = self.currently_active() {
|
|
if pipeline_id == window.pipeline_id() {
|
|
return debug!(
|
|
"Attempt to set the currently active window to the currently active window."
|
|
);
|
|
}
|
|
}
|
|
|
|
let global_scope = window.as_global_scope();
|
|
self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
|
|
self.currently_active.set(Some(global_scope.pipeline_id()));
|
|
}
|
|
|
|
pub(crate) fn unset_currently_active(&self, can_gc: CanGc) {
|
|
if self.currently_active().is_none() {
|
|
return debug!(
|
|
"Attempt to unset the currently active window on a windowproxy that does not have one."
|
|
);
|
|
}
|
|
let globalscope = self.global();
|
|
let window = DissimilarOriginWindow::new(&globalscope, self);
|
|
self.set_window(
|
|
window.upcast(),
|
|
WindowProxyHandler::x_origin_proxy_handler(),
|
|
can_gc,
|
|
);
|
|
self.currently_active.set(None);
|
|
}
|
|
|
|
pub(crate) fn currently_active(&self) -> Option<PipelineId> {
|
|
self.currently_active.get()
|
|
}
|
|
|
|
pub(crate) fn get_name(&self) -> DOMString {
|
|
self.name.borrow().clone()
|
|
}
|
|
|
|
pub(crate) fn set_name(&self, name: DOMString) {
|
|
*self.name.borrow_mut() = name;
|
|
}
|
|
}
|
|
|
|
/// A browsing context can have a creator browsing context, the browsing context that
|
|
/// was responsible for its creation. If a browsing context has a parent browsing context,
|
|
/// then that is its creator browsing context. Otherwise, if the browsing context has an
|
|
/// opener browsing context, then that is its creator browsing context. Otherwise, the
|
|
/// browsing context has no creator browsing context.
|
|
///
|
|
/// If a browsing context A has a creator browsing context, then the Document that was the
|
|
/// active document of that creator browsing context at the time A was created is the creator
|
|
/// Document.
|
|
///
|
|
/// See: <https://html.spec.whatwg.org/multipage/#creating-browsing-contexts>
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub(crate) struct CreatorBrowsingContextInfo {
|
|
/// Creator document URL.
|
|
url: Option<ServoUrl>,
|
|
|
|
/// Creator document origin.
|
|
origin: Option<ImmutableOrigin>,
|
|
}
|
|
|
|
impl CreatorBrowsingContextInfo {
|
|
pub(crate) fn from(
|
|
parent: Option<&WindowProxy>,
|
|
opener: Option<&WindowProxy>,
|
|
) -> CreatorBrowsingContextInfo {
|
|
let creator = match (parent, opener) {
|
|
(Some(parent), _) => parent.document(),
|
|
(None, Some(opener)) => opener.document(),
|
|
(None, None) => None,
|
|
};
|
|
|
|
let url = creator.as_deref().map(|document| document.url());
|
|
let origin = creator
|
|
.as_deref()
|
|
.map(|document| document.origin().immutable().clone());
|
|
|
|
CreatorBrowsingContextInfo { url, origin }
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize>
|
|
fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
|
|
let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
|
|
// Step 1
|
|
let mut tokenized_features = IndexMap::new();
|
|
// Step 2
|
|
let features = features.str();
|
|
let mut iter = features.chars();
|
|
let mut cur = iter.next();
|
|
|
|
// Step 3
|
|
while cur.is_some() {
|
|
// Step 3.1 & 3.2
|
|
let mut name = String::new();
|
|
let mut value = String::new();
|
|
// Step 3.3
|
|
while let Some(cur_char) = cur {
|
|
if !is_feature_sep(cur_char) {
|
|
break;
|
|
}
|
|
cur = iter.next();
|
|
}
|
|
// Step 3.4
|
|
while let Some(cur_char) = cur {
|
|
if is_feature_sep(cur_char) {
|
|
break;
|
|
}
|
|
name.push(cur_char.to_ascii_lowercase());
|
|
cur = iter.next();
|
|
}
|
|
// Step 3.5
|
|
let normalized_name = String::from(match name.as_ref() {
|
|
"screenx" => "left",
|
|
"screeny" => "top",
|
|
"innerwidth" => "width",
|
|
"innerheight" => "height",
|
|
_ => name.as_ref(),
|
|
});
|
|
// Step 3.6
|
|
while let Some(cur_char) = cur {
|
|
if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
|
|
break;
|
|
}
|
|
cur = iter.next();
|
|
}
|
|
// Step 3.7
|
|
if cur.is_some() && is_feature_sep(cur.unwrap()) {
|
|
// Step 3.7.1
|
|
while let Some(cur_char) = cur {
|
|
if !is_feature_sep(cur_char) || cur_char == ',' {
|
|
break;
|
|
}
|
|
cur = iter.next();
|
|
}
|
|
// Step 3.7.2
|
|
while let Some(cur_char) = cur {
|
|
if is_feature_sep(cur_char) {
|
|
break;
|
|
}
|
|
value.push(cur_char.to_ascii_lowercase());
|
|
cur = iter.next();
|
|
}
|
|
}
|
|
// Step 3.8
|
|
if !name.is_empty() {
|
|
tokenized_features.insert(normalized_name, value);
|
|
}
|
|
}
|
|
// Step 4
|
|
tokenized_features
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean>
|
|
fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
|
|
if let Some(value) = tokenized_features.get(name) {
|
|
// Step 1 & 2
|
|
if value.is_empty() || value == "yes" {
|
|
return true;
|
|
}
|
|
// Step 3 & 4
|
|
if let Ok(int) = parse_integer(value.chars()) {
|
|
return int != 0;
|
|
}
|
|
}
|
|
// Step 5
|
|
false
|
|
}
|
|
|
|
// This is only called from extern functions,
|
|
// there's no use using the lifetimed handles here.
|
|
// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
|
|
#[expect(unsafe_code)]
|
|
#[expect(non_snake_case)]
|
|
unsafe fn GetSubframeWindowProxy(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
) -> Option<(DomRoot<WindowProxy>, u32)> {
|
|
let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
|
|
if let Some(index) = index {
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
let script_window_proxies = ScriptThread::window_proxies();
|
|
if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
|
|
let browsing_context_id = win.window_proxy().browsing_context_id();
|
|
let (result_sender, result_receiver) = ipc::channel().unwrap();
|
|
|
|
let _ = win.as_global_scope().script_to_constellation_chan().send(
|
|
ScriptToConstellationMessage::GetChildBrowsingContextId(
|
|
browsing_context_id,
|
|
index as usize,
|
|
result_sender,
|
|
),
|
|
);
|
|
return result_receiver
|
|
.recv()
|
|
.ok()
|
|
.and_then(|maybe_bcid| maybe_bcid)
|
|
.and_then(|id| script_window_proxies.find_window_proxy(id))
|
|
.map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
|
|
} else if let Ok(win) =
|
|
root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
|
|
{
|
|
let browsing_context_id = win.window_proxy().browsing_context_id();
|
|
let (result_sender, result_receiver) = ipc::channel().unwrap();
|
|
|
|
let _ = win.global().script_to_constellation_chan().send(
|
|
ScriptToConstellationMessage::GetChildBrowsingContextId(
|
|
browsing_context_id,
|
|
index as usize,
|
|
result_sender,
|
|
),
|
|
);
|
|
return result_receiver
|
|
.recv()
|
|
.ok()
|
|
.and_then(|maybe_bcid| maybe_bcid)
|
|
.and_then(|id| script_window_proxies.find_window_proxy(id))
|
|
.map(|proxy| (proxy, JSPROP_READONLY as u32));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn get_own_property_descriptor(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
desc: RawMutableHandle<PropertyDescriptor>,
|
|
is_none: *mut bool,
|
|
) -> bool {
|
|
let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
|
|
if let Some((window, attrs)) = window {
|
|
rooted!(in(cx) let mut val = UndefinedValue());
|
|
unsafe { window.to_jsval(cx, val.handle_mut()) };
|
|
set_property_descriptor(
|
|
unsafe { MutableHandle::from_raw(desc) },
|
|
val.handle(),
|
|
attrs,
|
|
unsafe { &mut *is_none },
|
|
);
|
|
return true;
|
|
}
|
|
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn define_property(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
desc: RawHandle<PropertyDescriptor>,
|
|
res: *mut ObjectOpResult,
|
|
) -> bool {
|
|
if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
|
|
// Spec says to Reject whether this is a supported index or not,
|
|
// since we have no indexed setter or indexed creator. That means
|
|
// throwing in strict mode (FIXME: Bug 828137), doing nothing in
|
|
// non-strict mode.
|
|
unsafe {
|
|
(*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn has(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
bp: *mut bool,
|
|
) -> bool {
|
|
let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
|
|
if window.is_some() {
|
|
unsafe { *bp = true };
|
|
return true;
|
|
}
|
|
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
let mut found = false;
|
|
if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
|
|
return false;
|
|
}
|
|
|
|
unsafe { *bp = found };
|
|
true
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn get(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
receiver: RawHandleValue,
|
|
id: RawHandleId,
|
|
vp: RawMutableHandleValue,
|
|
) -> bool {
|
|
let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
|
|
if let Some((window, _attrs)) = window {
|
|
unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
|
|
return true;
|
|
}
|
|
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn set(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
v: RawHandleValue,
|
|
receiver: RawHandleValue,
|
|
res: *mut ObjectOpResult,
|
|
) -> bool {
|
|
if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
|
|
// Reject (which means throw if and only if strict) the set.
|
|
unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
|
|
return true;
|
|
}
|
|
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn get_prototype_if_ordinary(
|
|
_: *mut JSContext,
|
|
_: RawHandleObject,
|
|
is_ordinary: *mut bool,
|
|
_: RawMutableHandleObject,
|
|
) -> bool {
|
|
// Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
|
|
//
|
|
// https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof
|
|
//
|
|
// We nonetheless can implement it with a static [[Prototype]], because
|
|
// wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
|
|
// all non-ordinary behavior.
|
|
//
|
|
// But from a spec point of view, it's the exact same object in both cases --
|
|
// only the observer's changed. So this getPrototypeIfOrdinary trap on the
|
|
// non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
|
|
// usually means ordinary.
|
|
unsafe { *is_ordinary = false };
|
|
true
|
|
}
|
|
|
|
static PROXY_TRAPS: ProxyTraps = ProxyTraps {
|
|
// TODO: These traps should change their behavior depending on
|
|
// `IsPlatformObjectSameOrigin(this.[[Window]])`
|
|
enter: None,
|
|
getOwnPropertyDescriptor: Some(get_own_property_descriptor),
|
|
defineProperty: Some(define_property),
|
|
ownPropertyKeys: None,
|
|
delete_: None,
|
|
enumerate: None,
|
|
getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
|
|
getPrototype: None, // TODO: return `null` if cross origin-domain
|
|
setPrototype: None,
|
|
setImmutablePrototype: None,
|
|
preventExtensions: None,
|
|
isExtensible: None,
|
|
has: Some(has),
|
|
get: Some(get),
|
|
set: Some(set),
|
|
call: None,
|
|
construct: None,
|
|
hasOwn: None,
|
|
getOwnEnumerablePropertyKeys: None,
|
|
nativeCall: None,
|
|
objectClassIs: None,
|
|
className: None,
|
|
fun_toString: None,
|
|
boxedValue_unbox: None,
|
|
defaultValue: None,
|
|
trace: Some(trace),
|
|
finalize: Some(finalize),
|
|
objectMoved: None,
|
|
isCallable: None,
|
|
isConstructor: None,
|
|
};
|
|
|
|
/// Proxy handler for a WindowProxy.
|
|
/// Has ownership of the inner pointer and deallocates it when it is no longer needed.
|
|
pub(crate) struct WindowProxyHandler(*const libc::c_void);
|
|
|
|
impl MallocSizeOf for WindowProxyHandler {
|
|
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
|
|
// FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs.
|
|
0
|
|
}
|
|
}
|
|
|
|
// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
|
|
#[expect(unsafe_code)]
|
|
unsafe impl Send for WindowProxyHandler {}
|
|
// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
|
|
#[expect(unsafe_code)]
|
|
unsafe impl Sync for WindowProxyHandler {}
|
|
|
|
#[expect(unsafe_code)]
|
|
impl WindowProxyHandler {
|
|
fn new(traps: &ProxyTraps) -> Self {
|
|
// Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak.
|
|
let ptr = unsafe { CreateWrapperProxyHandler(traps) };
|
|
assert!(!ptr.is_null());
|
|
Self(ptr)
|
|
}
|
|
|
|
/// Returns a single, shared WindowProxyHandler that contains XORIGIN_PROXY_TRAPS.
|
|
pub(crate) fn x_origin_proxy_handler() -> &'static Self {
|
|
use std::sync::OnceLock;
|
|
/// We are sharing a single instance for the entire programs here due to lifetime issues.
|
|
/// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
|
|
/// it is safe to free it.
|
|
/// Sharing a single instance should be fine because all methods on this pointer in C++
|
|
/// are const and don't modify its internal state.
|
|
static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
|
|
SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
|
|
}
|
|
|
|
/// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS.
|
|
pub(crate) fn proxy_handler() -> &'static Self {
|
|
use std::sync::OnceLock;
|
|
/// We are sharing a single instance for the entire programs here due to lifetime issues.
|
|
/// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
|
|
/// it is safe to free it.
|
|
/// Sharing a single instance should be fine because all methods on this pointer in C++
|
|
/// are const and don't modify its internal state.
|
|
static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
|
|
SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
|
|
}
|
|
|
|
/// Creates a new WindowProxy object on the C++ side and returns the pointer to it.
|
|
/// The pointer should be owned by the GC.
|
|
pub(crate) fn new_window_proxy(
|
|
&self,
|
|
cx: &crate::script_runtime::JSContext,
|
|
window_jsobject: js::gc::HandleObject,
|
|
) -> *mut JSObject {
|
|
let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
|
|
assert!(!obj.is_null());
|
|
obj
|
|
}
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
impl Drop for WindowProxyHandler {
|
|
fn drop(&mut self) {
|
|
// Safety: Pointer is allocated by corresponding C++ function, owned by this
|
|
// struct and not accessible from outside.
|
|
unsafe {
|
|
DeleteWrapperProxyHandler(self.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The proxy traps for cross-origin windows.
|
|
// These traps often throw security errors, and only pass on calls to methods
|
|
// defined in the DissimilarOriginWindow IDL.
|
|
|
|
// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls
|
|
// to this function should be replaced with those to
|
|
// `report_cross_origin_denial`.
|
|
#[expect(unsafe_code)]
|
|
fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
|
|
if !unsafe { JS_IsExceptionPending(*cx) } {
|
|
let global = unsafe { GlobalScope::from_context(*cx, realm) };
|
|
throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
|
|
}
|
|
false
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn has_xorigin(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
bp: *mut bool,
|
|
) -> bool {
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
|
|
rooted!(in(cx) let target = slot.to_object());
|
|
let mut found = false;
|
|
unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
|
|
if found {
|
|
unsafe { *bp = true };
|
|
true
|
|
} else {
|
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
|
throw_security_error(cx, InRealm::Already(&in_realm_proof))
|
|
}
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn get_xorigin(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
receiver: RawHandleValue,
|
|
id: RawHandleId,
|
|
vp: RawMutableHandleValue,
|
|
) -> bool {
|
|
let mut found = false;
|
|
unsafe { has_xorigin(cx, proxy, id, &mut found) };
|
|
found && unsafe { get(cx, proxy, receiver, id, vp) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn set_xorigin(
|
|
cx: *mut JSContext,
|
|
_: RawHandleObject,
|
|
_: RawHandleId,
|
|
_: RawHandleValue,
|
|
_: RawHandleValue,
|
|
_: *mut ObjectOpResult,
|
|
) -> bool {
|
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
|
throw_security_error(cx, InRealm::Already(&in_realm_proof))
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn delete_xorigin(
|
|
cx: *mut JSContext,
|
|
_: RawHandleObject,
|
|
_: RawHandleId,
|
|
_: *mut ObjectOpResult,
|
|
) -> bool {
|
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
|
throw_security_error(cx, InRealm::Already(&in_realm_proof))
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
#[expect(non_snake_case)]
|
|
unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
|
|
cx: *mut JSContext,
|
|
proxy: RawHandleObject,
|
|
id: RawHandleId,
|
|
desc: RawMutableHandle<PropertyDescriptor>,
|
|
is_none: *mut bool,
|
|
) -> bool {
|
|
let mut found = false;
|
|
unsafe { has_xorigin(cx, proxy, id, &mut found) };
|
|
found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
#[expect(non_snake_case)]
|
|
unsafe extern "C" fn defineProperty_xorigin(
|
|
cx: *mut JSContext,
|
|
_: RawHandleObject,
|
|
_: RawHandleId,
|
|
_: RawHandle<PropertyDescriptor>,
|
|
_: *mut ObjectOpResult,
|
|
) -> bool {
|
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
|
throw_security_error(cx, InRealm::Already(&in_realm_proof))
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
#[expect(non_snake_case)]
|
|
unsafe extern "C" fn preventExtensions_xorigin(
|
|
cx: *mut JSContext,
|
|
_: RawHandleObject,
|
|
_: *mut ObjectOpResult,
|
|
) -> bool {
|
|
let cx = unsafe { SafeJSContext::from_ptr(cx) };
|
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
|
throw_security_error(cx, InRealm::Already(&in_realm_proof))
|
|
}
|
|
|
|
static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
|
|
enter: None,
|
|
getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
|
|
defineProperty: Some(defineProperty_xorigin),
|
|
ownPropertyKeys: None,
|
|
delete_: Some(delete_xorigin),
|
|
enumerate: None,
|
|
getPrototypeIfOrdinary: None,
|
|
getPrototype: None,
|
|
setPrototype: None,
|
|
setImmutablePrototype: None,
|
|
preventExtensions: Some(preventExtensions_xorigin),
|
|
isExtensible: None,
|
|
has: Some(has_xorigin),
|
|
get: Some(get_xorigin),
|
|
set: Some(set_xorigin),
|
|
call: None,
|
|
construct: None,
|
|
hasOwn: Some(has_xorigin),
|
|
getOwnEnumerablePropertyKeys: None,
|
|
nativeCall: None,
|
|
objectClassIs: None,
|
|
className: None,
|
|
fun_toString: None,
|
|
boxedValue_unbox: None,
|
|
defaultValue: None,
|
|
trace: Some(trace),
|
|
finalize: Some(finalize),
|
|
objectMoved: None,
|
|
isCallable: None,
|
|
isConstructor: None,
|
|
};
|
|
|
|
// How WindowProxy objects are garbage collected.
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
|
|
let this = slot.to_private() as *mut WindowProxy;
|
|
if this.is_null() {
|
|
// GC during obj creation or after transplanting.
|
|
return;
|
|
}
|
|
let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
|
|
debug!(
|
|
"WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
|
|
this, jsobject, obj
|
|
);
|
|
let _ = unsafe { Box::from_raw(this) };
|
|
}
|
|
|
|
#[expect(unsafe_code)]
|
|
unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
|
|
let mut slot = UndefinedValue();
|
|
unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
|
|
let this = slot.to_private() as *const WindowProxy;
|
|
if this.is_null() {
|
|
// GC during obj creation or after transplanting.
|
|
return;
|
|
}
|
|
unsafe { (*this).trace(trc) };
|
|
}
|