From f9f352780b56c50e7c4ba95dec9399670b22c2b9 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Tue, 8 Sep 2020 23:22:20 -0400 Subject: [PATCH 1/4] 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/api/session.md | 37 +++++++++++++++++++++++ shell/browser/api/electron_api_session.cc | 24 +++++++++++++++ shell/browser/api/electron_api_session.h | 24 +++++++++++++++ spec-main/extensions-spec.ts | 17 +++++++++++ 4 files changed, 102 insertions(+) diff --git a/docs/api/session.md b/docs/api/session.md index d78a7e4306bd5..601ac3f77ffcd 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -91,6 +91,43 @@ 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: + * 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. + +#### 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..c8338aaacd4cb 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -25,6 +25,12 @@ #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" // nogncheck #endif +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) +#include "base/scoped_observer.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_registry_observer.h" +#endif + class GURL; namespace base { @@ -55,6 +61,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 +135,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: @@ -159,6 +177,12 @@ class Session : public gin::Wrappable, ElectronBrowserContext* browser_context_; +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + ScopedObserver + registry_observer_{this}; +#endif + DISALLOW_COPY_AND_ASSIGN(Session); }; 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')); From 5efd553628ae53f27e43f68fe47e98eed377cded Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Thu, 10 Sep 2020 23:57:47 -0400 Subject: [PATCH 2/4] docs(extensions): elaborate on extension-loaded/unloaded details --- docs/api/session.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index 601ac3f77ffcd..05e4f21fa5aa4 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -102,13 +102,10 @@ 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. + * from a disabled state (if the extension is disabled then enabled). * if the extension requested it ([`chrome.runtime.reload()`](https://developer.chrome.com/extensions/runtime#method-reload)). - - #### Event: 'extension-unloaded' Returns: @@ -116,7 +113,9 @@ Returns: * `event` Event * `extension` [Extension](structures/extension.md) -Emitted after an extension is unloaded. +Emitted after an extension is unloaded. The extension no longer exists in +the group of enabled extensions, but it can still be a member of another +group, like disabled, blocklisted, or terminated. #### Event: 'extension-ready' From 57c4785b3adc73bbe97bbb9bd34819f54599b1c2 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Fri, 11 Sep 2020 00:17:49 -0400 Subject: [PATCH 3/4] fix: remove scoped extension registry observer --- shell/browser/api/electron_api_session.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index c8338aaacd4cb..cde9dd3fb543a 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -26,7 +26,6 @@ #endif #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) -#include "base/scoped_observer.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry_observer.h" #endif @@ -177,12 +176,6 @@ class Session : public gin::Wrappable, ElectronBrowserContext* browser_context_; -#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - ScopedObserver - registry_observer_{this}; -#endif - DISALLOW_COPY_AND_ASSIGN(Session); }; From ac12fbf087c3d12c3248216b47264e6a1a66edec Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Fri, 11 Sep 2020 00:24:11 -0400 Subject: [PATCH 4/4] docs: update extension-unloaded --- docs/api/session.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index 05e4f21fa5aa4..42030a1112eb6 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -103,7 +103,6 @@ added to the "enabled" set of extensions. This includes: - Extensions being loaded from `Session.loadExtension`. - Extensions being reloaded: * from a crash. - * from a disabled state (if the extension is disabled then enabled). * if the extension requested it ([`chrome.runtime.reload()`](https://developer.chrome.com/extensions/runtime#method-reload)). #### Event: 'extension-unloaded' @@ -113,9 +112,8 @@ Returns: * `event` Event * `extension` [Extension](structures/extension.md) -Emitted after an extension is unloaded. The extension no longer exists in -the group of enabled extensions, but it can still be a member of another -group, like disabled, blocklisted, or terminated. +Emitted after an extension is unloaded. This occurs when +`Session.removeExtension` is called. #### Event: 'extension-ready'