blob: 28ee68eefaba61525f1d5b19e7b94272e774d8cc [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/worker.h"
#include <string>
#include <utility>
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/task_runner.h"
#include "base/threading/thread.h"
#include "cobalt/browser/user_agent_platform_info.h"
#include "cobalt/script/environment_settings.h"
#include "cobalt/script/v8c/conversion_helpers.h"
#include "cobalt/script/v8c/v8c_exception_state.h"
#include "cobalt/script/v8c/v8c_value_handle.h"
#include "cobalt/web/error_event.h"
#include "cobalt/web/error_event_init.h"
#include "cobalt/web/message_port.h"
#include "cobalt/worker/dedicated_worker_global_scope.h"
#include "cobalt/worker/worker_global_scope.h"
#include "cobalt/worker/worker_options.h"
#include "cobalt/worker/worker_settings.h"
#include "v8/include/v8.h"
namespace cobalt {
namespace worker {
Worker::Worker(const char* name, const Options& options) : options_(options) {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// 1. Let is shared be true if worker is a SharedWorker object, and false
// otherwise.
is_shared_ = options.is_shared;
// 2. Moved to below.
// 3. Let parent worker global scope be null.
// 4. If owner is a WorkerGlobalScope object (i.e., we are creating a nested
// dedicated worker), then set parent worker global scope to owner.
// 5. Let unsafeWorkerCreationTime be the unsafe shared current time.
// 6. Let agent be the result of obtaining a dedicated/shared worker agent
// given outside settings and is shared. Run the rest of these steps in
// that agent.
std::string agent_name(name);
if (!options.options.name().empty()) {
agent_name.push_back(':');
agent_name.append(options.options.name());
}
web_agent_.reset(new web::Agent(agent_name));
web_agent_->Run(options.web_options,
base::Bind(&Worker::Initialize, base::Unretained(this)),
this);
}
void Worker::WillDestroyCurrentMessageLoop() {
#if defined(ENABLE_DEBUGGER)
debug_module_.reset();
#endif // ENABLE_DEBUGGER
// Destroy members that were constructed in the worker thread.
loader_.reset();
worker_global_scope_ = nullptr;
message_port_ = nullptr;
content_.reset();
}
Worker::~Worker() { Abort(); }
void Worker::Initialize(web::Context* context) {
// Algorithm for 'run a worker':
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// 7. Let realm execution context be the result of creating a new
// JavaScript realm given agent and the following customizations:
web_context_ = context;
// . For the global object, if is shared is true, create a new
// SharedWorkerGlobalScope object. Otherwise, create a new
// DedicatedWorkerGlobalScope object.
WorkerSettings* worker_settings = new WorkerSettings(options_.outside_port);
// From algorithm to set up a worker environment settings object
// Let inherited origin be outside settings's origin.
// The origin return a unique opaque origin if worker global scope's url's
// scheme is "data", and inherited origin otherwise.
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-worker-environment-settings-object
worker_settings->set_origin(
options_.outside_context->environment_settings()->GetOrigin());
web_context_->SetupEnvironmentSettings(worker_settings);
// From algorithm for to setup up a worker environment settings object:
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-worker-environment-settings-object
// 5. Set settings object's creation URL to worker global scope's url.
web_context_->environment_settings()->set_creation_url(options_.url);
// Continue algorithm for 'run a worker'.
// 8. Let worker global scope be the global object of realm execution
// context's Realm component.
scoped_refptr<DedicatedWorkerGlobalScope> dedicated_worker_global_scope =
new DedicatedWorkerGlobalScope(
web_context_->environment_settings(), options_.global_scope_options,
/*parent_cross_origin_isolated_capability*/ false);
worker_global_scope_ = dedicated_worker_global_scope;
// 9. Set up a worker environment settings object with realm execution
// context, outside settings, and unsafeWorkerCreationTime, and let
// inside settings be the result.
web_context_->global_environment()->CreateGlobalObject(
dedicated_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<DedicatedWorkerGlobalScope>());
DCHECK_EQ(dedicated_worker_global_scope,
base::polymorphic_downcast<DedicatedWorkerGlobalScope*>(
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
// 10. Set worker global scope's name to the value of options's name member.
dedicated_worker_global_scope->set_name(options_.options.name());
web_context_->SetupFinished();
// (Moved) 2. Let owner be the relevant owner to add given outside settings.
web::WindowOrWorkerGlobalScope* owner =
options_.outside_context->GetWindowOrWorkerGlobalScope();
if (!owner) {
// There is not a running owner.
return;
}
// 11. Append owner to worker global scope's owner set.
dedicated_worker_global_scope->owner_set()->insert(owner);
// 12. If is shared is true, then:
// 1. Set worker global scope's constructor origin to outside
// settings's
// origin.
// 2. Set worker global scope's constructor url to url.
// 3. Set worker global scope's type to the value of options's type
// member.
// 4. Set worker global scope's credentials to the value of options's
// credentials member.
// 13. Let destination be "sharedworker" if is shared is true, and
// "worker" otherwise.
// 14. Obtain script
Obtain();
}
// Fetch and Run classic script
void Worker::Obtain() {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// 14. Obtain script by switching on the value of options's type member:
// - "classic"
// Fetch a classic worker script given url, outside settings,
// destination, and inside settings.
// - "module"
// Fetch a module worker script graph given url, outside settings,
// destination, the value of the credentials member of options, and
// inside settings.
// In both cases, to perform the fetch given request, perform the
// following steps if the is top-level flag is set:
// 1. Set request's reserved client to inside settings.
// 2. Fetch request, and asynchronously wait to run the remaining steps as
// part of fetch's process response for the response response.
DCHECK(web_context_);
DCHECK(web_context_->environment_settings());
const GURL& url = web_context_->environment_settings()->creation_url();
DCHECK(!url.is_empty());
loader::Origin origin = loader::Origin(url.GetOrigin());
DCHECK(options_.outside_context);
// Window thread may remove its global object before destroying worker.
if (!options_.outside_context->GetWindowOrWorkerGlobalScope()) {
return;
}
csp::SecurityCallback csp_callback = base::Bind(
&web::CspDelegate::CanLoad,
base::Unretained(options_.outside_context->GetWindowOrWorkerGlobalScope()
->csp_delegate()),
web::CspDelegate::kWorker);
loader_ = web_context_->script_loader_factory()->CreateScriptLoader(
url, origin, csp_callback,
base::Bind(&Worker::OnContentProduced, base::Unretained(this)),
base::Bind(&WorkerGlobalScope::InitializePolicyContainerCallback,
worker_global_scope_),
base::Bind(&Worker::OnLoadingComplete, base::Unretained(this)));
}
void Worker::SendErrorEventToOutside(const std::string& message) {
LOG(WARNING) << "Worker loading failed : " << message;
options_.outside_context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::Bind(
[](base::WeakPtr<web::EventTarget> event_target,
const std::string& message, const std::string& filename) {
web::ErrorEventInit error;
error.set_message(message);
error.set_filename(filename);
event_target->DispatchEvent(new web::ErrorEvent(
event_target->environment_settings(), error));
},
base::AsWeakPtr(options_.outside_event_target), message,
web_context_->environment_settings()->creation_url().spec()));
}
void Worker::OnContentProduced(const loader::Origin& last_url_origin,
std::unique_ptr<std::string> content) {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
DCHECK(content);
// 14.3. "Set worker global scope's url to response's url."
worker_global_scope_->set_url(
web_context_->environment_settings()->creation_url());
// 14.4 - 14.10 initialize worker global scope
worker_global_scope_->Initialize();
// 14.11. Asynchronously complete the perform the fetch steps with response.
content_ = std::move(content);
}
void Worker::OnLoadingComplete(const base::Optional<std::string>& error) {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// If the algorithm asynchronously completes with null or with a script
// whose error to rethrow is non-null, then:
if (error || !content_) {
// 1. Queue a global task on the DOM manipulation task source given
// worker's relevant global object to fire an event named error at
// worker.
SendErrorEventToOutside(error.value_or("No content for worker."));
// 2. Run the environment discarding steps for inside settings.
// 3. Return.
return;
}
OnReadyToExecute();
}
void Worker::OnReadyToExecute() {
DCHECK(content_);
Execute(*content_,
base::SourceLocation(
web_context_->environment_settings()->base_url().spec(), 1, 1));
content_.reset();
}
void Worker::Execute(const std::string& content,
const base::SourceLocation& script_location) {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// 15. Associate worker with worker global scope.
// Done at step 8.
// 16. Let inside port be a new MessagePort object in inside settings's Realm.
// 17. Associate inside port with worker global scope.
message_port_ = new web::MessagePort(worker_global_scope_);
// 18. Entangle outside port and inside port.
// TODO(b/226640425): Implement this when Message Ports can be entangled.
// 19. Create a new WorkerLocation object and associate it with worker global
// scope.
// 20. Closing orphan workers: Start monitoring the worker such that no sooner
// than it stops being a protected worker, and no later than it stops
// being a permissible worker, worker global scope's closing flag is set
// to true.
// 21. Suspending workers: Start monitoring the worker, such that whenever
// worker global scope's closing flag is false and the worker is a
// suspendable worker, the user agent suspends execution of script in that
// worker until such time as either the closing flag switches to true or
// the worker stops being a suspendable worker.
// 22. Set inside settings's execution ready flag.
execution_ready_.Signal();
// 23. If script is a classic script, then run the classic script script.
// Otherwise, it is a module script; run the module script script.
bool mute_errors = false;
bool succeeded = false;
std::string retval = web_context_->script_runner()->Execute(
content, script_location, mute_errors, &succeeded);
if (!succeeded) {
SendErrorEventToOutside(retval);
}
// 24. Enable outside port's port message queue.
// TODO(b/226640425): Implement this when Message Ports can be entangled.
// 25. If is shared is false, enable the port message queue of the worker's
// implicit port.
// 26. If is shared is true, then queue a global task on DOM manipulation task
// source given worker global scope to fire an event named connect at
// worker global scope, using MessageEvent, with the data attribute
// initialized to the empty string, the ports attribute initialized to a
// new frozen array containing inside port, and the source attribute
// initialized to inside port.
// 27. Enable the client message queue of the ServiceWorkerContainer object
// whose associated service worker client is worker global scope's
// relevant settings object.
// TODO(b/226640425): Implement this when Message Ports can be entangled.
// 28. Event loop: Run the responsible event loop specified by inside settings
// until it is destroyed.
}
void Worker::Abort() {
// Algorithm for 'run a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
// 29. Clear the worker global scope's map of active timers.
if (worker_global_scope_ && message_loop()) {
message_loop()->task_runner()->PostBlockingTask(
FROM_HERE, base::Bind(
[](WorkerGlobalScope* worker_global_scope) {
worker_global_scope->DestroyTimers();
},
base::Unretained(worker_global_scope_.get())));
}
// 30. Disentangle all the ports in the list of the worker's ports.
// 31. Empty worker global scope's owner set.
if (worker_global_scope_) {
worker_global_scope_->owner_set()->clear();
}
if (web_agent_) {
std::unique_ptr<web::Agent> web_agent(std::move(web_agent_));
DCHECK(web_agent);
DCHECK(!web_agent_);
web_agent->WaitUntilDone();
web_context_ = nullptr;
web_agent->Stop();
web_agent.reset();
}
}
// Algorithm for 'Terminate a worker'
// https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#terminate-a-worker
void Worker::Terminate() {
// 1. Set the worker's WorkerGlobalScope object's closing flag to true.
if (worker_global_scope_) {
worker_global_scope_->set_closing_flag(true);
}
// 2. If there are any tasks queued in the WorkerGlobalScope object's relevant
// agent's event loop's task queues, discard them without processing them.
// 3. Abort the script currently running in the worker.
Abort();
// 4. If the worker's WorkerGlobalScope object is actually a
// DedicatedWorkerGlobalScope object (i.e. the worker is a dedicated
// worker), then empty the port message queue of the port that the worker's
// implicit port is entangled with.
// TODO(b/226640425): Implement this when Message Ports can be entangled.
}
void Worker::PostMessage(const script::ValueHandleHolder& message) {
DCHECK(message_loop());
auto structured_clone = std::make_unique<script::StructuredClone>(message);
if (base::MessageLoop::current() != message_loop()) {
// Block until the worker thread is ready to execute code to handle the
// event.
execution_ready_.Wait();
message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&web::MessagePort::PostMessageSerialized,
message_port()->AsWeakPtr(),
std::move(structured_clone)));
} else {
DCHECK(execution_ready_.IsSignaled());
message_port()->PostMessageSerialized(std::move(structured_clone));
}
}
} // namespace worker
} // namespace cobalt