diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 0bae5146e7bb..7869901ee730 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -582,6 +582,34 @@ Returns `BrowserWindow` - The window that owns the given `webContents`. Returns `BrowserWindow` - The window with the given `id`. +#### `BrowserWindow.addExtension(path)` + +* `path` String + +Adds Chrome extension located at `path`, and returns extension's name. + +The method will also not return if the extension's manifest is missing or incomplete. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `BrowserWindow.removeExtension(name)` + +* `name` String + +Remove a Chrome extension by name. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + +#### `BrowserWindow.getExtensions()` + +Returns `Object` - The keys are the extension names and each value is +an Object containing `name` and `version` properties. + +**Note:** This API cannot be called before the `ready` event of the `app` module +is emitted. + #### `BrowserWindow.addDevToolsExtension(path)` * `path` String diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index c0c842caf627..b8f1089ff219 100644 --- a/lib/browser/chrome-extension.js +++ b/lib/browser/chrome-extension.js @@ -15,6 +15,7 @@ const objectValues = function (object) { // Mapping between extensionId(hostname) and manifest. const manifestMap = {} // extensionId => manifest const manifestNameMap = {} // name => manifest +const devToolsExtensionNames = new Set() const generateExtensionIdFromName = function (name) { return name.replace(/[\W_]+/g, '-').toLowerCase() @@ -64,6 +65,7 @@ const getManifestFromPath = function (srcDirectory) { return manifest } else if (manifest && manifest.name) { console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`) + return manifest } } @@ -329,22 +331,21 @@ app.on('session-created', function (ses) { }) // The persistent path of "DevTools Extensions" preference file. -let loadedExtensionsPath = null +let loadedDevToolsExtensionsPath = null app.on('will-quit', function () { try { - const loadedExtensions = objectValues(manifestMap).map(function (manifest) { - return manifest.srcDirectory - }) - if (loadedExtensions.length > 0) { + const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) + .map(name => manifestNameMap[name].srcDirectory) + if (loadedDevToolsExtensions.length > 0) { try { - fs.mkdirSync(path.dirname(loadedExtensionsPath)) + fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)) } catch (error) { // Ignore error } - fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)) + fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)) } else { - fs.unlinkSync(loadedExtensionsPath) + fs.unlinkSync(loadedDevToolsExtensionsPath) } } catch (error) { // Ignore error @@ -354,14 +355,13 @@ app.on('will-quit', function () { // We can not use protocol or BrowserWindow until app is ready. app.once('ready', function () { // Load persisted extensions. - loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') + loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') try { - const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)) - if (Array.isArray(loadedExtensions)) { - for (const srcDirectory of loadedExtensions) { + const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath)) + if (Array.isArray(loadedDevToolsExtensions)) { + for (const srcDirectory of loadedDevToolsExtensions) { // Start background pages and set content scripts. - const manifest = getManifestFromPath(srcDirectory) - loadExtension(manifest) + BrowserWindow.addDevToolsExtension(srcDirectory) } } } catch (error) { @@ -369,7 +369,7 @@ app.once('ready', function () { } // The public API to add/remove extensions. - BrowserWindow.addDevToolsExtension = function (srcDirectory) { + BrowserWindow.addExtension = function (srcDirectory) { const manifest = getManifestFromPath(srcDirectory) if (manifest) { loadExtension(manifest) @@ -382,7 +382,7 @@ app.once('ready', function () { } } - BrowserWindow.removeDevToolsExtension = function (name) { + BrowserWindow.removeExtension = function (name) { const manifest = manifestNameMap[name] if (!manifest) return @@ -392,7 +392,7 @@ app.once('ready', function () { delete manifestNameMap[name] } - BrowserWindow.getDevToolsExtensions = function () { + BrowserWindow.getExtensions = function () { const extensions = {} Object.keys(manifestNameMap).forEach(function (name) { const manifest = manifestNameMap[name] @@ -400,4 +400,27 @@ app.once('ready', function () { }) return extensions } + + BrowserWindow.addDevToolsExtension = function (srcDirectory) { + const manifestName = BrowserWindow.addExtension(srcDirectory) + if (manifestName) { + devToolsExtensionNames.add(manifestName) + } + return manifestName + } + + BrowserWindow.removeDevToolsExtension = function (name) { + BrowserWindow.removeExtension(name) + devToolsExtensionNames.delete(name) + } + + BrowserWindow.getDevToolsExtensions = function () { + const extensions = BrowserWindow.getExtensions() + const devExtensions = {} + Array.from(devToolsExtensionNames).forEach(function (name) { + if (!extensions[name]) return + devExtensions[name] = extensions[name] + }) + return devExtensions + } }) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index bb8966d7dbfc..52c9b5fb47e3 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -2373,7 +2373,7 @@ describe('BrowserWindow module', function () { }) }) - describe('dev tool extensions', function () { + describe('extensions and dev tools extensions', function () { let showPanelTimeoutId const showLastDevToolsPanel = () => { @@ -2506,6 +2506,33 @@ describe('BrowserWindow module', function () { app.emit('will-quit') assert.equal(fs.existsSync(serializedPath), false) }) + + describe('BrowserWindow.addExtension', function () { + beforeEach(function () { + BrowserWindow.removeExtension('foo') + assert.equal(BrowserWindow.getExtensions().hasOwnProperty('foo'), false) + + var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo') + BrowserWindow.addExtension(extensionPath) + assert.equal(BrowserWindow.getExtensions().hasOwnProperty('foo'), true) + + showLastDevToolsPanel() + + w.loadURL('about:blank') + }) + + it('throws errors for missing manifest.json files', function () { + assert.throws(function () { + BrowserWindow.addExtension(path.join(__dirname, 'does-not-exist')) + }, /ENOENT: no such file or directory/) + }) + + it('throws errors for invalid manifest.json files', function () { + assert.throws(function () { + BrowserWindow.addExtension(path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest')) + }, /Unexpected token }/) + }) + }) }) describe('window.webContents.executeJavaScript', function () {