From d55b96fdf511a10ad647ae03596a105c61901d3e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 28 May 2016 17:51:49 +0900 Subject: [PATCH] Clean up the Chrome API implementation code --- lib/browser/chrome-extension.js | 86 ++++++++++++++++---------------- lib/renderer/chrome-api.js | 4 ++ lib/renderer/content-scripts-injector.js | 14 +++--- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index 6f710f9..e9abb99 100644 --- a/lib/browser/chrome-extension.js +++ b/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 `` @@ -60,30 +50,29 @@ const startBackgroundPages = function (manifest) { const html = new Buffer(`${scripts}`) 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. diff --git a/lib/renderer/chrome-api.js b/lib/renderer/chrome-api.js index a092559..c49fbd2 100644 --- a/lib/renderer/chrome-api.js +++ b/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() } diff --git a/lib/renderer/content-scripts-injector.js b/lib/renderer/content-scripts-injector.js index 1ef9fc2..6e41a8d 100644 --- a/lib/renderer/content-scripts-injector.js +++ b/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) + } } } } -- 2.7.4