aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/bindings
diff options
context:
space:
mode:
authorbors-servo <metajack+bors@gmail.com>2014-12-29 11:57:45 -0700
committerbors-servo <metajack+bors@gmail.com>2014-12-29 11:57:45 -0700
commit2c259f477c41331e66beab8bda865971982a1ff4 (patch)
tree12b1b56eec1482d6b6e31e85383a309856a39dc7 /components/script/dom/bindings
parentf76a460c53dfddef74262eceaf4b163b551adc08 (diff)
parent9a7cd3113403fe44a8919f049720b67bfa92c9f1 (diff)
downloadservo-2c259f477c41331e66beab8bda865971982a1ff4.tar.gz
servo-2c259f477c41331e66beab8bda865971982a1ff4.zip
auto merge of #4057 : jdm/servo/refcountdom, r=Ms2ger
This replaces the specialized TrustedXHRAddress and TrustedWorkerAddress code that was used for the same purpose. A non-zero refcount pins the given DOM object's reflector and prevents it from being GCed even when there are no other outstanding references visible to SpiderMonkey. This will enable us to implement asynchronous operations that refer to particular DOM objects (such as "queue a task to fire a simple event named load at the iframe element" from the spec) safely and conveniently, and paves the way for things like asynchronous network responses. Some concerns about the resulting size of XHR progress messages have been expressed, but I believe optimizations to reduce that can be implemented in subsequent PRs. r? @Ms2ger - note in particular the changes to the worker lifetime code. I couldn't figure out how to achieve an identical lifetime to the previous addref/release pairing, and I also was having trouble figuring out why the existing setup was safe. The new implementation now holds the main script task Worker object alive via the TrustedWorkerAddress field in the dedicated worker global scope, which is a significant difference.
Diffstat (limited to 'components/script/dom/bindings')
-rw-r--r--components/script/dom/bindings/global.rs4
-rw-r--r--components/script/dom/bindings/js.rs20
-rw-r--r--components/script/dom/bindings/refcounted.rs178
-rw-r--r--components/script/dom/bindings/trace.rs10
4 files changed, 190 insertions, 22 deletions
diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs
index 4efeb7aaf67..38e018fe24f 100644
--- a/components/script/dom/bindings/global.rs
+++ b/components/script/dom/bindings/global.rs
@@ -10,7 +10,7 @@
use dom::bindings::conversions::FromJSValConvertible;
use dom::bindings::js::{JS, JSRef, Root};
use dom::bindings::utils::{Reflectable, Reflector};
-use dom::workerglobalscope::WorkerGlobalScope;
+use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers};
use dom::window;
use script_task::ScriptChan;
@@ -81,7 +81,7 @@ impl<'a> GlobalRef<'a> {
/// `ScriptChan` used to send messages to the event loop of this global's
/// thread.
- pub fn script_chan<'b>(&'b self) -> &'b ScriptChan {
+ pub fn script_chan(&self) -> Box<ScriptChan+Send> {
match *self {
GlobalRef::Window(ref window) => window.script_chan(),
GlobalRef::Worker(ref worker) => worker.script_chan(),
diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs
index 1f3cb7814e0..a1957f6797a 100644
--- a/components/script/dom/bindings/js.rs
+++ b/components/script/dom/bindings/js.rs
@@ -48,8 +48,6 @@
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::{Reflector, Reflectable};
use dom::node::Node;
-use dom::xmlhttprequest::{XMLHttpRequest, TrustedXHRAddress};
-use dom::worker::{Worker, TrustedWorkerAddress};
use js::jsapi::JSObject;
use js::jsval::JSVal;
use layout_interface::TrustedNodeAddress;
@@ -142,24 +140,6 @@ impl JS<Node> {
}
}
-impl JS<XMLHttpRequest> {
- pub unsafe fn from_trusted_xhr_address(inner: TrustedXHRAddress) -> JS<XMLHttpRequest> {
- let TrustedXHRAddress(addr) = inner;
- JS {
- ptr: addr as *const XMLHttpRequest
- }
- }
-}
-
-impl JS<Worker> {
- pub unsafe fn from_trusted_worker_address(inner: TrustedWorkerAddress) -> JS<Worker> {
- let TrustedWorkerAddress(addr) = inner;
- JS {
- ptr: addr as *const Worker
- }
- }
-}
-
impl<T: Reflectable> JS<T> {
/// Create a new JS-owned value wrapped from a raw Rust pointer.
pub unsafe fn from_raw(raw: *const T) -> JS<T> {
diff --git a/components/script/dom/bindings/refcounted.rs b/components/script/dom/bindings/refcounted.rs
new file mode 100644
index 00000000000..7bffda2153d
--- /dev/null
+++ b/components/script/dom/bindings/refcounted.rs
@@ -0,0 +1,178 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#![deny(missing_docs)]
+
+//! A generic, safe mechnanism by which DOM objects can be pinned and transferred
+//! between tasks (or intra-task for asynchronous events). Akin to Gecko's
+//! nsMainThreadPtrHandle, this uses thread-safe reference counting and ensures
+//! that the actual SpiderMonkey GC integration occurs on the script task via
+//! message passing. Ownership of a `Trusted<T>` object means the DOM object of
+//! type T to which it points remains alive. Any other behaviour is undefined.
+//! To guarantee the lifetime of a DOM object when performing asynchronous operations,
+//! obtain a `Trusted<T>` from that object and pass it along with each operation.
+//! A usable pointer to the original DOM object can be obtained on the script task
+//! from a `Trusted<T>` via the `to_temporary` method.
+//!
+//! The implementation of Trusted<T> is as follows:
+//! A hashtable resides in the script task, keyed on the pointer to the Rust DOM object.
+//! The values in this hashtable are atomic reference counts. When a Trusted<T> object is
+//! created or cloned, this count is increased. When a Trusted<T> is dropped, the count
+//! decreases. If the count hits zero, a message is dispatched to the script task to remove
+//! the entry from the hashmap if the count is still zero. The JS reflector for the DOM object
+//! is rooted when a hashmap entry is first created, and unrooted when the hashmap entry
+//! is removed.
+
+use dom::bindings::js::{Temporary, JS, JSRef};
+use dom::bindings::utils::{Reflector, Reflectable};
+use script_task::{ScriptMsg, ScriptChan};
+
+use js::jsapi::{JS_AddObjectRoot, JS_RemoveObjectRoot, JSContext};
+
+use libc;
+use std::cell::RefCell;
+use std::collections::hash_map::{HashMap, Vacant, Occupied};
+use std::sync::{Arc, Mutex};
+
+local_data_key!(pub LiveReferences: LiveDOMReferences)
+
+/// A safe wrapper around a raw pointer to a DOM object that can be
+/// shared among tasks for use in asynchronous operations. The underlying
+/// DOM object is guaranteed to live at least as long as the last outstanding
+/// `Trusted<T>` instance.
+pub struct Trusted<T> {
+ /// A pointer to the Rust DOM object of type T, but void to allow
+ /// sending `Trusted<T>` between tasks, regardless of T's sendability.
+ ptr: *const libc::c_void,
+ refcount: Arc<Mutex<uint>>,
+ script_chan: Box<ScriptChan + Send>,
+ owner_thread: *const libc::c_void,
+}
+
+impl<T: Reflectable> Trusted<T> {
+ /// Create a new `Trusted<T>` instance from an existing DOM pointer. The DOM object will
+ /// be prevented from being GCed for the duration of the resulting `Trusted<T>` object's
+ /// lifetime.
+ pub fn new(cx: *mut JSContext, ptr: JSRef<T>, script_chan: Box<ScriptChan + Send>) -> Trusted<T> {
+ let live_references = LiveReferences.get().unwrap();
+ let refcount = live_references.addref(cx, &*ptr as *const T);
+ Trusted {
+ ptr: &*ptr as *const T as *const libc::c_void,
+ refcount: refcount,
+ script_chan: script_chan,
+ owner_thread: (&*live_references) as *const _ as *const libc::c_void,
+ }
+ }
+
+ /// Obtain a usable DOM pointer from a pinned `Trusted<T>` value. Fails if used on
+ /// a different thread than the original value from which this `Trusted<T>` was
+ /// obtained.
+ pub fn to_temporary(&self) -> Temporary<T> {
+ assert!({
+ let live_references = LiveReferences.get().unwrap();
+ self.owner_thread == (&*live_references) as *const _ as *const libc::c_void
+ });
+ unsafe {
+ Temporary::new(JS::from_raw(self.ptr as *const T))
+ }
+ }
+}
+
+impl<T: Reflectable> Clone for Trusted<T> {
+ fn clone(&self) -> Trusted<T> {
+ {
+ let mut refcount = self.refcount.lock();
+ *refcount += 1;
+ }
+
+ Trusted {
+ ptr: self.ptr,
+ refcount: self.refcount.clone(),
+ script_chan: self.script_chan.clone(),
+ owner_thread: self.owner_thread,
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl<T: Reflectable> Drop for Trusted<T> {
+ fn drop(&mut self) {
+ let mut refcount = self.refcount.lock();
+ assert!(*refcount > 0);
+ *refcount -= 1;
+ if *refcount == 0 {
+ self.script_chan.send(ScriptMsg::RefcountCleanup(self.ptr));
+ }
+ }
+}
+
+/// The set of live, pinned DOM objects that are currently prevented
+/// from being garbage collected due to outstanding references.
+pub struct LiveDOMReferences {
+ // keyed on pointer to Rust DOM object
+ table: RefCell<HashMap<*const libc::c_void, Arc<Mutex<uint>>>>
+}
+
+impl LiveDOMReferences {
+ /// Set up the task-local data required for storing the outstanding DOM references.
+ pub fn initialize() {
+ LiveReferences.replace(Some(LiveDOMReferences {
+ table: RefCell::new(HashMap::new()),
+ }));
+ }
+
+ fn addref<T: Reflectable>(&self, cx: *mut JSContext, ptr: *const T) -> Arc<Mutex<uint>> {
+ let mut table = self.table.borrow_mut();
+ match table.entry(ptr as *const libc::c_void) {
+ Occupied(mut entry) => {
+ let refcount = entry.get_mut();
+ *refcount.lock() += 1;
+ refcount.clone()
+ }
+ Vacant(entry) => {
+ unsafe {
+ let rootable = (*ptr).reflector().rootable();
+ JS_AddObjectRoot(cx, rootable);
+ }
+ let refcount = Arc::new(Mutex::new(1));
+ entry.set(refcount.clone());
+ refcount
+ }
+ }
+ }
+
+ /// Unpin the given DOM object if its refcount is 0.
+ pub fn cleanup(cx: *mut JSContext, raw_reflectable: *const libc::c_void) {
+ let live_references = LiveReferences.get().unwrap();
+ let reflectable = raw_reflectable as *const Reflector;
+ let mut table = live_references.table.borrow_mut();
+ match table.entry(raw_reflectable) {
+ Occupied(entry) => {
+ if *entry.get().lock() != 0 {
+ // there could have been a new reference taken since
+ // this message was dispatched.
+ return;
+ }
+
+ unsafe {
+ JS_RemoveObjectRoot(cx, (*reflectable).rootable());
+ }
+ let _ = entry.take();
+ }
+ Vacant(_) => {
+ // there could be a cleanup message dispatched, then a new
+ // pinned reference obtained and released before the message
+ // is processed, at which point there would be no matching
+ // hashtable entry.
+ info!("attempt to cleanup an unrecognized reflector");
+ }
+ }
+ }
+}
+
+impl Drop for LiveDOMReferences {
+ fn drop(&mut self) {
+ assert!(self.table.borrow().keys().count() == 0);
+ }
+}
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index b877d022c2e..955638a7f7d 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -28,8 +28,10 @@
//! a datatype.
use dom::bindings::js::JS;
+use dom::bindings::refcounted::Trusted;
use dom::bindings::utils::{Reflectable, Reflector, WindowProxyHandler};
use dom::node::{Node, TrustedNodeAddress};
+use script_task::ScriptChan;
use collections::hash::{Hash, Hasher};
use cssparser::RGBA;
@@ -203,6 +205,7 @@ no_jsmanaged_fields!(Receiver<T>)
no_jsmanaged_fields!(Rect<T>)
no_jsmanaged_fields!(ImageCacheTask, ScriptControlChan)
no_jsmanaged_fields!(Atom, Namespace, Timer)
+no_jsmanaged_fields!(Trusted<T>)
no_jsmanaged_fields!(PropertyDeclarationBlock)
// These three are interdependent, if you plan to put jsmanaged data
// in one of these make sure it is propagated properly to containing structs
@@ -217,6 +220,13 @@ no_jsmanaged_fields!(UntrustedNodeAddress)
no_jsmanaged_fields!(LengthOrPercentageOrAuto)
no_jsmanaged_fields!(RGBA)
+impl JSTraceable for Box<ScriptChan+Send> {
+ #[inline]
+ fn trace(&self, _trc: *mut JSTracer) {
+ // Do nothing
+ }
+}
+
impl<'a> JSTraceable for &'a str {
#[inline]
fn trace(&self, _: *mut JSTracer) {