Browse Source

Clean up the Chrome API implementation code

Cheng Zhao 9 years ago
parent
commit
d55b96fdf5

+ 42 - 44
lib/browser/chrome-extension.js

@@ -10,37 +10,27 @@ const objectValues = function (object) {
   return Object.keys(object).map(function (key) { return object[key] })
 }
 
-// Mapping between hostname and file path.
-const hostPathMap = {}
-let hostPathMapNextKey = 0
-
-const generateHostForPath = function (path) {
-  const key = `extension-${++hostPathMapNextKey}`
-  hostPathMap[key] = path
-  return key
-}
-
-const getPathForHost = function (host) {
-  return hostPathMap[host]
-}
+// Mapping between extensionId(hostname) and manifest.
+const manifestMap = {}  // extensionId => manifest
+const manifestNameMap = {}  // name => manifest
 
-// Cache manifests.
-const manifestMap = {}
+let nextExtensionId = 0
 
+// Create or get manifest object from |srcDirectory|.
 const getManifestFromPath = function (srcDirectory) {
   const manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json')))
-  if (!manifestMap[manifest.name]) {
-    const hostname = generateHostForPath(srcDirectory)
-    manifestMap[manifest.name] = manifest
+  if (!manifestNameMap[manifest.name]) {
+    const extensionId = `extension-${++nextExtensionId}`
+    manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
     Object.assign(manifest, {
       srcDirectory: srcDirectory,
-      hostname: hostname,
+      extensionId: extensionId,
       // We can not use 'file://' directly because all resources in the extension
       // will be treated as relative to the root in Chrome.
       startPage: url.format({
         protocol: 'chrome-extension',
         slashes: true,
-        hostname: hostname,
+        hostname: extensionId,
         pathname: manifest.devtools_page
       })
     })
@@ -52,7 +42,7 @@ const getManifestFromPath = function (srcDirectory) {
 const backgroundPages = {}
 
 const startBackgroundPages = function (manifest) {
-  if (backgroundPages[manifest.hostname] || !manifest.background) return
+  if (backgroundPages[manifest.extensionId] || !manifest.background) return
 
   const scripts = manifest.background.scripts.map((name) => {
     return `<script src="${name}"></script>`
@@ -60,30 +50,29 @@ const startBackgroundPages = function (manifest) {
   const html = new Buffer(`<html><body>${scripts}</body></html>`)
 
   const contents = webContents.create({})
-  backgroundPages[manifest.hostname] = { html: html, webContents: contents }
+  backgroundPages[manifest.extensionId] = { html: html, webContents: contents }
   contents.loadURL(url.format({
     protocol: 'chrome-extension',
     slashes: true,
-    hostname: manifest.hostname,
+    hostname: manifest.extensionId,
     pathname: '_generated_background_page.html'
   }))
-  contents.openDevTools()
 }
 
 const removeBackgroundPages = function (manifest) {
-  if (!backgroundPages[manifest.hostname]) return
+  if (!backgroundPages[manifest.extensionId]) return
 
-  backgroundPages[manifest.hostname].webContents.destroy()
-  delete backgroundPages[manifest.hostname]
+  backgroundPages[manifest.extensionId].webContents.destroy()
+  delete backgroundPages[manifest.extensionId]
 }
 
 // Handle the chrome.* API messages.
 let nextId = 0
 
-ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, hostname, connectInfo) {
-  const page = backgroundPages[hostname]
+ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
+  const page = backgroundPages[extensionId]
   if (!page) {
-    console.error(`Connect to unkown extension ${hostname}`)
+    console.error(`Connect to unkown extension ${extensionId}`)
     return
   }
 
@@ -93,7 +82,7 @@ ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, hostname, connectInfo) {
   event.sender.once('render-view-deleted', () => {
     page.webContents.sendToAll(`CHROME_PORT_ONDISCONNECT_${portId}`)
   })
-  page.webContents.sendToAll('CHROME_RUNTIME_ONCONNECT', event.sender.id, portId, hostname, connectInfo)
+  page.webContents.sendToAll('CHROME_RUNTIME_ONCONNECT', event.sender.id, portId, extensionId, connectInfo)
 })
 
 ipcMain.on('CHROME_PORT_DISCONNECT', function (event, webContentsId, portId) {
@@ -116,7 +105,7 @@ ipcMain.on('CHROME_PORT_POSTMESSAGE', function (event, webContentsId, portId, me
   contents.sendToAll(`CHROME_PORT_ONMESSAGE_${portId}`, message)
 })
 
-ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, webContentsId, hostname, details) {
+ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, webContentsId, extensionId, details) {
   const contents = webContents.fromId(webContentsId)
   if (!contents) {
     console.error(`Sending message to unkown webContentsId ${webContentsId}`)
@@ -125,17 +114,18 @@ ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, webContentsI
 
   let code, url
   if (details.file) {
+    const manifest = manifestMap[extensionId]
     code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
-    url = `chrome-extension://${hostname}/${details.file}`
+    url = `chrome-extension://${extensionId}${details.file}`
   } else {
     code = details.code
-    url = `chrome-extension://${hostname}/${String(Math.random()).substr(2, 8)}.js`
+    url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
   }
 
-  contents.send('CHROME_TABS_EXECUTESCRIPT', requestId, event.sender.id, hostname, url, code)
+  contents.send('CHROME_TABS_EXECUTESCRIPT', requestId, event.sender.id, extensionId, url, code)
 })
 
-ipcMain.on(`CHROME_TABS_EXECUTESCRIPT_RESULT`, (event, requestId, webContentsId, result) => {
+ipcMain.on('CHROME_TABS_EXECUTESCRIPT_RESULT', (event, requestId, webContentsId, result) => {
   const contents = webContents.fromId(webContentsId)
   if (!contents) {
     console.error(`Sending message to unkown webContentsId ${webContentsId}`)
@@ -153,7 +143,7 @@ const injectContentScripts = function (manifest) {
 
   const readArrayOfFiles = function (relativePath) {
     return {
-      url: `chrome-extension://${manifest.hostname}/${relativePath}`,
+      url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
       code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
     }
   }
@@ -168,6 +158,7 @@ const injectContentScripts = function (manifest) {
 
   try {
     const entry = {
+      extensionId: manifest.extensionId,
       contentScripts: manifest.content_scripts.map(contentScriptToEntry)
     }
     contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
@@ -195,9 +186,16 @@ const manifestToExtensionInfo = function (manifest) {
 }
 
 // Load the extensions for the window.
+const loadExtension = function (manifest) {
+  startBackgroundPages(manifest)
+  injectContentScripts(manifest)
+}
+
 const loadDevToolsExtensions = function (win, manifests) {
   if (!win.devToolsWebContents) return
 
+  manifests.forEach(loadExtension)
+
   const extensionInfoArray = manifests.map(manifestToExtensionInfo)
   win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
 }
@@ -233,8 +231,8 @@ app.once('ready', function () {
     if (!parsed.hostname || !parsed.path) return callback()
     if (!/extension-\d+/.test(parsed.hostname)) return callback()
 
-    const directory = getPathForHost(parsed.hostname)
-    if (!directory) return callback()
+    const manifest = manifestMap[parsed.hostname]
+    if (!manifest) return callback()
 
     if (parsed.path === '/_generated_background_page.html' &&
         backgroundPages[parsed.hostname]) {
@@ -244,7 +242,7 @@ app.once('ready', function () {
       })
     }
 
-    fs.readFile(path.join(directory, parsed.path), function (err, content) {
+    fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
       if (err) {
         return callback(-6)  // FILE_NOT_FOUND
       } else {
@@ -266,8 +264,7 @@ app.once('ready', function () {
       for (const srcDirectory of loadedExtensions) {
         // Start background pages and set content scripts.
         const manifest = getManifestFromPath(srcDirectory)
-        startBackgroundPages(manifest)
-        injectContentScripts(manifest)
+        loadExtension(manifest)
       }
     }
   } catch (error) {
@@ -285,12 +282,13 @@ app.once('ready', function () {
     }
   }
   BrowserWindow.removeDevToolsExtension = function (name) {
-    const manifest = manifestMap[name]
+    const manifest = manifestNameMap[name]
     if (!manifest) return
 
     removeBackgroundPages(manifest)
     removeContentScripts(manifest)
-    delete manifestMap[name]
+    delete manifestMap[manifest.extensionId]
+    delete manifestNameMap[name]
   }
 
   // Load extensions automatically when devtools is opened.

+ 4 - 0
lib/renderer/chrome-api.js

@@ -44,6 +44,7 @@ class Port {
   constructor (webContentsId, portId, extensionId, name) {
     this.webContentsId = webContentsId
     this.portId = portId
+    this.disconnected = false
 
     this.name = name
     this.onDisconnect = new Event()
@@ -60,6 +61,8 @@ class Port {
   }
 
   disconnect () {
+    if (this.disconnected) return
+
     ipcRenderer.send('CHROME_PORT_DISCONNECT', this.webContentsId, this.portId)
     this._onDisconnect()
   }
@@ -69,6 +72,7 @@ class Port {
   }
 
   _onDisconnect () {
+    this.disconnected = true
     ipcRenderer.removeAllListeners(`CHROME_PORT_ONMESSAGE_${this.portId}`)
     this.onDisconnect.emit()
   }

+ 8 - 6
lib/renderer/content-scripts-injector.js

@@ -12,26 +12,26 @@ const matchesPattern = function (pattern) {
 
 // Run the code with chrome API integrated.
 const runContentScript = function (extensionId, url, code) {
-  const chrome = {}
-  require('./chrome-api').injectTo(extensionId, chrome)
+  const context = {}
+  require('./chrome-api').injectTo(extensionId, context)
   const wrapper = `(function (chrome) {\n  ${code}\n  })`
   const compiledWrapper = runInThisContext(wrapper, {
     filename: url,
     lineOffset: 1,
     displayErrors: true
   })
-  return compiledWrapper.call(this, chrome)
+  return compiledWrapper.call(this, context.chrome)
 }
 
 // Run injected scripts.
 // https://developer.chrome.com/extensions/content_scripts
-const injectContentScript = function (script) {
+const injectContentScript = function (extensionId, script) {
   for (const match of script.matches) {
     if (!matchesPattern(match)) return
   }
 
   for (const {url, code} of script.js) {
-    const fire = runContentScript.bind(window, script.extensionId, url, code)
+    const fire = runContentScript.bind(window, extensionId, url, code)
     if (script.runAt === 'document_start') {
       process.once('document-start', fire)
     } else if (script.runAt === 'document_end') {
@@ -53,7 +53,9 @@ const preferences = process.getRenderProcessPreferences()
 if (preferences) {
   for (const pref of preferences) {
     if (pref.contentScripts) {
-      pref.contentScripts.forEach(injectContentScript)
+      for (const script of pref.contentScripts) {
+        injectContentScript(pref.extensionId, script)
+      }
     }
   }
 }