Browse Source

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

Introduce Chrome extensions management APIs independent of Dev Tools Extensions
John Kleinschmidt 7 years ago
parent
commit
ccdff72ee4
3 changed files with 96 additions and 18 deletions
  1. 28 0
      docs/api/browser-window.md
  2. 40 17
      lib/browser/chrome-extension.js
  3. 28 1
      spec/api-browser-window-spec.js

+ 28 - 0
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

+ 40 - 17
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
+  }
 })

+ 28 - 1
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 () {