diff options
Diffstat (limited to 'components/script/dom/windowproxy.rs')
-rw-r--r-- | components/script/dom/windowproxy.rs | 1244 |
1 files changed, 1244 insertions, 0 deletions
diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs new file mode 100644 index 00000000000..0f92e4b3367 --- /dev/null +++ b/components/script/dom/windowproxy.rs @@ -0,0 +1,1244 @@ +/* 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 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::fill_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, WindowProxyHandler}; +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::JSContext as SafeJSContext; +use crate::script_thread::ScriptThread; +use dom_struct::dom_struct; +use embedder_traits::EmbedderMsg; +use indexmap::map::IndexMap; +use ipc_channel::ipc; +use js::glue::{CreateWrapperProxyHandler, ProxyTraps}; +use js::glue::{GetProxyPrivate, GetProxyReservedSlot, SetProxyReservedSlot}; +use js::jsapi::Handle as RawHandle; +use js::jsapi::HandleId as RawHandleId; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::HandleValue as RawHandleValue; +use js::jsapi::MutableHandle as RawMutableHandle; +use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::MutableHandleValue as RawMutableHandleValue; +use js::jsapi::{JSAutoRealm, JSContext, JSErrNum, JSFreeOp, JSObject}; +use js::jsapi::{JSTracer, JS_DefinePropertyById, JSPROP_ENUMERATE, JSPROP_READONLY}; +use js::jsapi::{JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo}; +use js::jsapi::{JS_GetOwnPropertyDescriptorById, JS_IsExceptionPending}; +use js::jsapi::{JS_HasOwnPropertyById, JS_HasPropertyById}; +use js::jsapi::{ObjectOpResult, PropertyDescriptor}; +use js::jsval::{JSVal, NullValue, PrivateValue, UndefinedValue}; +use js::rust::get_object_class; +use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy}; +use js::rust::{Handle, MutableHandle}; +use js::JSCLASS_IS_GLOBAL; +use msg::constellation_msg::BrowsingContextId; +use msg::constellation_msg::PipelineId; +use msg::constellation_msg::TopLevelBrowsingContextId; +use net_traits::request::Referrer; +use script_traits::{ + AuxiliaryBrowsingContextLoadInfo, HistoryEntryReplacement, LoadData, LoadOrigin, +}; +use script_traits::{NewLayoutInfo, ScriptMsg}; +use servo_url::{ImmutableOrigin, ServoUrl}; +use std::cell::Cell; +use std::ptr; +use style::attr::parse_integer; + +#[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 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. + browsing_context_id: BrowsingContextId, + + // https://html.spec.whatwg.org/multipage/#opener-browsing-context + 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. + top_level_browsing_context_id: TopLevelBrowsingContextId, + + /// 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. + 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>, + + /// The containing iframe element, if this is a same-origin iframe + 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 base url. + creator_base_url: Option<ServoUrl>, + + /// The creator browsing context's url. + creator_url: Option<ServoUrl>, + + /// The creator browsing context's origin. + creator_origin: Option<ImmutableOrigin>, +} + +impl WindowProxy { + pub 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: browsing_context_id, + top_level_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 WindowProxyHandler(handler) = window.windowproxy_handler(); + assert!(!handler.is_null()); + + let cx = window.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 = NewWindowProxy(*cx, window_jsobject, handler)); + assert!(!js_proxy.is_null()); + + // Create a new browsing context. + 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, + )); + + // 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((&*window_proxy).as_void_ptr()), + ); + + // 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.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 = CreateWrapperProxyHandler(&XORIGIN_PROXY_HANDLER); + assert!(!handler.is_null()); + + let cx = global_to_clone_from.get_cx(); + + // Create a new browsing context. + let window_proxy = Box::new(WindowProxy::new_inherited( + browsing_context_id, + top_level_browsing_context_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 = NewWindowProxy(*cx, window_jsobject, handler)); + 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((&*window_proxy).as_void_ptr()), + ); + + // 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.set_jsobject(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 (chan, port) = ipc::channel().unwrap(); + let window = self + .currently_active + .get() + .and_then(|id| ScriptThread::find_document(id)) + .and_then(|doc| Some(DomRoot::from_ref(doc.window()))) + .unwrap(); + let msg = EmbedderMsg::AllowOpeningBrowser(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(|id| ScriptThread::find_document(id)) + .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, // Doesn't inherit secure context + ); + let load_info = AuxiliaryBrowsingContextLoadInfo { + load_data: load_data.clone(), + opener_pipeline_id: self.currently_active.get().unwrap(), + new_browsing_context_id: new_browsing_context_id, + new_top_level_browsing_context_id: new_top_level_browsing_context_id, + new_pipeline_id: new_pipeline_id, + }; + + let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); + let new_layout_info = NewLayoutInfo { + parent_info: None, + new_pipeline_id: 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: load_data, + pipeline_port: pipeline_receiver, + window_size: window.window_size(), + }; + let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info, pipeline_sender); + window.send_to_constellation(constellation_msg); + ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); + let msg = EmbedderMsg::BrowserCreated(new_top_level_browsing_context_id); + window.send_to_embedder(msg); + // TODO: if noopener is false, copy the sessionStorage storage area of the creator origin. + // See step 14 of https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context + 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 + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub 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 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 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 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 fn close(&self) { + self.is_closing.set(true); + } + + /// https://html.spec.whatwg.org/multipage/#is-closing + pub fn is_closing(&self) -> bool { + self.is_closing.get() + } + + /// https://html.spec.whatwg.org/multipage/#creator-base-url + 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() + } + + /// https://html.spec.whatwg.org/multipage/#creator-url + pub fn creator_url(&self) -> Option<ServoUrl> { + self.creator_url.clone() + } + + pub fn has_creator_url(&self) -> bool { + self.creator_base_url.is_some() + } + + /// https://html.spec.whatwg.org/multipage/#creator-origin + 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)] + // https://html.spec.whatwg.org/multipage/#dom-opener + pub fn opener(&self, cx: *mut JSContext, in_realm_proof: InRealm) -> JSVal { + if self.disowned.get() { + return NullValue(); + } + let opener_id = match self.opener { + Some(opener_browsing_context_id) => opener_browsing_context_id, + None => return 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 NullValue(), + } + }, + }; + if opener_proxy.is_browsing_context_discarded() { + return NullValue(); + } + rooted!(in(cx) let mut val = UndefinedValue()); + unsafe { opener_proxy.to_jsval(cx, val.handle_mut()) }; + return val.get(); + } + + // https://html.spec.whatwg.org/multipage/#window-open-steps + pub fn open( + &self, + url: USVString, + target: DOMString, + features: DOMString, + ) -> Fallible<Option<DomRoot<WindowProxy>>> { + // Step 4. + let non_empty_target = match target.as_ref() { + "" => DOMString::from("_blank"), + _ => target, + }; + // Step 5 + let tokenized_features = tokenize_open_features(features); + // Step 7-9 + let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer"); + let noopener = if noreferrer { + true + } else { + parse_open_feature_boolean(&tokenized_features, "noopener") + }; + // Step 10, 11 + let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) { + (Some(chosen), new) => (chosen, new), + (None, _) => return Ok(None), + }; + // TODO Step 12, set up browsing context features. + let target_document = match chosen.document() { + Some(target_document) => target_document, + None => return Ok(None), + }; + let target_window = target_document.window(); + // Step 13, and 14.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(|id| ScriptThread::find_document(id)) + .unwrap(); + // Step 14.1 + let url = match existing_document.url().join(&url) { + Ok(url) => url, + Err(_) => return Err(Error::Syntax), + }; + // Step 14.3 + let referrer = if noreferrer { + Referrer::NoReferrer + } else { + target_window.upcast::<GlobalScope>().get_referrer() + }; + // Step 14.5 + 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 replacement_flag = if new { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + target_window.load_url(replacement_flag, false, load_data); + } + if noopener { + // Step 15 (Dis-owning has been done in create_auxiliary_browsing_context). + return Ok(None); + } + // Step 17. + return Ok(target_document.browsing_context()); + } + + // https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name + pub 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 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(|id| ScriptThread::find_document(id)) + } + + 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)] + /// 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, traps: &ProxyTraps) { + unsafe { + debug!("Setting window of {:p}.", self); + let handler = CreateWrapperProxyHandler(traps); + assert!(!handler.is_null()); + + let cx = window.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 transpant 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 = NewWindowProxy(*cx, window_jsobject, handler)); + 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_void_ptr())); + + // 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 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, &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(), &XORIGIN_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; + } +} + +/// 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 struct CreatorBrowsingContextInfo { + /// Creator document URL. + url: Option<ServoUrl>, + + /// Creator document base URL. + base_url: Option<ServoUrl>, + + /// Creator document origin. + 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, + } + } +} + +// 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 mut iter = features.chars(); + let mut cur = iter.next(); + + // Step 3 + while cur != None { + // 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 == "" || value == "yes" { + return true; + } + // Step 3 & 4 + if let Ok(int) = parse_integer(value.chars()) { + return int != 0; + } + } + // Step 5 + return 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 +#[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, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> 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()); + desc.value = val.get(); + fill_property_descriptor(MutableHandle::from_raw(desc), proxy.get(), attrs); + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(proxy.get(), &mut slot); + rooted!(in(cx) let target = slot.to_object()); + if !JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc) { + return false; + } + + assert!(desc.obj.is_null() || desc.obj == target.get()); + if desc.obj == target.get() { + desc.obj = proxy.get(); + } + + true +} + +#[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() { + // 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. + (*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() { + // Reject (which means throw if and only if strict) the set. + (*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 { + // 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. + *is_ordinary = false; + return true; +} + +static PROXY_HANDLER: ProxyTraps = ProxyTraps { + enter: None, + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor), + defineProperty: Some(defineProperty), + ownPropertyKeys: None, + delete_: None, + enumerate: None, + getPrototypeIfOrdinary: Some(get_prototype_if_ordinary), + preventExtensions: None, + isExtensible: None, + has: Some(has), + get: Some(get), + set: Some(set), + call: None, + construct: None, + hasOwn: None, + getOwnEnumerablePropertyKeys: None, + nativeCall: None, + hasInstance: 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)] +pub fn new_window_proxy_handler() -> WindowProxyHandler { + unsafe { WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER)) } +} + +// 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. + +#[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>, +) -> bool { + let mut found = false; + has_xorigin(cx, proxy, id, &mut found); + found && getOwnPropertyDescriptor(cx, proxy, id, desc) +} + +#[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_HANDLER: ProxyTraps = ProxyTraps { + enter: None, + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin), + defineProperty: Some(defineProperty_xorigin), + ownPropertyKeys: None, + delete_: Some(delete_xorigin), + enumerate: None, + getPrototypeIfOrdinary: 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, + hasInstance: 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. + +#[allow(unsafe_code)] +unsafe extern "C" fn finalize(_fop: *mut JSFreeOp, obj: *mut JSObject) { + let mut slot = UndefinedValue(); + 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 = (*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() { + // GC during obj creation or after transplanting. + return; + } + (*this).trace(trc); +} |