diff options
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/bindings/js.rs | 20 | ||||
-rw-r--r-- | components/script/dom/bindings/refcounted.rs | 174 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 2 | ||||
-rw-r--r-- | components/script/dom/dedicatedworkerglobalscope.rs | 24 | ||||
-rw-r--r-- | components/script/dom/worker.rs | 50 | ||||
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 80 |
6 files changed, 201 insertions, 149 deletions
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..6118408ed4c --- /dev/null +++ b/components/script/dom/bindings/refcounted.rs @@ -0,0 +1,174 @@ +/* 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> { + cx: *mut JSContext, + /// 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: ScriptChan, +} + +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: ScriptChan) -> Trusted<T> { + let live_references = LiveReferences.get().unwrap(); + let refcount = live_references.addref(cx, &*ptr as *const T); + Trusted { + cx: cx, + ptr: &*ptr as *const T as *const libc::c_void, + refcount: refcount, + script_chan: script_chan, + } + } + + /// Obtain a usable DOM pointer from a pinned `Trusted<T>` value. Attempts to use the + /// resulting `Temporary<T>` off of the script thread will fail. + pub fn to_temporary(&self) -> Temporary<T> { + 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 { + cx: self.cx, + ptr: self.ptr, + refcount: self.refcount.clone(), + script_chan: self.script_chan.clone(), + } + } +} + +#[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 { + let ScriptChan(ref chan) = self.script_chan; + 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..0538e847049 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -28,6 +28,7 @@ //! a datatype. use dom::bindings::js::JS; +use dom::bindings::refcounted::Trusted; use dom::bindings::utils::{Reflectable, Reflector, WindowProxyHandler}; use dom::node::{Node, TrustedNodeAddress}; @@ -203,6 +204,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 diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index ae0bee5c670..0543e25a4c3 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -11,6 +11,7 @@ use dom::bindings::error::ErrorResult; use dom::bindings::error::Error::DataClone; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JSRef, Temporary, RootCollection}; +use dom::bindings::refcounted::LiveDOMReferences; use dom::bindings::utils::Reflectable; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; use dom::messageevent::MessageEvent; @@ -110,7 +111,6 @@ impl DedicatedWorkerGlobalScope { Ok(_) => (), Err(_) => println!("evaluate_script failed") } - global.delayed_release_worker(); let scope: JSRef<WorkerGlobalScope> = WorkerGlobalScopeCast::from_ref(*global); @@ -118,7 +118,7 @@ impl DedicatedWorkerGlobalScope { EventTargetCast::from_ref(*global); loop { match global.receiver.recv_opt() { - Ok(ScriptMsg::DOMMessage(data, nbytes)) => { + Ok(ScriptMsg::DOMMessage(_addr, data, nbytes)) => { let mut message = UndefinedValue(); unsafe { assert!(JS_ReadStructuredClone( @@ -128,7 +128,6 @@ impl DedicatedWorkerGlobalScope { } MessageEvent::dispatch_jsval(target, GlobalRef::Worker(scope), message); - global.delayed_release_worker(); }, Ok(ScriptMsg::RunnableMsg(runnable)) => { runnable.handler() @@ -136,9 +135,9 @@ impl DedicatedWorkerGlobalScope { Ok(ScriptMsg::WorkerPostMessage(addr, data, nbytes)) => { Worker::handle_message(addr, data, nbytes); }, - Ok(ScriptMsg::WorkerRelease(addr)) => { - Worker::handle_release(addr) - }, + Ok(ScriptMsg::RefcountCleanup(addr)) => { + LiveDOMReferences::cleanup(js_context.ptr, addr); + } Ok(ScriptMsg::FireTimer(TimerSource::FromWorker, timer_id)) => { scope.handle_fire_timer(timer_id); } @@ -164,24 +163,13 @@ impl<'a> DedicatedWorkerGlobalScopeMethods for JSRef<'a, DedicatedWorkerGlobalSc } let ScriptChan(ref sender) = self.parent_sender; - sender.send(ScriptMsg::WorkerPostMessage(self.worker, data, nbytes)); + sender.send(ScriptMsg::WorkerPostMessage(self.worker.clone(), data, nbytes)); Ok(()) } event_handler!(message, GetOnmessage, SetOnmessage) } -trait PrivateDedicatedWorkerGlobalScopeHelpers { - fn delayed_release_worker(self); -} - -impl<'a> PrivateDedicatedWorkerGlobalScopeHelpers for JSRef<'a, DedicatedWorkerGlobalScope> { - fn delayed_release_worker(self) { - let ScriptChan(ref sender) = self.parent_sender; - sender.send(ScriptMsg::WorkerRelease(self.worker)); - } -} - impl DedicatedWorkerGlobalScopeDerived for EventTarget { fn is_dedicatedworkerglobalscope(&self) -> bool { match *self.type_id() { diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 4d03d312223..e6708954a41 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -9,7 +9,8 @@ use dom::bindings::codegen::InheritTypes::EventTargetCast; use dom::bindings::error::{Fallible, ErrorResult}; use dom::bindings::error::Error::{Syntax, DataClone}; use dom::bindings::global::{GlobalRef, GlobalField}; -use dom::bindings::js::{JS, JSRef, Temporary}; +use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::refcounted::Trusted; use dom::bindings::trace::JSTraceable; use dom::bindings::utils::{Reflectable, reflect_dom_object}; use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; @@ -20,17 +21,16 @@ use script_task::{ScriptChan, ScriptMsg}; use servo_util::str::DOMString; use js::glue::JS_STRUCTURED_CLONE_VERSION; -use js::jsapi::{JSContext, JS_AddObjectRoot, JS_RemoveObjectRoot, JSTracer}; +use js::jsapi::JSContext; use js::jsapi::{JS_ReadStructuredClone, JS_WriteStructuredClone, JS_ClearPendingException}; use js::jsval::{JSVal, UndefinedValue}; use url::UrlParser; -use libc::{c_void, size_t}; +use libc::size_t; use std::cell::Cell; use std::ptr; -pub struct TrustedWorkerAddress(pub *const c_void); -no_jsmanaged_fields!(TrustedWorkerAddress) +pub type TrustedWorkerAddress = Trusted<Worker>; #[dom_struct] pub struct Worker { @@ -71,7 +71,7 @@ impl Worker { let (receiver, sender) = ScriptChan::new(); let worker = Worker::new(global, sender.clone()).root(); - let worker_ref = worker.addref(); + let worker_ref = Trusted::new(global.get_cx(), *worker, global.script_chan().clone()); DedicatedWorkerGlobalScope::run_worker_scope( worker_url, worker_ref, resource_task, global.script_chan().clone(), @@ -82,7 +82,7 @@ impl Worker { pub fn handle_message(address: TrustedWorkerAddress, data: *mut u64, nbytes: size_t) { - let worker = unsafe { JS::from_trusted_worker_address(address).root() }; + let worker = address.to_temporary().root(); let global = worker.global.root(); @@ -99,38 +99,6 @@ impl Worker { } } -impl Worker { - // Creates a trusted address to the object, and roots it. Always pair this with a release() - pub fn addref(&self) -> TrustedWorkerAddress { - let refcount = self.refcount.get(); - if refcount == 0 { - let cx = self.global.root().root_ref().get_cx(); - unsafe { - JS_AddObjectRoot(cx, self.reflector().rootable()); - } - } - self.refcount.set(refcount + 1); - TrustedWorkerAddress(self as *const Worker as *const c_void) - } - - pub fn release(&self) { - let refcount = self.refcount.get(); - assert!(refcount > 0) - self.refcount.set(refcount - 1); - if refcount == 1 { - let cx = self.global.root().root_ref().get_cx(); - unsafe { - JS_RemoveObjectRoot(cx, self.reflector().rootable()); - } - } - } - - pub fn handle_release(address: TrustedWorkerAddress) { - let worker = unsafe { JS::from_trusted_worker_address(address).root() }; - worker.release(); - } -} - impl<'a> WorkerMethods for JSRef<'a, Worker> { fn PostMessage(self, cx: *mut JSContext, message: JSVal) -> ErrorResult { let mut data = ptr::null_mut(); @@ -144,9 +112,9 @@ impl<'a> WorkerMethods for JSRef<'a, Worker> { return Err(DataClone); } - self.addref(); + let addr = Trusted::new(cx, self, self.global.root().root_ref().script_chan().clone()); let ScriptChan(ref sender) = self.sender; - sender.send(ScriptMsg::DOMMessage(data, nbytes)); + sender.send(ScriptMsg::DOMMessage(addr, data, nbytes)); Ok(()) } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 9c31ff28d9f..c69d8e805a8 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -15,6 +15,7 @@ use dom::bindings::error::Error::{InvalidState, InvalidAccess}; use dom::bindings::error::Error::{Network, Syntax, Security, Abort, Timeout}; use dom::bindings::global::{GlobalField, GlobalRef, GlobalRoot}; use dom::bindings::js::{MutNullableJS, JS, JSRef, Temporary, OptionalRootedRootable}; +use dom::bindings::refcounted::Trusted; use dom::bindings::str::ByteString; use dom::bindings::utils::{Reflectable, reflect_dom_object}; use dom::document::Document; @@ -37,13 +38,10 @@ use hyper::http::RawStatus; use hyper::mime::{mod, Mime}; use hyper::method::{Method, Get, Head, Connect, Trace, Extension}; -use js::jsapi::{JS_AddObjectRoot, JS_ParseJSON, JS_RemoveObjectRoot, JSContext}; +use js::jsapi::{JS_ParseJSON, JSContext}; use js::jsapi::JS_ClearPendingException; use js::jsval::{JSVal, NullValue, UndefinedValue}; -use libc; -use libc::c_void; - use net::resource_task::{ResourceTask, ResourceCORSData, Load, LoadData, LoadResponse, Payload, Done}; use cors::{allow_cross_origin_request, CORSRequest, RequestMode}; use servo_util::str::DOMString; @@ -73,15 +71,6 @@ enum XMLHttpRequestState { XHRDone = 4, // So as not to conflict with the ProgressMsg `Done` } -struct XHRReleaseHandler(TrustedXHRAddress); - -impl Runnable for XHRReleaseHandler { - fn handler(&self) { - let XHRReleaseHandler(addr) = *self; - XMLHttpRequest::handle_release(addr); - } -} - struct XHRProgressHandler { addr: TrustedXHRAddress, progress: XHRProgress, @@ -95,7 +84,7 @@ impl XHRProgressHandler { impl Runnable for XHRProgressHandler { fn handler(&self) { - XMLHttpRequest::handle_progress(self.addr, self.progress.clone()); + XMLHttpRequest::handle_progress(self.addr.clone(), self.progress.clone()); } } @@ -162,7 +151,6 @@ pub struct XMLHttpRequest { send_flag: Cell<bool>, global: GlobalField, - pinned_count: Cell<uint>, timer: DOMRefCell<Timer>, fetch_time: Cell<i64>, terminate_sender: DOMRefCell<Option<Sender<TerminateReason>>>, @@ -196,7 +184,6 @@ impl XMLHttpRequest { upload_events: Cell::new(false), global: GlobalField::from_rooted(global), - pinned_count: Cell::new(0), timer: DOMRefCell::new(Timer::new().unwrap()), fetch_time: Cell::new(0), terminate_sender: DOMRefCell::new(None), @@ -213,14 +200,8 @@ impl XMLHttpRequest { } pub fn handle_progress(addr: TrustedXHRAddress, progress: XHRProgress) { - unsafe { - let xhr = JS::from_trusted_xhr_address(addr).root(); - xhr.process_partial_response(progress); - } - } - - pub fn handle_release(addr: TrustedXHRAddress) { - addr.release_once(); + let xhr = addr.to_temporary().root(); + xhr.process_partial_response(progress); } fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask, @@ -233,9 +214,9 @@ impl XMLHttpRequest { SyncOrAsync::Sync(xhr) => { xhr.process_partial_response(msg); }, - SyncOrAsync::Async(addr, script_chan) => { + SyncOrAsync::Async(ref addr, script_chan) => { let ScriptChan(ref chan) = *script_chan; - chan.send(ScriptMsg::RunnableMsg(box XHRProgressHandler::new(addr, msg))); + chan.send(ScriptMsg::RunnableMsg(box XHRProgressHandler::new(addr.clone(), msg))); } } } @@ -639,9 +620,8 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { // unpin it. This is to ensure that the object will stay alive // as long as there are (possibly cancelled) inflight events queued up // in the script task's port - let addr = unsafe { - self.to_trusted() - }; + let addr = Trusted::new(self.global.root().root_ref().get_cx(), self, + script_chan.clone()); spawn_named("XHRTask", proc() { let _ = XMLHttpRequest::fetch(&mut SyncOrAsync::Async(addr, &script_chan), resource_task, @@ -650,8 +630,6 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { cors_request, gen_id, start_port); - let ScriptChan(ref chan) = script_chan; - chan.send(ScriptMsg::RunnableMsg(box XHRReleaseHandler(addr))); }); let timeout = self.timeout.get(); if timeout > 0 { @@ -768,20 +746,9 @@ impl XMLHttpRequestDerived for EventTarget { } } -pub struct TrustedXHRAddress(pub *const c_void); - -impl TrustedXHRAddress { - pub fn release_once(self) { - unsafe { - JS::from_trusted_xhr_address(self).root().release_once(); - } - } -} - +pub type TrustedXHRAddress = Trusted<XMLHttpRequest>; trait PrivateXMLHttpRequestHelpers { - unsafe fn to_trusted(self) -> TrustedXHRAddress; - fn release_once(self); fn change_ready_state(self, XMLHttpRequestState); fn process_partial_response(self, progress: XHRProgress); fn terminate_ongoing_fetch(self); @@ -796,33 +763,6 @@ trait PrivateXMLHttpRequestHelpers { } impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> { - // Creates a trusted address to the object, and roots it. Always pair this with a release() - unsafe fn to_trusted(self) -> TrustedXHRAddress { - if self.pinned_count.get() == 0 { - JS_AddObjectRoot(self.global.root().root_ref().get_cx(), self.reflector().rootable()); - } - let pinned_count = self.pinned_count.get(); - self.pinned_count.set(pinned_count + 1); - TrustedXHRAddress(self.deref() as *const XMLHttpRequest as *const libc::c_void) - } - - fn release_once(self) { - if self.sync.get() { - // Lets us call this at various termination cases without having to - // check self.sync every time, since the pinning mechanism only is - // meaningful during an async fetch - return; - } - assert!(self.pinned_count.get() > 0) - let pinned_count = self.pinned_count.get(); - self.pinned_count.set(pinned_count - 1); - if self.pinned_count.get() == 0 { - unsafe { - JS_RemoveObjectRoot(self.global.root().root_ref().get_cx(), self.reflector().rootable()); - } - } - } - fn change_ready_state(self, rs: XMLHttpRequestState) { assert!(self.ready_state.get() != rs) self.ready_state.set(rs); |