diff --git a/BUILD.gn b/BUILD.gn index e767f31273d..09cbd511731 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -139,15 +139,6 @@ webpack_build("electron_isolated_renderer_bundle") { out_file = "$target_gen_dir/js2c/isolated_bundle.js" } -webpack_build("electron_content_script_bundle") { - deps = [ ":build_electron_definitions" ] - - inputs = auto_filenames.content_script_bundle_deps - - config_file = "//electron/build/webpack/webpack.config.content_script.js" - out_file = "$target_gen_dir/js2c/content_script_bundle.js" -} - copy("electron_js2c_copy") { sources = [ "lib/common/asar.js", @@ -159,7 +150,6 @@ copy("electron_js2c_copy") { action("electron_js2c") { deps = [ ":electron_browser_bundle", - ":electron_content_script_bundle", ":electron_isolated_renderer_bundle", ":electron_js2c_copy", ":electron_renderer_bundle", @@ -169,7 +159,6 @@ action("electron_js2c") { webpack_sources = [ "$target_gen_dir/js2c/browser_init.js", - "$target_gen_dir/js2c/content_script_bundle.js", "$target_gen_dir/js2c/isolated_bundle.js", "$target_gen_dir/js2c/renderer_init.js", "$target_gen_dir/js2c/sandbox_bundle.js", diff --git a/build/webpack/webpack.config.content_script.js b/build/webpack/webpack.config.content_script.js deleted file mode 100644 index aaf39fbc070..00000000000 --- a/build/webpack/webpack.config.content_script.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = require('./webpack.config.base')({ - target: 'content_script', - alwaysHasNode: false -}) diff --git a/filenames.auto.gni b/filenames.auto.gni index 7241e72b588..bd39dbe9ae4 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -153,12 +153,6 @@ auto_filenames = { "lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/remote.js", "lib/renderer/api/web-frame.ts", - "lib/renderer/chrome-api.ts", - "lib/renderer/content-scripts-injector.ts", - "lib/renderer/extensions/event.ts", - "lib/renderer/extensions/i18n.ts", - "lib/renderer/extensions/storage.ts", - "lib/renderer/extensions/web-navigation.ts", "lib/renderer/inspector.ts", "lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal.ts", @@ -190,24 +184,6 @@ auto_filenames = { "tsconfig.json", ] - content_script_bundle_deps = [ - "lib/common/electron-binding-setup.ts", - "lib/common/webpack-globals-provider.ts", - "lib/content_script/init.js", - "lib/renderer/api/context-bridge.ts", - "lib/renderer/chrome-api.ts", - "lib/renderer/extensions/event.ts", - "lib/renderer/extensions/i18n.ts", - "lib/renderer/extensions/storage.ts", - "lib/renderer/extensions/web-navigation.ts", - "lib/renderer/ipc-renderer-internal-utils.ts", - "lib/renderer/ipc-renderer-internal.ts", - "lib/renderer/window-setup.ts", - "package.json", - "tsconfig.electron.json", - "tsconfig.json", - ] - browser_bundle_deps = [ "lib/browser/api/app.ts", "lib/browser/api/auto-updater.js", @@ -247,7 +223,6 @@ auto_filenames = { "lib/browser/api/web-contents-view.js", "lib/browser/api/web-contents.js", "lib/browser/chrome-extension-shim.js", - "lib/browser/chrome-extension.js", "lib/browser/crash-reporter-init.js", "lib/browser/default-menu.ts", "lib/browser/desktop-capturer.ts", @@ -308,12 +283,6 @@ auto_filenames = { "lib/renderer/api/module-list.ts", "lib/renderer/api/remote.js", "lib/renderer/api/web-frame.ts", - "lib/renderer/chrome-api.ts", - "lib/renderer/content-scripts-injector.ts", - "lib/renderer/extensions/event.ts", - "lib/renderer/extensions/i18n.ts", - "lib/renderer/extensions/storage.ts", - "lib/renderer/extensions/web-navigation.ts", "lib/renderer/init.ts", "lib/renderer/inspector.ts", "lib/renderer/ipc-renderer-internal-utils.ts", diff --git a/lib/browser/chrome-extension-shim.js b/lib/browser/chrome-extension-shim.js index 4f4a651ea9b..48919f51601 100644 --- a/lib/browser/chrome-extension-shim.js +++ b/lib/browser/chrome-extension-shim.js @@ -4,10 +4,6 @@ // BrowserWindow-based extensions stuff to the new native-backed extensions // API. -if (!process.electronBinding('features').isExtensionsEnabled()) { - throw new Error('Attempted to load JS chrome-extension shim without //extensions support enabled'); -} - const { app, session, BrowserWindow, deprecate } = require('electron'); app.whenReady().then(function () { diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js deleted file mode 100644 index a4232a6f9d9..00000000000 --- a/lib/browser/chrome-extension.js +++ /dev/null @@ -1,542 +0,0 @@ -'use strict'; - -if (process.electronBinding('features').isExtensionsEnabled()) { - throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled'); -} - -const { app, webContents, BrowserWindow } = require('electron'); -const { getAllWebContents } = process.electronBinding('web_contents'); -const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); -const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); - -const { Buffer } = require('buffer'); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); -const util = require('util'); - -// 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(); -}; - -const isWindowOrWebView = function (webContents) { - const type = webContents.getType(); - return type === 'window' || type === 'webview'; -}; - -const isBackgroundPage = function (webContents) { - return webContents.getType() === 'backgroundPage'; -}; - -// Create or get manifest object from |srcDirectory|. -const getManifestFromPath = function (srcDirectory) { - let manifest; - let manifestContent; - - try { - manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json')); - } catch (readError) { - console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`); - console.warn(readError.stack || readError); - throw readError; - } - - try { - manifest = JSON.parse(manifestContent); - } catch (parseError) { - console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`); - console.warn(parseError.stack || parseError); - throw parseError; - } - - if (!manifestNameMap[manifest.name]) { - const extensionId = generateExtensionIdFromName(manifest.name); - manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest; - - let extensionURL = url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: extensionId, - pathname: manifest.devtools_page - }); - - // Chromium requires that startPage matches '([^:]+:\/\/[^/]*)\/' - // We also can't use the file:// protocol here since that would make Chromium - // treat all extension resources as being relative to root which we don't want. - if (!manifest.devtools_page) extensionURL += '/'; - - Object.assign(manifest, { - srcDirectory: srcDirectory, - extensionId: extensionId, - startPage: extensionURL - }); - - return manifest; - } else if (manifest && manifest.name) { - console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`); - return manifest; - } -}; - -// Manage the background pages. -const backgroundPages = {}; - -const startBackgroundPages = function (manifest) { - if (backgroundPages[manifest.extensionId] || !manifest.background) return; - - let html; - let name; - if (manifest.background.page) { - name = manifest.background.page; - html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page)); - } else { - name = '_generated_background_page.html'; - const scripts = manifest.background.scripts.map((name) => { - return ``; - }).join(''); - html = Buffer.from(`${scripts}`); - } - - const contents = webContents.create({ - partition: 'persist:__chrome_extension', - type: 'backgroundPage', - sandbox: true, - enableRemoteModule: false - }); - backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }; - contents.loadURL(url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: manifest.extensionId, - pathname: name - })); -}; - -const removeBackgroundPages = function (manifest) { - if (!backgroundPages[manifest.extensionId]) return; - - backgroundPages[manifest.extensionId].webContents.destroy(); - delete backgroundPages[manifest.extensionId]; -}; - -const sendToBackgroundPages = function (...args) { - for (const page of Object.values(backgroundPages)) { - if (!page.webContents.isDestroyed()) { - page.webContents._sendInternalToAll(...args); - } - } -}; - -// Dispatch web contents events to Chrome APIs -const hookWebContentsEvents = function (webContents) { - const tabId = webContents.id; - - sendToBackgroundPages('CHROME_TABS_ONCREATED'); - - webContents.on('will-navigate', (event, url) => { - sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { - frameId: 0, - parentFrameId: -1, - processId: webContents.getProcessId(), - tabId: tabId, - timeStamp: Date.now(), - url: url - }); - }); - - webContents.on('did-navigate', (event, url) => { - sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { - frameId: 0, - parentFrameId: -1, - processId: webContents.getProcessId(), - tabId: tabId, - timeStamp: Date.now(), - url: url - }); - }); - - webContents.once('destroyed', () => { - sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId); - }); -}; - -// Handle the chrome.* API messages. -let nextId = 0; - -ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { - if (isBackgroundPage(event.sender)) { - throw new Error('chrome.runtime.connect is not supported in background page'); - } - - const page = backgroundPages[extensionId]; - if (!page || page.webContents.isDestroyed()) { - throw new Error(`Connect to unknown extension ${extensionId}`); - } - - const tabId = page.webContents.id; - const portId = ++nextId; - - event.sender.once('render-view-deleted', () => { - if (page.webContents.isDestroyed()) return; - page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`); - }); - page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo); - - return { tabId, portId }; -}); - -ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { - const manifest = manifestMap[extensionId]; - if (!manifest) { - throw new Error(`Invalid extensionId: ${extensionId}`); - } - return manifest; -}); - -ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { - if (isBackgroundPage(event.sender)) { - throw new Error('chrome.runtime.sendMessage is not supported in background page'); - } - - const page = backgroundPages[extensionId]; - if (!page || page.webContents.isDestroyed()) { - throw new Error(`Connect to unknown extension ${extensionId}`); - } - - return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message); -}); - -ipcMainInternal.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) { - const contents = webContents.fromId(tabId); - if (!contents) { - throw new Error(`Sending message to unknown tab ${tabId}`); - } - - const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id; - - return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message); -}); - -const getLanguage = () => { - return app.getLocale().replace(/-.*$/, '').toLowerCase(); -}; - -const getMessagesPath = (extensionId) => { - const metadata = manifestMap[extensionId]; - if (!metadata) { - throw new Error(`Invalid extensionId: ${extensionId}`); - } - - const localesDirectory = path.join(metadata.srcDirectory, '_locales'); - const language = getLanguage(); - - try { - const filename = path.join(localesDirectory, language, 'messages.json'); - fs.accessSync(filename, fs.constants.R_OK); - return filename; - } catch { - const defaultLocale = metadata.default_locale || 'en'; - return path.join(localesDirectory, defaultLocale, 'messages.json'); - } -}; - -ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) { - const messagesPath = getMessagesPath(extensionId); - return fs.promises.readFile(messagesPath, 'utf8'); -}); - -const validStorageTypes = new Set(['sync', 'local']); - -const getChromeStoragePath = (storageType, extensionId) => { - if (!validStorageTypes.has(storageType)) { - throw new Error(`Invalid storageType: ${storageType}`); - } - - if (!manifestMap[extensionId]) { - throw new Error(`Invalid extensionId: ${extensionId}`); - } - - return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`); -}; - -ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { - const filePath = getChromeStoragePath(storageType, extensionId); - - try { - return await fs.promises.readFile(filePath, 'utf8'); - } catch (error) { - if (error.code === 'ENOENT') { - return null; - } else { - throw error; - } - } -}); - -ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { - const filePath = getChromeStoragePath(storageType, extensionId); - - try { - await fs.promises.mkdir(path.dirname(filePath), { recursive: true }); - } catch { - // we just ignore the errors of mkdir - } - - return fs.promises.writeFile(filePath, data, 'utf8'); -}); - -const isChromeExtension = function (pageURL) { - const { protocol } = url.parse(pageURL); - return protocol === 'chrome-extension:'; -}; - -const assertChromeExtension = function (contents, api) { - const pageURL = contents._getURL(); - if (!isChromeExtension(pageURL)) { - console.error(`Blocked ${pageURL} from calling ${api}`); - throw new Error(`Blocked ${api}`); - } -}; - -ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { - assertChromeExtension(event.sender, 'chrome.tabs.executeScript()'); - - const contents = webContents.fromId(tabId); - if (!contents) { - throw new Error(`Sending message to unknown tab ${tabId}`); - } - - let code, url; - if (details.file) { - const manifest = manifestMap[extensionId]; - code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))); - url = `chrome-extension://${extensionId}${details.file}`; - } else { - code = details.code; - url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`; - } - - return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code); -}); - -exports.getContentScripts = () => { - return Object.values(contentScripts); -}; - -// Transfer the content scripts to renderer. -const contentScripts = {}; - -const injectContentScripts = function (manifest) { - if (contentScripts[manifest.name] || !manifest.content_scripts) return; - - const readArrayOfFiles = function (relativePath) { - return { - url: `chrome-extension://${manifest.extensionId}/${relativePath}`, - code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath))) - }; - }; - - const contentScriptToEntry = function (script) { - return { - matches: script.matches, - js: script.js ? script.js.map(readArrayOfFiles) : [], - css: script.css ? script.css.map(readArrayOfFiles) : [], - runAt: script.run_at || 'document_idle', - allFrames: script.all_frames || false - }; - }; - - try { - const entry = { - extensionId: manifest.extensionId, - contentScripts: manifest.content_scripts.map(contentScriptToEntry) - }; - contentScripts[manifest.name] = entry; - } catch (e) { - console.error('Failed to read content scripts', e); - } -}; - -const removeContentScripts = function (manifest) { - if (!contentScripts[manifest.name]) return; - - delete contentScripts[manifest.name]; -}; - -// Transfer the |manifest| to a format that can be recognized by the -// |DevToolsAPI.addExtensions|. -const manifestToExtensionInfo = function (manifest) { - return { - startPage: manifest.startPage, - srcDirectory: manifest.srcDirectory, - name: manifest.name, - exposeExperimentalAPIs: true - }; -}; - -// 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); - extensionInfoArray.forEach((extension) => { - win.devToolsWebContents._grantOriginAccess(extension.startPage); - }); - - extensionInfoArray.forEach((extensionInfo) => { - const info = JSON.stringify(extensionInfo); - win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${info})`); - }); -}; - -app.on('web-contents-created', function (event, webContents) { - if (!isWindowOrWebView(webContents)) return; - - hookWebContentsEvents(webContents); - webContents.on('devtools-opened', function () { - loadDevToolsExtensions(webContents, Object.values(manifestMap)); - }); -}); - -// The chrome-extension: can map a extension URL request to real file path. -const chromeExtensionHandler = function (request, callback) { - const parsed = url.parse(request.url); - if (!parsed.hostname || !parsed.path) return callback(); - - const manifest = manifestMap[parsed.hostname]; - if (!manifest) return callback(); - - const page = backgroundPages[parsed.hostname]; - if (page && parsed.path === `/${page.name}`) { - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - return callback({ - mimeType: 'text/html', - data: page.html - }); - } - - fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) { - if (err) { - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - return callback(-6); // FILE_NOT_FOUND - } else { - return callback(content); - } - }); -}; - -app.on('session-created', function (ses) { - ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler); -}); - -// The persistent path of "DevTools Extensions" preference file. -let loadedDevToolsExtensionsPath = null; - -app.on('will-quit', function () { - try { - const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) - .map(name => manifestNameMap[name].srcDirectory); - if (loadedDevToolsExtensions.length > 0) { - try { - fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)); - } catch { - // Ignore error - } - fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)); - } else { - fs.unlinkSync(loadedDevToolsExtensionsPath); - } - } catch { - // Ignore error - } -}); - -// We can not use protocol or BrowserWindow until app is ready. -app.whenReady().then(function () { - // The public API to add/remove extensions. - BrowserWindow.addExtension = function (srcDirectory) { - const manifest = getManifestFromPath(srcDirectory); - if (manifest) { - loadExtension(manifest); - for (const webContents of getAllWebContents()) { - if (isWindowOrWebView(webContents)) { - loadDevToolsExtensions(webContents, [manifest]); - } - } - return manifest.name; - } - }; - - BrowserWindow.removeExtension = function (name) { - const manifest = manifestNameMap[name]; - if (!manifest) return; - - removeBackgroundPages(manifest); - removeContentScripts(manifest); - delete manifestMap[manifest.extensionId]; - delete manifestNameMap[name]; - }; - - BrowserWindow.getExtensions = function () { - const extensions = {}; - Object.keys(manifestNameMap).forEach(function (name) { - const manifest = manifestNameMap[name]; - extensions[name] = { name: manifest.name, version: manifest.version }; - }); - 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; - }; - - // Load persisted extensions. - loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions'); - try { - const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath)); - if (Array.isArray(loadedDevToolsExtensions)) { - for (const srcDirectory of loadedDevToolsExtensions) { - // Start background pages and set content scripts. - BrowserWindow.addDevToolsExtension(srcDirectory); - } - } - } catch (error) { - if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') { - console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath); - console.error(error); - } - } -}); diff --git a/lib/browser/init.ts b/lib/browser/init.ts index 220e340a46a..412d3562377 100644 --- a/lib/browser/init.ts +++ b/lib/browser/init.ts @@ -160,11 +160,7 @@ require('@electron/internal/browser/devtools'); const features = process.electronBinding('features'); // Load the chrome extension support. -if (features.isExtensionsEnabled()) { - require('@electron/internal/browser/chrome-extension-shim'); -} else { - require('@electron/internal/browser/chrome-extension'); -} +require('@electron/internal/browser/chrome-extension-shim'); if (features.isRemoteModuleEnabled()) { require('@electron/internal/browser/remote/server'); diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index de3eb4a0604..acf13a4c191 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -96,21 +96,12 @@ const getPreloadScript = async function (preloadPath) { return { preloadPath, preloadSrc, preloadError }; }; -if (features.isExtensionsEnabled()) { - ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []); -} else { - const { getContentScripts } = require('@electron/internal/browser/chrome-extension'); - ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts()); -} +ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []); ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { const preloadPaths = event.sender._getPreloadPaths(); - let contentScripts = []; - if (!features.isExtensionsEnabled()) { - const { getContentScripts } = require('@electron/internal/browser/chrome-extension'); - contentScripts = getContentScripts(); - } + const contentScripts = []; const webPreferences = event.sender.getLastWebPreferences() || {}; diff --git a/lib/content_script/init.js b/lib/content_script/init.js deleted file mode 100644 index b2fa8b8f126..00000000000 --- a/lib/content_script/init.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -/* global nodeProcess, isolatedWorld, worldId */ - -const { EventEmitter } = require('events'); - -process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer'); - -const v8Util = process.electronBinding('v8_util'); - -// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the -// "ipc-internal" hidden value -v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal')); - -// The process object created by webpack is not an event emitter, fix it so -// the API is more compatible with non-sandboxed renderers. -for (const prop of Object.keys(EventEmitter.prototype)) { - if (Object.prototype.hasOwnProperty.call(process, prop)) { - delete process[prop]; - } -} -Object.setPrototypeOf(process, EventEmitter.prototype); - -const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args'); - -if (isolatedWorldArgs) { - const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } = isolatedWorldArgs; - const { windowSetup } = require('@electron/internal/renderer/window-setup'); - windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled); -} - -const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`); - -if (extensionId) { - const chromeAPI = require('@electron/internal/renderer/chrome-api'); - chromeAPI.injectTo(extensionId, window); -} diff --git a/lib/renderer/chrome-api.ts b/lib/renderer/chrome-api.ts deleted file mode 100644 index cdd9db63952..00000000000 --- a/lib/renderer/chrome-api.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; -import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'; -import * as url from 'url'; - -import { Event } from '@electron/internal/renderer/extensions/event'; - -class Tab { - public id: number - - constructor (tabId: number) { - this.id = tabId; - } -} - -class MessageSender { - public tab: Tab | null - public id: string - public url: string - - constructor (tabId: number, extensionId: string) { - this.tab = tabId ? new Tab(tabId) : null; - this.id = extensionId; - this.url = `chrome-extension://${extensionId}`; - } -} - -class Port { - public disconnected: boolean = false - public onDisconnect = new Event() - public onMessage = new Event() - public sender: MessageSender - - constructor (public tabId: number, public portId: number, extensionId: string, public name: string) { - this.onDisconnect = new Event(); - this.onMessage = new Event(); - this.sender = new MessageSender(tabId, extensionId); - - ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => { - this._onDisconnect(); - }); - - ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, ( - _event: Electron.Event, message: any - ) => { - const sendResponse = function () { console.error('sendResponse is not implemented'); }; - this.onMessage.emit(JSON.parse(message), this.sender, sendResponse); - }); - } - - disconnect () { - if (this.disconnected) return; - - ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`); - this._onDisconnect(); - } - - postMessage (message: any) { - ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message)); - } - - _onDisconnect () { - this.disconnected = true; - ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`); - this.onDisconnect.emit(); - } -} - -// Inject chrome API to the |context| -export function injectTo (extensionId: string, context: any) { - if (process.electronBinding('features').isExtensionsEnabled()) { - throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled'); - } - - const chrome = context.chrome = context.chrome || {}; - - ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, ( - _event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string } - ) => { - chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name)); - }); - - ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, ( - _event: Electron.Event, tabId: number, message: string - ) => { - return new Promise(resolve => { - chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve); - }); - }); - - ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => { - chrome.tabs.onCreated.emit(new Tab(tabId)); - }); - - ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => { - chrome.tabs.onRemoved.emit(tabId); - }); - - chrome.runtime = { - id: extensionId, - - // https://developer.chrome.com/extensions/runtime#method-getURL - getURL: function (path: string) { - return url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: extensionId, - pathname: path - }); - }, - - // https://developer.chrome.com/extensions/runtime#method-getManifest - getManifest: function () { - const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId); - return manifest; - }, - - // https://developer.chrome.com/extensions/runtime#method-connect - connect (...args: Array) { - // Parse the optional args. - let targetExtensionId = extensionId; - let connectInfo = { name: '' }; - if (args.length === 1) { - if (typeof args[0] === 'string') { - targetExtensionId = args[0]; - } else { - connectInfo = args[0]; - } - } else if (args.length === 2) { - [targetExtensionId, connectInfo] = args; - } - - const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo); - return new Port(tabId, portId, extensionId, connectInfo.name); - }, - - // https://developer.chrome.com/extensions/runtime#method-sendMessage - sendMessage (...args: Array) { - // Parse the optional args. - const targetExtensionId = extensionId; - let message: string; - let options: Object | undefined; - let responseCallback: Chrome.Tabs.SendMessageCallback = () => {}; - - if (typeof args[args.length - 1] === 'function') { - responseCallback = args.pop(); - } - - if (args.length === 1) { - [message] = args; - } else if (args.length === 2) { - if (typeof args[0] === 'string') { - [extensionId, message] = args; - } else { - [message, options] = args; - } - } else { - [extensionId, message, options] = args; - } - - if (options) { - console.error('options are not supported'); - } - - ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback); - }, - - onConnect: new Event(), - onMessage: new Event(), - onInstalled: new Event() - }; - - chrome.tabs = { - // https://developer.chrome.com/extensions/tabs#method-executeScript - executeScript ( - tabId: number, - details: Chrome.Tabs.ExecuteScriptDetails, - resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {} - ) { - ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details) - .then((result: any) => resultCallback([result])); - }, - - // https://developer.chrome.com/extensions/tabs#method-sendMessage - sendMessage ( - tabId: number, - message: any, - _options: Chrome.Tabs.SendMessageDetails, - responseCallback: Chrome.Tabs.SendMessageCallback = () => {} - ) { - ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback); - }, - - onUpdated: new Event(), - onCreated: new Event(), - onRemoved: new Event() - }; - - chrome.extension = { - getURL: chrome.runtime.getURL, - connect: chrome.runtime.connect, - onConnect: chrome.runtime.onConnect, - sendMessage: chrome.runtime.sendMessage, - onMessage: chrome.runtime.onMessage - }; - - chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId); - - chrome.pageAction = { - show () {}, - hide () {}, - setTitle () {}, - getTitle () {}, - setIcon () {}, - setPopup () {}, - getPopup () {} - }; - - chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId); - chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup(); - - // Electron has no concept of a browserAction but we should stub these APIs for compatibility - chrome.browserAction = { - setIcon () {}, - setPopup () {} - }; -} diff --git a/lib/renderer/init.ts b/lib/renderer/init.ts index 5f03d9911ad..bcae9d61c85 100644 --- a/lib/renderer/init.ts +++ b/lib/renderer/init.ts @@ -53,7 +53,6 @@ v8Util.setHiddenValue(global, 'ipcNative', { // Use electron module after everything is ready. const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); -const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); const { webFrameInit } = require('@electron/internal/renderer/web-frame-init'); webFrameInit(); @@ -100,10 +99,6 @@ switch (window.location.protocol) { break; } case 'chrome-extension:': { - // Inject the chrome.* APIs that chrome extensions require - if (!process.electronBinding('features').isExtensionsEnabled()) { - require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window); - } break; } case 'chrome:': @@ -112,12 +107,6 @@ switch (window.location.protocol) { // Override default web functions. const { windowSetup } = require('@electron/internal/renderer/window-setup'); windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled); - - // Inject content scripts. - if (!process.electronBinding('features').isExtensionsEnabled()) { - const contentScripts = ipcRendererUtils.invokeSync('ELECTRON_GET_CONTENT_SCRIPTS') as Electron.ContentScriptEntry[]; - require('@electron/internal/renderer/content-scripts-injector')(contentScripts); - } } } diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 0aece33d112..9dd7a98a1b0 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -129,10 +129,6 @@ switch (window.location.protocol) { break; } case 'chrome-extension:': { - // Inject the chrome.* APIs that chrome extensions require - if (!process.electronBinding('features').isExtensionsEnabled()) { - require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window); - } break; } case 'chrome': { @@ -142,11 +138,6 @@ switch (window.location.protocol) { // Override default web functions. const { windowSetup } = require('@electron/internal/renderer/window-setup'); windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled); - - // Inject content scripts. - if (!process.electronBinding('features').isExtensionsEnabled()) { - require('@electron/internal/renderer/content-scripts-injector')(contentScripts); - } } } diff --git a/script/gen-filenames.js b/script/gen-filenames.js index f306ba83f00..dc02c00a78d 100644 --- a/script/gen-filenames.js +++ b/script/gen-filenames.js @@ -23,10 +23,6 @@ const main = async () => { name: 'isolated_bundle_deps', config: 'webpack.config.isolated_renderer.js' }, - { - name: 'content_script_bundle_deps', - config: 'webpack.config.content_script.js' - }, { name: 'browser_bundle_deps', config: 'webpack.config.browser.js' diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 3168dae35bb..53fdb76148e 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -1,16 +1,15 @@ import { expect } from 'chai'; -import { app, session, BrowserWindow, ipcMain, WebContents, Extension } from 'electron/main'; +import { session, BrowserWindow, ipcMain, WebContents, Extension } from 'electron/main'; import { closeAllWindows, closeWindow } from './window-helpers'; import * as http from 'http'; import { AddressInfo } from 'net'; import * as path from 'path'; import * as fs from 'fs'; -import { ifdescribe } from './spec-helpers'; import { emittedOnce, emittedNTimes } from './events-helpers'; const fixtures = path.join(__dirname, 'fixtures'); -ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome extensions', () => { +describe('chrome extensions', () => { // NB. extensions are only allowed on http://, https:// and ftp:// (!) urls by default. let server: http.Server; let url: string; @@ -498,280 +497,3 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex }); }); }); - -ifdescribe(!process.electronBinding('features').isExtensionsEnabled())('chrome extensions', () => { - const fixtures = path.resolve(__dirname, 'fixtures'); - let w: BrowserWindow; - - before(() => { - BrowserWindow.addExtension(path.join(fixtures, 'extensions/chrome-api')); - }); - - after(() => { - BrowserWindow.removeExtension('chrome-api'); - }); - - beforeEach(() => { - w = new BrowserWindow({ show: false }); - }); - - afterEach(() => closeWindow(w).then(() => { w = null as unknown as BrowserWindow; })); - - it('chrome.runtime.connect parses arguments properly', async function () { - await w.loadURL('about:blank'); - - const promise = emittedOnce(w.webContents, 'console-message'); - - const message = { method: 'connect' }; - w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`); - - const [,, responseString] = await promise; - const response = JSON.parse(responseString); - - expect(response).to.be.true(); - }); - - it('runtime.getManifest returns extension manifest', async () => { - const actualManifest = (() => { - const data = fs.readFileSync(path.join(fixtures, 'extensions/chrome-api/manifest.json'), 'utf-8'); - return JSON.parse(data); - })(); - - await w.loadURL('about:blank'); - - const promise = emittedOnce(w.webContents, 'console-message'); - - const message = { method: 'getManifest' }; - w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`); - - const [,, manifestString] = await promise; - const manifest = JSON.parse(manifestString); - - expect(manifest.name).to.equal(actualManifest.name); - expect(manifest.content_scripts).to.have.lengthOf(actualManifest.content_scripts.length); - }); - - it('chrome.tabs.sendMessage receives the response', async function () { - await w.loadURL('about:blank'); - - const promise = emittedOnce(w.webContents, 'console-message'); - - const message = { method: 'sendMessage', args: ['Hello World!'] }; - w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`); - - const [,, responseString] = await promise; - const response = JSON.parse(responseString); - - expect(response.message).to.equal('Hello World!'); - expect(response.tabId).to.equal(w.webContents.id); - }); - - it('chrome.tabs.executeScript receives the response', async function () { - await w.loadURL('about:blank'); - - const promise = emittedOnce(w.webContents, 'console-message'); - - const message = { method: 'executeScript', args: ['1 + 2'] }; - w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`); - - const [,, responseString] = await promise; - const response = JSON.parse(responseString); - - expect(response).to.equal(3); - }); - - describe('extensions and dev tools extensions', () => { - let showPanelTimeoutId: NodeJS.Timeout | null = null; - - const showLastDevToolsPanel = (w: BrowserWindow) => { - w.webContents.once('devtools-opened', () => { - const show = () => { - if (w == null || w.isDestroyed()) return; - const { devToolsWebContents } = w as unknown as { devToolsWebContents: WebContents | undefined }; - if (devToolsWebContents == null || devToolsWebContents.isDestroyed()) { - return; - } - - const showLastPanel = () => { - // this is executed in the devtools context, where UI is a global - const { UI } = (window as any); - const lastPanelId = UI.inspectorView._tabbedPane._tabs.peekLast().id; - UI.inspectorView.showPanel(lastPanelId); - }; - devToolsWebContents.executeJavaScript(`(${showLastPanel})()`, false).then(() => { - showPanelTimeoutId = setTimeout(show, 100); - }); - }; - showPanelTimeoutId = setTimeout(show, 100); - }); - }; - - afterEach(() => { - if (showPanelTimeoutId != null) { - clearTimeout(showPanelTimeoutId); - showPanelTimeoutId = null; - } - }); - - describe('BrowserWindow.addDevToolsExtension', () => { - describe('for invalid extensions', () => { - it('throws errors for missing manifest.json files', () => { - const nonexistentExtensionPath = path.join(__dirname, 'does-not-exist'); - expect(() => { - BrowserWindow.addDevToolsExtension(nonexistentExtensionPath); - }).to.throw(/ENOENT: no such file or directory/); - }); - - it('throws errors for invalid manifest.json files', () => { - const badManifestExtensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest'); - expect(() => { - BrowserWindow.addDevToolsExtension(badManifestExtensionPath); - }).to.throw(/Unexpected token }/); - }); - }); - - describe('for a valid extension', () => { - const extensionName = 'foo'; - - before(() => { - const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo'); - BrowserWindow.addDevToolsExtension(extensionPath); - expect(BrowserWindow.getDevToolsExtensions()).to.have.property(extensionName); - }); - - after(() => { - BrowserWindow.removeDevToolsExtension('foo'); - expect(BrowserWindow.getDevToolsExtensions()).to.not.have.property(extensionName); - }); - - describe('when the devtools is docked', () => { - let message: any; - let w: BrowserWindow; - before(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); - const p = new Promise(resolve => ipcMain.once('answer', (event, message) => { - resolve(message); - })); - showLastDevToolsPanel(w); - w.loadURL('about:blank'); - w.webContents.openDevTools({ mode: 'bottom' }); - message = await p; - }); - after(closeAllWindows); - - describe('created extension info', function () { - it('has proper "runtimeId"', async function () { - expect(message).to.have.ownProperty('runtimeId'); - expect(message.runtimeId).to.equal(extensionName); - }); - it('has "tabId" matching webContents id', function () { - expect(message).to.have.ownProperty('tabId'); - expect(message.tabId).to.equal(w.webContents.id); - }); - it('has "i18nString" with proper contents', function () { - expect(message).to.have.ownProperty('i18nString'); - expect(message.i18nString).to.equal('foo - bar (baz)'); - }); - it('has "storageItems" with proper contents', function () { - expect(message).to.have.ownProperty('storageItems'); - expect(message.storageItems).to.deep.equal({ - local: { - set: { hello: 'world', world: 'hello' }, - remove: { world: 'hello' }, - clear: {} - }, - sync: { - set: { foo: 'bar', bar: 'foo' }, - remove: { foo: 'bar' }, - clear: {} - } - }); - }); - }); - }); - - describe('when the devtools is undocked', () => { - let message: any; - let w: BrowserWindow; - before(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); - showLastDevToolsPanel(w); - w.loadURL('about:blank'); - w.webContents.openDevTools({ mode: 'undocked' }); - message = await new Promise(resolve => ipcMain.once('answer', (event, message) => { - resolve(message); - })); - }); - after(closeAllWindows); - - describe('created extension info', function () { - it('has proper "runtimeId"', function () { - expect(message).to.have.ownProperty('runtimeId'); - expect(message.runtimeId).to.equal(extensionName); - }); - it('has "tabId" matching webContents id', function () { - expect(message).to.have.ownProperty('tabId'); - expect(message.tabId).to.equal(w.webContents.id); - }); - }); - }); - }); - }); - - it('works when used with partitions', async () => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - partition: 'temp' - } - }); - - const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo'); - BrowserWindow.addDevToolsExtension(extensionPath); - try { - showLastDevToolsPanel(w); - - const p: Promise = new Promise(resolve => ipcMain.once('answer', function (event, message) { - resolve(message); - })); - - w.loadURL('about:blank'); - w.webContents.openDevTools({ mode: 'bottom' }); - const message = await p; - expect(message.runtimeId).to.equal('foo'); - } finally { - BrowserWindow.removeDevToolsExtension('foo'); - await closeAllWindows(); - } - }); - - it('serializes the registered extensions on quit', () => { - const extensionName = 'foo'; - const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName); - const serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions'); - - BrowserWindow.addDevToolsExtension(extensionPath); - app.emit('will-quit'); - expect(JSON.parse(fs.readFileSync(serializedPath, 'utf8'))).to.deep.equal([extensionPath]); - - BrowserWindow.removeDevToolsExtension(extensionName); - app.emit('will-quit'); - expect(fs.existsSync(serializedPath)).to.be.false('file exists'); - }); - - describe('BrowserWindow.addExtension', () => { - it('throws errors for missing manifest.json files', () => { - expect(() => { - BrowserWindow.addExtension(path.join(__dirname, 'does-not-exist')); - }).to.throw('ENOENT: no such file or directory'); - }); - - it('throws errors for invalid manifest.json files', () => { - expect(() => { - BrowserWindow.addExtension(path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest')); - }).to.throw('Unexpected token }'); - }); - }); - }); -});