blob: e63c421d1e76c89062757b416a835d708a8976a7 [file] [log] [blame]
// 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_->Stop();
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_.name));
web_agent_->Run(
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.
WorkerSettings* worker_settings = new WorkerSettings();
worker_settings->set_origin(
loader::Origin(containing_service_worker_registration()->scope_url()));
// 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(worker_settings);
web_context_->environment_settings()->set_creation_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(script_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