| // 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/web/error_event.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" |
| |
| namespace cobalt { |
| namespace worker { |
| |
| namespace { |
| bool PermitAnyURL(const GURL&, bool) { return true; } |
| } // namespace |
| |
| Worker::Worker(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. Let owner be the relevant owner to add given outside settings. |
| // 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. |
| web_agent_.reset(new web::Agent( |
| 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(); |
| error_.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. |
| // TODO: Actual type here should depend on derived class (e.g. dedicated, |
| // shared, service) |
| web_context_->setup_environment_settings( |
| new WorkerSettings(options_.outside_port)); |
| web_context_->environment_settings()->set_base_url(options_.url); |
| // 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(), |
| 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_.web_options.name); |
| // 11. Append owner to worker global scope's owner set. |
| // 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. |
| const GURL& url = web_context_->environment_settings()->base_url(); |
| loader::Origin origin = loader::Origin(url.GetOrigin()); |
| |
| // Todo: implement csp check (b/225037465) |
| csp::SecurityCallback csp_callback = base::Bind(&PermitAnyURL); |
| |
| loader_ = web_context_->script_loader_factory()->CreateScriptLoader( |
| url, origin, csp_callback, |
| base::Bind(&Worker::OnContentProduced, base::Unretained(this)), |
| base::Bind(&Worker::OnLoadingComplete, base::Unretained(this))); |
| } |
| |
| 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()->base_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 |
| error_ = error; |
| // 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. |
| options_.outside_settings->context() |
| ->message_loop() |
| ->task_runner() |
| ->PostTask(FROM_HERE, |
| base::BindOnce( |
| [](web::WindowOrWorkerGlobalScope* global_scope) { |
| global_scope->DispatchEvent(new web::ErrorEvent()); |
| }, |
| base::Unretained(options_.outside_settings->context() |
| ->GetWindowOrWorkerGlobalScope()))); |
| if (error_) { |
| LOG(WARNING) << "Script loading failed : " << *error; |
| } else { |
| LOG(WARNING) << "Script loading failed : no content received."; |
| } |
| // 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) { |
| LOG(WARNING) << "Script execution failed : " << retval; |
| options_.outside_settings->context() |
| ->GetWindowOrWorkerGlobalScope() |
| ->DispatchEvent(new web::Event(base::Tokens::error())); |
| } |
| |
| // 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_) { |
| DCHECK(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 (web_agent_) { |
| DCHECK(message_loop()); |
| web_agent_->WaitUntilDone(); |
| web_agent_.reset(); |
| web_context_ = nullptr; |
| } |
| } |
| |
| // 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 postMessage(any message, object transfer); |
| // -> void PostMessage(const script::ValueHandleHolder& message, |
| // script::Sequence<script::ValueHandle*> transfer) {} |
| void Worker::PostMessage(const std::string& message) { |
| DCHECK(message_loop()); |
| 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(&Worker::PostMessage, base::Unretained(this), message)); |
| return; |
| } else { |
| DCHECK(execution_ready_.IsSignaled()); |
| DCHECK(message_port()); |
| if (message_port()) message_port()->PostMessage(message); |
| } |
| } |
| |
| } // namespace worker |
| } // namespace cobalt |