| /* 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/. */ |
| |
| use std::default::Default; |
| use std::rc::Rc; |
| |
| use base::generic_channel::GenericCallback; |
| use constellation_traits::{ |
| Job, JobError, JobResult, JobResultValue, JobType, ScriptToConstellationMessage, |
| }; |
| use dom_struct::dom_struct; |
| |
| use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{ |
| RegistrationOptions, ServiceWorkerContainerMethods, |
| }; |
| use crate::dom::bindings::error::Error; |
| use crate::dom::bindings::refcounted::TrustedPromise; |
| use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; |
| use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; |
| use crate::dom::bindings::str::USVString; |
| use crate::dom::client::Client; |
| 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::{InRealm, enter_realm}; |
| use crate::script_runtime::CanGc; |
| use crate::task_source::SendableTaskSource; |
| |
| #[dom_struct] |
| pub(crate) struct ServiceWorkerContainer { |
| eventtarget: EventTarget, |
| controller: MutNullableDom<ServiceWorker>, |
| client: Dom<Client>, |
| } |
| |
| impl ServiceWorkerContainer { |
| fn new_inherited(client: &Client) -> ServiceWorkerContainer { |
| ServiceWorkerContainer { |
| eventtarget: EventTarget::new_inherited(), |
| controller: Default::default(), |
| client: Dom::from_ref(client), |
| } |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<ServiceWorkerContainer> { |
| let client = Client::new(global.as_window(), can_gc); |
| let container = ServiceWorkerContainer::new_inherited(&client); |
| reflect_dom_object(Box::new(container), global, can_gc) |
| } |
| } |
| |
| impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer { |
| // https://w3c.github.io/ServiceWorker/#service-worker-container-controller-attribute |
| fn GetController(&self) -> Option<DomRoot<ServiceWorker>> { |
| self.client.get_controller() |
| } |
| |
| /// <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, |
| can_gc: CanGc, |
| ) -> Rc<Promise> { |
| // A: Step 2. |
| let global = self.client.global(); |
| |
| // A: Step 1 |
| let promise = Promise::new_in_current_realm(comp, can_gc); |
| let USVString(ref script_url) = script_url; |
| |
| // 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()), can_gc); |
| return promise; |
| }, |
| }; |
| |
| // A: Step 4-5 |
| let scope = match options.scope { |
| Some(ref scope) => { |
| let USVString(inner_scope) = scope; |
| match api_base_url.join(inner_scope) { |
| Ok(url) => url, |
| Err(_) => { |
| promise.reject_error(Error::Type("Invalid scope URL".to_owned()), can_gc); |
| return promise; |
| }, |
| } |
| }, |
| 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()), |
| can_gc, |
| ); |
| 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()), |
| can_gc, |
| ); |
| return promise; |
| } |
| |
| // B: Step 6 |
| match scope.scheme() { |
| "https" | "http" => {}, |
| _ => { |
| promise.reject_error( |
| Error::Type("Only secure origins are allowed".to_owned()), |
| can_gc, |
| ); |
| return promise; |
| }, |
| } |
| // B: Step 7 |
| if scope.path().to_ascii_lowercase().contains("%2f") || |
| scope.path().to_ascii_lowercase().contains("%5c") |
| { |
| promise.reject_error( |
| Error::Type("Scope URL contains forbidden characters".to_owned()), |
| can_gc, |
| ); |
| return promise; |
| } |
| |
| // Setup the callback for reject/resolve of the promise, |
| // from steps running "in-parallel" from here in the serviceworker manager. |
| let mut handler = RegisterJobResultHandler { |
| trusted_promise: Some(TrustedPromise::new(promise.clone())), |
| task_source: global.task_manager().dom_manipulation_task_source().into(), |
| }; |
| |
| let result_handler = GenericCallback::new(move |message| match message { |
| Ok(msg) => handler.handle(msg), |
| Err(err) => warn!("Error receiving a JobResult: {:?}", err), |
| }) |
| .expect("Failed to create callback"); |
| |
| 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, |
| result_handler, |
| self.client.creation_url(), |
| Some(scope_things), |
| ); |
| |
| // B: Step 14: schedule job. |
| let _ = global |
| .script_to_constellation_chan() |
| .send(ScriptToConstellationMessage::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: SendableTaskSource, |
| } |
| |
| 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(crate) 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 |
| self.task_source |
| .queue(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()), |
| CanGc::note(), |
| ); |
| }, |
| JobError::SecurityError => { |
| promise.reject_error(Error::Security, CanGc::note()); |
| }, |
| } |
| |
| })); |
| |
| // 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 |
| self.task_source.queue(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, |
| CanGc::note() |
| ); |
| |
| // Step 1.4 |
| promise.resolve_native(&*registration, CanGc::note()); |
| })); |
| |
| // TODO: step 2, handle equivalent jobs. |
| }, |
| } |
| } |
| } |