diff options
21 files changed, 882 insertions, 632 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index e6f8d990ceb..528d3d37fd0 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -153,14 +153,12 @@ use script_traits::{ IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg, }; use script_traits::{ - LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory, + Job, LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory, ServiceWorkerManagerFactory, }; use script_traits::{MediaSessionActionType, MouseEventType}; use script_traits::{MessagePortMsg, PortMessageTask, StructuredSerializedData}; -use script_traits::{ - SWManagerMsg, SWManagerSenders, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg, -}; +use script_traits::{SWManagerMsg, SWManagerSenders, UpdatePipelineIdReason, WebDriverCommandMsg}; use serde::{Deserialize, Serialize}; use servo_config::{opts, pref}; use servo_rand::{random, Rng, ServoRng, SliceRandom}; @@ -1983,8 +1981,8 @@ where ); } }, - FromScriptMsg::RegisterServiceWorker(scope_things, scope) => { - self.handle_register_serviceworker(scope_things, scope); + FromScriptMsg::ScheduleJob(job) => { + self.handle_schedule_serviceworker_job(source_pipeline_id, job); }, FromScriptMsg::ForwardDOMMessage(msg_vec, scope_url) => { if let Some(mgr) = self.sw_managers.get(&scope_url.origin()) { @@ -2640,9 +2638,26 @@ where } } - fn handle_register_serviceworker(&mut self, scope_things: ScopeThings, scope: ServoUrl) { + /// <https://w3c.github.io/ServiceWorker/#schedule-job-algorithm> + /// and + /// <https://w3c.github.io/ServiceWorker/#dfn-job-queue> + /// + /// The Job Queue is essentially the channel to a SW manager, + /// which are scoped per origin. + fn handle_schedule_serviceworker_job(&mut self, pipeline_id: PipelineId, job: Job) { + let origin = job.scope_url.origin(); + + if self + .check_origin_against_pipeline(&pipeline_id, &origin) + .is_err() + { + return warn!( + "Attempt to schedule a serviceworker job from an origin not matching the origin of the job." + ); + } + // This match is equivalent to Entry.or_insert_with but allows for early return. - let sw_manager = match self.sw_managers.entry(scope.origin()) { + let sw_manager = match self.sw_managers.entry(origin.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { let (own_sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); @@ -2653,7 +2668,7 @@ where own_sender: own_sender.clone(), receiver, }; - let content = ServiceWorkerUnprivilegedContent::new(sw_senders, scope.origin()); + let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin); if opts::multiprocess() { if content.spawn_multiprocess().is_err() { @@ -2665,7 +2680,7 @@ where entry.insert(own_sender) }, }; - let _ = sw_manager.send(ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope)); + let _ = sw_manager.send(ServiceWorkerMsg::ScheduleJob(job)); } fn handle_broadcast_storage_event( diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 8a599405df8..064595d6c5e 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -178,6 +178,20 @@ impl PipelineNamespace { } } + fn next_service_worker_id(&mut self) -> ServiceWorkerId { + ServiceWorkerId { + namespace_id: self.id, + index: ServiceWorkerIndex(self.next_index()), + } + } + + fn next_service_worker_registration_id(&mut self) -> ServiceWorkerRegistrationId { + ServiceWorkerRegistrationId { + namespace_id: self.id, + index: ServiceWorkerRegistrationIndex(self.next_index()), + } + } + fn next_blob_id(&mut self) -> BlobId { BlobId { namespace_id: self.id, @@ -424,6 +438,74 @@ impl fmt::Display for BroadcastChannelRouterId { } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct ServiceWorkerIndex(pub NonZeroU32); +malloc_size_of_is_0!(ServiceWorkerIndex); + +#[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct ServiceWorkerId { + pub namespace_id: PipelineNamespaceId, + pub index: ServiceWorkerIndex, +} + +impl ServiceWorkerId { + pub fn new() -> ServiceWorkerId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_service_worker_id = namespace.next_service_worker_id(); + tls.set(Some(namespace)); + next_service_worker_id + }) + } +} + +impl fmt::Display for ServiceWorkerId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let ServiceWorkerIndex(index) = self.index; + write!(fmt, "(ServiceWorkerId{},{})", namespace_id, index.get()) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct ServiceWorkerRegistrationIndex(pub NonZeroU32); +malloc_size_of_is_0!(ServiceWorkerRegistrationIndex); + +#[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct ServiceWorkerRegistrationId { + pub namespace_id: PipelineNamespaceId, + pub index: ServiceWorkerRegistrationIndex, +} + +impl ServiceWorkerRegistrationId { + pub fn new() -> ServiceWorkerRegistrationId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_service_worker_registration_id = + namespace.next_service_worker_registration_id(); + tls.set(Some(namespace)); + next_service_worker_registration_id + }) + } +} + +impl fmt::Display for ServiceWorkerRegistrationId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let ServiceWorkerRegistrationIndex(index) = self.index; + write!( + fmt, + "(ServiceWorkerRegistrationId{},{})", + namespace_id, + index.get() + ) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub struct BlobIndex(pub NonZeroU32); malloc_size_of_is_0!(BlobIndex); diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 7534541a06d..ba174193cdf 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -86,6 +86,7 @@ use msg::constellation_msg::{ BlobId, BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId, MessagePortRouterId, PipelineId, TopLevelBrowsingContextId, }; +use msg::constellation_msg::{ServiceWorkerId, ServiceWorkerRegistrationId}; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache::{ImageCache, PendingImageId}; @@ -179,6 +180,9 @@ unsafe_no_jsmanaged_fields!(MessagePortImpl); unsafe_no_jsmanaged_fields!(MessagePortId); unsafe_no_jsmanaged_fields!(MessagePortRouterId); +unsafe_no_jsmanaged_fields!(ServiceWorkerId); +unsafe_no_jsmanaged_fields!(ServiceWorkerRegistrationId); + unsafe_no_jsmanaged_fields!(BroadcastChannelRouterId); unsafe_no_jsmanaged_fields!(BlobId); diff --git a/components/script/dom/client.rs b/components/script/dom/client.rs index 53c692d5591..2768ee7c9ba 100644 --- a/components/script/dom/client.rs +++ b/components/script/dom/client.rs @@ -47,6 +47,7 @@ impl Client { self.active_worker.get() } + #[allow(dead_code)] pub fn set_controller(&self, worker: &ServiceWorker) { self.active_worker.set(Some(worker)); } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index e578f31c190..8e067e75de1 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -41,6 +41,8 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::performance::Performance; use crate::dom::performanceobserver::VALID_ENTRY_TYPES; use crate::dom::promise::Promise; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::window::Window; use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::dom::workletglobalscope::WorkletGlobalScope; @@ -81,6 +83,7 @@ use js::rust::{HandleValue, MutableHandleValue}; use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; use msg::constellation_msg::{ BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId, + ServiceWorkerId, ServiceWorkerRegistrationId, }; use net_traits::blob_url_store::{get_blob_origin, BlobBuf}; use net_traits::filemanager_thread::{ @@ -135,6 +138,13 @@ pub struct GlobalScope { /// The blobs managed by this global, if any. blob_state: DomRefCell<BlobState>, + /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map> + registration_map: + DomRefCell<HashMap<ServiceWorkerRegistrationId, Dom<ServiceWorkerRegistration>>>, + + /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map> + worker_map: DomRefCell<HashMap<ServiceWorkerId, Dom<ServiceWorker>>>, + /// Pipeline id associated with this global. pipeline_id: PipelineId, @@ -567,6 +577,8 @@ impl GlobalScope { blob_state: DomRefCell::new(BlobState::UnManaged), eventtarget: EventTarget::new_inherited(), crypto: Default::default(), + registration_map: DomRefCell::new(HashMap::new()), + worker_map: DomRefCell::new(HashMap::new()), pipeline_id, devtools_wants_updates: Default::default(), console_timers: DomRefCell::new(Default::default()), @@ -645,6 +657,72 @@ impl GlobalScope { ); } + /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object> + pub fn get_serviceworker_registration( + &self, + script_url: &ServoUrl, + scope: &ServoUrl, + registration_id: ServiceWorkerRegistrationId, + installing_worker: Option<ServiceWorkerId>, + _waiting_worker: Option<ServiceWorkerId>, + _active_worker: Option<ServiceWorkerId>, + ) -> DomRoot<ServiceWorkerRegistration> { + // Step 1 + let mut registrations = self.registration_map.borrow_mut(); + + if let Some(registration) = registrations.get(®istration_id) { + // Step 3 + return DomRoot::from_ref(&**registration); + } + + // Step 2.1 -> 2.5 + let new_registration = + ServiceWorkerRegistration::new(self, scope.clone(), registration_id.clone()); + + // Step 2.6 + if let Some(worker_id) = installing_worker { + let worker = self.get_serviceworker(script_url, scope, worker_id); + new_registration.set_installing(&*worker); + } + + // TODO: 2.7 (waiting worker) + + // TODO: 2.8 (active worker) + + // Step 2.9 + registrations.insert(registration_id, Dom::from_ref(&*new_registration)); + + // Step 3 + new_registration + } + + /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-object> + pub fn get_serviceworker( + &self, + script_url: &ServoUrl, + scope: &ServoUrl, + worker_id: ServiceWorkerId, + ) -> DomRoot<ServiceWorker> { + // Step 1 + let mut workers = self.worker_map.borrow_mut(); + + if let Some(worker) = workers.get(&worker_id) { + // Step 3 + DomRoot::from_ref(&**worker) + } else { + // Step 2.1 + // TODO: step 2.2, worker state. + let new_worker = + ServiceWorker::new(self, script_url.clone(), scope.clone(), worker_id.clone()); + + // Step 2.3 + workers.insert(worker_id, Dom::from_ref(&*new_worker)); + + // Step 3 + new_worker + } + } + /// Complete the transfer of a message-port. fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) { let should_start = if let MessagePortState::Managed(_id, message_ports) = diff --git a/components/script/dom/navigationpreloadmanager.rs b/components/script/dom/navigationpreloadmanager.rs index 7586f0a040f..d45fa9ac5f8 100644 --- a/components/script/dom/navigationpreloadmanager.rs +++ b/components/script/dom/navigationpreloadmanager.rs @@ -46,7 +46,7 @@ impl NavigationPreloadManagerMethods for NavigationPreloadManager { let promise = Promise::new_in_current_realm(&*self.global(), comp); // 2. - if self.serviceworker_registration.active().is_none() { + if self.serviceworker_registration.is_active() { promise.reject_native(&DOMException::new( &self.global(), DOMErrorName::InvalidStateError, @@ -68,7 +68,7 @@ impl NavigationPreloadManagerMethods for NavigationPreloadManager { let promise = Promise::new_in_current_realm(&*self.global(), comp); // 2. - if self.serviceworker_registration.active().is_none() { + if self.serviceworker_registration.is_active() { promise.reject_native(&DOMException::new( &self.global(), DOMErrorName::InvalidStateError, @@ -90,7 +90,7 @@ impl NavigationPreloadManagerMethods for NavigationPreloadManager { let promise = Promise::new_in_current_realm(&*self.global(), comp); // 2. - if self.serviceworker_registration.active().is_none() { + if self.serviceworker_registration.is_active() { promise.reject_native(&DOMException::new( &self.global(), DOMErrorName::InvalidStateError, @@ -114,7 +114,7 @@ impl NavigationPreloadManagerMethods for NavigationPreloadManager { let mut state = NavigationPreloadState::empty(); // 3. - if let Some(_) = self.serviceworker_registration.active() { + if self.serviceworker_registration.is_active() { if self .serviceworker_registration .get_navigation_preload_enabled() diff --git a/components/script/dom/serviceworker.rs b/components/script/dom/serviceworker.rs index fce07ab0809..bcbdd158a40 100644 --- a/components/script/dom/serviceworker.rs +++ b/components/script/dom/serviceworker.rs @@ -23,6 +23,7 @@ use crate::task::TaskOnce; use dom_struct::dom_struct; use js::jsapi::{Heap, JSObject}; use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::ServiceWorkerId; use script_traits::{DOMMessage, ScriptMsg}; use servo_url::ServoUrl; use std::cell::Cell; @@ -35,31 +36,35 @@ pub struct ServiceWorker { script_url: DomRefCell<String>, scope_url: ServoUrl, state: Cell<ServiceWorkerState>, - skip_waiting: Cell<bool>, + worker_id: ServiceWorkerId, } impl ServiceWorker { - fn new_inherited(script_url: &str, skip_waiting: bool, scope_url: ServoUrl) -> ServiceWorker { + fn new_inherited( + script_url: &str, + scope_url: ServoUrl, + worker_id: ServiceWorkerId, + ) -> ServiceWorker { ServiceWorker { eventtarget: EventTarget::new_inherited(), script_url: DomRefCell::new(String::from(script_url)), state: Cell::new(ServiceWorkerState::Installing), scope_url: scope_url, - skip_waiting: Cell::new(skip_waiting), + worker_id, } } - pub fn install_serviceworker( + pub fn new( global: &GlobalScope, script_url: ServoUrl, scope_url: ServoUrl, - skip_waiting: bool, + worker_id: ServiceWorkerId, ) -> DomRoot<ServiceWorker> { reflect_dom_object( Box::new(ServiceWorker::new_inherited( script_url.as_str(), - skip_waiting, scope_url, + worker_id, )), global, ) diff --git a/components/script/dom/serviceworkercontainer.rs b/components/script/dom/serviceworkercontainer.rs index 8145729d857..b65dcd59b7d 100644 --- a/components/script/dom/serviceworkercontainer.rs +++ b/components/script/dom/serviceworkercontainer.rs @@ -5,6 +5,7 @@ use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions; use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::ServiceWorkerContainerMethods; use crate::dom::bindings::error::Error; +use crate::dom::bindings::refcounted::TrustedPromise; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::USVString; @@ -13,10 +14,17 @@ use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::dom::serviceworker::ServiceWorker; +use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; +use crate::realms::enter_realm; use crate::realms::InRealm; -use crate::script_thread::ScriptThread; -use crate::serviceworkerjob::{Job, JobType}; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::TaskSource; +use crate::task_source::TaskSourceName; use dom_struct::dom_struct; +use ipc_channel::ipc; +use ipc_channel::router::ROUTER; +use script_traits::{Job, JobError, JobResult, JobResultValue, JobType, ScriptMsg}; use std::default::Default; use std::rc::Rc; @@ -50,28 +58,50 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { self.client.get_controller() } - #[allow(unrooted_must_root)] // Job is unrooted - /// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register and - A - /// https://w3c.github.io/ServiceWorker/#start-register-algorithm - B + /// https://w3c.github.io/ServiceWorker/#dom-serviceworkercontainer-register - A + /// and https://w3c.github.io/ServiceWorker/#start-register - B fn Register( &self, script_url: USVString, options: &RegistrationOptions, comp: InRealm, ) -> Rc<Promise> { + // A: Step 2. + let global = self.client.global(); + // A: Step 1 - let promise = Promise::new_in_current_realm(&*self.global(), comp); + let promise = Promise::new_in_current_realm(&*global, comp); let USVString(ref script_url) = script_url; - let api_base_url = self.global().api_base_url(); - // A: Step 3-5 + + // A: Step 3 + let api_base_url = global.api_base_url(); let script_url = match api_base_url.join(script_url) { Ok(url) => url, Err(_) => { + // B: Step 1 promise.reject_error(Error::Type("Invalid script URL".to_owned())); return promise; }, }; - // B: Step 2 + + // A: Step 4-5 + let scope = match options.scope { + Some(ref scope) => { + let &USVString(ref inner_scope) = scope; + match api_base_url.join(inner_scope) { + Ok(url) => url, + Err(_) => { + promise.reject_error(Error::Type("Invalid scope URL".to_owned())); + return promise; + }, + } + }, + None => script_url.join("./").unwrap(), + }; + + // A: Step 6 -> invoke B. + + // B: Step 3 match script_url.scheme() { "https" | "http" => {}, _ => { @@ -79,7 +109,7 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { return promise; }, } - // B: Step 3 + // B: Step 4 if script_url.path().to_ascii_lowercase().contains("%2f") || script_url.path().to_ascii_lowercase().contains("%5c") { @@ -88,20 +118,7 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { )); return promise; } - // B: Step 4-5 - let scope = match options.scope { - Some(ref scope) => { - let &USVString(ref inner_scope) = scope; - match api_base_url.join(inner_scope) { - Ok(url) => url, - Err(_) => { - promise.reject_error(Error::Type("Invalid scope URL".to_owned())); - return promise; - }, - } - }, - None => script_url.join("./").unwrap(), - }; + // B: Step 6 match scope.scheme() { "https" | "http" => {}, @@ -120,17 +137,134 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer { return promise; } - // B: Step 8 + // Setup the callback for reject/resolve of the promise, + // from steps running "in-parallel" from here in the serviceworker manager. + let (task_source, task_canceller) = ( + global.dom_manipulation_task_source(), + global.task_canceller(TaskSourceName::DOMManipulation), + ); + + let mut handler = RegisterJobResultHandler { + trusted_promise: Some(TrustedPromise::new(promise.clone())), + task_source, + task_canceller, + }; + + let (job_result_sender, job_result_receiver) = ipc::channel().expect("ipc channel failure"); + + ROUTER.add_route( + job_result_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to(); + match msg { + Ok(msg) => handler.handle(msg), + Err(err) => warn!("Error receiving a JobResult: {:?}", err), + } + }), + ); + + let scope_things = + ServiceWorkerRegistration::create_scope_things(&*global, script_url.clone()); + + // B: Step 8 - 13 let job = Job::create_job( JobType::Register, scope, script_url, - promise.clone(), - options.type_, - &*self.client, + job_result_sender, + self.client.creation_url(), + Some(scope_things), ); - // Job is unrooted here, do not do anything other than immediately scheduling - ScriptThread::schedule_job(job); + + // B: Step 14: schedule job. + let _ = global + .script_to_constellation_chan() + .send(ScriptMsg::ScheduleJob(job)); + + // A: Step 7 promise } } + +/// Callback for resolve/reject job promise for Register. +/// <https://w3c.github.io/ServiceWorker/#register> +struct RegisterJobResultHandler { + trusted_promise: Option<TrustedPromise>, + task_source: DOMManipulationTaskSource, + task_canceller: TaskCanceller, +} + +impl RegisterJobResultHandler { + /// <https://w3c.github.io/ServiceWorker/#reject-job-promise> + /// <https://w3c.github.io/ServiceWorker/#resolve-job-promise> + /// Handle a result to either resolve or reject the register job promise. + pub fn handle(&mut self, result: JobResult) { + match result { + JobResult::RejectPromise(error) => { + let promise = self + .trusted_promise + .take() + .expect("No promise to resolve for SW Register job."); + + // Step 1 + let _ = self.task_source.queue_with_canceller( + task!(reject_promise_with_security_error: move || { + let promise = promise.root(); + let _ac = enter_realm(&*promise.global()); + match error { + JobError::TypeError => { + promise.reject_error(Error::Type("Failed to register a ServiceWorker".to_string())); + }, + JobError::SecurityError => { + promise.reject_error(Error::Security); + }, + } + + }), + &self.task_canceller, + ); + + // TODO: step 2, handle equivalent jobs. + }, + JobResult::ResolvePromise(job, value) => { + let promise = self + .trusted_promise + .take() + .expect("No promise to resolve for SW Register job."); + + // Step 1 + let _ = self.task_source.queue_with_canceller( + task!(resolve_promise: move || { + let promise = promise.root(); + let global = promise.global(); + let _ac = enter_realm(&*global); + + // Step 1.1 + let JobResultValue::Registration { + id, + installing_worker, + waiting_worker, + active_worker, + } = value; + + // Step 1.2 (Job type is "register"). + let registration = global.get_serviceworker_registration( + &job.script_url, + &job.scope_url, + id, + installing_worker, + waiting_worker, + active_worker, + ); + + // Step 1.4 + promise.resolve_native(&*registration); + }), + &self.task_canceller, + ); + + // TODO: step 2, handle equivalent jobs. + }, + } + } +} diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 9699262a874..a605e744ead 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -268,15 +268,12 @@ impl ServiceWorkerGlobalScope { } = scope_things; let serialized_worker_url = script_url.to_string(); - let origin = GlobalScope::current() - .expect("No current global object") - .origin() - .immutable() - .clone(); + let origin = scope_url.origin(); thread::Builder::new() .name(format!("ServiceWorker for {}", serialized_worker_url)) .spawn(move || { thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER); + let runtime = new_rt_and_cx(None); let roots = RootCollection::new(); let _stack_roots = ThreadLocalStackRoots::new(&roots); @@ -298,34 +295,19 @@ impl ServiceWorkerGlobalScope { .referrer_policy(referrer_policy) .origin(origin); - let (url, source) = match load_whole_resource( - request, - &init.resource_threads.sender(), - &GlobalScope::current().expect("No current global object"), - ) { - Err(_) => { - println!("error loading script {}", serialized_worker_url); - return; - }, - Ok((metadata, bytes)) => { - (metadata.final_url, String::from_utf8(bytes).unwrap()) - }, - }; - - let runtime = new_rt_and_cx(None); - - let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); - ROUTER - .route_ipc_receiver_to_crossbeam_sender(devtools_receiver, devtools_mpsc_chan); - // Service workers are time limited // https://w3c.github.io/ServiceWorker/#service-worker-lifetime let sw_lifetime_timeout = pref!(dom.serviceworker.timeout_seconds) as u64; let time_out_port = after(Duration::new(sw_lifetime_timeout, 0)); + let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); + ROUTER + .route_ipc_receiver_to_crossbeam_sender(devtools_receiver, devtools_mpsc_chan); + + let resource_threads_sender = init.resource_threads.sender(); let global = ServiceWorkerGlobalScope::new( init, - url, + script_url, devtools_mpsc_port, runtime, own_sender, @@ -334,6 +316,19 @@ impl ServiceWorkerGlobalScope { swmanager_sender, scope_url, ); + + let (_url, source) = + match load_whole_resource(request, &resource_threads_sender, &*global.upcast()) + { + Err(_) => { + println!("error loading script {}", serialized_worker_url); + return; + }, + Ok((metadata, bytes)) => { + (metadata.final_url, String::from_utf8(bytes).unwrap()) + }, + }; + let scope = global.upcast::<WorkerGlobalScope>(); unsafe { @@ -356,7 +351,7 @@ impl ServiceWorkerGlobalScope { // until the event loop is destroyed, // which happens after the closing flag is set to true, // or until the worker has run beyond its allocated time. - while !scope.is_closing() || !global.has_timed_out() { + while !scope.is_closing() && !global.has_timed_out() { run_worker_event_loop(&*global, None); } }, @@ -364,6 +359,7 @@ impl ServiceWorkerGlobalScope { scope.script_chan(), CommonScriptMsg::CollectReports, ); + scope.clear_js_runtime(); }) .expect("Thread spawning failed"); } @@ -390,15 +386,10 @@ impl ServiceWorkerGlobalScope { } fn has_timed_out(&self) -> bool { - // Note: this should be included in the `select` inside `run_worker_event_loop`, - // otherwise a block on the select can prevent the timeout. - if self.time_out_port.try_recv().is_ok() { - let _ = self - .swmanager_sender - .send(ServiceWorkerMsg::Timeout(self.scope_url.clone())); - return true; - } - false + // TODO: https://w3c.github.io/ServiceWorker/#service-worker-lifetime + // Since we don't have a shutdown mechanism yet, see #26548 + // immediately stop the event-loop after executing the initial script. + true } fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) { diff --git a/components/script/dom/serviceworkerregistration.rs b/components/script/dom/serviceworkerregistration.rs index 9bb578e0d59..0c1734b41cc 100644 --- a/components/script/dom/serviceworkerregistration.rs +++ b/components/script/dom/serviceworkerregistration.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::ServiceWorkerBinding::ServiceWorkerState; use crate::dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::ServiceWorkerRegistrationMethods; use crate::dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::ServiceWorkerUpdateViaCache; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; @@ -16,6 +15,7 @@ use crate::dom::serviceworker::ServiceWorker; use crate::dom::workerglobalscope::prepare_workerscope_init; use devtools_traits::WorkerId; use dom_struct::dom_struct; +use msg::constellation_msg::ServiceWorkerRegistrationId; use script_traits::{ScopeThings, WorkerScriptLoadOrigin}; use servo_url::ServoUrl; use std::cell::Cell; @@ -24,58 +24,60 @@ use uuid::Uuid; #[dom_struct] pub struct ServiceWorkerRegistration { eventtarget: EventTarget, - active: Option<Dom<ServiceWorker>>, - installing: Option<Dom<ServiceWorker>>, - waiting: Option<Dom<ServiceWorker>>, + active: DomRefCell<Option<Dom<ServiceWorker>>>, + installing: DomRefCell<Option<Dom<ServiceWorker>>>, + waiting: DomRefCell<Option<Dom<ServiceWorker>>>, navigation_preload: MutNullableDom<NavigationPreloadManager>, scope: ServoUrl, navigation_preload_enabled: Cell<bool>, navigation_preload_header_value: DomRefCell<Option<ByteString>>, update_via_cache: ServiceWorkerUpdateViaCache, uninstalling: Cell<bool>, + registration_id: ServiceWorkerRegistrationId, } impl ServiceWorkerRegistration { - fn new_inherited(active_sw: &ServiceWorker, scope: ServoUrl) -> ServiceWorkerRegistration { + fn new_inherited( + scope: ServoUrl, + registration_id: ServiceWorkerRegistrationId, + ) -> ServiceWorkerRegistration { ServiceWorkerRegistration { eventtarget: EventTarget::new_inherited(), - active: Some(Dom::from_ref(active_sw)), - installing: None, - waiting: None, + active: DomRefCell::new(None), + installing: DomRefCell::new(None), + waiting: DomRefCell::new(None), navigation_preload: MutNullableDom::new(None), scope: scope, navigation_preload_enabled: Cell::new(false), navigation_preload_header_value: DomRefCell::new(None), update_via_cache: ServiceWorkerUpdateViaCache::Imports, uninstalling: Cell::new(false), + registration_id, } } #[allow(unrooted_must_root)] pub fn new( global: &GlobalScope, - script_url: &ServoUrl, scope: ServoUrl, + registration_id: ServiceWorkerRegistrationId, ) -> DomRoot<ServiceWorkerRegistration> { - let active_worker = - ServiceWorker::install_serviceworker(global, script_url.clone(), scope.clone(), true); - active_worker.set_transition_state(ServiceWorkerState::Installed); - reflect_dom_object( Box::new(ServiceWorkerRegistration::new_inherited( - &*active_worker, scope, + registration_id, )), global, ) } - pub fn active(&self) -> Option<&ServiceWorker> { - self.active.as_ref().map(|sw| &**sw) + /// Does this registration have an active worker? + pub fn is_active(&self) -> bool { + self.active.borrow().is_some() } - pub fn get_installed(&self) -> &ServiceWorker { - self.active.as_ref().unwrap() + pub fn set_installing(&self, worker: &ServiceWorker) { + *self.installing.borrow_mut() = Some(Dom::from_ref(worker)); } pub fn get_navigation_preload_header_value(&self) -> Option<ByteString> { @@ -124,13 +126,14 @@ impl ServiceWorkerRegistration { // https://w3c.github.io/ServiceWorker/#get-newest-worker-algorithm pub fn get_newest_worker(&self) -> Option<DomRoot<ServiceWorker>> { - if self.installing.as_ref().is_some() { - self.installing.as_ref().map(|sw| DomRoot::from_ref(&**sw)) - } else if self.waiting.as_ref().is_some() { - self.waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw)) - } else { - self.active.as_ref().map(|sw| DomRoot::from_ref(&**sw)) - } + let installing = self.installing.borrow(); + let waiting = self.waiting.borrow(); + let active = self.active.borrow(); + installing + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) + .or_else(|| waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw))) + .or_else(|| active.as_ref().map(|sw| DomRoot::from_ref(&**sw))) } } @@ -154,17 +157,26 @@ pub fn longest_prefix_match(stored_scope: &ServoUrl, potential_match: &ServoUrl) impl ServiceWorkerRegistrationMethods for ServiceWorkerRegistration { // https://w3c.github.io/ServiceWorker/#service-worker-registration-installing-attribute fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> { - self.installing.as_ref().map(|sw| DomRoot::from_ref(&**sw)) + self.installing + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-active-attribute fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> { - self.active.as_ref().map(|sw| DomRoot::from_ref(&**sw)) + self.active + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-waiting-attribute fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> { - self.waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw)) + self.waiting + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-scope-attribute diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 6e9490cb2c7..8d54c3f7da4 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -100,7 +100,7 @@ pub struct WorkerGlobalScope { #[ignore_malloc_size_of = "Arc"] closing: Option<Arc<AtomicBool>>, #[ignore_malloc_size_of = "Defined in js"] - runtime: Runtime, + runtime: DomRefCell<Option<Runtime>>, location: MutNullableDom<WorkerLocation>, navigator: MutNullableDom<WorkerNavigator>, @@ -151,7 +151,7 @@ impl WorkerGlobalScope { worker_type, worker_url: DomRefCell::new(worker_url), closing, - runtime, + runtime: DomRefCell::new(Some(runtime)), location: Default::default(), navigator: Default::default(), from_devtools_sender: init.from_devtools_sender, @@ -161,8 +161,17 @@ impl WorkerGlobalScope { } } + pub fn clear_js_runtime(&self) { + let runtime = self.runtime.borrow_mut().take(); + drop(runtime); + } + pub fn runtime_handle(&self) -> ParentRuntime { - self.runtime.prepare_for_new_child() + self.runtime + .borrow() + .as_ref() + .unwrap() + .prepare_for_new_child() } pub fn from_devtools_sender(&self) -> Option<IpcSender<DevtoolScriptControlMsg>> { @@ -175,7 +184,7 @@ impl WorkerGlobalScope { #[allow(unsafe_code)] pub fn get_cx(&self) -> JSContext { - unsafe { JSContext::from_ptr(self.runtime.cx()) } + unsafe { JSContext::from_ptr(self.runtime.borrow().as_ref().unwrap().cx()) } } pub fn is_closing(&self) -> bool { @@ -235,7 +244,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { }; } - rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); + rooted!(in(self.runtime.borrow().as_ref().unwrap().cx()) let mut rval = UndefinedValue()); for url in urls { let global_scope = self.upcast::<GlobalScope>(); let request = NetRequestInit::new(url.clone()) @@ -256,7 +265,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { Ok((metadata, bytes)) => (metadata.final_url, String::from_utf8(bytes).unwrap()), }; - let result = self.runtime.evaluate_script( + let result = self.runtime.borrow().as_ref().unwrap().evaluate_script( self.reflector().get_jsobject(), &source, url.as_str(), @@ -401,8 +410,9 @@ impl WorkerGlobalScope { #[allow(unsafe_code)] pub fn execute_script(&self, source: DOMString) { let _aes = AutoEntryScript::new(self.upcast()); - rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); - match self.runtime.evaluate_script( + let cx = self.runtime.borrow().as_ref().unwrap().cx(); + rooted!(in(cx) let mut rval = UndefinedValue()); + match self.runtime.borrow().as_ref().unwrap().evaluate_script( self.reflector().get_jsobject(), &source, self.worker_url.borrow().as_str(), @@ -419,7 +429,7 @@ impl WorkerGlobalScope { println!("evaluate_script failed"); unsafe { let ar = enter_realm(&*self); - report_pending_exception(self.runtime.cx(), true, InRealm::Entered(&ar)); + report_pending_exception(cx, true, InRealm::Entered(&ar)); } } }, diff --git a/components/script/lib.rs b/components/script/lib.rs index 886a76e9c95..be9e650e81d 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -92,8 +92,6 @@ pub mod script_thread; #[warn(deprecated)] pub mod serviceworker_manager; #[warn(deprecated)] -mod serviceworkerjob; -#[warn(deprecated)] mod stylesheet_loader; #[warn(deprecated)] mod stylesheet_set; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 41dc5406b4a..2107cd070cd 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -63,7 +63,6 @@ use crate::dom::node::{ use crate::dom::performanceentry::PerformanceEntry; use crate::dom::performancepainttiming::PerformancePaintTiming; use crate::dom::serviceworker::TrustedServiceWorkerAddress; -use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::servoparser::{ParserContext, ServoParser}; use crate::dom::transitionevent::TransitionEvent; use crate::dom::uievent::UIEvent; @@ -77,7 +76,6 @@ use crate::microtask::{Microtask, MicrotaskQueue}; use crate::realms::enter_realm; use crate::script_runtime::{get_reports, new_rt_and_cx, JSContext, Runtime, ScriptPort}; use crate::script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory}; -use crate::serviceworkerjob::{Job, JobQueue}; use crate::task_manager::TaskManager; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::dom_manipulation::DOMManipulationTaskSource; @@ -287,8 +285,6 @@ pub enum MainThreadScriptMsg { properties: Vec<Atom>, painter: Box<dyn Painter>, }, - /// Dispatches a job queue. - DispatchJobQueue { scope_url: ServoUrl }, /// A task related to a not fully-active document has been throttled. Inactive, /// Wake-up call from the task queue. @@ -533,10 +529,6 @@ pub struct ScriptThread { incomplete_loads: DomRefCell<Vec<InProgressLoad>>, /// A vector containing parser contexts which have not yet been fully processed incomplete_parser_contexts: RefCell<IncompleteParserContexts>, - /// A map to store service worker registrations for a given origin - registration_map: DomRefCell<HashMap<ServoUrl, Dom<ServiceWorkerRegistration>>>, - /// A job queue for Service Workers keyed by their scope url - job_queue_map: Rc<JobQueue>, /// Image cache for this script thread. image_cache: Arc<dyn ImageCache>, /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if @@ -927,15 +919,6 @@ impl ScriptThread { }) } - #[allow(unrooted_must_root)] - pub fn schedule_job(job: Job) { - SCRIPT_THREAD_ROOT.with(|root| { - let script_thread = unsafe { &*root.get().unwrap() }; - let job_queue = &*script_thread.job_queue_map; - job_queue.schedule_job(job, &script_thread); - }); - } - pub fn process_event(msg: CommonScriptMsg) { SCRIPT_THREAD_ROOT.with(|root| { if let Some(script_thread) = root.get() { @@ -1317,8 +1300,6 @@ impl ScriptThread { window_proxies: DomRefCell::new(HashMap::new()), incomplete_loads: DomRefCell::new(vec![]), incomplete_parser_contexts: RefCell::new(vec![]), - registration_map: DomRefCell::new(HashMap::new()), - job_queue_map: Rc::new(JobQueue::new()), image_cache: state.image_cache.clone(), image_cache_channel: image_cache_channel, @@ -1774,7 +1755,6 @@ impl ScriptThread { MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None, MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id), MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id), - MainThreadScriptMsg::DispatchJobQueue { .. } => None, MainThreadScriptMsg::Inactive => None, MainThreadScriptMsg::WakeUp => None, }, @@ -2008,9 +1988,6 @@ impl ScriptThread { properties, painter, } => self.handle_register_paint_worklet(pipeline_id, name, properties, painter), - MainThreadScriptMsg::DispatchJobQueue { scope_url } => { - self.job_queue_map.run_job(scope_url, self) - }, MainThreadScriptMsg::Inactive => {}, MainThreadScriptMsg::WakeUp => {}, } @@ -2720,57 +2697,6 @@ impl ScriptThread { } } - pub fn handle_get_registration( - &self, - scope_url: &ServoUrl, - ) -> Option<DomRoot<ServiceWorkerRegistration>> { - let maybe_registration_ref = self.registration_map.borrow(); - maybe_registration_ref - .get(scope_url) - .map(|x| DomRoot::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); - reg_ref.insert(scope.clone(), Dom::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) { - Some(r) => r, - None => return, - }; - let window = match self.documents.borrow().find_window(pipeline_id) { - Some(window) => window, - None => return warn!("Registration failed for {}", scope), - }; - - let script_url = maybe_registration.get_installed().get_script_url(); - let scope_things = - ServiceWorkerRegistration::create_scope_things(window.upcast(), script_url); - let _ = self.script_sender.send(( - pipeline_id, - ScriptMsg::RegisterServiceWorker(scope_things, scope.clone()), - )); - } - - pub fn schedule_job_queue(&self, scope_url: ServoUrl) { - let _ = self - .chan - .0 - .send(MainThreadScriptMsg::DispatchJobQueue { scope_url }); - } - pub fn dom_manipulation_task_source( &self, pipeline_id: PipelineId, diff --git a/components/script/serviceworker_manager.rs b/components/script/serviceworker_manager.rs index 73ead01fe17..7c0484144d9 100644 --- a/components/script/serviceworker_manager.rs +++ b/components/script/serviceworker_manager.rs @@ -13,10 +13,12 @@ use crate::dom::serviceworkerregistration::longest_prefix_match; use crossbeam_channel::{unbounded, Receiver, RecvError, Sender}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; +use msg::constellation_msg::PipelineNamespace; +use msg::constellation_msg::{ServiceWorkerId, ServiceWorkerRegistrationId}; use net_traits::{CoreResourceMsg, CustomResponseMediator}; use script_traits::{ - DOMMessage, SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerManagerFactory, - ServiceWorkerMsg, + DOMMessage, Job, JobError, JobResult, JobResultValue, JobType, SWManagerMsg, SWManagerSenders, + ScopeThings, ServiceWorkerManagerFactory, ServiceWorkerMsg, }; use servo_config::pref; use servo_url::ImmutableOrigin; @@ -29,11 +31,112 @@ enum Message { FromConstellation(ServiceWorkerMsg), } +/// <https://w3c.github.io/ServiceWorker/#dfn-service-worker> +#[derive(Clone)] +struct ServiceWorker { + /// A unique identifer. + pub id: ServiceWorkerId, + /// <https://w3c.github.io/ServiceWorker/#dfn-script-url> + pub script_url: ServoUrl, + /// A sender to the running service worker scope. + pub sender: Sender<ServiceWorkerScriptMsg>, +} + +impl ServiceWorker { + fn new( + script_url: ServoUrl, + sender: Sender<ServiceWorkerScriptMsg>, + id: ServiceWorkerId, + ) -> ServiceWorker { + ServiceWorker { + id, + script_url, + sender, + } + } + + /// Forward a DOM message to the running service worker scope. + fn forward_dom_message(&self, msg: DOMMessage) { + let DOMMessage { origin, data } = msg; + let _ = self.sender.send(ServiceWorkerScriptMsg::CommonWorker( + WorkerScriptMsg::DOMMessage { origin, data }, + )); + } + + /// Send a message to the running service worker scope. + fn send_message(&self, msg: ServiceWorkerScriptMsg) { + let _ = self.sender.send(msg); + } +} + +/// When updating a registration, which worker are we targetting? +#[allow(dead_code)] +enum RegistrationUpdateTarget { + Installing, + Waiting, + Active, +} + +/// https://w3c.github.io/ServiceWorker/#service-worker-registration-concept +struct ServiceWorkerRegistration { + /// A unique identifer. + id: ServiceWorkerRegistrationId, + /// https://w3c.github.io/ServiceWorker/#dfn-active-worker + active_worker: Option<ServiceWorker>, + /// https://w3c.github.io/ServiceWorker/#dfn-waiting-worker + waiting_worker: Option<ServiceWorker>, + /// https://w3c.github.io/ServiceWorker/#dfn-installing-worker + installing_worker: Option<ServiceWorker>, +} + +impl ServiceWorkerRegistration { + pub fn new() -> ServiceWorkerRegistration { + ServiceWorkerRegistration { + id: ServiceWorkerRegistrationId::new(), + active_worker: None, + waiting_worker: None, + installing_worker: None, + } + } + + /// <https://w3c.github.io/ServiceWorker/#get-newest-worker> + fn get_newest_worker(&self) -> Option<ServiceWorker> { + if let Some(worker) = self.active_worker.as_ref() { + return Some(worker.clone()); + } + if let Some(worker) = self.waiting_worker.as_ref() { + return Some(worker.clone()); + } + if let Some(worker) = self.installing_worker.as_ref() { + return Some(worker.clone()); + } + None + } + + /// <https://w3c.github.io/ServiceWorker/#update-registration-state> + fn update_registration_state( + &mut self, + target: RegistrationUpdateTarget, + worker: ServiceWorker, + ) { + match target { + RegistrationUpdateTarget::Active => { + self.active_worker = Some(worker); + }, + RegistrationUpdateTarget::Waiting => { + self.waiting_worker = Some(worker); + }, + RegistrationUpdateTarget::Installing => { + self.installing_worker = Some(worker); + }, + } + } +} + +/// A structure managing all registrations and workers for a given origin. pub struct ServiceWorkerManager { - // map of registered service worker descriptors - registered_workers: HashMap<ServoUrl, ScopeThings>, - // map of active service worker descriptors - active_workers: HashMap<ServoUrl, Sender<ServiceWorkerScriptMsg>>, + /// https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map + registrations: HashMap<ServoUrl, ServiceWorkerRegistration>, // Will be useful to implement posting a message to a client. // See https://github.com/servo/servo/issues/24660 _constellation_sender: IpcSender<SWManagerMsg>, @@ -52,9 +155,11 @@ impl ServiceWorkerManager { resource_port: Receiver<CustomResponseMediator>, constellation_sender: IpcSender<SWManagerMsg>, ) -> ServiceWorkerManager { + // Install a pipeline-namespace in the current thread. + PipelineNamespace::auto_install(); + ServiceWorkerManager { - registered_workers: HashMap::new(), - active_workers: HashMap::new(), + registrations: HashMap::new(), own_sender: own_sender, own_port: from_constellation_receiver, resource_receiver: resource_port, @@ -63,7 +168,7 @@ impl ServiceWorkerManager { } pub fn get_matching_scope(&self, load_url: &ServoUrl) -> Option<ServoUrl> { - for scope in self.registered_workers.keys() { + for scope in self.registrations.keys() { if longest_prefix_match(&scope, load_url) { return Some(scope.clone()); } @@ -71,31 +176,6 @@ impl ServiceWorkerManager { None } - pub fn wakeup_serviceworker( - &mut self, - scope_url: ServoUrl, - ) -> Option<Sender<ServiceWorkerScriptMsg>> { - let scope_things = self.registered_workers.get(&scope_url); - if let Some(scope_things) = scope_things { - let (sender, receiver) = unbounded(); - let (_devtools_sender, devtools_receiver) = ipc::channel().unwrap(); - ServiceWorkerGlobalScope::run_serviceworker_scope( - scope_things.clone(), - sender.clone(), - receiver, - devtools_receiver, - self.own_sender.clone(), - scope_url.clone(), - ); - // We store the activated worker - self.active_workers.insert(scope_url, sender.clone()); - return Some(sender); - } else { - warn!("Unable to activate service worker"); - None - } - } - fn handle_message(&mut self) { while let Ok(message) = self.receive_message() { let should_continue = match message { @@ -108,76 +188,198 @@ impl ServiceWorkerManager { } } - fn forward_message(&self, msg: DOMMessage, sender: &Sender<ServiceWorkerScriptMsg>) { - let DOMMessage { origin, data } = msg; - let _ = sender.send(ServiceWorkerScriptMsg::CommonWorker( - WorkerScriptMsg::DOMMessage { origin, data }, - )); + fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool { + if serviceworker_enabled() { + if let Some(scope) = self.get_matching_scope(&mediator.load_url) { + if let Some(registration) = self.registrations.get(&scope) { + if let Some(ref worker) = registration.active_worker { + worker.send_message(ServiceWorkerScriptMsg::Response(mediator)); + return true; + } + } + } + } + let _ = mediator.response_chan.send(None); + true + } + + fn receive_message(&mut self) -> Result<Message, RecvError> { + select! { + recv(self.own_port) -> msg => msg.map(Message::FromConstellation), + recv(self.resource_receiver) -> msg => msg.map(Message::FromResource), + } } fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool { match msg { - ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope) => { - if self.registered_workers.contains_key(&scope) { - warn!("ScopeThings for {:?} already stored in SW-Manager", scope); - } else { - self.registered_workers.insert(scope, scope_things); - } - true - }, - ServiceWorkerMsg::Timeout(scope) => { - if self.active_workers.contains_key(&scope) { - let _ = self.active_workers.remove(&scope); - } else { - warn!("ServiceWorker for {:?} is not active", scope); - } - true + ServiceWorkerMsg::Timeout(_scope) => { + // TODO: https://w3c.github.io/ServiceWorker/#terminate-service-worker }, ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => { - if self.active_workers.contains_key(&scope_url) { - if let Some(ref sender) = self.active_workers.get(&scope_url) { - self.forward_message(msg, &sender); - } - } else { - if let Some(ref sender) = self.wakeup_serviceworker(scope_url) { - self.forward_message(msg, &sender); + if let Some(registration) = self.registrations.get_mut(&scope_url) { + if let Some(ref worker) = registration.active_worker { + worker.forward_dom_message(msg); } } - true }, - ServiceWorkerMsg::Exit => false, + ServiceWorkerMsg::ScheduleJob(job) => match job.job_type { + JobType::Register => { + self.handle_register_job(job); + }, + JobType::Update => { + self.handle_update_job(job); + }, + JobType::Unregister => { + // TODO: https://w3c.github.io/ServiceWorker/#unregister-algorithm + }, + }, + ServiceWorkerMsg::Exit => return false, } + true } - fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool { - if serviceworker_enabled() { - if let Some(scope) = self.get_matching_scope(&mediator.load_url) { - if self.active_workers.contains_key(&scope) { - if let Some(sender) = self.active_workers.get(&scope) { - let _ = sender.send(ServiceWorkerScriptMsg::Response(mediator)); - } - } else { - if let Some(sender) = self.wakeup_serviceworker(scope) { - let _ = sender.send(ServiceWorkerScriptMsg::Response(mediator)); - } - } - } else { - let _ = mediator.response_chan.send(None); + /// <https://w3c.github.io/ServiceWorker/#register-algorithm> + fn handle_register_job(&mut self, mut job: Job) { + if !job.script_url.is_origin_trustworthy() { + // Step 1.1 + let _ = job + .client + .send(JobResult::RejectPromise(JobError::SecurityError)); + return; + } + + if job.script_url.origin() != job.referrer.origin() || + job.scope_url.origin() != job.referrer.origin() + { + // Step 2.1 + let _ = job + .client + .send(JobResult::RejectPromise(JobError::SecurityError)); + return; + } + + // Step 4: Get registration. + if let Some(registration) = self.registrations.get(&job.scope_url) { + // Step 5, we have a registation. + + // Step 5.1, get newest worker + let newest_worker = registration.get_newest_worker(); + + // step 5.2 + if newest_worker.is_some() { + // TODO: the various checks of job versus worker. + + // Step 2.1: Run resolve job. + let client = job.client.clone(); + let _ = client.send(JobResult::ResolvePromise( + job, + JobResultValue::Registration { + id: registration.id, + installing_worker: registration + .installing_worker + .as_ref() + .map(|worker| worker.id), + waiting_worker: registration + .waiting_worker + .as_ref() + .map(|worker| worker.id), + active_worker: registration.active_worker.as_ref().map(|worker| worker.id), + }, + )); + return; } } else { - let _ = mediator.response_chan.send(None); + // Step 6: we do not have a registration. + + // Step 6.1: Run Set Registration. + let new_registration = ServiceWorkerRegistration::new(); + self.registrations + .insert(job.scope_url.clone(), new_registration); + + // Step 7: Schedule update + job.job_type = JobType::Update; + let _ = self.own_sender.send(ServiceWorkerMsg::ScheduleJob(job)); } - true } - fn receive_message(&mut self) -> Result<Message, RecvError> { - select! { - recv(self.own_port) -> msg => msg.map(Message::FromConstellation), - recv(self.resource_receiver) -> msg => msg.map(Message::FromResource), + /// <https://w3c.github.io/ServiceWorker/#update> + fn handle_update_job(&mut self, job: Job) { + // Step 1: Get registation + if let Some(registration) = self.registrations.get_mut(&job.scope_url) { + // Step 3. + let newest_worker = registration.get_newest_worker(); + + // Step 4. + if let Some(worker) = newest_worker { + if worker.script_url != job.script_url { + let _ = job + .client + .send(JobResult::RejectPromise(JobError::TypeError)); + return; + } + } + + let scope_things = job + .scope_things + .clone() + .expect("Update job should have scope things."); + + // Very roughly steps 5 to 18. + // TODO: implement all steps precisely. + let new_worker = + update_serviceworker(self.own_sender.clone(), job.scope_url.clone(), scope_things); + + // Step 19, run Install. + + // Install: Step 4, run Update Registration State. + registration + .update_registration_state(RegistrationUpdateTarget::Installing, new_worker); + + // Install: Step 7, run Resolve Job Promise. + let client = job.client.clone(); + let _ = client.send(JobResult::ResolvePromise( + job, + JobResultValue::Registration { + id: registration.id, + installing_worker: registration + .installing_worker + .as_ref() + .map(|worker| worker.id), + waiting_worker: registration.waiting_worker.as_ref().map(|worker| worker.id), + active_worker: registration.active_worker.as_ref().map(|worker| worker.id), + }, + )); + } else { + // Step 2 + let _ = job + .client + .send(JobResult::RejectPromise(JobError::TypeError)); } } } +/// <https://w3c.github.io/ServiceWorker/#update-algorithm> +fn update_serviceworker( + own_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, + scope_things: ScopeThings, +) -> ServiceWorker { + let (sender, receiver) = unbounded(); + let (_devtools_sender, devtools_receiver) = ipc::channel().unwrap(); + let worker_id = ServiceWorkerId::new(); + + ServiceWorkerGlobalScope::run_serviceworker_scope( + scope_things.clone(), + sender.clone(), + receiver, + devtools_receiver, + own_sender, + scope_url.clone(), + ); + + ServiceWorker::new(scope_things.script_url, sender, worker_id) +} + impl ServiceWorkerManagerFactory for ServiceWorkerManager { fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin) { let (resource_chan, resource_port) = ipc::channel().unwrap(); diff --git a/components/script/serviceworkerjob.rs b/components/script/serviceworkerjob.rs deleted file mode 100644 index eeced0918f7..00000000000 --- a/components/script/serviceworkerjob.rs +++ /dev/null @@ -1,342 +0,0 @@ -/* 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 https://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 crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; -use crate::dom::bindings::error::Error; -use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; -use crate::dom::bindings::reflector::DomObject; -use crate::dom::bindings::root::Dom; -use crate::dom::client::Client; -use crate::dom::promise::Promise; -use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; -use crate::script_thread::ScriptThread; -use crate::task_source::dom_manipulation::DOMManipulationTaskSource; -use crate::task_source::TaskSource; -use servo_url::ServoUrl; -use std::cmp::PartialEq; -use std::collections::HashMap; -use std::rc::Rc; - -#[derive(Clone, Copy, Debug, JSTraceable, PartialEq)] -pub enum JobType { - Register, - Unregister, - Update, -} - -#[derive(Clone)] -pub enum SettleType { - Resolve(Trusted<ServiceWorkerRegistration>), - Reject(Error), -} - -#[unrooted_must_root_lint::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>, - pub worker_type: WorkerType, - // client can be a window client, worker client so `Client` will be an enum in future - pub client: Dom<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>, - worker_type: WorkerType, - client: &Client, - ) -> Job { - Job { - job_type: job_type, - scope_url: scope_url, - script_url: script_url, - promise: promise, - equivalent_jobs: vec![], - worker_type, - client: Dom::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 - } - } -} - -#[unrooted_must_root_lint::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, script_thread: &ScriptThread) { - debug!("scheduling {:?} job", job.job_type); - 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 _ = script_thread.schedule_job_queue(scope_url); - debug!("queued task to run newly-queued job"); - } else { - // Step 2 - let mut last_job = job_queue.pop().unwrap(); - if job == last_job && !last_job.promise.is_fulfilled() { - last_job.append_equivalent_job(job); - job_queue.push(last_job); - debug!("appended equivalent job"); - } else { - // restore the popped last_job - job_queue.push(last_job); - // and push this new job to job queue - job_queue.push(job); - debug!("pushed onto job queue job"); - } - } - } - - #[allow(unrooted_must_root)] - // https://w3c.github.io/ServiceWorker/#run-job-algorithm - pub fn run_job(&self, scope_url: ServoUrl, script_thread: &ScriptThread) { - debug!("running a job"); - let url = { - let queue_ref = self.0.borrow(); - let front_job = { - let job_vec = queue_ref.get(&scope_url); - job_vec.unwrap().first().unwrap() - }; - let front_scope_url = front_job.scope_url.clone(); - match front_job.job_type { - JobType::Register => self.run_register(front_job, scope_url, script_thread), - JobType::Update => self.update(front_job, script_thread), - JobType::Unregister => unreachable!(), - }; - front_scope_url - }; - self.finish_job(url, script_thread); - } - - #[allow(unrooted_must_root)] - // https://w3c.github.io/ServiceWorker/#register-algorithm - fn run_register(&self, job: &Job, scope_url: ServoUrl, script_thread: &ScriptThread) { - debug!("running register job"); - let global = &*job.client.global(); - let pipeline_id = global.pipeline_id(); - // Step 1-3 - if !job.script_url.is_origin_trustworthy() { - // Step 1.1 - reject_job_promise( - job, - Error::Type("Invalid script ServoURL".to_owned()), - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 1.2 (see run_job) - return; - } else if job.script_url.origin() != job.referrer.origin() || - job.scope_url.origin() != job.referrer.origin() - { - // Step 2.1/3.1 - reject_job_promise( - job, - Error::Security, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 2.2/3.2 (see run_job) - return; - } - - // 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 { - // Step 5.3.1 - resolve_job_promise( - job, - &*reg, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 5.3.2 (see run_job) - return; - } - } - } else { - // Step 6.1 - let new_reg = ServiceWorkerRegistration::new(&*global, &job.script_url, scope_url); - script_thread.handle_serviceworker_registration(&job.scope_url, &*new_reg, pipeline_id); - } - // Step 7 - self.update(job, script_thread) - } - - #[allow(unrooted_must_root)] - // https://w3c.github.io/ServiceWorker/#finish-job-algorithm - pub fn finish_job(&self, scope_url: ServoUrl, script_thread: &ScriptThread) { - debug!("finishing previous job"); - let run_job = if let Some(job_vec) = (*self.0.borrow_mut()).get_mut(&scope_url) { - assert_eq!(job_vec.first().as_ref().unwrap().scope_url, scope_url); - let _ = job_vec.remove(0); - !job_vec.is_empty() - } else { - warn!("non-existent job vector for Servourl: {:?}", scope_url); - false - }; - - if run_job { - debug!("further jobs in queue after finishing"); - self.run_job(scope_url, script_thread); - } - } - - // https://w3c.github.io/ServiceWorker/#update-algorithm - fn update(&self, job: &Job, script_thread: &ScriptThread) { - debug!("running update job"); - - let global = &*job.client.global(); - let pipeline_id = global.pipeline_id(); - // Step 1 - let reg = match script_thread.handle_get_registration(&job.scope_url) { - Some(reg) => reg, - None => { - let err_type = Error::Type("No registration to update".to_owned()); - // Step 2.1 - reject_job_promise( - job, - err_type, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 2.2 (see run_job) - return; - }, - }; - // Step 2 - if reg.get_uninstalling() { - let err_type = Error::Type("Update called on an uninstalling registration".to_owned()); - // Step 2.1 - reject_job_promise( - job, - err_type, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 2.2 (see run_job) - return; - } - // Step 3 - let newest_worker = reg.get_newest_worker(); - let newest_worker_url = newest_worker.as_ref().map(|w| w.get_script_url()); - // Step 4 - if newest_worker_url.as_ref() == Some(&job.script_url) && job.job_type == JobType::Update { - let err_type = Error::Type("Invalid script ServoURL".to_owned()); - // Step 4.1 - reject_job_promise( - job, - err_type, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 4.2 (see run_job) - return; - } - // Step 8 - if let Some(newest_worker) = newest_worker { - job.client.set_controller(&*newest_worker); - // Step 8.1 - resolve_job_promise( - job, - &*reg, - &script_thread.dom_manipulation_task_source(pipeline_id), - ); - // Step 8.2 present in run_job - } - // TODO Step 9 (create new service worker) - } -} - -fn settle_job_promise(promise: &Promise, settle: SettleType) { - match settle { - SettleType::Resolve(reg) => promise.resolve_native(&*reg.root()), - SettleType::Reject(err) => promise.reject_error(err), - }; -} - -#[allow(unrooted_must_root)] -fn queue_settle_promise_for_job( - job: &Job, - settle: SettleType, - task_source: &DOMManipulationTaskSource, -) { - let global = job.client.global(); - let promise = TrustedPromise::new(job.promise.clone()); - // FIXME(nox): Why are errors silenced here? - let _ = task_source.queue( - task!(settle_promise_for_job: move || { - let promise = promise.root(); - settle_job_promise(&promise, settle) - }), - &*global, - ); -} - -// https://w3c.github.io/ServiceWorker/#reject-job-promise-algorithm -// https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm -fn queue_settle_promise(job: &Job, settle: SettleType, task_source: &DOMManipulationTaskSource) { - // Step 1 - queue_settle_promise_for_job(job, settle.clone(), task_source); - // Step 2 - for job in &job.equivalent_jobs { - queue_settle_promise_for_job(job, settle.clone(), task_source); - } -} - -fn reject_job_promise(job: &Job, err: Error, task_source: &DOMManipulationTaskSource) { - queue_settle_promise(job, SettleType::Reject(err), task_source) -} - -fn resolve_job_promise( - job: &Job, - reg: &ServiceWorkerRegistration, - task_source: &DOMManipulationTaskSource, -) { - queue_settle_promise(job, SettleType::Resolve(Trusted::new(reg)), task_source) -} diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index e13f1958e1b..d0309c72864 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -76,8 +76,8 @@ use webrender_api::{ use webrender_api::{BuiltDisplayListDescriptor, HitTestFlags, HitTestResult}; pub use crate::script_msg::{ - DOMMessage, HistoryEntryReplacement, SWManagerMsg, SWManagerSenders, ScopeThings, - ServiceWorkerMsg, + DOMMessage, HistoryEntryReplacement, Job, JobError, JobResult, JobResultValue, JobType, + SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerMsg, }; pub use crate::script_msg::{ EventResult, IFrameSize, IFrameSizeMsg, LayoutMsg, LogEntry, ScriptMsg, diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index b5687d6217e..291d6f4798e 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -27,6 +27,7 @@ use msg::constellation_msg::{ TopLevelBrowsingContextId, }; use msg::constellation_msg::{HistoryStateId, TraversalDirection}; +use msg::constellation_msg::{ServiceWorkerId, ServiceWorkerRegistrationId}; use net_traits::request::RequestBuilder; use net_traits::storage_thread::StorageType; use net_traits::CoreResourceMsg; @@ -262,8 +263,8 @@ pub enum ScriptMsg { /// Send messages from postMessage calls from serviceworker /// to constellation for storing in service worker manager ForwardDOMMessage(DOMMessage, ServoUrl), - /// Store the data required to activate a service worker for the given scope - RegisterServiceWorker(ScopeThings, ServoUrl), + /// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm. + ScheduleJob(Job), /// Get Window Informations size and position GetClientWindow(IpcSender<(DeviceIntSize, DeviceIntPoint)>), /// Get the screen size (pixel) @@ -331,7 +332,7 @@ impl fmt::Debug for ScriptMsg { DiscardTopLevelBrowsingContext => "DiscardTopLevelBrowsingContext", PipelineExited => "PipelineExited", ForwardDOMMessage(..) => "ForwardDOMMessage", - RegisterServiceWorker(..) => "RegisterServiceWorker", + ScheduleJob(..) => "ScheduleJob", GetClientWindow(..) => "GetClientWindow", GetScreenSize(..) => "GetScreenSize", GetScreenAvailSize(..) => "GetScreenAvailSize", @@ -382,16 +383,118 @@ pub struct SWManagerSenders { /// Messages sent to Service Worker Manager thread #[derive(Debug, Deserialize, Serialize)] pub enum ServiceWorkerMsg { - /// Message to register the service worker - RegisterServiceWorker(ScopeThings, ServoUrl), /// Timeout message sent by active service workers Timeout(ServoUrl), /// Message sent by constellation to forward to a running service worker ForwardDOMMessage(DOMMessage, ServoUrl), + /// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm + ScheduleJob(Job), /// Exit the service worker manager Exit, } +#[derive(Debug, Deserialize, PartialEq, Serialize)] +/// https://w3c.github.io/ServiceWorker/#dfn-job-type +pub enum JobType { + /// <https://w3c.github.io/ServiceWorker/#register> + Register, + /// <https://w3c.github.io/ServiceWorker/#unregister-algorithm> + Unregister, + /// <https://w3c.github.io/ServiceWorker/#update-algorithm + Update, +} + +#[derive(Debug, Deserialize, Serialize)] +/// The kind of error the job promise should be rejected with. +pub enum JobError { + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + TypeError, + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + SecurityError, +} + +#[derive(Debug, Deserialize, Serialize)] +/// Messages sent from Job algorithms steps running in the SW manager, +/// in order to resolve or reject the job promise. +pub enum JobResult { + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + RejectPromise(JobError), + /// https://w3c.github.io/ServiceWorker/#resolve-job-promise + ResolvePromise(Job, JobResultValue), +} + +#[derive(Debug, Deserialize, Serialize)] +/// Jobs are resolved with the help of various values. +pub enum JobResultValue { + /// Data representing a serviceworker registration. + Registration { + /// The Id of the registration. + id: ServiceWorkerRegistrationId, + /// The installing worker, if any. + installing_worker: Option<ServiceWorkerId>, + /// The waiting worker, if any. + waiting_worker: Option<ServiceWorkerId>, + /// The active worker, if any. + active_worker: Option<ServiceWorkerId>, + }, +} + +#[derive(Debug, Deserialize, Serialize)] +/// https://w3c.github.io/ServiceWorker/#dfn-job +pub struct Job { + /// <https://w3c.github.io/ServiceWorker/#dfn-job-type> + pub job_type: JobType, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-scope-url> + pub scope_url: ServoUrl, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-script-url> + pub script_url: ServoUrl, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-client> + pub client: IpcSender<JobResult>, + /// <https://w3c.github.io/ServiceWorker/#job-referrer> + pub referrer: ServoUrl, + /// Various data needed to process job. + pub scope_things: Option<ScopeThings>, +} + +impl Job { + /// https://w3c.github.io/ServiceWorker/#create-job-algorithm + pub fn create_job( + job_type: JobType, + scope_url: ServoUrl, + script_url: ServoUrl, + client: IpcSender<JobResult>, + referrer: ServoUrl, + scope_things: Option<ScopeThings>, + ) -> Job { + Job { + job_type, + scope_url, + script_url, + client, + referrer, + scope_things, + } + } +} + +impl PartialEq for Job { + /// Equality criteria as described in https://w3c.github.io/ServiceWorker/#dfn-job-equivalent + fn eq(&self, other: &Self) -> bool { + // TODO: match on job type, take worker type and `update_via_cache_mode` into account. + 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 + } + } +} + /// Messages outgoing from the Service Worker Manager thread to constellation #[derive(Debug, Deserialize, Serialize)] pub enum SWManagerMsg { diff --git a/tests/wpt/mozilla/meta-layout-2020/mozilla/service-workers/service-worker-registration.https.html.ini b/tests/wpt/mozilla/meta-layout-2020/mozilla/service-workers/service-worker-registration.https.html.ini index 577ca67b292..222aa07e5c3 100644 --- a/tests/wpt/mozilla/meta-layout-2020/mozilla/service-workers/service-worker-registration.https.html.ini +++ b/tests/wpt/mozilla/meta-layout-2020/mozilla/service-workers/service-worker-registration.https.html.ini @@ -1,7 +1,19 @@ [service-worker-registration.https.html] + [Test: Asserts Installing Service Worker and its Registration] + expected: FAIL + + [Test: Installing Service Worker ScriptURL property] + expected: FAIL + [Test: Throws Error when Invalid Scope] expected: FAIL + [Test: Asserts Active Service Worker and its Registration] + expected: FAIL + + [Test: Active Service Worker ScriptURL property] + expected: FAIL + [Test: Asserts ServiceWorkerContainer in Navigator] expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index d62a360aad8..832db1e23ac 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -14305,7 +14305,7 @@ ], "service-workers": { "service-worker-registration.https.html": [ - "949992e45de6858c336936b4f1ea4bca76db1d91", + "04d74fb5c582a3fef8dc589868f0b8c3e402eab2", [ null, {} diff --git a/tests/wpt/mozilla/meta/mozilla/service-workers/service-worker-registration.https.html.ini b/tests/wpt/mozilla/meta/mozilla/service-workers/service-worker-registration.https.html.ini new file mode 100644 index 00000000000..0492835f43e --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/service-workers/service-worker-registration.https.html.ini @@ -0,0 +1,6 @@ +[service-worker-registration.https.html] + type: testharness + [Test: Asserts Active Service Worker and its Registration] + expected: FAIL + [Test: Active Service Worker ScriptURL property] + expected: FAIL diff --git a/tests/wpt/mozilla/tests/mozilla/service-workers/service-worker-registration.https.html b/tests/wpt/mozilla/tests/mozilla/service-workers/service-worker-registration.https.html index 949992e45de..04d74fb5c58 100644 --- a/tests/wpt/mozilla/tests/mozilla/service-workers/service-worker-registration.https.html +++ b/tests/wpt/mozilla/tests/mozilla/service-workers/service-worker-registration.https.html @@ -21,6 +21,12 @@ promise_test(function() { }, "Test: Active Service Worker ScriptURL property"); promise_test(function() { + return register_sw('resources/sw.js').then(function(sw_reg) { + assert_equals(sw_reg.installing.scriptURL, location.href.replace("service-worker-registration.https.html", "resources/sw.js")); + }); +}, "Test: Installing Service Worker ScriptURL property"); + +promise_test(function() { return register_sw('sw.js').then(function(sw_reg) { assert_class_string(sw_reg, "ServiceWorkerRegistration"); assert_class_string(sw_reg.active, "ServiceWorker"); @@ -29,6 +35,13 @@ promise_test(function() { }, "Test: Asserts Active Service Worker and its Registration"); promise_test(function() { + return register_sw('sw.js').then(function(sw_reg) { + assert_class_string(sw_reg, "ServiceWorkerRegistration"); + assert_class_string(sw_reg.installing, "ServiceWorker"); + }); +}, "Test: Asserts Installing Service Worker and its Registration"); + +promise_test(function() { return register_sw('resources/sw.js', './').then(function(sw_reg) { assert_equals(sw_reg.scope, location.href.replace("service-worker-registration.https.html", "")); }); |