| // Copyright 2022 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/worker/service_worker_object.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/script/environment_settings.h" |
| #include "cobalt/script/execution_state.h" |
| #include "cobalt/script/global_environment.h" |
| #include "cobalt/script/javascript_engine.h" |
| #include "cobalt/script/script_runner.h" |
| #include "cobalt/script/value_handle.h" |
| #include "cobalt/web/agent.h" |
| #include "cobalt/web/context.h" |
| #include "cobalt/worker/service_worker_global_scope.h" |
| #include "cobalt/worker/service_worker_state.h" |
| #include "cobalt/worker/worker_global_scope.h" |
| #include "cobalt/worker/worker_settings.h" |
| #include "url/gurl.h" |
| |
| namespace cobalt { |
| namespace worker { |
| |
| ServiceWorkerObject::ServiceWorkerObject(const Options& options) |
| : state_(kServiceWorkerStateParsed), options_(options) { |
| DCHECK(options.containing_service_worker_registration); |
| } |
| |
| ServiceWorkerObject::~ServiceWorkerObject() { |
| TRACE_EVENT0("cobalt::worker", "ServiceWorkerObject::~ServiceWorkerObject()"); |
| // Check that the object isn't destroyed without first calling Abort(). |
| DCHECK(!web_agent_); |
| DCHECK(!web_context_); |
| Abort(); |
| } |
| |
| void ServiceWorkerObject::Abort() { |
| TRACE_EVENT0("cobalt::worker", "ServiceWorkerObject::Abort()"); |
| if (web_agent_) { |
| DCHECK(message_loop()); |
| DCHECK(web_context_); |
| web_agent_->WaitUntilDone(); |
| web_agent_.reset(); |
| web_context_ = nullptr; |
| } |
| } |
| |
| void ServiceWorkerObject::SetScriptResource(const GURL& url, |
| std::string* resource) { |
| // The exact given resource may already be in the map, if that is the case, |
| // don't update the map at all, otherwise make a copy of the resource for |
| // storing in the map. |
| auto entry = script_resource_map_.find(url); |
| if (entry != script_resource_map_.end()) { |
| if (entry->second.get() != resource) { |
| // The map has an entry, but it's different than the given one, make a |
| // copy and replace. |
| entry->second.reset(new std::string(*resource)); |
| } |
| return; |
| } |
| |
| script_resource_map_[url].reset(new std::string(*resource)); |
| } |
| |
| bool ServiceWorkerObject::HasScriptResource() const { |
| return script_url_.is_valid() && |
| script_resource_map_.end() != script_resource_map_.find(script_url_); |
| } |
| |
| std::string* ServiceWorkerObject::LookupScriptResource(const GURL& url) const { |
| auto entry = script_resource_map_.find(url); |
| return entry != script_resource_map_.end() ? entry->second.get() : nullptr; |
| } |
| |
| void ServiceWorkerObject::PurgeScriptResourceMap() { |
| // Steps 13-15 of Algorithm for Install: |
| // https://w3c.github.io/ServiceWorker/#installation-algorithm |
| // 13. Let map be registration’s installing worker's script resource map. |
| // 14. Let usedSet be registration’s installing worker's set of used scripts. |
| // 15. For each url of map: |
| for (auto item = script_resource_map_.begin(), next_item = item; |
| item != script_resource_map_.end(); item = next_item) { |
| // Get next item here because erasing 'item' from the map will invalidate |
| // the iterator. |
| ++next_item; |
| // 15.1. If usedSet does not contain url, then remove map[url]. |
| if (set_of_used_scripts_.find(item->first) == set_of_used_scripts_.end()) { |
| script_resource_map_.erase(item); |
| } |
| } |
| } |
| |
| void ServiceWorkerObject::WillDestroyCurrentMessageLoop() { |
| TRACE_EVENT0("cobalt::worker", |
| "ServiceWorkerObject::WillDestroyCurrentMessageLoop()"); |
| // Destroy members that were constructed in the worker thread. |
| #if defined(ENABLE_DEBUGGER) |
| debug_module_.reset(); |
| #endif // ENABLE_DEBUGGER |
| |
| worker_global_scope_ = nullptr; |
| } |
| |
| void ServiceWorkerObject::ObtainWebAgentAndWaitUntilDone() { |
| TRACE_EVENT0("cobalt::worker", |
| "ServiceWorkerObject::ObtainWebAgentAndWaitUntilDone()"); |
| web_agent_.reset(new web::Agent( |
| options_.web_options, |
| base::Bind(&ServiceWorkerObject::Initialize, base::Unretained(this)), |
| this)); |
| web_agent_->WaitUntilDone(); |
| } |
| |
| void ServiceWorkerObject::Initialize(web::Context* context) { |
| TRACE_EVENT0("cobalt::worker", "ServiceWorkerObject::Initialize()"); |
| // Algorithm for "Run Service Worker" |
| // https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm |
| |
| // 8.1. Let realmExecutionContext be the result of creating a new JavaScript |
| // realm given agent and the following customizations: |
| // For the global object, create a new ServiceWorkerGlobalScope object. |
| // Let workerGlobalScope be the created object. |
| web_context_ = context; |
| // 8.2. Set serviceWorker’s global object to workerGlobalScope. |
| // 8.3. Let settingsObject be a new environment settings object whose |
| // algorithms are defined as follows: |
| // The realm execution context |
| // Return realmExecutionContext. |
| // The module map |
| // Return workerGlobalScope’s module map. |
| // The API URL character encoding |
| // Return UTF-8. |
| // The API base URL |
| // Return serviceWorker’s script url. |
| // The origin |
| // Return its registering service worker client's origin. |
| // The policy container |
| // Return workerGlobalScope’s policy container. |
| // The time origin |
| // Return the result of coarsening unsafeCreationTime given |
| // workerGlobalScope’s cross-origin isolated capability. |
| // 8.4. Set settingsObject’s id to a new unique opaque string, creation URL to |
| // serviceWorker’s script url, top-level creation URL to null, top-level |
| // origin to an implementation-defined value, target browsing context to |
| // null, and active service worker to null. |
| web_context_->setup_environment_settings(new WorkerSettings()); |
| web_context_->environment_settings()->set_base_url(script_url_); |
| scoped_refptr<ServiceWorkerGlobalScope> service_worker_global_scope = |
| new ServiceWorkerGlobalScope(web_context_->environment_settings(), this); |
| worker_global_scope_ = service_worker_global_scope; |
| web_context_->global_environment()->CreateGlobalObject( |
| service_worker_global_scope, web_context_->environment_settings()); |
| DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsWindow()); |
| DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsDedicatedWorker()); |
| DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker()); |
| DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->GetWrappableType() == |
| base::GetTypeId<ServiceWorkerGlobalScope>()); |
| DCHECK_EQ(service_worker_global_scope, |
| base::polymorphic_downcast<ServiceWorkerGlobalScope*>( |
| web_context_->GetWindowOrWorkerGlobalScope())); |
| |
| #if defined(ENABLE_DEBUGGER) |
| debug_module_.reset(new debug::backend::DebugModule( |
| nullptr /* debugger_hooks */, web_context_->global_environment(), |
| nullptr /* render_overlay */, nullptr /* resource_provider */, |
| nullptr /* window */, nullptr /* debugger_state */)); |
| #endif // ENABLE_DEBUGGER |
| |
| // 8.5. Set workerGlobalScope’s url to serviceWorker’s script url. |
| worker_global_scope_->set_url( |
| web_context_->environment_settings()->base_url()); |
| // 8.6. Set workerGlobalScope’s policy container to serviceWorker’s script |
| // resource’s policy container. |
| // 8.7. Set workerGlobalScope’s type to serviceWorker’s type. |
| // 8.8. Set workerGlobalScope’s force bypass cache for import scripts flag if |
| // forceBypassCache is true. |
| // 8.9. Create a new WorkerLocation object and associate it with |
| // workerGlobalScope. |
| // 8.10. If the run CSP initialization for a global object algorithm returns |
| // "Blocked" when executed upon workerGlobalScope, set startFailed to |
| // true and abort these steps. |
| // TODO(b/225037465): Implement CSP check. |
| // 8.11. If serviceWorker is an active worker, and there are any tasks queued |
| // in serviceWorker’s containing service worker registration’s task |
| // queues, queue them to serviceWorker’s event loop’s task queues in the |
| // same order using their original task sources. |
| // TODO(b/234787641): Queue tasks from the registration. |
| // 8.12. Let evaluationStatus be null. |
| // 8.13. If script is a classic script, then: |
| // 8.13.1. Set evaluationStatus to the result of running the classic script |
| // script. |
| |
| bool mute_errors = false; |
| bool succeeded = false; |
| std::string* content = LookupScriptResource(script_url_); |
| DCHECK(content); |
| base::SourceLocation script_location(script_url().spec(), 1, 1); |
| std::string retval = web_context_->script_runner()->Execute( |
| *content, script_location, mute_errors, &succeeded); |
| // 8.13.2. If evaluationStatus.[[Value]] is empty, this means the script was |
| // not evaluated. Set startFailed to true and abort these steps. |
| // We don't actually have access to an 'evaluationStatus' from ScriptRunner, |
| // so here we have to use the returned 'succeeded' boolean as a proxy for this |
| // step. |
| if (!succeeded) { |
| store_start_failed(true); |
| return; |
| } |
| // 8.14. Otherwise, if script is a module script, then: |
| // 8.14.1. Let evaluationPromise be the result of running the module script |
| // script, with report errors set to false. |
| // 8.14.2. Assert: evaluationPromise.[[PromiseState]] is not "pending". |
| // 8.14.3. If evaluationPromise.[[PromiseState]] is "rejected": |
| // 8.14.3.1. Set evaluationStatus to |
| // ThrowCompletion(evaluationPromise.[[PromiseResult]]). |
| // 8.14.4. Otherwise: |
| // 8.14.4.1. Set evaluationStatus to NormalCompletion(undefined). |
| // 8.15. If the script was aborted by the Terminate Service Worker algorithm, |
| // set startFailed to true and abort these steps. |
| // 8.16. Set serviceWorker’s start status to evaluationStatus. |
| start_status_.reset(new std::string(retval)); |
| // 8.17. If script’s has ever been evaluated flag is unset, then: |
| // 8.17.1. For each eventType of settingsObject’s global object's associated |
| // list of event listeners' event types: |
| // 8.17.1.1. Append eventType to workerGlobalScope’s associated service |
| // worker's set of event types to handle. |
| // 8.17.1.2. Set script’s has ever been evaluated flag. |
| // 8.18. Run the responsible event loop specified by settingsObject until it |
| // is destroyed. |
| // 8.19. Empty workerGlobalScope’s list of active timers. |
| } |
| |
| } // namespace worker |
| } // namespace cobalt |