Skip to content

Commit

Permalink
Allow windows to share browsing contexts.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Jeffrey committed Jan 29, 2017
1 parent fc67878 commit 403499a
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 108 deletions.
92 changes: 77 additions & 15 deletions components/script/dom/browsingcontext.rs
Expand Up @@ -5,7 +5,7 @@
use dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
use dom::bindings::js::{JS, Root, RootedReference};
use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor};
use dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
use dom::bindings::reflector::{DomObject, Reflector};
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::WindowProxyHandler;
use dom::bindings::utils::get_array_index_from_id;
Expand All @@ -19,18 +19,24 @@ use js::jsapi::{JSAutoCompartment, JSContext, JSErrNum, JSFreeOp, JSObject};
use js::jsapi::{JSPROP_READONLY, JSTracer, JS_DefinePropertyById};
use js::jsapi::{JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo};
use js::jsapi::{JS_GetOwnPropertyDescriptorById, JS_HasPropertyById};
use js::jsapi::{JS_TransplantObject, SetWindowProxy};
use js::jsapi::{MutableHandle, MutableHandleObject, MutableHandleValue};
use js::jsapi::{ObjectOpResult, PropertyDescriptor};
use js::jsval::{UndefinedValue, PrivateValue};
use js::rust::get_object_class;
use std::cell::Cell;
use std::ptr;

#[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 BrowsingContext {
/// The WindowProxy object.
/// Unlike other reflectors, we mutate this field because
/// we have to brain-transplant the reflector when the WindowProxy
/// changes Window.
reflector: Reflector,

/// Has this browsing context been discarded?
Expand All @@ -44,7 +50,7 @@ impl BrowsingContext {
pub fn new_inherited(frame_element: Option<&Element>) -> BrowsingContext {
BrowsingContext {
reflector: Reflector::new(),
discarded: Cell::new(false),
discarded: Cell::new(false),
frame_element: frame_element.map(JS::from_ref),
}
}
Expand All @@ -56,21 +62,29 @@ impl BrowsingContext {
assert!(!handler.is_null());

let cx = window.get_cx();
let parent = window.reflector().get_jsobject();
assert!(!parent.get().is_null());
assert!(((*get_object_class(parent.get())).flags & JSCLASS_IS_GLOBAL) != 0);
let _ac = JSAutoCompartment::new(cx, parent.get());
rooted!(in(cx) let window_proxy = NewWindowProxy(cx, parent, handler));
let window_jsobject = window.reflector().get_jsobject();
assert!(!window_jsobject.get().is_null());
assert!(((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL) != 0);
let _ac = JSAutoCompartment::new(cx, window_jsobject.get());

// Create a new window proxy.
rooted!(in(cx) let window_proxy = NewWindowProxy(cx, window_jsobject, handler));
assert!(!window_proxy.is_null());

let object = box BrowsingContext::new_inherited(frame_element);
// Create a new browsing context.
let mut browsing_context = box BrowsingContext::new_inherited(frame_element);

let raw = Box::into_raw(object);
SetProxyExtra(window_proxy.get(), 0, &PrivateValue(raw as *const _));
// The window proxy owns the browsing context.
// When we finalize the window proxy, it drops the browsing context it owns.
SetProxyExtra(window_proxy.get(), 0, &PrivateValue(&*browsing_context as *const _ as *const _));

(*raw).init_reflector(window_proxy.get());
// Notify the JS engine about the new window proxy binding.
SetWindowProxy(cx, window_jsobject, window_proxy.handle());

Root::from_ref(&*raw)
// Set the reflector.
debug!("Initializing reflector of {:p} to {:p}.", browsing_context, window_proxy.get());
browsing_context.reflector.set_jsobject(window_proxy.get());
Root::from_ref(&*Box::into_raw(browsing_context))
}
}

Expand All @@ -86,6 +100,50 @@ impl BrowsingContext {
self.frame_element.r()
}

#[allow(unsafe_code)]
/// Change the Window that this browsing context's 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.
pub fn set_window_proxy(&self, window: &Window) {
unsafe {
debug!("Setting window proxy of {:p}.", self);
let WindowProxyHandler(handler) = window.windowproxy_handler();
assert!(!handler.is_null());

let cx = window.get_cx();
let window_jsobject = window.reflector().get_jsobject();
let old_window_proxy = self.reflector.get_jsobject();
assert!(!window_jsobject.get().is_null());
assert!(((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL) != 0);
let _ac = JSAutoCompartment::new(cx, window_jsobject.get());

// The old window proxy no longer owns this browsing context.
SetProxyExtra(old_window_proxy.get(), 0, &PrivateValue(ptr::null_mut()));

// Brain transpant the window proxy.
// We need to do this, because the Window and WindowProxy
// objects need to be in the same compartment.
// JS_TransplantObject does this by copying the contents
// of the old window proxy to the new window proxy, then
// making the old window proxy a cross-compartment wrapper
// pointing to the new window proxy.
rooted!(in(cx) let new_window_proxy = NewWindowProxy(cx, window_jsobject, handler));
debug!("Transplanting window proxy from {:p} to {:p}.", old_window_proxy.get(), new_window_proxy.get());
rooted!(in(cx) let new_window_proxy = JS_TransplantObject(cx, old_window_proxy, new_window_proxy.handle()));
debug!("Transplanted window proxy is {:p}.", new_window_proxy.get());

// Transfer ownership of this browsing context from the old window proxy to the new one.
SetProxyExtra(new_window_proxy.get(), 0, &PrivateValue(self as *const _ as *const _));

// Notify the JS engine about the new window proxy binding.
SetWindowProxy(cx, window_jsobject, new_window_proxy.handle());

// Update the reflector.
debug!("Setting reflector of {:p} to {:p}.", self, new_window_proxy.get());
self.reflector.rootable().set(new_window_proxy.get());
}
}

pub fn window_proxy(&self) -> *mut JSObject {
let window_proxy = self.reflector.get_jsobject();
assert!(!window_proxy.get().is_null());
Expand Down Expand Up @@ -277,16 +335,20 @@ static PROXY_HANDLER: ProxyTraps = ProxyTraps {
#[allow(unsafe_code)]
unsafe extern fn finalize(_fop: *mut JSFreeOp, obj: *mut JSObject) {
let this = GetProxyExtra(obj, 0).to_private() as *mut BrowsingContext;
assert!(!this.is_null());
if this.is_null() {
// GC during obj creation or after transplanting.
return;
}
let jsobject = (*this).reflector.get_jsobject().get();
debug!("BrowsingContext finalize: {:p}, with reflector {:p} from {:p}.", this, jsobject, obj);
let _ = Box::from_raw(this);
debug!("BrowsingContext finalize: {:p}", this);
}

#[allow(unsafe_code)]
unsafe extern fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
let this = GetProxyExtra(obj, 0).to_private() as *const BrowsingContext;
if this.is_null() {
// GC during obj creation
// GC during obj creation or after transplanting.
return;
}
(*this).trace(trc);
Expand Down
43 changes: 26 additions & 17 deletions components/script/dom/document.rs
Expand Up @@ -184,13 +184,12 @@ impl PendingRestyle {
pub struct Document {
node: Node,
window: JS<Window>,
/// https://html.spec.whatwg.org/multipage/#concept-document-bc
browsing_context: Option<JS<BrowsingContext>>,
implementation: MutNullableJS<DOMImplementation>,
location: MutNullableJS<Location>,
content_type: DOMString,
last_modified: Option<String>,
encoding: Cell<EncodingRef>,
has_browsing_context: bool,
is_html_document: bool,
activity: Cell<DocumentActivity>,
url: DOMRefCell<ServoUrl>,
Expand Down Expand Up @@ -369,8 +368,12 @@ impl Document {

/// https://html.spec.whatwg.org/multipage/#concept-document-bc
#[inline]
pub fn browsing_context(&self) -> Option<&BrowsingContext> {
self.browsing_context.as_ref().map(|browsing_context| &**browsing_context)
pub fn browsing_context(&self) -> Option<Root<BrowsingContext>> {
if self.has_browsing_context {
Some(self.window.browsing_context())
} else {
None
}
}

#[inline]
Expand Down Expand Up @@ -398,7 +401,7 @@ impl Document {

pub fn set_activity(&self, activity: DocumentActivity) {
// This function should only be called on documents with a browsing context
assert!(self.browsing_context.is_some());
assert!(self.has_browsing_context);
// Set the document's activity level, reflow if necessary, and suspend or resume timers.
if activity != self.activity.get() {
self.activity.set(activity);
Expand Down Expand Up @@ -1568,7 +1571,7 @@ impl Document {
self.process_deferred_scripts();
},
LoadType::PageSource(_) => {
if self.browsing_context.is_some() {
if self.has_browsing_context {
// Disarm the reflow timer and trigger the initial reflow.
self.reflow_timeout.set(None);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
Expand Down Expand Up @@ -1830,7 +1833,7 @@ impl Document {

/// https://html.spec.whatwg.org/multipage/#cookie-averse-document-object
pub fn is_cookie_averse(&self) -> bool {
self.browsing_context.is_none() || !url_has_network_scheme(&self.url())
!self.has_browsing_context || !url_has_network_scheme(&self.url())
}

pub fn nodes_from_point(&self, client_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
Expand Down Expand Up @@ -1901,9 +1904,15 @@ fn url_has_network_scheme(url: &ServoUrl) -> bool {
}
}

#[derive(Copy, Clone, HeapSizeOf, JSTraceable, PartialEq, Eq)]
pub enum HasBrowsingContext {
No,
Yes,
}

impl Document {
pub fn new_inherited(window: &Window,
browsing_context: Option<&BrowsingContext>,
has_browsing_context: HasBrowsingContext,
url: Option<ServoUrl>,
origin: Origin,
is_html_document: IsHTMLDocument,
Expand All @@ -1926,7 +1935,7 @@ impl Document {
Document {
node: Node::new_document_node(),
window: JS::from_ref(window),
browsing_context: browsing_context.map(JS::from_ref),
has_browsing_context: has_browsing_context == HasBrowsingContext::Yes,
implementation: Default::default(),
location: Default::default(),
content_type: match content_type {
Expand Down Expand Up @@ -1970,7 +1979,7 @@ impl Document {
deferred_scripts: Default::default(),
asap_in_order_scripts_list: Default::default(),
asap_scripts_set: Default::default(),
scripting_enabled: browsing_context.is_some(),
scripting_enabled: has_browsing_context == HasBrowsingContext::Yes,
animation_frame_ident: Cell::new(0),
animation_frame_list: DOMRefCell::new(vec![]),
running_animation_callbacks: Cell::new(false),
Expand Down Expand Up @@ -2007,7 +2016,7 @@ impl Document {
let doc = window.Document();
let docloader = DocumentLoader::new(&*doc.loader());
Ok(Document::new(window,
None,
HasBrowsingContext::No,
None,
doc.origin().alias(),
IsHTMLDocument::NonHTMLDocument,
Expand All @@ -2021,7 +2030,7 @@ impl Document {
}

pub fn new(window: &Window,
browsing_context: Option<&BrowsingContext>,
has_browsing_context: HasBrowsingContext,
url: Option<ServoUrl>,
origin: Origin,
doctype: IsHTMLDocument,
Expand All @@ -2034,7 +2043,7 @@ impl Document {
referrer_policy: Option<ReferrerPolicy>)
-> Root<Document> {
let document = reflect_dom_object(box Document::new_inherited(window,
browsing_context,
has_browsing_context,
url,
origin,
doctype,
Expand Down Expand Up @@ -2107,7 +2116,7 @@ impl Document {
IsHTMLDocument::NonHTMLDocument
};
let new_doc = Document::new(self.window(),
None,
HasBrowsingContext::No,
None,
// https://github.com/whatwg/html/issues/2109
Origin::opaque_identifier(),
Expand Down Expand Up @@ -3011,10 +3020,10 @@ impl DocumentMethods for Document {

// https://html.spec.whatwg.org/multipage/#dom-document-defaultview
fn GetDefaultView(&self) -> Option<Root<Window>> {
if self.browsing_context.is_none() {
None
} else {
if self.has_browsing_context {
Some(Root::from_ref(&*self.window))
} else {
None
}
}

Expand Down
6 changes: 3 additions & 3 deletions components/script/dom/domimplementation.rs
Expand Up @@ -13,7 +13,7 @@ use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bindings::xmlname::{namespace_from_domstring, validate_qualified_name};
use dom::document::{Document, IsHTMLDocument};
use dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
use dom::document::DocumentSource;
use dom::documenttype::DocumentType;
use dom::htmlbodyelement::HTMLBodyElement;
Expand Down Expand Up @@ -78,7 +78,7 @@ impl DOMImplementationMethods for DOMImplementation {

// Step 1.
let doc = XMLDocument::new(win,
None,
HasBrowsingContext::No,
None,
self.document.origin().alias(),
IsHTMLDocument::NonHTMLDocument,
Expand Down Expand Up @@ -125,7 +125,7 @@ impl DOMImplementationMethods for DOMImplementation {

// Step 1-2.
let doc = Document::new(win,
None,
HasBrowsingContext::No,
None,
self.document.origin().alias(),
IsHTMLDocument::HTMLDocument,
Expand Down
6 changes: 3 additions & 3 deletions components/script/dom/domparser.rs
Expand Up @@ -15,7 +15,7 @@ use dom::bindings::error::Fallible;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::document::{Document, IsHTMLDocument};
use dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
use dom::document::DocumentSource;
use dom::servoparser::ServoParser;
use dom::window::Window;
Expand Down Expand Up @@ -60,7 +60,7 @@ impl DOMParserMethods for DOMParser {
match ty {
Text_html => {
let document = Document::new(&self.window,
None,
HasBrowsingContext::No,
Some(url.clone()),
doc.origin().alias(),
IsHTMLDocument::HTMLDocument,
Expand All @@ -78,7 +78,7 @@ impl DOMParserMethods for DOMParser {
Text_xml | Application_xml | Application_xhtml_xml => {
// FIXME: this should probably be FromParser when we actually parse the string (#3756).
let document = Document::new(&self.window,
None,
HasBrowsingContext::No,
Some(url.clone()),
doc.origin().alias(),
IsHTMLDocument::NonHTMLDocument,
Expand Down
4 changes: 2 additions & 2 deletions components/script/dom/node.rs
Expand Up @@ -29,7 +29,7 @@ use dom::bindings::str::{DOMString, USVString};
use dom::bindings::xmlname::namespace_from_domstring;
use dom::characterdata::{CharacterData, LayoutCharacterDataHelpers};
use dom::cssstylesheet::CSSStyleSheet;
use dom::document::{Document, DocumentSource, IsHTMLDocument};
use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::element::{Element, ElementCreator};
Expand Down Expand Up @@ -1726,7 +1726,7 @@ impl Node {
};
let window = document.window();
let loader = DocumentLoader::new(&*document.loader());
let document = Document::new(window, None,
let document = Document::new(window, HasBrowsingContext::No,
Some(document.url()),
// https://github.com/whatwg/dom/issues/378
document.origin().alias(),
Expand Down
4 changes: 2 additions & 2 deletions components/script/dom/servoparser/mod.rs
Expand Up @@ -14,7 +14,7 @@ use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::characterdata::CharacterData;
use dom::document::{Document, DocumentSource, IsHTMLDocument};
use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use dom::element::Element;
use dom::globalscope::GlobalScope;
use dom::htmlformelement::HTMLFormElement;
Expand Down Expand Up @@ -102,7 +102,7 @@ impl ServoParser {
let loader = DocumentLoader::new_with_threads(context_document.loader().resource_threads().clone(),
Some(url.clone()));
let document = Document::new(window,
None,
HasBrowsingContext::No,
Some(url.clone()),
context_document.origin().alias(),
IsHTMLDocument::HTMLDocument,
Expand Down

0 comments on commit 403499a

Please sign in to comment.