use std::cell::Cell;
use std::ptr;
use base::id::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId};
use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use html5ever::local_name;
use indexmap::map::IndexMap;
use ipc_channel::ipc;
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, JSAutoRealm, JSContext, JSErrNum, JSObject, JSTracer,
JS_DefinePropertyById, JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo,
JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById, JS_HasPropertyById,
JS_IsExceptionPending, MutableHandle as RawMutableHandle,
MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
ObjectOpResult, PropertyDescriptor, JSPROP_ENUMERATE, JSPROP_READONLY,
};
use js::jsval::{NullValue, PrivateValue, UndefinedValue};
use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
use js::rust::{get_object_class, Handle, MutableHandle, MutableHandleValue};
use js::JSCLASS_IS_GLOBAL;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use net_traits::request::Referrer;
use script_traits::{
AuxiliaryBrowsingContextLoadInfo, LoadData, LoadOrigin, NavigationHistoryBehavior,
NewLayoutInfo, ScriptMsg,
};
use serde::{Deserialize, Serialize};
use servo_url::{ImmutableOrigin, ServoUrl};
use style::attr::parse_integer;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::conversions::{root_from_handleobject, ToJSValConvertible};
use crate::dom::bindings::error::{throw_dom_exception, Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::proxyhandler::set_property_descriptor;
use crate::dom::bindings::reflector::{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, AsVoidPtr};
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::{enter_realm, AlreadyInRealm, InRealm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread;
#[dom_struct]
pub struct WindowProxy {
reflector: Reflector,
#[no_trace]
browsing_context_id: BrowsingContextId,
#[no_trace]
opener: Option<BrowsingContextId>,
#[no_trace]
top_level_browsing_context_id: TopLevelBrowsingContextId,
name: DomRefCell<DOMString>,
#[no_trace]
currently_active: Cell<Option<PipelineId>>,
discarded: Cell<bool>,
disowned: Cell<bool>,
is_closing: Cell<bool>,
frame_element: Option<Dom<Element>>,
parent: Option<Dom<WindowProxy>>,
delaying_load_events_mode: Cell<bool>,
#[no_trace]
creator_base_url: Option<ServoUrl>,
#[no_trace]
creator_url: Option<ServoUrl>,
#[no_trace]
creator_origin: Option<ImmutableOrigin>,
}
impl WindowProxy {
fn new_inherited(
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
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,
top_level_browsing_context_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_base_url: creator.base_url,
creator_url: creator.url,
creator_origin: creator.origin,
}
}
#[allow(unsafe_code)]
pub fn new(
window: &Window,
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
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());
rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
assert!(!js_proxy.is_null());
let current = Some(window.global().pipeline_id());
let window_proxy = Box::new(WindowProxy::new_inherited(
browsing_context_id,
top_level_browsing_context_id,
current,
frame_element,
parent,
opener,
creator,
));
SetProxyReservedSlot(
js_proxy.get(),
0,
&PrivateValue((*window_proxy).as_void_ptr()),
);
SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
debug!(
"Initializing reflector of {:p} to {:p}.",
window_proxy,
js_proxy.get()
);
window_proxy.reflector.set_jsobject(js_proxy.get());
DomRoot::from_ref(&*Box::into_raw(window_proxy))
}
}
#[allow(unsafe_code)]
pub fn new_dissimilar_origin(
global_to_clone_from: &GlobalScope,
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
parent: Option<&WindowProxy>,
opener: Option<BrowsingContextId>,
creator: CreatorBrowsingContextInfo,
) -> DomRoot<WindowProxy> {
unsafe {
let handler = WindowProxyHandler::x_origin_proxy_handler();
let cx = GlobalScope::get_cx();
let window_proxy = Box::new(WindowProxy::new_inherited(
browsing_context_id,
top_level_browsing_context_id,
None,
None,
parent,
opener,
creator,
));
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());
rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
assert!(!js_proxy.is_null());
SetProxyReservedSlot(
js_proxy.get(),
0,
&PrivateValue((*window_proxy).as_void_ptr()),
);
SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
debug!(
"Initializing reflector of {:p} to {:p}.",
window_proxy,
js_proxy.get()
);
window_proxy.reflector.set_jsobject(js_proxy.get());
DomRoot::from_ref(&*Box::into_raw(window_proxy))
}
}
fn create_auxiliary_browsing_context(
&self,
name: DOMString,
noopener: bool,
) -> Option<DomRoot<WindowProxy>> {
let (chan, port) = ipc::channel().unwrap();
let window = self
.currently_active
.get()
.and_then(ScriptThread::find_document)
.map(|doc| DomRoot::from_ref(doc.window()))
.unwrap();
let msg = EmbedderMsg::AllowOpeningWebView(chan);
window.send_to_embedder(msg);
if port.recv().unwrap() {
let new_top_level_browsing_context_id = TopLevelBrowsingContextId::new();
let new_browsing_context_id =
BrowsingContextId::from(new_top_level_browsing_context_id);
let new_pipeline_id = PipelineId::new();
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().immutable().clone()),
blank_url,
None,
document.global().get_referrer(),
document.get_referrer_policy(),
None, );
let load_info = AuxiliaryBrowsingContextLoadInfo {
load_data: load_data.clone(),
opener_pipeline_id: self.currently_active.get().unwrap(),
new_browsing_context_id,
new_top_level_browsing_context_id,
new_pipeline_id,
};
let new_layout_info = NewLayoutInfo {
parent_info: None,
new_pipeline_id,
browsing_context_id: new_browsing_context_id,
top_level_browsing_context_id: new_top_level_browsing_context_id,
opener: Some(self.browsing_context_id),
load_data,
window_size: window.window_size(),
};
let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info);
window.send_to_constellation(constellation_msg);
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
let auxiliary =
ScriptThread::find_document(new_pipeline_id).and_then(|doc| doc.browsing_context());
if let Some(proxy) = auxiliary {
if name.to_lowercase() != "_blank" {
proxy.set_name(name);
}
if noopener {
proxy.disown();
}
return Some(proxy);
}
}
None
}
pub fn is_delaying_load_events_mode(&self) -> bool {
self.delaying_load_events_mode.get()
}
pub fn start_delaying_load_events_mode(&self) {
self.delaying_load_events_mode.set(true);
}
pub 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);
}
}
}
pub fn disown(&self) {
self.disowned.set(true);
}
pub fn close(&self) {
self.is_closing.set(true);
}
pub fn is_closing(&self) -> bool {
self.is_closing.get()
}
pub fn creator_base_url(&self) -> Option<ServoUrl> {
self.creator_base_url.clone()
}
pub fn has_creator_base_url(&self) -> bool {
self.creator_base_url.is_some()
}
pub fn creator_url(&self) -> Option<ServoUrl> {
self.creator_url.clone()
}
pub fn has_creator_url(&self) -> bool {
self.creator_base_url.is_some()
}
pub fn creator_origin(&self) -> Option<ImmutableOrigin> {
self.creator_origin.clone()
}
pub fn has_creator_origin(&self) -> bool {
self.creator_origin.is_some()
}
#[allow(unsafe_code)]
pub 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 ScriptThread::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(
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) };
}
pub fn open(
&self,
url: USVString,
target: DOMString,
features: DOMString,
can_gc: CanGc,
) -> Fallible<Option<DomRoot<WindowProxy>>> {
let non_empty_target = match target.as_ref() {
"" => DOMString::from("_blank"),
_ => target,
};
let tokenized_features = tokenize_open_features(features);
let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
let noopener = if noreferrer {
true
} else {
parse_open_feature_boolean(&tokenized_features, "noopener")
};
let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
(Some(chosen), new) => (chosen, new),
(None, _) => return Ok(None),
};
let target_document = match chosen.document() {
Some(target_document) => target_document,
None => return Ok(None),
};
let target_window = target_document.window();
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),
};
let referrer = if noreferrer {
Referrer::NoReferrer
} else {
target_window.upcast::<GlobalScope>().get_referrer()
};
let referrer_policy = target_document.get_referrer_policy();
let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id();
let secure = target_window.upcast::<GlobalScope>().is_secure_context();
let load_data = LoadData::new(
LoadOrigin::Script(existing_document.origin().immutable().clone()),
url,
Some(pipeline_id),
referrer,
referrer_policy,
Some(secure),
);
let history_handling = if new {
NavigationHistoryBehavior::Replace
} else {
NavigationHistoryBehavior::Push
};
target_window.load_url(history_handling, false, load_data, can_gc);
}
if noopener {
return Ok(None);
}
Ok(target_document.browsing_context())
}
pub fn choose_browsing_context(
&self,
name: DOMString,
noopener: bool,
) -> (Option<DomRoot<WindowProxy>>, bool) {
match name.to_lowercase().as_ref() {
"" | "_self" => {
(Some(DomRoot::from_ref(self)), false)
},
"_parent" => {
if let Some(parent) = self.parent() {
return (Some(DomRoot::from_ref(parent)), false);
}
(None, false)
},
"_top" => {
(Some(DomRoot::from_ref(self.top())), false)
},
"_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
_ => {
match ScriptThread::find_window_proxy_by_name(&name) {
Some(proxy) => (Some(proxy), false),
None => (self.create_auxiliary_browsing_context(name, noopener), true),
}
},
}
}
pub fn is_auxiliary(&self) -> bool {
self.opener.is_some()
}
pub fn discard_browsing_context(&self) {
self.discarded.set(true);
}
pub fn is_browsing_context_discarded(&self) -> bool {
self.discarded.get()
}
pub fn browsing_context_id(&self) -> BrowsingContextId {
self.browsing_context_id
}
pub fn top_level_browsing_context_id(&self) -> TopLevelBrowsingContextId {
self.top_level_browsing_context_id
}
pub fn frame_element(&self) -> Option<&Element> {
self.frame_element.as_deref()
}
pub fn document(&self) -> Option<DomRoot<Document>> {
self.currently_active
.get()
.and_then(ScriptThread::find_document)
}
pub fn parent(&self) -> Option<&WindowProxy> {
self.parent.as_deref()
}
pub fn top(&self) -> &WindowProxy {
let mut result = self;
while let Some(parent) = result.parent() {
result = parent;
}
result
}
#[allow(unsafe_code)]
fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler) {
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);
SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
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());
SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(self.as_void_ptr()));
SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
debug!(
"Setting reflector of {:p} to {:p}.",
self,
new_js_proxy.get()
);
self.reflector.rootable().set(new_js_proxy.get());
}
}
pub fn set_currently_active(&self, window: &Window) {
let globalscope = window.upcast::<GlobalScope>();
let dest_pipeline_id = globalscope.pipeline_id();
if let Some(pipeline_id) = self.currently_active() {
if pipeline_id == dest_pipeline_id {
return debug!(
"Attempt to set the currently active window to the currently active window."
);
}
}
self.set_window(globalscope, WindowProxyHandler::proxy_handler());
self.currently_active.set(Some(globalscope.pipeline_id()));
}
pub fn unset_currently_active(&self) {
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(),
);
self.currently_active.set(None);
}
pub fn currently_active(&self) -> Option<PipelineId> {
self.currently_active.get()
}
pub fn get_name(&self) -> DOMString {
self.name.borrow().clone()
}
pub fn set_name(&self, name: DOMString) {
*self.name.borrow_mut() = name;
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CreatorBrowsingContextInfo {
url: Option<ServoUrl>,
base_url: Option<ServoUrl>,
origin: Option<ImmutableOrigin>,
}
impl CreatorBrowsingContextInfo {
pub 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 base_url = creator.as_deref().map(|document| document.base_url());
let url = creator.as_deref().map(|document| document.url());
let origin = creator
.as_deref()
.map(|document| document.origin().immutable().clone());
CreatorBrowsingContextInfo {
base_url,
url,
origin,
}
}
}
fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
let mut tokenized_features = IndexMap::new();
let mut iter = features.chars();
let mut cur = iter.next();
while cur.is_some() {
let mut name = String::new();
let mut value = String::new();
while let Some(cur_char) = cur {
if !is_feature_sep(cur_char) {
break;
}
cur = iter.next();
}
while let Some(cur_char) = cur {
if is_feature_sep(cur_char) {
break;
}
name.push(cur_char.to_ascii_lowercase());
cur = iter.next();
}
let normalized_name = String::from(match name.as_ref() {
"screenx" => "left",
"screeny" => "top",
"innerwidth" => "width",
"innerheight" => "height",
_ => name.as_ref(),
});
while let Some(cur_char) = cur {
if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
break;
}
cur = iter.next();
}
if cur.is_some() && is_feature_sep(cur.unwrap()) {
while let Some(cur_char) = cur {
if !is_feature_sep(cur_char) || cur_char == ',' {
break;
}
cur = iter.next();
}
while let Some(cur_char) = cur {
if is_feature_sep(cur_char) {
break;
}
value.push(cur_char.to_ascii_lowercase());
cur = iter.next();
}
}
if !name.is_empty() {
tokenized_features.insert(normalized_name, value);
}
}
tokenized_features
}
fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
if let Some(value) = tokenized_features.get(name) {
if value.is_empty() || value == "yes" {
return true;
}
if let Ok(int) = parse_integer(value.chars()) {
return int != 0;
}
}
false
}
#[allow(unsafe_code, non_snake_case)]
unsafe fn GetSubframeWindowProxy(
cx: *mut JSContext,
proxy: RawHandleObject,
id: RawHandleId,
) -> Option<(DomRoot<WindowProxy>, u32)> {
let index = get_array_index_from_id(cx, Handle::from_raw(id));
if let Some(index) = index {
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy, &mut slot);
rooted!(in(cx) let target = slot.to_object());
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
.upcast::<GlobalScope>()
.script_to_constellation_chan()
.send(ScriptMsg::GetChildBrowsingContextId(
browsing_context_id,
index as usize,
result_sender,
));
return result_receiver
.recv()
.ok()
.and_then(|maybe_bcid| maybe_bcid)
.and_then(ScriptThread::find_window_proxy)
.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(
ScriptMsg::GetChildBrowsingContextId(
browsing_context_id,
index as usize,
result_sender,
),
);
return result_receiver
.recv()
.ok()
.and_then(|maybe_bcid| maybe_bcid)
.and_then(ScriptThread::find_window_proxy)
.map(|proxy| (proxy, JSPROP_READONLY as u32));
}
}
None
}
#[allow(unsafe_code, non_snake_case)]
unsafe extern "C" fn getOwnPropertyDescriptor(
cx: *mut JSContext,
proxy: RawHandleObject,
id: RawHandleId,
desc: RawMutableHandle<PropertyDescriptor>,
is_none: *mut bool,
) -> bool {
let window = GetSubframeWindowProxy(cx, proxy, id);
if let Some((window, attrs)) = window {
rooted!(in(cx) let mut val = UndefinedValue());
window.to_jsval(cx, val.handle_mut());
set_property_descriptor(
MutableHandle::from_raw(desc),
val.handle(),
attrs,
&mut *is_none,
);
return true;
}
let mut slot = UndefinedValue();
GetProxyPrivate(proxy.get(), &mut slot);
rooted!(in(cx) let target = slot.to_object());
return JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none);
}
#[allow(unsafe_code, non_snake_case)]
unsafe extern "C" fn defineProperty(
cx: *mut JSContext,
proxy: RawHandleObject,
id: RawHandleId,
desc: RawHandle<PropertyDescriptor>,
res: *mut ObjectOpResult,
) -> bool {
if get_array_index_from_id(cx, Handle::from_raw(id)).is_some() {
(*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
return true;
}
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy.ptr, &mut slot);
rooted!(in(cx) let target = slot.to_object());
JS_DefinePropertyById(cx, target.handle().into(), id, desc, res)
}
#[allow(unsafe_code)]
unsafe extern "C" fn has(
cx: *mut JSContext,
proxy: RawHandleObject,
id: RawHandleId,
bp: *mut bool,
) -> bool {
let window = GetSubframeWindowProxy(cx, proxy, id);
if window.is_some() {
*bp = true;
return true;
}
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy.ptr, &mut slot);
rooted!(in(cx) let target = slot.to_object());
let mut found = false;
if !JS_HasPropertyById(cx, target.handle().into(), id, &mut found) {
return false;
}
*bp = found;
true
}
#[allow(unsafe_code)]
unsafe extern "C" fn get(
cx: *mut JSContext,
proxy: RawHandleObject,
receiver: RawHandleValue,
id: RawHandleId,
vp: RawMutableHandleValue,
) -> bool {
let window = GetSubframeWindowProxy(cx, proxy, id);
if let Some((window, _attrs)) = window {
window.to_jsval(cx, MutableHandle::from_raw(vp));
return true;
}
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy.ptr, &mut slot);
rooted!(in(cx) let target = slot.to_object());
JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp)
}
#[allow(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(cx, Handle::from_raw(id)).is_some() {
(*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t;
return true;
}
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy.ptr, &mut slot);
rooted!(in(cx) let target = slot.to_object());
JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res)
}
#[allow(unsafe_code)]
unsafe extern "C" fn get_prototype_if_ordinary(
_: *mut JSContext,
_: RawHandleObject,
is_ordinary: *mut bool,
_: RawMutableHandleObject,
) -> bool {
*is_ordinary = false;
true
}
static PROXY_TRAPS: ProxyTraps = ProxyTraps {
enter: None,
getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor),
defineProperty: Some(defineProperty),
ownPropertyKeys: None,
delete_: None,
enumerate: None,
getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
getPrototype: None, 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,
};
pub struct WindowProxyHandler(*const libc::c_void);
impl MallocSizeOf for WindowProxyHandler {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
0
}
}
#[allow(unsafe_code)]
unsafe impl Send for WindowProxyHandler {}
#[allow(unsafe_code)]
unsafe impl Sync for WindowProxyHandler {}
#[allow(unsafe_code)]
impl WindowProxyHandler {
fn new(traps: &ProxyTraps) -> Self {
let ptr = unsafe { CreateWrapperProxyHandler(traps) };
assert!(!ptr.is_null());
Self(ptr)
}
pub fn x_origin_proxy_handler() -> &'static Self {
use std::sync::OnceLock;
static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
}
pub fn proxy_handler() -> &'static Self {
use std::sync::OnceLock;
static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
}
pub 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
}
}
#[allow(unsafe_code)]
impl Drop for WindowProxyHandler {
fn drop(&mut self) {
unsafe {
DeleteWrapperProxyHandler(self.0);
}
}
}
#[allow(unsafe_code)]
unsafe fn throw_security_error(cx: *mut JSContext, realm: InRealm) -> bool {
if !JS_IsExceptionPending(cx) {
let safe_context = SafeJSContext::from_ptr(cx);
let global = GlobalScope::from_context(cx, realm);
throw_dom_exception(safe_context, &global, Error::Security);
}
false
}
#[allow(unsafe_code)]
unsafe extern "C" fn has_xorigin(
cx: *mut JSContext,
proxy: RawHandleObject,
id: RawHandleId,
bp: *mut bool,
) -> bool {
let mut slot = UndefinedValue();
GetProxyPrivate(*proxy.ptr, &mut slot);
rooted!(in(cx) let target = slot.to_object());
let mut found = false;
JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found);
if found {
*bp = true;
true
} else {
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
throw_security_error(cx, InRealm::Already(&in_realm_proof))
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn get_xorigin(
cx: *mut JSContext,
proxy: RawHandleObject,
receiver: RawHandleValue,
id: RawHandleId,
vp: RawMutableHandleValue,
) -> bool {
let mut found = false;
has_xorigin(cx, proxy, id, &mut found);
found && get(cx, proxy, receiver, id, vp)
}
#[allow(unsafe_code)]
unsafe extern "C" fn set_xorigin(
cx: *mut JSContext,
_: RawHandleObject,
_: RawHandleId,
_: RawHandleValue,
_: RawHandleValue,
_: *mut ObjectOpResult,
) -> bool {
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
throw_security_error(cx, InRealm::Already(&in_realm_proof))
}
#[allow(unsafe_code)]
unsafe extern "C" fn delete_xorigin(
cx: *mut JSContext,
_: RawHandleObject,
_: RawHandleId,
_: *mut ObjectOpResult,
) -> bool {
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
throw_security_error(cx, InRealm::Already(&in_realm_proof))
}
#[allow(unsafe_code, 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;
has_xorigin(cx, proxy, id, &mut found);
found && getOwnPropertyDescriptor(cx, proxy, id, desc, is_none)
}
#[allow(unsafe_code, non_snake_case)]
unsafe extern "C" fn defineProperty_xorigin(
cx: *mut JSContext,
_: RawHandleObject,
_: RawHandleId,
_: RawHandle<PropertyDescriptor>,
_: *mut ObjectOpResult,
) -> bool {
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
throw_security_error(cx, InRealm::Already(&in_realm_proof))
}
#[allow(unsafe_code, non_snake_case)]
unsafe extern "C" fn preventExtensions_xorigin(
cx: *mut JSContext,
_: RawHandleObject,
_: *mut ObjectOpResult,
) -> bool {
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(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,
};
#[allow(unsafe_code)]
unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
let mut slot = UndefinedValue();
GetProxyReservedSlot(obj, 0, &mut slot);
let this = slot.to_private() as *mut WindowProxy;
if this.is_null() {
return;
}
let jsobject = (*this).reflector.get_jsobject().get();
debug!(
"WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
this, jsobject, obj
);
let _ = Box::from_raw(this);
}
#[allow(unsafe_code)]
unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
let mut slot = UndefinedValue();
GetProxyReservedSlot(obj, 0, &mut slot);
let this = slot.to_private() as *const WindowProxy;
if this.is_null() {
return;
}
(*this).trace(trc);
}