serviceworker: make job queue unique per origin

This commit is contained in:
Gregory Terzian 2020-04-25 16:01:28 +08:00
parent 7e74f73301
commit 89eb7c2aa2
19 changed files with 839 additions and 592 deletions

View file

@ -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(

View file

@ -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,
@ -423,6 +437,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);

View file

@ -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);

View file

@ -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));
}

View file

@ -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(&registration_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) =

View file

@ -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()

View file

@ -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,
)

View file

@ -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,45 +58,33 @@ 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
match script_url.scheme() {
"https" | "http" => {},
_ => {
promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
return promise;
},
}
// B: Step 3
if script_url.path().to_ascii_lowercase().contains("%2f") ||
script_url.path().to_ascii_lowercase().contains("%5c")
{
promise.reject_error(Error::Type(
"Script URL contains forbidden characters".to_owned(),
));
return promise;
}
// B: Step 4-5
// A: Step 4-5
let scope = match options.scope {
Some(ref scope) => {
let &USVString(ref inner_scope) = scope;
@ -102,6 +98,27 @@ impl ServiceWorkerContainerMethods for ServiceWorkerContainer {
},
None => script_url.join("./").unwrap(),
};
// A: Step 6 -> invoke B.
// B: Step 3
match script_url.scheme() {
"https" | "http" => {},
_ => {
promise.reject_error(Error::Type("Only secure origins are allowed".to_owned()));
return promise;
},
}
// B: Step 4
if script_url.path().to_ascii_lowercase().contains("%2f") ||
script_url.path().to_ascii_lowercase().contains("%5c")
{
promise.reject_error(Error::Type(
"Script URL contains forbidden characters".to_owned(),
));
return promise;
}
// 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.
},
}
}
}

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -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,65 +188,18 @@ 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_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::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);
}
}
true
},
ServiceWorkerMsg::Exit => false,
}
}
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));
if let Some(registration) = self.registrations.get(&scope) {
if let Some(ref worker) = registration.active_worker {
worker.send_message(ServiceWorkerScriptMsg::Response(mediator));
return true;
}
}
} else {
let _ = mediator.response_chan.send(None);
}
} else {
let _ = mediator.response_chan.send(None);
}
let _ = mediator.response_chan.send(None);
true
}
@ -176,6 +209,173 @@ impl ServiceWorkerManager {
recv(self.resource_receiver) -> msg => msg.map(Message::FromResource),
}
}
fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
match msg {
ServiceWorkerMsg::Timeout(scope) => {},
ServiceWorkerMsg::ForwardDOMMessage(msg, scope_url) => {
if let Some(registration) = self.registrations.get_mut(&scope_url) {
if let Some(ref worker) = registration.active_worker {
worker.forward_dom_message(msg);
}
}
},
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
}
/// <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 {
// 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));
}
}
/// <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 {

View file

@ -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)
}

View file

@ -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,

View file

@ -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 {

View file

@ -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

View file

@ -14298,7 +14298,7 @@
],
"service-workers": {
"service-worker-registration.https.html": [
"949992e45de6858c336936b4f1ea4bca76db1d91",
"04d74fb5c582a3fef8dc589868f0b8c3e402eab2",
[
null,
{}

View file

@ -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

View file

@ -20,6 +20,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");
@ -28,6 +34,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", ""));