123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- // Copyright (c) 2025 Salesforce, Inc.
- // Use of this source code is governed by the MIT license that can be
- // found in the LICENSE file.
- #include "shell/browser/api/electron_api_service_worker_main.h"
- #include <string>
- #include <unordered_map>
- #include <utility>
- #include <vector>
- #include "base/logging.h"
- #include "base/no_destructor.h"
- #include "content/browser/service_worker/service_worker_context_wrapper.h" // nogncheck
- #include "content/browser/service_worker/service_worker_version.h" // nogncheck
- #include "gin/handle.h"
- #include "gin/object_template_builder.h"
- #include "services/service_manager/public/cpp/interface_provider.h"
- #include "shell/browser/api/message_port.h"
- #include "shell/browser/browser.h"
- #include "shell/browser/javascript_environment.h"
- #include "shell/common/api/api.mojom.h"
- #include "shell/common/gin_converters/blink_converter.h"
- #include "shell/common/gin_converters/gurl_converter.h"
- #include "shell/common/gin_converters/value_converter.h"
- #include "shell/common/gin_helper/dictionary.h"
- #include "shell/common/gin_helper/error_thrower.h"
- #include "shell/common/gin_helper/object_template_builder.h"
- #include "shell/common/gin_helper/promise.h"
- #include "shell/common/node_includes.h"
- #include "shell/common/v8_util.h"
- namespace {
- // Use private API to get the live version of the service worker. This will
- // exist while in starting, stopping, or stopped running status.
- content::ServiceWorkerVersion* GetLiveVersion(
- content::ServiceWorkerContext* service_worker_context,
- int64_t version_id) {
- auto* wrapper = static_cast<content::ServiceWorkerContextWrapper*>(
- service_worker_context);
- return wrapper->GetLiveVersion(version_id);
- }
- // Get a public ServiceWorkerVersionBaseInfo object directly from the service
- // worker.
- std::optional<content::ServiceWorkerVersionBaseInfo> GetLiveVersionInfo(
- content::ServiceWorkerContext* service_worker_context,
- int64_t version_id) {
- auto* version = GetLiveVersion(service_worker_context, version_id);
- if (version) {
- return version->GetInfo();
- }
- return std::nullopt;
- }
- } // namespace
- namespace electron::api {
- // ServiceWorkerKey -> ServiceWorkerMain*
- typedef std::unordered_map<ServiceWorkerKey,
- ServiceWorkerMain*,
- ServiceWorkerKey::Hasher>
- VersionIdMap;
- VersionIdMap& GetVersionIdMap() {
- static base::NoDestructor<VersionIdMap> instance;
- return *instance;
- }
- ServiceWorkerMain* FromServiceWorkerKey(const ServiceWorkerKey& key) {
- VersionIdMap& version_map = GetVersionIdMap();
- auto iter = version_map.find(key);
- auto* service_worker = iter == version_map.end() ? nullptr : iter->second;
- return service_worker;
- }
- // static
- ServiceWorkerMain* ServiceWorkerMain::FromVersionID(
- int64_t version_id,
- const content::StoragePartition* storage_partition) {
- ServiceWorkerKey key(version_id, storage_partition);
- return FromServiceWorkerKey(key);
- }
- gin::WrapperInfo ServiceWorkerMain::kWrapperInfo = {gin::kEmbedderNativeGin};
- ServiceWorkerMain::ServiceWorkerMain(content::ServiceWorkerContext* sw_context,
- int64_t version_id,
- const ServiceWorkerKey& key)
- : version_id_(version_id), key_(key), service_worker_context_(sw_context) {
- GetVersionIdMap().emplace(key_, this);
- InvalidateVersionInfo();
- }
- ServiceWorkerMain::~ServiceWorkerMain() {
- Destroy();
- }
- void ServiceWorkerMain::Destroy() {
- version_destroyed_ = true;
- InvalidateVersionInfo();
- MaybeDisconnectRemote();
- GetVersionIdMap().erase(key_);
- Unpin();
- }
- void ServiceWorkerMain::MaybeDisconnectRemote() {
- if (remote_.is_bound() &&
- (version_destroyed_ ||
- (!service_worker_context_->IsLiveStartingServiceWorker(version_id_) &&
- !service_worker_context_->IsLiveRunningServiceWorker(version_id_)))) {
- remote_.reset();
- }
- }
- mojom::ElectronRenderer* ServiceWorkerMain::GetRendererApi() {
- if (!remote_.is_bound()) {
- if (!service_worker_context_->IsLiveRunningServiceWorker(version_id_)) {
- return nullptr;
- }
- service_worker_context_->GetRemoteAssociatedInterfaces(version_id_)
- .GetInterface(&remote_);
- }
- return remote_.get();
- }
- void ServiceWorkerMain::Send(v8::Isolate* isolate,
- bool internal,
- const std::string& channel,
- v8::Local<v8::Value> args) {
- blink::CloneableMessage message;
- if (!gin::ConvertFromV8(isolate, args, &message)) {
- isolate->ThrowException(v8::Exception::Error(
- gin::StringToV8(isolate, "Failed to serialize arguments")));
- return;
- }
- auto* renderer_api_remote = GetRendererApi();
- if (!renderer_api_remote) {
- return;
- }
- renderer_api_remote->Message(internal, channel, std::move(message));
- }
- void ServiceWorkerMain::InvalidateVersionInfo() {
- if (version_info_ != nullptr) {
- version_info_.reset();
- }
- if (version_destroyed_)
- return;
- auto version_info = GetLiveVersionInfo(service_worker_context_, version_id_);
- if (version_info) {
- version_info_ =
- std::make_unique<content::ServiceWorkerVersionBaseInfo>(*version_info);
- } else {
- // When ServiceWorkerContextCore::RemoveLiveVersion is called, it posts a
- // task to notify that the service worker has stopped. At this point, the
- // live version will no longer exist.
- Destroy();
- }
- }
- void ServiceWorkerMain::OnRunningStatusChanged(
- blink::EmbeddedWorkerStatus running_status) {
- // Disconnect remote when content::ServiceWorkerHost has terminated.
- MaybeDisconnectRemote();
- InvalidateVersionInfo();
- // Redundant worker has been marked for deletion. Now that it's stopped, let's
- // destroy our wrapper.
- if (redundant_ && running_status == blink::EmbeddedWorkerStatus::kStopped) {
- Destroy();
- }
- }
- void ServiceWorkerMain::OnVersionRedundant() {
- // Redundant service workers have been either unregistered or replaced. A new
- // ServiceWorkerMain will need to be created.
- // Set internal state to mark it for deletion once it has fully stopped.
- redundant_ = true;
- }
- bool ServiceWorkerMain::IsDestroyed() const {
- return version_destroyed_;
- }
- const blink::StorageKey ServiceWorkerMain::GetStorageKey() {
- GURL scope = version_info_ ? version_info()->scope : GURL::EmptyGURL();
- return blink::StorageKey::CreateFirstParty(url::Origin::Create(scope));
- }
- gin_helper::Dictionary ServiceWorkerMain::StartExternalRequest(
- v8::Isolate* isolate,
- bool has_timeout) {
- auto details = gin_helper::Dictionary::CreateEmpty(isolate);
- if (version_destroyed_) {
- isolate->ThrowException(v8::Exception::TypeError(
- gin::StringToV8(isolate, "ServiceWorkerMain is destroyed")));
- return details;
- }
- auto request_uuid = base::Uuid::GenerateRandomV4();
- auto timeout_type =
- has_timeout
- ? content::ServiceWorkerExternalRequestTimeoutType::kDefault
- : content::ServiceWorkerExternalRequestTimeoutType::kDoesNotTimeout;
- content::ServiceWorkerExternalRequestResult start_result =
- service_worker_context_->StartingExternalRequest(
- version_id_, timeout_type, request_uuid);
- details.Set("id", request_uuid.AsLowercaseString());
- details.Set("ok",
- start_result == content::ServiceWorkerExternalRequestResult::kOk);
- return details;
- }
- void ServiceWorkerMain::FinishExternalRequest(v8::Isolate* isolate,
- std::string uuid) {
- base::Uuid request_uuid = base::Uuid::ParseLowercase(uuid);
- if (!request_uuid.is_valid()) {
- isolate->ThrowException(v8::Exception::TypeError(
- gin::StringToV8(isolate, "Invalid external request UUID")));
- return;
- }
- DCHECK(service_worker_context_);
- if (!service_worker_context_)
- return;
- content::ServiceWorkerExternalRequestResult result =
- service_worker_context_->FinishedExternalRequest(version_id_,
- request_uuid);
- std::string error;
- switch (result) {
- case content::ServiceWorkerExternalRequestResult::kOk:
- break;
- case content::ServiceWorkerExternalRequestResult::kBadRequestId:
- error = "Unknown external request UUID";
- break;
- case content::ServiceWorkerExternalRequestResult::kWorkerNotRunning:
- error = "Service worker is no longer running";
- break;
- case content::ServiceWorkerExternalRequestResult::kWorkerNotFound:
- error = "Service worker was not found";
- break;
- case content::ServiceWorkerExternalRequestResult::kNullContext:
- default:
- error = "Service worker context is unavailable and may be shutting down";
- break;
- }
- if (!error.empty()) {
- isolate->ThrowException(
- v8::Exception::TypeError(gin::StringToV8(isolate, error)));
- }
- }
- size_t ServiceWorkerMain::CountExternalRequestsForTest() {
- if (version_destroyed_)
- return 0;
- auto& storage_key = GetStorageKey();
- return service_worker_context_->CountExternalRequestsForTest(storage_key);
- }
- int64_t ServiceWorkerMain::VersionID() const {
- return version_id_;
- }
- GURL ServiceWorkerMain::ScopeURL() const {
- if (version_destroyed_)
- return GURL::EmptyGURL();
- return version_info()->scope;
- }
- // static
- gin::Handle<ServiceWorkerMain> ServiceWorkerMain::New(v8::Isolate* isolate) {
- return gin::Handle<ServiceWorkerMain>();
- }
- // static
- gin::Handle<ServiceWorkerMain> ServiceWorkerMain::From(
- v8::Isolate* isolate,
- content::ServiceWorkerContext* sw_context,
- const content::StoragePartition* storage_partition,
- int64_t version_id) {
- ServiceWorkerKey service_worker_key(version_id, storage_partition);
- auto* service_worker = FromServiceWorkerKey(service_worker_key);
- if (service_worker)
- return gin::CreateHandle(isolate, service_worker);
- // Ensure ServiceWorkerVersion exists and is not redundant (pending deletion)
- auto* live_version = GetLiveVersion(sw_context, version_id);
- if (!live_version || live_version->is_redundant()) {
- return gin::Handle<ServiceWorkerMain>();
- }
- auto handle = gin::CreateHandle(
- isolate,
- new ServiceWorkerMain(sw_context, version_id, service_worker_key));
- // Prevent garbage collection of worker until it has been deleted internally.
- handle->Pin(isolate);
- return handle;
- }
- // static
- void ServiceWorkerMain::FillObjectTemplate(
- v8::Isolate* isolate,
- v8::Local<v8::ObjectTemplate> templ) {
- gin_helper::ObjectTemplateBuilder(isolate, templ)
- .SetMethod("_send", &ServiceWorkerMain::Send)
- .SetMethod("isDestroyed", &ServiceWorkerMain::IsDestroyed)
- .SetMethod("_startExternalRequest",
- &ServiceWorkerMain::StartExternalRequest)
- .SetMethod("_finishExternalRequest",
- &ServiceWorkerMain::FinishExternalRequest)
- .SetMethod("_countExternalRequests",
- &ServiceWorkerMain::CountExternalRequestsForTest)
- .SetProperty("versionId", &ServiceWorkerMain::VersionID)
- .SetProperty("scope", &ServiceWorkerMain::ScopeURL)
- .Build();
- }
- const char* ServiceWorkerMain::GetTypeName() {
- return GetClassName();
- }
- } // namespace electron::api
- namespace {
- using electron::api::ServiceWorkerMain;
- void Initialize(v8::Local<v8::Object> exports,
- v8::Local<v8::Value> unused,
- v8::Local<v8::Context> context,
- void* priv) {
- v8::Isolate* isolate = context->GetIsolate();
- gin_helper::Dictionary dict(isolate, exports);
- dict.Set("ServiceWorkerMain", ServiceWorkerMain::GetConstructor(context));
- }
- } // namespace
- NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_service_worker_main,
- Initialize)
|