Browse Source

feat: ServiceWorkerMain

Samuel Maddock 5 months ago
parent
commit
f59d8d618a

+ 1 - 0
docs/README.md

@@ -128,6 +128,7 @@ These individual tutorials expand on topics discussed in the guide above.
 * [pushNotifications](api/push-notifications.md)
 * [safeStorage](api/safe-storage.md)
 * [screen](api/screen.md)
+* [ServiceWorkerMain](api/service-worker-main.md)
 * [session](api/session.md)
 * [ShareMenu](api/share-menu.md)
 * [systemPreferences](api/system-preferences.md)

+ 75 - 0
docs/api/service-worker-main.md

@@ -0,0 +1,75 @@
+# ServiceWorkerMain
+
+> An instance of a Service Worker representing a version of a script for a given scope.
+
+Process: [Main](../glossary.md#main-process)
+
+## Class: ServiceWorkerMain
+
+Process: [Main](../glossary.md#main-process)<br />
+_This class is not exported from the `'electron'` module. It is only available as a return value of other methods in the Electron API._
+
+### Instance Methods
+
+#### `serviceWorker.isDestroyed()` _Experimental_
+
+Returns `boolean` - Whether the service worker has been destroyed.
+
+#### `serviceWorker.send(channel, ...args)` _Experimental_
+
+- `channel` string
+- `...args` any[]
+
+Send an asynchronous message to the service worker process via `channel`, along with
+arguments. Arguments will be serialized with the [Structured Clone Algorithm][SCA],
+just like [`postMessage`][], so prototype chains will not be included.
+Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
+
+The service worker process can handle the message by listening to `channel` with the
+[`ipcRenderer`](ipc-renderer.md) module.
+
+#### `serviceWorker.startTask()` _Experimental_
+
+Returns `Object`:
+
+- `end` Function - Method to call when the task has ended. If never called, the service won't terminate while otherwise idle.
+
+Initiate a task to keep the service worker alive until ended.
+
+```js
+const { session } = require('electron')
+const { serviceWorkers } = session.defaultSession
+
+async function fetchData () {}
+
+const versionId = 0
+const serviceWorker = serviceWorkers.getWorkerFromVersionID(versionId)
+
+serviceWorker?.ipc.handle('request-data', async () => {
+  // Keep service worker alive while fetching data
+  const task = serviceWorker.startTask()
+  try {
+    return await fetchData()
+  } finally {
+    // Mark task as ended to allow service worker to terminate when idle.
+    task.end()
+  }
+})
+```
+
+### Instance Properties
+
+#### `serviceWorker.ipc` _Readonly_ _Experimental_
+
+An [`IpcMainServiceWorker`](ipc-main-service-worker.md) instance scoped to the service worker.
+
+#### `serviceWorker.scope` _Readonly_ _Experimental_
+
+A `string` representing the scope URL of the service worker.
+
+#### `serviceWorker.versionId` _Readonly_ _Experimental_
+
+A `number` representing the ID of the specific version of the service worker script in its scope.
+
+[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
+[`postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

+ 58 - 2
docs/api/service-workers.md

@@ -56,6 +56,17 @@ Returns:
 
 Emitted when a service worker has been registered. Can occur after a call to [`navigator.serviceWorker.register('/sw.js')`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register) successfully resolves or when a Chrome extension is loaded.
 
+#### Event: 'running-status-changed' _Experimental_
+
+Returns:
+
+* `details` Event\<\>
+  * `versionId` number - ID of the updated service worker version
+  * `runningStatus` string - Running status.
+    Possible values include `starting`, `running`, `stopping`, or `stopped`.
+
+Emitted when a service worker's running status has changed.
+
 ### Instance Methods
 
 The following methods are available on instances of `ServiceWorkers`:
@@ -64,10 +75,55 @@ The following methods are available on instances of `ServiceWorkers`:
 
 Returns `Record<number, ServiceWorkerInfo>` - A [ServiceWorkerInfo](structures/service-worker-info.md) object where the keys are the service worker version ID and the values are the information about that service worker.
 
-#### `serviceWorkers.getFromVersionID(versionId)`
+#### `serviceWorkers.getInfoFromVersionID(versionId)`
+
+* `versionId` number - ID of the service worker version
 
-* `versionId` number
+Returns [`ServiceWorkerInfo`](structures/service-worker-info.md) - Information about this service worker
+
+If the service worker does not exist or is not running this method will throw an exception.
+
+#### `serviceWorkers.getFromVersionID(versionId)` _Deprecated_
+
+* `versionId` number - ID of the service worker version
 
 Returns [`ServiceWorkerInfo`](structures/service-worker-info.md) - Information about this service worker
 
 If the service worker does not exist or is not running this method will throw an exception.
+
+**Deprecated:** Use the new `serviceWorkers.getInfoFromVersionID` API.
+
+#### `serviceWorkers.getWorkerFromVersionID(versionId)` _Experimental_
+
+* `versionId` number - ID of the service worker version
+
+Returns [`ServiceWorkerMain | undefined`](service-worker-main.md) - Instance of the service worker associated with the given version ID.
+
+#### `serviceWorkers.startWorkerForScope(scope)` _Experimental_
+
+* `scope` string - The scope of the service worker to start.
+
+Returns `Promise<ServiceWorkerMain>` - Resolves with the service worker when it's started.
+
+Starts the service worker or does nothing if already running.
+
+```js
+const { app, session } = require('electron')
+const { serviceWorkers } = session.defaultSession
+
+// Collect service workers scopes
+const workerScopes = Object.values(serviceWorkers.getAllRunning()).map((info) => info.scope)
+
+app.on('browser-window-created', async (event, window) => {
+  for (const scope of workerScopes) {
+    try {
+      // Ensure worker is started and send message
+      const serviceWorker = await serviceWorkers.startWorkerForScope(scope)
+      serviceWorker.send('window-created', { windowId: window.id })
+    } catch (error) {
+      console.error(`Failed to start service worker for ${scope}`)
+      console.error(error)
+    }
+  }
+})
+```

+ 1 - 0
docs/api/structures/service-worker-info.md

@@ -3,3 +3,4 @@
 * `scriptUrl` string - The full URL to the script that this service worker runs
 * `scope` string - The base URL that this service worker is active for.
 * `renderProcessId` number - The virtual ID of the process that this service worker is running in.  This is not an OS level PID.  This aligns with the ID set used for `webContents.getProcessId()`.
+* `versionId` number - ID of the service worker version

+ 15 - 0
docs/breaking-changes.md

@@ -33,6 +33,21 @@ session.registerPreloadScript({
 })
 ```
 
+### Deprecated: `getFromVersionID` on `session.serviceWorkers`
+
+The `session.serviceWorkers.fromVersionID(versionId)` API has been deprecated
+in favor of `session.serviceWorkers.getInfoFromVersionID(versionId)`. This was
+changed to make it more clear which object is returned with the introduction
+of the `session.serviceWorkers.getWorkerFromVersionID(versionId)` API.
+
+```js
+// Deprecated
+session.serviceWorkers.fromVersionID(versionId)
+
+// Replace with
+session.serviceWorkers.getInfoFromVersionID(versionId)
+```
+
 ## Planned Breaking API Changes (34.0)
 
 ### Deprecated: `level`, `message`, `line`, and `sourceId` arguments in `console-message` event on `WebContents`

+ 2 - 0
filenames.auto.gni

@@ -45,6 +45,7 @@ auto_filenames = {
     "docs/api/push-notifications.md",
     "docs/api/safe-storage.md",
     "docs/api/screen.md",
+"docs/api/service-worker-main.md",
     "docs/api/service-workers.md",
     "docs/api/session.md",
     "docs/api/share-menu.md",
@@ -242,6 +243,7 @@ auto_filenames = {
     "lib/browser/api/push-notifications.ts",
     "lib/browser/api/safe-storage.ts",
     "lib/browser/api/screen.ts",
+    "lib/browser/api/service-worker-main.ts",
     "lib/browser/api/session.ts",
     "lib/browser/api/share-menu.ts",
     "lib/browser/api/system-preferences.ts",

+ 4 - 0
filenames.gni

@@ -304,6 +304,8 @@ filenames = {
     "shell/browser/api/electron_api_screen.h",
     "shell/browser/api/electron_api_service_worker_context.cc",
     "shell/browser/api/electron_api_service_worker_context.h",
+"shell/browser/api/electron_api_service_worker_main.cc",
+    "shell/browser/api/electron_api_service_worker_main.h",
     "shell/browser/api/electron_api_session.cc",
     "shell/browser/api/electron_api_session.h",
     "shell/browser/api/electron_api_system_preferences.cc",
@@ -622,6 +624,8 @@ filenames = {
     "shell/common/gin_converters/osr_converter.cc",
     "shell/common/gin_converters/osr_converter.h",
     "shell/common/gin_converters/serial_port_info_converter.h",
+"shell/common/gin_converters/service_worker_converter.cc",
+    "shell/common/gin_converters/service_worker_converter.h",
     "shell/common/gin_converters/std_converter.h",
     "shell/common/gin_converters/time_converter.cc",
     "shell/common/gin_converters/time_converter.h",

+ 1 - 0
lib/browser/api/module-list.ts

@@ -29,6 +29,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
   { name: 'protocol', loader: () => require('./protocol') },
   { name: 'safeStorage', loader: () => require('./safe-storage') },
   { name: 'screen', loader: () => require('./screen') },
+  { name: 'ServiceWorkerMain', loader: () => require('./service-worker-main') },
   { name: 'session', loader: () => require('./session') },
   { name: 'ShareMenu', loader: () => require('./share-menu') },
   { name: 'systemPreferences', loader: () => require('./system-preferences') },

+ 39 - 0
lib/browser/api/service-worker-main.ts

@@ -0,0 +1,39 @@
+import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
+
+const { ServiceWorkerMain } = process._linkedBinding('electron_browser_service_worker_main');
+
+Object.defineProperty(ServiceWorkerMain.prototype, 'ipc', {
+  get () {
+    const ipc = new IpcMainImpl();
+    Object.defineProperty(this, 'ipc', { value: ipc });
+    return ipc;
+  }
+});
+
+ServiceWorkerMain.prototype.send = function (channel, ...args) {
+  if (typeof channel !== 'string') {
+    throw new TypeError('Missing required channel argument');
+  }
+
+  try {
+    return this._send(false /* internal */, channel, args);
+  } catch (e) {
+    console.error('Error sending from ServiceWorkerMain: ', e);
+  }
+};
+
+ServiceWorkerMain.prototype.startTask = function () {
+  // TODO(samuelmaddock): maybe make timeout configurable in the future
+  const hasTimeout = false;
+  const { id, ok } = this._startExternalRequest(hasTimeout);
+
+  if (!ok) {
+    throw new Error('Unable to start service worker task.');
+  }
+
+  return {
+    end: () => this._finishExternalRequest(id)
+  };
+};
+
+module.exports = ServiceWorkerMain;

+ 3 - 0
lib/browser/init.ts

@@ -146,6 +146,9 @@ require('@electron/internal/browser/devtools');
 // Load protocol module to ensure it is populated on app ready
 require('@electron/internal/browser/api/protocol');
 
+// Load service-worker-main module to ensure it is populated on app ready
+require('@electron/internal/browser/api/service-worker-main');
+
 // Load web-contents module to ensure it is populated on app ready
 require('@electron/internal/browser/api/web-contents');
 

+ 144 - 5
shell/browser/api/electron_api_service_worker_context.cc

@@ -13,11 +13,18 @@
 #include "gin/data_object_builder.h"
 #include "gin/handle.h"
 #include "gin/object_template_builder.h"
+#include "shell/browser/api/electron_api_service_worker_main.h"
 #include "shell/browser/electron_browser_context.h"
 #include "shell/browser/javascript_environment.h"
 #include "shell/common/gin_converters/gurl_converter.h"
+#include "shell/common/gin_converters/service_worker_converter.h"
 #include "shell/common/gin_converters/value_converter.h"
 #include "shell/common/gin_helper/dictionary.h"
+#include "shell/common/gin_helper/promise.h"
+#include "shell/common/node_util.h"
+
+using ServiceWorkerStatus =
+    content::ServiceWorkerRunningInfo::ServiceWorkerVersionStatus;
 
 namespace electron::api {
 
@@ -72,8 +79,8 @@ gin::WrapperInfo ServiceWorkerContext::kWrapperInfo = {gin::kEmbedderNativeGin};
 ServiceWorkerContext::ServiceWorkerContext(
     v8::Isolate* isolate,
     ElectronBrowserContext* browser_context) {
-  service_worker_context_ =
-      browser_context->GetDefaultStoragePartition()->GetServiceWorkerContext();
+  storage_partition_ = browser_context->GetDefaultStoragePartition();
+  service_worker_context_ = storage_partition_->GetServiceWorkerContext();
   service_worker_context_->AddObserver(this);
 }
 
@@ -81,6 +88,23 @@ ServiceWorkerContext::~ServiceWorkerContext() {
   service_worker_context_->RemoveObserver(this);
 }
 
+void ServiceWorkerContext::OnRunningStatusChanged(
+    int64_t version_id,
+    blink::EmbeddedWorkerStatus running_status) {
+  ServiceWorkerMain* worker =
+      ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
+  if (worker)
+    worker->OnRunningStatusChanged();
+
+  v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
+  v8::HandleScope scope(isolate);
+  EmitWithoutEvent("running-status-changed",
+                   gin::DataObjectBuilder(isolate)
+                       .Set("versionId", version_id)
+                       .Set("runningStatus", running_status)
+                       .Build());
+}
+
 void ServiceWorkerContext::OnReportConsoleMessage(
     int64_t version_id,
     const GURL& scope,
@@ -105,6 +129,32 @@ void ServiceWorkerContext::OnRegistrationCompleted(const GURL& scope) {
        gin::DataObjectBuilder(isolate).Set("scope", scope).Build());
 }
 
+void ServiceWorkerContext::OnVersionRedundant(int64_t version_id,
+                                              const GURL& scope) {
+  ServiceWorkerMain* worker =
+      ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
+  if (worker)
+    worker->OnVersionRedundant();
+}
+
+void ServiceWorkerContext::OnVersionStartingRunning(int64_t version_id) {
+  OnRunningStatusChanged(version_id, blink::EmbeddedWorkerStatus::kStarting);
+}
+
+void ServiceWorkerContext::OnVersionStartedRunning(
+    int64_t version_id,
+    const content::ServiceWorkerRunningInfo& running_info) {
+  OnRunningStatusChanged(version_id, blink::EmbeddedWorkerStatus::kRunning);
+}
+
+void ServiceWorkerContext::OnVersionStoppingRunning(int64_t version_id) {
+  OnRunningStatusChanged(version_id, blink::EmbeddedWorkerStatus::kStopping);
+}
+
+void ServiceWorkerContext::OnVersionStoppedRunning(int64_t version_id) {
+  OnRunningStatusChanged(version_id, blink::EmbeddedWorkerStatus::kStopped);
+}
+
 void ServiceWorkerContext::OnDestruct(content::ServiceWorkerContext* context) {
   if (context == service_worker_context_) {
     delete this;
@@ -124,7 +174,7 @@ v8::Local<v8::Value> ServiceWorkerContext::GetAllRunningWorkerInfo(
   return builder.Build();
 }
 
-v8::Local<v8::Value> ServiceWorkerContext::GetWorkerInfoFromID(
+v8::Local<v8::Value> ServiceWorkerContext::GetInfoFromVersionID(
     gin_helper::ErrorThrower thrower,
     int64_t version_id) {
   const base::flat_map<int64_t, content::ServiceWorkerRunningInfo>& info_map =
@@ -138,6 +188,87 @@ v8::Local<v8::Value> ServiceWorkerContext::GetWorkerInfoFromID(
                                         std::move(iter->second));
 }
 
+v8::Local<v8::Value> ServiceWorkerContext::GetFromVersionID(
+    gin_helper::ErrorThrower thrower,
+    int64_t version_id) {
+  util::EmitWarning(thrower.isolate(),
+                    "The session.serviceWorkers.getFromVersionID API is "
+                    "deprecated, use "
+                    "session.serviceWorkers.getInfoFromVersionID instead.",
+                    "ServiceWorkersDeprecateGetFromVersionID");
+
+  return GetInfoFromVersionID(thrower, version_id);
+}
+
+v8::Local<v8::Value> ServiceWorkerContext::GetWorkerFromVersionID(
+    v8::Isolate* isolate,
+    int64_t version_id) {
+  return ServiceWorkerMain::From(isolate, service_worker_context_,
+                                 storage_partition_, version_id)
+      .ToV8();
+}
+
+gin::Handle<ServiceWorkerMain>
+ServiceWorkerContext::GetWorkerFromVersionIDIfExists(v8::Isolate* isolate,
+                                                     int64_t version_id) {
+  ServiceWorkerMain* worker =
+      ServiceWorkerMain::FromVersionID(version_id, storage_partition_);
+  if (!worker)
+    return gin::Handle<ServiceWorkerMain>();
+  return gin::CreateHandle(isolate, worker);
+}
+
+v8::Local<v8::Promise> ServiceWorkerContext::StartWorkerForScope(
+    v8::Isolate* isolate,
+    GURL scope) {
+  auto shared_promise =
+      std::make_shared<gin_helper::Promise<v8::Local<v8::Value>>>(isolate);
+  v8::Local<v8::Promise> handle = shared_promise->GetHandle();
+
+  blink::StorageKey storage_key =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(scope));
+  service_worker_context_->StartWorkerForScope(
+      scope, storage_key,
+      base::BindOnce(&ServiceWorkerContext::DidStartWorkerForScope,
+                     weak_ptr_factory_.GetWeakPtr(), shared_promise),
+      base::BindOnce(&ServiceWorkerContext::DidFailToStartWorkerForScope,
+                     weak_ptr_factory_.GetWeakPtr(), shared_promise));
+
+  return handle;
+}
+
+void ServiceWorkerContext::DidStartWorkerForScope(
+    std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>> shared_promise,
+    int64_t version_id,
+    int process_id,
+    int thread_id) {
+  v8::Isolate* isolate = shared_promise->isolate();
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Value> service_worker_main =
+      GetWorkerFromVersionID(isolate, version_id);
+  shared_promise->Resolve(service_worker_main);
+  shared_promise.reset();
+}
+
+void ServiceWorkerContext::DidFailToStartWorkerForScope(
+    std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>> shared_promise,
+    blink::ServiceWorkerStatusCode status_code) {
+  shared_promise->RejectWithErrorMessage("Failed to start service worker.");
+  shared_promise.reset();
+}
+
+v8::Local<v8::Promise> ServiceWorkerContext::StopAllWorkers(
+    v8::Isolate* isolate) {
+  auto promise = gin_helper::Promise<void>(isolate);
+  v8::Local<v8::Promise> handle = promise.GetHandle();
+
+  service_worker_context_->StopAllServiceWorkers(base::BindOnce(
+      [](gin_helper::Promise<void> promise) { promise.Resolve(); },
+      std::move(promise)));
+
+  return handle;
+}
+
 // static
 gin::Handle<ServiceWorkerContext> ServiceWorkerContext::Create(
     v8::Isolate* isolate,
@@ -153,8 +284,16 @@ gin::ObjectTemplateBuilder ServiceWorkerContext::GetObjectTemplateBuilder(
              ServiceWorkerContext>::GetObjectTemplateBuilder(isolate)
       .SetMethod("getAllRunning",
                  &ServiceWorkerContext::GetAllRunningWorkerInfo)
-      .SetMethod("getFromVersionID",
-                 &ServiceWorkerContext::GetWorkerInfoFromID);
+      .SetMethod("getFromVersionID", &ServiceWorkerContext::GetFromVersionID)
+      .SetMethod("getInfoFromVersionID",
+                 &ServiceWorkerContext::GetInfoFromVersionID)
+      .SetMethod("getWorkerFromVersionID",
+                 &ServiceWorkerContext::GetWorkerFromVersionID)
+      .SetMethod("_getWorkerFromVersionIDIfExists",
+                 &ServiceWorkerContext::GetWorkerFromVersionIDIfExists)
+      .SetMethod("startWorkerForScope",
+                 &ServiceWorkerContext::StartWorkerForScope)
+      .SetMethod("_stopAllWorkers", &ServiceWorkerContext::StopAllWorkers);
 }
 
 const char* ServiceWorkerContext::GetTypeName() {

+ 46 - 2
shell/browser/api/electron_api_service_worker_context.h

@@ -10,18 +10,30 @@
 #include "content/public/browser/service_worker_context_observer.h"
 #include "gin/wrappable.h"
 #include "shell/browser/event_emitter_mixin.h"
+#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
+
+namespace content {
+class StoragePartition;
+}
 
 namespace gin {
 template <typename T>
 class Handle;
 }  // namespace gin
 
+namespace gin_helper {
+template <typename T>
+class Promise;
+}  // namespace gin_helper
+
 namespace electron {
 
 class ElectronBrowserContext;
 
 namespace api {
 
+class ServiceWorkerMain;
+
 class ServiceWorkerContext final
     : public gin::Wrappable<ServiceWorkerContext>,
       public gin_helper::EventEmitterMixin<ServiceWorkerContext>,
@@ -32,14 +44,39 @@ class ServiceWorkerContext final
       ElectronBrowserContext* browser_context);
 
   v8::Local<v8::Value> GetAllRunningWorkerInfo(v8::Isolate* isolate);
-  v8::Local<v8::Value> GetWorkerInfoFromID(gin_helper::ErrorThrower thrower,
-                                           int64_t version_id);
+  v8::Local<v8::Value> GetInfoFromVersionID(gin_helper::ErrorThrower thrower,
+                                            int64_t version_id);
+  v8::Local<v8::Value> GetFromVersionID(gin_helper::ErrorThrower thrower,
+                                        int64_t version_id);
+  v8::Local<v8::Value> GetWorkerFromVersionID(v8::Isolate* isolate,
+                                              int64_t version_id);
+  gin::Handle<ServiceWorkerMain> GetWorkerFromVersionIDIfExists(
+      v8::Isolate* isolate,
+      int64_t version_id);
+  v8::Local<v8::Promise> StartWorkerForScope(v8::Isolate* isolate, GURL scope);
+  void DidStartWorkerForScope(
+      std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>> shared_promise,
+      int64_t version_id,
+      int process_id,
+      int thread_id);
+  void DidFailToStartWorkerForScope(
+      std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>> shared_promise,
+      blink::ServiceWorkerStatusCode status_code);
+  void StopWorkersForScope(GURL scope);
+  v8::Local<v8::Promise> StopAllWorkers(v8::Isolate* isolate);
 
   // content::ServiceWorkerContextObserver
   void OnReportConsoleMessage(int64_t version_id,
                               const GURL& scope,
                               const content::ConsoleMessage& message) override;
   void OnRegistrationCompleted(const GURL& scope) override;
+  void OnVersionStartingRunning(int64_t version_id) override;
+  void OnVersionStartedRunning(
+      int64_t version_id,
+      const content::ServiceWorkerRunningInfo& running_info) override;
+  void OnVersionStoppingRunning(int64_t version_id) override;
+  void OnVersionStoppedRunning(int64_t version_id) override;
+  void OnVersionRedundant(int64_t version_id, const GURL& scope) override;
   void OnDestruct(content::ServiceWorkerContext* context) override;
 
   // gin::Wrappable
@@ -58,8 +95,15 @@ class ServiceWorkerContext final
   ~ServiceWorkerContext() override;
 
  private:
+  void OnRunningStatusChanged(int64_t version_id,
+                              blink::EmbeddedWorkerStatus running_status);
+
   raw_ptr<content::ServiceWorkerContext> service_worker_context_;
 
+  // Service worker registration and versions are unique to a storage partition.
+  // Keep a reference to the storage partition to be used for lookups.
+  raw_ptr<content::StoragePartition> storage_partition_;
+
   base::WeakPtrFactory<ServiceWorkerContext> weak_ptr_factory_{this};
 };
 

+ 319 - 0
shell/browser/api/electron_api_service_worker_main.cc

@@ -0,0 +1,319 @@
+// 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 "electron/shell/common/api/api.mojom.h"
+#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/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();
+  GetVersionIdMap().erase(key_);
+  Unpin();
+}
+
+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() {
+  InvalidateVersionInfo();
+
+  // Disconnect remote when content::ServiceWorkerHost has terminated.
+  if (remote_.is_bound() &&
+      !service_worker_context_->IsLiveStartingServiceWorker(version_id_) &&
+      !service_worker_context_->IsLiveRunningServiceWorker(version_id_)) {
+    remote_.reset();
+  }
+}
+
+void ServiceWorkerMain::OnVersionRedundant() {
+  // Redundant service workers have become either unregistered or replaced.
+  // A new ServiceWorkerMain will need to be created.
+  Destroy();
+}
+
+bool ServiceWorkerMain::IsDestroyed() const {
+  return version_destroyed_;
+}
+
+const blink::StorageKey ServiceWorkerMain::GetStorageKey() {
+  GURL scope = version_info()->scope;
+  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) {
+  if (version_destroyed_) {
+    isolate->ThrowException(v8::Exception::TypeError(
+        gin::StringToV8(isolate, "ServiceWorkerMain is destroyed")));
+    return;
+  }
+
+  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;
+  }
+
+  service_worker_context_->FinishedExternalRequest(version_id_, request_uuid);
+}
+
+size_t ServiceWorkerMain::CountExternalRequests() {
+  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::CountExternalRequests)
+      .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)

+ 174 - 0
shell/browser/api/electron_api_service_worker_main.h

@@ -0,0 +1,174 @@
+// Copyright (c) 2025 Salesforce, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_MAIN_H_
+#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_MAIN_H_
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process.h"
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/service_worker_version_base_info.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "shell/browser/event_emitter_mixin.h"
+#include "shell/common/api/api.mojom.h"
+#include "shell/common/gin_helper/constructible.h"
+#include "shell/common/gin_helper/pinnable.h"
+#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
+
+class GURL;
+
+namespace content {
+class StoragePartition;
+}
+
+namespace gin {
+class Arguments;
+}  // namespace gin
+
+namespace gin_helper {
+class Dictionary;
+template <typename T>
+class Handle;
+template <typename T>
+class Promise;
+}  // namespace gin_helper
+
+namespace electron::api {
+
+// Key to uniquely identify a ServiceWorkerMain by its Version ID within the
+// associated StoragePartition.
+struct ServiceWorkerKey {
+  int64_t version_id;
+  raw_ptr<const content::StoragePartition> storage_partition;
+
+  ServiceWorkerKey(int64_t id, const content::StoragePartition* partition)
+      : version_id(id), storage_partition(partition) {}
+
+  bool operator<(const ServiceWorkerKey& other) const {
+    return std::tie(version_id, storage_partition) <
+           std::tie(other.version_id, other.storage_partition);
+  }
+
+  bool operator==(const ServiceWorkerKey& other) const {
+    return version_id == other.version_id &&
+           storage_partition == other.storage_partition;
+  }
+
+  struct Hasher {
+    std::size_t operator()(const ServiceWorkerKey& key) const {
+      return std::hash<const content::StoragePartition*>()(
+                 key.storage_partition) ^
+             std::hash<int64_t>()(key.version_id);
+    }
+  };
+};
+
+// Creates a wrapper to align with the lifecycle of the non-public
+// content::ServiceWorkerVersion. Object instances are pinned for the lifetime
+// of the underlying SW such that registered IPC handlers continue to dispatch.
+//
+// Instances are uniquely identified by pairing their version ID and the
+// StoragePartition in which they're registered. In Electron, this is always
+// the default StoragePartition for the associated BrowserContext.
+class ServiceWorkerMain final
+    : public gin::Wrappable<ServiceWorkerMain>,
+      public gin_helper::EventEmitterMixin<ServiceWorkerMain>,
+      public gin_helper::Pinnable<ServiceWorkerMain>,
+      public gin_helper::Constructible<ServiceWorkerMain> {
+ public:
+  // Create a new ServiceWorkerMain and return the V8 wrapper of it.
+  static gin::Handle<ServiceWorkerMain> New(v8::Isolate* isolate);
+
+  static gin::Handle<ServiceWorkerMain> From(
+      v8::Isolate* isolate,
+      content::ServiceWorkerContext* sw_context,
+      const content::StoragePartition* storage_partition,
+      int64_t version_id);
+  static ServiceWorkerMain* FromVersionID(
+      int64_t version_id,
+      const content::StoragePartition* storage_partition);
+
+  // gin_helper::Constructible
+  static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
+  static const char* GetClassName() { return "ServiceWorkerMain"; }
+
+  // gin::Wrappable
+  static gin::WrapperInfo kWrapperInfo;
+  const char* GetTypeName() override;
+
+  // disable copy
+  ServiceWorkerMain(const ServiceWorkerMain&) = delete;
+  ServiceWorkerMain& operator=(const ServiceWorkerMain&) = delete;
+
+  void OnRunningStatusChanged();
+  void OnVersionRedundant();
+
+ protected:
+  explicit ServiceWorkerMain(content::ServiceWorkerContext* sw_context,
+                             int64_t version_id,
+                             const ServiceWorkerKey& key);
+  ~ServiceWorkerMain() override;
+
+ private:
+  void Destroy();
+  const blink::StorageKey GetStorageKey();
+
+  // Increments external requests for the service worker to keep it alive.
+  gin_helper::Dictionary StartExternalRequest(v8::Isolate* isolate,
+                                              bool has_timeout);
+  void FinishExternalRequest(v8::Isolate* isolate, std::string uuid);
+  size_t CountExternalRequests();
+
+  // Get or create a Mojo connection to the renderer process.
+  mojom::ElectronRenderer* GetRendererApi();
+
+  // Send a message to the renderer process.
+  void Send(v8::Isolate* isolate,
+            bool internal,
+            const std::string& channel,
+            v8::Local<v8::Value> args);
+
+  void InvalidateVersionInfo();
+  const content::ServiceWorkerVersionBaseInfo* version_info() const {
+    return version_info_.get();
+  }
+
+  bool IsDestroyed() const;
+
+  int64_t VersionID() const;
+  GURL ScopeURL() const;
+
+  // Version ID unique only to the StoragePartition.
+  int64_t version_id_;
+
+  // Unique identifier pairing the Version ID and StoragePartition.
+  ServiceWorkerKey key_;
+
+  // Whether the Service Worker version has been destroyed.
+  bool version_destroyed_ = false;
+
+  // Store copy of version info so it's accessible when not running.
+  std::unique_ptr<content::ServiceWorkerVersionBaseInfo> version_info_;
+
+  raw_ptr<content::ServiceWorkerContext> service_worker_context_;
+  mojo::AssociatedRemote<mojom::ElectronRenderer> remote_;
+
+  std::unique_ptr<gin_helper::Promise<void>> start_worker_promise_;
+
+  base::WeakPtrFactory<ServiceWorkerMain> weak_factory_{this};
+};
+
+}  // namespace electron::api
+
+#endif  // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_SERVICE_WORKER_MAIN_H_

+ 25 - 0
shell/common/gin_converters/service_worker_converter.cc

@@ -0,0 +1,25 @@
+// 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/common/gin_converters/service_worker_converter.h"
+
+#include "base/containers/fixed_flat_map.h"
+
+namespace gin {
+
+// static
+v8::Local<v8::Value> Converter<blink::EmbeddedWorkerStatus>::ToV8(
+    v8::Isolate* isolate,
+    const blink::EmbeddedWorkerStatus& val) {
+  static constexpr auto Lookup =
+      base::MakeFixedFlatMap<blink::EmbeddedWorkerStatus, std::string_view>({
+          {blink::EmbeddedWorkerStatus::kStarting, "starting"},
+          {blink::EmbeddedWorkerStatus::kRunning, "running"},
+          {blink::EmbeddedWorkerStatus::kStopping, "stopping"},
+          {blink::EmbeddedWorkerStatus::kStopped, "stopped"},
+      });
+  return StringToV8(isolate, Lookup.at(val));
+}
+
+}  // namespace gin

+ 21 - 0
shell/common/gin_converters/service_worker_converter.h

@@ -0,0 +1,21 @@
+// Copyright (c) 2025 Salesforce, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_SERVICE_WORKER_CONVERTER_H_
+#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_SERVICE_WORKER_CONVERTER_H_
+
+#include "gin/converter.h"
+#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
+
+namespace gin {
+
+template <>
+struct Converter<blink::EmbeddedWorkerStatus> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const blink::EmbeddedWorkerStatus& val);
+};
+
+}  // namespace gin
+
+#endif  // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_SERVICE_WORKER_CONVERTER_H_

+ 34 - 33
shell/common/node_bindings.cc

@@ -49,39 +49,40 @@
 #include "shell/common/crash_keys.h"
 #endif
 
-#define ELECTRON_BROWSER_BINDINGS(V)     \
-  V(electron_browser_app)                \
-  V(electron_browser_auto_updater)       \
-  V(electron_browser_content_tracing)    \
-  V(electron_browser_crash_reporter)     \
-  V(electron_browser_desktop_capturer)   \
-  V(electron_browser_dialog)             \
-  V(electron_browser_event_emitter)      \
-  V(electron_browser_global_shortcut)    \
-  V(electron_browser_image_view)         \
-  V(electron_browser_in_app_purchase)    \
-  V(electron_browser_menu)               \
-  V(electron_browser_message_port)       \
-  V(electron_browser_native_theme)       \
-  V(electron_browser_notification)       \
-  V(electron_browser_power_monitor)      \
-  V(electron_browser_power_save_blocker) \
-  V(electron_browser_protocol)           \
-  V(electron_browser_printing)           \
-  V(electron_browser_push_notifications) \
-  V(electron_browser_safe_storage)       \
-  V(electron_browser_session)            \
-  V(electron_browser_screen)             \
-  V(electron_browser_system_preferences) \
-  V(electron_browser_base_window)        \
-  V(electron_browser_tray)               \
-  V(electron_browser_utility_process)    \
-  V(electron_browser_view)               \
-  V(electron_browser_web_contents)       \
-  V(electron_browser_web_contents_view)  \
-  V(electron_browser_web_frame_main)     \
-  V(electron_browser_web_view_manager)   \
-  V(electron_browser_window)             \
+#define ELECTRON_BROWSER_BINDINGS(V)      \
+  V(electron_browser_app)                 \
+  V(electron_browser_auto_updater)        \
+  V(electron_browser_content_tracing)     \
+  V(electron_browser_crash_reporter)      \
+  V(electron_browser_desktop_capturer)    \
+  V(electron_browser_dialog)              \
+  V(electron_browser_event_emitter)       \
+  V(electron_browser_global_shortcut)     \
+  V(electron_browser_image_view)          \
+  V(electron_browser_in_app_purchase)     \
+  V(electron_browser_menu)                \
+  V(electron_browser_message_port)        \
+  V(electron_browser_native_theme)        \
+  V(electron_browser_notification)        \
+  V(electron_browser_power_monitor)       \
+  V(electron_browser_power_save_blocker)  \
+  V(electron_browser_protocol)            \
+  V(electron_browser_printing)            \
+  V(electron_browser_push_notifications)  \
+  V(electron_browser_safe_storage)        \
+  V(electron_browser_service_worker_main) \
+  V(electron_browser_session)             \
+  V(electron_browser_screen)              \
+  V(electron_browser_system_preferences)  \
+  V(electron_browser_base_window)         \
+  V(electron_browser_tray)                \
+  V(electron_browser_utility_process)     \
+  V(electron_browser_view)                \
+  V(electron_browser_web_contents)        \
+  V(electron_browser_web_contents_view)   \
+  V(electron_browser_web_frame_main)      \
+  V(electron_browser_web_view_manager)    \
+  V(electron_browser_window)              \
   V(electron_common_net)
 
 #define ELECTRON_COMMON_BINDINGS(V)   \

+ 5 - 0
typings/internal-ambient.d.ts

@@ -111,6 +111,10 @@ declare namespace NodeJS {
     setListeningForShutdown(listening: boolean): void;
   }
 
+  interface ServiceWorkerMainBinding {
+    ServiceWorkerMain: typeof Electron.ServiceWorkerMain;
+  }
+
   interface SessionBinding {
     fromPartition: typeof Electron.Session.fromPartition,
     fromPath: typeof Electron.Session.fromPath,
@@ -228,6 +232,7 @@ declare namespace NodeJS {
     _linkedBinding(name: 'electron_browser_safe_storage'): { safeStorage: Electron.SafeStorage };
     _linkedBinding(name: 'electron_browser_session'): SessionBinding;
     _linkedBinding(name: 'electron_browser_screen'): { createScreen(): Electron.Screen };
+    _linkedBinding(name: 'electron_browser_service_worker_main'): ServiceWorkerMainBinding;
     _linkedBinding(name: 'electron_browser_system_preferences'): { systemPreferences: Electron.SystemPreferences };
     _linkedBinding(name: 'electron_browser_tray'): { Tray: Electron.Tray };
     _linkedBinding(name: 'electron_browser_view'): { View: Electron.View };

+ 13 - 0
typings/internal-electron.d.ts

@@ -66,6 +66,19 @@ declare namespace Electron {
     }
   }
 
+  interface ServiceWorkers {
+    _getWorkerFromVersionIDIfExists(versionId: number): Electron.ServiceWorkerMain | undefined;
+    _stopAllWorkers(): Promise<void>;
+  }
+
+  interface ServiceWorkerMain {
+    _send(internal: boolean, channel: string, args: any): void;
+    _startExternalRequest(hasTimeout: boolean): { id: string, ok: boolean };
+    _finishExternalRequest(uuid: string): void;
+    _countExternalRequests(): number;
+  }
+
+
   interface TouchBar {
     _removeFromWindow: (win: BaseWindow) => void;
   }