From e3f61b465dac3fa2d944e931fd5fec58d5e27498 Mon Sep 17 00:00:00 2001 From: Sam Maddock Date: Fri, 21 Feb 2025 18:36:51 -0500 Subject: [PATCH] refactor: move extension APIs to session.extensions (#45597) refactor: move extensions to session.extensions --- docs/api/extensions-api.md | 124 +++++++++++++++ docs/api/extensions.md | 2 +- docs/api/session.md | 20 ++- docs/breaking-changes.md | 7 + docs/tutorial/devtools-extension.md | 4 +- filenames.auto.gni | 1 + filenames.gni | 2 + lib/browser/api/session.ts | 44 ++++++ shell/browser/api/electron_api_extensions.cc | 158 +++++++++++++++++++ shell/browser/api/electron_api_extensions.h | 79 ++++++++++ shell/browser/api/electron_api_session.cc | 127 ++------------- shell/browser/api/electron_api_session.h | 27 +--- spec/api-service-worker-main-spec.ts | 4 +- spec/extensions-spec.ts | 114 ++++++------- 14 files changed, 508 insertions(+), 205 deletions(-) create mode 100644 docs/api/extensions-api.md create mode 100644 shell/browser/api/electron_api_extensions.cc create mode 100644 shell/browser/api/electron_api_extensions.h diff --git a/docs/api/extensions-api.md b/docs/api/extensions-api.md new file mode 100644 index 0000000000..30add8f9bc --- /dev/null +++ b/docs/api/extensions-api.md @@ -0,0 +1,124 @@ +## Class: Extensions + +> Load and interact with extensions. + +Process: [Main](../glossary.md#main-process)
+_This class is not exported from the `'electron'` module. It is only available as a return value of other methods in the Electron API._ + +Instances of the `Extensions` class are accessed by using `extensions` property of +a `Session`. + +### Instance Events + +The following events are available on instances of `Extensions`: + +#### Event: 'extension-loaded' + +Returns: + +* `event` Event +* `extension` [Extension](structures/extension.md) + +Emitted after an extension is loaded. This occurs whenever an extension is +added to the "enabled" set of extensions. This includes: + +* Extensions being loaded from `Extensions.loadExtension`. +* Extensions being reloaded: + * from a crash. + * if the extension requested it ([`chrome.runtime.reload()`](https://developer.chrome.com/extensions/runtime#method-reload)). + +#### Event: 'extension-unloaded' + +Returns: + +* `event` Event +* `extension` [Extension](structures/extension.md) + +Emitted after an extension is unloaded. This occurs when +`Session.removeExtension` is called. + +#### Event: 'extension-ready' + +Returns: + +* `event` Event +* `extension` [Extension](structures/extension.md) + +Emitted after an extension is loaded and all necessary browser state is +initialized to support the start of the extension's background page. + +### Instance Methods + +The following methods are available on instances of `Extensions`: + +#### `extensions.loadExtension(path[, options])` + +* `path` string - Path to a directory containing an unpacked Chrome extension +* `options` Object (optional) + * `allowFileAccess` boolean - Whether to allow the extension to read local files over `file://` + protocol and inject content scripts into `file://` pages. This is required e.g. for loading + devtools extensions on `file://` URLs. Defaults to false. + +Returns `Promise` - resolves when the extension is loaded. + +This method will raise an exception if the extension could not be loaded. If +there are warnings when installing the extension (e.g. if the extension +requests an API that Electron does not support) then they will be logged to the +console. + +Note that Electron does not support the full range of Chrome extensions APIs. +See [Supported Extensions APIs](extensions.md#supported-extensions-apis) for +more details on what is supported. + +Note that in previous versions of Electron, extensions that were loaded would +be remembered for future runs of the application. This is no longer the case: +`loadExtension` must be called on every boot of your app if you want the +extension to be loaded. + +```js +const { app, session } = require('electron') +const path = require('node:path') + +app.whenReady().then(async () => { + await session.defaultSession.extensions.loadExtension( + path.join(__dirname, 'react-devtools'), + // allowFileAccess is required to load the devtools extension on file:// URLs. + { allowFileAccess: true } + ) + // Note that in order to use the React DevTools extension, you'll need to + // download and unzip a copy of the extension. +}) +``` + +This API does not support loading packed (.crx) extensions. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +**Note:** Loading extensions into in-memory (non-persistent) sessions is not +supported and will throw an error. + +#### `extensions.removeExtension(extensionId)` + +* `extensionId` string - ID of extension to remove + +Unloads an extension. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `extensions.getExtension(extensionId)` + +* `extensionId` string - ID of extension to query + +Returns `Extension | null` - The loaded extension with the given ID. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `extensions.getAllExtensions()` + +Returns `Extension[]` - A list of all loaded extensions. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. diff --git a/docs/api/extensions.md b/docs/api/extensions.md index e375725db9..2bc93d499a 100644 --- a/docs/api/extensions.md +++ b/docs/api/extensions.md @@ -14,7 +14,7 @@ but it also happens to support some other extension capabilities. Electron only supports loading unpacked extensions (i.e., `.crx` files do not work). Extensions are installed per-`session`. To load an extension, call -[`ses.loadExtension`](session.md#sesloadextensionpath-options): +[`ses.extensions.loadExtension`](extensions-api.md#extensionsloadextensionpath-options): ```js const { session } = require('electron') diff --git a/docs/api/session.md b/docs/api/session.md index ff335ec96b..65a3a36be0 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -1485,7 +1485,7 @@ will not work on non-persistent (in-memory) sessions. **Note:** On macOS and Windows 10 this word will be removed from the OS custom dictionary as well -#### `ses.loadExtension(path[, options])` +#### `ses.loadExtension(path[, options])` _Deprecated_ * `path` string - Path to a directory containing an unpacked Chrome extension * `options` Object (optional) @@ -1532,7 +1532,9 @@ is emitted. **Note:** Loading extensions into in-memory (non-persistent) sessions is not supported and will throw an error. -#### `ses.removeExtension(extensionId)` +**Deprecated:** Use the new `ses.extensions.loadExtension` API. + +#### `ses.removeExtension(extensionId)` _Deprecated_ * `extensionId` string - ID of extension to remove @@ -1541,7 +1543,9 @@ Unloads an extension. **Note:** This API cannot be called before the `ready` event of the `app` module is emitted. -#### `ses.getExtension(extensionId)` +**Deprecated:** Use the new `ses.extensions.removeExtension` API. + +#### `ses.getExtension(extensionId)` _Deprecated_ * `extensionId` string - ID of extension to query @@ -1550,13 +1554,17 @@ Returns `Extension | null` - The loaded extension with the given ID. **Note:** This API cannot be called before the `ready` event of the `app` module is emitted. -#### `ses.getAllExtensions()` +**Deprecated:** Use the new `ses.extensions.getExtension` API. + +#### `ses.getAllExtensions()` _Deprecated_ Returns `Extension[]` - A list of all loaded extensions. **Note:** This API cannot be called before the `ready` event of the `app` module is emitted. +**Deprecated:** Use the new `ses.extensions.getAllExtensions` API. + #### `ses.getStoragePath()` Returns `string | null` - The absolute file system path where data for this @@ -1619,6 +1627,10 @@ session is persisted on disk. For in memory sessions this returns `null`. A [`Cookies`](cookies.md) object for this session. +#### `ses.extensions` _Readonly_ + +A [`Extensions`](extensions-api.md) object for this session. + #### `ses.serviceWorkers` _Readonly_ A [`ServiceWorkers`](service-workers.md) object for this session. diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index 061ce6d287..e80017014c 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -14,6 +14,13 @@ This document uses the following convention to categorize breaking changes: ## Planned Breaking API Changes (36.0) +### Deprecated: Extension methods and events on `session` + +`session.loadExtension`, `session.removeExtension`, `session.getExtension`, +`session.getAllExtensions`, 'extension-loaded' event, 'extension-unloaded' +event, and 'extension-ready' events have all moved to the new +`session.extensions` class. + ### Removed: `systemPreferences.isAeroGlassEnabled()` The `systemPreferences.isAeroGlassEnabled()` function has been removed without replacement. diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 5a159fd65c..dbd2796bca 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -96,9 +96,9 @@ of the extension is not working as expected. [devtools-extension]: https://developer.chrome.com/extensions/devtools [session]: ../api/session.md [react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi -[load-extension]: ../api/session.md#sesloadextensionpath-options +[load-extension]: ../api/extensions-api.md#extensionsloadextensionpath-options [extension-structure]: ../api/structures/extension.md -[remove-extension]: ../api/session.md#sesremoveextensionextensionid +[remove-extension]: ../api/extensions-api.md#extensionsremoveextensionextensionid [electron-devtools-installer]: https://github.com/MarshallOfSound/electron-devtools-installer [supported-extension-apis]: ../api/extensions.md [issue-tracker]: https://github.com/electron/electron/issues diff --git a/filenames.auto.gni b/filenames.auto.gni index 165f94e5c9..a2514d5e99 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -21,6 +21,7 @@ auto_filenames = { "docs/api/dock.md", "docs/api/download-item.md", "docs/api/environment-variables.md", + "docs/api/extensions-api.md", "docs/api/extensions.md", "docs/api/global-shortcut.md", "docs/api/in-app-purchase.md", diff --git a/filenames.gni b/filenames.gni index 6c62dcd04a..7bd470eec7 100644 --- a/filenames.gni +++ b/filenames.gni @@ -739,6 +739,8 @@ filenames = { ] lib_sources_extensions = [ + "shell/browser/api/electron_api_extensions.cc", + "shell/browser/api/electron_api_extensions.h", "shell/browser/extensions/api/extension_action/extension_action_api.cc", "shell/browser/extensions/api/extension_action/extension_action_api.h", "shell/browser/extensions/api/management/electron_management_api_delegate.cc", diff --git a/lib/browser/api/session.ts b/lib/browser/api/session.ts index 2b2c6da886..3e573205f3 100644 --- a/lib/browser/api/session.ts +++ b/lib/browser/api/session.ts @@ -24,6 +24,21 @@ Object.freeze(systemPickerVideoSource); Session.prototype._init = function () { addIpcDispatchListeners(this); + + if (this.extensions) { + const rerouteExtensionEvent = (eventName: string) => { + const warn = deprecate.warnOnce(`${eventName} event`, `session.extensions ${eventName} event`); + this.extensions.on(eventName as any, (...args: any[]) => { + if (this.listenerCount(eventName) !== 0) { + warn(); + this.emit(eventName, ...args); + } + }); + }; + rerouteExtensionEvent('extension-loaded'); + rerouteExtensionEvent('extension-unloaded'); + rerouteExtensionEvent('extension-ready'); + } }; Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) { @@ -67,6 +82,35 @@ Session.prototype.setPreloads = function (preloads) { }); }; +Session.prototype.getAllExtensions = deprecate.moveAPI( + function (this: Electron.Session) { + return this.extensions.getAllExtensions(); + }, + 'session.getAllExtensions', + 'session.extensions.getAllExtensions' +); +Session.prototype.getExtension = deprecate.moveAPI( + function (this: Electron.Session, extensionId) { + return this.extensions.getExtension(extensionId); + }, + 'session.getExtension', + 'session.extensions.getExtension' +); +Session.prototype.loadExtension = deprecate.moveAPI( + function (this: Electron.Session, path, options) { + return this.extensions.loadExtension(path, options); + }, + 'session.loadExtension', + 'session.extensions.loadExtension' +); +Session.prototype.removeExtension = deprecate.moveAPI( + function (this: Electron.Session, extensionId) { + return this.extensions.removeExtension(extensionId); + }, + 'session.removeExtension', + 'session.extensions.removeExtension' +); + export default { fromPartition, fromPath, diff --git a/shell/browser/api/electron_api_extensions.cc b/shell/browser/api/electron_api_extensions.cc new file mode 100644 index 0000000000..038636c414 --- /dev/null +++ b/shell/browser/api/electron_api_extensions.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2019 Slack Technologies, 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_extensions.h" + +#include "chrome/browser/browser_process.h" +#include "extensions/browser/extension_registry.h" +#include "gin/data_object_builder.h" +#include "gin/handle.h" +#include "gin/object_template_builder.h" +#include "shell/browser/api/electron_api_extensions.h" +#include "shell/browser/electron_browser_context.h" +#include "shell/browser/extensions/electron_extension_system.h" +#include "shell/browser/javascript_environment.h" +#include "shell/common/gin_converters/extension_converter.h" +#include "shell/common/gin_converters/file_path_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/promise.h" +#include "shell/common/node_util.h" + +namespace electron::api { + +gin::WrapperInfo Extensions::kWrapperInfo = {gin::kEmbedderNativeGin}; + +Extensions::Extensions(v8::Isolate* isolate, + ElectronBrowserContext* browser_context) + : browser_context_(browser_context) { + extensions::ExtensionRegistry::Get(browser_context)->AddObserver(this); +} + +Extensions::~Extensions() { + extensions::ExtensionRegistry::Get(browser_context())->RemoveObserver(this); +} + +// static +gin::Handle Extensions::Create( + v8::Isolate* isolate, + ElectronBrowserContext* browser_context) { + return gin::CreateHandle(isolate, new Extensions(isolate, browser_context)); +} + +v8::Local Extensions::LoadExtension( + v8::Isolate* isolate, + const base::FilePath& extension_path, + gin::Arguments* args) { + gin_helper::Promise promise(isolate); + v8::Local handle = promise.GetHandle(); + + if (!extension_path.IsAbsolute()) { + promise.RejectWithErrorMessage( + "The path to the extension in 'loadExtension' must be absolute"); + return handle; + } + + if (browser_context()->IsOffTheRecord()) { + promise.RejectWithErrorMessage( + "Extensions cannot be loaded in a temporary session"); + return handle; + } + + int load_flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE; + gin_helper::Dictionary options; + if (args->GetNext(&options)) { + bool allowFileAccess = false; + options.Get("allowFileAccess", &allowFileAccess); + if (allowFileAccess) + load_flags |= extensions::Extension::ALLOW_FILE_ACCESS; + } + + auto* extension_system = static_cast( + extensions::ExtensionSystem::Get(browser_context())); + extension_system->LoadExtension( + extension_path, load_flags, + base::BindOnce( + [](gin_helper::Promise promise, + const extensions::Extension* extension, + const std::string& error_msg) { + if (extension) { + if (!error_msg.empty()) + util::EmitWarning(promise.isolate(), error_msg, + "ExtensionLoadWarning"); + promise.Resolve(extension); + } else { + promise.RejectWithErrorMessage(error_msg); + } + }, + std::move(promise))); + + return handle; +} + +void Extensions::RemoveExtension(const std::string& extension_id) { + auto* extension_system = static_cast( + extensions::ExtensionSystem::Get(browser_context())); + extension_system->RemoveExtension(extension_id); +} + +v8::Local Extensions::GetExtension(v8::Isolate* isolate, + const std::string& extension_id) { + auto* registry = extensions::ExtensionRegistry::Get(browser_context()); + const extensions::Extension* extension = + registry->GetInstalledExtension(extension_id); + if (extension) { + return gin::ConvertToV8(isolate, extension); + } else { + return v8::Null(isolate); + } +} + +v8::Local Extensions::GetAllExtensions(v8::Isolate* isolate) { + auto* registry = extensions::ExtensionRegistry::Get(browser_context()); + const extensions::ExtensionSet extensions = + registry->GenerateInstalledExtensionsSet(); + std::vector extensions_vector; + for (const auto& extension : extensions) { + if (extension->location() != + extensions::mojom::ManifestLocation::kComponent) + extensions_vector.emplace_back(extension.get()); + } + return gin::ConvertToV8(isolate, extensions_vector); +} + +void Extensions::OnExtensionLoaded(content::BrowserContext* browser_context, + const extensions::Extension* extension) { + Emit("extension-loaded", extension); +} + +void Extensions::OnExtensionUnloaded( + content::BrowserContext* browser_context, + const extensions::Extension* extension, + extensions::UnloadedExtensionReason reason) { + Emit("extension-unloaded", extension); +} + +void Extensions::OnExtensionReady(content::BrowserContext* browser_context, + const extensions::Extension* extension) { + Emit("extension-ready", extension); +} + +// static +gin::ObjectTemplateBuilder Extensions::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin_helper::EventEmitterMixin::GetObjectTemplateBuilder( + isolate) + .SetMethod("loadExtension", &Extensions::LoadExtension) + .SetMethod("removeExtension", &Extensions::RemoveExtension) + .SetMethod("getExtension", &Extensions::GetExtension) + .SetMethod("getAllExtensions", &Extensions::GetAllExtensions); +} + +const char* Extensions::GetTypeName() { + return "Extensions"; +} + +} // namespace electron::api diff --git a/shell/browser/api/electron_api_extensions.h b/shell/browser/api/electron_api_extensions.h new file mode 100644 index 0000000000..81b474aca9 --- /dev/null +++ b/shell/browser/api/electron_api_extensions.h @@ -0,0 +1,79 @@ +// Copyright (c) 2019 Slack Technologies, 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_EXTENSIONS_H_ +#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_EXTENSIONS_H_ + +#include "base/memory/raw_ptr.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_registry_observer.h" +#include "gin/wrappable.h" +#include "shell/browser/event_emitter_mixin.h" + +namespace gin { +template +class Handle; +} // namespace gin + +namespace electron { + +class ElectronBrowserContext; + +namespace api { + +class Extensions final : public gin::Wrappable, + public gin_helper::EventEmitterMixin, + private extensions::ExtensionRegistryObserver { + public: + static gin::Handle Create( + v8::Isolate* isolate, + ElectronBrowserContext* browser_context); + + // gin::Wrappable + static gin::WrapperInfo kWrapperInfo; + gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + const char* GetTypeName() override; + + v8::Local LoadExtension(v8::Isolate* isolate, + const base::FilePath& extension_path, + gin::Arguments* args); + void RemoveExtension(const std::string& extension_id); + v8::Local GetExtension(v8::Isolate* isolate, + const std::string& extension_id); + v8::Local GetAllExtensions(v8::Isolate* isolate); + + // extensions::ExtensionRegistryObserver: + void OnExtensionLoaded(content::BrowserContext* browser_context, + const extensions::Extension* extension) override; + void OnExtensionReady(content::BrowserContext* browser_context, + const extensions::Extension* extension) override; + void OnExtensionUnloaded(content::BrowserContext* browser_context, + const extensions::Extension* extension, + extensions::UnloadedExtensionReason reason) override; + + // disable copy + Extensions(const Extensions&) = delete; + Extensions& operator=(const Extensions&) = delete; + + protected: + explicit Extensions(v8::Isolate* isolate, + ElectronBrowserContext* browser_context); + ~Extensions() override; + + private: + content::BrowserContext* browser_context() const { + return browser_context_.get(); + } + + raw_ptr browser_context_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace api + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_EXTENSIONS_H_ diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 571ba3a009..95bd0dc441 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -97,9 +97,7 @@ #include "url/origin.h" #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) -#include "extensions/browser/extension_registry.h" -#include "shell/browser/extensions/electron_extension_system.h" -#include "shell/common/gin_converters/extension_converter.h" +#include "shell/browser/api/electron_api_extensions.h" #endif #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) @@ -569,10 +567,6 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context) service->SetHunspellObserver(this); } #endif - -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - extensions::ExtensionRegistry::Get(browser_context)->AddObserver(this); -#endif } Session::~Session() { @@ -584,10 +578,6 @@ Session::~Session() { service->SetHunspellObserver(nullptr); } #endif - -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - extensions::ExtensionRegistry::Get(browser_context())->RemoveObserver(this); -#endif } void Session::OnDownloadCreated(content::DownloadManager* manager, @@ -1305,103 +1295,6 @@ v8::Local Session::GetSharedDictionaryUsageInfo() { return handle; } -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) -v8::Local Session::LoadExtension( - const base::FilePath& extension_path, - gin::Arguments* args) { - gin_helper::Promise promise(isolate_); - v8::Local handle = promise.GetHandle(); - - if (!extension_path.IsAbsolute()) { - promise.RejectWithErrorMessage( - "The path to the extension in 'loadExtension' must be absolute"); - return handle; - } - - if (browser_context()->IsOffTheRecord()) { - promise.RejectWithErrorMessage( - "Extensions cannot be loaded in a temporary session"); - return handle; - } - - int load_flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE; - gin_helper::Dictionary options; - if (args->GetNext(&options)) { - bool allowFileAccess = false; - options.Get("allowFileAccess", &allowFileAccess); - if (allowFileAccess) - load_flags |= extensions::Extension::ALLOW_FILE_ACCESS; - } - - auto* extension_system = static_cast( - extensions::ExtensionSystem::Get(browser_context())); - extension_system->LoadExtension( - extension_path, load_flags, - base::BindOnce( - [](gin_helper::Promise promise, - const extensions::Extension* extension, - const std::string& error_msg) { - if (extension) { - if (!error_msg.empty()) - util::EmitWarning(promise.isolate(), error_msg, - "ExtensionLoadWarning"); - promise.Resolve(extension); - } else { - promise.RejectWithErrorMessage(error_msg); - } - }, - std::move(promise))); - - return handle; -} - -void Session::RemoveExtension(const std::string& extension_id) { - auto* extension_system = static_cast( - extensions::ExtensionSystem::Get(browser_context())); - extension_system->RemoveExtension(extension_id); -} - -v8::Local Session::GetExtension(const std::string& extension_id) { - auto* registry = extensions::ExtensionRegistry::Get(browser_context()); - const extensions::Extension* extension = - registry->GetInstalledExtension(extension_id); - if (extension) { - return gin::ConvertToV8(isolate_, extension); - } else { - return v8::Null(isolate_); - } -} - -v8::Local Session::GetAllExtensions() { - auto* registry = extensions::ExtensionRegistry::Get(browser_context()); - const extensions::ExtensionSet extensions = - registry->GenerateInstalledExtensionsSet(); - std::vector extensions_vector; - for (const auto& extension : extensions) { - if (extension->location() != - extensions::mojom::ManifestLocation::kComponent) - extensions_vector.emplace_back(extension.get()); - } - return gin::ConvertToV8(isolate_, extensions_vector); -} - -void Session::OnExtensionLoaded(content::BrowserContext* browser_context, - const extensions::Extension* extension) { - Emit("extension-loaded", extension); -} - -void Session::OnExtensionUnloaded(content::BrowserContext* browser_context, - const extensions::Extension* extension, - extensions::UnloadedExtensionReason reason) { - Emit("extension-unloaded", extension); -} - -void Session::OnExtensionReady(content::BrowserContext* browser_context, - const extensions::Extension* extension) { - Emit("extension-ready", extension); -} -#endif - v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = Cookies::Create(isolate, browser_context()); @@ -1410,6 +1303,17 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { return cookies_.Get(isolate); } +v8::Local Session::Extensions(v8::Isolate* isolate) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + if (extensions_.IsEmpty()) { + v8::Local handle; + handle = Extensions::Create(isolate, browser_context()).ToV8(); + extensions_.Reset(isolate, handle); + } +#endif + return extensions_.Get(isolate); +} + v8::Local Session::Protocol(v8::Isolate* isolate) { return protocol_.Get(isolate); } @@ -1872,12 +1776,6 @@ void Session::FillObjectTemplate(v8::Isolate* isolate, &Session::ClearSharedDictionaryCache) .SetMethod("clearSharedDictionaryCacheForIsolationKey", &Session::ClearSharedDictionaryCacheForIsolationKey) -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - .SetMethod("loadExtension", &Session::LoadExtension) - .SetMethod("removeExtension", &Session::RemoveExtension) - .SetMethod("getExtension", &Session::GetExtension) - .SetMethod("getAllExtensions", &Session::GetAllExtensions) -#endif #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) .SetMethod("getSpellCheckerLanguages", &Session::GetSpellCheckerLanguages) .SetMethod("setSpellCheckerLanguages", &Session::SetSpellCheckerLanguages) @@ -1903,6 +1801,7 @@ void Session::FillObjectTemplate(v8::Isolate* isolate, .SetMethod("clearCodeCaches", &Session::ClearCodeCaches) .SetMethod("clearData", &Session::ClearData) .SetProperty("cookies", &Session::Cookies) + .SetProperty("extensions", &Session::Extensions) .SetProperty("netLog", &Session::NetLog) .SetProperty("protocol", &Session::Protocol) .SetProperty("serviceWorkers", &Session::ServiceWorkerContext) diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index 76e4f9559c..7911400581 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -29,11 +29,6 @@ #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" // nogncheck #endif -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) -#include "extensions/browser/extension_registry.h" -#include "extensions/browser/extension_registry_observer.h" -#endif - class GURL; namespace base { @@ -70,9 +65,6 @@ class Session final : public gin::Wrappable, public IpcDispatcher, #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) private SpellcheckHunspellDictionary::Observer, -#endif -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - private extensions::ExtensionRegistryObserver, #endif private content::DownloadManager::Observer { public: @@ -156,6 +148,7 @@ class Session final : public gin::Wrappable, v8::Local ClearSharedDictionaryCacheForIsolationKey( const gin_helper::Dictionary& options); v8::Local Cookies(v8::Isolate* isolate); + v8::Local Extensions(v8::Isolate* isolate); v8::Local Protocol(v8::Isolate* isolate); v8::Local ServiceWorkerContext(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); @@ -178,23 +171,6 @@ class Session final : public gin::Wrappable, bool IsSpellCheckerEnabled() const; #endif -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - v8::Local LoadExtension(const base::FilePath& extension_path, - gin::Arguments* args); - void RemoveExtension(const std::string& extension_id); - v8::Local GetExtension(const std::string& extension_id); - v8::Local GetAllExtensions(); - - // extensions::ExtensionRegistryObserver: - void OnExtensionLoaded(content::BrowserContext* browser_context, - const extensions::Extension* extension) override; - void OnExtensionReady(content::BrowserContext* browser_context, - const extensions::Extension* extension) override; - void OnExtensionUnloaded(content::BrowserContext* browser_context, - const extensions::Extension* extension, - extensions::UnloadedExtensionReason reason) override; -#endif - // disable copy Session(const Session&) = delete; Session& operator=(const Session&) = delete; @@ -223,6 +199,7 @@ class Session final : public gin::Wrappable, // Cached gin_helper::Wrappable objects. v8::Global cookies_; + v8::Global extensions_; v8::Global protocol_; v8::Global net_log_; v8::Global service_worker_context_; diff --git a/spec/api-service-worker-main-spec.ts b/spec/api-service-worker-main-spec.ts index d6f3d65c87..6e1edced5c 100644 --- a/spec/api-service-worker-main-spec.ts +++ b/spec/api-service-worker-main-spec.ts @@ -401,7 +401,7 @@ describe('ServiceWorkerMain module', () => { it('can observe extension service workers', async () => { const serviceWorkerPromise = waitForServiceWorker(); - const extension = await ses.loadExtension(testExtensionFixture); + const extension = await ses.extensions.loadExtension(testExtensionFixture); const serviceWorker = await serviceWorkerPromise; expect(serviceWorker.scope).to.equal(extension.url); }); @@ -409,7 +409,7 @@ describe('ServiceWorkerMain module', () => { it('has extension state available when preload runs', async () => { registerPreload('preload-send-extension.js'); const serviceWorkerPromise = waitForServiceWorker(); - const extensionPromise = ses.loadExtension(testExtensionFixture); + const extensionPromise = ses.extensions.loadExtension(testExtensionFixture); const serviceWorker = await serviceWorkerPromise; const result = await new Promise((resolve) => { serviceWorker.ipc.handleOnce('preload-extension-result', (_event, result) => { diff --git a/spec/extensions-spec.ts b/spec/extensions-spec.ts index 5f4c32650d..92dc26980c 100644 --- a/spec/extensions-spec.ts +++ b/spec/extensions-spec.ts @@ -50,8 +50,8 @@ describe('chrome extensions', () => { }); afterEach(closeAllWindows); afterEach(() => { - for (const e of session.defaultSession.getAllExtensions()) { - session.defaultSession.removeExtension(e.id); + for (const e of session.defaultSession.extensions.getAllExtensions()) { + session.defaultSession.extensions.removeExtension(e.id); } }); @@ -61,7 +61,7 @@ describe('chrome extensions', () => { await w.loadURL('about:blank'); const promise = once(app, 'web-contents-created') as Promise<[any, WebContents]>; - await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); const args: any = await promise; const wc: Electron.WebContents = args[1]; await expect(wc.executeJavaScript(` @@ -96,7 +96,7 @@ describe('chrome extensions', () => { await w.loadURL(url); const extPath = path.join(fixtures, 'extensions', 'host-permissions', 'malformed'); - customSession.loadExtension(extPath); + customSession.extensions.loadExtension(extPath); const warning = await new Promise(resolve => { process.on('warning', resolve); }); @@ -107,7 +107,7 @@ describe('chrome extensions', () => { it('can grant special privileges to urls with host permissions', async () => { const extPath = path.join(fixtures, 'extensions', 'host-permissions', 'privileged-tab-info'); - await customSession.loadExtension(extPath); + await customSession.extensions.loadExtension(extPath); await w.loadURL(url); @@ -149,7 +149,7 @@ describe('chrome extensions', () => { await w.loadURL('about:blank'); const extPath = path.join(fixtures, 'extensions', 'minimum-chrome-version'); - const load = customSession.loadExtension(extPath); + const load = customSession.extensions.loadExtension(extPath); await expect(load).to.eventually.be.rejectedWith( `Loading extension at ${extPath} failed with: This extension requires Chromium version 999 or greater.` ); @@ -162,7 +162,7 @@ describe('chrome extensions', () => { it('bypasses CORS in requests made from extensions', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } }); - const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); + const extension = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); await w.loadURL(`${extension.url}bare-page.html`); await expect(fetch(w.webContents, `${url}/cors`)).to.not.be.rejectedWith(TypeError); }); @@ -173,7 +173,7 @@ describe('chrome extensions', () => { // extension in an in-memory session results in it being installed in the // default session. const customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); @@ -182,13 +182,13 @@ describe('chrome extensions', () => { it('does not crash when loading an extension with missing manifest', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const promise = customSession.loadExtension(path.join(fixtures, 'extensions', 'missing-manifest')); + const promise = customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'missing-manifest')); await expect(promise).to.eventually.be.rejectedWith(/Manifest file is missing or unreadable/); }); it('does not crash when failing to load an extension', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const promise = customSession.loadExtension(path.join(fixtures, 'extensions', 'load-error')); + const promise = customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'load-error')); await expect(promise).to.eventually.be.rejected(); }); @@ -196,7 +196,7 @@ describe('chrome extensions', () => { const extensionPath = path.join(fixtures, 'extensions', 'red-bg'); const manifest = JSON.parse(await fs.readFile(path.join(extensionPath, 'manifest.json'), 'utf-8')); const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const extension = await customSession.loadExtension(extensionPath); + const extension = await customSession.extensions.loadExtension(extensionPath); expect(extension.id).to.be.a('string'); expect(extension.name).to.be.a('string'); expect(extension.path).to.be.a('string'); @@ -207,14 +207,14 @@ describe('chrome extensions', () => { it('removes an extension', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + const { id } = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); { const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); expect(bg).to.equal('red'); } - customSession.removeExtension(id); + customSession.extensions.removeExtension(id); { const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(url); @@ -226,40 +226,40 @@ describe('chrome extensions', () => { it('emits extension lifecycle events', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); - const loadedPromise = once(customSession, 'extension-loaded'); - const readyPromise = emittedUntil(customSession, 'extension-ready', (event: Event, extension: Extension) => { + const loadedPromise = once(customSession.extensions, 'extension-loaded'); + const readyPromise = emittedUntil(customSession.extensions, 'extension-ready', (event: Event, extension: Extension) => { return extension.name !== 'Chromium PDF Viewer'; }); - const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + const extension = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const [, loadedExtension] = await loadedPromise; const [, readyExtension] = await readyPromise; expect(loadedExtension).to.deep.equal(extension); expect(readyExtension).to.deep.equal(extension); - const unloadedPromise = once(customSession, 'extension-unloaded'); - await customSession.removeExtension(extension.id); + const unloadedPromise = once(customSession.extensions, 'extension-unloaded'); + await customSession.extensions.removeExtension(extension.id); const [, unloadedExtension] = await unloadedPromise; expect(unloadedExtension).to.deep.equal(extension); }); it('lists loaded extensions in getAllExtensions', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const e = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); - expect(customSession.getAllExtensions()).to.deep.equal([e]); - customSession.removeExtension(e.id); - expect(customSession.getAllExtensions()).to.deep.equal([]); + const e = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + expect(customSession.extensions.getAllExtensions()).to.deep.equal([e]); + customSession.extensions.removeExtension(e.id); + expect(customSession.extensions.getAllExtensions()).to.deep.equal([]); }); it('gets an extension by id', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const e = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); - expect(customSession.getExtension(e.id)).to.deep.equal(e); + const e = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + expect(customSession.extensions.getExtension(e.id)).to.deep.equal(e); }); it('confines an extension to the session it was loaded in', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const w = new BrowserWindow({ show: false }); // not in the session await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); @@ -268,7 +268,7 @@ describe('chrome extensions', () => { it('loading an extension in a temporary session throws an error', async () => { const customSession = session.fromPartition(uuid.v4()); - await expect(customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'))).to.eventually.be.rejectedWith('Extensions cannot be loaded in a temporary session'); + await expect(customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'red-bg'))).to.eventually.be.rejectedWith('Extensions cannot be loaded in a temporary session'); }); describe('chrome.i18n', () => { @@ -282,7 +282,7 @@ describe('chrome extensions', () => { }; beforeEach(async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n', 'v2')); + extension = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n', 'v2')); w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } }); await w.loadURL(url); }); @@ -311,7 +311,7 @@ describe('chrome extensions', () => { }; beforeEach(async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-runtime')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-runtime')); w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } }); await w.loadURL(url); }); @@ -343,7 +343,7 @@ describe('chrome extensions', () => { describe('chrome.storage', () => { it('stores and retrieves a key', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-storage')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-storage')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } }); try { const p = once(ipcMain, 'storage-success'); @@ -386,7 +386,7 @@ describe('chrome extensions', () => { it('can cancel http requests', async () => { await w.loadURL(url); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest')); await expect(waitUntil(haveRejectedFetch)).to.eventually.be.fulfilled(); }); @@ -405,7 +405,7 @@ describe('chrome extensions', () => { }); await w.loadURL(url); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest')); fetch(w.webContents, url); })(); }); @@ -418,7 +418,7 @@ describe('chrome extensions', () => { resolve(); }); await w.loadFile(path.join(fixtures, 'api', 'webrequest.html'), { query: { port: `${port}` } }); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss')); })(); }); }); @@ -426,7 +426,7 @@ describe('chrome extensions', () => { describe('WebSocket', () => { it('can be proxied', async () => { await w.loadFile(path.join(fixtures, 'api', 'webrequest.html'), { query: { port: `${port}` } }); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss')); customSession.webRequest.onSendHeaders((details) => { if (details.url.startsWith('ws://')) { expect(details.requestHeaders.foo).be.equal('bar'); @@ -440,7 +440,7 @@ describe('chrome extensions', () => { let customSession: Session; before(async () => { customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-api')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-api')); }); afterEach(closeAllWindows); @@ -512,7 +512,7 @@ describe('chrome extensions', () => { afterEach(closeAllWindows); it('loads a lazy background page when sending a message', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } }); try { w.loadURL(url); @@ -528,7 +528,7 @@ describe('chrome extensions', () => { it('can use extension.getBackgroundPage from a ui page', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); + const { id } = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(`chrome-extension://${id}/page-get-background.html`); const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise'); @@ -537,7 +537,7 @@ describe('chrome extensions', () => { it('can use extension.getBackgroundPage from a ui page', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); + const { id } = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(`chrome-extension://${id}/page-get-background.html`); const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise'); @@ -546,7 +546,7 @@ describe('chrome extensions', () => { it('can use runtime.getBackgroundPage from a ui page', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); + const { id } = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(`chrome-extension://${id}/page-runtime-get-background.html`); const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise'); @@ -556,7 +556,7 @@ describe('chrome extensions', () => { it('has session in background page', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const promise = once(app, 'web-contents-created') as Promise<[any, WebContents]>; - const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); + const { id } = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); const [, bgPageContents] = await promise; expect(bgPageContents.getType()).to.equal('backgroundPage'); await once(bgPageContents, 'did-finish-load'); @@ -567,7 +567,7 @@ describe('chrome extensions', () => { it('can open devtools of background page', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const promise = once(app, 'web-contents-created') as Promise<[any, WebContents]>; - await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); const [, bgPageContents] = await promise; expect(bgPageContents.getType()).to.equal('backgroundPage'); bgPageContents.openDevTools(); @@ -609,7 +609,7 @@ describe('chrome extensions', () => { // TODO(jkleinsc) fix this flaky test on WOA ifit(process.platform !== 'win32' || process.arch !== 'arm64')('loads a devtools extension', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); - customSession.loadExtension(path.join(fixtures, 'extensions', 'devtools-extension')); + customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'devtools-extension')); const winningMessage = once(ipcMain, 'winning'); const w = new BrowserWindow({ show: true, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } }); await w.loadURL(url); @@ -623,10 +623,10 @@ describe('chrome extensions', () => { const fixtures = path.resolve(__dirname, 'fixtures'); const extensionPath = path.resolve(fixtures, 'extensions'); - const addExtension = (name: string) => session.defaultSession.loadExtension(path.resolve(extensionPath, name)); + const addExtension = (name: string) => session.defaultSession.extensions.loadExtension(path.resolve(extensionPath, name)); const removeAllExtensions = () => { - Object.keys(session.defaultSession.getAllExtensions()).forEach(extName => { - session.defaultSession.removeExtension(extName); + Object.keys(session.defaultSession.extensions.getAllExtensions()).forEach(extName => { + session.defaultSession.extensions.removeExtension(extName); }); }; @@ -716,11 +716,11 @@ describe('chrome extensions', () => { ({ port } = await listen(server)); - session.defaultSession.loadExtension(contentScript); + session.defaultSession.extensions.loadExtension(contentScript); }); after(() => { - session.defaultSession.removeExtension('content-script-test'); + session.defaultSession.extensions.removeExtension('content-script-test'); server.close(); }); @@ -779,14 +779,14 @@ describe('chrome extensions', () => { describe('extension ui pages', () => { afterEach(async () => { - for (const e of session.defaultSession.getAllExtensions()) { - session.defaultSession.removeExtension(e.id); + for (const e of session.defaultSession.extensions.getAllExtensions()) { + session.defaultSession.extensions.removeExtension(e.id); } await closeAllWindows(); }); it('loads a ui page of an extension', async () => { - const { id } = await session.defaultSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); + const { id } = await session.defaultSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); const w = new BrowserWindow({ show: false }); await w.loadURL(`chrome-extension://${id}/bare-page.html`); const textContent = await w.webContents.executeJavaScript('document.body.textContent'); @@ -794,7 +794,7 @@ describe('chrome extensions', () => { }); it('can load resources', async () => { - const { id } = await session.defaultSession.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); + const { id } = await session.defaultSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'ui-page')); const w = new BrowserWindow({ show: false }); await w.loadURL(`chrome-extension://${id}/page-script-load.html`); const textContent = await w.webContents.executeJavaScript('document.body.textContent'); @@ -809,7 +809,7 @@ describe('chrome extensions', () => { const registrationPromise = new Promise(resolve => { customSession.serviceWorkers.once('registration-completed', (event, { scope }) => resolve(scope)); }); - const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker')); + const extension = await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker')); const scope = await registrationPromise; expect(scope).equals(extension.url); }); @@ -817,7 +817,7 @@ describe('chrome extensions', () => { it('can run chrome extension APIs', async () => { const customSession = session.fromPartition(`persist:${uuid.v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } }); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker')); await w.loadURL(url); @@ -835,7 +835,7 @@ describe('chrome extensions', () => { before(async () => { customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n', 'v3')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n', 'v3')); }); beforeEach(() => { @@ -927,7 +927,7 @@ describe('chrome extensions', () => { before(async () => { customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-action-fail')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-action-fail')); }); beforeEach(() => { @@ -984,7 +984,7 @@ describe('chrome extensions', () => { before(async () => { customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'api-async')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'api-async')); }); beforeEach(() => { @@ -1082,7 +1082,7 @@ describe('chrome extensions', () => { it('does not return privileged properties without tabs permission', async () => { const noPrivilegeSes = session.fromPartition(`persist:${uuid.v4()}`); - await noPrivilegeSes.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'no-privileges')); + await noPrivilegeSes.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-tabs', 'no-privileges')); w = new BrowserWindow({ show: false, webPreferences: { session: noPrivilegeSes } }); await w.loadURL(url); @@ -1263,7 +1263,7 @@ describe('chrome extensions', () => { before(async () => { customSession = session.fromPartition(`persist:${uuid.v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-scripting')); + await customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'chrome-scripting')); }); beforeEach(() => {