Merge pull request #9918 from alexstrat/add-extensions-loading-api

Introduce Chrome extensions management APIs independent of Dev Tools Extensions
This commit is contained in:
John Kleinschmidt 2017-08-07 20:06:22 -04:00 committed by GitHub
commit ccdff72ee4
3 changed files with 96 additions and 18 deletions

View file

@ -582,6 +582,34 @@ Returns `BrowserWindow` - The window that owns the given `webContents`.
Returns `BrowserWindow` - The window with the given `id`. 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)` #### `BrowserWindow.addDevToolsExtension(path)`
* `path` String * `path` String

View file

@ -15,6 +15,7 @@ const objectValues = function (object) {
// Mapping between extensionId(hostname) and manifest. // Mapping between extensionId(hostname) and manifest.
const manifestMap = {} // extensionId => manifest const manifestMap = {} // extensionId => manifest
const manifestNameMap = {} // name => manifest const manifestNameMap = {} // name => manifest
const devToolsExtensionNames = new Set()
const generateExtensionIdFromName = function (name) { const generateExtensionIdFromName = function (name) {
return name.replace(/[\W_]+/g, '-').toLowerCase() return name.replace(/[\W_]+/g, '-').toLowerCase()
@ -64,6 +65,7 @@ const getManifestFromPath = function (srcDirectory) {
return manifest return manifest
} else if (manifest && manifest.name) { } else if (manifest && manifest.name) {
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`) 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. // The persistent path of "DevTools Extensions" preference file.
let loadedExtensionsPath = null let loadedDevToolsExtensionsPath = null
app.on('will-quit', function () { app.on('will-quit', function () {
try { try {
const loadedExtensions = objectValues(manifestMap).map(function (manifest) { const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
return manifest.srcDirectory .map(name => manifestNameMap[name].srcDirectory)
}) if (loadedDevToolsExtensions.length > 0) {
if (loadedExtensions.length > 0) {
try { try {
fs.mkdirSync(path.dirname(loadedExtensionsPath)) fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath))
} catch (error) { } catch (error) {
// Ignore error // Ignore error
} }
fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)) fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions))
} else { } else {
fs.unlinkSync(loadedExtensionsPath) fs.unlinkSync(loadedDevToolsExtensionsPath)
} }
} catch (error) { } catch (error) {
// Ignore error // Ignore error
@ -354,14 +355,13 @@ app.on('will-quit', function () {
// We can not use protocol or BrowserWindow until app is ready. // We can not use protocol or BrowserWindow until app is ready.
app.once('ready', function () { app.once('ready', function () {
// Load persisted extensions. // Load persisted extensions.
loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
try { try {
const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)) const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath))
if (Array.isArray(loadedExtensions)) { if (Array.isArray(loadedDevToolsExtensions)) {
for (const srcDirectory of loadedExtensions) { for (const srcDirectory of loadedDevToolsExtensions) {
// Start background pages and set content scripts. // Start background pages and set content scripts.
const manifest = getManifestFromPath(srcDirectory) BrowserWindow.addDevToolsExtension(srcDirectory)
loadExtension(manifest)
} }
} }
} catch (error) { } catch (error) {
@ -369,7 +369,7 @@ app.once('ready', function () {
} }
// The public API to add/remove extensions. // The public API to add/remove extensions.
BrowserWindow.addDevToolsExtension = function (srcDirectory) { BrowserWindow.addExtension = function (srcDirectory) {
const manifest = getManifestFromPath(srcDirectory) const manifest = getManifestFromPath(srcDirectory)
if (manifest) { if (manifest) {
loadExtension(manifest) loadExtension(manifest)
@ -382,7 +382,7 @@ app.once('ready', function () {
} }
} }
BrowserWindow.removeDevToolsExtension = function (name) { BrowserWindow.removeExtension = function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name]
if (!manifest) return if (!manifest) return
@ -392,7 +392,7 @@ app.once('ready', function () {
delete manifestNameMap[name] delete manifestNameMap[name]
} }
BrowserWindow.getDevToolsExtensions = function () { BrowserWindow.getExtensions = function () {
const extensions = {} const extensions = {}
Object.keys(manifestNameMap).forEach(function (name) { Object.keys(manifestNameMap).forEach(function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name]
@ -400,4 +400,27 @@ app.once('ready', function () {
}) })
return extensions 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
}
}) })

View file

@ -2373,7 +2373,7 @@ describe('BrowserWindow module', function () {
}) })
}) })
describe('dev tool extensions', function () { describe('extensions and dev tools extensions', function () {
let showPanelTimeoutId let showPanelTimeoutId
const showLastDevToolsPanel = () => { const showLastDevToolsPanel = () => {
@ -2506,6 +2506,33 @@ describe('BrowserWindow module', function () {
app.emit('will-quit') app.emit('will-quit')
assert.equal(fs.existsSync(serializedPath), false) 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 () { describe('window.webContents.executeJavaScript', function () {