diff --git a/filenames.gni b/filenames.gni index 0b623b0b5d54..48cb82497256 100644 --- a/filenames.gni +++ b/filenames.gni @@ -34,11 +34,13 @@ filenames = { "lib/browser/api/view.js", "lib/browser/api/web-contents.js", "lib/browser/api/web-contents-view.js", + "lib/browser/chrome-devtools.js", "lib/browser/chrome-extension.js", "lib/browser/default-menu.js", "lib/browser/guest-view-manager.js", "lib/browser/guest-window-manager.js", "lib/browser/init.js", + "lib/browser/ipc-main-internal-utils.js", "lib/browser/ipc-main-internal.js", "lib/browser/navigation-controller.js", "lib/browser/objects-registry.js", @@ -64,6 +66,7 @@ filenames = { "lib/renderer/content-scripts-injector.js", "lib/renderer/init.js", "lib/renderer/inspector.js", + "lib/renderer/ipc-renderer-internal-utils.js", "lib/renderer/ipc-renderer-internal.js", "lib/renderer/remote.js", "lib/renderer/security-warnings.js", diff --git a/lib/browser/chrome-devtools.js b/lib/browser/chrome-devtools.js new file mode 100644 index 000000000000..abcb1658446c --- /dev/null +++ b/lib/browser/chrome-devtools.js @@ -0,0 +1,121 @@ +'use strict' + +const { dialog, Menu } = require('electron') +const fs = require('fs') +const url = require('url') + +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') + +const convertToMenuTemplate = function (event, items) { + return items.map(function (item) { + const transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(event, item.subItems) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + } + + if (item.id != null) { + transformed.click = function () { + event._replyInternal('ELECTRON_INSPECTOR_CONTEXT_MENU_CLICK', item.id) + } + } + + return transformed + }) +} + +const getEditMenuItems = function () { + return [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' } + ] +} + +const isChromeDevTools = function (pageURL) { + const { protocol } = url.parse(pageURL) + return protocol === 'chrome-devtools:' +} + +const assertChromeDevTools = function (contents, api) { + const pageURL = contents._getURL() + if (!isChromeDevTools(pageURL)) { + console.error(`Blocked ${pageURL} from calling ${api}`) + throw new Error(`Blocked ${api}`) + } +} + +ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event, items, isEditMenu) { + assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()') + + const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(event, items) + const menu = Menu.buildFromTemplate(template) + const window = event.sender.getOwnerBrowserWindow() + + menu.once('menu-will-close', () => { + setTimeout(() => { + event._replyInternal('ELECTRON_INSPECTOR_CONTEXT_MENU_CLOSE') + }) + }) + + menu.popup({ window }) +}) + +ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', function (event) { + return new Promise((resolve, reject) => { + assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()') + + dialog.showOpenDialog({}, function (files) { + if (files) { + const path = files[0] + fs.readFile(path, (error, data) => { + if (error) { + reject(error) + } else { + resolve([path, data]) + } + }) + } else { + resolve([]) + } + }) + }) +}) + +ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', function (event, message, title) { + return new Promise((resolve, reject) => { + assertChromeDevTools(event.sender, 'window.confirm()') + + if (message == null) message = '' + if (title == null) title = '' + + const options = { + message: `${message}`, + title: `${title}`, + buttons: ['OK', 'Cancel'], + cancelId: 1 + } + const window = event.sender.getOwnerBrowserWindow() + dialog.showMessageBox(window, options, (response) => { + resolve(response === 0) + }) + }) +}) diff --git a/lib/browser/init.js b/lib/browser/init.js index c50c5f0d1eb8..7aa2346f625a 100644 --- a/lib/browser/init.js +++ b/lib/browser/init.js @@ -149,6 +149,9 @@ app.setPath('userData', path.join(app.getPath('appData'), app.getName())) app.setPath('userCache', path.join(app.getPath('cache'), app.getName())) app.setAppPath(packagePath) +// Load the chrome devtools support. +require('@electron/internal/browser/chrome-devtools') + // Load the chrome extension support. require('@electron/internal/browser/chrome-extension') diff --git a/lib/browser/ipc-main-internal-utils.js b/lib/browser/ipc-main-internal-utils.js new file mode 100644 index 000000000000..25145d8d02ef --- /dev/null +++ b/lib/browser/ipc-main-internal-utils.js @@ -0,0 +1,29 @@ +'use strict' + +const ipcMain = require('@electron/internal/browser/ipc-main-internal') +const errorUtils = require('@electron/internal/common/error-utils') + +const callHandler = async function (handler, event, args, reply) { + try { + const result = await handler(event, ...args) + reply([null, result]) + } catch (error) { + reply([errorUtils.serialize(error)]) + } +} + +exports.handle = function (channel, handler) { + ipcMain.on(channel, (event, requestId, ...args) => { + callHandler(handler, event, args, responseArgs => { + event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs) + }) + }) +} + +exports.handleSync = function (channel, handler) { + ipcMain.on(channel, (event, ...args) => { + callHandler(handler, event, args, responseArgs => { + event.returnValue = responseArgs + }) + }) +} diff --git a/lib/renderer/inspector.js b/lib/renderer/inspector.js index 5ed4e9d2be42..314301a9d6a5 100644 --- a/lib/renderer/inspector.js +++ b/lib/renderer/inspector.js @@ -1,6 +1,7 @@ 'use strict' -const { getRemote, potentiallyRemoteRequire } = require('@electron/internal/renderer/remote') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') window.onload = function () { // Use menu API to show context menu. @@ -20,64 +21,16 @@ function completeURL (project, path) { } window.confirm = function (message, title) { - const dialog = getRemote('dialog') - if (title == null) { - title = '' - } - return !dialog.showMessageBox({ - message: message, - title: title, - buttons: ['OK', 'Cancel'], - cancelId: 1 - }) + return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) } -const convertToMenuTemplate = function (items) { - return items.map(function (item) { - const transformed = item.type === 'subMenu' ? { - type: 'submenu', - label: item.label, - enabled: item.enabled, - submenu: convertToMenuTemplate(item.subItems) - } : item.type === 'separator' ? { - type: 'separator' - } : item.type === 'checkbox' ? { - type: 'checkbox', - label: item.label, - enabled: item.enabled, - checked: item.checked - } : { - type: 'normal', - label: item.label, - enabled: item.enabled - } +ipcRenderer.on('ELECTRON_INSPECTOR_CONTEXT_MENU_CLICK', function (event, id) { + window.DevToolsAPI.contextMenuItemSelected(id) +}) - if (item.id != null) { - transformed.click = function () { - window.DevToolsAPI.contextMenuItemSelected(item.id) - return window.DevToolsAPI.contextMenuCleared() - } - } - - return transformed - }) -} - -const createMenu = function (x, y, items) { - const Menu = getRemote('Menu') - - let template = convertToMenuTemplate(items) - if (useEditMenuItems(x, y, template)) { - template = getEditMenuItems() - } - const menu = Menu.buildFromTemplate(template) - - // The menu is expected to show asynchronously. - setTimeout(function () { - const getCurrentWindow = getRemote('getCurrentWindow') - menu.popup({ window: getCurrentWindow() }) - }) -} +ipcRenderer.on('ELECTRON_INSPECTOR_CONTEXT_MENU_CLOSE', function () { + window.DevToolsAPI.contextMenuCleared() +}) const useEditMenuItems = function (x, y, items) { return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) { @@ -85,49 +38,21 @@ const useEditMenuItems = function (x, y, items) { }) } -const getEditMenuItems = function () { - return [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - { - role: 'pasteAndMatchStyle' - }, - { - role: 'delete' - }, - { - role: 'selectAll' - } - ] +const createMenu = function (x, y, items) { + const isEditMenu = useEditMenuItems(x, y, items) + ipcRendererUtils.invoke('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu) } const showFileChooserDialog = function (callback) { - const dialog = getRemote('dialog') - const files = dialog.showOpenDialog({}) - if (files != null) { - callback(pathToHtml5FileObject(files[0])) - } + ipcRendererUtils.invoke('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => { + if (path && data) { + callback(dataToHtml5FileObject(path, data)) + } + }) } -const pathToHtml5FileObject = function (path) { - const fs = potentiallyRemoteRequire('fs') - const blob = new Blob([fs.readFileSync(path)]) +const dataToHtml5FileObject = function (path, data) { + const blob = new Blob([data]) blob.name = path return blob } diff --git a/lib/renderer/ipc-renderer-internal-utils.js b/lib/renderer/ipc-renderer-internal-utils.js new file mode 100644 index 000000000000..e0c71aa4bf45 --- /dev/null +++ b/lib/renderer/ipc-renderer-internal-utils.js @@ -0,0 +1,30 @@ +'use strict' + +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const errorUtils = require('@electron/internal/common/error-utils') + +let nextId = 0 + +exports.invoke = function (command, ...args) { + return new Promise((resolve, reject) => { + const requestId = ++nextId + ipcRenderer.once(`${command}_RESPONSE_${requestId}`, (event, error, result) => { + if (error) { + reject(errorUtils.deserialize(error)) + } else { + resolve(result) + } + }) + ipcRenderer.send(command, requestId, ...args) + }) +} + +exports.invokeSync = function (command, ...args) { + const [ error, result ] = ipcRenderer.sendSync(command, ...args) + + if (error) { + throw errorUtils.deserialize(error) + } else { + return result + } +} diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index b4801a88681d..76817c67a7c9 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -5,8 +5,6 @@ // needs. // This file implements the following APIs: -// - window.alert() -// - window.confirm() // - window.history.back() // - window.history.forward() // - window.history.go()