diff --git a/docs/api/session.md b/docs/api/session.md index d78a7e4306bd5..42030a1112eb6 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -91,6 +91,40 @@ session.defaultSession.on('will-download', (event, item, webContents) => { }) ``` +#### 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 `Session.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. + #### Event: 'preconnect' Returns: diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index cee5574852526..a876acce41fd7 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -281,6 +281,10 @@ 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() { @@ -294,6 +298,10 @@ 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, @@ -749,6 +757,22 @@ v8::Local Session::GetAllExtensions() { } return gin::ConvertToV8(v8::Isolate::GetCurrent(), 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) { diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index c6c63c57328a7..cde9dd3fb543a 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -25,6 +25,11 @@ #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 { @@ -55,6 +60,9 @@ class Session : public gin::Wrappable, public gin_helper::CleanedUpAtExit, #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) public SpellcheckHunspellDictionary::Observer, +#endif +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + public extensions::ExtensionRegistryObserver, #endif public content::DownloadManager::Observer { public: @@ -126,6 +134,15 @@ class Session : public gin::Wrappable, 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 protected: diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 113ddddde993e..7caac968b859f 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -127,6 +127,23 @@ describe('chrome extensions', () => { } }); + it('emits extension lifecycle events', async () => { + const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); + + const loadedPromise = emittedOnce(customSession, 'extension-loaded'); + const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + const [, loadedExtension] = await loadedPromise; + const [, readyExtension] = await emittedOnce(customSession, 'extension-ready'); + + expect(loadedExtension.id).to.equal(extension.id); + expect(readyExtension.id).to.equal(extension.id); + + const unloadedPromise = emittedOnce(customSession, 'extension-unloaded'); + await customSession.removeExtension(extension.id); + const [, unloadedExtension] = await unloadedPromise; + expect(unloadedExtension.id).to.equal(extension.id); + }); + it('lists loaded extensions in getAllExtensions', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const e = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));