diff --git a/src/components/script/dom/bindings/global.rs b/src/components/script/dom/bindings/global.rs index ed7807f9d72..35b94d0e472 100644 --- a/src/components/script/dom/bindings/global.rs +++ b/src/components/script/dom/bindings/global.rs @@ -72,6 +72,8 @@ 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 { match *self { Window(ref window) => &window.script_chan, diff --git a/src/components/script/dom/bindings/js.rs b/src/components/script/dom/bindings/js.rs index 6a57b45449f..ab8b3e3c7f5 100644 --- a/src/components/script/dom/bindings/js.rs +++ b/src/components/script/dom/bindings/js.rs @@ -48,6 +48,7 @@ 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 layout_interface::TrustedNodeAddress; use script_task::StackRoots; @@ -143,6 +144,15 @@ impl JS { } } +impl JS { + pub unsafe fn from_trusted_worker_address(inner: TrustedWorkerAddress) -> JS { + let TrustedWorkerAddress(addr) = inner; + JS { + ptr: addr as *const Worker + } + } +} + impl JS { /// Create a new JS-owned value wrapped from a raw Rust pointer. pub unsafe fn from_raw(raw: *const T) -> JS { diff --git a/src/components/script/dom/dedicatedworkerglobalscope.rs b/src/components/script/dom/dedicatedworkerglobalscope.rs index f9e3210715e..08b27eb193f 100644 --- a/src/components/script/dom/dedicatedworkerglobalscope.rs +++ b/src/components/script/dom/dedicatedworkerglobalscope.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding; +use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods; use dom::bindings::codegen::InheritTypes::DedicatedWorkerGlobalScopeDerived; use dom::bindings::codegen::InheritTypes::{EventTargetCast, WorkerGlobalScopeCast}; use dom::bindings::global::Worker; @@ -12,13 +13,17 @@ use dom::bindings::utils::{Reflectable, Reflector}; use dom::eventtarget::EventTarget; use dom::eventtarget::WorkerGlobalScopeTypeId; use dom::messageevent::MessageEvent; +use dom::worker::{Worker, TrustedWorkerAddress}; use dom::workerglobalscope::DedicatedGlobalScope; use dom::workerglobalscope::WorkerGlobalScope; use dom::xmlhttprequest::XMLHttpRequest; -use script_task::{ScriptTask, ScriptChan, ScriptMsg, DOMMessage, XHRProgressMsg}; +use script_task::{ScriptTask, ScriptChan}; +use script_task::{ScriptMsg, DOMMessage, XHRProgressMsg, WorkerRelease}; +use script_task::WorkerPostMessage; use script_task::StackRootTLS; use servo_net::resource_task::{ResourceTask, load_whole_resource}; +use servo_util::str::DOMString; use js::rust::Cx; @@ -31,40 +36,52 @@ use url::Url; pub struct DedicatedWorkerGlobalScope { workerglobalscope: WorkerGlobalScope, receiver: Untraceable>, + /// Sender to the parent thread. + parent_sender: ScriptChan, + worker: Untraceable, } impl DedicatedWorkerGlobalScope { pub fn new_inherited(worker_url: Url, + worker: TrustedWorkerAddress, cx: Rc, - receiver: Receiver, resource_task: ResourceTask, - script_chan: ScriptChan) + parent_sender: ScriptChan, + own_sender: ScriptChan, + receiver: Receiver) -> DedicatedWorkerGlobalScope { DedicatedWorkerGlobalScope { workerglobalscope: WorkerGlobalScope::new_inherited( DedicatedGlobalScope, worker_url, cx, resource_task, - script_chan), + own_sender), receiver: Untraceable::new(receiver), + parent_sender: parent_sender, + worker: Untraceable::new(worker), } } pub fn new(worker_url: Url, + worker: TrustedWorkerAddress, cx: Rc, - receiver: Receiver, resource_task: ResourceTask, - script_chan: ScriptChan) + parent_sender: ScriptChan, + own_sender: ScriptChan, + receiver: Receiver) -> Temporary { let scope = box DedicatedWorkerGlobalScope::new_inherited( - worker_url, cx.clone(), receiver, resource_task, script_chan); + worker_url, worker, cx.clone(), resource_task, parent_sender, + own_sender, receiver); DedicatedWorkerGlobalScopeBinding::Wrap(cx.ptr, scope) } } impl DedicatedWorkerGlobalScope { pub fn run_worker_scope(worker_url: Url, - resource_task: ResourceTask) -> ScriptChan { - let (receiver, sender) = ScriptChan::new(); - let sender_clone = sender.clone(); + worker: TrustedWorkerAddress, + resource_task: ResourceTask, + parent_sender: ScriptChan, + own_sender: ScriptChan, + receiver: Receiver) { TaskBuilder::new() .native() .named(format!("Web Worker at {}", worker_url.serialize())) @@ -84,13 +101,14 @@ impl DedicatedWorkerGlobalScope { let (_js_runtime, js_context) = ScriptTask::new_rt_and_cx(); let global = DedicatedWorkerGlobalScope::new( - worker_url, js_context.clone(), receiver, resource_task, - sender).root(); + worker_url, worker, js_context.clone(), resource_task, + parent_sender, own_sender, receiver).root(); match js_context.evaluate_script( global.reflector().get_jsobject(), source, url.serialize(), 1) { Ok(_) => (), Err(_) => println!("evaluate_script failed") } + global.delayed_release_worker(); let scope: &JSRef = WorkerGlobalScopeCast::from_ref(&*global); @@ -99,21 +117,42 @@ impl DedicatedWorkerGlobalScope { loop { match global.receiver.recv_opt() { Ok(DOMMessage(message)) => { - MessageEvent::dispatch(target, &Worker(*scope), message) + MessageEvent::dispatch(target, &Worker(*scope), message); + global.delayed_release_worker(); }, Ok(XHRProgressMsg(addr, progress)) => { XMLHttpRequest::handle_xhr_progress(addr, progress) }, + Ok(WorkerPostMessage(addr, message)) => { + Worker::handle_message(addr, message); + }, + Ok(WorkerRelease(addr)) => { + Worker::handle_release(addr) + }, Ok(_) => fail!("Unexpected message"), Err(_) => break, } } }); - return sender_clone; } } -pub trait DedicatedWorkerGlobalScopeMethods { +impl<'a> DedicatedWorkerGlobalScopeMethods for JSRef<'a, DedicatedWorkerGlobalScope> { + fn PostMessage(&self, message: DOMString) { + let ScriptChan(ref sender) = self.parent_sender; + sender.send(WorkerPostMessage(*self.worker, message)); + } +} + +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(WorkerRelease(*self.worker)); + } } impl Reflectable for DedicatedWorkerGlobalScope { diff --git a/src/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl b/src/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl index c0c69219e1e..d92b8a73911 100644 --- a/src/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl +++ b/src/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl @@ -6,5 +6,6 @@ [Global/*=Worker,DedicatedWorker*/] /*sealed*/ interface DedicatedWorkerGlobalScope : WorkerGlobalScope { //void postMessage(any message, optional sequence transfer); + void postMessage(DOMString message); // attribute EventHandler onmessage; }; diff --git a/src/components/script/dom/worker.rs b/src/components/script/dom/worker.rs index 670ae7a5beb..f5e25db68e2 100644 --- a/src/components/script/dom/worker.rs +++ b/src/components/script/dom/worker.rs @@ -4,33 +4,48 @@ use dom::bindings::codegen::Bindings::WorkerBinding; use dom::bindings::codegen::Bindings::WorkerBinding::WorkerMethods; +use dom::bindings::codegen::InheritTypes::EventTargetCast; use dom::bindings::error::{Fallible, Syntax}; -use dom::bindings::global::GlobalRef; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::global::{GlobalRef, GlobalField}; +use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; use dom::eventtarget::{EventTarget, WorkerTypeId}; +use dom::messageevent::MessageEvent; use script_task::{ScriptChan, DOMMessage}; use servo_util::str::DOMString; + +use js::jsapi::{JS_AddObjectRoot, JS_RemoveObjectRoot}; use url::UrlParser; +use libc::c_void; +use std::cell::Cell; + +pub struct TrustedWorkerAddress(pub *const c_void); + #[deriving(Encodable)] pub struct Worker { eventtarget: EventTarget, + refcount: Cell, + global: GlobalField, + /// Sender to the Receiver associated with the DedicatedWorkerGlobalScope + /// this Worker created. sender: ScriptChan, } impl Worker { - pub fn new_inherited(sender: ScriptChan) -> Worker { + pub fn new_inherited(global: &GlobalRef, sender: ScriptChan) -> Worker { Worker { eventtarget: EventTarget::new_inherited(WorkerTypeId), + refcount: Cell::new(0), + global: GlobalField::from_rooted(global), sender: sender, } } pub fn new(global: &GlobalRef, sender: ScriptChan) -> Temporary { - reflect_dom_object(box Worker::new_inherited(sender), + reflect_dom_object(box Worker::new_inherited(global, sender), global, WorkerBinding::Wrap) } @@ -45,14 +60,62 @@ impl Worker { }; let resource_task = global.resource_task(); - let sender = DedicatedWorkerGlobalScope::run_worker_scope( - worker_url, resource_task); - Ok(Worker::new(global, sender)) + let (receiver, sender) = ScriptChan::new(); + + let worker = Worker::new(global, sender.clone()).root(); + let worker_ref = worker.addref(); + + DedicatedWorkerGlobalScope::run_worker_scope( + worker_url, worker_ref, resource_task, global.script_chan().clone(), + sender, receiver); + + Ok(Temporary::from_rooted(&*worker)) + } + + pub fn handle_message(address: TrustedWorkerAddress, message: DOMString) { + let worker = unsafe { JS::from_trusted_worker_address(address).root() }; + + let target: &JSRef = EventTargetCast::from_ref(&*worker); + let global = worker.global.root(); + MessageEvent::dispatch(target, &global.root_ref(), message); + } +} + +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, message: DOMString) { + self.addref(); let ScriptChan(ref sender) = self.sender; sender.send(DOMMessage(message)); } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index ba6cd12c372..dd01017ff76 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -20,6 +20,7 @@ use dom::eventtarget::{EventTarget, EventTargetHelpers}; use dom::node; use dom::node::{ElementNodeTypeId, Node, NodeHelpers}; use dom::window::{TimerId, Window, WindowHelpers}; +use dom::worker::{Worker, TrustedWorkerAddress}; use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress}; use html::hubbub_html_parser::HtmlParserResult; use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredScript}; @@ -86,6 +87,10 @@ pub enum ScriptMsg { /// Message sent through Worker.postMessage (only dispatched to /// DedicatedWorkerGlobalScope). DOMMessage(DOMString), + /// Posts a message to the Worker object (dispatched to all tasks). + WorkerPostMessage(TrustedWorkerAddress, DOMString), + /// Releases one reference to the Worker object (dispatched to all tasks). + WorkerRelease(TrustedWorkerAddress), } /// Encapsulates internal communication within the script task. @@ -441,6 +446,8 @@ impl ScriptTask { FromConstellation(ResizeMsg(..)) => fail!("should have handled ResizeMsg already"), FromScript(XHRProgressMsg(addr, progress)) => XMLHttpRequest::handle_xhr_progress(addr, progress), FromScript(DOMMessage(..)) => fail!("unexpected message"), + FromScript(WorkerPostMessage(addr, message)) => Worker::handle_message(addr, message), + FromScript(WorkerRelease(addr)) => Worker::handle_release(addr), } }