diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/trace.rs | 1 | ||||
-rw-r--r-- | components/script/dom/client.rs | 26 | ||||
-rw-r--r-- | components/script/dom/promise.rs | 13 | ||||
-rw-r--r-- | components/script/dom/serviceworker.rs | 6 | ||||
-rw-r--r-- | components/script/dom/serviceworkercontainer.rs | 55 | ||||
-rw-r--r-- | components/script/dom/serviceworkerregistration.rs | 38 | ||||
-rw-r--r-- | components/script/dom/urlhelper.rs | 14 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/script_thread.rs | 72 | ||||
-rw-r--r-- | components/script/serviceworkerjob.rs | 292 |
10 files changed, 456 insertions, 62 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 8ecb06fdb1e..8323c6996ae 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -195,7 +195,6 @@ impl JSTraceable for Heap<*mut JSObject> { } } - impl JSTraceable for Heap<JSVal> { fn trace(&self, trc: *mut JSTracer) { trace_jsval(trc, "heap value", self); diff --git a/components/script/dom/client.rs b/components/script/dom/client.rs index bc763d41ea9..496a13d32dc 100644 --- a/components/script/dom/client.rs +++ b/components/script/dom/client.rs @@ -4,20 +4,20 @@ use dom::bindings::codegen::Bindings::ClientBinding::{ClientMethods, Wrap}; use dom::bindings::codegen::Bindings::ClientBinding::FrameType; -use dom::bindings::js::JS; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, Root, MutNullableHeap}; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::{DOMString, USVString}; use dom::serviceworker::ServiceWorker; use dom::window::Window; use servo_url::ServoUrl; +use std::default::Default; use uuid::Uuid; #[dom_struct] pub struct Client { reflector_: Reflector, - active_worker: Option<JS<ServiceWorker>>, - url: USVString, + active_worker: MutNullableHeap<JS<ServiceWorker>>, + url: ServoUrl, frame_type: FrameType, #[ignore_heap_size_of = "Defined in uuid"] id: Uuid @@ -27,8 +27,8 @@ impl Client { fn new_inherited(url: ServoUrl) -> Client { Client { reflector_: Reflector::new(), - active_worker: None, - url: USVString(url.as_str().to_owned()), + active_worker: Default::default(), + url: url, frame_type: FrameType::None, id: Uuid::new_v4() } @@ -39,12 +39,24 @@ impl Client { window, Wrap) } + + pub fn creation_url(&self) -> ServoUrl { + self.url.clone() + } + + pub fn get_controller(&self) -> Option<Root<ServiceWorker>> { + self.active_worker.get() + } + + pub fn set_controller(&self, worker: &ServiceWorker) { + self.active_worker.set(Some(worker)); + } } impl ClientMethods for Client { // https://w3c.github.io/ServiceWorker/#client-url-attribute fn Url(&self) -> USVString { - self.url.clone() + USVString(self.url.as_str().to_owned()) } // https://w3c.github.io/ServiceWorker/#client-frametype diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 81149f8e193..0afcfcba266 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -23,8 +23,8 @@ use js::conversions::ToJSValConvertible; use js::jsapi::{CallOriginalPromiseResolve, CallOriginalPromiseReject, CallOriginalPromiseThen}; use js::jsapi::{JSAutoCompartment, CallArgs, JS_GetFunctionObject, JS_NewFunction}; use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, GetFunctionNativeReserved}; -use js::jsapi::{JS_ClearPendingException, JSObject, AddRawValueRoot, RemoveRawValueRoot}; -use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise}; +use js::jsapi::{JS_ClearPendingException, JSObject, AddRawValueRoot, RemoveRawValueRoot, PromiseState}; +use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise, GetPromiseState}; use js::jsapi::{SetFunctionNativeReserved, NewFunctionWithReserved, AddPromiseReactions}; use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value}; use std::ptr; @@ -200,6 +200,15 @@ impl Promise { } #[allow(unsafe_code)] + pub fn is_settled(&self) -> bool { + let state = unsafe { GetPromiseState(self.promise_obj()) }; + match state { + PromiseState::Rejected | PromiseState::Fulfilled => true, + _ => false + } + } + + #[allow(unsafe_code)] fn promise_obj(&self) -> HandleObject { let obj = self.reflector().get_jsobject(); unsafe { diff --git a/components/script/dom/serviceworker.rs b/components/script/dom/serviceworker.rs index d61f1120906..63971d7e42e 100644 --- a/components/script/dom/serviceworker.rs +++ b/components/script/dom/serviceworker.rs @@ -46,9 +46,9 @@ impl ServiceWorker { } pub fn install_serviceworker(global: &GlobalScope, - script_url: ServoUrl, - scope_url: ServoUrl, - skip_waiting: bool) -> Root<ServiceWorker> { + script_url: ServoUrl, + scope_url: ServoUrl, + skip_waiting: bool) -> Root<ServiceWorker> { reflect_dom_object(box ServiceWorker::new_inherited(script_url.as_str(), skip_waiting, scope_url), global, Wrap) diff --git a/components/script/dom/serviceworkercontainer.rs b/components/script/dom/serviceworkercontainer.rs index 136ba01fcee..9d8d73b1c43 100644 --- a/components/script/dom/serviceworkercontainer.rs +++ b/components/script/dom/serviceworkercontainer.rs @@ -5,16 +5,16 @@ use dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{ServiceWorkerContainerMethods, Wrap}; use dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions; use dom::bindings::error::Error; -use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::bindings::reflector::{Reflectable, reflect_dom_object}; use dom::bindings::str::USVString; +use dom::client::Client; use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; use dom::promise::Promise; use dom::serviceworker::ServiceWorker; -use dom::serviceworkerregistration::ServiceWorkerRegistration; use script_thread::ScriptThread; +use serviceworkerjob::{Job, JobType}; use std::ascii::AsciiExt; use std::default::Default; use std::rc::Rc; @@ -23,48 +23,44 @@ use std::rc::Rc; pub struct ServiceWorkerContainer { eventtarget: EventTarget, controller: MutNullableHeap<JS<ServiceWorker>>, + client: JS<Client> } impl ServiceWorkerContainer { - fn new_inherited() -> ServiceWorkerContainer { + fn new_inherited(client: &Client) -> ServiceWorkerContainer { ServiceWorkerContainer { eventtarget: EventTarget::new_inherited(), controller: Default::default(), + client: JS::from_ref(client), } } + #[allow(unrooted_must_root)] pub fn new(global: &GlobalScope) -> Root<ServiceWorkerContainer> { - reflect_dom_object(box ServiceWorkerContainer::new_inherited(), global, Wrap) - } -} - -pub trait Controllable { - fn set_controller(&self, active_worker: &ServiceWorker); -} - -impl Controllable for ServiceWorkerContainer { - fn set_controller(&self, active_worker: &ServiceWorker) { - self.controller.set(Some(active_worker)); - self.upcast::<EventTarget>().fire_event(atom!("controllerchange")); + let client = Client::new(&global.as_window()); + let container = ServiceWorkerContainer::new_inherited(&*client); + reflect_dom_object(box container, global, Wrap) } } impl ServiceWorkerContainerMethods for ServiceWorkerContainer { // https://w3c.github.io/ServiceWorker/#service-worker-container-controller-attribute fn GetController(&self) -> Option<Root<ServiceWorker>> { - return self.controller.get() + self.client.get_controller() } #[allow(unrooted_must_root)] - // https://w3c.github.io/ServiceWorker/#service-worker-container-register-method + // https://w3c.github.io/ServiceWorker/#service-worker-container-register-method and - A + // https://w3c.github.io/ServiceWorker/#start-register-algorithm - B fn Register(&self, script_url: USVString, options: &RegistrationOptions) -> Rc<Promise> { + // A: Step 1 let promise = Promise::new(&*self.global()); - let ctx = self.global().get_cx(); + let ctx = (&*self.global()).get_cx(); let USVString(ref script_url) = script_url; let api_base_url = self.global().api_base_url(); - // Step 3-4 + // A: Step 3-5 let script_url = match api_base_url.join(script_url) { Ok(url) => url, Err(_) => { @@ -72,7 +68,7 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { return promise; } }; - // Step 5 + // B: Step 2 match script_url.scheme() { "https" | "http" => {}, _ => { @@ -80,13 +76,13 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { return promise; } } - // Step 6 + // B: Step 3 if script_url.path().to_ascii_lowercase().contains("%2f") || script_url.path().to_ascii_lowercase().contains("%5c") { promise.reject_error(ctx, Error::Type("Script URL contains forbidden characters".to_owned())); return promise; } - // Step 8-9 + // B: Step 4-5 let scope = match options.scope { Some(ref scope) => { let &USVString(ref inner_scope) = scope; @@ -100,7 +96,7 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { }, None => script_url.join("./").unwrap() }; - // Step 11 + // B: Step 6 match scope.scheme() { "https" | "http" => {}, _ => { @@ -108,21 +104,16 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { return promise; } } - // Step 12 + // B: Step 7 if scope.path().to_ascii_lowercase().contains("%2f") || scope.path().to_ascii_lowercase().contains("%5c") { promise.reject_error(ctx, Error::Type("Scope URL contains forbidden characters".to_owned())); return promise; } - let global = self.global(); - let worker_registration = ServiceWorkerRegistration::new(&global, - script_url, - scope.clone(), - self); - ScriptThread::set_registration(scope, &*worker_registration, self.global().pipeline_id()); - - promise.resolve_native(ctx, &*worker_registration); + // B: Step 8 + let job = Job::create_job(JobType::Register, scope, script_url, promise.clone(), &*self.client); + ScriptThread::schedule_job(job, &*self.global()); promise } } diff --git a/components/script/dom/serviceworkerregistration.rs b/components/script/dom/serviceworkerregistration.rs index 58ca0933d69..869c8855676 100644 --- a/components/script/dom/serviceworkerregistration.rs +++ b/components/script/dom/serviceworkerregistration.rs @@ -10,10 +10,11 @@ use dom::bindings::str::USVString; use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; use dom::serviceworker::ServiceWorker; -use dom::serviceworkercontainer::Controllable; use dom::workerglobalscope::prepare_workerscope_init; use script_traits::{WorkerScriptLoadOrigin, ScopeThings}; use servo_url::ServoUrl; +use std::cell::Cell; + #[dom_struct] pub struct ServiceWorkerRegistration { @@ -21,7 +22,8 @@ pub struct ServiceWorkerRegistration { active: Option<JS<ServiceWorker>>, installing: Option<JS<ServiceWorker>>, waiting: Option<JS<ServiceWorker>>, - scope: String + scope: ServoUrl, + uninstalling: Cell<bool> } impl ServiceWorkerRegistration { @@ -31,17 +33,16 @@ impl ServiceWorkerRegistration { active: Some(JS::from_ref(active_sw)), installing: None, waiting: None, - scope: scope.as_str().to_owned(), + scope: scope, + uninstalling: Cell::new(false) } } #[allow(unrooted_must_root)] pub fn new(global: &GlobalScope, - script_url: ServoUrl, - scope: ServoUrl, - container: &Controllable) -> Root<ServiceWorkerRegistration> { + script_url: &ServoUrl, + scope: ServoUrl) -> Root<ServiceWorkerRegistration> { let active_worker = ServiceWorker::install_serviceworker(global, script_url.clone(), scope.clone(), true); active_worker.set_transition_state(ServiceWorkerState::Installed); - container.set_controller(&*active_worker.clone()); reflect_dom_object(box ServiceWorkerRegistration::new_inherited(&*active_worker, scope), global, Wrap) } @@ -49,6 +50,14 @@ impl ServiceWorkerRegistration { self.active.as_ref().unwrap() } + pub fn get_uninstalling(&self) -> bool { + self.uninstalling.get() + } + + pub fn set_uninstalling(&self, flag: bool) { + self.uninstalling.set(flag) + } + pub fn create_scope_things(global: &GlobalScope, script_url: ServoUrl) -> ScopeThings { let worker_load_origin = WorkerScriptLoadOrigin { referrer_url: None, @@ -58,7 +67,7 @@ impl ServiceWorkerRegistration { let worker_id = global.get_next_worker_id(); let devtools_chan = global.devtools_chan().cloned(); - let init = prepare_workerscope_init(global, None); + let init = prepare_workerscope_init(&global, None); ScopeThings { script_url: script_url, init: init, @@ -67,6 +76,17 @@ impl ServiceWorkerRegistration { worker_id: worker_id } } + + // https://w3c.github.io/ServiceWorker/#get-newest-worker-algorithm + pub fn get_newest_worker(&self) -> Option<Root<ServiceWorker>> { + if self.installing.as_ref().is_some() { + self.installing.as_ref().map(|sw| Root::from_ref(&**sw)) + } else if self.waiting.as_ref().is_some() { + self.waiting.as_ref().map(|sw| Root::from_ref(&**sw)) + } else { + self.active.as_ref().map(|sw| Root::from_ref(&**sw)) + } + } } pub fn longest_prefix_match(stored_scope: &ServoUrl, potential_match: &ServoUrl) -> bool { @@ -100,6 +120,6 @@ impl ServiceWorkerRegistrationMethods for ServiceWorkerRegistration { // https://w3c.github.io/ServiceWorker/#service-worker-registration-scope-attribute fn Scope(&self) -> USVString { - USVString(self.scope.clone()) + USVString(self.scope.as_str().to_owned()) } } diff --git a/components/script/dom/urlhelper.rs b/components/script/dom/urlhelper.rs index dd22e16f551..ec50b4edb71 100644 --- a/components/script/dom/urlhelper.rs +++ b/components/script/dom/urlhelper.rs @@ -92,4 +92,18 @@ impl UrlHelper { let _ = quirks::set_username(url, &value.0); } } + // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy + pub fn is_origin_trustworthy(url: &ServoUrl) -> bool { + // Step 3 + if url.scheme() == "http" || url.scheme() == "wss" { + true + // Step 4 + } else if url.host().is_some() { + let host = url.host_str().unwrap(); + host == "127.0.0.0/8" || host == "::1/128" + // Step 5 + } else { + url.scheme() == "file" + } + } } diff --git a/components/script/lib.rs b/components/script/lib.rs index 90b320bd25f..0d726a723af 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -113,6 +113,7 @@ pub mod script_runtime; #[allow(unsafe_code)] pub mod script_thread; mod serviceworker_manager; +mod serviceworkerjob; mod task_source; pub mod textinput; mod timers; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 9b00846dbb3..e5eaf30a5d8 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -89,6 +89,7 @@ use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeDat use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent}; use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent}; use script_traits::webdriver_msg::WebDriverScriptCommand; +use serviceworkerjob::{Job, JobQueue, AsyncJobHandler, FinishJobHandler, InvokeType, SettleType}; use servo_url::ServoUrl; use std::cell::Cell; use std::collections::{hash_map, HashMap, HashSet}; @@ -397,6 +398,8 @@ pub struct ScriptThread { incomplete_loads: DOMRefCell<Vec<InProgressLoad>>, /// A map to store service worker registrations for a given origin registration_map: DOMRefCell<HashMap<ServoUrl, JS<ServiceWorkerRegistration>>>, + /// A job queue for Service Workers keyed by their scope url + job_queue_map: Rc<JobQueue>, /// A handle to the image cache thread. image_cache_thread: ImageCacheThread, /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if @@ -561,11 +564,12 @@ impl ScriptThread { }) } - // stores a service worker registration - pub fn set_registration(scope_url: ServoUrl, registration:&ServiceWorkerRegistration, pipeline_id: PipelineId) { + #[allow(unrooted_must_root)] + pub fn schedule_job(job: Job, global: &GlobalScope) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; - script_thread.handle_serviceworker_registration(scope_url, registration, pipeline_id); + let job_queue = &*script_thread.job_queue_map; + job_queue.schedule_job(job, global, &script_thread); }); } @@ -638,6 +642,7 @@ impl ScriptThread { documents: DOMRefCell::new(Documents::new()), incomplete_loads: DOMRefCell::new(vec!()), registration_map: DOMRefCell::new(HashMap::new()), + job_queue_map: Rc::new(JobQueue::new()), image_cache_thread: state.image_cache_thread, image_cache_channel: ImageCacheChan(ipc_image_cache_channel), @@ -1434,33 +1439,84 @@ impl ScriptThread { } } - fn handle_serviceworker_registration(&self, - scope: ServoUrl, + pub fn handle_get_registration(&self, scope_url: &ServoUrl) -> Option<Root<ServiceWorkerRegistration>> { + let maybe_registration_ref = self.registration_map.borrow(); + maybe_registration_ref.get(scope_url).map(|x| Root::from_ref(&**x)) + } + + pub fn handle_serviceworker_registration(&self, + scope: &ServoUrl, registration: &ServiceWorkerRegistration, pipeline_id: PipelineId) { { let ref mut reg_ref = *self.registration_map.borrow_mut(); // according to spec we should replace if an older registration exists for // same scope otherwise just insert the new one - let _ = reg_ref.remove(&scope); + let _ = reg_ref.remove(scope); reg_ref.insert(scope.clone(), JS::from_ref(registration)); } // send ScopeThings to sw-manager let ref maybe_registration_ref = *self.registration_map.borrow(); - let maybe_registration = match maybe_registration_ref.get(&scope) { + let maybe_registration = match maybe_registration_ref.get(scope) { Some(r) => r, None => return }; if let Some(window) = self.documents.borrow().find_window(pipeline_id) { let script_url = maybe_registration.get_installed().get_script_url(); let scope_things = ServiceWorkerRegistration::create_scope_things(window.upcast(), script_url); - let _ = self.constellation_chan.send(ConstellationMsg::RegisterServiceWorker(scope_things, scope)); + let _ = self.constellation_chan.send(ConstellationMsg::RegisterServiceWorker(scope_things, scope.clone())); } else { warn!("Registration failed for {}", scope); } } + pub fn dispatch_job_queue(&self, job_handler: Box<AsyncJobHandler>) { + let scope_url = job_handler.scope_url.clone(); + let queue_ref = self.job_queue_map.0.borrow(); + let front_job = { + let job_vec = queue_ref.get(&scope_url); + job_vec.unwrap().first().unwrap() + }; + match job_handler.invoke_type { + InvokeType::Run => (&*self.job_queue_map).run_job(job_handler, self), + InvokeType::Register => self.job_queue_map.run_register(front_job, job_handler, self), + InvokeType::Update => self.job_queue_map.update(front_job, &*front_job.client.global(), self), + InvokeType::Settle(settle_type) => { + let promise = &front_job.promise; + let global = &*front_job.client.global(); + let trusted_global = Trusted::new(global); + let _ac = JSAutoCompartment::new(global.get_cx(), promise.reflector().get_jsobject().get()); + match settle_type { + SettleType::Resolve(reg) => promise.resolve_native(global.get_cx(), &*reg.root()), + SettleType::Reject(err) => promise.reject_error(global.get_cx(), err) + } + let finish_job_handler = box FinishJobHandler::new(scope_url, trusted_global); + self.queue_finish_job(finish_job_handler, global); + } + } + } + + pub fn queue_serviceworker_job(&self, async_job_handler: Box<AsyncJobHandler>, global: &GlobalScope) { + let _ = self.dom_manipulation_task_source.queue(async_job_handler, &*global); + } + + pub fn queue_finish_job(&self, finish_job_handler: Box<FinishJobHandler>, global: &GlobalScope) { + let _ = self.dom_manipulation_task_source.queue(finish_job_handler, global); + } + + pub fn invoke_finish_job(&self, finish_job_handler: Box<FinishJobHandler>) { + let job_queue = &*self.job_queue_map; + let global = &*finish_job_handler.global.root(); + let scope_url = (*finish_job_handler).scope_url; + job_queue.finish_job(scope_url, global, self); + } + + pub fn invoke_job_update(&self, job: &Job, global: &GlobalScope) { + let job_queue = &*self.job_queue_map; + job_queue.update(job, global, self); + } + /// Handles a request for the window title. fn handle_get_title_msg(&self, pipeline_id: PipelineId) { let document = match self.documents.borrow().find_document(pipeline_id) { diff --git a/components/script/serviceworkerjob.rs b/components/script/serviceworkerjob.rs new file mode 100644 index 00000000000..e82d40b6bef --- /dev/null +++ b/components/script/serviceworkerjob.rs @@ -0,0 +1,292 @@ +/* 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/. */ + +//! A Job is an abstraction of async operation in service worker lifecycle propagation. +//! Each Job is uniquely identified by its scope_url, and is keyed accordingly under +//! the script thread. The script thread contains a JobQueue, which stores all scheduled Jobs +//! by multiple service worker clients in a Vec. + +use dom::bindings::cell::DOMRefCell; +use dom::bindings::error::Error; +use dom::bindings::js::JS; +use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::Reflectable; +use dom::client::Client; +use dom::globalscope::GlobalScope; +use dom::promise::Promise; +use dom::serviceworkerregistration::ServiceWorkerRegistration; +use dom::urlhelper::UrlHelper; +use script_thread::{ScriptThread, Runnable}; +use servo_url::ServoUrl; +use std::cmp::PartialEq; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(PartialEq, Clone, Debug, JSTraceable)] +pub enum JobType { + Register, + Unregister, + Update +} + +#[derive(Clone)] +pub enum SettleType { + Resolve(Trusted<ServiceWorkerRegistration>), + Reject(Error) +} + +// This encapsulates what operation to invoke of JobQueue from script thread +pub enum InvokeType { + Settle(SettleType), + Run, + Register, + Update +} + +#[must_root] +#[derive(JSTraceable)] +pub struct Job { + pub job_type: JobType, + pub scope_url: ServoUrl, + pub script_url: ServoUrl, + pub promise: Rc<Promise>, + pub equivalent_jobs: Vec<Job>, + // client can be a window client, worker client so `Client` will be an enum in future + pub client: JS<Client>, + pub referrer: ServoUrl +} + +impl Job { + #[allow(unrooted_must_root)] + // https://w3c.github.io/ServiceWorker/#create-job-algorithm + pub fn create_job(job_type: JobType, + scope_url: ServoUrl, + script_url: ServoUrl, + promise: Rc<Promise>, + client: &Client) -> Job { + Job { + job_type: job_type, + scope_url: scope_url, + script_url: script_url, + promise: promise, + equivalent_jobs: vec![], + client: JS::from_ref(client), + referrer: client.creation_url() + } + } + #[allow(unrooted_must_root)] + pub fn append_equivalent_job(&mut self, job: Job) { + self.equivalent_jobs.push(job); + } +} + +impl PartialEq for Job { + // Equality criteria as described in https://w3c.github.io/ServiceWorker/#dfn-job-equivalent + fn eq(&self, other: &Self) -> bool { + let same_job = self.job_type == other.job_type; + if same_job { + match self.job_type { + JobType::Register | JobType::Update => { + self.scope_url == other.scope_url && self.script_url == other.script_url + }, + JobType::Unregister => self.scope_url == other.scope_url + } + } else { + false + } + } +} + +pub struct FinishJobHandler { + pub scope_url: ServoUrl, + pub global: Trusted<GlobalScope>, +} + +impl FinishJobHandler { + pub fn new(scope_url: ServoUrl, global: Trusted<GlobalScope>) -> FinishJobHandler { + FinishJobHandler { + scope_url: scope_url, + global: global + } + } +} + +impl Runnable for FinishJobHandler { + fn main_thread_handler(self: Box<FinishJobHandler>, script_thread: &ScriptThread) { + script_thread.invoke_finish_job(self); + } +} + +pub struct AsyncJobHandler { + pub scope_url: ServoUrl, + pub invoke_type: InvokeType +} + +impl AsyncJobHandler { + fn new(scope_url: ServoUrl, invoke_type: InvokeType) -> AsyncJobHandler { + AsyncJobHandler { + scope_url: scope_url, + invoke_type: invoke_type + } + } +} + +impl Runnable for AsyncJobHandler { + #[allow(unrooted_must_root)] + fn main_thread_handler(self: Box<AsyncJobHandler>, script_thread: &ScriptThread) { + script_thread.dispatch_job_queue(self); + } +} + +#[must_root] +#[derive(JSTraceable)] +pub struct JobQueue(pub DOMRefCell<HashMap<ServoUrl, Vec<Job>>>); + +impl JobQueue { + pub fn new() -> JobQueue { + JobQueue(DOMRefCell::new(HashMap::new())) + } + #[allow(unrooted_must_root)] + // https://w3c.github.io/ServiceWorker/#schedule-job-algorithm + pub fn schedule_job(&self, + job: Job, + global: &GlobalScope, + script_thread: &ScriptThread) { + let mut queue_ref = self.0.borrow_mut(); + let job_queue = queue_ref.entry(job.scope_url.clone()).or_insert(vec![]); + // Step 1 + if job_queue.is_empty() { + let scope_url = job.scope_url.clone(); + job_queue.push(job); + let run_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Run); + script_thread.queue_serviceworker_job(box run_job_handler, global); + } else { + // Step 2 + let mut last_job = job_queue.pop().unwrap(); + if job == last_job && !last_job.promise.is_settled() { + last_job.append_equivalent_job(job); + job_queue.push(last_job); + } else { + // restore the popped last_job + job_queue.push(last_job); + // and push this new job to job queue + job_queue.push(job); + } + } + } + + #[allow(unrooted_must_root)] + // https://w3c.github.io/ServiceWorker/#run-job-algorithm + pub fn run_job(&self, run_job_handler: Box<AsyncJobHandler>, script_thread: &ScriptThread) { + let queue_ref = &*self.0.borrow(); + let front_job = { + let job_vec = queue_ref.get(&run_job_handler.scope_url); + job_vec.unwrap().first().unwrap() + }; + let global = &*front_job.client.global(); + let handler = *run_job_handler; + match front_job.job_type { + JobType::Register => { + let register_job_handler = AsyncJobHandler::new(handler.scope_url, InvokeType::Register); + script_thread.queue_serviceworker_job(box register_job_handler, global); + }, + JobType::Update => { + let update_job_handler = AsyncJobHandler::new(handler.scope_url, InvokeType::Update); + script_thread.queue_serviceworker_job(box update_job_handler, global); + } + _ => { /* TODO implement Unregister */ } + } + } + + #[allow(unrooted_must_root)] + // https://w3c.github.io/ServiceWorker/#register-algorithm + pub fn run_register(&self, job: &Job, register_job_handler: Box<AsyncJobHandler>, script_thread: &ScriptThread) { + let global = &*job.client.global(); + let AsyncJobHandler { scope_url, .. } = *register_job_handler; + // Step 1-3 + if !UrlHelper::is_origin_trustworthy(&job.script_url) { + let settle_type = SettleType::Reject(Error::Type("Invalid script ServoURL".to_owned())); + let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); + return script_thread.queue_serviceworker_job(box async_job_handler, global); + } else if job.script_url.origin() != job.referrer.origin() || job.scope_url.origin() != job.referrer.origin() { + let settle_type = SettleType::Reject(Error::Security); + let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); + return script_thread.queue_serviceworker_job(box async_job_handler, global); + } + // Step 4-5 + if let Some(reg) = script_thread.handle_get_registration(&job.scope_url) { + // Step 5.1 + if reg.get_uninstalling() { + reg.set_uninstalling(false); + } + // Step 5.3 + if let Some(ref newest_worker) = reg.get_newest_worker() { + if (&*newest_worker).get_script_url() == job.script_url { + let settle_type = SettleType::Resolve(Trusted::new(&*reg)); + let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); + script_thread.queue_serviceworker_job(box async_job_handler, global); + let finish_job_handler = box FinishJobHandler::new(job.scope_url.clone(), Trusted::new(&*global)); + script_thread.queue_finish_job(finish_job_handler, &*global); + } + } + } else { + // Step 6.1 + let pipeline = global.pipeline_id(); + let new_reg = ServiceWorkerRegistration::new(&*global, &job.script_url, scope_url); + script_thread.handle_serviceworker_registration(&job.scope_url, &*new_reg, pipeline); + } + // Step 7 + script_thread.invoke_job_update(job, &*global); + } + + #[allow(unrooted_must_root)] + // https://w3c.github.io/ServiceWorker/#finish-job-algorithm + pub fn finish_job(&self, scope_url: ServoUrl, global: &GlobalScope, script_thread: &ScriptThread) { + if let Some(job_vec) = (*self.0.borrow_mut()).get_mut(&scope_url) { + if job_vec.first().map_or(false, |job| job.scope_url == scope_url) { + let _ = job_vec.remove(0); + } + if !job_vec.is_empty() { + let run_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Run); + script_thread.queue_serviceworker_job(box run_job_handler, global); + } + } else { + warn!("non-existent job vector for Servourl: {:?}", scope_url); + } + } + + // https://w3c.github.io/ServiceWorker/#update-algorithm + pub fn update(&self, job: &Job, global: &GlobalScope, script_thread: &ScriptThread) { + let reg = match script_thread.handle_get_registration(&job.scope_url) { + Some(reg) => reg, + None => return + }; + // Step 1 + if reg.get_uninstalling() { + let err_type = Error::Type("Update called on an uninstalling registration".to_owned()); + let settle_type = SettleType::Reject(err_type); + let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); + return script_thread.queue_serviceworker_job(box async_job_handler, global); + } + let newest_worker = match reg.get_newest_worker() { + Some(worker) => worker, + None => return + }; + // Step 2 + if (&*newest_worker).get_script_url() == job.script_url && job.job_type == JobType::Update { + // Step 4 + let err_type = Error::Type("Invalid script ServoURL".to_owned()); + let settle_type = SettleType::Reject(err_type); + let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); + script_thread.queue_serviceworker_job(box async_job_handler, global); + } else { + job.client.set_controller(&*newest_worker); + let settle_type = SettleType::Resolve(Trusted::new(&*reg)); + let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); + script_thread.queue_serviceworker_job(box async_job_handler, global); + } + let finish_job_handler = box FinishJobHandler::new(job.scope_url.clone(), Trusted::new(global)); + script_thread.queue_finish_job(finish_job_handler, global); + } +} |