diff options
-rw-r--r-- | components/script/dom/bindings/global.rs | 31 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 2 | ||||
-rw-r--r-- | components/script/dom/dedicatedworkerglobalscope.rs | 38 | ||||
-rw-r--r-- | components/script/dom/webidls/Worker.webidl | 2 | ||||
-rw-r--r-- | components/script/dom/worker.rs | 78 | ||||
-rw-r--r-- | components/script/dom/workerglobalscope.rs | 29 | ||||
-rw-r--r-- | tests/html/worker/worker_post_block.js | 9 | ||||
-rw-r--r-- | tests/html/worker/worker_post_interval.js | 6 | ||||
-rw-r--r-- | tests/html/worker/worker_terminate.html | 48 | ||||
-rw-r--r-- | tests/wpt/metadata/html/dom/interfaces.html.ini | 3 | ||||
-rw-r--r-- | tests/wpt/metadata/workers/Worker_terminate_event_queue.htm.ini | 3 | ||||
-rw-r--r-- | tests/wpt/metadata/workers/constructors/Worker/terminate.html.ini | 5 | ||||
-rw-r--r-- | tests/wpt/metadata/workers/nested_worker.worker.js.ini | 9 |
13 files changed, 216 insertions, 47 deletions
diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index 8cb8c54b426..9cac7d9b940 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -15,7 +15,7 @@ use dom::bindings::reflector::{Reflectable, Reflector}; use dom::window::{self, ScriptHelpers}; use dom::workerglobalscope::WorkerGlobalScope; use ipc_channel::ipc::IpcSender; -use js::jsapi::GetGlobalForObjectCrossCompartment; +use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment}; use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue}; use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; use msg::constellation_msg::{ConstellationChan, PipelineId}; @@ -147,9 +147,8 @@ impl<'a> GlobalRef<'a> { /// thread. pub fn script_chan(&self) -> Box<ScriptChan + Send> { match *self { - GlobalRef::Window(ref window) => { - MainThreadScriptChan(window.main_thread_script_chan().clone()).clone() - } + GlobalRef::Window(ref window) => + MainThreadScriptChan(window.main_thread_script_chan().clone()).clone(), GlobalRef::Worker(ref worker) => worker.script_chan(), } } @@ -280,11 +279,10 @@ pub fn global_root_from_reflector<T: Reflectable>(reflector: &T) -> GlobalRoot { global_root_from_object(*reflector.reflector().get_jsobject()) } -/// Returns the global object of the realm that the given JS object was created in. +/// Returns the Rust global object from a JS global object. #[allow(unrooted_must_root)] -pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot { +pub fn global_root_from_global(global: *mut JSObject) -> GlobalRoot { unsafe { - let global = GetGlobalForObjectCrossCompartment(obj); let clasp = JS_GetClass(global); assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); match root_from_object(global) { @@ -300,3 +298,22 @@ pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot { panic!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope") } } + +/// Returns the global object of the realm that the given JS object was created in. +#[allow(unrooted_must_root)] +pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot { + unsafe { + let global = GetGlobalForObjectCrossCompartment(obj); + global_root_from_global(global) + } +} + +/// Returns the global object for the given JSContext +#[allow(unrooted_must_root)] +pub fn global_root_from_context(cx: *mut JSContext) -> GlobalRoot { + unsafe { + let global = CurrentGlobalOrNull(cx); + assert!(!global.is_null()); + global_root_from_global(global) + } +} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index cf831f2a226..9cc2d13fb77 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -38,6 +38,7 @@ use dom::bindings::js::{JS, Root}; use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::utils::WindowProxyHandler; +use dom::worker::SharedRt; use encoding::types::EncodingRef; use euclid::length::Length as EuclidLength; use euclid::matrix2d::Matrix2D; @@ -319,6 +320,7 @@ no_jsmanaged_fields!(AttrIdentifier); no_jsmanaged_fields!(AttrValue); no_jsmanaged_fields!(ElementSnapshot); no_jsmanaged_fields!(HttpsState); +no_jsmanaged_fields!(SharedRt); no_jsmanaged_fields!(TouchpadPressurePhase); impl JSTraceable for ConstellationChan<ScriptMsg> { diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 32ec841fc08..1b2d18b18b3 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -9,20 +9,21 @@ use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding; use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods; use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use dom::bindings::error::ErrorResult; -use dom::bindings::global::GlobalRef; +use dom::bindings::global::{GlobalRef, global_root_from_context}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{Root, RootCollection}; use dom::bindings::refcounted::LiveDOMReferences; use dom::bindings::reflector::Reflectable; use dom::bindings::structuredclone::StructuredCloneData; +use dom::bindings::trace::JSTraceable; use dom::messageevent::MessageEvent; -use dom::worker::{SimpleWorkerErrorHandler, TrustedWorkerAddress, WorkerMessageHandler}; +use dom::worker::{SimpleWorkerErrorHandler, SharedRt, TrustedWorkerAddress, WorkerMessageHandler}; use dom::workerglobalscope::WorkerGlobalScope; use dom::workerglobalscope::WorkerGlobalScopeInit; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; -use js::jsapi::{HandleValue, JSContext, RootedValue}; -use js::jsapi::{JSAutoCompartment, JSAutoRequest}; +use js::jsapi::{HandleValue, JS_SetInterruptCallback}; +use js::jsapi::{JSAutoCompartment, JSAutoRequest, JSContext, RootedValue}; use js::jsval::UndefinedValue; use js::rust::Runtime; use msg::constellation_msg::PipelineId; @@ -33,6 +34,7 @@ use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_ use script_traits::{TimerEvent, TimerSource}; use std::mem::replace; use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel}; +use std::sync::{Arc, Mutex}; use url::Url; use util::str::DOMString; use util::thread::spawn_named; @@ -206,10 +208,12 @@ impl DedicatedWorkerGlobalScope { DedicatedWorkerGlobalScopeBinding::Wrap(cx, scope) } + #[allow(unsafe_code)] pub fn run_worker_scope(init: WorkerGlobalScopeInit, worker_url: Url, id: PipelineId, from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, + main_thread_rt: Arc<Mutex<Option<SharedRt>>>, worker: TrustedWorkerAddress, parent_sender: Box<ScriptChan + Send>, own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, @@ -237,6 +241,7 @@ impl DedicatedWorkerGlobalScope { }; let runtime = new_rt_and_cx(); + *main_thread_rt.lock().unwrap() = Some(SharedRt::new(&runtime)); let (devtools_mpsc_chan, devtools_mpsc_port) = channel(); ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan); @@ -257,6 +262,15 @@ impl DedicatedWorkerGlobalScope { // registration (#6631), so we instead use a random number and cross our fingers. let scope = global.upcast::<WorkerGlobalScope>(); + unsafe { + // Handle interrupt requests + JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback)); + } + + if scope.is_closing() { + return; + } + { let _ar = AutoWorkerReset::new(global.r(), worker); scope.execute_script(DOMString::from(source)); @@ -265,6 +279,9 @@ impl DedicatedWorkerGlobalScope { let reporter_name = format!("worker-reporter-{}", random::<u64>()); scope.mem_profiler_chan().run_with_memory_reporting(|| { while let Ok(event) = global.receive_event() { + if scope.is_closing() { + break; + } global.handle_event(event); } }, reporter_name, parent_sender, CommonScriptMsg::CollectReports); @@ -387,6 +404,19 @@ impl DedicatedWorkerGlobalScope { } } +#[allow(unsafe_code)] +unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { + let global = global_root_from_context(cx); + let worker = match global.r() { + GlobalRef::Worker(w) => w, + _ => panic!("global for worker is not a worker scope") + }; + assert!(worker.is::<DedicatedWorkerGlobalScope>()); + + // A false response causes the script to terminate + !worker.is_closing() +} + impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult { diff --git a/components/script/dom/webidls/Worker.webidl b/components/script/dom/webidls/Worker.webidl index 62cd5c89f92..5331d6fdc36 100644 --- a/components/script/dom/webidls/Worker.webidl +++ b/components/script/dom/webidls/Worker.webidl @@ -12,7 +12,7 @@ interface AbstractWorker { // https://html.spec.whatwg.org/multipage/#worker [Constructor(DOMString scriptURL)/*, Exposed=Window,Worker*/] interface Worker : EventTarget { - //void terminate(); + void terminate(); [Throws] void postMessage(any message/*, optional sequence<Transferable> transfer*/); diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index f1f15c3c8f9..f6cf53b801e 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -21,12 +21,15 @@ use dom::eventtarget::EventTarget; use dom::messageevent::MessageEvent; use dom::workerglobalscope::WorkerGlobalScopeInit; use ipc_channel::ipc; -use js::jsapi::{HandleValue, JSContext, RootedValue}; -use js::jsapi::{JSAutoCompartment, JSAutoRequest}; +use js::jsapi::{HandleValue, JSContext, JSRuntime, RootedValue}; +use js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_RequestInterruptCallback}; use js::jsval::UndefinedValue; +use js::rust::Runtime; use script_runtime::ScriptChan; use script_thread::Runnable; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Sender, channel}; +use std::sync::{Arc, Mutex}; use util::str::DOMString; pub type TrustedWorkerAddress = Trusted<Worker>; @@ -39,21 +42,26 @@ pub struct Worker { /// Sender to the Receiver associated with the DedicatedWorkerGlobalScope /// this Worker created. sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, + closing: Arc<AtomicBool>, + #[ignore_heap_size_of = "Defined in rust-mozjs"] + runtime: Arc<Mutex<Option<SharedRt>>> } impl Worker { - fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>) - -> Worker { + fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, + closing: Arc<AtomicBool>) -> Worker { Worker { eventtarget: EventTarget::new_inherited(), sender: sender, + closing: closing, + runtime: Arc::new(Mutex::new(None)) } } pub fn new(global: GlobalRef, - sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>) - -> Root<Worker> { - reflect_dom_object(box Worker::new_inherited(sender), + sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, + closing: Arc<AtomicBool>) -> Root<Worker> { + reflect_dom_object(box Worker::new_inherited(sender, closing), global, WorkerBinding::Wrap) } @@ -71,7 +79,8 @@ impl Worker { let scheduler_chan = global.scheduler_chan(); let (sender, receiver) = channel(); - let worker = Worker::new(global, sender.clone()); + let closing = Arc::new(AtomicBool::new(false)); + let worker = Worker::new(global, sender.clone(), closing.clone()); let worker_ref = Trusted::new(worker.r(), global.script_chan()); let worker_id = global.get_next_worker_id(); @@ -100,18 +109,28 @@ impl Worker { constellation_chan: constellation_chan, scheduler_chan: scheduler_chan, worker_id: worker_id, + closing: closing, }; + DedicatedWorkerGlobalScope::run_worker_scope( - init, worker_url, global.pipeline(), devtools_receiver, worker_ref, + init, worker_url, global.pipeline(), devtools_receiver, worker.runtime.clone(), worker_ref, global.script_chan(), sender, receiver); Ok(worker) } + pub fn is_closing(&self) -> bool { + self.closing.load(Ordering::SeqCst) + } + pub fn handle_message(address: TrustedWorkerAddress, data: StructuredCloneData) { let worker = address.root(); + if worker.is_closing() { + return; + } + let global = worker.r().global(); let target = worker.upcast(); let _ar = JSAutoRequest::new(global.r().get_cx()); @@ -129,6 +148,11 @@ impl Worker { pub fn handle_error_message(address: TrustedWorkerAddress, message: DOMString, filename: DOMString, lineno: u32, colno: u32) { let worker = address.root(); + + if worker.is_closing() { + return; + } + let global = worker.r().global(); let error = RootedValue::new(global.r().get_cx(), UndefinedValue()); let errorevent = ErrorEvent::new(global.r(), atom!("error"), @@ -147,6 +171,19 @@ impl WorkerMethods for Worker { Ok(()) } + // https://html.spec.whatwg.org/multipage/#terminate-a-worker + fn Terminate(&self) { + // Step 1 + if self.closing.swap(true, Ordering::SeqCst) { + return; + } + + // Step 4 + if let Some(runtime) = *self.runtime.lock().unwrap() { + runtime.request_interrupt(); + } + } + // https://html.spec.whatwg.org/multipage/#handler-worker-onmessage event_handler!(message, GetOnmessage, SetOnmessage); @@ -221,3 +258,26 @@ impl Runnable for WorkerErrorHandler { Worker::handle_error_message(this.addr, this.msg, this.file_name, this.line_num, this.col_num); } } + +#[derive(Copy, Clone)] +pub struct SharedRt { + rt: *mut JSRuntime +} + +impl SharedRt { + pub fn new(rt: &Runtime) -> SharedRt { + SharedRt { + rt: rt.rt() + } + } + + #[allow(unsafe_code)] + pub fn request_interrupt(&self) { + unsafe { + JS_RequestInterruptCallback(self.rt); + } + } +} + +#[allow(unsafe_code)] +unsafe impl Send for SharedRt {} diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 51180fe10c2..a0a73c0d993 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -18,7 +18,7 @@ use dom::window::{base64_atob, base64_btoa}; use dom::workerlocation::WorkerLocation; use dom::workernavigator::WorkerNavigator; use ipc_channel::ipc::IpcSender; -use js::jsapi::{HandleValue, JSAutoRequest, JSContext}; +use js::jsapi::{HandleValue, JSAutoRequest, JSContext, JSRuntime}; use js::rust::Runtime; use msg::constellation_msg::{ConstellationChan, PipelineId}; use net_traits::{LoadContext, ResourceThread, load_whole_resource}; @@ -29,6 +29,8 @@ use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, Tim use std::cell::Cell; use std::default::Default; use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::Receiver; use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback}; use url::Url; @@ -47,6 +49,7 @@ pub struct WorkerGlobalScopeInit { pub constellation_chan: ConstellationChan<ConstellationMsg>, pub scheduler_chan: IpcSender<TimerEventRequest>, pub worker_id: WorkerId, + pub closing: Arc<AtomicBool>, } // https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface @@ -55,6 +58,7 @@ pub struct WorkerGlobalScope { eventtarget: EventTarget, worker_id: WorkerId, worker_url: Url, + closing: Arc<AtomicBool>, #[ignore_heap_size_of = "Defined in js"] runtime: Runtime, next_worker_id: Cell<WorkerId>, @@ -104,6 +108,7 @@ impl WorkerGlobalScope { next_worker_id: Cell::new(WorkerId(0)), worker_id: init.worker_id, worker_url: worker_url, + closing: init.closing, runtime: runtime, resource_thread: init.resource_thread, location: Default::default(), @@ -155,10 +160,18 @@ impl WorkerGlobalScope { self.timers.unschedule_callback(handle); } + pub fn runtime(&self) -> *mut JSRuntime { + self.runtime.rt() + } + pub fn get_cx(&self) -> *mut JSContext { self.runtime.cx() } + pub fn is_closing(&self) -> bool { + self.closing.load(Ordering::SeqCst) + } + pub fn resource_thread(&self) -> &ResourceThread { &self.resource_thread } @@ -307,11 +320,15 @@ impl WorkerGlobalScope { self.reflector().get_jsobject(), String::from(source), self.worker_url.serialize(), 1) { Ok(_) => (), Err(_) => { - // TODO: An error needs to be dispatched to the parent. - // https://github.com/servo/servo/issues/6422 - println!("evaluate_script failed"); - let _ar = JSAutoRequest::new(self.runtime.cx()); - report_pending_exception(self.runtime.cx(), self.reflector().get_jsobject().get()); + if self.is_closing() { + println!("evaluate_script failed (terminated)"); + } else { + // TODO: An error needs to be dispatched to the parent. + // https://github.com/servo/servo/issues/6422 + println!("evaluate_script failed"); + let _ar = JSAutoRequest::new(self.runtime.cx()); + report_pending_exception(self.runtime.cx(), self.reflector().get_jsobject().get()); + } } } } diff --git a/tests/html/worker/worker_post_block.js b/tests/html/worker/worker_post_block.js new file mode 100644 index 00000000000..a9837b71afd --- /dev/null +++ b/tests/html/worker/worker_post_block.js @@ -0,0 +1,9 @@ +var prev = Date.now() +for (var i=0; true; i++) { + + if (i % 100000000 == 0) { + var now = Date.now(); + postMessage(now - prev); + prev = now; + } +} diff --git a/tests/html/worker/worker_post_interval.js b/tests/html/worker/worker_post_interval.js new file mode 100644 index 00000000000..ce1126136fa --- /dev/null +++ b/tests/html/worker/worker_post_interval.js @@ -0,0 +1,6 @@ +var prev = Date.now() +setInterval(function () { + var now = Date.now(); + postMessage(now - prev); + prev = now; +}, 500); diff --git a/tests/html/worker/worker_terminate.html b/tests/html/worker/worker_terminate.html new file mode 100644 index 00000000000..d6c40a5c69d --- /dev/null +++ b/tests/html/worker/worker_terminate.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Worker Test</title> +</head> +<body> +<script> +var workerPath = (function () { + var workerType; + switch (window.location.search.match(/worker=(\w+)/)[1]) { + case 'block': + workerType = 'block'; + break; + default: + workerType = 'interval'; + break; + } + + return './worker_post_' + workerType + '.js'; +})(); + +function startWorker() { + window.w = new Worker(workerPath); + w.onmessage = function(m) { + var p = document.createElement('p'); + p.innerHTML = JSON.stringify(m.data); + document.body.appendChild(p); + }; + + var ps = document.getElementsByTagName('p'); + while (ps.length) { + document.body.removeChild(ps[0]); + } +} + +function stopWorker() { + if (w) { + w.terminate(); + } + + window.w = null; +} +</script> + <button onclick="startWorker()">Start Worker</button> + <button onclick="stopWorker()">Stop Worker</button> +</body> +</html> diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini index 5df3f0cc1ac..b28e62669b5 100644 --- a/tests/wpt/metadata/html/dom/interfaces.html.ini +++ b/tests/wpt/metadata/html/dom/interfaces.html.ini @@ -7458,9 +7458,6 @@ [SharedWorkerGlobalScope interface: attribute onconnect] expected: FAIL - [Worker interface: operation terminate()] - expected: FAIL - [SharedWorker interface: existence and properties of interface object] expected: FAIL diff --git a/tests/wpt/metadata/workers/Worker_terminate_event_queue.htm.ini b/tests/wpt/metadata/workers/Worker_terminate_event_queue.htm.ini deleted file mode 100644 index bef6cb90a7b..00000000000 --- a/tests/wpt/metadata/workers/Worker_terminate_event_queue.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[Worker_terminate_event_queue.htm] - type: testharness - disabled: too much output diff --git a/tests/wpt/metadata/workers/constructors/Worker/terminate.html.ini b/tests/wpt/metadata/workers/constructors/Worker/terminate.html.ini deleted file mode 100644 index 0f57531e41e..00000000000 --- a/tests/wpt/metadata/workers/constructors/Worker/terminate.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[terminate.html] - type: testharness - [terminate()] - expected: FAIL - diff --git a/tests/wpt/metadata/workers/nested_worker.worker.js.ini b/tests/wpt/metadata/workers/nested_worker.worker.js.ini deleted file mode 100644 index 4a839a55bca..00000000000 --- a/tests/wpt/metadata/workers/nested_worker.worker.js.ini +++ /dev/null @@ -1,9 +0,0 @@ -[nested_worker.worker] - type: testharness - expected: TIMEOUT - [Nested worker] - expected: FAIL - - [Checking contents for text file] - expected: FAIL - |