From 9d0d9a166465bf2a07e874ef60c1ed96b3eb7b8a Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Wed, 23 Sep 2020 15:29:08 -0400 Subject: [PATCH] feat(extensions): expose ExtensionRegistryObserver events in Session (#25385) * feat(extensions): expose ExtensionRegistryObserver events in Session Extensions can be loaded and unloaded for various reasons. In some cases this can occur by no means of the Electron programmer, such as in the case of chrome.runtime.reload(). In order to be able to manage state about extensions outside of Electron's APIs, events reloaded to loading and unloaded are needed. * docs(extensions): elaborate on extension-loaded/unloaded details * fix: remove scoped extension registry observer * docs: update extension-unloaded --- docs/api/session.md | 34 +++++++++++++++++++++++ shell/browser/api/electron_api_session.cc | 24 ++++++++++++++++ shell/browser/api/electron_api_session.h | 17 ++++++++++++ spec-main/extensions-spec.ts | 17 ++++++++++++ 4 files changed, 92 insertions(+) diff --git a/docs/api/session.md b/docs/api/session.md index d78a7e4306b..42030a1112e 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 8e54065503d..cac71b7ee19 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 c6c63c57328..cde9dd3fb54 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 0a7f205e5b5..bc4624db93e 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -123,6 +123,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'));