build: enable JS semicolons (#22783)

This commit is contained in:
Samuel Attard 2020-03-20 13:28:31 -07:00 committed by GitHub
parent 24e21467b9
commit 5d657dece4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
354 changed files with 21512 additions and 21510 deletions

View file

@ -6,6 +6,7 @@
"browser": true "browser": true
}, },
"rules": { "rules": {
"semi": ["error", "always"],
"no-var": "error", "no-var": "error",
"no-unused-vars": 0, "no-unused-vars": 0,
"no-global-assign": 0, "no-global-assign": 0,

View file

@ -1,48 +1,48 @@
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron' import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron';
import * as path from 'path' import * as path from 'path';
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null;
// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
app.quit() app.quit();
}) });
function decorateURL (url: string) { function decorateURL (url: string) {
// safely add `?utm_source=default_app // safely add `?utm_source=default_app
const parsedUrl = new URL(url) const parsedUrl = new URL(url);
parsedUrl.searchParams.append('utm_source', 'default_app') parsedUrl.searchParams.append('utm_source', 'default_app');
return parsedUrl.toString() return parsedUrl.toString();
} }
// Find the shortest path to the electron binary // Find the shortest path to the electron binary
const absoluteElectronPath = process.execPath const absoluteElectronPath = process.execPath;
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath) const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
const electronPath = absoluteElectronPath.length < relativeElectronPath.length const electronPath = absoluteElectronPath.length < relativeElectronPath.length
? absoluteElectronPath ? absoluteElectronPath
: relativeElectronPath : relativeElectronPath;
const indexPath = path.resolve(app.getAppPath(), 'index.html') const indexPath = path.resolve(app.getAppPath(), 'index.html');
function isTrustedSender (webContents: Electron.WebContents) { function isTrustedSender (webContents: Electron.WebContents) {
if (webContents !== (mainWindow && mainWindow.webContents)) { if (webContents !== (mainWindow && mainWindow.webContents)) {
return false return false;
} }
const parsedUrl = new URL(webContents.getURL()) const parsedUrl = new URL(webContents.getURL());
const urlPath = process.platform === 'win32' const urlPath = process.platform === 'win32'
// Strip the prefixed "/" that occurs on windows // Strip the prefixed "/" that occurs on windows
? path.resolve(parsedUrl.pathname.substr(1)) ? path.resolve(parsedUrl.pathname.substr(1))
: parsedUrl.pathname : parsedUrl.pathname;
return parsedUrl.protocol === 'file:' && urlPath === indexPath return parsedUrl.protocol === 'file:' && urlPath === indexPath;
} }
ipcMain.handle('bootstrap', (event) => { ipcMain.handle('bootstrap', (event) => {
return isTrustedSender(event.sender) ? electronPath : null return isTrustedSender(event.sender) ? electronPath : null;
}) });
async function createWindow () { async function createWindow () {
await app.whenReady() await app.whenReady();
const options: Electron.BrowserWindowConstructorOptions = { const options: Electron.BrowserWindowConstructorOptions = {
width: 960, width: 960,
@ -57,46 +57,46 @@ async function createWindow () {
}, },
useContentSize: true, useContentSize: true,
show: false show: false
} };
if (process.platform === 'linux') { if (process.platform === 'linux') {
options.icon = path.join(__dirname, 'icon.png') options.icon = path.join(__dirname, 'icon.png');
} }
mainWindow = new BrowserWindow(options) mainWindow = new BrowserWindow(options);
mainWindow.on('ready-to-show', () => mainWindow!.show()) mainWindow.on('ready-to-show', () => mainWindow!.show());
mainWindow.webContents.on('new-window', (event, url) => { mainWindow.webContents.on('new-window', (event, url) => {
event.preventDefault() event.preventDefault();
shell.openExternal(decorateURL(url)) shell.openExternal(decorateURL(url));
}) });
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => { mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => {
const parsedUrl = new URL(webContents.getURL()) const parsedUrl = new URL(webContents.getURL());
const options: Electron.MessageBoxOptions = { const options: Electron.MessageBoxOptions = {
title: 'Permission Request', title: 'Permission Request',
message: `Allow '${parsedUrl.origin}' to access '${permission}'?`, message: `Allow '${parsedUrl.origin}' to access '${permission}'?`,
buttons: ['OK', 'Cancel'], buttons: ['OK', 'Cancel'],
cancelId: 1 cancelId: 1
} };
dialog.showMessageBox(mainWindow!, options).then(({ response }) => { dialog.showMessageBox(mainWindow!, options).then(({ response }) => {
done(response === 0) done(response === 0);
}) });
}) });
return mainWindow return mainWindow;
} }
export const loadURL = async (appUrl: string) => { export const loadURL = async (appUrl: string) => {
mainWindow = await createWindow() mainWindow = await createWindow();
mainWindow.loadURL(appUrl) mainWindow.loadURL(appUrl);
mainWindow.focus() mainWindow.focus();
} };
export const loadFile = async (appPath: string) => { export const loadFile = async (appPath: string) => {
mainWindow = await createWindow() mainWindow = await createWindow();
mainWindow.loadFile(appPath) mainWindow.loadFile(appPath);
mainWindow.focus() mainWindow.focus();
} };

View file

@ -1,8 +1,8 @@
import { app, dialog } from 'electron' import { app, dialog } from 'electron';
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import * as url from 'url' import * as url from 'url';
type DefaultAppOptions = { type DefaultAppOptions = {
file: null | string; file: null | string;
@ -14,10 +14,10 @@ type DefaultAppOptions = {
modules: string[]; modules: string[];
} }
const Module = require('module') const Module = require('module');
// Parse command line options. // Parse command line options.
const argv = process.argv.slice(1) const argv = process.argv.slice(1);
const option: DefaultAppOptions = { const option: DefaultAppOptions = {
file: null, file: null,
@ -27,50 +27,50 @@ const option: DefaultAppOptions = {
interactive: false, interactive: false,
abi: false, abi: false,
modules: [] modules: []
} };
let nextArgIsRequire = false let nextArgIsRequire = false;
for (const arg of argv) { for (const arg of argv) {
if (nextArgIsRequire) { if (nextArgIsRequire) {
option.modules.push(arg) option.modules.push(arg);
nextArgIsRequire = false nextArgIsRequire = false;
continue continue;
} else if (arg === '--version' || arg === '-v') { } else if (arg === '--version' || arg === '-v') {
option.version = true option.version = true;
break break;
} else if (arg.match(/^--app=/)) { } else if (arg.match(/^--app=/)) {
option.file = arg.split('=')[1] option.file = arg.split('=')[1];
break break;
} else if (arg === '--interactive' || arg === '-i' || arg === '-repl') { } else if (arg === '--interactive' || arg === '-i' || arg === '-repl') {
option.interactive = true option.interactive = true;
} else if (arg === '--test-type=webdriver') { } else if (arg === '--test-type=webdriver') {
option.webdriver = true option.webdriver = true;
} else if (arg === '--require' || arg === '-r') { } else if (arg === '--require' || arg === '-r') {
nextArgIsRequire = true nextArgIsRequire = true;
continue continue;
} else if (arg === '--abi' || arg === '-a') { } else if (arg === '--abi' || arg === '-a') {
option.abi = true option.abi = true;
continue continue;
} else if (arg === '--no-help') { } else if (arg === '--no-help') {
option.noHelp = true option.noHelp = true;
continue continue;
} else if (arg[0] === '-') { } else if (arg[0] === '-') {
continue continue;
} else { } else {
option.file = arg option.file = arg;
break break;
} }
} }
if (nextArgIsRequire) { if (nextArgIsRequire) {
console.error('Invalid Usage: --require [file]\n\n"file" is required') console.error('Invalid Usage: --require [file]\n\n"file" is required');
process.exit(1) process.exit(1);
} }
// Set up preload modules // Set up preload modules
if (option.modules.length > 0) { if (option.modules.length > 0) {
Module._preloadModules(option.modules) Module._preloadModules(option.modules);
} }
function loadApplicationPackage (packagePath: string) { function loadApplicationPackage (packagePath: string) {
@ -79,102 +79,102 @@ function loadApplicationPackage (packagePath: string) {
configurable: false, configurable: false,
enumerable: true, enumerable: true,
value: true value: true
}) });
try { try {
// Override app name and version. // Override app name and version.
packagePath = path.resolve(packagePath) packagePath = path.resolve(packagePath);
const packageJsonPath = path.join(packagePath, 'package.json') const packageJsonPath = path.join(packagePath, 'package.json');
let appPath let appPath;
if (fs.existsSync(packageJsonPath)) { if (fs.existsSync(packageJsonPath)) {
let packageJson let packageJson;
try { try {
packageJson = require(packageJsonPath) packageJson = require(packageJsonPath);
} catch (e) { } catch (e) {
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`) showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`);
return return;
} }
if (packageJson.version) { if (packageJson.version) {
app.setVersion(packageJson.version) app.setVersion(packageJson.version);
} }
if (packageJson.productName) { if (packageJson.productName) {
app.name = packageJson.productName app.name = packageJson.productName;
} else if (packageJson.name) { } else if (packageJson.name) {
app.name = packageJson.name app.name = packageJson.name;
} }
appPath = packagePath appPath = packagePath;
} }
try { try {
const filePath = Module._resolveFilename(packagePath, module, true) const filePath = Module._resolveFilename(packagePath, module, true);
app._setDefaultAppPaths(appPath || path.dirname(filePath)) app._setDefaultAppPaths(appPath || path.dirname(filePath));
} catch (e) { } catch (e) {
showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`) showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`);
return return;
} }
// Run the app. // Run the app.
Module._load(packagePath, module, true) Module._load(packagePath, module, true);
} catch (e) { } catch (e) {
console.error('App threw an error during load') console.error('App threw an error during load');
console.error(e.stack || e) console.error(e.stack || e);
throw e throw e;
} }
} }
function showErrorMessage (message: string) { function showErrorMessage (message: string) {
app.focus() app.focus();
dialog.showErrorBox('Error launching app', message) dialog.showErrorBox('Error launching app', message);
process.exit(1) process.exit(1);
} }
async function loadApplicationByURL (appUrl: string) { async function loadApplicationByURL (appUrl: string) {
const { loadURL } = await import('./default_app') const { loadURL } = await import('./default_app');
loadURL(appUrl) loadURL(appUrl);
} }
async function loadApplicationByFile (appPath: string) { async function loadApplicationByFile (appPath: string) {
const { loadFile } = await import('./default_app') const { loadFile } = await import('./default_app');
loadFile(appPath) loadFile(appPath);
} }
function startRepl () { function startRepl () {
if (process.platform === 'win32') { if (process.platform === 'win32') {
console.error('Electron REPL not currently supported on Windows') console.error('Electron REPL not currently supported on Windows');
process.exit(1) process.exit(1);
} }
// prevent quitting // prevent quitting
app.on('window-all-closed', () => {}) app.on('window-all-closed', () => {});
const repl = require('repl') const repl = require('repl');
repl.start('> ').on('exit', () => { repl.start('> ').on('exit', () => {
process.exit(0) process.exit(0);
}) });
} }
// Start the specified app if there is one specified in command line, otherwise // Start the specified app if there is one specified in command line, otherwise
// start the default app. // start the default app.
if (option.file && !option.webdriver) { if (option.file && !option.webdriver) {
const file = option.file const file = option.file;
const protocol = url.parse(file).protocol const protocol = url.parse(file).protocol;
const extension = path.extname(file) const extension = path.extname(file);
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') { if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
loadApplicationByURL(file) loadApplicationByURL(file);
} else if (extension === '.html' || extension === '.htm') { } else if (extension === '.html' || extension === '.htm') {
loadApplicationByFile(path.resolve(file)) loadApplicationByFile(path.resolve(file));
} else { } else {
loadApplicationPackage(file) loadApplicationPackage(file);
} }
} else if (option.version) { } else if (option.version) {
console.log('v' + process.versions.electron) console.log('v' + process.versions.electron);
process.exit(0) process.exit(0);
} else if (option.abi) { } else if (option.abi) {
console.log(process.versions.modules) console.log(process.versions.modules);
process.exit(0) process.exit(0);
} else if (option.interactive) { } else if (option.interactive) {
startRepl() startRepl();
} else { } else {
if (!option.noHelp) { if (!option.noHelp) {
const welcomeMessage = ` const welcomeMessage = `
@ -192,10 +192,10 @@ Options:
-i, --interactive Open a REPL to the main process. -i, --interactive Open a REPL to the main process.
-r, --require Module to preload (option can be repeated). -r, --require Module to preload (option can be repeated).
-v, --version Print the version. -v, --version Print the version.
-a, --abi Print the Node ABI version.` -a, --abi Print the Node ABI version.`;
console.log(welcomeMessage) console.log(welcomeMessage);
} }
loadApplicationByFile('index.html') loadApplicationByFile('index.html');
} }

View file

@ -1,53 +1,53 @@
import { ipcRenderer, contextBridge } from 'electron' import { ipcRenderer, contextBridge } from 'electron';
async function getOcticonSvg (name: string) { async function getOcticonSvg (name: string) {
try { try {
const response = await fetch(`octicon/${name}.svg`) const response = await fetch(`octicon/${name}.svg`);
const div = document.createElement('div') const div = document.createElement('div');
div.innerHTML = await response.text() div.innerHTML = await response.text();
return div return div;
} catch { } catch {
return null return null;
} }
} }
async function loadSVG (element: HTMLSpanElement) { async function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) { for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) { if (cssClass.startsWith('octicon-')) {
const icon = await getOcticonSvg(cssClass.substr(8)) const icon = await getOcticonSvg(cssClass.substr(8));
if (icon) { if (icon) {
for (const elemClass of element.classList) { for (const elemClass of element.classList) {
icon.classList.add(elemClass) icon.classList.add(elemClass);
} }
element.before(icon) element.before(icon);
element.remove() element.remove();
break break;
} }
} }
} }
} }
async function initialize () { async function initialize () {
const electronPath = await ipcRenderer.invoke('bootstrap') const electronPath = await ipcRenderer.invoke('bootstrap');
function replaceText (selector: string, text: string) { function replaceText (selector: string, text: string) {
const element = document.querySelector<HTMLElement>(selector) const element = document.querySelector<HTMLElement>(selector);
if (element) { if (element) {
element.innerText = text element.innerText = text;
} }
} }
replaceText('.electron-version', `Electron v${process.versions.electron}`) replaceText('.electron-version', `Electron v${process.versions.electron}`);
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`) replaceText('.chrome-version', `Chromium v${process.versions.chrome}`);
replaceText('.node-version', `Node v${process.versions.node}`) replaceText('.node-version', `Node v${process.versions.node}`);
replaceText('.v8-version', `v8 v${process.versions.v8}`) replaceText('.v8-version', `v8 v${process.versions.v8}`);
replaceText('.command-example', `${electronPath} path-to-app`) replaceText('.command-example', `${electronPath} path-to-app`);
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) { for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element) loadSVG(element);
} }
} }
contextBridge.exposeInMainWorld('electronDefaultApp', { contextBridge.exposeInMainWorld('electronDefaultApp', {
initialize initialize
}) });

View file

@ -1,44 +1,44 @@
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import { deprecate, Menu } from 'electron' import { deprecate, Menu } from 'electron';
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const bindings = process.electronBinding('app') const bindings = process.electronBinding('app');
const commandLine = process.electronBinding('command_line') const commandLine = process.electronBinding('command_line');
const { app, App } = bindings const { app, App } = bindings;
// Only one app object permitted. // Only one app object permitted.
export default app export default app;
let dockMenu: Electron.Menu | null = null let dockMenu: Electron.Menu | null = null;
// App is an EventEmitter. // App is an EventEmitter.
Object.setPrototypeOf(App.prototype, EventEmitter.prototype) Object.setPrototypeOf(App.prototype, EventEmitter.prototype);
EventEmitter.call(app as any) EventEmitter.call(app as any);
// Properties. // Properties.
const nativeASGetter = app.isAccessibilitySupportEnabled const nativeASGetter = app.isAccessibilitySupportEnabled;
const nativeASSetter = app.setAccessibilitySupportEnabled const nativeASSetter = app.setAccessibilitySupportEnabled;
Object.defineProperty(App.prototype, 'accessibilitySupportEnabled', { Object.defineProperty(App.prototype, 'accessibilitySupportEnabled', {
get: () => nativeASGetter.call(app), get: () => nativeASGetter.call(app),
set: (enabled) => nativeASSetter.call(app, enabled) set: (enabled) => nativeASSetter.call(app, enabled)
}) });
const nativeBCGetter = app.getBadgeCount const nativeBCGetter = app.getBadgeCount;
const nativeBCSetter = app.setBadgeCount const nativeBCSetter = app.setBadgeCount;
Object.defineProperty(App.prototype, 'badgeCount', { Object.defineProperty(App.prototype, 'badgeCount', {
get: () => nativeBCGetter.call(app), get: () => nativeBCGetter.call(app),
set: (count) => nativeBCSetter.call(app, count) set: (count) => nativeBCSetter.call(app, count)
}) });
const nativeNGetter = app.getName const nativeNGetter = app.getName;
const nativeNSetter = app.setName const nativeNSetter = app.setName;
Object.defineProperty(App.prototype, 'name', { Object.defineProperty(App.prototype, 'name', {
get: () => nativeNGetter.call(app), get: () => nativeNGetter.call(app),
set: (name) => nativeNSetter.call(app, name) set: (name) => nativeNSetter.call(app, name)
}) });
Object.assign(app, { Object.assign(app, {
commandLine: { commandLine: {
@ -47,98 +47,98 @@ Object.assign(app, {
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)), appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)) appendArgument: (arg: string) => commandLine.appendArgument(String(arg))
} as Electron.CommandLine } as Electron.CommandLine
}) });
// we define this here because it'd be overly complicated to // we define this here because it'd be overly complicated to
// do in native land // do in native land
Object.defineProperty(app, 'applicationMenu', { Object.defineProperty(app, 'applicationMenu', {
get () { get () {
return Menu.getApplicationMenu() return Menu.getApplicationMenu();
}, },
set (menu: Electron.Menu | null) { set (menu: Electron.Menu | null) {
return Menu.setApplicationMenu(menu) return Menu.setApplicationMenu(menu);
} }
}) });
App.prototype.isPackaged = (() => { App.prototype.isPackaged = (() => {
const execFile = path.basename(process.execPath).toLowerCase() const execFile = path.basename(process.execPath).toLowerCase();
if (process.platform === 'win32') { if (process.platform === 'win32') {
return execFile !== 'electron.exe' return execFile !== 'electron.exe';
} }
return execFile !== 'electron' return execFile !== 'electron';
})() })();
app._setDefaultAppPaths = (packagePath) => { app._setDefaultAppPaths = (packagePath) => {
// Set the user path according to application's name. // Set the user path according to application's name.
app.setPath('userData', path.join(app.getPath('appData'), app.name!)) app.setPath('userData', path.join(app.getPath('appData'), app.name!));
app.setPath('userCache', path.join(app.getPath('cache'), app.name!)) app.setPath('userCache', path.join(app.getPath('cache'), app.name!));
app.setAppPath(packagePath) app.setAppPath(packagePath);
// Add support for --user-data-dir= // Add support for --user-data-dir=
if (app.commandLine.hasSwitch('user-data-dir')) { if (app.commandLine.hasSwitch('user-data-dir')) {
const userDataDir = app.commandLine.getSwitchValue('user-data-dir') const userDataDir = app.commandLine.getSwitchValue('user-data-dir');
if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir) if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir);
} }
} };
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const setDockMenu = app.dock!.setMenu const setDockMenu = app.dock!.setMenu;
app.dock!.setMenu = (menu) => { app.dock!.setMenu = (menu) => {
dockMenu = menu dockMenu = menu;
setDockMenu(menu) setDockMenu(menu);
} };
app.dock!.getMenu = () => dockMenu app.dock!.getMenu = () => dockMenu;
} }
if (process.platform === 'linux') { if (process.platform === 'linux') {
const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m;
const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m;
const getStatus = (pid: number) => { const getStatus = (pid: number) => {
try { try {
return fs.readFileSync(`/proc/${pid}/status`, 'utf8') return fs.readFileSync(`/proc/${pid}/status`, 'utf8');
} catch { } catch {
return '' return '';
} }
} };
const getEntry = (file: string, pattern: RegExp) => { const getEntry = (file: string, pattern: RegExp) => {
const match = file.match(pattern) const match = file.match(pattern);
return match ? parseInt(match[1], 10) : 0 return match ? parseInt(match[1], 10) : 0;
} };
const getProcessMemoryInfo = (pid: number) => { const getProcessMemoryInfo = (pid: number) => {
const file = getStatus(pid) const file = getStatus(pid);
return { return {
workingSetSize: getEntry(file, patternVmRSS), workingSetSize: getEntry(file, patternVmRSS),
peakWorkingSetSize: getEntry(file, patternVmHWM) peakWorkingSetSize: getEntry(file, patternVmHWM)
} };
} };
const nativeFn = app.getAppMetrics const nativeFn = app.getAppMetrics;
app.getAppMetrics = () => { app.getAppMetrics = () => {
const metrics = nativeFn.call(app) const metrics = nativeFn.call(app);
for (const metric of metrics) { for (const metric of metrics) {
metric.memory = getProcessMemoryInfo(metric.pid) metric.memory = getProcessMemoryInfo(metric.pid);
} }
return metrics return metrics;
} };
} }
// Routes the events to webContents. // Routes the events to webContents.
const events = ['certificate-error', 'select-client-certificate'] const events = ['certificate-error', 'select-client-certificate'];
for (const name of events) { for (const name of events) {
app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => { app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => {
webContents.emit(name, event, ...args) webContents.emit(name, event, ...args);
}) });
} }
// Deprecate allowRendererProcessReuse but only if they set it to false, no need to log if // Deprecate allowRendererProcessReuse but only if they set it to false, no need to log if
// they are setting it to true // they are setting it to true
deprecate.removeProperty(app, 'allowRendererProcessReuse', [false]) deprecate.removeProperty(app, 'allowRendererProcessReuse', [false]);
// Wrappers for native classes. // Wrappers for native classes.
const { DownloadItem } = process.electronBinding('download_item') const { DownloadItem } = process.electronBinding('download_item');
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype);

View file

@ -1,7 +1,7 @@
'use strict' 'use strict';
if (process.platform === 'win32') { if (process.platform === 'win32') {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win') module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win');
} else { } else {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native') module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native');
} }

View file

@ -1,10 +1,10 @@
'use strict' 'use strict';
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter;
const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater') const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater');
// AutoUpdater is an EventEmitter. // AutoUpdater is an EventEmitter.
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype) Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype);
EventEmitter.call(autoUpdater) EventEmitter.call(autoUpdater);
module.exports = autoUpdater module.exports = autoUpdater;

View file

@ -1,74 +1,74 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win') const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win');
class AutoUpdater extends EventEmitter { class AutoUpdater extends EventEmitter {
quitAndInstall () { quitAndInstall () {
if (!this.updateAvailable) { if (!this.updateAvailable) {
return this.emitError('No update available, can\'t quit and install') return this.emitError('No update available, can\'t quit and install');
} }
squirrelUpdate.processStart() squirrelUpdate.processStart();
app.quit() app.quit();
} }
getFeedURL () { getFeedURL () {
return this.updateURL return this.updateURL;
} }
setFeedURL (options) { setFeedURL (options) {
let updateURL let updateURL;
if (typeof options === 'object') { if (typeof options === 'object') {
if (typeof options.url === 'string') { if (typeof options.url === 'string') {
updateURL = options.url updateURL = options.url;
} else { } else {
throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call') throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call');
} }
} else if (typeof options === 'string') { } else if (typeof options === 'string') {
updateURL = options updateURL = options;
} else { } else {
throw new Error('Expected an options object with a \'url\' property to be provided') throw new Error('Expected an options object with a \'url\' property to be provided');
} }
this.updateURL = updateURL this.updateURL = updateURL;
} }
checkForUpdates () { checkForUpdates () {
if (!this.updateURL) { if (!this.updateURL) {
return this.emitError('Update URL is not set') return this.emitError('Update URL is not set');
} }
if (!squirrelUpdate.supported()) { if (!squirrelUpdate.supported()) {
return this.emitError('Can not find Squirrel') return this.emitError('Can not find Squirrel');
} }
this.emit('checking-for-update') this.emit('checking-for-update');
squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => { squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => {
if (error != null) { if (error != null) {
return this.emitError(error) return this.emitError(error);
} }
if (update == null) { if (update == null) {
return this.emit('update-not-available') return this.emit('update-not-available');
} }
this.updateAvailable = true this.updateAvailable = true;
this.emit('update-available') this.emit('update-available');
squirrelUpdate.update(this.updateURL, (error) => { squirrelUpdate.update(this.updateURL, (error) => {
if (error != null) { if (error != null) {
return this.emitError(error) return this.emitError(error);
} }
const { releaseNotes, version } = update const { releaseNotes, version } = update;
// Date is not available on Windows, so fake it. // Date is not available on Windows, so fake it.
const date = new Date() const date = new Date();
this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
this.quitAndInstall() this.quitAndInstall();
}) });
}) });
}) });
} }
// Private: Emit both error object and message, this is to keep compatibility // Private: Emit both error object and message, this is to keep compatibility
// with Old APIs. // with Old APIs.
emitError (message) { emitError (message) {
this.emit('error', new Error(message), message) this.emit('error', new Error(message), message);
} }
} }
module.exports = new AutoUpdater() module.exports = new AutoUpdater();

View file

@ -1,24 +1,24 @@
'use strict' 'use strict';
const fs = require('fs') const fs = require('fs');
const path = require('path') const path = require('path');
const spawn = require('child_process').spawn const spawn = require('child_process').spawn;
// i.e. my-app/app-0.1.13/ // i.e. my-app/app-0.1.13/
const appFolder = path.dirname(process.execPath) const appFolder = path.dirname(process.execPath);
// i.e. my-app/Update.exe // i.e. my-app/Update.exe
const updateExe = path.resolve(appFolder, '..', 'Update.exe') const updateExe = path.resolve(appFolder, '..', 'Update.exe');
const exeName = path.basename(process.execPath) const exeName = path.basename(process.execPath);
let spawnedArgs = [] let spawnedArgs = [];
let spawnedProcess let spawnedProcess;
const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]) const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
// Spawn a command and invoke the callback when it completes with an error // Spawn a command and invoke the callback when it completes with an error
// and the output from standard out. // and the output from standard out.
const spawnUpdate = function (args, detached, callback) { const spawnUpdate = function (args, detached, callback) {
let error, errorEmitted, stderr, stdout let error, errorEmitted, stderr, stdout;
try { try {
// Ensure we don't spawn multiple squirrel processes // Ensure we don't spawn multiple squirrel processes
@ -28,92 +28,92 @@ const spawnUpdate = function (args, detached, callback) {
if (spawnedProcess && !isSameArgs(args)) { if (spawnedProcess && !isSameArgs(args)) {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`AutoUpdater process with arguments ${args} is already running`) return callback(`AutoUpdater process with arguments ${args} is already running`);
} else if (!spawnedProcess) { } else if (!spawnedProcess) {
spawnedProcess = spawn(updateExe, args, { spawnedProcess = spawn(updateExe, args, {
detached: detached, detached: detached,
windowsHide: true windowsHide: true
}) });
spawnedArgs = args || [] spawnedArgs = args || [];
} }
} catch (error1) { } catch (error1) {
error = error1 error = error1;
// Shouldn't happen, but still guard it. // Shouldn't happen, but still guard it.
process.nextTick(function () { process.nextTick(function () {
return callback(error) return callback(error);
}) });
return return;
} }
stdout = '' stdout = '';
stderr = '' stderr = '';
spawnedProcess.stdout.on('data', (data) => { stdout += data }) spawnedProcess.stdout.on('data', (data) => { stdout += data; });
spawnedProcess.stderr.on('data', (data) => { stderr += data }) spawnedProcess.stderr.on('data', (data) => { stderr += data; });
errorEmitted = false errorEmitted = false;
spawnedProcess.on('error', (error) => { spawnedProcess.on('error', (error) => {
errorEmitted = true errorEmitted = true;
callback(error) callback(error);
}) });
return spawnedProcess.on('exit', function (code, signal) { return spawnedProcess.on('exit', function (code, signal) {
spawnedProcess = undefined spawnedProcess = undefined;
spawnedArgs = [] spawnedArgs = [];
// We may have already emitted an error. // We may have already emitted an error.
if (errorEmitted) { if (errorEmitted) {
return return;
} }
// Process terminated with error. // Process terminated with error.
if (code !== 0) { if (code !== 0) {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`) return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`);
} }
// Success. // Success.
callback(null, stdout) callback(null, stdout);
}) });
} };
// Start an instance of the installed app. // Start an instance of the installed app.
exports.processStart = function () { exports.processStart = function () {
return spawnUpdate(['--processStartAndWait', exeName], true, function () {}) return spawnUpdate(['--processStartAndWait', exeName], true, function () {});
} };
// Download the releases specified by the URL and write new results to stdout. // Download the releases specified by the URL and write new results to stdout.
exports.checkForUpdate = function (updateURL, callback) { exports.checkForUpdate = function (updateURL, callback) {
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) { return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) {
let ref, ref1, update let ref, ref1, update;
if (error != null) { if (error != null) {
return callback(error) return callback(error);
} }
try { try {
// Last line of output is the JSON details about the releases // Last line of output is the JSON details about the releases
const json = stdout.trim().split('\n').pop() const json = stdout.trim().split('\n').pop();
update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : undefined : undefined : undefined update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : undefined : undefined : undefined;
} catch { } catch {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`Invalid result:\n${stdout}`) return callback(`Invalid result:\n${stdout}`);
} }
return callback(null, update) return callback(null, update);
}) });
} };
// Update the application to the latest remote version specified by URL. // Update the application to the latest remote version specified by URL.
exports.update = function (updateURL, callback) { exports.update = function (updateURL, callback) {
return spawnUpdate(['--update', updateURL], false, callback) return spawnUpdate(['--update', updateURL], false, callback);
} };
// Is the Update.exe installed with the current application? // Is the Update.exe installed with the current application?
exports.supported = function () { exports.supported = function () {
try { try {
fs.accessSync(updateExe, fs.R_OK) fs.accessSync(updateExe, fs.R_OK);
return true return true;
} catch { } catch {
return false return false;
} }
} };

View file

@ -1,16 +1,16 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { BrowserView } = process.electronBinding('browser_view') const { BrowserView } = process.electronBinding('browser_view');
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype) Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
BrowserView.fromWebContents = (webContents) => { BrowserView.fromWebContents = (webContents) => {
for (const view of BrowserView.getAllViews()) { for (const view of BrowserView.getAllViews()) {
if (view.webContents.equal(webContents)) return view if (view.webContents.equal(webContents)) return view;
} }
return null return null;
} };
module.exports = BrowserView module.exports = BrowserView;

View file

@ -1,29 +1,29 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { WebContentsView, TopLevelWindow, deprecate } = electron const { WebContentsView, TopLevelWindow, deprecate } = electron;
const { BrowserWindow } = process.electronBinding('window') const { BrowserWindow } = process.electronBinding('window');
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype) Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype);
BrowserWindow.prototype._init = function () { BrowserWindow.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
TopLevelWindow.prototype._init.call(this) TopLevelWindow.prototype._init.call(this);
// Avoid recursive require. // Avoid recursive require.
const { app } = electron const { app } = electron;
// Create WebContentsView. // Create WebContentsView.
this.setContentView(new WebContentsView(this.webContents)) this.setContentView(new WebContentsView(this.webContents));
const nativeSetBounds = this.setBounds const nativeSetBounds = this.setBounds;
this.setBounds = (bounds, ...opts) => { this.setBounds = (bounds, ...opts) => {
bounds = { bounds = {
...this.getBounds(), ...this.getBounds(),
...bounds ...bounds
} };
nativeSetBounds.call(this, bounds, ...opts) nativeSetBounds.call(this, bounds, ...opts);
} };
// Sometimes the webContents doesn't get focus when window is shown, so we // Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to // have to force focusing on webContents in this case. The safest way is to
@ -34,172 +34,172 @@ BrowserWindow.prototype._init = function () {
// Finder, we still do it on all platforms in case of other bugs we don't // Finder, we still do it on all platforms in case of other bugs we don't
// know. // know.
this.webContents.once('load-url', function () { this.webContents.once('load-url', function () {
this.focus() this.focus();
}) });
// Redirect focus/blur event to app instance too. // Redirect focus/blur event to app instance too.
this.on('blur', (event) => { this.on('blur', (event) => {
app.emit('browser-window-blur', event, this) app.emit('browser-window-blur', event, this);
}) });
this.on('focus', (event) => { this.on('focus', (event) => {
app.emit('browser-window-focus', event, this) app.emit('browser-window-focus', event, this);
}) });
// Subscribe to visibilityState changes and pass to renderer process. // Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized() let isVisible = this.isVisible() && !this.isMinimized();
const visibilityChanged = () => { const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized() const newState = this.isVisible() && !this.isMinimized();
if (isVisible !== newState) { if (isVisible !== newState) {
isVisible = newState isVisible = newState;
const visibilityState = isVisible ? 'visible' : 'hidden' const visibilityState = isVisible ? 'visible' : 'hidden';
this.webContents.emit('-window-visibility-change', visibilityState) this.webContents.emit('-window-visibility-change', visibilityState);
} }
} };
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'] const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
for (const event of visibilityEvents) { for (const event of visibilityEvents) {
this.on(event, visibilityChanged) this.on(event, visibilityChanged);
} }
// Notify the creation of the window. // Notify the creation of the window.
const event = process.electronBinding('event').createEmpty() const event = process.electronBinding('event').createEmpty();
app.emit('browser-window-created', event, this) app.emit('browser-window-created', event, this);
Object.defineProperty(this, 'devToolsWebContents', { Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true, enumerable: true,
configurable: false, configurable: false,
get () { get () {
return this.webContents.devToolsWebContents return this.webContents.devToolsWebContents;
} }
}) });
// Properties // Properties
Object.defineProperty(this, 'autoHideMenuBar', { Object.defineProperty(this, 'autoHideMenuBar', {
get: () => this.isMenuBarAutoHide(), get: () => this.isMenuBarAutoHide(),
set: (autoHide) => this.setAutoHideMenuBar(autoHide) set: (autoHide) => this.setAutoHideMenuBar(autoHide)
}) });
Object.defineProperty(this, 'minimizable', { Object.defineProperty(this, 'minimizable', {
get: () => this.isMinimizable(), get: () => this.isMinimizable(),
set: (min) => this.setMinimizable(min) set: (min) => this.setMinimizable(min)
}) });
Object.defineProperty(this, 'maximizable', { Object.defineProperty(this, 'maximizable', {
get: () => this.isMaximizable(), get: () => this.isMaximizable(),
set: (max) => this.setMaximizable(max) set: (max) => this.setMaximizable(max)
}) });
Object.defineProperty(this, 'resizable', { Object.defineProperty(this, 'resizable', {
get: () => this.isResizable(), get: () => this.isResizable(),
set: (res) => this.setResizable(res) set: (res) => this.setResizable(res)
}) });
Object.defineProperty(this, 'fullScreenable', { Object.defineProperty(this, 'fullScreenable', {
get: () => this.isFullScreenable(), get: () => this.isFullScreenable(),
set: (full) => this.setFullScreenable(full) set: (full) => this.setFullScreenable(full)
}) });
Object.defineProperty(this, 'closable', { Object.defineProperty(this, 'closable', {
get: () => this.isClosable(), get: () => this.isClosable(),
set: (close) => this.setClosable(close) set: (close) => this.setClosable(close)
}) });
Object.defineProperty(this, 'movable', { Object.defineProperty(this, 'movable', {
get: () => this.isMovable(), get: () => this.isMovable(),
set: (move) => this.setMovable(move) set: (move) => this.setMovable(move)
}) });
} };
const isBrowserWindow = (win) => { const isBrowserWindow = (win) => {
return win && win.constructor.name === 'BrowserWindow' return win && win.constructor.name === 'BrowserWindow';
} };
BrowserWindow.fromId = (id) => { BrowserWindow.fromId = (id) => {
const win = TopLevelWindow.fromId(id) const win = TopLevelWindow.fromId(id);
return isBrowserWindow(win) ? win : null return isBrowserWindow(win) ? win : null;
} };
BrowserWindow.getAllWindows = () => { BrowserWindow.getAllWindows = () => {
return TopLevelWindow.getAllWindows().filter(isBrowserWindow) return TopLevelWindow.getAllWindows().filter(isBrowserWindow);
} };
BrowserWindow.getFocusedWindow = () => { BrowserWindow.getFocusedWindow = () => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused()) return window if (window.isFocused() || window.isDevToolsFocused()) return window;
} }
return null return null;
} };
BrowserWindow.fromWebContents = (webContents) => { BrowserWindow.fromWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents && window.webContents.equal(webContents)) return window if (window.webContents && window.webContents.equal(webContents)) return window;
} }
return null return null;
} };
BrowserWindow.fromBrowserView = (browserView) => { BrowserWindow.fromBrowserView = (browserView) => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView) return window if (window.getBrowserView() === browserView) return window;
} }
return null return null;
} };
// Helpers. // Helpers.
Object.assign(BrowserWindow.prototype, { Object.assign(BrowserWindow.prototype, {
loadURL (...args) { loadURL (...args) {
return this.webContents.loadURL(...args) return this.webContents.loadURL(...args);
}, },
getURL (...args) { getURL (...args) {
return this.webContents.getURL() return this.webContents.getURL();
}, },
loadFile (...args) { loadFile (...args) {
return this.webContents.loadFile(...args) return this.webContents.loadFile(...args);
}, },
reload (...args) { reload (...args) {
return this.webContents.reload(...args) return this.webContents.reload(...args);
}, },
send (...args) { send (...args) {
return this.webContents.send(...args) return this.webContents.send(...args);
}, },
openDevTools (...args) { openDevTools (...args) {
return this.webContents.openDevTools(...args) return this.webContents.openDevTools(...args);
}, },
closeDevTools () { closeDevTools () {
return this.webContents.closeDevTools() return this.webContents.closeDevTools();
}, },
isDevToolsOpened () { isDevToolsOpened () {
return this.webContents.isDevToolsOpened() return this.webContents.isDevToolsOpened();
}, },
isDevToolsFocused () { isDevToolsFocused () {
return this.webContents.isDevToolsFocused() return this.webContents.isDevToolsFocused();
}, },
toggleDevTools () { toggleDevTools () {
return this.webContents.toggleDevTools() return this.webContents.toggleDevTools();
}, },
inspectElement (...args) { inspectElement (...args) {
return this.webContents.inspectElement(...args) return this.webContents.inspectElement(...args);
}, },
inspectSharedWorker () { inspectSharedWorker () {
return this.webContents.inspectSharedWorker() return this.webContents.inspectSharedWorker();
}, },
inspectServiceWorker () { inspectServiceWorker () {
return this.webContents.inspectServiceWorker() return this.webContents.inspectServiceWorker();
}, },
showDefinitionForSelection () { showDefinitionForSelection () {
return this.webContents.showDefinitionForSelection() return this.webContents.showDefinitionForSelection();
}, },
capturePage (...args) { capturePage (...args) {
return this.webContents.capturePage(...args) return this.webContents.capturePage(...args);
}, },
setTouchBar (touchBar) { setTouchBar (touchBar) {
electron.TouchBar._setOnWindow(touchBar, this) electron.TouchBar._setOnWindow(touchBar, this);
}, },
setBackgroundThrottling (allowed) { setBackgroundThrottling (allowed) {
this.webContents.setBackgroundThrottling(allowed) this.webContents.setBackgroundThrottling(allowed);
} }
}) });
module.exports = BrowserWindow module.exports = BrowserWindow;

View file

@ -1,2 +1,2 @@
'use strict' 'use strict';
module.exports = process.electronBinding('content_tracing') module.exports = process.electronBinding('content_tracing');

View file

@ -1,12 +1,12 @@
'use strict' 'use strict';
const CrashReporter = require('@electron/internal/common/crash-reporter') const CrashReporter = require('@electron/internal/common/crash-reporter');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init') const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
class CrashReporterMain extends CrashReporter { class CrashReporterMain extends CrashReporter {
init (options) { init (options) {
return crashReporterInit(options) return crashReporterInit(options);
} }
} }
module.exports = new CrashReporterMain() module.exports = new CrashReporterMain();

View file

@ -1,13 +1,13 @@
'use strict' 'use strict';
const { app, BrowserWindow, deprecate } = require('electron') const { app, BrowserWindow, deprecate } = require('electron');
const binding = process.electronBinding('dialog') const binding = process.electronBinding('dialog');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const DialogType = { const DialogType = {
OPEN: 'OPEN', OPEN: 'OPEN',
SAVE: 'SAVE' SAVE: 'SAVE'
} };
const saveFileDialogProperties = { const saveFileDialogProperties = {
createDirectory: 1 << 0, createDirectory: 1 << 0,
@ -15,7 +15,7 @@ const saveFileDialogProperties = {
treatPackageAsDirectory: 1 << 2, treatPackageAsDirectory: 1 << 2,
showOverwriteConfirmation: 1 << 3, showOverwriteConfirmation: 1 << 3,
dontAddToRecent: 1 << 4 dontAddToRecent: 1 << 4
} };
const openFileDialogProperties = { const openFileDialogProperties = {
openFile: 1 << 0, openFile: 1 << 0,
@ -27,15 +27,15 @@ const openFileDialogProperties = {
noResolveAliases: 1 << 6, // macOS noResolveAliases: 1 << 6, // macOS
treatPackageAsDirectory: 1 << 7, // macOS treatPackageAsDirectory: 1 << 7, // macOS
dontAddToRecent: 1 << 8 // Windows dontAddToRecent: 1 << 8 // Windows
} };
const normalizeAccessKey = (text) => { const normalizeAccessKey = (text) => {
if (typeof text !== 'string') return text if (typeof text !== 'string') return text;
// macOS does not have access keys so remove single ampersands // macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand // and replace double ampersands with a single ampersand
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1') return text.replace(/&(&?)/g, '$1');
} }
// Linux uses a single underscore as an access key prefix so escape // Linux uses a single underscore as an access key prefix so escape
@ -44,41 +44,41 @@ const normalizeAccessKey = (text) => {
// a single underscore // a single underscore
if (process.platform === 'linux') { if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => { return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&') return after if (after === '&') return after;
return `_${after}` return `_${after}`;
}) });
} }
return text return text;
} };
const checkAppInitialized = function () { const checkAppInitialized = function () {
if (!app.isReady()) { if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready') throw new Error('dialog module can only be used after app is ready');
} }
} };
const setupDialogProperties = (type, properties) => { const setupDialogProperties = (type, properties) => {
const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties;
let dialogProperties = 0 let dialogProperties = 0;
for (const prop in dialogPropertiesTypes) { for (const prop in dialogPropertiesTypes) {
if (properties.includes(prop)) { if (properties.includes(prop)) {
dialogProperties |= dialogPropertiesTypes[prop] dialogProperties |= dialogPropertiesTypes[prop];
} }
} }
return dialogProperties return dialogProperties;
} };
const saveDialog = (sync, window, options) => { const saveDialog = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) options = { title: 'Save' } if (options == null) options = { title: 'Save' };
const { const {
buttonLabel = '', buttonLabel = '',
@ -90,33 +90,33 @@ const saveDialog = (sync, window, options) => {
securityScopedBookmarks = false, securityScopedBookmarks = false,
nameFieldLabel = '', nameFieldLabel = '',
showsTagField = true showsTagField = true
} = options } = options;
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string') if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
settings.properties = setupDialogProperties(DialogType.SAVE, properties) settings.properties = setupDialogProperties(DialogType.SAVE, properties);
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings) return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
} };
const openDialog = (sync, window, options) => { const openDialog = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) { if (options == null) {
options = { options = {
title: 'Open', title: 'Open',
properties: ['openFile'] properties: ['openFile']
} };
} }
const { const {
@ -127,33 +127,33 @@ const openDialog = (sync, window, options) => {
title = '', title = '',
message = '', message = '',
securityScopedBookmarks = false securityScopedBookmarks = false
} = options } = options;
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array') if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window } const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window };
settings.properties = setupDialogProperties(DialogType.OPEN, properties) settings.properties = setupDialogProperties(DialogType.OPEN, properties);
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings) return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
} };
const messageBox = (sync, window, options) => { const messageBox = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) options = { type: 'none' } if (options == null) options = { type: 'none' };
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
const messageBoxOptions = { noLink: 1 << 0 } const messageBoxOptions = { noLink: 1 << 0 };
let { let {
buttons = [], buttons = [],
@ -167,32 +167,32 @@ const messageBox = (sync, window, options) => {
message = '', message = '',
title = '', title = '',
type = 'none' type = 'none'
} = options } = options;
const messageBoxType = messageBoxTypes.indexOf(type) const messageBoxType = messageBoxTypes.indexOf(type);
if (messageBoxType === -1) throw new TypeError('Invalid message box type') if (messageBoxType === -1) throw new TypeError('Invalid message box type');
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array') if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey) if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean') if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof detail !== 'string') throw new TypeError('Detail must be a string') if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string') if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');
checkboxChecked = !!checkboxChecked checkboxChecked = !!checkboxChecked;
if (checkboxChecked && !checkboxLabel) { if (checkboxChecked && !checkboxLabel) {
throw new Error('checkboxChecked requires that checkboxLabel also be passed') throw new Error('checkboxChecked requires that checkboxLabel also be passed');
} }
// Choose a default button to get selected when dialog is cancelled. // Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) { if (cancelId == null) {
// If the defaultId is set to 0, ensure the cancel button is a different index (1) // If the defaultId is set to 0, ensure the cancel button is a different index (1)
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0 cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
for (let i = 0; i < buttons.length; i++) { for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase() const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') { if (text === 'cancel' || text === 'no') {
cancelId = i cancelId = i;
break break;
} }
} }
} }
@ -210,57 +210,57 @@ const messageBox = (sync, window, options) => {
checkboxLabel, checkboxLabel,
checkboxChecked, checkboxChecked,
icon icon
} };
if (sync) { if (sync) {
return binding.showMessageBoxSync(settings) return binding.showMessageBoxSync(settings);
} else { } else {
return binding.showMessageBox(settings) return binding.showMessageBox(settings);
} }
} };
module.exports = { module.exports = {
showOpenDialog: function (window, options) { showOpenDialog: function (window, options) {
return openDialog(false, window, options) return openDialog(false, window, options);
}, },
showOpenDialogSync: function (window, options) { showOpenDialogSync: function (window, options) {
return openDialog(true, window, options) return openDialog(true, window, options);
}, },
showSaveDialog: function (window, options) { showSaveDialog: function (window, options) {
return saveDialog(false, window, options) return saveDialog(false, window, options);
}, },
showSaveDialogSync: function (window, options) { showSaveDialogSync: function (window, options) {
return saveDialog(true, window, options) return saveDialog(true, window, options);
}, },
showMessageBox: function (window, options) { showMessageBox: function (window, options) {
return messageBox(false, window, options) return messageBox(false, window, options);
}, },
showMessageBoxSync: function (window, options) { showMessageBoxSync: function (window, options) {
return messageBox(true, window, options) return messageBox(true, window, options);
}, },
showErrorBox: function (...args) { showErrorBox: function (...args) {
return binding.showErrorBox(...args) return binding.showErrorBox(...args);
}, },
showCertificateTrustDialog: function (window, options) { showCertificateTrustDialog: function (window, options) {
if (window && window.constructor !== BrowserWindow) options = window if (window && window.constructor !== BrowserWindow) options = window;
if (options == null || typeof options !== 'object') { if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object') throw new TypeError('options must be an object');
} }
const { certificate, message = '' } = options const { certificate, message = '' } = options;
if (certificate == null || typeof certificate !== 'object') { if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object') throw new TypeError('certificate must be an object');
} }
if (typeof message !== 'string') throw new TypeError('message must be a string') if (typeof message !== 'string') throw new TypeError('message must be a string');
return binding.showCertificateTrustDialog(window, certificate, message) return binding.showCertificateTrustDialog(window, certificate, message);
} }
} };

View file

@ -1,8 +1,8 @@
import { defineProperties } from '@electron/internal/common/define-properties' import { defineProperties } from '@electron/internal/common/define-properties';
import { commonModuleList } from '@electron/internal/common/api/module-list' import { commonModuleList } from '@electron/internal/common/api/module-list';
import { browserModuleList } from '@electron/internal/browser/api/module-list' import { browserModuleList } from '@electron/internal/browser/api/module-list';
module.exports = {} module.exports = {};
defineProperties(module.exports, commonModuleList) defineProperties(module.exports, commonModuleList);
defineProperties(module.exports, browserModuleList) defineProperties(module.exports, browserModuleList);

View file

@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('global_shortcut').globalShortcut module.exports = process.electronBinding('global_shortcut').globalShortcut;

View file

@ -1,22 +1,22 @@
'use strict' 'use strict';
const { deprecate } = require('electron') const { deprecate } = require('electron');
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase') const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase');
// inAppPurchase is an EventEmitter. // inAppPurchase is an EventEmitter.
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype);
EventEmitter.call(inAppPurchase) EventEmitter.call(inAppPurchase);
module.exports = inAppPurchase module.exports = inAppPurchase;
} else { } else {
module.exports = { module.exports = {
purchaseProduct: (productID, quantity, callback) => { purchaseProduct: (productID, quantity, callback) => {
throw new Error('The inAppPurchase module can only be used on macOS') throw new Error('The inAppPurchase module can only be used on macOS');
}, },
canMakePayments: () => false, canMakePayments: () => false,
getReceiptURL: () => '' getReceiptURL: () => ''
} };
} }

View file

@ -1,8 +1,8 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
const ipcMain = new IpcMainImpl() const ipcMain = new IpcMainImpl();
// Do not throw exception when channel name is "error". // Do not throw exception when channel name is "error".
ipcMain.on('error', () => {}) ipcMain.on('error', () => {});
export default ipcMain export default ipcMain;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const isMac = process.platform === 'darwin' const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32' const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux' const isLinux = process.platform === 'linux';
const roles = { const roles = {
about: { about: {
get label () { get label () {
return isLinux ? 'About' : `About ${app.name}` return isLinux ? 'About' : `About ${app.name}`;
} }
}, },
close: { close: {
@ -38,7 +38,7 @@ const roles = {
accelerator: 'Shift+CmdOrCtrl+R', accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
windowMethod: (window) => { windowMethod: (window) => {
window.webContents.reloadIgnoringCache() window.webContents.reloadIgnoringCache();
} }
}, },
front: { front: {
@ -49,7 +49,7 @@ const roles = {
}, },
hide: { hide: {
get label () { get label () {
return `Hide ${app.name}` return `Hide ${app.name}`;
}, },
accelerator: 'Command+H' accelerator: 'Command+H'
}, },
@ -77,9 +77,9 @@ const roles = {
quit: { quit: {
get label () { get label () {
switch (process.platform) { switch (process.platform) {
case 'darwin': return `Quit ${app.name}` case 'darwin': return `Quit ${app.name}`;
case 'win32': return 'Exit' case 'win32': return 'Exit';
default: return 'Quit' default: return 'Quit';
} }
}, },
accelerator: isWindows ? undefined : 'CommandOrControl+Q', accelerator: isWindows ? undefined : 'CommandOrControl+Q',
@ -101,7 +101,7 @@ const roles = {
accelerator: 'CommandOrControl+0', accelerator: 'CommandOrControl+0',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel = 0 webContents.zoomLevel = 0;
} }
}, },
selectall: { selectall: {
@ -134,7 +134,7 @@ const roles = {
label: 'Toggle Full Screen', label: 'Toggle Full Screen',
accelerator: isMac ? 'Control+Command+F' : 'F11', accelerator: isMac ? 'Control+Command+F' : 'F11',
windowMethod: (window) => { windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen()) window.setFullScreen(!window.isFullScreen());
} }
}, },
undo: { undo: {
@ -156,7 +156,7 @@ const roles = {
accelerator: 'CommandOrControl+Plus', accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel += 0.5 webContents.zoomLevel += 0.5;
} }
}, },
zoomout: { zoomout: {
@ -164,13 +164,13 @@ const roles = {
accelerator: 'CommandOrControl+-', accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel -= 0.5 webContents.zoomLevel -= 0.5;
} }
}, },
// App submenu should be used for Mac only // App submenu should be used for Mac only
appmenu: { appmenu: {
get label () { get label () {
return app.name return app.name;
}, },
submenu: [ submenu: [
{ role: 'about' }, { role: 'about' },
@ -249,71 +249,71 @@ const roles = {
]) ])
] ]
} }
} };
exports.roleList = roles exports.roleList = roles;
const canExecuteRole = (role) => { const canExecuteRole = (role) => {
if (!Object.prototype.hasOwnProperty.call(roles, role)) return false if (!Object.prototype.hasOwnProperty.call(roles, role)) return false;
if (!isMac) return true if (!isMac) return true;
// macOS handles all roles natively except for a few // macOS handles all roles natively except for a few
return roles[role].nonNativeMacOSRole return roles[role].nonNativeMacOSRole;
} };
exports.getDefaultLabel = (role) => { exports.getDefaultLabel = (role) => {
return Object.prototype.hasOwnProperty.call(roles, role) ? roles[role].label : '' return Object.prototype.hasOwnProperty.call(roles, role) ? roles[role].label : '';
} };
exports.getDefaultAccelerator = (role) => { exports.getDefaultAccelerator = (role) => {
if (Object.prototype.hasOwnProperty.call(roles, role)) return roles[role].accelerator if (Object.prototype.hasOwnProperty.call(roles, role)) return roles[role].accelerator;
} };
exports.shouldRegisterAccelerator = (role) => { exports.shouldRegisterAccelerator = (role) => {
const hasRoleRegister = Object.prototype.hasOwnProperty.call(roles, role) && roles[role].registerAccelerator !== undefined const hasRoleRegister = Object.prototype.hasOwnProperty.call(roles, role) && roles[role].registerAccelerator !== undefined;
return hasRoleRegister ? roles[role].registerAccelerator : true return hasRoleRegister ? roles[role].registerAccelerator : true;
} };
exports.getDefaultSubmenu = (role) => { exports.getDefaultSubmenu = (role) => {
if (!Object.prototype.hasOwnProperty.call(roles, role)) return if (!Object.prototype.hasOwnProperty.call(roles, role)) return;
let { submenu } = roles[role] let { submenu } = roles[role];
// remove null items from within the submenu // remove null items from within the submenu
if (Array.isArray(submenu)) { if (Array.isArray(submenu)) {
submenu = submenu.filter((item) => item != null) submenu = submenu.filter((item) => item != null);
} }
return submenu return submenu;
} };
exports.execute = (role, focusedWindow, focusedWebContents) => { exports.execute = (role, focusedWindow, focusedWebContents) => {
if (!canExecuteRole(role)) return false if (!canExecuteRole(role)) return false;
const { appMethod, webContentsMethod, windowMethod } = roles[role] const { appMethod, webContentsMethod, windowMethod } = roles[role];
if (appMethod) { if (appMethod) {
app[appMethod]() app[appMethod]();
return true return true;
} }
if (windowMethod && focusedWindow != null) { if (windowMethod && focusedWindow != null) {
if (typeof windowMethod === 'function') { if (typeof windowMethod === 'function') {
windowMethod(focusedWindow) windowMethod(focusedWindow);
} else { } else {
focusedWindow[windowMethod]() focusedWindow[windowMethod]();
} }
return true return true;
} }
if (webContentsMethod && focusedWebContents != null) { if (webContentsMethod && focusedWebContents != null) {
if (typeof webContentsMethod === 'function') { if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents) webContentsMethod(focusedWebContents);
} else { } else {
focusedWebContents[webContentsMethod]() focusedWebContents[webContentsMethod]();
} }
return true return true;
} }
return false return false;
} };

View file

@ -1,87 +1,87 @@
'use strict' 'use strict';
const roles = require('@electron/internal/browser/api/menu-item-roles') const roles = require('@electron/internal/browser/api/menu-item-roles');
let nextCommandId = 0 let nextCommandId = 0;
const MenuItem = function (options) { const MenuItem = function (options) {
const { Menu } = require('electron') const { Menu } = require('electron');
// Preserve extra fields specified by user // Preserve extra fields specified by user
for (const key in options) { for (const key in options) {
if (!(key in this)) this[key] = options[key] if (!(key in this)) this[key] = options[key];
} }
if (typeof this.role === 'string' || this.role instanceof String) { if (typeof this.role === 'string' || this.role instanceof String) {
this.role = this.role.toLowerCase() this.role = this.role.toLowerCase();
} }
this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) this.submenu = this.submenu || roles.getDefaultSubmenu(this.role);
if (this.submenu != null && this.submenu.constructor !== Menu) { if (this.submenu != null && this.submenu.constructor !== Menu) {
this.submenu = Menu.buildFromTemplate(this.submenu) this.submenu = Menu.buildFromTemplate(this.submenu);
} }
if (this.type == null && this.submenu != null) { if (this.type == null && this.submenu != null) {
this.type = 'submenu' this.type = 'submenu';
} }
if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) { if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) {
throw new Error('Invalid submenu') throw new Error('Invalid submenu');
} }
this.overrideReadOnlyProperty('type', 'normal') this.overrideReadOnlyProperty('type', 'normal');
this.overrideReadOnlyProperty('role') this.overrideReadOnlyProperty('role');
this.overrideReadOnlyProperty('accelerator') this.overrideReadOnlyProperty('accelerator');
this.overrideReadOnlyProperty('icon') this.overrideReadOnlyProperty('icon');
this.overrideReadOnlyProperty('submenu') this.overrideReadOnlyProperty('submenu');
this.overrideProperty('label', roles.getDefaultLabel(this.role)) this.overrideProperty('label', roles.getDefaultLabel(this.role));
this.overrideProperty('sublabel', '') this.overrideProperty('sublabel', '');
this.overrideProperty('toolTip', '') this.overrideProperty('toolTip', '');
this.overrideProperty('enabled', true) this.overrideProperty('enabled', true);
this.overrideProperty('visible', true) this.overrideProperty('visible', true);
this.overrideProperty('checked', false) this.overrideProperty('checked', false);
this.overrideProperty('acceleratorWorksWhenHidden', true) this.overrideProperty('acceleratorWorksWhenHidden', true);
this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role)) this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role));
if (!MenuItem.types.includes(this.type)) { if (!MenuItem.types.includes(this.type)) {
throw new Error(`Unknown menu item type: ${this.type}`) throw new Error(`Unknown menu item type: ${this.type}`);
} }
this.overrideReadOnlyProperty('commandId', ++nextCommandId) this.overrideReadOnlyProperty('commandId', ++nextCommandId);
const click = options.click const click = options.click;
this.click = (event, focusedWindow, focusedWebContents) => { this.click = (event, focusedWindow, focusedWebContents) => {
// Manually flip the checked flags when clicked. // Manually flip the checked flags when clicked.
if (this.type === 'checkbox' || this.type === 'radio') { if (this.type === 'checkbox' || this.type === 'radio') {
this.checked = !this.checked this.checked = !this.checked;
} }
if (!roles.execute(this.role, focusedWindow, focusedWebContents)) { if (!roles.execute(this.role, focusedWindow, focusedWebContents)) {
if (typeof click === 'function') { if (typeof click === 'function') {
click(this, focusedWindow, event) click(this, focusedWindow, event);
} else if (typeof this.selector === 'string' && process.platform === 'darwin') { } else if (typeof this.selector === 'string' && process.platform === 'darwin') {
Menu.sendActionToFirstResponder(this.selector) Menu.sendActionToFirstResponder(this.selector);
} }
} }
} };
} };
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'];
MenuItem.prototype.getDefaultRoleAccelerator = function () { MenuItem.prototype.getDefaultRoleAccelerator = function () {
return roles.getDefaultAccelerator(this.role) return roles.getDefaultAccelerator(this.role);
} };
MenuItem.prototype.overrideProperty = function (name, defaultValue = null) { MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
if (this[name] == null) { if (this[name] == null) {
this[name] = defaultValue this[name] = defaultValue;
} }
} };
MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) { MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
this.overrideProperty(name, defaultValue) this.overrideProperty(name, defaultValue);
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
enumerable: true, enumerable: true,
writable: false, writable: false,
value: this[name] value: this[name]
}) });
} };
module.exports = MenuItem module.exports = MenuItem;

View file

@ -1,41 +1,41 @@
'use strict' 'use strict';
function splitArray (arr, predicate) { function splitArray (arr, predicate) {
const result = arr.reduce((multi, item) => { const result = arr.reduce((multi, item) => {
const current = multi[multi.length - 1] const current = multi[multi.length - 1];
if (predicate(item)) { if (predicate(item)) {
if (current.length > 0) multi.push([]) if (current.length > 0) multi.push([]);
} else { } else {
current.push(item) current.push(item);
} }
return multi return multi;
}, [[]]) }, [[]]);
if (result[result.length - 1].length === 0) { if (result[result.length - 1].length === 0) {
return result.slice(0, result.length - 1) return result.slice(0, result.length - 1);
} }
return result return result;
} }
function joinArrays (arrays, joinIDs) { function joinArrays (arrays, joinIDs) {
return arrays.reduce((joined, arr, i) => { return arrays.reduce((joined, arr, i) => {
if (i > 0 && arr.length) { if (i > 0 && arr.length) {
if (joinIDs.length > 0) { if (joinIDs.length > 0) {
joined.push(joinIDs[0]) joined.push(joinIDs[0]);
joinIDs.splice(0, 1) joinIDs.splice(0, 1);
} else { } else {
joined.push({ type: 'separator' }) joined.push({ type: 'separator' });
} }
} }
return joined.concat(arr) return joined.concat(arr);
}, []) }, []);
} }
function pushOntoMultiMap (map, key, value) { function pushOntoMultiMap (map, key, value) {
if (!map.has(key)) { if (!map.has(key)) {
map.set(key, []) map.set(key, []);
} }
map.get(key).push(value) map.get(key).push(value);
} }
function indexOfGroupContainingID (groups, id, ignoreGroup) { function indexOfGroupContainingID (groups, id, ignoreGroup) {
@ -45,102 +45,102 @@ function indexOfGroupContainingID (groups, id, ignoreGroup) {
candidateGroup.some( candidateGroup.some(
candidateItem => candidateItem.id === id candidateItem => candidateItem.id === id
) )
) );
} }
// Sort nodes topologically using a depth-first approach. Encountered cycles // Sort nodes topologically using a depth-first approach. Encountered cycles
// are broken. // are broken.
function sortTopologically (originalOrder, edgesById) { function sortTopologically (originalOrder, edgesById) {
const sorted = [] const sorted = [];
const marked = new Set() const marked = new Set();
const visit = (mark) => { const visit = (mark) => {
if (marked.has(mark)) return if (marked.has(mark)) return;
marked.add(mark) marked.add(mark);
const edges = edgesById.get(mark) const edges = edgesById.get(mark);
if (edges != null) { if (edges != null) {
edges.forEach(visit) edges.forEach(visit);
} }
sorted.push(mark) sorted.push(mark);
} };
originalOrder.forEach(visit) originalOrder.forEach(visit);
return sorted return sorted;
} }
function attemptToMergeAGroup (groups) { function attemptToMergeAGroup (groups) {
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
const group = groups[i] const group = groups[i];
for (const item of group) { for (const item of group) {
const toIDs = [...(item.before || []), ...(item.after || [])] const toIDs = [...(item.before || []), ...(item.after || [])];
for (const id of toIDs) { for (const id of toIDs) {
const index = indexOfGroupContainingID(groups, id, group) const index = indexOfGroupContainingID(groups, id, group);
if (index === -1) continue if (index === -1) continue;
const mergeTarget = groups[index] const mergeTarget = groups[index];
mergeTarget.push(...group) mergeTarget.push(...group);
groups.splice(i, 1) groups.splice(i, 1);
return true return true;
} }
} }
} }
return false return false;
} }
function mergeGroups (groups) { function mergeGroups (groups) {
let merged = true let merged = true;
while (merged) { while (merged) {
merged = attemptToMergeAGroup(groups) merged = attemptToMergeAGroup(groups);
} }
return groups return groups;
} }
function sortItemsInGroup (group) { function sortItemsInGroup (group) {
const originalOrder = group.map((node, i) => i) const originalOrder = group.map((node, i) => i);
const edges = new Map() const edges = new Map();
const idToIndex = new Map(group.map((item, i) => [item.id, i])) const idToIndex = new Map(group.map((item, i) => [item.id, i]));
group.forEach((item, i) => { group.forEach((item, i) => {
if (item.before) { if (item.before) {
item.before.forEach(toID => { item.before.forEach(toID => {
const to = idToIndex.get(toID) const to = idToIndex.get(toID);
if (to != null) { if (to != null) {
pushOntoMultiMap(edges, to, i) pushOntoMultiMap(edges, to, i);
} }
}) });
} }
if (item.after) { if (item.after) {
item.after.forEach(toID => { item.after.forEach(toID => {
const to = idToIndex.get(toID) const to = idToIndex.get(toID);
if (to != null) { if (to != null) {
pushOntoMultiMap(edges, i, to) pushOntoMultiMap(edges, i, to);
} }
}) });
} }
}) });
const sortedNodes = sortTopologically(originalOrder, edges) const sortedNodes = sortTopologically(originalOrder, edges);
return sortedNodes.map(i => group[i]) return sortedNodes.map(i => group[i]);
} }
function findEdgesInGroup (groups, i, edges) { function findEdgesInGroup (groups, i, edges) {
const group = groups[i] const group = groups[i];
for (const item of group) { for (const item of group) {
if (item.beforeGroupContaining) { if (item.beforeGroupContaining) {
for (const id of item.beforeGroupContaining) { for (const id of item.beforeGroupContaining) {
const to = indexOfGroupContainingID(groups, id, group) const to = indexOfGroupContainingID(groups, id, group);
if (to !== -1) { if (to !== -1) {
pushOntoMultiMap(edges, to, i) pushOntoMultiMap(edges, to, i);
return return;
} }
} }
} }
if (item.afterGroupContaining) { if (item.afterGroupContaining) {
for (const id of item.afterGroupContaining) { for (const id of item.afterGroupContaining) {
const to = indexOfGroupContainingID(groups, id, group) const to = indexOfGroupContainingID(groups, id, group);
if (to !== -1) { if (to !== -1) {
pushOntoMultiMap(edges, i, to) pushOntoMultiMap(edges, i, to);
return return;
} }
} }
} }
@ -148,29 +148,29 @@ function findEdgesInGroup (groups, i, edges) {
} }
function sortGroups (groups) { function sortGroups (groups) {
const originalOrder = groups.map((item, i) => i) const originalOrder = groups.map((item, i) => i);
const edges = new Map() const edges = new Map();
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
findEdgesInGroup(groups, i, edges) findEdgesInGroup(groups, i, edges);
} }
const sortedGroupIndexes = sortTopologically(originalOrder, edges) const sortedGroupIndexes = sortTopologically(originalOrder, edges);
return sortedGroupIndexes.map(i => groups[i]) return sortedGroupIndexes.map(i => groups[i]);
} }
function sortMenuItems (menuItems) { function sortMenuItems (menuItems) {
const isSeparator = (item) => item.type === 'separator' const isSeparator = (item) => item.type === 'separator';
const separators = menuItems.filter(i => i.type === 'separator') const separators = menuItems.filter(i => i.type === 'separator');
// Split the items into their implicit groups based upon separators. // Split the items into their implicit groups based upon separators.
const groups = splitArray(menuItems, isSeparator) const groups = splitArray(menuItems, isSeparator);
const mergedGroups = mergeGroups(groups) const mergedGroups = mergeGroups(groups);
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup) const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup);
const sortedGroups = sortGroups(mergedGroupsWithSortedItems) const sortedGroups = sortGroups(mergedGroupsWithSortedItems);
const joined = joinArrays(sortedGroups, separators) const joined = joinArrays(sortedGroups, separators);
return joined return joined;
} }
module.exports = { sortMenuItems } module.exports = { sortMenuItems };

View file

@ -1,16 +1,16 @@
'use strict' 'use strict';
const { TopLevelWindow, MenuItem, webContents } = require('electron') const { TopLevelWindow, MenuItem, webContents } = require('electron');
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils') const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils');
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter;
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const bindings = process.electronBinding('menu') const bindings = process.electronBinding('menu');
const { Menu } = bindings const { Menu } = bindings;
let applicationMenu = null let applicationMenu = null;
let groupIdIndex = 0 let groupIdIndex = 0;
Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype);
// Menu Delegate. // Menu Delegate.
// This object should hold no reference to |Menu| to avoid cyclic reference. // This object should hold no reference to |Menu| to avoid cyclic reference.
@ -20,172 +20,172 @@ const delegate = {
shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined, shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined,
isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined,
getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => {
const command = menu.commandsMap[id] const command = menu.commandsMap[id];
if (!command) return if (!command) return;
if (command.accelerator != null) return command.accelerator if (command.accelerator != null) return command.accelerator;
if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() if (useDefaultAccelerator) return command.getDefaultRoleAccelerator();
}, },
shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined, shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined,
executeCommand: (menu, event, id) => { executeCommand: (menu, event, id) => {
const command = menu.commandsMap[id] const command = menu.commandsMap[id];
if (!command) return if (!command) return;
command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents()) command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents());
}, },
menuWillShow: (menu) => { menuWillShow: (menu) => {
// Ensure radio groups have at least one menu item selected // Ensure radio groups have at least one menu item selected
for (const id of Object.keys(menu.groupsMap)) { for (const id of Object.keys(menu.groupsMap)) {
const found = menu.groupsMap[id].find(item => item.checked) || null const found = menu.groupsMap[id].find(item => item.checked) || null;
if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true) if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true);
} }
} }
} };
/* Instance Methods */ /* Instance Methods */
Menu.prototype._init = function () { Menu.prototype._init = function () {
this.commandsMap = {} this.commandsMap = {};
this.groupsMap = {} this.groupsMap = {};
this.items = [] this.items = [];
this.delegate = delegate this.delegate = delegate;
} };
Menu.prototype.popup = function (options = {}) { Menu.prototype.popup = function (options = {}) {
if (options == null || typeof options !== 'object') { if (options == null || typeof options !== 'object') {
throw new TypeError('Options must be an object') throw new TypeError('Options must be an object');
} }
let { window, x, y, positioningItem, callback } = options let { window, x, y, positioningItem, callback } = options;
// no callback passed // no callback passed
if (!callback || typeof callback !== 'function') callback = () => {} if (!callback || typeof callback !== 'function') callback = () => {};
// set defaults // set defaults
if (typeof x !== 'number') x = -1 if (typeof x !== 'number') x = -1;
if (typeof y !== 'number') y = -1 if (typeof y !== 'number') y = -1;
if (typeof positioningItem !== 'number') positioningItem = -1 if (typeof positioningItem !== 'number') positioningItem = -1;
// find which window to use // find which window to use
const wins = TopLevelWindow.getAllWindows() const wins = TopLevelWindow.getAllWindows();
if (!wins || wins.indexOf(window) === -1) { if (!wins || wins.indexOf(window) === -1) {
window = TopLevelWindow.getFocusedWindow() window = TopLevelWindow.getFocusedWindow();
if (!window && wins && wins.length > 0) { if (!window && wins && wins.length > 0) {
window = wins[0] window = wins[0];
} }
if (!window) { if (!window) {
throw new Error('Cannot open Menu without a TopLevelWindow present') throw new Error('Cannot open Menu without a TopLevelWindow present');
} }
} }
this.popupAt(window, x, y, positioningItem, callback) this.popupAt(window, x, y, positioningItem, callback);
return { browserWindow: window, x, y, position: positioningItem } return { browserWindow: window, x, y, position: positioningItem };
} };
Menu.prototype.closePopup = function (window) { Menu.prototype.closePopup = function (window) {
if (window instanceof TopLevelWindow) { if (window instanceof TopLevelWindow) {
this.closePopupAt(window.id) this.closePopupAt(window.id);
} else { } else {
// Passing -1 (invalid) would make closePopupAt close the all menu runners // Passing -1 (invalid) would make closePopupAt close the all menu runners
// belong to this menu. // belong to this menu.
this.closePopupAt(-1) this.closePopupAt(-1);
} }
} };
Menu.prototype.getMenuItemById = function (id) { Menu.prototype.getMenuItemById = function (id) {
const items = this.items const items = this.items;
let found = items.find(item => item.id === id) || null let found = items.find(item => item.id === id) || null;
for (let i = 0; !found && i < items.length; i++) { for (let i = 0; !found && i < items.length; i++) {
if (items[i].submenu) { if (items[i].submenu) {
found = items[i].submenu.getMenuItemById(id) found = items[i].submenu.getMenuItemById(id);
} }
} }
return found return found;
} };
Menu.prototype.append = function (item) { Menu.prototype.append = function (item) {
return this.insert(this.getItemCount(), item) return this.insert(this.getItemCount(), item);
} };
Menu.prototype.insert = function (pos, item) { Menu.prototype.insert = function (pos, item) {
if ((item ? item.constructor : undefined) !== MenuItem) { if ((item ? item.constructor : undefined) !== MenuItem) {
throw new TypeError('Invalid item') throw new TypeError('Invalid item');
} }
if (pos < 0) { if (pos < 0) {
throw new RangeError(`Position ${pos} cannot be less than 0`) throw new RangeError(`Position ${pos} cannot be less than 0`);
} else if (pos > this.getItemCount()) { } else if (pos > this.getItemCount()) {
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`) throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`);
} }
// insert item depending on its type // insert item depending on its type
insertItemByType.call(this, item, pos) insertItemByType.call(this, item, pos);
// set item properties // set item properties
if (item.sublabel) this.setSublabel(pos, item.sublabel) if (item.sublabel) this.setSublabel(pos, item.sublabel);
if (item.toolTip) this.setToolTip(pos, item.toolTip) if (item.toolTip) this.setToolTip(pos, item.toolTip);
if (item.icon) this.setIcon(pos, item.icon) if (item.icon) this.setIcon(pos, item.icon);
if (item.role) this.setRole(pos, item.role) if (item.role) this.setRole(pos, item.role);
// Make menu accessable to items. // Make menu accessable to items.
item.overrideReadOnlyProperty('menu', this) item.overrideReadOnlyProperty('menu', this);
// Remember the items. // Remember the items.
this.items.splice(pos, 0, item) this.items.splice(pos, 0, item);
this.commandsMap[item.commandId] = item this.commandsMap[item.commandId] = item;
} };
Menu.prototype._callMenuWillShow = function () { Menu.prototype._callMenuWillShow = function () {
if (this.delegate) this.delegate.menuWillShow(this) if (this.delegate) this.delegate.menuWillShow(this);
this.items.forEach(item => { this.items.forEach(item => {
if (item.submenu) item.submenu._callMenuWillShow() if (item.submenu) item.submenu._callMenuWillShow();
}) });
} };
/* Static Methods */ /* Static Methods */
Menu.getApplicationMenu = () => applicationMenu Menu.getApplicationMenu = () => applicationMenu;
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder;
// set application menu with a preexisting menu // set application menu with a preexisting menu
Menu.setApplicationMenu = function (menu) { Menu.setApplicationMenu = function (menu) {
if (menu && menu.constructor !== Menu) { if (menu && menu.constructor !== Menu) {
throw new TypeError('Invalid menu') throw new TypeError('Invalid menu');
} }
applicationMenu = menu applicationMenu = menu;
v8Util.setHiddenValue(global, 'applicationMenuSet', true) v8Util.setHiddenValue(global, 'applicationMenuSet', true);
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
if (!menu) return if (!menu) return;
menu._callMenuWillShow() menu._callMenuWillShow();
bindings.setApplicationMenu(menu) bindings.setApplicationMenu(menu);
} else { } else {
const windows = TopLevelWindow.getAllWindows() const windows = TopLevelWindow.getAllWindows();
return windows.map(w => w.setMenu(menu)) return windows.map(w => w.setMenu(menu));
} }
} };
Menu.buildFromTemplate = function (template) { Menu.buildFromTemplate = function (template) {
if (!Array.isArray(template)) { if (!Array.isArray(template)) {
throw new TypeError('Invalid template for Menu: Menu template must be an array') throw new TypeError('Invalid template for Menu: Menu template must be an array');
} }
if (!areValidTemplateItems(template)) { if (!areValidTemplateItems(template)) {
throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type') throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type');
} }
const filtered = removeExtraSeparators(template) const filtered = removeExtraSeparators(template);
const sorted = sortTemplate(filtered) const sorted = sortTemplate(filtered);
const menu = new Menu() const menu = new Menu();
sorted.forEach(item => { sorted.forEach(item => {
if (item instanceof MenuItem) { if (item instanceof MenuItem) {
menu.append(item) menu.append(item);
} else { } else {
menu.append(new MenuItem(item)) menu.append(new MenuItem(item));
} }
}) });
return menu return menu;
} };
/* Helper Functions */ /* Helper Functions */
@ -196,50 +196,50 @@ function areValidTemplateItems (template) {
typeof item === 'object' && typeof item === 'object' &&
(Object.prototype.hasOwnProperty.call(item, 'label') || (Object.prototype.hasOwnProperty.call(item, 'label') ||
Object.prototype.hasOwnProperty.call(item, 'role') || Object.prototype.hasOwnProperty.call(item, 'role') ||
item.type === 'separator')) item.type === 'separator'));
} }
function sortTemplate (template) { function sortTemplate (template) {
const sorted = sortMenuItems(template) const sorted = sortMenuItems(template);
for (const item of sorted) { for (const item of sorted) {
if (Array.isArray(item.submenu)) { if (Array.isArray(item.submenu)) {
item.submenu = sortTemplate(item.submenu) item.submenu = sortTemplate(item.submenu);
} }
} }
return sorted return sorted;
} }
// Search between separators to find a radio menu item and return its group id // Search between separators to find a radio menu item and return its group id
function generateGroupId (items, pos) { function generateGroupId (items, pos) {
if (pos > 0) { if (pos > 0) {
for (let idx = pos - 1; idx >= 0; idx--) { for (let idx = pos - 1; idx >= 0; idx--) {
if (items[idx].type === 'radio') return items[idx].groupId if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break if (items[idx].type === 'separator') break;
} }
} else if (pos < items.length) { } else if (pos < items.length) {
for (let idx = pos; idx <= items.length - 1; idx++) { for (let idx = pos; idx <= items.length - 1; idx++) {
if (items[idx].type === 'radio') return items[idx].groupId if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break if (items[idx].type === 'separator') break;
} }
} }
groupIdIndex += 1 groupIdIndex += 1;
return groupIdIndex return groupIdIndex;
} }
function removeExtraSeparators (items) { function removeExtraSeparators (items) {
// fold adjacent separators together // fold adjacent separators together
let ret = items.filter((e, idx, arr) => { let ret = items.filter((e, idx, arr) => {
if (e.visible === false) return true if (e.visible === false) return true;
return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator' return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator';
}) });
// remove edge separators // remove edge separators
ret = ret.filter((e, idx, arr) => { ret = ret.filter((e, idx, arr) => {
if (e.visible === false) return true if (e.visible === false) return true;
return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1) return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1);
}) });
return ret return ret;
} }
function insertItemByType (item, pos) { function insertItemByType (item, pos) {
@ -250,28 +250,28 @@ function insertItemByType (item, pos) {
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu), submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
radio: () => { radio: () => {
// Grouping radio menu items // Grouping radio menu items
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)) item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
if (this.groupsMap[item.groupId] == null) { if (this.groupsMap[item.groupId] == null) {
this.groupsMap[item.groupId] = [] this.groupsMap[item.groupId] = [];
} }
this.groupsMap[item.groupId].push(item) this.groupsMap[item.groupId].push(item);
// Setting a radio menu item should flip other items in the group. // Setting a radio menu item should flip other items in the group.
v8Util.setHiddenValue(item, 'checked', item.checked) v8Util.setHiddenValue(item, 'checked', item.checked);
Object.defineProperty(item, 'checked', { Object.defineProperty(item, 'checked', {
enumerable: true, enumerable: true,
get: () => v8Util.getHiddenValue(item, 'checked'), get: () => v8Util.getHiddenValue(item, 'checked'),
set: () => { set: () => {
this.groupsMap[item.groupId].forEach(other => { this.groupsMap[item.groupId].forEach(other => {
if (other !== item) v8Util.setHiddenValue(other, 'checked', false) if (other !== item) v8Util.setHiddenValue(other, 'checked', false);
}) });
v8Util.setHiddenValue(item, 'checked', true) v8Util.setHiddenValue(item, 'checked', true);
} }
}) });
this.insertRadioItem(pos, item.commandId, item.label, item.groupId) this.insertRadioItem(pos, item.commandId, item.label, item.groupId);
} }
} };
types[item.type]() types[item.type]();
} }
module.exports = Menu module.exports = Menu;

View file

@ -1,12 +1,12 @@
import { MessagePortMain } from '@electron/internal/browser/message-port-main' import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { createPair } = process.electronBinding('message_port') const { createPair } = process.electronBinding('message_port');
export default class MessageChannelMain { export default class MessageChannelMain {
port1: MessagePortMain; port1: MessagePortMain;
port2: MessagePortMain; port2: MessagePortMain;
constructor () { constructor () {
const { port1, port2 } = createPair() const { port1, port2 } = createPair();
this.port1 = new MessagePortMain(port1) this.port1 = new MessagePortMain(port1);
this.port2 = new MessagePortMain(port2) this.port2 = new MessagePortMain(port2);
} }
} }

View file

@ -1,11 +1,11 @@
'use strict' 'use strict';
// TODO: Figure out a way to not duplicate this information between here and module-list // TODO: Figure out a way to not duplicate this information between here and module-list
// It is currently duplicated as module-list "require"s all the browser API file and the // It is currently duplicated as module-list "require"s all the browser API file and the
// remote module in the renderer process depends on that file. As a result webpack // remote module in the renderer process depends on that file. As a result webpack
// includes all the browser API files in the renderer process as well and we want to avoid that // includes all the browser API files in the renderer process as well and we want to avoid that
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Browser side modules, please sort alphabetically. // Browser side modules, please sort alphabetically.
module.exports = [ module.exports = [
@ -38,7 +38,7 @@ module.exports = [
{ name: 'View' }, { name: 'View' },
{ name: 'webContents' }, { name: 'webContents' },
{ name: 'WebContentsView' } { name: 'WebContentsView' }
] ];
if (features.isViewApiEnabled()) { if (features.isViewApiEnabled()) {
module.exports.push( module.exports.push(
@ -49,5 +49,5 @@ if (features.isViewApiEnabled()) {
{ name: 'MdTextButton' }, { name: 'MdTextButton' },
{ name: 'ResizeArea' }, { name: 'ResizeArea' },
{ name: 'TextField' } { name: 'TextField' }
) );
} }

View file

@ -1,6 +1,6 @@
// TODO: Updating this file also required updating the module-keys file // TODO: Updating this file also required updating the module-keys file
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Browser side modules, please sort alphabetically. // Browser side modules, please sort alphabetically.
export const browserModuleList: ElectronInternal.ModuleEntry[] = [ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
@ -33,7 +33,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'View', loader: () => require('./view') }, { name: 'View', loader: () => require('./view') },
{ name: 'webContents', loader: () => require('./web-contents') }, { name: 'webContents', loader: () => require('./web-contents') },
{ name: 'WebContentsView', loader: () => require('./web-contents-view') } { name: 'WebContentsView', loader: () => require('./web-contents-view') }
] ];
if (features.isViewApiEnabled()) { if (features.isViewApiEnabled()) {
browserModuleList.push( browserModuleList.push(
@ -44,5 +44,5 @@ if (features.isViewApiEnabled()) {
{ name: 'MdTextButton', loader: () => require('./views/md-text-button') }, { name: 'MdTextButton', loader: () => require('./views/md-text-button') },
{ name: 'ResizeArea', loader: () => require('./views/resize-area') }, { name: 'ResizeArea', loader: () => require('./views/resize-area') },
{ name: 'TextField', loader: () => require('./views/text-field') } { name: 'TextField', loader: () => require('./views/text-field') }
) );
} }

View file

@ -1,8 +1,8 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const { NativeTheme, nativeTheme } = process.electronBinding('native_theme') const { NativeTheme, nativeTheme } = process.electronBinding('native_theme');
Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype) Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype);
EventEmitter.call(nativeTheme as any) EventEmitter.call(nativeTheme as any);
module.exports = nativeTheme module.exports = nativeTheme;

View file

@ -1,32 +1,32 @@
'use strict' 'use strict';
// TODO(deepak1556): Deprecate and remove standalone netLog module, // TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of session module. // it is now a property of session module.
const { app, session } = require('electron') const { app, session } = require('electron');
// Fallback to default session. // Fallback to default session.
Object.setPrototypeOf(module.exports, new Proxy({}, { Object.setPrototypeOf(module.exports, new Proxy({}, {
get (target, property) { get (target, property) {
if (!app.isReady()) return if (!app.isReady()) return;
const netLog = session.defaultSession.netLog const netLog = session.defaultSession.netLog;
if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(netLog), property)) return if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(netLog), property)) return;
// check for properties on the prototype chain that aren't functions // check for properties on the prototype chain that aren't functions
if (typeof netLog[property] !== 'function') return netLog[property] if (typeof netLog[property] !== 'function') return netLog[property];
// Returning a native function directly would throw error. // Returning a native function directly would throw error.
return (...args) => netLog[property](...args) return (...args) => netLog[property](...args);
}, },
ownKeys () { ownKeys () {
if (!app.isReady()) return [] if (!app.isReady()) return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog)) return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog));
}, },
getOwnPropertyDescriptor (target) { getOwnPropertyDescriptor (target) {
return { configurable: true, enumerable: true } return { configurable: true, enumerable: true };
} }
})) }));

View file

@ -1,16 +1,16 @@
'use strict' 'use strict';
const url = require('url') const url = require('url');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Readable, Writable } = require('stream') const { Readable, Writable } = require('stream');
const { app } = require('electron') const { app } = require('electron');
const { Session } = process.electronBinding('session') const { Session } = process.electronBinding('session');
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net') const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net');
const { URLLoader } = net const { URLLoader } = net;
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype);
const kSupportedProtocols = new Set(['http:', 'https:']) const kSupportedProtocols = new Set(['http:', 'https:']);
// set of headers that Node.js discards duplicates for // set of headers that Node.js discards duplicates for
// see https://nodejs.org/api/http.html#http_message_headers // see https://nodejs.org/api/http.html#http_message_headers
@ -33,27 +33,27 @@ const discardableDuplicateHeaders = new Set([
'server', 'server',
'age', 'age',
'expires' 'expires'
]) ]);
class IncomingMessage extends Readable { class IncomingMessage extends Readable {
constructor (responseHead) { constructor (responseHead) {
super() super();
this._shouldPush = false this._shouldPush = false;
this._data = [] this._data = [];
this._responseHead = responseHead this._responseHead = responseHead;
} }
get statusCode () { get statusCode () {
return this._responseHead.statusCode return this._responseHead.statusCode;
} }
get statusMessage () { get statusMessage () {
return this._responseHead.statusMessage return this._responseHead.statusMessage;
} }
get headers () { get headers () {
const filteredHeaders = {} const filteredHeaders = {};
const { rawHeaders } = this._responseHead const { rawHeaders } = this._responseHead;
rawHeaders.forEach(header => { rawHeaders.forEach(header => {
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) && if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
discardableDuplicateHeaders.has(header.key)) { discardableDuplicateHeaders.has(header.key)) {
@ -63,147 +63,147 @@ class IncomingMessage extends Readable {
// keep set-cookie as an array per Node.js rules // keep set-cookie as an array per Node.js rules
// see https://nodejs.org/api/http.html#http_message_headers // see https://nodejs.org/api/http.html#http_message_headers
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) { if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
filteredHeaders[header.key].push(header.value) filteredHeaders[header.key].push(header.value);
} else { } else {
filteredHeaders[header.key] = [header.value] filteredHeaders[header.key] = [header.value];
} }
} else { } else {
// for non-cookie headers, the values are joined together with ', ' // for non-cookie headers, the values are joined together with ', '
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) { if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
filteredHeaders[header.key] += `, ${header.value}` filteredHeaders[header.key] += `, ${header.value}`;
} else { } else {
filteredHeaders[header.key] = header.value filteredHeaders[header.key] = header.value;
} }
} }
} }
}) });
return filteredHeaders return filteredHeaders;
} }
get httpVersion () { get httpVersion () {
return `${this.httpVersionMajor}.${this.httpVersionMinor}` return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
} }
get httpVersionMajor () { get httpVersionMajor () {
return this._responseHead.httpVersion.major return this._responseHead.httpVersion.major;
} }
get httpVersionMinor () { get httpVersionMinor () {
return this._responseHead.httpVersion.minor return this._responseHead.httpVersion.minor;
} }
get rawTrailers () { get rawTrailers () {
throw new Error('HTTP trailers are not supported') throw new Error('HTTP trailers are not supported');
} }
get trailers () { get trailers () {
throw new Error('HTTP trailers are not supported') throw new Error('HTTP trailers are not supported');
} }
_storeInternalData (chunk) { _storeInternalData (chunk) {
this._data.push(chunk) this._data.push(chunk);
this._pushInternalData() this._pushInternalData();
} }
_pushInternalData () { _pushInternalData () {
while (this._shouldPush && this._data.length > 0) { while (this._shouldPush && this._data.length > 0) {
const chunk = this._data.shift() const chunk = this._data.shift();
this._shouldPush = this.push(chunk) this._shouldPush = this.push(chunk);
} }
} }
_read () { _read () {
this._shouldPush = true this._shouldPush = true;
this._pushInternalData() this._pushInternalData();
} }
} }
/** Writable stream that buffers up everything written to it. */ /** Writable stream that buffers up everything written to it. */
class SlurpStream extends Writable { class SlurpStream extends Writable {
constructor () { constructor () {
super() super();
this._data = Buffer.alloc(0) this._data = Buffer.alloc(0);
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
this._data = Buffer.concat([this._data, chunk]) this._data = Buffer.concat([this._data, chunk]);
callback() callback();
} }
data () { return this._data } data () { return this._data; }
} }
class ChunkedBodyStream extends Writable { class ChunkedBodyStream extends Writable {
constructor (clientRequest) { constructor (clientRequest) {
super() super();
this._clientRequest = clientRequest this._clientRequest = clientRequest;
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
if (this._downstream) { if (this._downstream) {
this._downstream.write(chunk).then(callback, callback) this._downstream.write(chunk).then(callback, callback);
} else { } else {
// the contract of _write is that we won't be called again until we call // the contract of _write is that we won't be called again until we call
// the callback, so we're good to just save a single chunk. // the callback, so we're good to just save a single chunk.
this._pendingChunk = chunk this._pendingChunk = chunk;
this._pendingCallback = callback this._pendingCallback = callback;
// The first write to a chunked body stream begins the request. // The first write to a chunked body stream begins the request.
this._clientRequest._startRequest() this._clientRequest._startRequest();
} }
} }
_final (callback) { _final (callback) {
this._downstream.done() this._downstream.done();
callback() callback();
} }
startReading (pipe) { startReading (pipe) {
if (this._downstream) { if (this._downstream) {
throw new Error('two startReading calls???') throw new Error('two startReading calls???');
} }
this._downstream = pipe this._downstream = pipe;
if (this._pendingChunk) { if (this._pendingChunk) {
const doneWriting = (maybeError) => { const doneWriting = (maybeError) => {
const cb = this._pendingCallback const cb = this._pendingCallback;
delete this._pendingCallback delete this._pendingCallback;
delete this._pendingChunk delete this._pendingChunk;
cb(maybeError) cb(maybeError);
} };
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting) this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
} }
} }
} }
function parseOptions (options) { function parseOptions (options) {
if (typeof options === 'string') { if (typeof options === 'string') {
options = url.parse(options) options = url.parse(options);
} else { } else {
options = { ...options } options = { ...options };
} }
const method = (options.method || 'GET').toUpperCase() const method = (options.method || 'GET').toUpperCase();
let urlStr = options.url let urlStr = options.url;
if (!urlStr) { if (!urlStr) {
const urlObj = {} const urlObj = {};
const protocol = options.protocol || 'http:' const protocol = options.protocol || 'http:';
if (!kSupportedProtocols.has(protocol)) { if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported') throw new Error('Protocol "' + protocol + '" not supported');
} }
urlObj.protocol = protocol urlObj.protocol = protocol;
if (options.host) { if (options.host) {
urlObj.host = options.host urlObj.host = options.host;
} else { } else {
if (options.hostname) { if (options.hostname) {
urlObj.hostname = options.hostname urlObj.hostname = options.hostname;
} else { } else {
urlObj.hostname = 'localhost' urlObj.hostname = 'localhost';
} }
if (options.port) { if (options.port) {
urlObj.port = options.port urlObj.port = options.port;
} }
} }
@ -214,22 +214,22 @@ function parseOptions (options) {
// well, and b) possibly too restrictive for real-world usage. That's // well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create // why it only scans for spaces because those are guaranteed to create
// an invalid request. // an invalid request.
throw new TypeError('Request path contains unescaped characters') throw new TypeError('Request path contains unescaped characters');
} }
const pathObj = url.parse(options.path || '/') const pathObj = url.parse(options.path || '/');
urlObj.pathname = pathObj.pathname urlObj.pathname = pathObj.pathname;
urlObj.search = pathObj.search urlObj.search = pathObj.search;
urlObj.hash = pathObj.hash urlObj.hash = pathObj.hash;
urlStr = url.format(urlObj) urlStr = url.format(urlObj);
} }
const redirectPolicy = options.redirect || 'follow' const redirectPolicy = options.redirect || 'follow';
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) { if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual') throw new Error('redirect mode should be one of follow, error or manual');
} }
if (options.headers != null && typeof options.headers !== 'object') { if (options.headers != null && typeof options.headers !== 'object') {
throw new TypeError('headers must be an object') throw new TypeError('headers must be an object');
} }
const urlLoaderOptions = { const urlLoaderOptions = {
@ -237,180 +237,180 @@ function parseOptions (options) {
url: urlStr, url: urlStr,
redirectPolicy, redirectPolicy,
extraHeaders: options.headers || {} extraHeaders: options.headers || {}
} };
for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) { for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) {
if (!_isValidHeaderName(name)) { if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`) throw new Error(`Invalid header name: '${name}'`);
} }
if (!_isValidHeaderValue(value.toString())) { if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`) throw new Error(`Invalid value for header '${name}': '${value}'`);
} }
} }
if (options.session) { if (options.session) {
if (options.session instanceof Session) { if (options.session instanceof Session) {
urlLoaderOptions.session = options.session urlLoaderOptions.session = options.session;
} else { } else {
throw new TypeError('`session` should be an instance of the Session class') throw new TypeError('`session` should be an instance of the Session class');
} }
} else if (options.partition) { } else if (options.partition) {
if (typeof options.partition === 'string') { if (typeof options.partition === 'string') {
urlLoaderOptions.partition = options.partition urlLoaderOptions.partition = options.partition;
} else { } else {
throw new TypeError('`partition` should be a string') throw new TypeError('`partition` should be a string');
} }
} }
return urlLoaderOptions return urlLoaderOptions;
} }
class ClientRequest extends Writable { class ClientRequest extends Writable {
constructor (options, callback) { constructor (options, callback) {
super({ autoDestroy: true }) super({ autoDestroy: true });
if (!app.isReady()) { if (!app.isReady()) {
throw new Error('net module can only be used after app is ready') throw new Error('net module can only be used after app is ready');
} }
if (callback) { if (callback) {
this.once('response', callback) this.once('response', callback);
} }
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options) const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
this._urlLoaderOptions = urlLoaderOptions this._urlLoaderOptions = urlLoaderOptions;
this._redirectPolicy = redirectPolicy this._redirectPolicy = redirectPolicy;
this._started = false this._started = false;
} }
set chunkedEncoding (value) { set chunkedEncoding (value) {
if (this._started) { if (this._started) {
throw new Error('chunkedEncoding can only be set before the request is started') throw new Error('chunkedEncoding can only be set before the request is started');
} }
if (typeof this._chunkedEncoding !== 'undefined') { if (typeof this._chunkedEncoding !== 'undefined') {
throw new Error('chunkedEncoding can only be set once') throw new Error('chunkedEncoding can only be set once');
} }
this._chunkedEncoding = !!value this._chunkedEncoding = !!value;
if (this._chunkedEncoding) { if (this._chunkedEncoding) {
this._body = new ChunkedBodyStream(this) this._body = new ChunkedBodyStream(this);
this._urlLoaderOptions.body = (pipe) => { this._urlLoaderOptions.body = (pipe) => {
this._body.startReading(pipe) this._body.startReading(pipe);
} };
} }
} }
setHeader (name, value) { setHeader (name, value) {
if (typeof name !== 'string') { if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value)') throw new TypeError('`name` should be a string in setHeader(name, value)');
} }
if (value == null) { if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value)') throw new Error('`value` required in setHeader("' + name + '", value)');
} }
if (this._started || this._firstWrite) { if (this._started || this._firstWrite) {
throw new Error('Can\'t set headers after they are sent') throw new Error('Can\'t set headers after they are sent');
} }
if (!_isValidHeaderName(name)) { if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`) throw new Error(`Invalid header name: '${name}'`);
} }
if (!_isValidHeaderValue(value.toString())) { if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`) throw new Error(`Invalid value for header '${name}': '${value}'`);
} }
const key = name.toLowerCase() const key = name.toLowerCase();
this._urlLoaderOptions.extraHeaders[key] = value this._urlLoaderOptions.extraHeaders[key] = value;
} }
getHeader (name) { getHeader (name) {
if (name == null) { if (name == null) {
throw new Error('`name` is required for getHeader(name)') throw new Error('`name` is required for getHeader(name)');
} }
const key = name.toLowerCase() const key = name.toLowerCase();
return this._urlLoaderOptions.extraHeaders[key] return this._urlLoaderOptions.extraHeaders[key];
} }
removeHeader (name) { removeHeader (name) {
if (name == null) { if (name == null) {
throw new Error('`name` is required for removeHeader(name)') throw new Error('`name` is required for removeHeader(name)');
} }
if (this._started || this._firstWrite) { if (this._started || this._firstWrite) {
throw new Error('Can\'t remove headers after they are sent') throw new Error('Can\'t remove headers after they are sent');
} }
const key = name.toLowerCase() const key = name.toLowerCase();
delete this._urlLoaderOptions.extraHeaders[key] delete this._urlLoaderOptions.extraHeaders[key];
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
this._firstWrite = true this._firstWrite = true;
if (!this._body) { if (!this._body) {
this._body = new SlurpStream() this._body = new SlurpStream();
this._body.on('finish', () => { this._body.on('finish', () => {
this._urlLoaderOptions.body = this._body.data() this._urlLoaderOptions.body = this._body.data();
this._startRequest() this._startRequest();
}) });
} }
// TODO: is this the right way to forward to another stream? // TODO: is this the right way to forward to another stream?
this._body.write(chunk, encoding, callback) this._body.write(chunk, encoding, callback);
} }
_final (callback) { _final (callback) {
if (this._body) { if (this._body) {
// TODO: is this the right way to forward to another stream? // TODO: is this the right way to forward to another stream?
this._body.end(callback) this._body.end(callback);
} else { } else {
// end() called without a body, go ahead and start the request // end() called without a body, go ahead and start the request
this._startRequest() this._startRequest();
callback() callback();
} }
} }
_startRequest () { _startRequest () {
this._started = true this._started = true;
const stringifyValues = (obj) => { const stringifyValues = (obj) => {
const ret = {} const ret = {};
for (const k of Object.keys(obj)) { for (const k of Object.keys(obj)) {
ret[k] = obj[k].toString() ret[k] = obj[k].toString();
} }
return ret return ret;
} };
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) } const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) };
this._urlLoader = new URLLoader(opts) this._urlLoader = new URLLoader(opts);
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => { this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
const response = this._response = new IncomingMessage(responseHead) const response = this._response = new IncomingMessage(responseHead);
this.emit('response', response) this.emit('response', response);
}) });
this._urlLoader.on('data', (event, data) => { this._urlLoader.on('data', (event, data) => {
this._response._storeInternalData(Buffer.from(data)) this._response._storeInternalData(Buffer.from(data));
}) });
this._urlLoader.on('complete', () => { this._urlLoader.on('complete', () => {
if (this._response) { this._response._storeInternalData(null) } if (this._response) { this._response._storeInternalData(null); }
}) });
this._urlLoader.on('error', (event, netErrorString) => { this._urlLoader.on('error', (event, netErrorString) => {
const error = new Error(netErrorString) const error = new Error(netErrorString);
if (this._response) this._response.destroy(error) if (this._response) this._response.destroy(error);
this._die(error) this._die(error);
}) });
this._urlLoader.on('login', (event, authInfo, callback) => { this._urlLoader.on('login', (event, authInfo, callback) => {
const handled = this.emit('login', authInfo, callback) const handled = this.emit('login', authInfo, callback);
if (!handled) { if (!handled) {
// If there were no listeners, cancel the authentication request. // If there were no listeners, cancel the authentication request.
callback() callback();
} }
}) });
this._urlLoader.on('redirect', (event, redirectInfo, headers) => { this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
const { statusCode, newMethod, newUrl } = redirectInfo const { statusCode, newMethod, newUrl } = redirectInfo;
if (this._redirectPolicy === 'error') { if (this._redirectPolicy === 'error') {
this._die(new Error('Attempted to redirect, but redirect policy was \'error\'')) this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
} else if (this._redirectPolicy === 'manual') { } else if (this._redirectPolicy === 'manual') {
let _followRedirect = false let _followRedirect = false;
this._followRedirectCb = () => { _followRedirect = true } this._followRedirectCb = () => { _followRedirect = true; };
try { try {
this.emit('redirect', statusCode, newMethod, newUrl, headers) this.emit('redirect', statusCode, newMethod, newUrl, headers);
} finally { } finally {
this._followRedirectCb = null this._followRedirectCb = null;
if (!_followRedirect && !this._aborted) { if (!_followRedirect && !this._aborted) {
this._die(new Error('Redirect was cancelled')) this._die(new Error('Redirect was cancelled'));
} }
} }
} else if (this._redirectPolicy === 'follow') { } else if (this._redirectPolicy === 'follow') {
@ -418,61 +418,61 @@ class ClientRequest extends Writable {
// allowed but does nothing. (Perhaps it should throw an error // allowed but does nothing. (Perhaps it should throw an error
// though...? Since the redirect will happen regardless.) // though...? Since the redirect will happen regardless.)
try { try {
this._followRedirectCb = () => {} this._followRedirectCb = () => {};
this.emit('redirect', statusCode, newMethod, newUrl, headers) this.emit('redirect', statusCode, newMethod, newUrl, headers);
} finally { } finally {
this._followRedirectCb = null this._followRedirectCb = null;
} }
} else { } else {
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`)) this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
} }
}) });
this._urlLoader.on('upload-progress', (event, position, total) => { this._urlLoader.on('upload-progress', (event, position, total) => {
this._uploadProgress = { active: true, started: true, current: position, total } this._uploadProgress = { active: true, started: true, current: position, total };
this.emit('upload-progress', position, total) // Undocumented, for now this.emit('upload-progress', position, total); // Undocumented, for now
}) });
this._urlLoader.on('download-progress', (event, current) => { this._urlLoader.on('download-progress', (event, current) => {
if (this._response) { if (this._response) {
this._response.emit('download-progress', current) // Undocumented, for now this._response.emit('download-progress', current); // Undocumented, for now
} }
}) });
} }
followRedirect () { followRedirect () {
if (this._followRedirectCb) { if (this._followRedirectCb) {
this._followRedirectCb() this._followRedirectCb();
} else { } else {
throw new Error('followRedirect() called, but was not waiting for a redirect') throw new Error('followRedirect() called, but was not waiting for a redirect');
} }
} }
abort () { abort () {
if (!this._aborted) { if (!this._aborted) {
process.nextTick(() => { this.emit('abort') }) process.nextTick(() => { this.emit('abort'); });
} }
this._aborted = true this._aborted = true;
this._die() this._die();
} }
_die (err) { _die (err) {
this.destroy(err) this.destroy(err);
if (this._urlLoader) { if (this._urlLoader) {
this._urlLoader.cancel() this._urlLoader.cancel();
if (this._response) this._response.destroy(err) if (this._response) this._response.destroy(err);
} }
} }
getUploadProgress () { getUploadProgress () {
return this._uploadProgress ? { ...this._uploadProgress } : { active: false } return this._uploadProgress ? { ...this._uploadProgress } : { active: false };
} }
} }
Net.prototype.request = function (options, callback) { Net.prototype.request = function (options, callback) {
return new ClientRequest(options, callback) return new ClientRequest(options, callback);
} };
net.ClientRequest = ClientRequest net.ClientRequest = ClientRequest;
module.exports = net module.exports = net;

View file

@ -1,10 +1,10 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Notification, isSupported } = process.electronBinding('notification') const { Notification, isSupported } = process.electronBinding('notification');
Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype) Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype);
Notification.isSupported = isSupported Notification.isSupported = isSupported;
module.exports = Notification module.exports = Notification;

View file

@ -1,14 +1,14 @@
'use strict' 'use strict';
import { createLazyInstance } from '../utils' import { createLazyInstance } from '../utils';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor') const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor');
// PowerMonitor is an EventEmitter. // PowerMonitor is an EventEmitter.
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype);
const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true) const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true);
if (process.platform === 'linux') { if (process.platform === 'linux') {
// In order to delay system shutdown when e.preventDefault() is invoked // In order to delay system shutdown when e.preventDefault() is invoked
@ -18,21 +18,21 @@ if (process.platform === 'linux') {
// //
// So here we watch for 'shutdown' listeners to be added or removed and // So here we watch for 'shutdown' listeners to be added or removed and
// set or unset our shutdown delay lock accordingly. // set or unset our shutdown delay lock accordingly.
const { app } = require('electron') const { app } = require('electron');
app.whenReady().then(() => { app.whenReady().then(() => {
powerMonitor.on('newListener', (event: string) => { powerMonitor.on('newListener', (event: string) => {
// whenever the listener count is incremented to one... // whenever the listener count is incremented to one...
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.blockShutdown() powerMonitor.blockShutdown();
} }
}) });
powerMonitor.on('removeListener', (event: string) => { powerMonitor.on('removeListener', (event: string) => {
// whenever the listener count is decremented to zero... // whenever the listener count is decremented to zero...
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.unblockShutdown() powerMonitor.unblockShutdown();
} }
}) });
}) });
} }
module.exports = powerMonitor module.exports = powerMonitor;

View file

@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;

View file

@ -1,29 +1,29 @@
import { app, session } from 'electron' import { app, session } from 'electron';
// Global protocol APIs. // Global protocol APIs.
const protocol = process.electronBinding('protocol') const protocol = process.electronBinding('protocol');
// Fallback protocol APIs of default session. // Fallback protocol APIs of default session.
Object.setPrototypeOf(protocol, new Proxy({}, { Object.setPrototypeOf(protocol, new Proxy({}, {
get (_target, property) { get (_target, property) {
if (!app.isReady()) return if (!app.isReady()) return;
const protocol = session.defaultSession!.protocol const protocol = session.defaultSession!.protocol;
if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(protocol), property)) return if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(protocol), property)) return;
// Returning a native function directly would throw error. // Returning a native function directly would throw error.
return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args) return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args);
}, },
ownKeys () { ownKeys () {
if (!app.isReady()) return [] if (!app.isReady()) return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol)) return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol));
}, },
getOwnPropertyDescriptor () { getOwnPropertyDescriptor () {
return { configurable: true, enumerable: true } return { configurable: true, enumerable: true };
} }
})) }));
export default protocol export default protocol;

View file

@ -1,10 +1,10 @@
'use strict' 'use strict';
import { createLazyInstance } from '../utils' import { createLazyInstance } from '../utils';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Screen, createScreen } = process.electronBinding('screen') const { Screen, createScreen } = process.electronBinding('screen');
// Screen is an EventEmitter. // Screen is an EventEmitter.
Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype) Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype);
module.exports = createLazyInstance(createScreen, Screen, true) module.exports = createLazyInstance(createScreen, Screen, true);

View file

@ -1,25 +1,25 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { app, deprecate } = require('electron') const { app, deprecate } = require('electron');
const { fromPartition, Session, Cookies, Protocol, ServiceWorkerContext } = process.electronBinding('session') const { fromPartition, Session, Cookies, Protocol, ServiceWorkerContext } = process.electronBinding('session');
// Public API. // Public API.
Object.defineProperties(exports, { Object.defineProperties(exports, {
defaultSession: { defaultSession: {
enumerable: true, enumerable: true,
get () { return fromPartition('') } get () { return fromPartition(''); }
}, },
fromPartition: { fromPartition: {
enumerable: true, enumerable: true,
value: fromPartition value: fromPartition
} }
}) });
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype) Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype);
Object.setPrototypeOf(ServiceWorkerContext.prototype, EventEmitter.prototype) Object.setPrototypeOf(ServiceWorkerContext.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype) Object.setPrototypeOf(Session.prototype, EventEmitter.prototype);
Session.prototype._init = function () { Session.prototype._init = function () {
app.emit('session-created', this) app.emit('session-created', this);
} };

View file

@ -1,41 +1,41 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import { deprecate } from 'electron' import { deprecate } from 'electron';
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences') const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences');
// SystemPreferences is an EventEmitter. // SystemPreferences is an EventEmitter.
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype) Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype);
EventEmitter.call(systemPreferences) EventEmitter.call(systemPreferences);
if ('getAppLevelAppearance' in systemPreferences) { if ('getAppLevelAppearance' in systemPreferences) {
const nativeALAGetter = systemPreferences.getAppLevelAppearance const nativeALAGetter = systemPreferences.getAppLevelAppearance;
const nativeALASetter = systemPreferences.setAppLevelAppearance const nativeALASetter = systemPreferences.setAppLevelAppearance;
Object.defineProperty(SystemPreferences.prototype, 'appLevelAppearance', { Object.defineProperty(SystemPreferences.prototype, 'appLevelAppearance', {
get: () => nativeALAGetter.call(systemPreferences), get: () => nativeALAGetter.call(systemPreferences),
set: (appearance) => nativeALASetter.call(systemPreferences, appearance) set: (appearance) => nativeALASetter.call(systemPreferences, appearance)
}) });
} }
if ('getEffectiveAppearance' in systemPreferences) { if ('getEffectiveAppearance' in systemPreferences) {
const nativeEAGetter = systemPreferences.getAppLevelAppearance const nativeEAGetter = systemPreferences.getAppLevelAppearance;
Object.defineProperty(SystemPreferences.prototype, 'effectiveAppearance', { Object.defineProperty(SystemPreferences.prototype, 'effectiveAppearance', {
get: () => nativeEAGetter.call(systemPreferences) get: () => nativeEAGetter.call(systemPreferences)
}) });
} }
SystemPreferences.prototype.isDarkMode = deprecate.moveAPI( SystemPreferences.prototype.isDarkMode = deprecate.moveAPI(
SystemPreferences.prototype.isDarkMode, SystemPreferences.prototype.isDarkMode,
'systemPreferences.isDarkMode()', 'systemPreferences.isDarkMode()',
'nativeTheme.shouldUseDarkColors' 'nativeTheme.shouldUseDarkColors'
) );
SystemPreferences.prototype.isInvertedColorScheme = deprecate.moveAPI( SystemPreferences.prototype.isInvertedColorScheme = deprecate.moveAPI(
SystemPreferences.prototype.isInvertedColorScheme, SystemPreferences.prototype.isInvertedColorScheme,
'systemPreferences.isInvertedColorScheme()', 'systemPreferences.isInvertedColorScheme()',
'nativeTheme.shouldUseInvertedColorScheme' 'nativeTheme.shouldUseInvertedColorScheme'
) );
SystemPreferences.prototype.isHighContrastColorScheme = deprecate.moveAPI( SystemPreferences.prototype.isHighContrastColorScheme = deprecate.moveAPI(
SystemPreferences.prototype.isHighContrastColorScheme, SystemPreferences.prototype.isHighContrastColorScheme,
'systemPreferences.isHighContrastColorScheme()', 'systemPreferences.isHighContrastColorScheme()',
'nativeTheme.shouldUseHighContrastColors' 'nativeTheme.shouldUseHighContrastColors'
) );
module.exports = systemPreferences module.exports = systemPreferences;

View file

@ -1,24 +1,24 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { TopLevelWindow } = process.electronBinding('top_level_window') const { TopLevelWindow } = process.electronBinding('top_level_window');
Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype) Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype);
TopLevelWindow.prototype._init = function () { TopLevelWindow.prototype._init = function () {
// Avoid recursive require. // Avoid recursive require.
const { app } = electron const { app } = electron;
// Simulate the application menu on platforms other than macOS. // Simulate the application menu on platforms other than macOS.
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
const menu = app.applicationMenu const menu = app.applicationMenu;
if (menu) this.setMenu(menu) if (menu) this.setMenu(menu);
} }
} };
TopLevelWindow.getFocusedWindow = () => { TopLevelWindow.getFocusedWindow = () => {
return TopLevelWindow.getAllWindows().find((win) => win.isFocused()) return TopLevelWindow.getAllWindows().find((win) => win.isFocused());
} };
module.exports = TopLevelWindow module.exports = TopLevelWindow;

View file

@ -1,365 +1,365 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
let nextItemID = 1 let nextItemID = 1;
class TouchBar extends EventEmitter { class TouchBar extends EventEmitter {
// Bind a touch bar to a window // Bind a touch bar to a window
static _setOnWindow (touchBar, window) { static _setOnWindow (touchBar, window) {
if (window._touchBar != null) { if (window._touchBar != null) {
window._touchBar._removeFromWindow(window) window._touchBar._removeFromWindow(window);
} }
if (touchBar == null) { if (touchBar == null) {
window._setTouchBarItems([]) window._setTouchBarItems([]);
return return;
} }
if (Array.isArray(touchBar)) { if (Array.isArray(touchBar)) {
touchBar = new TouchBar(touchBar) touchBar = new TouchBar(touchBar);
} }
touchBar._addToWindow(window) touchBar._addToWindow(window);
} }
constructor (options) { constructor (options) {
super() super();
if (options == null) { if (options == null) {
throw new Error('Must specify options object as first argument') throw new Error('Must specify options object as first argument');
} }
let { items, escapeItem } = options let { items, escapeItem } = options;
if (!Array.isArray(items)) { if (!Array.isArray(items)) {
items = [] items = [];
} }
this.changeListener = (item) => { this.changeListener = (item) => {
this.emit('change', item.id, item.type) this.emit('change', item.id, item.type);
} };
this.windowListeners = {} this.windowListeners = {};
this.items = {} this.items = {};
this.ordereredItems = [] this.ordereredItems = [];
this.escapeItem = escapeItem this.escapeItem = escapeItem;
const registerItem = (item) => { const registerItem = (item) => {
this.items[item.id] = item this.items[item.id] = item;
item.on('change', this.changeListener) item.on('change', this.changeListener);
if (item.child instanceof TouchBar) { if (item.child instanceof TouchBar) {
item.child.ordereredItems.forEach(registerItem) item.child.ordereredItems.forEach(registerItem);
} }
} };
let hasOtherItemsProxy = false let hasOtherItemsProxy = false;
const idSet = new Set() const idSet = new Set();
items.forEach((item) => { items.forEach((item) => {
if (!(item instanceof TouchBarItem)) { if (!(item instanceof TouchBarItem)) {
throw new Error('Each item must be an instance of TouchBarItem') throw new Error('Each item must be an instance of TouchBarItem');
} }
if (item.type === 'other_items_proxy') { if (item.type === 'other_items_proxy') {
if (!hasOtherItemsProxy) { if (!hasOtherItemsProxy) {
hasOtherItemsProxy = true hasOtherItemsProxy = true;
} else { } else {
throw new Error('Must only have one OtherItemsProxy per TouchBar') throw new Error('Must only have one OtherItemsProxy per TouchBar');
} }
} }
if (!idSet.has(item.id)) { if (!idSet.has(item.id)) {
idSet.add(item.id) idSet.add(item.id);
} else { } else {
throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar') throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar');
} }
}) });
// register in separate loop after all items are validated // register in separate loop after all items are validated
for (const item of items) { for (const item of items) {
this.ordereredItems.push(item) this.ordereredItems.push(item);
registerItem(item) registerItem(item);
} }
} }
set escapeItem (item) { set escapeItem (item) {
if (item != null && !(item instanceof TouchBarItem)) { if (item != null && !(item instanceof TouchBarItem)) {
throw new Error('Escape item must be an instance of TouchBarItem') throw new Error('Escape item must be an instance of TouchBarItem');
} }
if (this.escapeItem != null) { if (this.escapeItem != null) {
this.escapeItem.removeListener('change', this.changeListener) this.escapeItem.removeListener('change', this.changeListener);
} }
this._escapeItem = item this._escapeItem = item;
if (this.escapeItem != null) { if (this.escapeItem != null) {
this.escapeItem.on('change', this.changeListener) this.escapeItem.on('change', this.changeListener);
} }
this.emit('escape-item-change', item) this.emit('escape-item-change', item);
} }
get escapeItem () { get escapeItem () {
return this._escapeItem return this._escapeItem;
} }
_addToWindow (window) { _addToWindow (window) {
const { id } = window const { id } = window;
// Already added to window // Already added to window
if (Object.prototype.hasOwnProperty.call(this.windowListeners, id)) return if (Object.prototype.hasOwnProperty.call(this.windowListeners, id)) return;
window._touchBar = this window._touchBar = this;
const changeListener = (itemID) => { const changeListener = (itemID) => {
window._refreshTouchBarItem(itemID) window._refreshTouchBarItem(itemID);
} };
this.on('change', changeListener) this.on('change', changeListener);
const escapeItemListener = (item) => { const escapeItemListener = (item) => {
window._setEscapeTouchBarItem(item != null ? item : {}) window._setEscapeTouchBarItem(item != null ? item : {});
} };
this.on('escape-item-change', escapeItemListener) this.on('escape-item-change', escapeItemListener);
const interactionListener = (event, itemID, details) => { const interactionListener = (event, itemID, details) => {
let item = this.items[itemID] let item = this.items[itemID];
if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
item = this.escapeItem item = this.escapeItem;
} }
if (item != null && item.onInteraction != null) { if (item != null && item.onInteraction != null) {
item.onInteraction(details) item.onInteraction(details);
} }
} };
window.on('-touch-bar-interaction', interactionListener) window.on('-touch-bar-interaction', interactionListener);
const removeListeners = () => { const removeListeners = () => {
this.removeListener('change', changeListener) this.removeListener('change', changeListener);
this.removeListener('escape-item-change', escapeItemListener) this.removeListener('escape-item-change', escapeItemListener);
window.removeListener('-touch-bar-interaction', interactionListener) window.removeListener('-touch-bar-interaction', interactionListener);
window.removeListener('closed', removeListeners) window.removeListener('closed', removeListeners);
window._touchBar = null window._touchBar = null;
delete this.windowListeners[id] delete this.windowListeners[id];
const unregisterItems = (items) => { const unregisterItems = (items) => {
for (const item of items) { for (const item of items) {
item.removeListener('change', this.changeListener) item.removeListener('change', this.changeListener);
if (item.child instanceof TouchBar) { if (item.child instanceof TouchBar) {
unregisterItems(item.child.ordereredItems) unregisterItems(item.child.ordereredItems);
} }
} }
} };
unregisterItems(this.ordereredItems) unregisterItems(this.ordereredItems);
if (this.escapeItem) { if (this.escapeItem) {
this.escapeItem.removeListener('change', this.changeListener) this.escapeItem.removeListener('change', this.changeListener);
} }
} };
window.once('closed', removeListeners) window.once('closed', removeListeners);
this.windowListeners[id] = removeListeners this.windowListeners[id] = removeListeners;
window._setTouchBarItems(this.ordereredItems) window._setTouchBarItems(this.ordereredItems);
escapeItemListener(this.escapeItem) escapeItemListener(this.escapeItem);
} }
_removeFromWindow (window) { _removeFromWindow (window) {
const removeListeners = this.windowListeners[window.id] const removeListeners = this.windowListeners[window.id];
if (removeListeners != null) removeListeners() if (removeListeners != null) removeListeners();
} }
} }
class TouchBarItem extends EventEmitter { class TouchBarItem extends EventEmitter {
constructor () { constructor () {
super() super();
this._addImmutableProperty('id', `${nextItemID++}`) this._addImmutableProperty('id', `${nextItemID++}`);
this._parents = [] this._parents = [];
} }
_addImmutableProperty (name, value) { _addImmutableProperty (name, value) {
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
get: function () { get: function () {
return value return value;
}, },
set: function () { set: function () {
throw new Error(`Cannot override property ${name}`) throw new Error(`Cannot override property ${name}`);
}, },
enumerable: true, enumerable: true,
configurable: false configurable: false
}) });
} }
_addLiveProperty (name, initialValue) { _addLiveProperty (name, initialValue) {
const privateName = `_${name}` const privateName = `_${name}`;
this[privateName] = initialValue this[privateName] = initialValue;
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
get: function () { get: function () {
return this[privateName] return this[privateName];
}, },
set: function (value) { set: function (value) {
this[privateName] = value this[privateName] = value;
this.emit('change', this) this.emit('change', this);
}, },
enumerable: true enumerable: true
}) });
} }
_addParent (item) { _addParent (item) {
const existing = this._parents.some(test => test.id === item.id) const existing = this._parents.some(test => test.id === item.id);
if (!existing) { if (!existing) {
this._parents.push({ this._parents.push({
id: item.id, id: item.id,
type: item.type type: item.type
}) });
} }
} }
} }
TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'button') this._addImmutableProperty('type', 'button');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('accessibilityLabel', config.accessibilityLabel) this._addLiveProperty('accessibilityLabel', config.accessibilityLabel);
this._addLiveProperty('backgroundColor', config.backgroundColor) this._addLiveProperty('backgroundColor', config.backgroundColor);
this._addLiveProperty('icon', config.icon) this._addLiveProperty('icon', config.icon);
this._addLiveProperty('iconPosition', config.iconPosition) this._addLiveProperty('iconPosition', config.iconPosition);
this._addLiveProperty('enabled', typeof config.enabled !== 'boolean' ? true : config.enabled) this._addLiveProperty('enabled', typeof config.enabled !== 'boolean' ? true : config.enabled);
if (typeof config.click === 'function') { if (typeof config.click === 'function') {
this._addImmutableProperty('onInteraction', () => { this._addImmutableProperty('onInteraction', () => {
config.click() config.click();
}) });
} }
} }
} };
TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'colorpicker') this._addImmutableProperty('type', 'colorpicker');
this._addLiveProperty('availableColors', config.availableColors) this._addLiveProperty('availableColors', config.availableColors);
this._addLiveProperty('selectedColor', config.selectedColor) this._addLiveProperty('selectedColor', config.selectedColor);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._selectedColor = details.color this._selectedColor = details.color;
config.change(details.color) config.change(details.color);
}) });
} }
} }
} };
TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'group') this._addImmutableProperty('type', 'group');
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
this._addLiveProperty('child', defaultChild) this._addLiveProperty('child', defaultChild);
this.child.ordereredItems.forEach((item) => item._addParent(this)) this.child.ordereredItems.forEach((item) => item._addParent(this));
} }
} };
TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'label') this._addImmutableProperty('type', 'label');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('accessibilityLabel', config.accessibilityLabel) this._addLiveProperty('accessibilityLabel', config.accessibilityLabel);
this._addLiveProperty('textColor', config.textColor) this._addLiveProperty('textColor', config.textColor);
} }
} };
TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'popover') this._addImmutableProperty('type', 'popover');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('icon', config.icon) this._addLiveProperty('icon', config.icon);
this._addLiveProperty('showCloseButton', config.showCloseButton) this._addLiveProperty('showCloseButton', config.showCloseButton);
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
this._addLiveProperty('child', defaultChild) this._addLiveProperty('child', defaultChild);
this.child.ordereredItems.forEach((item) => item._addParent(this)) this.child.ordereredItems.forEach((item) => item._addParent(this));
} }
} };
TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'slider') this._addImmutableProperty('type', 'slider');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('minValue', config.minValue) this._addLiveProperty('minValue', config.minValue);
this._addLiveProperty('maxValue', config.maxValue) this._addLiveProperty('maxValue', config.maxValue);
this._addLiveProperty('value', config.value) this._addLiveProperty('value', config.value);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._value = details.value this._value = details.value;
config.change(details.value) config.change(details.value);
}) });
} }
} }
} };
TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'spacer') this._addImmutableProperty('type', 'spacer');
this._addImmutableProperty('size', config.size) this._addImmutableProperty('size', config.size);
} }
} };
TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'segmented_control') this._addImmutableProperty('type', 'segmented_control');
this._addLiveProperty('segmentStyle', config.segmentStyle) this._addLiveProperty('segmentStyle', config.segmentStyle);
this._addLiveProperty('segments', config.segments || []) this._addLiveProperty('segments', config.segments || []);
this._addLiveProperty('selectedIndex', config.selectedIndex) this._addLiveProperty('selectedIndex', config.selectedIndex);
this._addLiveProperty('mode', config.mode) this._addLiveProperty('mode', config.mode);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._selectedIndex = details.selectedIndex this._selectedIndex = details.selectedIndex;
config.change(details.selectedIndex, details.isSelected) config.change(details.selectedIndex, details.isSelected);
}) });
} }
} }
} };
TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
let { select, highlight } = config let { select, highlight } = config;
this._addImmutableProperty('type', 'scrubber') this._addImmutableProperty('type', 'scrubber');
this._addLiveProperty('items', config.items) this._addLiveProperty('items', config.items);
this._addLiveProperty('selectedStyle', config.selectedStyle || null) this._addLiveProperty('selectedStyle', config.selectedStyle || null);
this._addLiveProperty('overlayStyle', config.overlayStyle || null) this._addLiveProperty('overlayStyle', config.overlayStyle || null);
this._addLiveProperty('showArrowButtons', config.showArrowButtons || false) this._addLiveProperty('showArrowButtons', config.showArrowButtons || false);
this._addLiveProperty('mode', config.mode || 'free') this._addLiveProperty('mode', config.mode || 'free');
const cont = typeof config.continuous === 'undefined' ? true : config.continuous const cont = typeof config.continuous === 'undefined' ? true : config.continuous;
this._addLiveProperty('continuous', cont) this._addLiveProperty('continuous', cont);
if (typeof select === 'function' || typeof highlight === 'function') { if (typeof select === 'function' || typeof highlight === 'function') {
if (select == null) select = () => {} if (select == null) select = () => {};
if (highlight == null) highlight = () => {} if (highlight == null) highlight = () => {};
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
if (details.type === 'select' && typeof select === 'function') { if (details.type === 'select' && typeof select === 'function') {
select(details.selectedIndex) select(details.selectedIndex);
} else if (details.type === 'highlight' && typeof highlight === 'function') { } else if (details.type === 'highlight' && typeof highlight === 'function') {
highlight(details.highlightedIndex) highlight(details.highlightedIndex);
} }
}) });
} }
} }
} };
TouchBar.TouchBarOtherItemsProxy = class TouchBarOtherItemsProxy extends TouchBarItem { TouchBar.TouchBarOtherItemsProxy = class TouchBarOtherItemsProxy extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
this._addImmutableProperty('type', 'other_items_proxy') this._addImmutableProperty('type', 'other_items_proxy');
} }
} };
module.exports = TouchBar module.exports = TouchBar;

View file

@ -1,9 +1,9 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { deprecate } = require('electron') const { deprecate } = require('electron');
const { Tray } = process.electronBinding('tray') const { Tray } = process.electronBinding('tray');
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype) Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype);
module.exports = Tray module.exports = Tray;

View file

@ -1,11 +1,11 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { View } = process.electronBinding('view') const { View } = process.electronBinding('view');
Object.setPrototypeOf(View.prototype, EventEmitter.prototype) Object.setPrototypeOf(View.prototype, EventEmitter.prototype);
View.prototype._init = function () { View.prototype._init = function () {
} };
module.exports = View module.exports = View;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { LayoutManager } = electron const { LayoutManager } = electron;
const { BoxLayout } = process.electronBinding('box_layout') const { BoxLayout } = process.electronBinding('box_layout');
Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype) Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype);
BoxLayout.prototype._init = function () { BoxLayout.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
LayoutManager.prototype._init.call(this) LayoutManager.prototype._init.call(this);
} };
module.exports = BoxLayout module.exports = BoxLayout;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { Button } = process.electronBinding('button') const { Button } = process.electronBinding('button');
Object.setPrototypeOf(Button.prototype, View.prototype) Object.setPrototypeOf(Button.prototype, View.prototype);
Button.prototype._init = function () { Button.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = Button module.exports = Button;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { Button } = electron const { Button } = electron;
const { LabelButton } = process.electronBinding('label_button') const { LabelButton } = process.electronBinding('label_button');
Object.setPrototypeOf(LabelButton.prototype, Button.prototype) Object.setPrototypeOf(LabelButton.prototype, Button.prototype);
LabelButton.prototype._init = function () { LabelButton.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
Button.prototype._init.call(this) Button.prototype._init.call(this);
} };
module.exports = LabelButton module.exports = LabelButton;

View file

@ -1,8 +1,8 @@
'use strict' 'use strict';
const { LayoutManager } = process.electronBinding('layout_manager') const { LayoutManager } = process.electronBinding('layout_manager');
LayoutManager.prototype._init = function () { LayoutManager.prototype._init = function () {
} };
module.exports = LayoutManager module.exports = LayoutManager;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { LabelButton } = electron const { LabelButton } = electron;
const { MdTextButton } = process.electronBinding('md_text_button') const { MdTextButton } = process.electronBinding('md_text_button');
Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype) Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype);
MdTextButton.prototype._init = function () { MdTextButton.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
LabelButton.prototype._init.call(this) LabelButton.prototype._init.call(this);
} };
module.exports = MdTextButton module.exports = MdTextButton;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { ResizeArea } = process.electronBinding('resize_area') const { ResizeArea } = process.electronBinding('resize_area');
Object.setPrototypeOf(ResizeArea.prototype, View.prototype) Object.setPrototypeOf(ResizeArea.prototype, View.prototype);
ResizeArea.prototype._init = function () { ResizeArea.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = ResizeArea module.exports = ResizeArea;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { TextField } = process.electronBinding('text_field') const { TextField } = process.electronBinding('text_field');
Object.setPrototypeOf(TextField.prototype, View.prototype) Object.setPrototypeOf(TextField.prototype, View.prototype);
TextField.prototype._init = function () { TextField.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = TextField module.exports = TextField;

View file

@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { WebContentsView } = process.electronBinding('web_contents_view') const { WebContentsView } = process.electronBinding('web_contents_view');
Object.setPrototypeOf(WebContentsView.prototype, View.prototype) Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
WebContentsView.prototype._init = function () { WebContentsView.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = WebContentsView module.exports = WebContentsView;

View file

@ -1,27 +1,27 @@
'use strict' 'use strict';
const features = process.electronBinding('features') const features = process.electronBinding('features');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const electron = require('electron') const electron = require('electron');
const path = require('path') const path = require('path');
const url = require('url') const url = require('url');
const { app, ipcMain, session } = electron const { app, ipcMain, session } = electron;
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager') const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager');
const NavigationController = require('@electron/internal/browser/navigation-controller') const NavigationController = require('@electron/internal/browser/navigation-controller');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { MessagePortMain } = require('@electron/internal/browser/message-port-main') const { MessagePortMain } = require('@electron/internal/browser/message-port-main');
// session is not used here, the purpose is to make sure session is initalized // session is not used here, the purpose is to make sure session is initalized
// before the webContents module. // before the webContents module.
// eslint-disable-next-line // eslint-disable-next-line
session session
let nextId = 0 let nextId = 0;
const getNextId = function () { const getNextId = function () {
return ++nextId return ++nextId;
} };
// Stock page sizes // Stock page sizes
const PDFPageSizes = { const PDFPageSizes = {
@ -62,7 +62,7 @@ const PDFPageSizes = {
width_microns: 279400, width_microns: 279400,
custom_display_name: 'Tabloid' custom_display_name: 'Tabloid'
} }
} };
// Default printing setting // Default printing setting
const defaultPrintingSetting = { const defaultPrintingSetting = {
@ -94,90 +94,90 @@ const defaultPrintingSetting = {
// 2 = color - see ColorModel in //printing/print_job_constants.h // 2 = color - see ColorModel in //printing/print_job_constants.h
color: 2, color: 2,
collate: true collate: true
} };
// JavaScript implementations of WebContents. // JavaScript implementations of WebContents.
const binding = process.electronBinding('web_contents') const binding = process.electronBinding('web_contents');
const { WebContents } = binding const { WebContents } = binding;
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype) Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype);
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype) Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype);
// WebContents::send(channel, args..) // WebContents::send(channel, args..)
// WebContents::sendToAll(channel, args..) // WebContents::sendToAll(channel, args..)
WebContents.prototype.send = function (channel, ...args) { WebContents.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = false const internal = false;
const sendToAll = false const sendToAll = false;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype.postMessage = function (...args) { WebContents.prototype.postMessage = function (...args) {
if (Array.isArray(args[2])) { if (Array.isArray(args[2])) {
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o) args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
} }
this._postMessage(...args) this._postMessage(...args);
} };
WebContents.prototype.sendToAll = function (channel, ...args) { WebContents.prototype.sendToAll = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = false const internal = false;
const sendToAll = true const sendToAll = true;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype._sendInternal = function (channel, ...args) { WebContents.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = true const internal = true;
const sendToAll = false const sendToAll = false;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype._sendInternalToAll = function (channel, ...args) { WebContents.prototype._sendInternalToAll = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = true const internal = true;
const sendToAll = true const sendToAll = true;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) { WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') { } else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument') throw new Error('Missing required frameId argument');
} }
const internal = false const internal = false;
const sendToAll = false const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args) return this._sendToFrame(internal, sendToAll, frameId, channel, args);
} };
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) { WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') { } else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument') throw new Error('Missing required frameId argument');
} }
const internal = true const internal = true;
const sendToAll = false const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args) return this._sendToFrame(internal, sendToAll, frameId, channel, args);
} };
// Following methods are mapped to webFrame. // Following methods are mapped to webFrame.
const webFrameMethods = [ const webFrameMethods = [
@ -185,138 +185,138 @@ const webFrameMethods = [
'insertText', 'insertText',
'removeInsertedCSS', 'removeInsertedCSS',
'setVisualZoomLevelLimits' 'setVisualZoomLevelLimits'
] ];
for (const method of webFrameMethods) { for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args) { WebContents.prototype[method] = function (...args) {
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
} };
} }
const waitTillCanExecuteJavaScript = async (webContents) => { const waitTillCanExecuteJavaScript = async (webContents) => {
if (webContents.getURL() && !webContents.isLoadingMainFrame()) return if (webContents.getURL() && !webContents.isLoadingMainFrame()) return;
return new Promise((resolve) => { return new Promise((resolve) => {
webContents.once('did-stop-loading', () => { webContents.once('did-stop-loading', () => {
resolve() resolve();
}) });
}) });
} };
// Make sure WebContents::executeJavaScript would run the code only when the // Make sure WebContents::executeJavaScript would run the code only when the
// WebContents has been loaded. // WebContents has been loaded.
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) { WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
await waitTillCanExecuteJavaScript(this) await waitTillCanExecuteJavaScript(this);
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture);
} };
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, hasUserGesture) { WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, hasUserGesture) {
await waitTillCanExecuteJavaScript(this) await waitTillCanExecuteJavaScript(this);
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', code, hasUserGesture) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', code, hasUserGesture);
} };
// Translate the options of printToPDF. // Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options) { WebContents.prototype.printToPDF = function (options) {
const printSettings = { const printSettings = {
...defaultPrintingSetting, ...defaultPrintingSetting,
requestID: getNextId() requestID: getNextId()
} };
if (options.landscape !== undefined) { if (options.landscape !== undefined) {
if (typeof options.landscape !== 'boolean') { if (typeof options.landscape !== 'boolean') {
const error = new Error('landscape must be a Boolean') const error = new Error('landscape must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.landscape = options.landscape printSettings.landscape = options.landscape;
} }
if (options.scaleFactor !== undefined) { if (options.scaleFactor !== undefined) {
if (typeof options.scaleFactor !== 'number') { if (typeof options.scaleFactor !== 'number') {
const error = new Error('scaleFactor must be a Number') const error = new Error('scaleFactor must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.scaleFactor = options.scaleFactor printSettings.scaleFactor = options.scaleFactor;
} }
if (options.marginsType !== undefined) { if (options.marginsType !== undefined) {
if (typeof options.marginsType !== 'number') { if (typeof options.marginsType !== 'number') {
const error = new Error('marginsType must be a Number') const error = new Error('marginsType must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.marginsType = options.marginsType printSettings.marginsType = options.marginsType;
} }
if (options.printSelectionOnly !== undefined) { if (options.printSelectionOnly !== undefined) {
if (typeof options.printSelectionOnly !== 'boolean') { if (typeof options.printSelectionOnly !== 'boolean') {
const error = new Error('printSelectionOnly must be a Boolean') const error = new Error('printSelectionOnly must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.shouldPrintSelectionOnly = options.printSelectionOnly printSettings.shouldPrintSelectionOnly = options.printSelectionOnly;
} }
if (options.printBackground !== undefined) { if (options.printBackground !== undefined) {
if (typeof options.printBackground !== 'boolean') { if (typeof options.printBackground !== 'boolean') {
const error = new Error('printBackground must be a Boolean') const error = new Error('printBackground must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.shouldPrintBackgrounds = options.printBackground printSettings.shouldPrintBackgrounds = options.printBackground;
} }
if (options.pageRanges !== undefined) { if (options.pageRanges !== undefined) {
const pageRanges = options.pageRanges const pageRanges = options.pageRanges;
if (!Object.prototype.hasOwnProperty.call(pageRanges, 'from') || !Object.prototype.hasOwnProperty.call(pageRanges, 'to')) { if (!Object.prototype.hasOwnProperty.call(pageRanges, 'from') || !Object.prototype.hasOwnProperty.call(pageRanges, 'to')) {
const error = new Error('pageRanges must be an Object with \'from\' and \'to\' properties') const error = new Error('pageRanges must be an Object with \'from\' and \'to\' properties');
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof pageRanges.from !== 'number') { if (typeof pageRanges.from !== 'number') {
const error = new Error('pageRanges.from must be a Number') const error = new Error('pageRanges.from must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof pageRanges.to !== 'number') { if (typeof pageRanges.to !== 'number') {
const error = new Error('pageRanges.to must be a Number') const error = new Error('pageRanges.to must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
// Chromium uses 1-based page ranges, so increment each by 1. // Chromium uses 1-based page ranges, so increment each by 1.
printSettings.pageRange = [{ printSettings.pageRange = [{
from: pageRanges.from + 1, from: pageRanges.from + 1,
to: pageRanges.to + 1 to: pageRanges.to + 1
}] }];
} }
if (options.headerFooter !== undefined) { if (options.headerFooter !== undefined) {
const headerFooter = options.headerFooter const headerFooter = options.headerFooter;
printSettings.headerFooterEnabled = true printSettings.headerFooterEnabled = true;
if (typeof headerFooter === 'object') { if (typeof headerFooter === 'object') {
if (!headerFooter.url || !headerFooter.title) { if (!headerFooter.url || !headerFooter.title) {
const error = new Error('url and title properties are required for headerFooter') const error = new Error('url and title properties are required for headerFooter');
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof headerFooter.title !== 'string') { if (typeof headerFooter.title !== 'string') {
const error = new Error('headerFooter.title must be a String') const error = new Error('headerFooter.title must be a String');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.title = headerFooter.title printSettings.title = headerFooter.title;
if (typeof headerFooter.url !== 'string') { if (typeof headerFooter.url !== 'string') {
const error = new Error('headerFooter.url must be a String') const error = new Error('headerFooter.url must be a String');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.url = headerFooter.url printSettings.url = headerFooter.url;
} else { } else {
const error = new Error('headerFooter must be an Object') const error = new Error('headerFooter must be an Object');
return Promise.reject(error) return Promise.reject(error);
} }
} }
// Optionally set size for PDF. // Optionally set size for PDF.
if (options.pageSize !== undefined) { if (options.pageSize !== undefined) {
const pageSize = options.pageSize const pageSize = options.pageSize;
if (typeof pageSize === 'object') { if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) { if (!pageSize.height || !pageSize.width) {
const error = new Error('height and width properties are required for pageSize') const error = new Error('height and width properties are required for pageSize');
return Promise.reject(error) return Promise.reject(error);
} }
// Dimensions in Microns // Dimensions in Microns
// 1 meter = 10^6 microns // 1 meter = 10^6 microns
@ -325,28 +325,28 @@ WebContents.prototype.printToPDF = function (options) {
custom_display_name: 'Custom', custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height), height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width) width_microns: Math.ceil(pageSize.width)
} };
} else if (PDFPageSizes[pageSize]) { } else if (PDFPageSizes[pageSize]) {
printSettings.mediaSize = PDFPageSizes[pageSize] printSettings.mediaSize = PDFPageSizes[pageSize];
} else { } else {
const error = new Error(`Unsupported pageSize: ${pageSize}`) const error = new Error(`Unsupported pageSize: ${pageSize}`);
return Promise.reject(error) return Promise.reject(error);
} }
} else { } else {
printSettings.mediaSize = PDFPageSizes.A4 printSettings.mediaSize = PDFPageSizes.A4;
} }
// Chromium expects this in a 0-100 range number, not as float // Chromium expects this in a 0-100 range number, not as float
printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100 printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100;
// PrinterType enum from //printing/print_job_constants.h // PrinterType enum from //printing/print_job_constants.h
printSettings.printerType = 2 printSettings.printerType = 2;
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
return this._printToPDF(printSettings) return this._printToPDF(printSettings);
} else { } else {
const error = new Error('Printing feature is disabled') const error = new Error('Printing feature is disabled');
return Promise.reject(error) return Promise.reject(error);
} }
} };
WebContents.prototype.print = function (options = {}, callback) { WebContents.prototype.print = function (options = {}, callback) {
// TODO(codebytere): deduplicate argument sanitization by moving rest of // TODO(codebytere): deduplicate argument sanitization by moving rest of
@ -354,10 +354,10 @@ WebContents.prototype.print = function (options = {}, callback) {
if (typeof options === 'object') { if (typeof options === 'object') {
// Optionally set size for PDF. // Optionally set size for PDF.
if (options.pageSize !== undefined) { if (options.pageSize !== undefined) {
const pageSize = options.pageSize const pageSize = options.pageSize;
if (typeof pageSize === 'object') { if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) { if (!pageSize.height || !pageSize.width) {
throw new Error('height and width properties are required for pageSize') throw new Error('height and width properties are required for pageSize');
} }
// Dimensions in Microns - 1 meter = 10^6 microns // Dimensions in Microns - 1 meter = 10^6 microns
options.mediaSize = { options.mediaSize = {
@ -365,40 +365,40 @@ WebContents.prototype.print = function (options = {}, callback) {
custom_display_name: 'Custom', custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height), height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width) width_microns: Math.ceil(pageSize.width)
} };
} else if (PDFPageSizes[pageSize]) { } else if (PDFPageSizes[pageSize]) {
options.mediaSize = PDFPageSizes[pageSize] options.mediaSize = PDFPageSizes[pageSize];
} else { } else {
throw new Error(`Unsupported pageSize: ${pageSize}`) throw new Error(`Unsupported pageSize: ${pageSize}`);
} }
} }
} }
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
if (callback) { if (callback) {
this._print(options, callback) this._print(options, callback);
} else { } else {
this._print(options) this._print(options);
} }
} else { } else {
console.error('Error: Printing feature is disabled.') console.error('Error: Printing feature is disabled.');
} }
} };
WebContents.prototype.getPrinters = function () { WebContents.prototype.getPrinters = function () {
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
return this._getPrinters() return this._getPrinters();
} else { } else {
console.error('Error: Printing feature is disabled.') console.error('Error: Printing feature is disabled.');
return [] return [];
} }
} };
WebContents.prototype.loadFile = function (filePath, options = {}) { WebContents.prototype.loadFile = function (filePath, options = {}) {
if (typeof filePath !== 'string') { if (typeof filePath !== 'string') {
throw new Error('Must pass filePath as a string') throw new Error('Must pass filePath as a string');
} }
const { query, search, hash } = options const { query, search, hash } = options;
return this.loadURL(url.format({ return this.loadURL(url.format({
protocol: 'file', protocol: 'file',
@ -407,104 +407,104 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
query, query,
search, search,
hash hash
})) }));
} };
const addReplyToEvent = (event) => { const addReplyToEvent = (event) => {
event.reply = (...args) => { event.reply = (...args) => {
event.sender.sendToFrame(event.frameId, ...args) event.sender.sendToFrame(event.frameId, ...args);
} };
} };
const addReplyInternalToEvent = (event) => { const addReplyInternalToEvent = (event) => {
Object.defineProperty(event, '_replyInternal', { Object.defineProperty(event, '_replyInternal', {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
value: (...args) => { value: (...args) => {
event.sender._sendToFrameInternal(event.frameId, ...args) event.sender._sendToFrameInternal(event.frameId, ...args);
} }
}) });
} };
const addReturnValueToEvent = (event) => { const addReturnValueToEvent = (event) => {
Object.defineProperty(event, 'returnValue', { Object.defineProperty(event, 'returnValue', {
set: (value) => event.sendReply([value]), set: (value) => event.sendReply([value]),
get: () => {} get: () => {}
}) });
} };
// Add JavaScript wrappers for WebContents class. // Add JavaScript wrappers for WebContents class.
WebContents.prototype._init = function () { WebContents.prototype._init = function () {
// The navigation controller. // The navigation controller.
NavigationController.call(this, this) NavigationController.call(this, this);
// Every remote callback from renderer process would add a listener to the // Every remote callback from renderer process would add a listener to the
// render-view-deleted event, so ignore the listeners warning. // render-view-deleted event, so ignore the listeners warning.
this.setMaxListeners(0) this.setMaxListeners(0);
// Dispatch IPC messages to the ipc module. // Dispatch IPC messages to the ipc module.
this.on('-ipc-message', function (event, internal, channel, args) { this.on('-ipc-message', function (event, internal, channel, args) {
if (internal) { if (internal) {
addReplyInternalToEvent(event) addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args) ipcMainInternal.emit(channel, event, ...args);
} else { } else {
addReplyToEvent(event) addReplyToEvent(event);
this.emit('ipc-message', event, channel, ...args) this.emit('ipc-message', event, channel, ...args);
ipcMain.emit(channel, event, ...args) ipcMain.emit(channel, event, ...args);
} }
}) });
this.on('-ipc-invoke', function (event, internal, channel, args) { this.on('-ipc-invoke', function (event, internal, channel, args) {
event._reply = (result) => event.sendReply({ result }) event._reply = (result) => event.sendReply({ result });
event._throw = (error) => { event._throw = (error) => {
console.error(`Error occurred in handler for '${channel}':`, error) console.error(`Error occurred in handler for '${channel}':`, error);
event.sendReply({ error: error.toString() }) event.sendReply({ error: error.toString() });
} };
const target = internal ? ipcMainInternal : ipcMain const target = internal ? ipcMainInternal : ipcMain;
if (target._invokeHandlers.has(channel)) { if (target._invokeHandlers.has(channel)) {
target._invokeHandlers.get(channel)(event, ...args) target._invokeHandlers.get(channel)(event, ...args);
} else { } else {
event._throw(`No handler registered for '${channel}'`) event._throw(`No handler registered for '${channel}'`);
} }
}) });
this.on('-ipc-message-sync', function (event, internal, channel, args) { this.on('-ipc-message-sync', function (event, internal, channel, args) {
addReturnValueToEvent(event) addReturnValueToEvent(event);
if (internal) { if (internal) {
addReplyInternalToEvent(event) addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args) ipcMainInternal.emit(channel, event, ...args);
} else { } else {
addReplyToEvent(event) addReplyToEvent(event);
this.emit('ipc-message-sync', event, channel, ...args) this.emit('ipc-message-sync', event, channel, ...args);
ipcMain.emit(channel, event, ...args) ipcMain.emit(channel, event, ...args);
} }
}) });
this.on('-ipc-ports', function (event, internal, channel, message, ports) { this.on('-ipc-ports', function (event, internal, channel, message, ports) {
event.ports = ports.map(p => new MessagePortMain(p)) event.ports = ports.map(p => new MessagePortMain(p));
ipcMain.emit(channel, event, message) ipcMain.emit(channel, event, message);
}) });
// Handle context menu action request from pepper plugin. // Handle context menu action request from pepper plugin.
this.on('pepper-context-menu', function (event, params, callback) { this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require. // Access Menu via electron.Menu to prevent circular require.
const menu = electron.Menu.buildFromTemplate(params.menu) const menu = electron.Menu.buildFromTemplate(params.menu);
menu.popup({ menu.popup({
window: event.sender.getOwnerBrowserWindow(), window: event.sender.getOwnerBrowserWindow(),
x: params.x, x: params.x,
y: params.y, y: params.y,
callback callback
}) });
}) });
this.on('crashed', (event, ...args) => { this.on('crashed', (event, ...args) => {
app.emit('renderer-process-crashed', event, this, ...args) app.emit('renderer-process-crashed', event, this, ...args);
}) });
// The devtools requests the webContents to reload. // The devtools requests the webContents to reload.
this.on('devtools-reload-page', function () { this.on('devtools-reload-page', function () {
this.reload() this.reload();
}) });
// Handle window.open for BrowserWindow and BrowserView. // Handle window.open for BrowserWindow and BrowserView.
if (['browserView', 'window'].includes(this.getType())) { if (['browserView', 'window'].includes(this.getType())) {
@ -516,9 +516,9 @@ WebContents.prototype._init = function () {
show: true, show: true,
width: 800, width: 800,
height: 600 height: 600
} };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData) internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData);
}) });
// Create a new browser window for the native implementation of // Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode. // "window.open", used in sandbox and nativeWindowOpen mode.
@ -526,8 +526,8 @@ WebContents.prototype._init = function () {
userGesture, left, top, width, height, url, frameName) => { userGesture, left, top, width, height, url, frameName) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab')) { disposition !== 'background-tab')) {
event.preventDefault() event.preventDefault();
return return;
} }
const options = { const options = {
@ -537,70 +537,70 @@ WebContents.prototype._init = function () {
width: width || 800, width: width || 800,
height: height || 600, height: height || 600,
webContents webContents
} };
const referrer = { url: '', policy: 'default' } const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options) internalWindowOpen(event, url, referrer, frameName, disposition, options);
}) });
} }
this.on('login', (event, ...args) => { this.on('login', (event, ...args) => {
app.emit('login', event, this, ...args) app.emit('login', event, this, ...args);
}) });
const event = process.electronBinding('event').createEmpty() const event = process.electronBinding('event').createEmpty();
app.emit('web-contents-created', event, this) app.emit('web-contents-created', event, this);
// Properties // Properties
Object.defineProperty(this, 'audioMuted', { Object.defineProperty(this, 'audioMuted', {
get: () => this.isAudioMuted(), get: () => this.isAudioMuted(),
set: (muted) => this.setAudioMuted(muted) set: (muted) => this.setAudioMuted(muted)
}) });
Object.defineProperty(this, 'userAgent', { Object.defineProperty(this, 'userAgent', {
get: () => this.getUserAgent(), get: () => this.getUserAgent(),
set: (agent) => this.setUserAgent(agent) set: (agent) => this.setUserAgent(agent)
}) });
Object.defineProperty(this, 'zoomLevel', { Object.defineProperty(this, 'zoomLevel', {
get: () => this.getZoomLevel(), get: () => this.getZoomLevel(),
set: (level) => this.setZoomLevel(level) set: (level) => this.setZoomLevel(level)
}) });
Object.defineProperty(this, 'zoomFactor', { Object.defineProperty(this, 'zoomFactor', {
get: () => this.getZoomFactor(), get: () => this.getZoomFactor(),
set: (factor) => this.setZoomFactor(factor) set: (factor) => this.setZoomFactor(factor)
}) });
Object.defineProperty(this, 'frameRate', { Object.defineProperty(this, 'frameRate', {
get: () => this.getFrameRate(), get: () => this.getFrameRate(),
set: (rate) => this.setFrameRate(rate) set: (rate) => this.setFrameRate(rate)
}) });
} };
// Public APIs. // Public APIs.
module.exports = { module.exports = {
create (options = {}) { create (options = {}) {
return binding.create(options) return binding.create(options);
}, },
fromId (id) { fromId (id) {
return binding.fromId(id) return binding.fromId(id);
}, },
getFocusedWebContents () { getFocusedWebContents () {
let focused = null let focused = null;
for (const contents of binding.getAllWebContents()) { for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue if (!contents.isFocused()) continue;
if (focused == null) focused = contents if (focused == null) focused = contents;
// Return webview web contents which may be embedded inside another // Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused // web contents that is also reporting as focused
if (contents.getType() === 'webview') return contents if (contents.getType() === 'webview') return contents;
} }
return focused return focused;
}, },
getAllWebContents () { getAllWebContents () {
return binding.getAllWebContents() return binding.getAllWebContents();
} }
} };

View file

@ -1,37 +1,37 @@
'use strict' 'use strict';
// This is a temporary shim to aid in transition from the old // This is a temporary shim to aid in transition from the old
// BrowserWindow-based extensions stuff to the new native-backed extensions // BrowserWindow-based extensions stuff to the new native-backed extensions
// API. // API.
if (!process.electronBinding('features').isExtensionsEnabled()) { if (!process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension shim without //extensions support enabled') throw new Error('Attempted to load JS chrome-extension shim without //extensions support enabled');
} }
const { app, session, BrowserWindow, deprecate } = require('electron') const { app, session, BrowserWindow, deprecate } = require('electron');
app.whenReady().then(function () { app.whenReady().then(function () {
const addExtension = function (srcDirectory) { const addExtension = function (srcDirectory) {
return session.defaultSession.loadExtension(srcDirectory) return session.defaultSession.loadExtension(srcDirectory);
} };
const removeExtension = function (name) { const removeExtension = function (name) {
const extension = session.defaultSession.getAllExtensions().find(e => e.name === name) const extension = session.defaultSession.getAllExtensions().find(e => e.name === name);
if (extension) { session.defaultSession.removeExtension(extension.id) } if (extension) { session.defaultSession.removeExtension(extension.id); }
} };
const getExtensions = function () { const getExtensions = function () {
const extensions = {} const extensions = {};
session.defaultSession.getAllExtensions().forEach(e => { session.defaultSession.getAllExtensions().forEach(e => {
extensions[e.name] = e extensions[e.name] = e;
}) });
return extensions return extensions;
} };
BrowserWindow.addExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addExtension', 'session.loadExtension') BrowserWindow.addExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addExtension', 'session.loadExtension');
BrowserWindow.removeExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeExtension', 'session.removeExtension') BrowserWindow.removeExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeExtension', 'session.removeExtension');
BrowserWindow.getExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getExtensions', 'session.getAllExtensions') BrowserWindow.getExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getExtensions', 'session.getAllExtensions');
BrowserWindow.addDevToolsExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addDevToolsExtension', 'session.loadExtension') BrowserWindow.addDevToolsExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addDevToolsExtension', 'session.loadExtension');
BrowserWindow.removeDevToolsExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeDevToolsExtension', 'session.removeExtension') BrowserWindow.removeDevToolsExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeDevToolsExtension', 'session.removeExtension');
BrowserWindow.getDevToolsExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getDevToolsExtensions', 'session.getAllExtensions') BrowserWindow.getDevToolsExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getDevToolsExtensions', 'session.getAllExtensions');
}) });

View file

@ -1,105 +1,105 @@
'use strict' 'use strict';
if (process.electronBinding('features').isExtensionsEnabled()) { if (process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled') throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled');
} }
const { app, webContents, BrowserWindow } = require('electron') const { app, webContents, BrowserWindow } = require('electron');
const { getAllWebContents } = process.electronBinding('web_contents') const { getAllWebContents } = process.electronBinding('web_contents');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { Buffer } = require('buffer') const { Buffer } = require('buffer');
const fs = require('fs') const fs = require('fs');
const path = require('path') const path = require('path');
const url = require('url') const url = require('url');
const util = require('util') const util = require('util');
// Mapping between extensionId(hostname) and manifest. // Mapping between extensionId(hostname) and manifest.
const manifestMap = {} // extensionId => manifest const manifestMap = {}; // extensionId => manifest
const manifestNameMap = {} // name => manifest const manifestNameMap = {}; // name => manifest
const devToolsExtensionNames = new Set() const devToolsExtensionNames = new Set();
const generateExtensionIdFromName = function (name) { const generateExtensionIdFromName = function (name) {
return name.replace(/[\W_]+/g, '-').toLowerCase() return name.replace(/[\W_]+/g, '-').toLowerCase();
} };
const isWindowOrWebView = function (webContents) { const isWindowOrWebView = function (webContents) {
const type = webContents.getType() const type = webContents.getType();
return type === 'window' || type === 'webview' return type === 'window' || type === 'webview';
} };
const isBackgroundPage = function (webContents) { const isBackgroundPage = function (webContents) {
return webContents.getType() === 'backgroundPage' return webContents.getType() === 'backgroundPage';
} };
// Create or get manifest object from |srcDirectory|. // Create or get manifest object from |srcDirectory|.
const getManifestFromPath = function (srcDirectory) { const getManifestFromPath = function (srcDirectory) {
let manifest let manifest;
let manifestContent let manifestContent;
try { try {
manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json')) manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'));
} catch (readError) { } catch (readError) {
console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`) console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`);
console.warn(readError.stack || readError) console.warn(readError.stack || readError);
throw readError throw readError;
} }
try { try {
manifest = JSON.parse(manifestContent) manifest = JSON.parse(manifestContent);
} catch (parseError) { } catch (parseError) {
console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`) console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`);
console.warn(parseError.stack || parseError) console.warn(parseError.stack || parseError);
throw parseError throw parseError;
} }
if (!manifestNameMap[manifest.name]) { if (!manifestNameMap[manifest.name]) {
const extensionId = generateExtensionIdFromName(manifest.name) const extensionId = generateExtensionIdFromName(manifest.name);
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest;
let extensionURL = url.format({ let extensionURL = url.format({
protocol: 'chrome-extension', protocol: 'chrome-extension',
slashes: true, slashes: true,
hostname: extensionId, hostname: extensionId,
pathname: manifest.devtools_page pathname: manifest.devtools_page
}) });
// Chromium requires that startPage matches '([^:]+:\/\/[^/]*)\/' // Chromium requires that startPage matches '([^:]+:\/\/[^/]*)\/'
// We also can't use the file:// protocol here since that would make Chromium // 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. // treat all extension resources as being relative to root which we don't want.
if (!manifest.devtools_page) extensionURL += '/' if (!manifest.devtools_page) extensionURL += '/';
Object.assign(manifest, { Object.assign(manifest, {
srcDirectory: srcDirectory, srcDirectory: srcDirectory,
extensionId: extensionId, extensionId: extensionId,
startPage: extensionURL startPage: extensionURL
}) });
return manifest return manifest;
} else if (manifest && manifest.name) { } else if (manifest && manifest.name) {
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`) console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`);
return manifest return manifest;
} }
} };
// Manage the background pages. // Manage the background pages.
const backgroundPages = {} const backgroundPages = {};
const startBackgroundPages = function (manifest) { const startBackgroundPages = function (manifest) {
if (backgroundPages[manifest.extensionId] || !manifest.background) return if (backgroundPages[manifest.extensionId] || !manifest.background) return;
let html let html;
let name let name;
if (manifest.background.page) { if (manifest.background.page) {
name = manifest.background.page name = manifest.background.page;
html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page)) html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page));
} else { } else {
name = '_generated_background_page.html' name = '_generated_background_page.html';
const scripts = manifest.background.scripts.map((name) => { const scripts = manifest.background.scripts.map((name) => {
return `<script src="${name}"></script>` return `<script src="${name}"></script>`;
}).join('') }).join('');
html = Buffer.from(`<html><body>${scripts}</body></html>`) html = Buffer.from(`<html><body>${scripts}</body></html>`);
} }
const contents = webContents.create({ const contents = webContents.create({
@ -107,36 +107,36 @@ const startBackgroundPages = function (manifest) {
type: 'backgroundPage', type: 'backgroundPage',
sandbox: true, sandbox: true,
enableRemoteModule: false enableRemoteModule: false
}) });
backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name } backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name };
contents.loadURL(url.format({ contents.loadURL(url.format({
protocol: 'chrome-extension', protocol: 'chrome-extension',
slashes: true, slashes: true,
hostname: manifest.extensionId, hostname: manifest.extensionId,
pathname: name pathname: name
})) }));
} };
const removeBackgroundPages = function (manifest) { const removeBackgroundPages = function (manifest) {
if (!backgroundPages[manifest.extensionId]) return if (!backgroundPages[manifest.extensionId]) return;
backgroundPages[manifest.extensionId].webContents.destroy() backgroundPages[manifest.extensionId].webContents.destroy();
delete backgroundPages[manifest.extensionId] delete backgroundPages[manifest.extensionId];
} };
const sendToBackgroundPages = function (...args) { const sendToBackgroundPages = function (...args) {
for (const page of Object.values(backgroundPages)) { for (const page of Object.values(backgroundPages)) {
if (!page.webContents.isDestroyed()) { if (!page.webContents.isDestroyed()) {
page.webContents._sendInternalToAll(...args) page.webContents._sendInternalToAll(...args);
} }
} }
} };
// Dispatch web contents events to Chrome APIs // Dispatch web contents events to Chrome APIs
const hookWebContentsEvents = function (webContents) { const hookWebContentsEvents = function (webContents) {
const tabId = webContents.id const tabId = webContents.id;
sendToBackgroundPages('CHROME_TABS_ONCREATED') sendToBackgroundPages('CHROME_TABS_ONCREATED');
webContents.on('will-navigate', (event, url) => { webContents.on('will-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
@ -146,8 +146,8 @@ const hookWebContentsEvents = function (webContents) {
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url
}) });
}) });
webContents.on('did-navigate', (event, url) => { webContents.on('did-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
@ -157,189 +157,189 @@ const hookWebContentsEvents = function (webContents) {
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url
}) });
}) });
webContents.once('destroyed', () => { webContents.once('destroyed', () => {
sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId) sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId);
}) });
} };
// Handle the chrome.* API messages. // Handle the chrome.* API messages.
let nextId = 0 let nextId = 0;
ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
if (isBackgroundPage(event.sender)) { if (isBackgroundPage(event.sender)) {
throw new Error('chrome.runtime.connect is not supported in background page') throw new Error('chrome.runtime.connect is not supported in background page');
} }
const page = backgroundPages[extensionId] const page = backgroundPages[extensionId];
if (!page || page.webContents.isDestroyed()) { if (!page || page.webContents.isDestroyed()) {
throw new Error(`Connect to unknown extension ${extensionId}`) throw new Error(`Connect to unknown extension ${extensionId}`);
} }
const tabId = page.webContents.id const tabId = page.webContents.id;
const portId = ++nextId const portId = ++nextId;
event.sender.once('render-view-deleted', () => { event.sender.once('render-view-deleted', () => {
if (page.webContents.isDestroyed()) return if (page.webContents.isDestroyed()) return;
page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`) page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`);
}) });
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo) page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo);
return { tabId, portId } return { tabId, portId };
}) });
ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) {
const manifest = manifestMap[extensionId] const manifest = manifestMap[extensionId];
if (!manifest) { if (!manifest) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
return manifest return manifest;
}) });
ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) {
if (isBackgroundPage(event.sender)) { if (isBackgroundPage(event.sender)) {
throw new Error('chrome.runtime.sendMessage is not supported in background page') throw new Error('chrome.runtime.sendMessage is not supported in background page');
} }
const page = backgroundPages[extensionId] const page = backgroundPages[extensionId];
if (!page || page.webContents.isDestroyed()) { if (!page || page.webContents.isDestroyed()) {
throw new Error(`Connect to unknown extension ${extensionId}`) throw new Error(`Connect to unknown extension ${extensionId}`);
} }
return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message) 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) { ipcMainInternal.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) {
const contents = webContents.fromId(tabId) const contents = webContents.fromId(tabId);
if (!contents) { if (!contents) {
throw new Error(`Sending message to unknown tab ${tabId}`) throw new Error(`Sending message to unknown tab ${tabId}`);
} }
const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id;
return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message) return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message);
}) });
const getLanguage = () => { const getLanguage = () => {
return app.getLocale().replace(/-.*$/, '').toLowerCase() return app.getLocale().replace(/-.*$/, '').toLowerCase();
} };
const getMessagesPath = (extensionId) => { const getMessagesPath = (extensionId) => {
const metadata = manifestMap[extensionId] const metadata = manifestMap[extensionId];
if (!metadata) { if (!metadata) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
const localesDirectory = path.join(metadata.srcDirectory, '_locales') const localesDirectory = path.join(metadata.srcDirectory, '_locales');
const language = getLanguage() const language = getLanguage();
try { try {
const filename = path.join(localesDirectory, language, 'messages.json') const filename = path.join(localesDirectory, language, 'messages.json');
fs.accessSync(filename, fs.constants.R_OK) fs.accessSync(filename, fs.constants.R_OK);
return filename return filename;
} catch { } catch {
const defaultLocale = metadata.default_locale || 'en' const defaultLocale = metadata.default_locale || 'en';
return path.join(localesDirectory, defaultLocale, 'messages.json') return path.join(localesDirectory, defaultLocale, 'messages.json');
} }
} };
ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) { ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) {
const messagesPath = getMessagesPath(extensionId) const messagesPath = getMessagesPath(extensionId);
return fs.promises.readFile(messagesPath, 'utf8') return fs.promises.readFile(messagesPath, 'utf8');
}) });
const validStorageTypes = new Set(['sync', 'local']) const validStorageTypes = new Set(['sync', 'local']);
const getChromeStoragePath = (storageType, extensionId) => { const getChromeStoragePath = (storageType, extensionId) => {
if (!validStorageTypes.has(storageType)) { if (!validStorageTypes.has(storageType)) {
throw new Error(`Invalid storageType: ${storageType}`) throw new Error(`Invalid storageType: ${storageType}`);
} }
if (!manifestMap[extensionId]) { if (!manifestMap[extensionId]) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`) return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`);
} };
ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) {
const filePath = getChromeStoragePath(storageType, extensionId) const filePath = getChromeStoragePath(storageType, extensionId);
try { try {
return await fs.promises.readFile(filePath, 'utf8') return await fs.promises.readFile(filePath, 'utf8');
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
return null return null;
} else { } else {
throw error throw error;
} }
} }
}) });
ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) {
const filePath = getChromeStoragePath(storageType, extensionId) const filePath = getChromeStoragePath(storageType, extensionId);
try { try {
await fs.promises.mkdir(path.dirname(filePath), { recursive: true }) await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
} catch { } catch {
// we just ignore the errors of mkdir // we just ignore the errors of mkdir
} }
return fs.promises.writeFile(filePath, data, 'utf8') return fs.promises.writeFile(filePath, data, 'utf8');
}) });
const isChromeExtension = function (pageURL) { const isChromeExtension = function (pageURL) {
const { protocol } = url.parse(pageURL) const { protocol } = url.parse(pageURL);
return protocol === 'chrome-extension:' return protocol === 'chrome-extension:';
} };
const assertChromeExtension = function (contents, api) { const assertChromeExtension = function (contents, api) {
const pageURL = contents._getURL() const pageURL = contents._getURL();
if (!isChromeExtension(pageURL)) { if (!isChromeExtension(pageURL)) {
console.error(`Blocked ${pageURL} from calling ${api}`) console.error(`Blocked ${pageURL} from calling ${api}`);
throw new Error(`Blocked ${api}`) throw new Error(`Blocked ${api}`);
} }
} };
ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) {
assertChromeExtension(event.sender, 'chrome.tabs.executeScript()') assertChromeExtension(event.sender, 'chrome.tabs.executeScript()');
const contents = webContents.fromId(tabId) const contents = webContents.fromId(tabId);
if (!contents) { if (!contents) {
throw new Error(`Sending message to unknown tab ${tabId}`) throw new Error(`Sending message to unknown tab ${tabId}`);
} }
let code, url let code, url;
if (details.file) { if (details.file) {
const manifest = manifestMap[extensionId] const manifest = manifestMap[extensionId];
code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))) code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)));
url = `chrome-extension://${extensionId}${details.file}` url = `chrome-extension://${extensionId}${details.file}`;
} else { } else {
code = details.code code = details.code;
url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js` url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`;
} }
return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code) return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code);
}) });
exports.getContentScripts = () => { exports.getContentScripts = () => {
return Object.values(contentScripts) return Object.values(contentScripts);
} };
// Transfer the content scripts to renderer. // Transfer the content scripts to renderer.
const contentScripts = {} const contentScripts = {};
const injectContentScripts = function (manifest) { const injectContentScripts = function (manifest) {
if (contentScripts[manifest.name] || !manifest.content_scripts) return if (contentScripts[manifest.name] || !manifest.content_scripts) return;
const readArrayOfFiles = function (relativePath) { const readArrayOfFiles = function (relativePath) {
return { return {
url: `chrome-extension://${manifest.extensionId}/${relativePath}`, url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath))) code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
} };
} };
const contentScriptToEntry = function (script) { const contentScriptToEntry = function (script) {
return { return {
@ -348,25 +348,25 @@ const injectContentScripts = function (manifest) {
css: script.css ? script.css.map(readArrayOfFiles) : [], css: script.css ? script.css.map(readArrayOfFiles) : [],
runAt: script.run_at || 'document_idle', runAt: script.run_at || 'document_idle',
allFrames: script.all_frames || false allFrames: script.all_frames || false
} };
} };
try { try {
const entry = { const entry = {
extensionId: manifest.extensionId, extensionId: manifest.extensionId,
contentScripts: manifest.content_scripts.map(contentScriptToEntry) contentScripts: manifest.content_scripts.map(contentScriptToEntry)
} };
contentScripts[manifest.name] = entry contentScripts[manifest.name] = entry;
} catch (e) { } catch (e) {
console.error('Failed to read content scripts', e) console.error('Failed to read content scripts', e);
} }
} };
const removeContentScripts = function (manifest) { const removeContentScripts = function (manifest) {
if (!contentScripts[manifest.name]) return if (!contentScripts[manifest.name]) return;
delete contentScripts[manifest.name] delete contentScripts[manifest.name];
} };
// Transfer the |manifest| to a format that can be recognized by the // Transfer the |manifest| to a format that can be recognized by the
// |DevToolsAPI.addExtensions|. // |DevToolsAPI.addExtensions|.
@ -376,167 +376,167 @@ const manifestToExtensionInfo = function (manifest) {
srcDirectory: manifest.srcDirectory, srcDirectory: manifest.srcDirectory,
name: manifest.name, name: manifest.name,
exposeExperimentalAPIs: true exposeExperimentalAPIs: true
} };
} };
// Load the extensions for the window. // Load the extensions for the window.
const loadExtension = function (manifest) { const loadExtension = function (manifest) {
startBackgroundPages(manifest) startBackgroundPages(manifest);
injectContentScripts(manifest) injectContentScripts(manifest);
} };
const loadDevToolsExtensions = function (win, manifests) { const loadDevToolsExtensions = function (win, manifests) {
if (!win.devToolsWebContents) return if (!win.devToolsWebContents) return;
manifests.forEach(loadExtension) manifests.forEach(loadExtension);
const extensionInfoArray = manifests.map(manifestToExtensionInfo) const extensionInfoArray = manifests.map(manifestToExtensionInfo);
extensionInfoArray.forEach((extension) => { extensionInfoArray.forEach((extension) => {
win.devToolsWebContents._grantOriginAccess(extension.startPage) win.devToolsWebContents._grantOriginAccess(extension.startPage);
}) });
extensionInfoArray.forEach((extensionInfo) => { extensionInfoArray.forEach((extensionInfo) => {
const info = JSON.stringify(extensionInfo) const info = JSON.stringify(extensionInfo);
win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${info})`) win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${info})`);
}) });
} };
app.on('web-contents-created', function (event, webContents) { app.on('web-contents-created', function (event, webContents) {
if (!isWindowOrWebView(webContents)) return if (!isWindowOrWebView(webContents)) return;
hookWebContentsEvents(webContents) hookWebContentsEvents(webContents);
webContents.on('devtools-opened', function () { webContents.on('devtools-opened', function () {
loadDevToolsExtensions(webContents, Object.values(manifestMap)) loadDevToolsExtensions(webContents, Object.values(manifestMap));
}) });
}) });
// The chrome-extension: can map a extension URL request to real file path. // The chrome-extension: can map a extension URL request to real file path.
const chromeExtensionHandler = function (request, callback) { const chromeExtensionHandler = function (request, callback) {
const parsed = url.parse(request.url) const parsed = url.parse(request.url);
if (!parsed.hostname || !parsed.path) return callback() if (!parsed.hostname || !parsed.path) return callback();
const manifest = manifestMap[parsed.hostname] const manifest = manifestMap[parsed.hostname];
if (!manifest) return callback() if (!manifest) return callback();
const page = backgroundPages[parsed.hostname] const page = backgroundPages[parsed.hostname];
if (page && parsed.path === `/${page.name}`) { if (page && parsed.path === `/${page.name}`) {
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback({ return callback({
mimeType: 'text/html', mimeType: 'text/html',
data: page.html data: page.html
}) });
} }
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) { fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
if (err) { if (err) {
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(-6) // FILE_NOT_FOUND return callback(-6); // FILE_NOT_FOUND
} else { } else {
return callback(content) return callback(content);
} }
}) });
} };
app.on('session-created', function (ses) { app.on('session-created', function (ses) {
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler) ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler);
}) });
// The persistent path of "DevTools Extensions" preference file. // The persistent path of "DevTools Extensions" preference file.
let loadedDevToolsExtensionsPath = null let loadedDevToolsExtensionsPath = null;
app.on('will-quit', function () { app.on('will-quit', function () {
try { try {
const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
.map(name => manifestNameMap[name].srcDirectory) .map(name => manifestNameMap[name].srcDirectory);
if (loadedDevToolsExtensions.length > 0) { if (loadedDevToolsExtensions.length > 0) {
try { try {
fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)) fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath));
} catch { } catch {
// Ignore error // Ignore error
} }
fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)) fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions));
} else { } else {
fs.unlinkSync(loadedDevToolsExtensionsPath) fs.unlinkSync(loadedDevToolsExtensionsPath);
} }
} catch { } catch {
// Ignore error // Ignore error
} }
}) });
// We can not use protocol or BrowserWindow until app is ready. // We can not use protocol or BrowserWindow until app is ready.
app.whenReady().then(function () { app.whenReady().then(function () {
// The public API to add/remove extensions. // The public API to add/remove extensions.
BrowserWindow.addExtension = function (srcDirectory) { BrowserWindow.addExtension = function (srcDirectory) {
const manifest = getManifestFromPath(srcDirectory) const manifest = getManifestFromPath(srcDirectory);
if (manifest) { if (manifest) {
loadExtension(manifest) loadExtension(manifest);
for (const webContents of getAllWebContents()) { for (const webContents of getAllWebContents()) {
if (isWindowOrWebView(webContents)) { if (isWindowOrWebView(webContents)) {
loadDevToolsExtensions(webContents, [manifest]) loadDevToolsExtensions(webContents, [manifest]);
} }
} }
return manifest.name return manifest.name;
} }
} };
BrowserWindow.removeExtension = function (name) { BrowserWindow.removeExtension = function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name];
if (!manifest) return if (!manifest) return;
removeBackgroundPages(manifest) removeBackgroundPages(manifest);
removeContentScripts(manifest) removeContentScripts(manifest);
delete manifestMap[manifest.extensionId] delete manifestMap[manifest.extensionId];
delete manifestNameMap[name] delete manifestNameMap[name];
} };
BrowserWindow.getExtensions = function () { BrowserWindow.getExtensions = function () {
const extensions = {} const extensions = {};
Object.keys(manifestNameMap).forEach(function (name) { Object.keys(manifestNameMap).forEach(function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name];
extensions[name] = { name: manifest.name, version: manifest.version } extensions[name] = { name: manifest.name, version: manifest.version };
}) });
return extensions return extensions;
} };
BrowserWindow.addDevToolsExtension = function (srcDirectory) { BrowserWindow.addDevToolsExtension = function (srcDirectory) {
const manifestName = BrowserWindow.addExtension(srcDirectory) const manifestName = BrowserWindow.addExtension(srcDirectory);
if (manifestName) { if (manifestName) {
devToolsExtensionNames.add(manifestName) devToolsExtensionNames.add(manifestName);
} }
return manifestName return manifestName;
} };
BrowserWindow.removeDevToolsExtension = function (name) { BrowserWindow.removeDevToolsExtension = function (name) {
BrowserWindow.removeExtension(name) BrowserWindow.removeExtension(name);
devToolsExtensionNames.delete(name) devToolsExtensionNames.delete(name);
} };
BrowserWindow.getDevToolsExtensions = function () { BrowserWindow.getDevToolsExtensions = function () {
const extensions = BrowserWindow.getExtensions() const extensions = BrowserWindow.getExtensions();
const devExtensions = {} const devExtensions = {};
Array.from(devToolsExtensionNames).forEach(function (name) { Array.from(devToolsExtensionNames).forEach(function (name) {
if (!extensions[name]) return if (!extensions[name]) return;
devExtensions[name] = extensions[name] devExtensions[name] = extensions[name];
}) });
return devExtensions return devExtensions;
} };
// Load persisted extensions. // Load persisted extensions.
loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions');
try { try {
const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath)) const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath));
if (Array.isArray(loadedDevToolsExtensions)) { if (Array.isArray(loadedDevToolsExtensions)) {
for (const srcDirectory of loadedDevToolsExtensions) { for (const srcDirectory of loadedDevToolsExtensions) {
// Start background pages and set content scripts. // Start background pages and set content scripts.
BrowserWindow.addDevToolsExtension(srcDirectory) BrowserWindow.addDevToolsExtension(srcDirectory);
} }
} }
} catch (error) { } catch (error) {
if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') { if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') {
console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath) console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath);
console.error(error) console.error(error);
} }
} }
}) });

View file

@ -1,25 +1,25 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const path = require('path') const path = require('path');
const getTempDirectory = function () { const getTempDirectory = function () {
try { try {
return app.getPath('temp') return app.getPath('temp');
} catch { } catch {
// Delibrately laze-load the os module, this file is on the hot // Delibrately laze-load the os module, this file is on the hot
// path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet // path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet
return require('os').tmpdir() return require('os').tmpdir();
} }
} };
exports.crashReporterInit = function (options) { exports.crashReporterInit = function (options) {
const productName = options.productName || app.name const productName = options.productName || app.name;
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`) const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`);
return { return {
productName, productName,
crashesDirectory, crashesDirectory,
appVersion: app.getVersion() appVersion: app.getVersion()
} };
} };

View file

@ -1,11 +1,11 @@
import { shell, Menu } from 'electron' import { shell, Menu } from 'electron';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const isMac = process.platform === 'darwin' const isMac = process.platform === 'darwin';
export const setDefaultApplicationMenu = () => { export const setDefaultApplicationMenu = () => {
if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return;
const helpMenu: Electron.MenuItemConstructorOptions = { const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help', role: 'help',
@ -13,32 +13,32 @@ export const setDefaultApplicationMenu = () => {
{ {
label: 'Learn More', label: 'Learn More',
click: async () => { click: async () => {
await shell.openExternal('https://electronjs.org') await shell.openExternal('https://electronjs.org');
} }
}, },
{ {
label: 'Documentation', label: 'Documentation',
click: async () => { click: async () => {
const version = process.versions.electron const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`) await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
} }
}, },
{ {
label: 'Community Discussions', label: 'Community Discussions',
click: async () => { click: async () => {
await shell.openExternal('https://discuss.atom.io/c/electron') await shell.openExternal('https://discuss.atom.io/c/electron');
} }
}, },
{ {
label: 'Search Issues', label: 'Search Issues',
click: async () => { click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues') await shell.openExternal('https://github.com/electron/electron/issues');
} }
} }
] ]
} };
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' } const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [ const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []), ...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' }, { role: 'fileMenu' },
@ -46,8 +46,8 @@ export const setDefaultApplicationMenu = () => {
{ role: 'viewMenu' }, { role: 'viewMenu' },
{ role: 'windowMenu' }, { role: 'windowMenu' },
helpMenu helpMenu
] ];
const menu = Menu.buildFromTemplate(template) const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu) Menu.setApplicationMenu(menu);
} };

View file

@ -1,62 +1,62 @@
const { createDesktopCapturer } = process.electronBinding('desktop_capturer') const { createDesktopCapturer } = process.electronBinding('desktop_capturer');
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b) const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
let currentlyRunning: { let currentlyRunning: {
options: ElectronInternal.GetSourcesOptions; options: ElectronInternal.GetSourcesOptions;
getSources: Promise<ElectronInternal.GetSourcesResult[]>; getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [] }[] = [];
export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => { export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => {
for (const running of currentlyRunning) { for (const running of currentlyRunning) {
if (deepEqual(running.options, options)) { if (deepEqual(running.options, options)) {
// If a request is currently running for the same options // If a request is currently running for the same options
// return that promise // return that promise
return running.getSources return running.getSources;
} }
} }
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => { const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer() let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
const stopRunning = () => { const stopRunning = () => {
if (capturer) { if (capturer) {
delete capturer._onerror delete capturer._onerror;
delete capturer._onfinished delete capturer._onfinished;
capturer = null capturer = null;
} }
// Remove from currentlyRunning once we resolve or reject // Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options) currentlyRunning = currentlyRunning.filter(running => running.options !== options);
} };
capturer._onerror = (error: string) => { capturer._onerror = (error: string) => {
stopRunning() stopRunning();
reject(error) reject(error);
} };
capturer._onfinished = (sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => { capturer._onfinished = (sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => {
stopRunning() stopRunning();
resolve(sources.map(source => ({ resolve(sources.map(source => ({
id: source.id, id: source.id,
name: source.name, name: source.name,
thumbnail: source.thumbnail.toDataURL(), thumbnail: source.thumbnail.toDataURL(),
display_id: source.display_id, display_id: source.display_id,
appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null
}))) })));
} };
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons) capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons);
// If the WebContents is destroyed before receiving result, just remove the // If the WebContents is destroyed before receiving result, just remove the
// reference to emit and the capturer itself so that it never dispatches // reference to emit and the capturer itself so that it never dispatches
// back to the renderer // back to the renderer
event.sender.once('destroyed', () => stopRunning()) event.sender.once('destroyed', () => stopRunning());
}) });
currentlyRunning.push({ currentlyRunning.push({
options, options,
getSources getSources
}) });
return getSources return getSources;
} };

View file

@ -1,9 +1,9 @@
import { dialog, Menu } from 'electron' import { dialog, Menu } from 'electron';
import * as fs from 'fs' import * as fs from 'fs';
import * as url from 'url' import * as url from 'url';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils' import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) { const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
return items.map(function (item) { return items.map(function (item) {
@ -23,15 +23,15 @@ const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id:
type: 'normal', type: 'normal',
label: item.label, label: item.label,
enabled: item.enabled enabled: item.enabled
} };
if (item.id != null) { if (item.id != null) {
transformed.click = () => handler(item.id) transformed.click = () => handler(item.id);
} }
return transformed return transformed;
}) });
} };
const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] { const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
return [ return [
@ -44,56 +44,56 @@ const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
{ role: 'pasteAndMatchStyle' }, { role: 'pasteAndMatchStyle' },
{ role: 'delete' }, { role: 'delete' },
{ role: 'selectAll' } { role: 'selectAll' }
] ];
} };
const isChromeDevTools = function (pageURL: string) { const isChromeDevTools = function (pageURL: string) {
const { protocol } = url.parse(pageURL) const { protocol } = url.parse(pageURL);
return protocol === 'devtools:' return protocol === 'devtools:';
} };
const assertChromeDevTools = function (contents: Electron.WebContents, api: string) { const assertChromeDevTools = function (contents: Electron.WebContents, api: string) {
const pageURL = contents._getURL() const pageURL = contents._getURL();
if (!isChromeDevTools(pageURL)) { if (!isChromeDevTools(pageURL)) {
console.error(`Blocked ${pageURL} from calling ${api}`) console.error(`Blocked ${pageURL} from calling ${api}`);
throw new Error(`Blocked ${api}`) throw new Error(`Blocked ${api}`);
} }
} };
ipcMainInternal.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainInvokeEvent, items: ContextMenuItem[], isEditMenu: boolean) { ipcMainInternal.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainInvokeEvent, items: ContextMenuItem[], isEditMenu: boolean) {
return new Promise(resolve => { return new Promise(resolve => {
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()') assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve) const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
const menu = Menu.buildFromTemplate(template) const menu = Menu.buildFromTemplate(template);
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
menu.popup({ window, callback: () => resolve() }) menu.popup({ window, callback: () => resolve() });
}) });
}) });
ipcMainInternal.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainInvokeEvent) { ipcMainInternal.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainInvokeEvent) {
assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()') assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()');
const result = await dialog.showOpenDialog({}) const result = await dialog.showOpenDialog({});
if (result.canceled) return [] if (result.canceled) return [];
const path = result.filePaths[0] const path = result.filePaths[0];
const data = await fs.promises.readFile(path) const data = await fs.promises.readFile(path);
return [path, data] return [path, data];
}) });
ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainInvokeEvent, message: string = '', title: string = '') { ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainInvokeEvent, message: string = '', title: string = '') {
assertChromeDevTools(event.sender, 'window.confirm()') assertChromeDevTools(event.sender, 'window.confirm()');
const options = { const options = {
message: String(message), message: String(message),
title: String(title), title: String(title),
buttons: ['OK', 'Cancel'], buttons: ['OK', 'Cancel'],
cancelId: 1 cancelId: 1
} };
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
const { response } = await dialog.showMessageBox(window, options) const { response } = await dialog.showMessageBox(window, options);
return response === 0 return response === 0;
}) });

View file

@ -1,14 +1,14 @@
'use strict' 'use strict';
const { webContents } = require('electron') const { webContents } = require('electron');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string') const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods') const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods');
const { serialize } = require('@electron/internal/common/type-utils') const { serialize } = require('@electron/internal/common/type-utils');
// Doesn't exist in early initialization. // Doesn't exist in early initialization.
let webViewManager = null let webViewManager = null;
const supportedWebViewEvents = [ const supportedWebViewEvents = [
'load-commit', 'load-commit',
@ -43,155 +43,155 @@ const supportedWebViewEvents = [
'found-in-page', 'found-in-page',
'did-change-theme-color', 'did-change-theme-color',
'update-target-url' 'update-target-url'
] ];
const guestInstances = {} const guestInstances = {};
const embedderElementsMap = {} const embedderElementsMap = {};
function sanitizeOptionsForGuest (options) { function sanitizeOptionsForGuest (options) {
const ret = { ...options } const ret = { ...options };
// WebContents values can't be sent over IPC. // WebContents values can't be sent over IPC.
delete ret.webContents delete ret.webContents;
return ret return ret;
} }
// Create a new guest instance. // Create a new guest instance.
const createGuest = function (embedder, params) { const createGuest = function (embedder, params) {
if (webViewManager == null) { if (webViewManager == null) {
webViewManager = process.electronBinding('web_view_manager') webViewManager = process.electronBinding('web_view_manager');
} }
const guest = webContents.create({ const guest = webContents.create({
type: 'webview', type: 'webview',
partition: params.partition, partition: params.partition,
embedder: embedder embedder: embedder
}) });
const guestInstanceId = guest.id const guestInstanceId = guest.id;
guestInstances[guestInstanceId] = { guestInstances[guestInstanceId] = {
guest: guest, guest: guest,
embedder: embedder embedder: embedder
} };
// Clear the guest from map when it is destroyed. // Clear the guest from map when it is destroyed.
guest.once('destroyed', () => { guest.once('destroyed', () => {
if (Object.prototype.hasOwnProperty.call(guestInstances, guestInstanceId)) { if (Object.prototype.hasOwnProperty.call(guestInstances, guestInstanceId)) {
detachGuest(embedder, guestInstanceId) detachGuest(embedder, guestInstanceId);
} }
}) });
// Init guest web view after attached. // Init guest web view after attached.
guest.once('did-attach', function (event) { guest.once('did-attach', function (event) {
params = this.attachParams params = this.attachParams;
delete this.attachParams delete this.attachParams;
const previouslyAttached = this.viewInstanceId != null const previouslyAttached = this.viewInstanceId != null;
this.viewInstanceId = params.instanceId this.viewInstanceId = params.instanceId;
// Only load URL and set size on first attach // Only load URL and set size on first attach
if (previouslyAttached) { if (previouslyAttached) {
return return;
} }
if (params.src) { if (params.src) {
const opts = {} const opts = {};
if (params.httpreferrer) { if (params.httpreferrer) {
opts.httpReferrer = params.httpreferrer opts.httpReferrer = params.httpreferrer;
} }
if (params.useragent) { if (params.useragent) {
opts.userAgent = params.useragent opts.userAgent = params.useragent;
} }
this.loadURL(params.src, opts) this.loadURL(params.src, opts);
} }
embedder.emit('did-attach-webview', event, guest) embedder.emit('did-attach-webview', event, guest);
}) });
const sendToEmbedder = (channel, ...args) => { const sendToEmbedder = (channel, ...args) => {
if (!embedder.isDestroyed()) { if (!embedder.isDestroyed()) {
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args) embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
} }
} };
// Dispatch events to embedder. // Dispatch events to embedder.
const fn = function (event) { const fn = function (event) {
guest.on(event, function (_, ...args) { guest.on(event, function (_, ...args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args) sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args);
}) });
} };
for (const event of supportedWebViewEvents) { for (const event of supportedWebViewEvents) {
fn(event) fn(event);
} }
guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) { guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', 'new-window', url, sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', 'new-window', url,
frameName, disposition, sanitizeOptionsForGuest(options), frameName, disposition, sanitizeOptionsForGuest(options),
additionalFeatures, referrer) additionalFeatures, referrer);
}) });
// Dispatch guest's IPC messages to embedder. // Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, channel, args) { guest.on('ipc-message-host', function (_, channel, args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args) sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args);
}) });
// Notify guest of embedder window visibility when it is ready // Notify guest of embedder window visibility when it is ready
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
guest.on('dom-ready', function () { guest.on('dom-ready', function () {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null && guestInstance.visibilityState != null) { if (guestInstance != null && guestInstance.visibilityState != null) {
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState) guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState);
} }
}) });
// Forward internal web contents event to embedder to handle // Forward internal web contents event to embedder to handle
// native window.open setup // native window.open setup
guest.on('-add-new-contents', (...args) => { guest.on('-add-new-contents', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) { if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId) const embedder = getEmbedder(guestInstanceId);
if (embedder != null) { if (embedder != null) {
embedder.emit('-add-new-contents', ...args) embedder.emit('-add-new-contents', ...args);
} }
} }
}) });
return guestInstanceId return guestInstanceId;
} };
// Attach the guest to an element of embedder. // Attach the guest to an element of embedder.
const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
const embedder = event.sender const embedder = event.sender;
// Destroy the old guest when attaching. // Destroy the old guest when attaching.
const key = `${embedder.id}-${elementInstanceId}` const key = `${embedder.id}-${elementInstanceId}`;
const oldGuestInstanceId = embedderElementsMap[key] const oldGuestInstanceId = embedderElementsMap[key];
if (oldGuestInstanceId != null) { if (oldGuestInstanceId != null) {
// Reattachment to the same guest is just a no-op. // Reattachment to the same guest is just a no-op.
if (oldGuestInstanceId === guestInstanceId) { if (oldGuestInstanceId === guestInstanceId) {
return return;
} }
const oldGuestInstance = guestInstances[oldGuestInstanceId] const oldGuestInstance = guestInstances[oldGuestInstanceId];
if (oldGuestInstance) { if (oldGuestInstance) {
oldGuestInstance.guest.detachFromOuterFrame() oldGuestInstance.guest.detachFromOuterFrame();
} }
} }
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
// If this isn't a valid guest instance then do nothing. // If this isn't a valid guest instance then do nothing.
if (!guestInstance) { if (!guestInstance) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
} }
const { guest } = guestInstance const { guest } = guestInstance;
if (guest.hostWebContents !== event.sender) { if (guest.hostWebContents !== event.sender) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
} }
// If this guest is already attached to an element then remove it // If this guest is already attached to an element then remove it
if (guestInstance.elementInstanceId) { if (guestInstance.elementInstanceId) {
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}` const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[oldKey] delete embedderElementsMap[oldKey];
// Remove guest from embedder if moving across web views // Remove guest from embedder if moving across web views
if (guest.viewInstanceId !== params.instanceId) { if (guest.viewInstanceId !== params.instanceId) {
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId) webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`) guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`);
} }
} }
@ -206,7 +206,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
webSecurity: !params.disablewebsecurity, webSecurity: !params.disablewebsecurity,
enableBlinkFeatures: params.blinkfeatures, enableBlinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures disableBlinkFeatures: params.disableblinkfeatures
} };
// parse the 'webpreferences' attribute string, if set // parse the 'webpreferences' attribute string, if set
// this uses the same parsing rules as window.open uses for its features // this uses the same parsing rules as window.open uses for its features
@ -214,14 +214,14 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
parseFeaturesString(params.webpreferences, function (key, value) { parseFeaturesString(params.webpreferences, function (key, value) {
if (value === undefined) { if (value === undefined) {
// no value was specified, default it to true // no value was specified, default it to true
value = true value = true;
} }
webPreferences[key] = value webPreferences[key] = value;
}) });
} }
if (params.preload) { if (params.preload) {
webPreferences.preloadURL = params.preload webPreferences.preloadURL = params.preload;
} }
// Security options that guest will always inherit from embedder // Security options that guest will always inherit from embedder
@ -233,203 +233,203 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
['enableRemoteModule', false], ['enableRemoteModule', false],
['sandbox', true], ['sandbox', true],
['nodeIntegrationInSubFrames', false] ['nodeIntegrationInSubFrames', false]
]) ]);
// Inherit certain option values from embedder // Inherit certain option values from embedder
const lastWebPreferences = embedder.getLastWebPreferences() const lastWebPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) { for (const [name, value] of inheritedWebPreferences) {
if (lastWebPreferences[name] === value) { if (lastWebPreferences[name] === value) {
webPreferences[name] = value webPreferences[name] = value;
} }
} }
embedder.emit('will-attach-webview', event, webPreferences, params) embedder.emit('will-attach-webview', event, webPreferences, params);
if (event.defaultPrevented) { if (event.defaultPrevented) {
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId;
guest.destroy() guest.destroy();
return return;
} }
guest.attachParams = params guest.attachParams = params;
embedderElementsMap[key] = guestInstanceId embedderElementsMap[key] = guestInstanceId;
guest.setEmbedder(embedder) guest.setEmbedder(embedder);
guestInstance.embedder = embedder guestInstance.embedder = embedder;
guestInstance.elementInstanceId = elementInstanceId guestInstance.elementInstanceId = elementInstanceId;
watchEmbedder(embedder) watchEmbedder(embedder);
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences);
guest.attachToIframe(embedder, embedderFrameId) guest.attachToIframe(embedder, embedderFrameId);
} };
// Remove an guest-embedder relationship. // Remove an guest-embedder relationship.
const detachGuest = function (embedder, guestInstanceId) { const detachGuest = function (embedder, guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (embedder !== guestInstance.embedder) { if (embedder !== guestInstance.embedder) {
return return;
} }
webViewManager.removeGuest(embedder, guestInstanceId) webViewManager.removeGuest(embedder, guestInstanceId);
delete guestInstances[guestInstanceId] delete guestInstances[guestInstanceId];
const key = `${embedder.id}-${guestInstance.elementInstanceId}` const key = `${embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[key] delete embedderElementsMap[key];
} };
// Once an embedder has had a guest attached we watch it for destruction to // Once an embedder has had a guest attached we watch it for destruction to
// destroy any remaining guests. // destroy any remaining guests.
const watchedEmbedders = new Set() const watchedEmbedders = new Set();
const watchEmbedder = function (embedder) { const watchEmbedder = function (embedder) {
if (watchedEmbedders.has(embedder)) { if (watchedEmbedders.has(embedder)) {
return return;
} }
watchedEmbedders.add(embedder) watchedEmbedders.add(embedder);
// Forward embedder window visiblity change events to guest // Forward embedder window visiblity change events to guest
const onVisibilityChange = function (visibilityState) { const onVisibilityChange = function (visibilityState) {
for (const guestInstanceId of Object.keys(guestInstances)) { for (const guestInstanceId of Object.keys(guestInstances)) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
guestInstance.visibilityState = visibilityState guestInstance.visibilityState = visibilityState;
if (guestInstance.embedder === embedder) { if (guestInstance.embedder === embedder) {
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState) guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState);
} }
} }
} };
embedder.on('-window-visibility-change', onVisibilityChange) embedder.on('-window-visibility-change', onVisibilityChange);
embedder.once('will-destroy', () => { embedder.once('will-destroy', () => {
// Usually the guestInstances is cleared when guest is destroyed, but it // Usually the guestInstances is cleared when guest is destroyed, but it
// may happen that the embedder gets manually destroyed earlier than guest, // may happen that the embedder gets manually destroyed earlier than guest,
// and the embedder will be invalid in the usual code path. // and the embedder will be invalid in the usual code path.
for (const guestInstanceId of Object.keys(guestInstances)) { for (const guestInstanceId of Object.keys(guestInstances)) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance.embedder === embedder) { if (guestInstance.embedder === embedder) {
detachGuest(embedder, parseInt(guestInstanceId)) detachGuest(embedder, parseInt(guestInstanceId));
} }
} }
// Clear the listeners. // Clear the listeners.
embedder.removeListener('-window-visibility-change', onVisibilityChange) embedder.removeListener('-window-visibility-change', onVisibilityChange);
watchedEmbedders.delete(embedder) watchedEmbedders.delete(embedder);
}) });
} };
const isWebViewTagEnabledCache = new WeakMap() const isWebViewTagEnabledCache = new WeakMap();
const isWebViewTagEnabled = function (contents) { const isWebViewTagEnabled = function (contents) {
if (!isWebViewTagEnabledCache.has(contents)) { if (!isWebViewTagEnabledCache.has(contents)) {
const webPreferences = contents.getLastWebPreferences() || {} const webPreferences = contents.getLastWebPreferences() || {};
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag) isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag);
} }
return isWebViewTagEnabledCache.get(contents) return isWebViewTagEnabledCache.get(contents);
} };
const makeSafeHandler = function (channel, handler) { const makeSafeHandler = function (channel, handler) {
return (event, ...args) => { return (event, ...args) => {
if (isWebViewTagEnabled(event.sender)) { if (isWebViewTagEnabled(event.sender)) {
return handler(event, ...args) return handler(event, ...args);
} else { } else {
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`) console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
throw new Error('<webview> disabled') throw new Error('<webview> disabled');
} }
} };
} };
const handleMessage = function (channel, handler) { const handleMessage = function (channel, handler) {
ipcMainInternal.handle(channel, makeSafeHandler(channel, handler)) ipcMainInternal.handle(channel, makeSafeHandler(channel, handler));
} };
const handleMessageSync = function (channel, handler) { const handleMessageSync = function (channel, handler) {
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler)) ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
} };
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) {
return createGuest(event.sender, params) return createGuest(event.sender, params);
}) });
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
try { try {
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params) attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params);
} catch (error) { } catch (error) {
console.error(`Guest attach failed: ${error}`) console.error(`Guest attach failed: ${error}`);
} }
}) });
// this message is sent by the actual <webview> // this message is sent by the actual <webview>
ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) { ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
const guest = getGuest(guestInstanceId) const guest = getGuest(guestInstanceId);
if (guest === event.sender) { if (guest === event.sender) {
event.sender.emit('focus-change', {}, focus, guestInstanceId) event.sender.emit('focus-change', {}, focus, guestInstanceId);
} else { } else {
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`) console.error(`focus-change for guestInstanceId: ${guestInstanceId}`);
} }
}) });
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!asyncMethods.has(method)) { if (!asyncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guest[method](...args) return guest[method](...args);
}) });
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!syncMethods.has(method)) { if (!syncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guest[method](...args) return guest[method](...args);
}) });
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId, property) { handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId, property) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!properties.has(property)) { if (!properties.has(property)) {
throw new Error(`Invalid property: ${property}`) throw new Error(`Invalid property: ${property}`);
} }
return guest[property] return guest[property];
}) });
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId, property, val) { handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId, property, val) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!properties.has(property)) { if (!properties.has(property)) {
throw new Error(`Invalid property: ${property}`) throw new Error(`Invalid property: ${property}`);
} }
guest[property] = val guest[property] = val;
}) });
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
return serialize(await guest.capturePage(...args)) return serialize(await guest.capturePage(...args));
}) });
// Returns WebContents from its guest id hosted in given webContents. // Returns WebContents from its guest id hosted in given webContents.
const getGuestForWebContents = function (guestInstanceId, contents) { const getGuestForWebContents = function (guestInstanceId, contents) {
const guest = getGuest(guestInstanceId) const guest = getGuest(guestInstanceId);
if (!guest) { if (!guest) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
} }
if (guest.hostWebContents !== contents) { if (guest.hostWebContents !== contents) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
} }
return guest return guest;
} };
// Returns WebContents from its guest id. // Returns WebContents from its guest id.
const getGuest = function (guestInstanceId) { const getGuest = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null) return guestInstance.guest if (guestInstance != null) return guestInstance.guest;
} };
// Returns the embedder of the guest. // Returns the embedder of the guest.
const getEmbedder = function (guestInstanceId) { const getEmbedder = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null) return guestInstance.embedder if (guestInstance != null) return guestInstance.embedder;
} };
exports.isWebViewTagEnabled = isWebViewTagEnabled exports.isWebViewTagEnabled = isWebViewTagEnabled;

View file

@ -1,14 +1,14 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { BrowserWindow } = electron const { BrowserWindow } = electron;
const { isSameOrigin } = process.electronBinding('v8_util') const { isSameOrigin } = process.electronBinding('v8_util');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string') const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty;
const frameToGuest = new Map() const frameToGuest = new Map();
// Security options that child windows will always inherit from parent windows // Security options that child windows will always inherit from parent windows
const inheritedWebPreferences = new Map([ const inheritedWebPreferences = new Map([
@ -20,109 +20,109 @@ const inheritedWebPreferences = new Map([
['sandbox', true], ['sandbox', true],
['webviewTag', false], ['webviewTag', false],
['nodeIntegrationInSubFrames', false] ['nodeIntegrationInSubFrames', false]
]) ]);
// Copy attribute of |parent| to |child| if it is not defined in |child|. // Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent, visited) { const mergeOptions = function (child, parent, visited) {
// Check for circular reference. // Check for circular reference.
if (visited == null) visited = new Set() if (visited == null) visited = new Set();
if (visited.has(parent)) return if (visited.has(parent)) return;
visited.add(parent) visited.add(parent);
for (const key in parent) { for (const key in parent) {
if (key === 'type') continue if (key === 'type') continue;
if (!hasProp.call(parent, key)) continue if (!hasProp.call(parent, key)) continue;
if (key in child && key !== 'webPreferences') continue if (key in child && key !== 'webPreferences') continue;
const value = parent[key] const value = parent[key];
if (typeof value === 'object' && !Array.isArray(value)) { if (typeof value === 'object' && !Array.isArray(value)) {
child[key] = mergeOptions(child[key] || {}, value, visited) child[key] = mergeOptions(child[key] || {}, value, visited);
} else { } else {
child[key] = value child[key] = value;
} }
} }
visited.delete(parent) visited.delete(parent);
return child return child;
} };
// Merge |options| with the |embedder|'s window's options. // Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) { const mergeBrowserWindowOptions = function (embedder, options) {
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
if (embedder.browserWindowOptions != null) { if (embedder.browserWindowOptions != null) {
let parentOptions = embedder.browserWindowOptions let parentOptions = embedder.browserWindowOptions;
// if parent's visibility is available, that overrides 'show' flag (#12125) // if parent's visibility is available, that overrides 'show' flag (#12125)
const win = BrowserWindow.fromWebContents(embedder.webContents) const win = BrowserWindow.fromWebContents(embedder.webContents);
if (win != null) { if (win != null) {
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() } parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() };
} }
// Inherit the original options if it is a BrowserWindow. // Inherit the original options if it is a BrowserWindow.
mergeOptions(options, parentOptions) mergeOptions(options, parentOptions);
} else { } else {
// Or only inherit webPreferences if it is a webview. // Or only inherit webPreferences if it is a webview.
mergeOptions(options.webPreferences, embedder.getLastWebPreferences()) mergeOptions(options.webPreferences, embedder.getLastWebPreferences());
} }
// Inherit certain option values from parent window // Inherit certain option values from parent window
const webPreferences = embedder.getLastWebPreferences() const webPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) { for (const [name, value] of inheritedWebPreferences) {
if (webPreferences[name] === value) { if (webPreferences[name] === value) {
options.webPreferences[name] = value options.webPreferences[name] = value;
} }
} }
if (!webPreferences.nativeWindowOpen) { if (!webPreferences.nativeWindowOpen) {
// Sets correct openerId here to give correct options to 'new-window' event handler // Sets correct openerId here to give correct options to 'new-window' event handler
options.webPreferences.openerId = embedder.id options.webPreferences.openerId = embedder.id;
} }
return options return options;
} };
// Setup a new guest with |embedder| // Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) { const setupGuest = function (embedder, frameName, guest, options) {
// When |embedder| is destroyed we should also destroy attached guest, and if // When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double // guest is closed by user then we should prevent |embedder| from double
// closing guest. // closing guest.
const guestId = guest.webContents.id const guestId = guest.webContents.id;
const closedByEmbedder = function () { const closedByEmbedder = function () {
guest.removeListener('closed', closedByUser) guest.removeListener('closed', closedByUser);
guest.destroy() guest.destroy();
} };
const closedByUser = function () { const closedByUser = function () {
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId);
embedder.removeListener('current-render-view-deleted', closedByEmbedder) embedder.removeListener('current-render-view-deleted', closedByEmbedder);
} };
embedder.once('current-render-view-deleted', closedByEmbedder) embedder.once('current-render-view-deleted', closedByEmbedder);
guest.once('closed', closedByUser) guest.once('closed', closedByUser);
if (frameName) { if (frameName) {
frameToGuest.set(frameName, guest) frameToGuest.set(frameName, guest);
guest.frameName = frameName guest.frameName = frameName;
guest.once('closed', function () { guest.once('closed', function () {
frameToGuest.delete(frameName) frameToGuest.delete(frameName);
}) });
} }
return guestId return guestId;
} };
// Create a new guest created by |embedder| with |options|. // Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, referrer, frameName, options, postData) { const createGuest = function (embedder, url, referrer, frameName, options, postData) {
let guest = frameToGuest.get(frameName) let guest = frameToGuest.get(frameName);
if (frameName && (guest != null)) { if (frameName && (guest != null)) {
guest.loadURL(url) guest.loadURL(url);
return guest.webContents.id return guest.webContents.id;
} }
// Remember the embedder window's id. // Remember the embedder window's id.
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
guest = new BrowserWindow(options) guest = new BrowserWindow(options);
if (!options.webContents) { if (!options.webContents) {
// We should not call `loadURL` if the window was constructed from an // We should not call `loadURL` if the window was constructed from an
// existing webContents (window.open in a sandboxed renderer). // existing webContents (window.open in a sandboxed renderer).
@ -131,236 +131,236 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD
// webContents is not necessary (it will navigate there anyway). // webContents is not necessary (it will navigate there anyway).
const loadOptions = { const loadOptions = {
httpReferrer: referrer httpReferrer: referrer
} };
if (postData != null) { if (postData != null) {
loadOptions.postData = postData loadOptions.postData = postData;
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded' loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded';
if (postData.length > 0) { if (postData.length > 0) {
const postDataFront = postData[0].bytes.toString() const postDataFront = postData[0].bytes.toString();
const boundary = /^--.*[^-\r\n]/.exec(postDataFront) const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
if (boundary != null) { if (boundary != null) {
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}` loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`;
} }
} }
} }
guest.loadURL(url, loadOptions) guest.loadURL(url, loadOptions);
} }
return setupGuest(embedder, frameName, guest, options) return setupGuest(embedder, frameName, guest, options);
} };
const getGuestWindow = function (guestContents) { const getGuestWindow = function (guestContents) {
let guestWindow = BrowserWindow.fromWebContents(guestContents) let guestWindow = BrowserWindow.fromWebContents(guestContents);
if (guestWindow == null) { if (guestWindow == null) {
const hostContents = guestContents.hostWebContents const hostContents = guestContents.hostWebContents;
if (hostContents != null) { if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents) guestWindow = BrowserWindow.fromWebContents(hostContents);
} }
} }
if (!guestWindow) { if (!guestWindow) {
throw new Error('getGuestWindow failed') throw new Error('getGuestWindow failed');
} }
return guestWindow return guestWindow;
} };
const isChildWindow = function (sender, target) { const isChildWindow = function (sender, target) {
return target.getLastWebPreferences().openerId === sender.id return target.getLastWebPreferences().openerId === sender.id;
} };
const isRelatedWindow = function (sender, target) { const isRelatedWindow = function (sender, target) {
return isChildWindow(sender, target) || isChildWindow(target, sender) return isChildWindow(sender, target) || isChildWindow(target, sender);
} };
const isScriptableWindow = function (sender, target) { const isScriptableWindow = function (sender, target) {
return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL()) return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL());
} };
const isNodeIntegrationEnabled = function (sender) { const isNodeIntegrationEnabled = function (sender) {
return sender.getLastWebPreferences().nodeIntegration === true return sender.getLastWebPreferences().nodeIntegration === true;
} };
// Checks whether |sender| can access the |target|: // Checks whether |sender| can access the |target|:
const canAccessWindow = function (sender, target) { const canAccessWindow = function (sender, target) {
return isChildWindow(sender, target) || return isChildWindow(sender, target) ||
isScriptableWindow(sender, target) || isScriptableWindow(sender, target) ||
isNodeIntegrationEnabled(sender) isNodeIntegrationEnabled(sender);
} };
// Routed window.open messages with raw options // Routed window.open messages with raw options
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
if (url == null || url === '') url = 'about:blank' if (url == null || url === '') url = 'about:blank';
if (frameName == null) frameName = '' if (frameName == null) frameName = '';
if (features == null) features = '' if (features == null) features = '';
const options = {} const options = {};
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'] const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'];
const disposition = 'new-window' const disposition = 'new-window';
// Used to store additional features // Used to store additional features
const additionalFeatures = [] const additionalFeatures = [];
// Parse the features // Parse the features
parseFeaturesString(features, function (key, value) { parseFeaturesString(features, function (key, value) {
if (value === undefined) { if (value === undefined) {
additionalFeatures.push(key) additionalFeatures.push(key);
} else { } else {
// Don't allow webPreferences to be set since it must be an object // Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden // that cannot be directly overridden
if (key === 'webPreferences') return if (key === 'webPreferences') return;
if (webPreferences.includes(key)) { if (webPreferences.includes(key)) {
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
options.webPreferences[key] = value options.webPreferences[key] = value;
} else { } else {
options[key] = value options[key] = value;
} }
} }
}) });
if (options.left) { if (options.left) {
if (options.x == null) { if (options.x == null) {
options.x = options.left options.x = options.left;
} }
} }
if (options.top) { if (options.top) {
if (options.y == null) { if (options.y == null) {
options.y = options.top options.y = options.top;
} }
} }
if (options.title == null) { if (options.title == null) {
options.title = frameName options.title = frameName;
} }
if (options.width == null) { if (options.width == null) {
options.width = 800 options.width = 800;
} }
if (options.height == null) { if (options.height == null) {
options.height = 600 options.height = 600;
} }
for (const name of ints) { for (const name of ints) {
if (options[name] != null) { if (options[name] != null) {
options[name] = parseInt(options[name], 10) options[name] = parseInt(options[name], 10);
} }
} }
const referrer = { url: '', policy: 'default' } const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures) internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures);
}) });
// Routed window.open messages with fully parsed options // Routed window.open messages with fully parsed options
function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) { function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options) options = mergeBrowserWindowOptions(event.sender, options);
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer) event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer);
const { newGuest } = event const { newGuest } = event;
if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) {
if (newGuest != null) { if (newGuest != null) {
if (options.webContents === newGuest.webContents) { if (options.webContents === newGuest.webContents) {
// the webContents is not changed, so set defaultPrevented to false to // the webContents is not changed, so set defaultPrevented to false to
// stop the callers of this event from destroying the webContents. // stop the callers of this event from destroying the webContents.
event.defaultPrevented = false event.defaultPrevented = false;
} }
event.returnValue = setupGuest(event.sender, frameName, newGuest, options) event.returnValue = setupGuest(event.sender, frameName, newGuest, options);
} else { } else {
event.returnValue = null event.returnValue = null;
} }
} else { } else {
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData) event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData);
} }
} }
const makeSafeHandler = function (handler) { const makeSafeHandler = function (handler) {
return (event, guestId, ...args) => { return (event, guestId, ...args) => {
// Access webContents via electron to prevent circular require. // Access webContents via electron to prevent circular require.
const guestContents = electron.webContents.fromId(guestId) const guestContents = electron.webContents.fromId(guestId);
if (!guestContents) { if (!guestContents) {
throw new Error(`Invalid guestId: ${guestId}`) throw new Error(`Invalid guestId: ${guestId}`);
} }
return handler(event, guestContents, ...args) return handler(event, guestContents, ...args);
} };
} };
const handleMessage = function (channel, handler) { const handleMessage = function (channel, handler) {
ipcMainInternal.handle(channel, makeSafeHandler(handler)) ipcMainInternal.handle(channel, makeSafeHandler(handler));
} };
const handleMessageSync = function (channel, handler) { const handleMessageSync = function (channel, handler) {
ipcMainUtils.handleSync(channel, makeSafeHandler(handler)) ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
} };
const securityCheck = function (contents, guestContents, check) { const securityCheck = function (contents, guestContents, check) {
if (!check(contents, guestContents)) { if (!check(contents, guestContents)) {
console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`) console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`);
throw new Error(`Access denied to guestId: ${guestContents.id}`) throw new Error(`Access denied to guestId: ${guestContents.id}`);
} }
} };
const windowMethods = new Set([ const windowMethods = new Set([
'destroy', 'destroy',
'focus', 'focus',
'blur' 'blur'
]) ]);
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!windowMethods.has(method)) { if (!windowMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return getGuestWindow(guestContents)[method](...args) return getGuestWindow(guestContents)[method](...args);
}) });
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => {
if (targetOrigin == null) { if (targetOrigin == null) {
targetOrigin = '*' targetOrigin = '*';
} }
// The W3C does not seem to have word on how postMessage should work when the // The W3C does not seem to have word on how postMessage should work when the
// origins do not match, so we do not do |canAccessWindow| check here since // origins do not match, so we do not do |canAccessWindow| check here since
// postMessage across origins is useful and not harmful. // postMessage across origins is useful and not harmful.
securityCheck(event.sender, guestContents, isRelatedWindow) securityCheck(event.sender, guestContents, isRelatedWindow);
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) { if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
const sourceId = event.sender.id const sourceId = event.sender.id;
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin);
} }
}) });
const webContentsMethodsAsync = new Set([ const webContentsMethodsAsync = new Set([
'loadURL', 'loadURL',
'executeJavaScript', 'executeJavaScript',
'print' 'print'
]) ]);
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsAsync.has(method)) { if (!webContentsMethodsAsync.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guestContents[method](...args) return guestContents[method](...args);
}) });
const webContentsMethodsSync = new Set([ const webContentsMethodsSync = new Set([
'getURL' 'getURL'
]) ]);
handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsSync.has(method)) { if (!webContentsMethodsSync.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guestContents[method](...args) return guestContents[method](...args);
}) });
exports.internalWindowOpen = internalWindowOpen exports.internalWindowOpen = internalWindowOpen;

View file

@ -1,23 +1,23 @@
import { Buffer } from 'buffer' import { Buffer } from 'buffer';
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import * as fs from 'fs' import * as fs from 'fs';
import { Socket } from 'net' import { Socket } from 'net';
import * as path from 'path' import * as path from 'path';
import * as util from 'util' import * as util from 'util';
const Module = require('module') const Module = require('module');
// We modified the original process.argv to let node.js load the init.js, // We modified the original process.argv to let node.js load the init.js,
// we need to restore it here. // we need to restore it here.
process.argv.splice(1, 1) process.argv.splice(1, 1);
// Clear search paths. // Clear search paths.
require('../common/reset-search-paths') require('../common/reset-search-paths');
// Import common settings. // Import common settings.
require('@electron/internal/common/init') require('@electron/internal/common/init');
process.electronBinding('event_emitter').setEventEmitterPrototype(EventEmitter.prototype) process.electronBinding('event_emitter').setEventEmitterPrototype(EventEmitter.prototype);
if (process.platform === 'win32') { if (process.platform === 'win32') {
// Redirect node's console to use our own implementations, since node can not // Redirect node's console to use our own implementations, since node can not
@ -25,27 +25,27 @@ if (process.platform === 'win32') {
const consoleLog = (...args: any[]) => { const consoleLog = (...args: any[]) => {
// @ts-ignore this typing is incorrect; 'format' is an optional parameter // @ts-ignore this typing is incorrect; 'format' is an optional parameter
// See https://nodejs.org/api/util.html#util_util_format_format_args // See https://nodejs.org/api/util.html#util_util_format_format_args
return process.log(util.format(...args) + '\n') return process.log(util.format(...args) + '\n');
} };
const streamWrite: Socket['write'] = function (chunk: Buffer | string, encoding?: any, callback?: Function) { const streamWrite: Socket['write'] = function (chunk: Buffer | string, encoding?: any, callback?: Function) {
if (Buffer.isBuffer(chunk)) { if (Buffer.isBuffer(chunk)) {
chunk = chunk.toString(encoding) chunk = chunk.toString(encoding);
} }
process.log(chunk) process.log(chunk);
if (callback) { if (callback) {
callback() callback();
} }
return true return true;
} };
console.log = console.error = console.warn = consoleLog console.log = console.error = console.warn = consoleLog;
process.stdout.write = process.stderr.write = streamWrite process.stdout.write = process.stderr.write = streamWrite;
} }
// Don't quit on fatal error. // Don't quit on fatal error.
process.on('uncaughtException', function (error) { process.on('uncaughtException', function (error) {
// Do nothing if the user has a custom uncaught exception handler. // Do nothing if the user has a custom uncaught exception handler.
if (process.listenerCount('uncaughtException') > 1) { if (process.listenerCount('uncaughtException') > 1) {
return return;
} }
// Show error in GUI. // Show error in GUI.
@ -54,18 +54,18 @@ process.on('uncaughtException', function (error) {
// so we import it inside the handler down here // so we import it inside the handler down here
import('electron') import('electron')
.then(({ dialog }) => { .then(({ dialog }) => {
const stack = error.stack ? error.stack : `${error.name}: ${error.message}` const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
const message = 'Uncaught Exception:\n' + stack const message = 'Uncaught Exception:\n' + stack;
dialog.showErrorBox('A JavaScript error occurred in the main process', message) dialog.showErrorBox('A JavaScript error occurred in the main process', message);
}) });
}) });
// Emit 'exit' event on quit. // Emit 'exit' event on quit.
const { app } = require('electron') const { app } = require('electron');
app.on('quit', function (event, exitCode) { app.on('quit', function (event, exitCode) {
process.emit('exit', exitCode) process.emit('exit', exitCode);
}) });
if (process.platform === 'win32') { if (process.platform === 'win32') {
// If we are a Squirrel.Windows-installed app, set app user model ID // If we are a Squirrel.Windows-installed app, set app user model ID
@ -82,141 +82,141 @@ if (process.platform === 'win32') {
// form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
// app.setAppUserModelId with a matching identifier so that renderer processes // app.setAppUserModelId with a matching identifier so that renderer processes
// will inherit this value. // will inherit this value.
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe') const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');
if (fs.existsSync(updateDotExe)) { if (fs.existsSync(updateDotExe)) {
const packageDir = path.dirname(path.resolve(updateDotExe)) const packageDir = path.dirname(path.resolve(updateDotExe));
const packageName = path.basename(packageDir).replace(/\s/g, '') const packageName = path.basename(packageDir).replace(/\s/g, '');
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '') const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '');
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
} }
} }
// Map process.exit to app.exit, which quits gracefully. // Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit as () => never process.exit = app.exit as () => never;
// Load the RPC server. // Load the RPC server.
require('@electron/internal/browser/rpc-server') require('@electron/internal/browser/rpc-server');
// Load the guest view manager. // Load the guest view manager.
require('@electron/internal/browser/guest-view-manager') require('@electron/internal/browser/guest-view-manager');
require('@electron/internal/browser/guest-window-manager') require('@electron/internal/browser/guest-window-manager');
// Now we try to load app's package.json. // Now we try to load app's package.json.
let packagePath = null let packagePath = null;
let packageJson = null let packageJson = null;
const searchPaths = ['app', 'app.asar', 'default_app.asar'] const searchPaths = ['app', 'app.asar', 'default_app.asar'];
if (process.resourcesPath) { if (process.resourcesPath) {
for (packagePath of searchPaths) { for (packagePath of searchPaths) {
try { try {
packagePath = path.join(process.resourcesPath, packagePath) packagePath = path.join(process.resourcesPath, packagePath);
packageJson = Module._load(path.join(packagePath, 'package.json')) packageJson = Module._load(path.join(packagePath, 'package.json'));
break break;
} catch { } catch {
continue continue;
} }
} }
} }
if (packageJson == null) { if (packageJson == null) {
process.nextTick(function () { process.nextTick(function () {
return process.exit(1) return process.exit(1);
}) });
throw new Error('Unable to find a valid app') throw new Error('Unable to find a valid app');
} }
// Set application's version. // Set application's version.
if (packageJson.version != null) { if (packageJson.version != null) {
app.setVersion(packageJson.version) app.setVersion(packageJson.version);
} }
// Set application's name. // Set application's name.
if (packageJson.productName != null) { if (packageJson.productName != null) {
app.name = `${packageJson.productName}`.trim() app.name = `${packageJson.productName}`.trim();
} else if (packageJson.name != null) { } else if (packageJson.name != null) {
app.name = `${packageJson.name}`.trim() app.name = `${packageJson.name}`.trim();
} }
// Set application's desktop name. // Set application's desktop name.
if (packageJson.desktopName != null) { if (packageJson.desktopName != null) {
app.setDesktopName(packageJson.desktopName) app.setDesktopName(packageJson.desktopName);
} else { } else {
app.setDesktopName(`${app.name}.desktop`) app.setDesktopName(`${app.name}.desktop`);
} }
// Set v8 flags, delibrately lazy load so that apps that do not use this // Set v8 flags, delibrately lazy load so that apps that do not use this
// feature do not pay the price // feature do not pay the price
if (packageJson.v8Flags != null) { if (packageJson.v8Flags != null) {
require('v8').setFlagsFromString(packageJson.v8Flags) require('v8').setFlagsFromString(packageJson.v8Flags);
} }
app._setDefaultAppPaths(packagePath) app._setDefaultAppPaths(packagePath);
// Load the chrome devtools support. // Load the chrome devtools support.
require('@electron/internal/browser/devtools') require('@electron/internal/browser/devtools');
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Load the chrome extension support. // Load the chrome extension support.
if (features.isExtensionsEnabled()) { if (features.isExtensionsEnabled()) {
require('@electron/internal/browser/chrome-extension-shim') require('@electron/internal/browser/chrome-extension-shim');
} else { } else {
require('@electron/internal/browser/chrome-extension') require('@electron/internal/browser/chrome-extension');
} }
if (features.isRemoteModuleEnabled()) { if (features.isRemoteModuleEnabled()) {
require('@electron/internal/browser/remote/server') require('@electron/internal/browser/remote/server');
} }
// Load protocol module to ensure it is populated on app ready // Load protocol module to ensure it is populated on app ready
require('@electron/internal/browser/api/protocol') require('@electron/internal/browser/api/protocol');
// Set main startup script of the app. // Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js' const mainStartupScript = packageJson.main || 'index.js';
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'] const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'];
function currentPlatformSupportsAppIndicator () { function currentPlatformSupportsAppIndicator () {
if (process.platform !== 'linux') return false if (process.platform !== 'linux') return false;
const currentDesktop = process.env.XDG_CURRENT_DESKTOP const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
if (!currentDesktop) return false if (!currentDesktop) return false;
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true;
// ubuntu based or derived session (default ubuntu one, communitheme…) supports // ubuntu based or derived session (default ubuntu one, communitheme…) supports
// indicator too. // indicator too.
if (/ubuntu/ig.test(currentDesktop)) return true if (/ubuntu/ig.test(currentDesktop)) return true;
return false return false;
} }
// Workaround for electron/electron#5050 and electron/electron#9046 // Workaround for electron/electron#5050 and electron/electron#9046
if (currentPlatformSupportsAppIndicator()) { if (currentPlatformSupportsAppIndicator()) {
process.env.XDG_CURRENT_DESKTOP = 'Unity' process.env.XDG_CURRENT_DESKTOP = 'Unity';
} }
// Quit when all windows are closed and no other one is listening to this. // Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (app.listenerCount('window-all-closed') === 1) { if (app.listenerCount('window-all-closed') === 1) {
app.quit() app.quit();
} }
}) });
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu') const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu');
// Create default menu. // Create default menu.
// //
// Note that the task must be added before loading any app, so we can make sure // Note that the task must be added before loading any app, so we can make sure
// the call is maded before any user window is created, otherwise the default // the call is maded before any user window is created, otherwise the default
// menu may show even when user explicitly hides the menu. // menu may show even when user explicitly hides the menu.
app.whenReady().then(setDefaultApplicationMenu) app.whenReady().then(setDefaultApplicationMenu);
if (packagePath) { if (packagePath) {
// Finally load app's main.js and transfer control to C++. // Finally load app's main.js and transfer control to C++.
process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false) process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false);
Module._load(path.join(packagePath, mainStartupScript), Module, true) Module._load(path.join(packagePath, mainStartupScript), Module, true);
} else { } else {
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)') console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
console.error('This normally means you\'ve damaged the Electron package somehow') console.error('This normally means you\'ve damaged the Electron package somehow');
} }

View file

@ -1,33 +1,33 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import { IpcMainInvokeEvent } from 'electron' import { IpcMainInvokeEvent } from 'electron';
export class IpcMainImpl extends EventEmitter { export class IpcMainImpl extends EventEmitter {
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map(); private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
handle: Electron.IpcMain['handle'] = (method, fn) => { handle: Electron.IpcMain['handle'] = (method, fn) => {
if (this._invokeHandlers.has(method)) { if (this._invokeHandlers.has(method)) {
throw new Error(`Attempted to register a second handler for '${method}'`) throw new Error(`Attempted to register a second handler for '${method}'`);
} }
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`) throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`);
} }
this._invokeHandlers.set(method, async (e, ...args) => { this._invokeHandlers.set(method, async (e, ...args) => {
try { try {
(e as any)._reply(await Promise.resolve(fn(e, ...args))) (e as any)._reply(await Promise.resolve(fn(e, ...args)));
} catch (err) { } catch (err) {
(e as any)._throw(err) (e as any)._throw(err);
} }
}) });
} }
handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => { handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
this.handle(method, (e, ...args) => { this.handle(method, (e, ...args) => {
this.removeHandler(method) this.removeHandler(method);
return fn(e, ...args) return fn(e, ...args);
}) });
} }
removeHandler (method: string) { removeHandler (method: string) {
this._invokeHandlers.delete(method) this._invokeHandlers.delete(method);
} }
} }

View file

@ -1,44 +1,44 @@
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any
export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) { export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) {
ipcMainInternal.on(channel, async (event, ...args) => { ipcMainInternal.on(channel, async (event, ...args) => {
try { try {
event.returnValue = [null, await handler(event, ...args)] event.returnValue = [null, await handler(event, ...args)];
} catch (error) { } catch (error) {
event.returnValue = [error] event.returnValue = [error];
} }
}) });
} };
let nextId = 0 let nextId = 0;
export function invokeInWebContents<T> (sender: Electron.WebContentsInternal, sendToAll: boolean, command: string, ...args: any[]) { export function invokeInWebContents<T> (sender: Electron.WebContentsInternal, sendToAll: boolean, command: string, ...args: any[]) {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
const requestId = ++nextId const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}` const channel = `${command}_RESPONSE_${requestId}`;
ipcMainInternal.on(channel, function handler ( ipcMainInternal.on(channel, function handler (
event, error: Electron.SerializedError, result: any event, error: Electron.SerializedError, result: any
) { ) {
if (event.sender !== sender) { if (event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`) console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
return return;
} }
ipcMainInternal.removeListener(channel, handler) ipcMainInternal.removeListener(channel, handler);
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve(result) resolve(result);
} }
}) });
if (sendToAll) { if (sendToAll) {
sender._sendInternalToAll(command, requestId, ...args) sender._sendInternalToAll(command, requestId, ...args);
} else { } else {
sender._sendInternal(command, requestId, ...args) sender._sendInternal(command, requestId, ...args);
} }
}) });
} }

View file

@ -1,6 +1,6 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal;
// Do not throw exception when channel name is "error". // Do not throw exception when channel name is "error".
ipcMainInternal.on('error', () => {}) ipcMainInternal.on('error', () => {});

View file

@ -1,28 +1,28 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
export class MessagePortMain extends EventEmitter { export class MessagePortMain extends EventEmitter {
_internalPort: any _internalPort: any
constructor (internalPort: any) { constructor (internalPort: any) {
super() super();
this._internalPort = internalPort this._internalPort = internalPort;
this._internalPort.emit = (channel: string, event: {ports: any[]}) => { this._internalPort.emit = (channel: string, event: {ports: any[]}) => {
if (channel === 'message') { event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) } } if (channel === 'message') { event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) }; }
this.emit(channel, event) this.emit(channel, event);
} };
} }
start () { start () {
return this._internalPort.start() return this._internalPort.start();
} }
close () { close () {
return this._internalPort.close() return this._internalPort.close();
} }
postMessage (...args: any[]) { postMessage (...args: any[]) {
if (Array.isArray(args[1])) { if (Array.isArray(args[1])) {
args[1] = args[1].map((o: any) => o instanceof MessagePortMain ? o._internalPort : o) args[1] = args[1].map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
} }
return this._internalPort.postMessage(...args) return this._internalPort.postMessage(...args);
} }
} }

View file

@ -1,23 +1,23 @@
'use strict' 'use strict';
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
// The history operation in renderer is redirected to browser. // The history operation in renderer is redirected to browser.
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) {
event.sender.goBack() event.sender.goBack();
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) {
event.sender.goForward() event.sender.goForward();
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) {
event.sender.goToOffset(offset) event.sender.goToOffset(offset);
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
event.returnValue = event.sender.length() event.returnValue = event.sender.length();
}) });
// JavaScript implementation of Chromium's NavigationController. // JavaScript implementation of Chromium's NavigationController.
// Instead of relying on Chromium for history control, we compeletely do history // Instead of relying on Chromium for history control, we compeletely do history
@ -26,64 +26,64 @@ ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
// process is restarted everytime. // process is restarted everytime.
const NavigationController = (function () { const NavigationController = (function () {
function NavigationController (webContents) { function NavigationController (webContents) {
this.webContents = webContents this.webContents = webContents;
this.clearHistory() this.clearHistory();
// webContents may have already navigated to a page. // webContents may have already navigated to a page.
if (this.webContents._getURL()) { if (this.webContents._getURL()) {
this.currentIndex++ this.currentIndex++;
this.history.push(this.webContents._getURL()) this.history.push(this.webContents._getURL());
} }
this.webContents.on('navigation-entry-committed', (event, url, inPage, replaceEntry) => { this.webContents.on('navigation-entry-committed', (event, url, inPage, replaceEntry) => {
if (this.inPageIndex > -1 && !inPage) { if (this.inPageIndex > -1 && !inPage) {
// Navigated to a new page, clear in-page mark. // Navigated to a new page, clear in-page mark.
this.inPageIndex = -1 this.inPageIndex = -1;
} else if (this.inPageIndex === -1 && inPage && !replaceEntry) { } else if (this.inPageIndex === -1 && inPage && !replaceEntry) {
// Started in-page navigations. // Started in-page navigations.
this.inPageIndex = this.currentIndex this.inPageIndex = this.currentIndex;
} }
if (this.pendingIndex >= 0) { if (this.pendingIndex >= 0) {
// Go to index. // Go to index.
this.currentIndex = this.pendingIndex this.currentIndex = this.pendingIndex;
this.pendingIndex = -1 this.pendingIndex = -1;
this.history[this.currentIndex] = url this.history[this.currentIndex] = url;
} else if (replaceEntry) { } else if (replaceEntry) {
// Non-user initialized navigation. // Non-user initialized navigation.
this.history[this.currentIndex] = url this.history[this.currentIndex] = url;
} else { } else {
// Normal navigation. Clear history. // Normal navigation. Clear history.
this.history = this.history.slice(0, this.currentIndex + 1) this.history = this.history.slice(0, this.currentIndex + 1);
this.currentIndex++ this.currentIndex++;
this.history.push(url) this.history.push(url);
} }
}) });
} }
NavigationController.prototype.loadURL = function (url, options) { NavigationController.prototype.loadURL = function (url, options) {
if (options == null) { if (options == null) {
options = {} options = {};
} }
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
const resolveAndCleanup = () => { const resolveAndCleanup = () => {
removeListeners() removeListeners();
resolve() resolve();
} };
const rejectAndCleanup = (errorCode, errorDescription, url) => { const rejectAndCleanup = (errorCode, errorDescription, url) => {
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`) const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`);
Object.assign(err, { errno: errorCode, code: errorDescription, url }) Object.assign(err, { errno: errorCode, code: errorDescription, url });
removeListeners() removeListeners();
reject(err) reject(err);
} };
const finishListener = () => { const finishListener = () => {
resolveAndCleanup() resolveAndCleanup();
} };
const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => { const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
if (isMainFrame) { if (isMainFrame) {
rejectAndCleanup(errorCode, errorDescription, validatedURL) rejectAndCleanup(errorCode, errorDescription, validatedURL);
} }
} };
let navigationStarted = false let navigationStarted = false;
const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => { const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => {
if (isMainFrame) { if (isMainFrame) {
if (navigationStarted && !isSameDocument) { if (navigationStarted && !isSameDocument) {
@ -96,11 +96,11 @@ const NavigationController = (function () {
// considered navigation events but are triggered with isSameDocument. // considered navigation events but are triggered with isSameDocument.
// We can ignore these to allow virtual routing on page load as long // We can ignore these to allow virtual routing on page load as long
// as the routing does not leave the document // as the routing does not leave the document
return rejectAndCleanup(-3, 'ERR_ABORTED', url) return rejectAndCleanup(-3, 'ERR_ABORTED', url);
} }
navigationStarted = true navigationStarted = true;
} }
} };
const stopLoadingListener = () => { const stopLoadingListener = () => {
// By the time we get here, either 'finish' or 'fail' should have fired // By the time we get here, either 'finish' or 'fail' should have fired
// if the navigation occurred. However, in some situations (e.g. when // if the navigation occurred. However, in some situations (e.g. when
@ -110,134 +110,134 @@ const NavigationController = (function () {
// TODO(jeremy): enumerate all the cases in which this can happen. If // TODO(jeremy): enumerate all the cases in which this can happen. If
// the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT // the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT
// would be more appropriate. // would be more appropriate.
rejectAndCleanup(-2, 'ERR_FAILED', url) rejectAndCleanup(-2, 'ERR_FAILED', url);
} };
const removeListeners = () => { const removeListeners = () => {
this.webContents.removeListener('did-finish-load', finishListener) this.webContents.removeListener('did-finish-load', finishListener);
this.webContents.removeListener('did-fail-load', failListener) this.webContents.removeListener('did-fail-load', failListener);
this.webContents.removeListener('did-start-navigation', navigationListener) this.webContents.removeListener('did-start-navigation', navigationListener);
this.webContents.removeListener('did-stop-loading', stopLoadingListener) this.webContents.removeListener('did-stop-loading', stopLoadingListener);
} };
this.webContents.on('did-finish-load', finishListener) this.webContents.on('did-finish-load', finishListener);
this.webContents.on('did-fail-load', failListener) this.webContents.on('did-fail-load', failListener);
this.webContents.on('did-start-navigation', navigationListener) this.webContents.on('did-start-navigation', navigationListener);
this.webContents.on('did-stop-loading', stopLoadingListener) this.webContents.on('did-stop-loading', stopLoadingListener);
}) });
// Add a no-op rejection handler to silence the unhandled rejection error. // Add a no-op rejection handler to silence the unhandled rejection error.
p.catch(() => {}) p.catch(() => {});
this.pendingIndex = -1 this.pendingIndex = -1;
this.webContents._loadURL(url, options) this.webContents._loadURL(url, options);
this.webContents.emit('load-url', url, options) this.webContents.emit('load-url', url, options);
return p return p;
} };
NavigationController.prototype.getURL = function () { NavigationController.prototype.getURL = function () {
if (this.currentIndex === -1) { if (this.currentIndex === -1) {
return '' return '';
} else { } else {
return this.history[this.currentIndex] return this.history[this.currentIndex];
} }
} };
NavigationController.prototype.stop = function () { NavigationController.prototype.stop = function () {
this.pendingIndex = -1 this.pendingIndex = -1;
return this.webContents._stop() return this.webContents._stop();
} };
NavigationController.prototype.reload = function () { NavigationController.prototype.reload = function () {
this.pendingIndex = this.currentIndex this.pendingIndex = this.currentIndex;
return this.webContents._loadURL(this.getURL(), {}) return this.webContents._loadURL(this.getURL(), {});
} };
NavigationController.prototype.reloadIgnoringCache = function () { NavigationController.prototype.reloadIgnoringCache = function () {
this.pendingIndex = this.currentIndex this.pendingIndex = this.currentIndex;
return this.webContents._loadURL(this.getURL(), { return this.webContents._loadURL(this.getURL(), {
extraHeaders: 'pragma: no-cache\n', extraHeaders: 'pragma: no-cache\n',
reloadIgnoringCache: true reloadIgnoringCache: true
}) });
} };
NavigationController.prototype.canGoBack = function () { NavigationController.prototype.canGoBack = function () {
return this.getActiveIndex() > 0 return this.getActiveIndex() > 0;
} };
NavigationController.prototype.canGoForward = function () { NavigationController.prototype.canGoForward = function () {
return this.getActiveIndex() < this.history.length - 1 return this.getActiveIndex() < this.history.length - 1;
} };
NavigationController.prototype.canGoToIndex = function (index) { NavigationController.prototype.canGoToIndex = function (index) {
return index >= 0 && index < this.history.length return index >= 0 && index < this.history.length;
} };
NavigationController.prototype.canGoToOffset = function (offset) { NavigationController.prototype.canGoToOffset = function (offset) {
return this.canGoToIndex(this.currentIndex + offset) return this.canGoToIndex(this.currentIndex + offset);
} };
NavigationController.prototype.clearHistory = function () { NavigationController.prototype.clearHistory = function () {
this.history = [] this.history = [];
this.currentIndex = -1 this.currentIndex = -1;
this.pendingIndex = -1 this.pendingIndex = -1;
this.inPageIndex = -1 this.inPageIndex = -1;
} };
NavigationController.prototype.goBack = function () { NavigationController.prototype.goBack = function () {
if (!this.canGoBack()) { if (!this.canGoBack()) {
return return;
} }
this.pendingIndex = this.getActiveIndex() - 1 this.pendingIndex = this.getActiveIndex() - 1;
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goBack() return this.webContents._goBack();
} else { } else {
return this.webContents._loadURL(this.history[this.pendingIndex], {}) return this.webContents._loadURL(this.history[this.pendingIndex], {});
} }
} };
NavigationController.prototype.goForward = function () { NavigationController.prototype.goForward = function () {
if (!this.canGoForward()) { if (!this.canGoForward()) {
return return;
} }
this.pendingIndex = this.getActiveIndex() + 1 this.pendingIndex = this.getActiveIndex() + 1;
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goForward() return this.webContents._goForward();
} else { } else {
return this.webContents._loadURL(this.history[this.pendingIndex], {}) return this.webContents._loadURL(this.history[this.pendingIndex], {});
} }
} };
NavigationController.prototype.goToIndex = function (index) { NavigationController.prototype.goToIndex = function (index) {
if (!this.canGoToIndex(index)) { if (!this.canGoToIndex(index)) {
return return;
} }
this.pendingIndex = index this.pendingIndex = index;
return this.webContents._loadURL(this.history[this.pendingIndex], {}) return this.webContents._loadURL(this.history[this.pendingIndex], {});
} };
NavigationController.prototype.goToOffset = function (offset) { NavigationController.prototype.goToOffset = function (offset) {
if (!this.canGoToOffset(offset)) { if (!this.canGoToOffset(offset)) {
return return;
} }
const pendingIndex = this.currentIndex + offset const pendingIndex = this.currentIndex + offset;
if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) {
this.pendingIndex = pendingIndex this.pendingIndex = pendingIndex;
return this.webContents._goToOffset(offset) return this.webContents._goToOffset(offset);
} else { } else {
return this.goToIndex(pendingIndex) return this.goToIndex(pendingIndex);
} }
} };
NavigationController.prototype.getActiveIndex = function () { NavigationController.prototype.getActiveIndex = function () {
if (this.pendingIndex === -1) { if (this.pendingIndex === -1) {
return this.currentIndex return this.currentIndex;
} else { } else {
return this.pendingIndex return this.pendingIndex;
} }
} };
NavigationController.prototype.length = function () { NavigationController.prototype.length = function () {
return this.history.length return this.history.length;
} };
return NavigationController return NavigationController;
})() })();
module.exports = NavigationController module.exports = NavigationController;

View file

@ -1,12 +1,12 @@
'use strict' 'use strict';
import { WebContents } from 'electron' import { WebContents } from 'electron';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const getOwnerKey = (webContents: WebContents, contextId: string) => { const getOwnerKey = (webContents: WebContents, contextId: string) => {
return `${webContents.id}-${contextId}` return `${webContents.id}-${contextId}`;
} };
class ObjectsRegistry { class ObjectsRegistry {
private nextId: number = 0 private nextId: number = 0
@ -23,29 +23,29 @@ class ObjectsRegistry {
// registered then the already assigned ID would be returned. // registered then the already assigned ID would be returned.
add (webContents: WebContents, contextId: string, obj: any) { add (webContents: WebContents, contextId: string, obj: any) {
// Get or assign an ID to the object. // Get or assign an ID to the object.
const id = this.saveToStorage(obj) const id = this.saveToStorage(obj);
// Add object to the set of referenced objects. // Add object to the set of referenced objects.
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
let owner = this.owners[ownerKey] let owner = this.owners[ownerKey];
if (!owner) { if (!owner) {
owner = this.owners[ownerKey] = new Map() owner = this.owners[ownerKey] = new Map();
this.registerDeleteListener(webContents, contextId) this.registerDeleteListener(webContents, contextId);
} }
if (!owner.has(id)) { if (!owner.has(id)) {
owner.set(id, 0) owner.set(id, 0);
// Increase reference count if not referenced before. // Increase reference count if not referenced before.
this.storage[id].count++ this.storage[id].count++;
} }
owner.set(id, owner.get(id)! + 1) owner.set(id, owner.get(id)! + 1);
return id return id;
} }
// Get an object according to its ID. // Get an object according to its ID.
get (id: number) { get (id: number) {
const pointer = this.storage[id] const pointer = this.storage[id];
if (pointer != null) return pointer.object if (pointer != null) return pointer.object;
} }
// Dereference an object according to its ID. // Dereference an object according to its ID.
@ -60,79 +60,79 @@ class ObjectsRegistry {
// For more details on why we do renderer side ref counting see // For more details on why we do renderer side ref counting see
// https://github.com/electron/electron/pull/17464 // https://github.com/electron/electron/pull/17464
remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) { remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) {
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey] const owner = this.owners[ownerKey];
if (owner && owner.has(id)) { if (owner && owner.has(id)) {
const newRefCount = owner.get(id)! - rendererSideRefCount const newRefCount = owner.get(id)! - rendererSideRefCount;
// Only completely remove if the number of references GCed in the // Only completely remove if the number of references GCed in the
// renderer is the same as the number of references we sent them // renderer is the same as the number of references we sent them
if (newRefCount <= 0) { if (newRefCount <= 0) {
// Remove the reference in owner. // Remove the reference in owner.
owner.delete(id) owner.delete(id);
// Dereference from the storage. // Dereference from the storage.
this.dereference(id) this.dereference(id);
} else { } else {
owner.set(id, newRefCount) owner.set(id, newRefCount);
} }
} }
} }
// Clear all references to objects refrenced by the WebContents. // Clear all references to objects refrenced by the WebContents.
clear (webContents: WebContents, contextId: string) { clear (webContents: WebContents, contextId: string) {
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey] const owner = this.owners[ownerKey];
if (!owner) return if (!owner) return;
for (const id of owner.keys()) this.dereference(id) for (const id of owner.keys()) this.dereference(id);
delete this.owners[ownerKey] delete this.owners[ownerKey];
} }
// Private: Saves the object into storage and assigns an ID for it. // Private: Saves the object into storage and assigns an ID for it.
saveToStorage (object: any) { saveToStorage (object: any) {
let id: number = v8Util.getHiddenValue(object, 'atomId') let id: number = v8Util.getHiddenValue(object, 'atomId');
if (!id) { if (!id) {
id = ++this.nextId id = ++this.nextId;
this.storage[id] = { this.storage[id] = {
count: 0, count: 0,
object: object object: object
} };
v8Util.setHiddenValue(object, 'atomId', id) v8Util.setHiddenValue(object, 'atomId', id);
} }
return id return id;
} }
// Private: Dereference the object from store. // Private: Dereference the object from store.
dereference (id: number) { dereference (id: number) {
const pointer = this.storage[id] const pointer = this.storage[id];
if (pointer == null) { if (pointer == null) {
return return;
} }
pointer.count -= 1 pointer.count -= 1;
if (pointer.count === 0) { if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId') v8Util.deleteHiddenValue(pointer.object, 'atomId');
delete this.storage[id] delete this.storage[id];
} }
} }
// Private: Clear the storage when renderer process is destroyed. // Private: Clear the storage when renderer process is destroyed.
registerDeleteListener (webContents: WebContents, contextId: string) { registerDeleteListener (webContents: WebContents, contextId: string) {
// contextId => ${processHostId}-${contextCount} // contextId => ${processHostId}-${contextCount}
const processHostId = contextId.split('-')[0] const processHostId = contextId.split('-')[0];
const listener = (_: any, deletedProcessHostId: string) => { const listener = (_: any, deletedProcessHostId: string) => {
if (deletedProcessHostId && if (deletedProcessHostId &&
deletedProcessHostId.toString() === processHostId) { deletedProcessHostId.toString() === processHostId) {
webContents.removeListener('render-view-deleted' as any, listener) webContents.removeListener('render-view-deleted' as any, listener);
this.clear(webContents, contextId) this.clear(webContents, contextId);
} }
} };
// Note that the "render-view-deleted" event may not be emitted on time when // Note that the "render-view-deleted" event may not be emitted on time when
// the renderer process get destroyed because of navigation, we rely on the // the renderer process get destroyed because of navigation, we rely on the
// renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to // renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to
// guard this situation. // guard this situation.
webContents.on('render-view-deleted' as any, listener) webContents.on('render-view-deleted' as any, listener);
} }
} }
export default new ObjectsRegistry() export default new ObjectsRegistry();

View file

@ -1,29 +1,29 @@
'use strict' 'use strict';
import * as electron from 'electron' import * as electron from 'electron';
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import objectsRegistry from './objects-registry' import objectsRegistry from './objects-registry';
import { ipcMainInternal } from '../ipc-main-internal' import { ipcMainInternal } from '../ipc-main-internal';
import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils' import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event') const eventBinding = process.electronBinding('event');
const features = process.electronBinding('features') const features = process.electronBinding('features');
if (!features.isRemoteModuleEnabled()) { if (!features.isRemoteModuleEnabled()) {
throw new Error('remote module is disabled') throw new Error('remote module is disabled');
} }
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty;
// The internal properties of Function. // The internal properties of Function.
const FUNCTION_PROPERTIES = [ const FUNCTION_PROPERTIES = [
'length', 'name', 'arguments', 'caller', 'prototype' 'length', 'name', 'arguments', 'caller', 'prototype'
] ];
// The remote functions in renderer processes. // The remote functions in renderer processes.
// id => Function // id => Function
const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>() const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>();
type ObjectMember = { type ObjectMember = {
name: string, name: string,
@ -35,28 +35,28 @@ type ObjectMember = {
// Return the description of object's members: // Return the description of object's members:
const getObjectMembers = function (object: any): ObjectMember[] { const getObjectMembers = function (object: any): ObjectMember[] {
let names = Object.getOwnPropertyNames(object) let names = Object.getOwnPropertyNames(object);
// For Function, we should not override following properties even though they // For Function, we should not override following properties even though they
// are "own" properties. // are "own" properties.
if (typeof object === 'function') { if (typeof object === 'function') {
names = names.filter((name) => { names = names.filter((name) => {
return !FUNCTION_PROPERTIES.includes(name) return !FUNCTION_PROPERTIES.includes(name);
}) });
} }
// Map properties to descriptors. // Map properties to descriptors.
return names.map((name) => { return names.map((name) => {
const descriptor = Object.getOwnPropertyDescriptor(object, name)! const descriptor = Object.getOwnPropertyDescriptor(object, name)!;
let type: ObjectMember['type'] let type: ObjectMember['type'];
let writable = false let writable = false;
if (descriptor.get === undefined && typeof object[name] === 'function') { if (descriptor.get === undefined && typeof object[name] === 'function') {
type = 'method' type = 'method';
} else { } else {
if (descriptor.set || descriptor.writable) writable = true if (descriptor.set || descriptor.writable) writable = true;
type = 'get' type = 'get';
} }
return { name, enumerable: descriptor.enumerable, writable, type } return { name, enumerable: descriptor.enumerable, writable, type };
}) });
} };
type ObjProtoDescriptor = { type ObjProtoDescriptor = {
members: ObjectMember[], members: ObjectMember[],
@ -65,13 +65,13 @@ type ObjProtoDescriptor = {
// Return the description of object's prototype. // Return the description of object's prototype.
const getObjectPrototype = function (object: any): ObjProtoDescriptor { const getObjectPrototype = function (object: any): ObjProtoDescriptor {
const proto = Object.getPrototypeOf(object) const proto = Object.getPrototypeOf(object);
if (proto === null || proto === Object.prototype) return null if (proto === null || proto === Object.prototype) return null;
return { return {
members: getObjectMembers(proto), members: getObjectMembers(proto),
proto: getObjectPrototype(proto) proto: getObjectPrototype(proto)
} };
} };
type MetaType = { type MetaType = {
type: 'number', type: 'number',
@ -118,25 +118,25 @@ type MetaType = {
// Convert a real value into meta data. // Convert a real value into meta data.
const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType { const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType {
// Determine the type of value. // Determine the type of value.
let type: MetaType['type'] = typeof value let type: MetaType['type'] = typeof value;
if (type === 'object') { if (type === 'object') {
// Recognize certain types of objects. // Recognize certain types of objects.
if (value instanceof Buffer) { if (value instanceof Buffer) {
type = 'buffer' type = 'buffer';
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
type = 'array' type = 'array';
} else if (value instanceof Error) { } else if (value instanceof Error) {
type = 'error' type = 'error';
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
type = 'value' type = 'value';
} else if (isPromise(value)) { } else if (isPromise(value)) {
type = 'promise' type = 'promise';
} else if (hasProp.call(value, 'callee') && value.length != null) { } else if (hasProp.call(value, 'callee') && value.length != null) {
// Treat the arguments object as array. // Treat the arguments object as array.
type = 'array' type = 'array';
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
// Treat simple objects as value. // Treat simple objects as value.
type = 'value' type = 'value';
} }
} }
@ -145,7 +145,7 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
return { return {
type, type,
members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject)) members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
} };
} else if (type === 'object' || type === 'function') { } else if (type === 'object' || type === 'function') {
return { return {
type, type,
@ -156,20 +156,20 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
id: objectsRegistry.add(sender, contextId, value), id: objectsRegistry.add(sender, contextId, value),
members: getObjectMembers(value), members: getObjectMembers(value),
proto: getObjectPrototype(value) proto: getObjectPrototype(value)
} };
} else if (type === 'buffer') { } else if (type === 'buffer') {
return { type, value } return { type, value };
} else if (type === 'promise') { } else if (type === 'promise') {
// Add default handler to prevent unhandled rejections in main process // Add default handler to prevent unhandled rejections in main process
// Instead they should appear in the renderer process // Instead they should appear in the renderer process
value.then(function () {}, function () {}) value.then(function () {}, function () {});
return { return {
type, type,
then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) { then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) {
value.then(onFulfilled, onRejected) value.then(onFulfilled, onRejected);
}) })
} };
} else if (type === 'error') { } else if (type === 'error') {
return { return {
type, type,
@ -178,42 +178,42 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
name, name,
value: valueToMeta(sender, contextId, value[name]) value: valueToMeta(sender, contextId, value[name])
})) }))
} };
} else { } else {
return { return {
type: 'value', type: 'value',
value value
} };
} }
} };
const throwRPCError = function (message: string) { const throwRPCError = function (message: string) {
const error = new Error(message) as Error & {code: string, errno: number} const error = new Error(message) as Error & {code: string, errno: number};
error.code = 'EBADRPC' error.code = 'EBADRPC';
error.errno = -72 error.errno = -72;
throw error throw error;
} };
const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => { const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => {
const location = v8Util.getHiddenValue(callIntoRenderer, 'location') const location = v8Util.getHiddenValue(callIntoRenderer, 'location');
let message = 'Attempting to call a function in a renderer window that has been closed or released.' + let message = 'Attempting to call a function in a renderer window that has been closed or released.' +
`\nFunction provided here: ${location}` `\nFunction provided here: ${location}`;
if (sender instanceof EventEmitter) { if (sender instanceof EventEmitter) {
const remoteEvents = sender.eventNames().filter((eventName) => { const remoteEvents = sender.eventNames().filter((eventName) => {
return sender.listeners(eventName).includes(callIntoRenderer) return sender.listeners(eventName).includes(callIntoRenderer);
}) });
if (remoteEvents.length > 0) { if (remoteEvents.length > 0) {
message += `\nRemote event names: ${remoteEvents.join(', ')}` message += `\nRemote event names: ${remoteEvents.join(', ')}`;
remoteEvents.forEach((eventName) => { remoteEvents.forEach((eventName) => {
sender.removeListener(eventName as any, callIntoRenderer) sender.removeListener(eventName as any, callIntoRenderer);
}) });
} }
} }
console.warn(message) console.warn(message);
} };
type MetaTypeFromRenderer = { type MetaTypeFromRenderer = {
type: 'value', type: 'value',
@ -248,301 +248,301 @@ const fakeConstructor = (constructor: Function, name: string) =>
new Proxy(Object, { new Proxy(Object, {
get (target, prop, receiver) { get (target, prop, receiver) {
if (prop === 'name') { if (prop === 'name') {
return name return name;
} else { } else {
return Reflect.get(target, prop, receiver) return Reflect.get(target, prop, receiver);
} }
} }
}) });
// Convert array of meta data from renderer into array of real values. // Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) { const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
const metaToValue = function (meta: MetaTypeFromRenderer): any { const metaToValue = function (meta: MetaTypeFromRenderer): any {
switch (meta.type) { switch (meta.type) {
case 'value': case 'value':
return meta.value return meta.value;
case 'remote-object': case 'remote-object':
return objectsRegistry.get(meta.id) return objectsRegistry.get(meta.id);
case 'array': case 'array':
return unwrapArgs(sender, frameId, contextId, meta.value) return unwrapArgs(sender, frameId, contextId, meta.value);
case 'buffer': case 'buffer':
return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength) return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength);
case 'promise': case 'promise':
return Promise.resolve({ return Promise.resolve({
then: metaToValue(meta.then) then: metaToValue(meta.then)
}) });
case 'object': { case 'object': {
const ret: any = meta.name !== 'Object' ? Object.create({ const ret: any = meta.name !== 'Object' ? Object.create({
constructor: fakeConstructor(Object, meta.name) constructor: fakeConstructor(Object, meta.name)
}) : {} }) : {};
for (const { name, value } of meta.members) { for (const { name, value } of meta.members) {
ret[name] = metaToValue(value) ret[name] = metaToValue(value);
} }
return ret return ret;
} }
case 'function-with-return-value': { case 'function-with-return-value': {
const returnValue = metaToValue(meta.value) const returnValue = metaToValue(meta.value);
return function () { return function () {
return returnValue return returnValue;
} };
} }
case 'function': { case 'function': {
// Merge contextId and meta.id, since meta.id can be the same in // Merge contextId and meta.id, since meta.id can be the same in
// different webContents. // different webContents.
const objectId: [string, number] = [contextId, meta.id] const objectId: [string, number] = [contextId, meta.id];
// Cache the callbacks in renderer. // Cache the callbacks in renderer.
if (rendererFunctions.has(objectId)) { if (rendererFunctions.has(objectId)) {
return rendererFunctions.get(objectId) return rendererFunctions.get(objectId);
} }
const callIntoRenderer = function (this: any, ...args: any[]) { const callIntoRenderer = function (this: any, ...args: any[]) {
let succeed = false let succeed = false;
if (!sender.isDestroyed()) { if (!sender.isDestroyed()) {
succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args)) succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
} }
if (!succeed) { if (!succeed) {
removeRemoteListenersAndLogWarning(this, callIntoRenderer) removeRemoteListenersAndLogWarning(this, callIntoRenderer);
} }
} };
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location) v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location);
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) Object.defineProperty(callIntoRenderer, 'length', { value: meta.length });
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender) v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender);
rendererFunctions.set(objectId, callIntoRenderer) rendererFunctions.set(objectId, callIntoRenderer);
return callIntoRenderer return callIntoRenderer;
} }
default: default:
throw new TypeError(`Unknown type: ${(meta as any).type}`) throw new TypeError(`Unknown type: ${(meta as any).type}`);
} }
} };
return args.map(metaToValue) return args.map(metaToValue);
} };
const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) { const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) {
const webPreferences = (contents as any).getLastWebPreferences() || {} const webPreferences = (contents as any).getLastWebPreferences() || {};
return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : false return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : false;
} };
const isRemoteModuleEnabledCache = new WeakMap() const isRemoteModuleEnabledCache = new WeakMap();
const isRemoteModuleEnabled = function (contents: electron.WebContents) { const isRemoteModuleEnabled = function (contents: electron.WebContents) {
if (!isRemoteModuleEnabledCache.has(contents)) { if (!isRemoteModuleEnabledCache.has(contents)) {
isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents)) isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents));
} }
return isRemoteModuleEnabledCache.get(contents) return isRemoteModuleEnabledCache.get(contents);
} };
const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) { const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) {
ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => { ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => {
let returnValue let returnValue;
if (!isRemoteModuleEnabled(event.sender)) { if (!isRemoteModuleEnabled(event.sender)) {
event.returnValue = null event.returnValue = null;
return return;
} }
try { try {
returnValue = handler(event, contextId, ...args) returnValue = handler(event, contextId, ...args);
} catch (error) { } catch (error) {
returnValue = { returnValue = {
type: 'exception', type: 'exception',
value: valueToMeta(event.sender, contextId, error) value: valueToMeta(event.sender, contextId, error)
} };
} }
if (returnValue !== undefined) { if (returnValue !== undefined) {
event.returnValue = returnValue event.returnValue = returnValue;
} }
}) });
} };
const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) { const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) {
const event = eventBinding.createWithSender(contents) const event = eventBinding.createWithSender(contents);
electron.app.emit(eventName, event, contents, ...args) electron.app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args) contents.emit(eventName, event, ...args);
return event return event;
} };
const logStack = function (contents: electron.WebContents, code: string, stack: string | undefined) { const logStack = function (contents: electron.WebContents, code: string, stack: string | undefined) {
if (stack) { if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack) console.warn(`WebContents (${contents.id}): ${code}`, stack);
} }
} };
handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) { handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
const objectId: [string, number] = [passedContextId, id] const objectId: [string, number] = [passedContextId, id];
if (!rendererFunctions.has(objectId)) { if (!rendererFunctions.has(objectId)) {
// Do nothing if the error has already been reported before. // Do nothing if the error has already been reported before.
return return;
} }
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)!) removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)!);
}) });
handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName, stack) { handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName, stack) {
logStack(event.sender, `remote.require('${moduleName}')`, stack) logStack(event.sender, `remote.require('${moduleName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName) const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.require('${moduleName}')`) throw new Error(`Blocked remote.require('${moduleName}')`);
} else { } else {
customEvent.returnValue = process.mainModule!.require(moduleName) customEvent.returnValue = process.mainModule!.require(moduleName);
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName, stack) { handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName, stack) {
logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack) logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName) const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`) throw new Error(`Blocked remote.getBuiltin('${moduleName}')`);
} else { } else {
customEvent.returnValue = (electron as any)[moduleName] customEvent.returnValue = (electron as any)[moduleName];
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName, stack) { handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName, stack) {
logStack(event.sender, `remote.getGlobal('${globalName}')`, stack) logStack(event.sender, `remote.getGlobal('${globalName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName) const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getGlobal('${globalName}')`) throw new Error(`Blocked remote.getGlobal('${globalName}')`);
} else { } else {
customEvent.returnValue = (global as any)[globalName] customEvent.returnValue = (global as any)[globalName];
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId, stack) { handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId, stack) {
logStack(event.sender, 'remote.getCurrentWindow()', stack) logStack(event.sender, 'remote.getCurrentWindow()', stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window') const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window');
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWindow()') throw new Error('Blocked remote.getCurrentWindow()');
} else { } else {
customEvent.returnValue = event.sender.getOwnerBrowserWindow() customEvent.returnValue = event.sender.getOwnerBrowserWindow();
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId, stack) { handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId, stack) {
logStack(event.sender, 'remote.getCurrentWebContents()', stack) logStack(event.sender, 'remote.getCurrentWebContents()', stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents') const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents');
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWebContents()') throw new Error('Blocked remote.getCurrentWebContents()');
} else { } else {
customEvent.returnValue = event.sender customEvent.returnValue = event.sender;
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const constructor = objectsRegistry.get(id) const constructor = objectsRegistry.get(id);
if (constructor == null) { if (constructor == null) {
throwRPCError(`Cannot call constructor on missing remote object ${id}`) throwRPCError(`Cannot call constructor on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, new constructor(...args)) return valueToMeta(event.sender, contextId, new constructor(...args));
}) });
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const func = objectsRegistry.get(id) const func = objectsRegistry.get(id);
if (func == null) { if (func == null) {
throwRPCError(`Cannot call function on missing remote object ${id}`) throwRPCError(`Cannot call function on missing remote object ${id}`);
} }
try { try {
return valueToMeta(event.sender, contextId, func(...args), true) return valueToMeta(event.sender, contextId, func(...args), true);
} catch (error) { } catch (error) {
const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
(err as any).cause = error (err as any).cause = error;
throw err throw err;
} }
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const object = objectsRegistry.get(id) const object = objectsRegistry.get(id);
if (object == null) { if (object == null) {
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`) throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, new object[method](...args)) return valueToMeta(event.sender, contextId, new object[method](...args));
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const object = objectsRegistry.get(id) const object = objectsRegistry.get(id);
if (object == null) { if (object == null) {
throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`) throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`);
} }
try { try {
return valueToMeta(event.sender, contextId, object[method](...args), true) return valueToMeta(event.sender, contextId, object[method](...args), true);
} catch (error) { } catch (error) {
const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
(err as any).cause = error (err as any).cause = error;
throw err throw err;
} }
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const obj = objectsRegistry.get(id) const obj = objectsRegistry.get(id);
if (obj == null) { if (obj == null) {
throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`) throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`);
} }
obj[name] = args[0] obj[name] = args[0];
return null return null;
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
const obj = objectsRegistry.get(id) const obj = objectsRegistry.get(id);
if (obj == null) { if (obj == null) {
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`) throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, obj[name]) return valueToMeta(event.sender, contextId, obj[name]);
}) });
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) { handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount) objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => { handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
objectsRegistry.clear(event.sender, contextId) objectsRegistry.clear(event.sender, contextId);
}) });
module.exports = { module.exports = {
isRemoteModuleEnabled isRemoteModuleEnabled
} };

View file

@ -1,118 +1,118 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const fs = require('fs') const fs = require('fs');
const eventBinding = process.electronBinding('event') const eventBinding = process.electronBinding('event');
const clipboard = process.electronBinding('clipboard') const clipboard = process.electronBinding('clipboard');
const features = process.electronBinding('features') const features = process.electronBinding('features');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init') const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const guestViewManager = require('@electron/internal/browser/guest-view-manager') const guestViewManager = require('@electron/internal/browser/guest-view-manager');
const typeUtils = require('@electron/internal/common/type-utils') const typeUtils = require('@electron/internal/common/type-utils');
const emitCustomEvent = function (contents, eventName, ...args) { const emitCustomEvent = function (contents, eventName, ...args) {
const event = eventBinding.createWithSender(contents) const event = eventBinding.createWithSender(contents);
electron.app.emit(eventName, event, contents, ...args) electron.app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args) contents.emit(eventName, event, ...args);
return event return event;
} };
const logStack = function (contents, code, stack) { const logStack = function (contents, code, stack) {
if (stack) { if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack) console.warn(`WebContents (${contents.id}): ${code}`, stack);
} }
} };
// Implements window.close() // Implements window.close()
ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
if (window) { if (window) {
window.close() window.close();
} }
event.returnValue = null event.returnValue = null;
}) });
ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
return crashReporterInit(options) return crashReporterInit(options);
}) });
ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
return event.sender.getLastWebPreferences() return event.sender.getLastWebPreferences();
}) });
// Methods not listed in this set are called directly in the renderer process. // Methods not listed in this set are called directly in the renderer process.
const allowedClipboardMethods = (() => { const allowedClipboardMethods = (() => {
switch (process.platform) { switch (process.platform) {
case 'darwin': case 'darwin':
return new Set(['readFindText', 'writeFindText']) return new Set(['readFindText', 'writeFindText']);
case 'linux': case 'linux':
return new Set(Object.keys(clipboard)) return new Set(Object.keys(clipboard));
default: default:
return new Set() return new Set();
} }
})() })();
ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) {
if (!allowedClipboardMethods.has(method)) { if (!allowedClipboardMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args))) return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)));
}) });
if (features.isDesktopCapturerEnabled()) { if (features.isDesktopCapturerEnabled()) {
const desktopCapturer = require('@electron/internal/browser/desktop-capturer') const desktopCapturer = require('@electron/internal/browser/desktop-capturer');
ipcMainInternal.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, options, stack) { ipcMainInternal.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, options, stack) {
logStack(event.sender, 'desktopCapturer.getSources()', stack) logStack(event.sender, 'desktopCapturer.getSources()', stack);
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources') const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
console.error('Blocked desktopCapturer.getSources()') console.error('Blocked desktopCapturer.getSources()');
return [] return [];
} }
return desktopCapturer.getSources(event, options) return desktopCapturer.getSources(event, options);
}) });
} }
const isRemoteModuleEnabled = features.isRemoteModuleEnabled() const isRemoteModuleEnabled = features.isRemoteModuleEnabled()
? require('@electron/internal/browser/remote/server').isRemoteModuleEnabled ? require('@electron/internal/browser/remote/server').isRemoteModuleEnabled
: () => false : () => false;
const getPreloadScript = async function (preloadPath) { const getPreloadScript = async function (preloadPath) {
let preloadSrc = null let preloadSrc = null;
let preloadError = null let preloadError = null;
try { try {
preloadSrc = (await fs.promises.readFile(preloadPath)).toString() preloadSrc = (await fs.promises.readFile(preloadPath)).toString();
} catch (error) { } catch (error) {
preloadError = error preloadError = error;
} }
return { preloadPath, preloadSrc, preloadError } return { preloadPath, preloadSrc, preloadError };
} };
if (features.isExtensionsEnabled()) { if (features.isExtensionsEnabled()) {
ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []) ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []);
} else { } else {
const { getContentScripts } = require('@electron/internal/browser/chrome-extension') const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts()) ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts());
} }
ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
const preloadPaths = event.sender._getPreloadPaths() const preloadPaths = event.sender._getPreloadPaths();
let contentScripts = [] let contentScripts = [];
if (!features.isExtensionsEnabled()) { if (!features.isExtensionsEnabled()) {
const { getContentScripts } = require('@electron/internal/browser/chrome-extension') const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
contentScripts = getContentScripts() contentScripts = getContentScripts();
} }
const webPreferences = event.sender.getLastWebPreferences() || {} const webPreferences = event.sender.getLastWebPreferences() || {};
return { return {
contentScripts, contentScripts,
@ -129,9 +129,9 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event)
versions: process.versions, versions: process.versions,
execPath: process.helperExecPath execPath: process.helperExecPath
} }
} };
}) });
ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) { ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
event.sender.emit('preload-error', event, preloadPath, error) event.sender.emit('preload-error', event, preloadPath, error);
}) });

View file

@ -1,4 +1,4 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
/** /**
* Creates a lazy instance of modules that can't be required before the * Creates a lazy instance of modules that can't be required before the
@ -16,23 +16,23 @@ export function createLazyInstance (
holder: Object, holder: Object,
isEventEmitter: Boolean isEventEmitter: Boolean
) { ) {
let lazyModule: Object let lazyModule: Object;
const module: any = {} const module: any = {};
for (const method in (holder as any).prototype) { // eslint-disable-line guard-for-in for (const method in (holder as any).prototype) { // eslint-disable-line guard-for-in
module[method] = (...args: any) => { module[method] = (...args: any) => {
// create new instance of module at runtime if none exists // create new instance of module at runtime if none exists
if (!lazyModule) { if (!lazyModule) {
lazyModule = creator() lazyModule = creator();
if (isEventEmitter) EventEmitter.call(lazyModule as any) if (isEventEmitter) EventEmitter.call(lazyModule as any);
} }
// check for properties on the prototype chain that aren't functions // check for properties on the prototype chain that aren't functions
if (typeof (lazyModule as any)[method] !== 'function') { if (typeof (lazyModule as any)[method] !== 'function') {
return (lazyModule as any)[method] return (lazyModule as any)[method];
} }
return (lazyModule as any)[method](...args) return (lazyModule as any)[method](...args);
} };
} }
return module return module;
} }

View file

@ -1,29 +1,29 @@
'use strict' 'use strict';
const clipboard = process.electronBinding('clipboard') const clipboard = process.electronBinding('clipboard');
if (process.type === 'renderer') { if (process.type === 'renderer') {
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const typeUtils = require('@electron/internal/common/type-utils') const typeUtils = require('@electron/internal/common/type-utils');
const makeRemoteMethod = function (method) { const makeRemoteMethod = function (method) {
return (...args) => { return (...args) => {
args = typeUtils.serialize(args) args = typeUtils.serialize(args);
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args) const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args);
return typeUtils.deserialize(result) return typeUtils.deserialize(result);
} };
} };
if (process.platform === 'linux') { if (process.platform === 'linux') {
// On Linux we could not access clipboard in renderer process. // On Linux we could not access clipboard in renderer process.
for (const method of Object.keys(clipboard)) { for (const method of Object.keys(clipboard)) {
clipboard[method] = makeRemoteMethod(method) clipboard[method] = makeRemoteMethod(method);
} }
} else if (process.platform === 'darwin') { } else if (process.platform === 'darwin') {
// Read/write to find pasteboard over IPC since only main process is notified of changes // Read/write to find pasteboard over IPC since only main process is notified of changes
clipboard.readFindText = makeRemoteMethod('readFindText') clipboard.readFindText = makeRemoteMethod('readFindText');
clipboard.writeFindText = makeRemoteMethod('writeFindText') clipboard.writeFindText = makeRemoteMethod('writeFindText');
} }
} }
module.exports = clipboard module.exports = clipboard;

View file

@ -1,95 +1,95 @@
let deprecationHandler: ElectronInternal.DeprecationHandler | null = null let deprecationHandler: ElectronInternal.DeprecationHandler | null = null;
function warnOnce (oldName: string, newName?: string) { function warnOnce (oldName: string, newName?: string) {
let warned = false let warned = false;
const msg = newName const msg = newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.` ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.` : `'${oldName}' is deprecated and will be removed.`;
return () => { return () => {
if (!warned && !process.noDeprecation) { if (!warned && !process.noDeprecation) {
warned = true warned = true;
deprecate.log(msg) deprecate.log(msg);
} }
} };
} }
const deprecate: ElectronInternal.DeprecationUtil = { const deprecate: ElectronInternal.DeprecationUtil = {
warnOnce, warnOnce,
setHandler: (handler) => { deprecationHandler = handler }, setHandler: (handler) => { deprecationHandler = handler; },
getHandler: () => deprecationHandler, getHandler: () => deprecationHandler,
warn: (oldName, newName) => { warn: (oldName, newName) => {
if (!process.noDeprecation) { if (!process.noDeprecation) {
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`) deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
} }
}, },
log: (message) => { log: (message) => {
if (typeof deprecationHandler === 'function') { if (typeof deprecationHandler === 'function') {
deprecationHandler(message) deprecationHandler(message);
} else if (process.throwDeprecation) { } else if (process.throwDeprecation) {
throw new Error(message) throw new Error(message);
} else if (process.traceDeprecation) { } else if (process.traceDeprecation) {
return console.trace(message) return console.trace(message);
} else { } else {
return console.warn(`(electron) ${message}`) return console.warn(`(electron) ${message}`);
} }
}, },
// remove a function with no replacement // remove a function with no replacement
removeFunction: (fn, removedName) => { removeFunction: (fn, removedName) => {
if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`) } if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`); }
// wrap the deprecated function to warn user // wrap the deprecated function to warn user
const warn = warnOnce(`${fn.name} function`) const warn = warnOnce(`${fn.name} function`);
return function (this: any) { return function (this: any) {
warn() warn();
fn.apply(this, arguments) fn.apply(this, arguments);
} };
}, },
// change the name of a function // change the name of a function
renameFunction: (fn, newName) => { renameFunction: (fn, newName) => {
const warn = warnOnce(`${fn.name} function`, `${newName} function`) const warn = warnOnce(`${fn.name} function`, `${newName} function`);
return function (this: any) { return function (this: any) {
warn() warn();
return fn.apply(this, arguments) return fn.apply(this, arguments);
} };
}, },
moveAPI: (fn: Function, oldUsage: string, newUsage: string) => { moveAPI: (fn: Function, oldUsage: string, newUsage: string) => {
const warn = warnOnce(oldUsage, newUsage) const warn = warnOnce(oldUsage, newUsage);
return function (this: any) { return function (this: any) {
warn() warn();
return fn.apply(this, arguments) return fn.apply(this, arguments);
} };
}, },
// change the name of an event // change the name of an event
event: (emitter, oldName, newName) => { event: (emitter, oldName, newName) => {
const warn = newName.startsWith('-') /* internal event */ const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`) ? warnOnce(`${oldName} event`)
: warnOnce(`${oldName} event`, `${newName} event`) : warnOnce(`${oldName} event`, `${newName} event`);
return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) { return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) {
if (this.listenerCount(oldName) !== 0) { if (this.listenerCount(oldName) !== 0) {
warn() warn();
this.emit(oldName, ...args) this.emit(oldName, ...args);
} }
}) });
}, },
// deprecate a getter/setter function pair in favor of a property // deprecate a getter/setter function pair in favor of a property
fnToProperty: (prototype: any, prop: string, getter: string, setter?: string) => { fnToProperty: (prototype: any, prop: string, getter: string, setter?: string) => {
const withWarnOnce = function (obj: any, key: any, oldName: string, newName: string) { const withWarnOnce = function (obj: any, key: any, oldName: string, newName: string) {
const warn = warnOnce(oldName, newName) const warn = warnOnce(oldName, newName);
const method = obj[key] const method = obj[key];
return function (this: any, ...args: any) { return function (this: any, ...args: any) {
warn() warn();
return method.apply(this, args) return method.apply(this, args);
} };
} };
prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`) prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`);
if (setter) { if (setter) {
prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`) prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`);
} }
}, },
@ -98,55 +98,55 @@ const deprecate: ElectronInternal.DeprecationUtil = {
// if the property's already been removed, warn about it // if the property's already been removed, warn about it
const info = Object.getOwnPropertyDescriptor((o as any).__proto__, removedName) // eslint-disable-line const info = Object.getOwnPropertyDescriptor((o as any).__proto__, removedName) // eslint-disable-line
if (!info) { if (!info) {
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`) deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`);
return o return o;
} }
if (!info.get || !info.set) { if (!info.get || !info.set) {
deprecate.log(`Unable to remove property '${removedName}' from an object does not have a getter / setter`) deprecate.log(`Unable to remove property '${removedName}' from an object does not have a getter / setter`);
return o return o;
} }
// wrap the deprecated property in an accessor to warn // wrap the deprecated property in an accessor to warn
const warn = warnOnce(removedName) const warn = warnOnce(removedName);
return Object.defineProperty(o, removedName, { return Object.defineProperty(o, removedName, {
configurable: true, configurable: true,
get: () => { get: () => {
warn() warn();
return info.get!.call(o) return info.get!.call(o);
}, },
set: newVal => { set: newVal => {
if (!onlyForValues || onlyForValues.includes(newVal)) { if (!onlyForValues || onlyForValues.includes(newVal)) {
warn() warn();
} }
return info.set!.call(o, newVal) return info.set!.call(o, newVal);
} }
}) });
}, },
// change the name of a property // change the name of a property
renameProperty: (o, oldName, newName) => { renameProperty: (o, oldName, newName) => {
const warn = warnOnce(oldName, newName) const warn = warnOnce(oldName, newName);
// if the new property isn't there yet, // if the new property isn't there yet,
// inject it and warn about it // inject it and warn about it
if ((oldName in o) && !(newName in o)) { if ((oldName in o) && !(newName in o)) {
warn() warn();
o[newName] = (o as any)[oldName] o[newName] = (o as any)[oldName];
} }
// wrap the deprecated property in an accessor to warn // wrap the deprecated property in an accessor to warn
// and redirect to the new property // and redirect to the new property
return Object.defineProperty(o, oldName, { return Object.defineProperty(o, oldName, {
get: () => { get: () => {
warn() warn();
return o[newName] return o[newName];
}, },
set: value => { set: value => {
warn() warn();
o[newName] = value o[newName] = value;
} }
}) });
} }
} };
export default deprecate export default deprecate;

View file

@ -5,4 +5,4 @@ export const commonModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'shell', loader: () => require('./shell') }, { name: 'shell', loader: () => require('./shell') },
// The internal modules, invisible unless you know their names. // The internal modules, invisible unless you know their names.
{ name: 'deprecate', loader: () => require('./deprecate'), private: true } { name: 'deprecate', loader: () => require('./deprecate'), private: true }
] ];

View file

@ -1,5 +1,5 @@
'use strict' 'use strict';
const { nativeImage } = process.electronBinding('native_image') const { nativeImage } = process.electronBinding('native_image');
module.exports = nativeImage module.exports = nativeImage;

View file

@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('shell') module.exports = process.electronBinding('shell');

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
'use strict' 'use strict';
/* global require */ /* global require */
// Monkey-patch the fs module. // Monkey-patch the fs module.
require('electron/js2c/asar').wrapFsWithAsar(require('fs')) require('electron/js2c/asar').wrapFsWithAsar(require('fs'));

View file

@ -1,19 +1,19 @@
'use strict' 'use strict';
const binding = process.electronBinding('crash_reporter') const binding = process.electronBinding('crash_reporter');
class CrashReporter { class CrashReporter {
constructor () { constructor () {
this.productName = null this.productName = null;
this.crashesDirectory = null this.crashesDirectory = null;
} }
init (options) { init (options) {
throw new Error('Not implemented') throw new Error('Not implemented');
} }
start (options) { start (options) {
if (options == null) options = {} if (options == null) options = {};
const { const {
productName, productName,
@ -22,77 +22,77 @@ class CrashReporter {
ignoreSystemCrashHandler = false, ignoreSystemCrashHandler = false,
submitURL, submitURL,
uploadToServer = true uploadToServer = true
} = options } = options;
if (companyName == null) throw new Error('companyName is a required option to crashReporter.start') if (companyName == null) throw new Error('companyName is a required option to crashReporter.start');
if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start') if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start');
const ret = this.init({ const ret = this.init({
submitURL, submitURL,
productName productName
}) });
this.productName = ret.productName this.productName = ret.productName;
this.crashesDirectory = ret.crashesDirectory this.crashesDirectory = ret.crashesDirectory;
if (extra._productName == null) extra._productName = ret.productName if (extra._productName == null) extra._productName = ret.productName;
if (extra._companyName == null) extra._companyName = companyName if (extra._companyName == null) extra._companyName = companyName;
if (extra._version == null) extra._version = ret.appVersion if (extra._version == null) extra._version = ret.appVersion;
binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra) binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra);
} }
getLastCrashReport () { getLastCrashReport () {
const reports = this.getUploadedReports() const reports = this.getUploadedReports()
.sort((a, b) => { .sort((a, b) => {
const ats = (a && a.date) ? new Date(a.date).getTime() : 0 const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
const bts = (b && b.date) ? new Date(b.date).getTime() : 0 const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
return bts - ats return bts - ats;
}) });
return (reports.length > 0) ? reports[0] : null return (reports.length > 0) ? reports[0] : null;
} }
getUploadedReports () { getUploadedReports () {
const crashDir = this.getCrashesDirectory() const crashDir = this.getCrashesDirectory();
if (!crashDir) { if (!crashDir) {
throw new Error('crashReporter has not been started') throw new Error('crashReporter has not been started');
} }
return binding.getUploadedReports(crashDir) return binding.getUploadedReports(crashDir);
} }
getCrashesDirectory () { getCrashesDirectory () {
return this.crashesDirectory return this.crashesDirectory;
} }
getUploadToServer () { getUploadToServer () {
if (process.type === 'browser') { if (process.type === 'browser') {
return binding.getUploadToServer() return binding.getUploadToServer();
} else { } else {
throw new Error('getUploadToServer can only be called from the main process') throw new Error('getUploadToServer can only be called from the main process');
} }
} }
setUploadToServer (uploadToServer) { setUploadToServer (uploadToServer) {
if (process.type === 'browser') { if (process.type === 'browser') {
return binding.setUploadToServer(uploadToServer) return binding.setUploadToServer(uploadToServer);
} else { } else {
throw new Error('setUploadToServer can only be called from the main process') throw new Error('setUploadToServer can only be called from the main process');
} }
} }
addExtraParameter (key, value) { addExtraParameter (key, value) {
binding.addExtraParameter(key, value) binding.addExtraParameter(key, value);
} }
removeExtraParameter (key) { removeExtraParameter (key) {
binding.removeExtraParameter(key) binding.removeExtraParameter(key);
} }
getParameters () { getParameters () {
return binding.getParameters() return binding.getParameters();
} }
} }
module.exports = CrashReporter module.exports = CrashReporter;

View file

@ -1,17 +1,17 @@
const handleESModule = (loader: ElectronInternal.ModuleLoader) => () => { const handleESModule = (loader: ElectronInternal.ModuleLoader) => () => {
const value = loader() const value = loader();
if (value.__esModule && value.default) return value.default if (value.__esModule && value.default) return value.default;
return value return value;
} };
// Attaches properties to |targetExports|. // Attaches properties to |targetExports|.
export function defineProperties (targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) { export function defineProperties (targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) {
const descriptors: PropertyDescriptorMap = {} const descriptors: PropertyDescriptorMap = {};
for (const module of moduleList) { for (const module of moduleList) {
descriptors[module.name] = { descriptors[module.name] = {
enumerable: !module.private, enumerable: !module.private,
get: handleESModule(module.loader) get: handleESModule(module.loader)
} };
} }
return Object.defineProperties(targetExports, descriptors) return Object.defineProperties(targetExports, descriptors);
} }

View file

@ -1,13 +1,13 @@
export function electronBindingSetup (binding: typeof process['_linkedBinding'], processType: typeof process['type']): typeof process['electronBinding'] { export function electronBindingSetup (binding: typeof process['_linkedBinding'], processType: typeof process['type']): typeof process['electronBinding'] {
return function electronBinding (name: string) { return function electronBinding (name: string) {
try { try {
return binding(`electron_${processType}_${name}`) return binding(`electron_${processType}_${name}`);
} catch (error) { } catch (error) {
if (/No such module/.test(error.message)) { if (/No such module/.test(error.message)) {
return binding(`electron_common_${name}`) return binding(`electron_common_${name}`);
} else { } else {
throw error throw error;
} }
} }
} };
} }

View file

@ -1,10 +1,10 @@
import * as util from 'util' import * as util from 'util';
import { electronBindingSetup } from '@electron/internal/common/electron-binding-setup' import { electronBindingSetup } from '@electron/internal/common/electron-binding-setup';
const timers = require('timers') const timers = require('timers');
process.electronBinding = electronBindingSetup(process._linkedBinding, process.type) process.electronBinding = electronBindingSetup(process._linkedBinding, process.type);
type AnyFn = (...args: any[]) => any type AnyFn = (...args: any[]) => any
@ -17,11 +17,11 @@ type AnyFn = (...args: any[]) => any
const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T { const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
return wrap(func, function (func) { return wrap(func, function (func) {
return function (this: any, ...args: any[]) { return function (this: any, ...args: any[]) {
process.activateUvLoop() process.activateUvLoop();
return func.apply(this, args) return func.apply(this, args);
} };
}) as T }) as T;
} };
/** /**
* Casts to any below for func are due to Typescript not supporting symbols * Casts to any below for func are due to Typescript not supporting symbols
@ -30,41 +30,41 @@ const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
* Refs: https://github.com/Microsoft/TypeScript/issues/1863 * Refs: https://github.com/Microsoft/TypeScript/issues/1863
*/ */
function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) { function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
const wrapped = wrapper(func) const wrapped = wrapper(func);
if ((func as any)[util.promisify.custom]) { if ((func as any)[util.promisify.custom]) {
(wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom]) (wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom]);
} }
return wrapped return wrapped;
} }
process.nextTick = wrapWithActivateUvLoop(process.nextTick) process.nextTick = wrapWithActivateUvLoop(process.nextTick);
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.clearImmediate = timers.clearImmediate global.clearImmediate = timers.clearImmediate;
// setTimeout needs to update the polling timeout of the event loop, when // setTimeout needs to update the polling timeout of the event loop, when
// called under Chromium's event loop the node's event loop won't get a chance // called under Chromium's event loop the node's event loop won't get a chance
// to update the timeout, so we have to force the node's event loop to // to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in browser process. // recalculate the timeout in browser process.
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval) timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// Only override the global setTimeout/setInterval impls in the browser process // Only override the global setTimeout/setInterval impls in the browser process
if (process.type === 'browser') { if (process.type === 'browser') {
global.setTimeout = timers.setTimeout global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval global.setInterval = timers.setInterval;
} }
if (process.platform === 'win32') { if (process.platform === 'win32') {
// Always returns EOF for stdin stream. // Always returns EOF for stdin stream.
const { Readable } = require('stream') const { Readable } = require('stream');
const stdin = new Readable() const stdin = new Readable();
stdin.push(null) stdin.push(null);
Object.defineProperty(process, 'stdin', { Object.defineProperty(process, 'stdin', {
configurable: false, configurable: false,
enumerable: true, enumerable: true,
get () { get () {
return stdin return stdin;
} }
}) });
} }

View file

@ -1,21 +1,21 @@
'use strict' 'use strict';
// parses a feature string that has the format used in window.open() // parses a feature string that has the format used in window.open()
// - `features` input string // - `features` input string
// - `emit` function(key, value) - called for each parsed KV // - `emit` function(key, value) - called for each parsed KV
module.exports = function parseFeaturesString (features, emit) { module.exports = function parseFeaturesString (features, emit) {
features = `${features}`.trim() features = `${features}`.trim();
// split the string by ',' // split the string by ','
features.split(/\s*,\s*/).forEach((feature) => { features.split(/\s*,\s*/).forEach((feature) => {
// expected form is either a key by itself or a key/value pair in the form of // expected form is either a key by itself or a key/value pair in the form of
// 'key=value' // 'key=value'
let [key, value] = feature.split(/\s*=\s*/) let [key, value] = feature.split(/\s*=\s*/);
if (!key) return if (!key) return;
// interpret the value as a boolean, if possible // interpret the value as a boolean, if possible
value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value;
// emit the parsed pair // emit the parsed pair
emit(key, value) emit(key, value);
}) });
} };

View file

@ -1,42 +1,42 @@
import * as path from 'path' import * as path from 'path';
const Module = require('module') const Module = require('module');
// Clear Node's global search paths. // Clear Node's global search paths.
Module.globalPaths.length = 0 Module.globalPaths.length = 0;
// Prevent Node from adding paths outside this app to search paths. // Prevent Node from adding paths outside this app to search paths.
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep;
const originalNodeModulePaths = Module._nodeModulePaths const originalNodeModulePaths = Module._nodeModulePaths;
Module._nodeModulePaths = function (from: string) { Module._nodeModulePaths = function (from: string) {
const paths: string[] = originalNodeModulePaths(from) const paths: string[] = originalNodeModulePaths(from);
const fromPath = path.resolve(from) + path.sep const fromPath = path.resolve(from) + path.sep;
// If "from" is outside the app then we do nothing. // If "from" is outside the app then we do nothing.
if (fromPath.startsWith(resourcesPathWithTrailingSlash)) { if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
return paths.filter(function (candidate) { return paths.filter(function (candidate) {
return candidate.startsWith(resourcesPathWithTrailingSlash) return candidate.startsWith(resourcesPathWithTrailingSlash);
}) });
} else { } else {
return paths return paths;
} }
} };
// Make a fake Electron module that we will insert into the module cache // Make a fake Electron module that we will insert into the module cache
const electronModule = new Module('electron', null) const electronModule = new Module('electron', null);
electronModule.id = 'electron' electronModule.id = 'electron';
electronModule.loaded = true electronModule.loaded = true;
electronModule.filename = 'electron' electronModule.filename = 'electron';
Object.defineProperty(electronModule, 'exports', { Object.defineProperty(electronModule, 'exports', {
get: () => require('electron') get: () => require('electron')
}) });
Module._cache.electron = electronModule Module._cache.electron = electronModule;
const originalResolveFilename = Module._resolveFilename const originalResolveFilename = Module._resolveFilename;
Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean) { Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean) {
if (request === 'electron') { if (request === 'electron') {
return 'electron' return 'electron';
} else { } else {
return originalResolveFilename(request, parent, isMain) return originalResolveFilename(request, parent, isMain);
} }
} };

View file

@ -1,4 +1,4 @@
const { nativeImage, NativeImage } = process.electronBinding('native_image') const { nativeImage, NativeImage } = process.electronBinding('native_image');
export function isPromise (val: any) { export function isPromise (val: any) {
return ( return (
@ -10,7 +10,7 @@ export function isPromise (val: any) {
val.constructor.reject instanceof Function && val.constructor.reject instanceof Function &&
val.constructor.resolve && val.constructor.resolve &&
val.constructor.resolve instanceof Function val.constructor.resolve instanceof Function
) );
} }
const serializableTypes = [ const serializableTypes = [
@ -21,17 +21,17 @@ const serializableTypes = [
Error, Error,
RegExp, RegExp,
ArrayBuffer ArrayBuffer
] ];
export function isSerializableObject (value: any) { export function isSerializableObject (value: any) {
return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type) return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type);
} }
const objectMap = function (source: Object, mapper: (value: any) => any) { const objectMap = function (source: Object, mapper: (value: any) => any) {
const sourceEntries = Object.entries(source) const sourceEntries = Object.entries(source);
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]) const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]);
return Object.fromEntries(targetEntries) return Object.fromEntries(targetEntries);
} };
export function serialize (value: any): any { export function serialize (value: any): any {
if (value instanceof NativeImage) { if (value instanceof NativeImage) {
@ -39,28 +39,28 @@ export function serialize (value: any): any {
buffer: value.toBitmap(), buffer: value.toBitmap(),
size: value.getSize(), size: value.getSize(),
__ELECTRON_SERIALIZED_NativeImage__: true __ELECTRON_SERIALIZED_NativeImage__: true
} };
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
return value.map(serialize) return value.map(serialize);
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
return value return value;
} else if (value instanceof Object) { } else if (value instanceof Object) {
return objectMap(value, serialize) return objectMap(value, serialize);
} else { } else {
return value return value;
} }
} }
export function deserialize (value: any): any { export function deserialize (value: any): any {
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) { if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
return nativeImage.createFromBitmap(value.buffer, value.size) return nativeImage.createFromBitmap(value.buffer, value.size);
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
return value.map(deserialize) return value.map(deserialize);
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
return value return value;
} else if (value instanceof Object) { } else if (value instanceof Object) {
return objectMap(value, deserialize) return objectMap(value, deserialize);
} else { } else {
return value return value;
} }
} }

View file

@ -48,7 +48,7 @@ export const syncMethods = new Set([
'getZoomLevel', 'getZoomLevel',
'setZoomFactor', 'setZoomFactor',
'setZoomLevel' 'setZoomLevel'
]) ]);
export const properties = new Set([ export const properties = new Set([
'audioMuted', 'audioMuted',
@ -56,7 +56,7 @@ export const properties = new Set([
'zoomLevel', 'zoomLevel',
'zoomFactor', 'zoomFactor',
'frameRate' 'frameRate'
]) ]);
export const asyncMethods = new Set([ export const asyncMethods = new Set([
'loadURL', 'loadURL',
@ -70,4 +70,4 @@ export const asyncMethods = new Set([
'setVisualZoomLevelLimits', 'setVisualZoomLevelLimits',
'print', 'print',
'printToPDF' 'printToPDF'
]) ]);

View file

@ -5,4 +5,4 @@
// //
// Will mutate this captured one as well and that is OK. // Will mutate this captured one as well and that is OK.
export const Promise = global.Promise export const Promise = global.Promise;

View file

@ -1,37 +1,37 @@
'use strict' 'use strict';
/* global nodeProcess, isolatedWorld, worldId */ /* global nodeProcess, isolatedWorld, worldId */
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer') process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the // The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the
// "ipc-internal" hidden value // "ipc-internal" hidden value
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal')) 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 process object created by webpack is not an event emitter, fix it so
// the API is more compatible with non-sandboxed renderers. // the API is more compatible with non-sandboxed renderers.
for (const prop of Object.keys(EventEmitter.prototype)) { for (const prop of Object.keys(EventEmitter.prototype)) {
if (Object.prototype.hasOwnProperty.call(process, prop)) { if (Object.prototype.hasOwnProperty.call(process, prop)) {
delete process[prop] delete process[prop];
} }
} }
Object.setPrototypeOf(process, EventEmitter.prototype) Object.setPrototypeOf(process, EventEmitter.prototype);
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args') const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args');
if (isolatedWorldArgs) { if (isolatedWorldArgs) {
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } = isolatedWorldArgs const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } = isolatedWorldArgs;
const { windowSetup } = require('@electron/internal/renderer/window-setup') const { windowSetup } = require('@electron/internal/renderer/window-setup');
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled) windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled);
} }
const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`) const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`);
if (extensionId) { if (extensionId) {
const chromeAPI = require('@electron/internal/renderer/chrome-api') const chromeAPI = require('@electron/internal/renderer/chrome-api');
chromeAPI.injectTo(extensionId, window) chromeAPI.injectTo(extensionId, window);
} }

View file

@ -1,27 +1,27 @@
'use strict' 'use strict';
/* global nodeProcess, isolatedWorld */ /* global nodeProcess, isolatedWorld */
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer') process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the // The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the
// "ipc-internal" hidden value // "ipc-internal" hidden value
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal')) v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal'));
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl') const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl');
if (webViewImpl) { if (webViewImpl) {
// Must setup the WebView element in main world. // Must setup the WebView element in main world.
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element');
setupWebView(v8Util, webViewImpl) setupWebView(v8Util, webViewImpl);
} }
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args') const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args');
if (isolatedWorldArgs) { if (isolatedWorldArgs) {
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } = isolatedWorldArgs const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } = isolatedWorldArgs;
const { windowSetup } = require('@electron/internal/renderer/window-setup') const { windowSetup } = require('@electron/internal/renderer/window-setup');
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled) windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled);
} }

View file

@ -1,20 +1,20 @@
const { hasSwitch } = process.electronBinding('command_line') const { hasSwitch } = process.electronBinding('command_line');
const binding = process.electronBinding('context_bridge') const binding = process.electronBinding('context_bridge');
const contextIsolationEnabled = hasSwitch('context-isolation') const contextIsolationEnabled = hasSwitch('context-isolation');
const checkContextIsolationEnabled = () => { const checkContextIsolationEnabled = () => {
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled') if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled');
} };
const contextBridge = { const contextBridge = {
exposeInMainWorld: (key: string, api: Record<string, any>) => { exposeInMainWorld: (key: string, api: Record<string, any>) => {
checkContextIsolationEnabled() checkContextIsolationEnabled();
return binding.exposeAPIInMainWorld(key, api) return binding.exposeAPIInMainWorld(key, api);
}, },
debugGC: () => binding._debugGCMaps({}) debugGC: () => binding._debugGCMaps({})
} };
if (!binding._debugGCMaps) delete contextBridge.debugGC if (!binding._debugGCMaps) delete contextBridge.debugGC;
export default contextBridge export default contextBridge;

View file

@ -1,12 +1,12 @@
'use strict' 'use strict';
const CrashReporter = require('@electron/internal/common/crash-reporter') const CrashReporter = require('@electron/internal/common/crash-reporter');
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
class CrashReporterRenderer extends CrashReporter { class CrashReporterRenderer extends CrashReporter {
init (options) { init (options) {
return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options) return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options);
} }
} }
module.exports = new CrashReporterRenderer() module.exports = new CrashReporterRenderer();

View file

@ -1,39 +1,39 @@
import { nativeImage } from 'electron' import { nativeImage } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const { hasSwitch } = process.electronBinding('command_line') const { hasSwitch } = process.electronBinding('command_line');
// |options.types| can't be empty and must be an array // |options.types| can't be empty and must be an array
function isValid (options: Electron.SourcesOptions) { function isValid (options: Electron.SourcesOptions) {
const types = options ? options.types : undefined const types = options ? options.types : undefined;
return Array.isArray(types) return Array.isArray(types);
} }
const enableStacks = hasSwitch('enable-api-filtering-logging') const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () { function getCurrentStack () {
const target = {} const target = {};
if (enableStacks) { if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack) Error.captureStackTrace(target, getCurrentStack);
} }
return (target as any).stack return (target as any).stack;
} }
export async function getSources (options: Electron.SourcesOptions) { export async function getSources (options: Electron.SourcesOptions) {
if (!isValid(options)) throw new Error('Invalid options') if (!isValid(options)) throw new Error('Invalid options');
const captureWindow = options.types.includes('window') const captureWindow = options.types.includes('window');
const captureScreen = options.types.includes('screen') const captureScreen = options.types.includes('screen');
const { thumbnailSize = { width: 150, height: 150 } } = options const { thumbnailSize = { width: 150, height: 150 } } = options;
const { fetchWindowIcons = false } = options const { fetchWindowIcons = false } = options;
const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', { const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
captureWindow, captureWindow,
captureScreen, captureScreen,
thumbnailSize, thumbnailSize,
fetchWindowIcons fetchWindowIcons
} as ElectronInternal.GetSourcesOptions, getCurrentStack()) } as ElectronInternal.GetSourcesOptions, getCurrentStack());
return sources.map(source => ({ return sources.map(source => ({
id: source.id, id: source.id,
@ -41,5 +41,5 @@ export async function getSources (options: Electron.SourcesOptions) {
thumbnail: nativeImage.createFromDataURL(source.thumbnail), thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id, display_id: source.display_id,
appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null
})) }));
} }

View file

@ -1,8 +1,8 @@
import { defineProperties } from '@electron/internal/common/define-properties' import { defineProperties } from '@electron/internal/common/define-properties';
import { commonModuleList } from '@electron/internal/common/api/module-list' import { commonModuleList } from '@electron/internal/common/api/module-list';
import { rendererModuleList } from '@electron/internal/renderer/api/module-list' import { rendererModuleList } from '@electron/internal/renderer/api/module-list';
module.exports = {} module.exports = {};
defineProperties(module.exports, commonModuleList) defineProperties(module.exports, commonModuleList);
defineProperties(module.exports, rendererModuleList) defineProperties(module.exports, rendererModuleList);

View file

@ -1,36 +1,36 @@
const { ipc } = process.electronBinding('ipc') const { ipc } = process.electronBinding('ipc');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
// Created by init.js. // Created by init.js.
const ipcRenderer = v8Util.getHiddenValue<Electron.IpcRenderer>(global, 'ipc') const ipcRenderer = v8Util.getHiddenValue<Electron.IpcRenderer>(global, 'ipc');
const internal = false const internal = false;
ipcRenderer.send = function (channel, ...args) { ipcRenderer.send = function (channel, ...args) {
return ipc.send(internal, channel, args) return ipc.send(internal, channel, args);
} };
ipcRenderer.sendSync = function (channel, ...args) { ipcRenderer.sendSync = function (channel, ...args) {
return ipc.sendSync(internal, channel, args)[0] return ipc.sendSync(internal, channel, args)[0];
} };
ipcRenderer.sendToHost = function (channel, ...args) { ipcRenderer.sendToHost = function (channel, ...args) {
return ipc.sendToHost(channel, args) return ipc.sendToHost(channel, args);
} };
ipcRenderer.sendTo = function (webContentsId, channel, ...args) { ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
return ipc.sendTo(internal, false, webContentsId, channel, args) return ipc.sendTo(internal, false, webContentsId, channel, args);
} };
ipcRenderer.invoke = async function (channel, ...args) { ipcRenderer.invoke = async function (channel, ...args) {
const { error, result } = await ipc.invoke(internal, channel, args) const { error, result } = await ipc.invoke(internal, channel, args);
if (error) { if (error) {
throw new Error(`Error invoking remote method '${channel}': ${error}`) throw new Error(`Error invoking remote method '${channel}': ${error}`);
} }
return result return result;
} };
ipcRenderer.postMessage = function (channel: string, message: any, transferables: any) { ipcRenderer.postMessage = function (channel: string, message: any, transferables: any) {
return ipc.postMessage(channel, message, transferables) return ipc.postMessage(channel, message, transferables);
} };
export default ipcRenderer export default ipcRenderer;

View file

@ -1,7 +1,7 @@
const features = process.electronBinding('features') const features = process.electronBinding('features');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const enableRemoteModule = v8Util.getHiddenValue<boolean>(global, 'enableRemoteModule') const enableRemoteModule = v8Util.getHiddenValue<boolean>(global, 'enableRemoteModule');
// Renderer side modules, please sort alphabetically. // Renderer side modules, please sort alphabetically.
export const rendererModuleList: ElectronInternal.ModuleEntry[] = [ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
@ -9,12 +9,12 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'crashReporter', loader: () => require('./crash-reporter') }, { name: 'crashReporter', loader: () => require('./crash-reporter') },
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') }, { name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
{ name: 'webFrame', loader: () => require('./web-frame') } { name: 'webFrame', loader: () => require('./web-frame') }
] ];
if (features.isDesktopCapturerEnabled()) { if (features.isDesktopCapturerEnabled()) {
rendererModuleList.push({ name: 'desktopCapturer', loader: () => require('./desktop-capturer') }) rendererModuleList.push({ name: 'desktopCapturer', loader: () => require('./desktop-capturer') });
} }
if (features.isRemoteModuleEnabled() && enableRemoteModule) { if (features.isRemoteModuleEnabled() && enableRemoteModule) {
rendererModuleList.push({ name: 'remote', loader: () => require('./remote') }) rendererModuleList.push({ name: 'remote', loader: () => require('./remote') });
} }

View file

@ -1,26 +1,26 @@
'use strict' 'use strict';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const { hasSwitch } = process.electronBinding('command_line') const { hasSwitch } = process.electronBinding('command_line');
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry') const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils') const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const callbacksRegistry = new CallbacksRegistry() const callbacksRegistry = new CallbacksRegistry();
const remoteObjectCache = v8Util.createIDWeakMap() const remoteObjectCache = v8Util.createIDWeakMap();
// An unique ID that can represent current context. // An unique ID that can represent current context.
const contextId = v8Util.getHiddenValue(global, 'contextId') const contextId = v8Util.getHiddenValue(global, 'contextId');
// Notify the main process when current context is going to be released. // Notify the main process when current context is going to be released.
// Note that when the renderer process is destroyed, the message may not be // Note that when the renderer process is destroyed, the message may not be
// sent, we also listen to the "render-view-deleted" event in the main process // sent, we also listen to the "render-view-deleted" event in the main process
// to guard that situation. // to guard that situation.
process.on('exit', () => { process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE';
ipcRendererInternal.send(command, contextId) ipcRendererInternal.send(command, contextId);
}) });
// Convert the arguments object into an array of meta data. // Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) { function wrapArgs (args, visited = new Set()) {
@ -30,182 +30,182 @@ function wrapArgs (args, visited = new Set()) {
return { return {
type: 'value', type: 'value',
value: null value: null
} };
} }
if (Array.isArray(value)) { if (Array.isArray(value)) {
visited.add(value) visited.add(value);
const meta = { const meta = {
type: 'array', type: 'array',
value: wrapArgs(value, visited) value: wrapArgs(value, visited)
} };
visited.delete(value) visited.delete(value);
return meta return meta;
} else if (value instanceof Buffer) { } else if (value instanceof Buffer) {
return { return {
type: 'buffer', type: 'buffer',
value value
} };
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
return { return {
type: 'value', type: 'value',
value value
} };
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
if (isPromise(value)) { if (isPromise(value)) {
return { return {
type: 'promise', type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) { then: valueToMeta(function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected) value.then(onFulfilled, onRejected);
}) })
} };
} else if (v8Util.getHiddenValue(value, 'atomId')) { } else if (v8Util.getHiddenValue(value, 'atomId')) {
return { return {
type: 'remote-object', type: 'remote-object',
id: v8Util.getHiddenValue(value, 'atomId') id: v8Util.getHiddenValue(value, 'atomId')
} };
} }
const meta = { const meta = {
type: 'object', type: 'object',
name: value.constructor ? value.constructor.name : '', name: value.constructor ? value.constructor.name : '',
members: [] members: []
} };
visited.add(value) visited.add(value);
for (const prop in value) { // eslint-disable-line guard-for-in for (const prop in value) { // eslint-disable-line guard-for-in
meta.members.push({ meta.members.push({
name: prop, name: prop,
value: valueToMeta(value[prop]) value: valueToMeta(value[prop])
}) });
} }
visited.delete(value) visited.delete(value);
return meta return meta;
} else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
return { return {
type: 'function-with-return-value', type: 'function-with-return-value',
value: valueToMeta(value()) value: valueToMeta(value())
} };
} else if (typeof value === 'function') { } else if (typeof value === 'function') {
return { return {
type: 'function', type: 'function',
id: callbacksRegistry.add(value), id: callbacksRegistry.add(value),
location: v8Util.getHiddenValue(value, 'location'), location: v8Util.getHiddenValue(value, 'location'),
length: value.length length: value.length
} };
} else { } else {
return { return {
type: 'value', type: 'value',
value value
} };
} }
} };
return args.map(valueToMeta) return args.map(valueToMeta);
} }
// Populate object's members from descriptors. // Populate object's members from descriptors.
// The |ref| will be kept referenced by |members|. // The |ref| will be kept referenced by |members|.
// This matches |getObjectMemebers| in rpc-server. // This matches |getObjectMemebers| in rpc-server.
function setObjectMembers (ref, object, metaId, members) { function setObjectMembers (ref, object, metaId, members) {
if (!Array.isArray(members)) return if (!Array.isArray(members)) return;
for (const member of members) { for (const member of members) {
if (Object.prototype.hasOwnProperty.call(object, member.name)) continue if (Object.prototype.hasOwnProperty.call(object, member.name)) continue;
const descriptor = { enumerable: member.enumerable } const descriptor = { enumerable: member.enumerable };
if (member.type === 'method') { if (member.type === 'method') {
const remoteMemberFunction = function (...args) { const remoteMemberFunction = function (...args) {
let command let command;
if (this && this.constructor === remoteMemberFunction) { if (this && this.constructor === remoteMemberFunction) {
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR' command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
} else { } else {
command = 'ELECTRON_BROWSER_MEMBER_CALL' command = 'ELECTRON_BROWSER_MEMBER_CALL';
} }
const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args)) const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
return metaToValue(ret) return metaToValue(ret);
} };
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name) let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name);
descriptor.get = () => { descriptor.get = () => {
descriptorFunction.ref = ref // The member should reference its object. descriptorFunction.ref = ref; // The member should reference its object.
return descriptorFunction return descriptorFunction;
} };
// Enable monkey-patch the method // Enable monkey-patch the method
descriptor.set = (value) => { descriptor.set = (value) => {
descriptorFunction = value descriptorFunction = value;
return value return value;
} };
descriptor.configurable = true descriptor.configurable = true;
} else if (member.type === 'get') { } else if (member.type === 'get') {
descriptor.get = () => { descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_GET' const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name) const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name);
return metaToValue(meta) return metaToValue(meta);
} };
if (member.writable) { if (member.writable) {
descriptor.set = (value) => { descriptor.set = (value) => {
const args = wrapArgs([value]) const args = wrapArgs([value]);
const command = 'ELECTRON_BROWSER_MEMBER_SET' const command = 'ELECTRON_BROWSER_MEMBER_SET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args) const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args);
if (meta != null) metaToValue(meta) if (meta != null) metaToValue(meta);
return value return value;
} };
} }
} }
Object.defineProperty(object, member.name, descriptor) Object.defineProperty(object, member.name, descriptor);
} }
} }
// Populate object's prototype from descriptor. // Populate object's prototype from descriptor.
// This matches |getObjectPrototype| in rpc-server. // This matches |getObjectPrototype| in rpc-server.
function setObjectPrototype (ref, object, metaId, descriptor) { function setObjectPrototype (ref, object, metaId, descriptor) {
if (descriptor === null) return if (descriptor === null) return;
const proto = {} const proto = {};
setObjectMembers(ref, proto, metaId, descriptor.members) setObjectMembers(ref, proto, metaId, descriptor.members);
setObjectPrototype(ref, proto, metaId, descriptor.proto) setObjectPrototype(ref, proto, metaId, descriptor.proto);
Object.setPrototypeOf(object, proto) Object.setPrototypeOf(object, proto);
} }
// Wrap function in Proxy for accessing remote properties // Wrap function in Proxy for accessing remote properties
function proxyFunctionProperties (remoteMemberFunction, metaId, name) { function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
let loaded = false let loaded = false;
// Lazily load function properties // Lazily load function properties
const loadRemoteProperties = () => { const loadRemoteProperties = () => {
if (loaded) return if (loaded) return;
loaded = true loaded = true;
const command = 'ELECTRON_BROWSER_MEMBER_GET' const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name) const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name);
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
} };
return new Proxy(remoteMemberFunction, { return new Proxy(remoteMemberFunction, {
set: (target, property, value, receiver) => { set: (target, property, value, receiver) => {
if (property !== 'ref') loadRemoteProperties() if (property !== 'ref') loadRemoteProperties();
target[property] = value target[property] = value;
return true return true;
}, },
get: (target, property, receiver) => { get: (target, property, receiver) => {
if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties() if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties();
const value = target[property] const value = target[property];
if (property === 'toString' && typeof value === 'function') { if (property === 'toString' && typeof value === 'function') {
return value.bind(target) return value.bind(target);
} }
return value return value;
}, },
ownKeys: (target) => { ownKeys: (target) => {
loadRemoteProperties() loadRemoteProperties();
return Object.getOwnPropertyNames(target) return Object.getOwnPropertyNames(target);
}, },
getOwnPropertyDescriptor: (target, property) => { getOwnPropertyDescriptor: (target, property) => {
const descriptor = Object.getOwnPropertyDescriptor(target, property) const descriptor = Object.getOwnPropertyDescriptor(target, property);
if (descriptor) return descriptor if (descriptor) return descriptor;
loadRemoteProperties() loadRemoteProperties();
return Object.getOwnPropertyDescriptor(target, property) return Object.getOwnPropertyDescriptor(target, property);
} }
}) });
} }
// Convert meta data from browser into real value. // Convert meta data from browser into real value.
@ -216,143 +216,143 @@ function metaToValue (meta) {
buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength), buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }), promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToError(meta), error: () => metaToError(meta),
exception: () => { throw metaToError(meta.value) } exception: () => { throw metaToError(meta.value); }
} };
if (Object.prototype.hasOwnProperty.call(types, meta.type)) { if (Object.prototype.hasOwnProperty.call(types, meta.type)) {
return types[meta.type]() return types[meta.type]();
} else { } else {
let ret let ret;
if (remoteObjectCache.has(meta.id)) { if (remoteObjectCache.has(meta.id)) {
v8Util.addRemoteObjectRef(contextId, meta.id) v8Util.addRemoteObjectRef(contextId, meta.id);
return remoteObjectCache.get(meta.id) return remoteObjectCache.get(meta.id);
} }
// A shadow class to represent the remote function object. // A shadow class to represent the remote function object.
if (meta.type === 'function') { if (meta.type === 'function') {
const remoteFunction = function (...args) { const remoteFunction = function (...args) {
let command let command;
if (this && this.constructor === remoteFunction) { if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR' command = 'ELECTRON_BROWSER_CONSTRUCTOR';
} else { } else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL' command = 'ELECTRON_BROWSER_FUNCTION_CALL';
} }
const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args)) const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args));
return metaToValue(obj) return metaToValue(obj);
} };
ret = remoteFunction ret = remoteFunction;
} else { } else {
ret = {} ret = {};
} }
setObjectMembers(ret, ret, meta.id, meta.members) setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto) setObjectPrototype(ret, ret, meta.id, meta.proto);
Object.defineProperty(ret.constructor, 'name', { value: meta.name }) Object.defineProperty(ret.constructor, 'name', { value: meta.name });
// Track delegate obj's lifetime & tell browser to clean up when object is GCed. // Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, contextId, meta.id) v8Util.setRemoteObjectFreer(ret, contextId, meta.id);
v8Util.setHiddenValue(ret, 'atomId', meta.id) v8Util.setHiddenValue(ret, 'atomId', meta.id);
v8Util.addRemoteObjectRef(contextId, meta.id) v8Util.addRemoteObjectRef(contextId, meta.id);
remoteObjectCache.set(meta.id, ret) remoteObjectCache.set(meta.id, ret);
return ret return ret;
} }
} }
function metaToError (meta) { function metaToError (meta) {
const obj = meta.value const obj = meta.value;
for (const { name, value } of meta.members) { for (const { name, value } of meta.members) {
obj[name] = metaToValue(value) obj[name] = metaToValue(value);
} }
return obj return obj;
} }
function handleMessage (channel, handler) { function handleMessage (channel, handler) {
ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => { ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
if (passedContextId === contextId) { if (passedContextId === contextId) {
handler(id, ...args) handler(id, ...args);
} else { } else {
// Message sent to an un-exist context, notify the error to main process. // Message sent to an un-exist context, notify the error to main process.
ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id) ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id);
} }
}) });
} }
const enableStacks = hasSwitch('enable-api-filtering-logging') const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () { function getCurrentStack () {
const target = {} const target = {};
if (enableStacks) { if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack) Error.captureStackTrace(target, getCurrentStack);
} }
return target.stack return target.stack;
} }
// Browser calls a callback in renderer. // Browser calls a callback in renderer.
handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => { handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
callbacksRegistry.apply(id, metaToValue(args)) callbacksRegistry.apply(id, metaToValue(args));
}) });
// A callback in browser is released. // A callback in browser is released.
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => { handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
callbacksRegistry.remove(id) callbacksRegistry.remove(id);
}) });
exports.require = (module) => { exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE' const command = 'ELECTRON_BROWSER_REQUIRE';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack()) const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta) return metaToValue(meta);
} };
// Alias to remote.require('electron').xxx. // Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => { exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN' const command = 'ELECTRON_BROWSER_GET_BUILTIN';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack()) const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta) return metaToValue(meta);
} };
exports.getCurrentWindow = () => { exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack()) const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta) return metaToValue(meta);
} };
// Get current WebContents object. // Get current WebContents object.
exports.getCurrentWebContents = () => { exports.getCurrentWebContents = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS' const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack()) const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta) return metaToValue(meta);
} };
// Get a global object in browser. // Get a global object in browser.
exports.getGlobal = (name) => { exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL' const command = 'ELECTRON_BROWSER_GLOBAL';
const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack()) const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack());
return metaToValue(meta) return metaToValue(meta);
} };
// Get the process object in browser. // Get the process object in browser.
Object.defineProperty(exports, 'process', { Object.defineProperty(exports, 'process', {
get: () => exports.getGlobal('process') get: () => exports.getGlobal('process')
}) });
// Create a function that will return the specified value when called in browser. // Create a function that will return the specified value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) => { exports.createFunctionWithReturnValue = (returnValue) => {
const func = () => returnValue const func = () => returnValue;
v8Util.setHiddenValue(func, 'returnValue', true) v8Util.setHiddenValue(func, 'returnValue', true);
return func return func;
} };
const addBuiltinProperty = (name) => { const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, { Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name) get: () => exports.getBuiltin(name)
}) });
} };
const { commonModuleList } = require('@electron/internal/common/api/module-list') const { commonModuleList } = require('@electron/internal/common/api/module-list');
const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys')) const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys'));
// And add a helper receiver for each one. // And add a helper receiver for each one.
browserModules browserModules
.filter((m) => !m.private) .filter((m) => !m.private)
.map((m) => m.name) .map((m) => m.name)
.forEach(addBuiltinProperty) .forEach(addBuiltinProperty);

View file

@ -1,49 +1,49 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const binding = process.electronBinding('web_frame') const binding = process.electronBinding('web_frame');
class WebFrame extends EventEmitter { class WebFrame extends EventEmitter {
constructor (public context: Window) { constructor (public context: Window) {
super() super();
// Lots of webview would subscribe to webFrame's events. // Lots of webview would subscribe to webFrame's events.
this.setMaxListeners(0) this.setMaxListeners(0);
} }
findFrameByRoutingId (...args: Array<any>) { findFrameByRoutingId (...args: Array<any>) {
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args)) return getWebFrame(binding._findFrameByRoutingId(this.context, ...args));
} }
getFrameForSelector (...args: Array<any>) { getFrameForSelector (...args: Array<any>) {
return getWebFrame(binding._getFrameForSelector(this.context, ...args)) return getWebFrame(binding._getFrameForSelector(this.context, ...args));
} }
findFrameByName (...args: Array<any>) { findFrameByName (...args: Array<any>) {
return getWebFrame(binding._findFrameByName(this.context, ...args)) return getWebFrame(binding._findFrameByName(this.context, ...args));
} }
get opener () { get opener () {
return getWebFrame(binding._getOpener(this.context)) return getWebFrame(binding._getOpener(this.context));
} }
get parent () { get parent () {
return getWebFrame(binding._getParent(this.context)) return getWebFrame(binding._getParent(this.context));
} }
get top () { get top () {
return getWebFrame(binding._getTop(this.context)) return getWebFrame(binding._getTop(this.context));
} }
get firstChild () { get firstChild () {
return getWebFrame(binding._getFirstChild(this.context)) return getWebFrame(binding._getFirstChild(this.context));
} }
get nextSibling () { get nextSibling () {
return getWebFrame(binding._getNextSibling(this.context)) return getWebFrame(binding._getNextSibling(this.context));
} }
get routingId () { get routingId () {
return binding._getRoutingId(this.context) return binding._getRoutingId(this.context);
} }
} }
@ -53,17 +53,17 @@ for (const name in binding) {
// TODO(felixrieseberg): Once we can type web_frame natives, we could // TODO(felixrieseberg): Once we can type web_frame natives, we could
// use a neat `keyof` here // use a neat `keyof` here
(WebFrame as any).prototype[name] = function (...args: Array<any>) { (WebFrame as any).prototype[name] = function (...args: Array<any>) {
return binding[name](this.context, ...args) return binding[name](this.context, ...args);
} };
} }
} }
// Helper to return WebFrame or null depending on context. // Helper to return WebFrame or null depending on context.
// TODO(zcbenz): Consider returning same WebFrame for the same frame. // TODO(zcbenz): Consider returning same WebFrame for the same frame.
function getWebFrame (context: Window) { function getWebFrame (context: Window) {
return context ? new WebFrame(context) : null return context ? new WebFrame(context) : null;
} }
const _webFrame = new WebFrame(window) const _webFrame = new WebFrame(window);
export default _webFrame export default _webFrame;

View file

@ -1,14 +1,14 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import * as url from 'url' import * as url from 'url';
import { Event } from '@electron/internal/renderer/extensions/event' import { Event } from '@electron/internal/renderer/extensions/event';
class Tab { class Tab {
public id: number public id: number
constructor (tabId: number) { constructor (tabId: number) {
this.id = tabId this.id = tabId;
} }
} }
@ -18,9 +18,9 @@ class MessageSender {
public url: string public url: string
constructor (tabId: number, extensionId: string) { constructor (tabId: number, extensionId: string) {
this.tab = tabId ? new Tab(tabId) : null this.tab = tabId ? new Tab(tabId) : null;
this.id = extensionId this.id = extensionId;
this.url = `chrome-extension://${extensionId}` this.url = `chrome-extension://${extensionId}`;
} }
} }
@ -31,69 +31,69 @@ class Port {
public sender: MessageSender public sender: MessageSender
constructor (public tabId: number, public portId: number, extensionId: string, public name: string) { constructor (public tabId: number, public portId: number, extensionId: string, public name: string) {
this.onDisconnect = new Event() this.onDisconnect = new Event();
this.onMessage = new Event() this.onMessage = new Event();
this.sender = new MessageSender(tabId, extensionId) this.sender = new MessageSender(tabId, extensionId);
ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => { ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
this._onDisconnect() this._onDisconnect();
}) });
ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, ( ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (
_event: Electron.Event, message: any _event: Electron.Event, message: any
) => { ) => {
const sendResponse = function () { console.error('sendResponse is not implemented') } const sendResponse = function () { console.error('sendResponse is not implemented'); };
this.onMessage.emit(JSON.parse(message), this.sender, sendResponse) this.onMessage.emit(JSON.parse(message), this.sender, sendResponse);
}) });
} }
disconnect () { disconnect () {
if (this.disconnected) return if (this.disconnected) return;
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`) ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`);
this._onDisconnect() this._onDisconnect();
} }
postMessage (message: any) { postMessage (message: any) {
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message)) ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message));
} }
_onDisconnect () { _onDisconnect () {
this.disconnected = true this.disconnected = true;
ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`) ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`);
this.onDisconnect.emit() this.onDisconnect.emit();
} }
} }
// Inject chrome API to the |context| // Inject chrome API to the |context|
export function injectTo (extensionId: string, context: any) { export function injectTo (extensionId: string, context: any) {
if (process.electronBinding('features').isExtensionsEnabled()) { if (process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled') throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled');
} }
const chrome = context.chrome = context.chrome || {} const chrome = context.chrome = context.chrome || {};
ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, ( ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (
_event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string } _event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string }
) => { ) => {
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name)) chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name));
}) });
ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, ( ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (
_event: Electron.Event, tabId: number, message: string _event: Electron.Event, tabId: number, message: string
) => { ) => {
return new Promise(resolve => { return new Promise(resolve => {
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve) chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve);
}) });
}) });
ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => { ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => {
chrome.tabs.onCreated.emit(new Tab(tabId)) chrome.tabs.onCreated.emit(new Tab(tabId));
}) });
ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => { ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => {
chrome.tabs.onRemoved.emit(tabId) chrome.tabs.onRemoved.emit(tabId);
}) });
chrome.runtime = { chrome.runtime = {
id: extensionId, id: extensionId,
@ -105,69 +105,69 @@ export function injectTo (extensionId: string, context: any) {
slashes: true, slashes: true,
hostname: extensionId, hostname: extensionId,
pathname: path pathname: path
}) });
}, },
// https://developer.chrome.com/extensions/runtime#method-getManifest // https://developer.chrome.com/extensions/runtime#method-getManifest
getManifest: function () { getManifest: function () {
const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId) const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId);
return manifest return manifest;
}, },
// https://developer.chrome.com/extensions/runtime#method-connect // https://developer.chrome.com/extensions/runtime#method-connect
connect (...args: Array<any>) { connect (...args: Array<any>) {
// Parse the optional args. // Parse the optional args.
let targetExtensionId = extensionId let targetExtensionId = extensionId;
let connectInfo = { name: '' } let connectInfo = { name: '' };
if (args.length === 1) { if (args.length === 1) {
if (typeof args[0] === 'string') { if (typeof args[0] === 'string') {
targetExtensionId = args[0] targetExtensionId = args[0];
} else { } else {
connectInfo = args[0] connectInfo = args[0];
} }
} else if (args.length === 2) { } else if (args.length === 2) {
[targetExtensionId, connectInfo] = args [targetExtensionId, connectInfo] = args;
} }
const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo) const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo);
return new Port(tabId, portId, extensionId, connectInfo.name) return new Port(tabId, portId, extensionId, connectInfo.name);
}, },
// https://developer.chrome.com/extensions/runtime#method-sendMessage // https://developer.chrome.com/extensions/runtime#method-sendMessage
sendMessage (...args: Array<any>) { sendMessage (...args: Array<any>) {
// Parse the optional args. // Parse the optional args.
const targetExtensionId = extensionId const targetExtensionId = extensionId;
let message: string let message: string;
let options: Object | undefined let options: Object | undefined;
let responseCallback: Chrome.Tabs.SendMessageCallback = () => {} let responseCallback: Chrome.Tabs.SendMessageCallback = () => {};
if (typeof args[args.length - 1] === 'function') { if (typeof args[args.length - 1] === 'function') {
responseCallback = args.pop() responseCallback = args.pop();
} }
if (args.length === 1) { if (args.length === 1) {
[message] = args [message] = args;
} else if (args.length === 2) { } else if (args.length === 2) {
if (typeof args[0] === 'string') { if (typeof args[0] === 'string') {
[extensionId, message] = args [extensionId, message] = args;
} else { } else {
[message, options] = args [message, options] = args;
} }
} else { } else {
[extensionId, message, options] = args [extensionId, message, options] = args;
} }
if (options) { if (options) {
console.error('options are not supported') console.error('options are not supported');
} }
ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback) ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback);
}, },
onConnect: new Event(), onConnect: new Event(),
onMessage: new Event(), onMessage: new Event(),
onInstalled: new Event() onInstalled: new Event()
} };
chrome.tabs = { chrome.tabs = {
// https://developer.chrome.com/extensions/tabs#method-executeScript // https://developer.chrome.com/extensions/tabs#method-executeScript
@ -177,7 +177,7 @@ export function injectTo (extensionId: string, context: any) {
resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {} resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {}
) { ) {
ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details) ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details)
.then((result: any) => resultCallback([result])) .then((result: any) => resultCallback([result]));
}, },
// https://developer.chrome.com/extensions/tabs#method-sendMessage // https://developer.chrome.com/extensions/tabs#method-sendMessage
@ -187,13 +187,13 @@ export function injectTo (extensionId: string, context: any) {
_options: Chrome.Tabs.SendMessageDetails, _options: Chrome.Tabs.SendMessageDetails,
responseCallback: Chrome.Tabs.SendMessageCallback = () => {} responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
) { ) {
ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback) ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback);
}, },
onUpdated: new Event(), onUpdated: new Event(),
onCreated: new Event(), onCreated: new Event(),
onRemoved: new Event() onRemoved: new Event()
} };
chrome.extension = { chrome.extension = {
getURL: chrome.runtime.getURL, getURL: chrome.runtime.getURL,
@ -201,9 +201,9 @@ export function injectTo (extensionId: string, context: any) {
onConnect: chrome.runtime.onConnect, onConnect: chrome.runtime.onConnect,
sendMessage: chrome.runtime.sendMessage, sendMessage: chrome.runtime.sendMessage,
onMessage: chrome.runtime.onMessage onMessage: chrome.runtime.onMessage
} };
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId) chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId);
chrome.pageAction = { chrome.pageAction = {
show () {}, show () {},
@ -213,14 +213,14 @@ export function injectTo (extensionId: string, context: any) {
setIcon () {}, setIcon () {},
setPopup () {}, setPopup () {},
getPopup () {} getPopup () {}
} };
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId) chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId);
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup() 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 // Electron has no concept of a browserAction but we should stub these APIs for compatibility
chrome.browserAction = { chrome.browserAction = {
setIcon () {}, setIcon () {},
setPopup () {} setPopup () {}
} };
} }

View file

@ -1,8 +1,8 @@
import { webFrame } from 'electron' import { webFrame } from 'electron';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const IsolatedWorldIDs = { const IsolatedWorldIDs = {
/** /**
@ -10,93 +10,93 @@ const IsolatedWorldIDs = {
* electron_render_frame_observer.h * electron_render_frame_observer.h
*/ */
ISOLATED_WORLD_EXTENSIONS: 1 << 20 ISOLATED_WORLD_EXTENSIONS: 1 << 20
} };
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS;
const extensionWorldId: {[key: string]: number | undefined} = {} const extensionWorldId: {[key: string]: number | undefined} = {};
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52 // https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
const getIsolatedWorldIdForInstance = () => { const getIsolatedWorldIdForInstance = () => {
// TODO(samuelmaddock): allocate and cleanup IDs // TODO(samuelmaddock): allocate and cleanup IDs
return isolatedWorldIds++ return isolatedWorldIds++;
} };
const escapePattern = function (pattern: string) { const escapePattern = function (pattern: string) {
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&') return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
} };
// Check whether pattern matches. // Check whether pattern matches.
// https://developer.chrome.com/extensions/match_patterns // https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern: string) { const matchesPattern = function (pattern: string) {
if (pattern === '<all_urls>') return true if (pattern === '<all_urls>') return true;
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`) const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
const url = `${location.protocol}//${location.host}${location.pathname}` const url = `${location.protocol}//${location.host}${location.pathname}`;
return url.match(regexp) return url.match(regexp);
} };
// Run the code with chrome API integrated. // Run the code with chrome API integrated.
const runContentScript = function (this: any, extensionId: string, url: string, code: string) { const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
// Assign unique world ID to each extension // Assign unique world ID to each extension
const worldId = extensionWorldId[extensionId] || const worldId = extensionWorldId[extensionId] ||
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance()) (extensionWorldId[extensionId] = getIsolatedWorldIdForInstance());
// store extension ID for content script to read in isolated world // store extension ID for content script to read in isolated world
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId) v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId);
webFrame.setIsolatedWorldInfo(worldId, { webFrame.setIsolatedWorldInfo(worldId, {
name: `${extensionId} [${worldId}]` name: `${extensionId} [${worldId}]`
// TODO(samuelmaddock): read `content_security_policy` from extension manifest // TODO(samuelmaddock): read `content_security_policy` from extension manifest
// csp: manifest.content_security_policy, // csp: manifest.content_security_policy,
}) });
const sources = [{ code, url }] const sources = [{ code, url }];
return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources) return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources);
} };
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) { const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
for (const { url, code } of scripts) { for (const { url, code } of scripts) {
runContentScript.call(window, extensionId, url, code) runContentScript.call(window, extensionId, url, code);
} }
} };
const runStylesheet = function (this: any, url: string, code: string) { const runStylesheet = function (this: any, url: string, code: string) {
webFrame.insertCSS(code) webFrame.insertCSS(code);
} };
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) { const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
for (const { url, code } of css) { for (const { url, code } of css) {
runStylesheet.call(window, url, code) runStylesheet.call(window, url, code);
} }
} };
// Run injected scripts. // Run injected scripts.
// https://developer.chrome.com/extensions/content_scripts // https://developer.chrome.com/extensions/content_scripts
const injectContentScript = function (extensionId: string, script: Electron.ContentScript) { const injectContentScript = function (extensionId: string, script: Electron.ContentScript) {
if (!process.isMainFrame && !script.allFrames) return if (!process.isMainFrame && !script.allFrames) return;
if (!script.matches.some(matchesPattern)) return if (!script.matches.some(matchesPattern)) return;
if (script.js) { if (script.js) {
const fire = runAllContentScript.bind(window, script.js, extensionId) const fire = runAllContentScript.bind(window, script.js, extensionId);
if (script.runAt === 'document_start') { if (script.runAt === 'document_start') {
process.once('document-start', fire) process.once('document-start', fire);
} else if (script.runAt === 'document_end') { } else if (script.runAt === 'document_end') {
process.once('document-end', fire) process.once('document-end', fire);
} else { } else {
document.addEventListener('DOMContentLoaded', fire) document.addEventListener('DOMContentLoaded', fire);
} }
} }
if (script.css) { if (script.css) {
const fire = runAllStylesheet.bind(window, script.css) const fire = runAllStylesheet.bind(window, script.css);
if (script.runAt === 'document_start') { if (script.runAt === 'document_start') {
process.once('document-start', fire) process.once('document-start', fire);
} else if (script.runAt === 'document_end') { } else if (script.runAt === 'document_end') {
process.once('document-end', fire) process.once('document-end', fire);
} else { } else {
document.addEventListener('DOMContentLoaded', fire) document.addEventListener('DOMContentLoaded', fire);
} }
} }
} };
// Handle the request of chrome.tabs.executeJavaScript. // Handle the request of chrome.tabs.executeJavaScript.
ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function ( ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
@ -105,15 +105,15 @@ ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
url: string, url: string,
code: string code: string
) { ) {
return runContentScript.call(window, extensionId, url, code) return runContentScript.call(window, extensionId, url, code);
}) });
module.exports = (entries: Electron.ContentScriptEntry[]) => { module.exports = (entries: Electron.ContentScriptEntry[]) => {
for (const entry of entries) { for (const entry of entries) {
if (entry.contentScripts) { if (entry.contentScripts) {
for (const script of entry.contentScripts) { for (const script of entry.contentScripts) {
injectContentScript(entry.extensionId, script) injectContentScript(entry.extensionId, script);
} }
} }
} }
} };

View file

@ -2,19 +2,19 @@ export class Event {
private listeners: Function[] = [] private listeners: Function[] = []
addListener (callback: Function) { addListener (callback: Function) {
this.listeners.push(callback) this.listeners.push(callback);
} }
removeListener (callback: Function) { removeListener (callback: Function) {
const index = this.listeners.indexOf(callback) const index = this.listeners.indexOf(callback);
if (index !== -1) { if (index !== -1) {
this.listeners.splice(index, 1) this.listeners.splice(index, 1);
} }
} }
emit (...args: any[]) { emit (...args: any[]) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
listener(...args) listener(...args);
} }
} }
} }

View file

@ -4,7 +4,7 @@
// Does not implement predefined messages: // Does not implement predefined messages:
// https://developer.chrome.com/extensions/i18n#overview-predefined // https://developer.chrome.com/extensions/i18n#overview-predefined
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
interface Placeholder { interface Placeholder {
content: string; content: string;
@ -13,48 +13,48 @@ interface Placeholder {
const getMessages = (extensionId: number) => { const getMessages = (extensionId: number) => {
try { try {
const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId) const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId);
return JSON.parse(data) || {} return JSON.parse(data) || {};
} catch { } catch {
return {} return {};
} }
} };
const replaceNumberedSubstitutions = (message: string, substitutions: string[]) => { const replaceNumberedSubstitutions = (message: string, substitutions: string[]) => {
return message.replace(/\$(\d+)/, (_, number) => { return message.replace(/\$(\d+)/, (_, number) => {
const index = parseInt(number, 10) - 1 const index = parseInt(number, 10) - 1;
return substitutions[index] || '' return substitutions[index] || '';
}) });
} };
const replacePlaceholders = (message: string, placeholders: Record<string, Placeholder>, substitutions: string[] | string) => { const replacePlaceholders = (message: string, placeholders: Record<string, Placeholder>, substitutions: string[] | string) => {
if (typeof substitutions === 'string') substitutions = [substitutions] if (typeof substitutions === 'string') substitutions = [substitutions];
if (!Array.isArray(substitutions)) substitutions = [] if (!Array.isArray(substitutions)) substitutions = [];
if (placeholders) { if (placeholders) {
Object.keys(placeholders).forEach((name: string) => { Object.keys(placeholders).forEach((name: string) => {
let { content } = placeholders[name] let { content } = placeholders[name];
const substitutionsArray = Array.isArray(substitutions) ? substitutions : [] const substitutionsArray = Array.isArray(substitutions) ? substitutions : [];
content = replaceNumberedSubstitutions(content, substitutionsArray) content = replaceNumberedSubstitutions(content, substitutionsArray);
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content) message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content);
}) });
} }
return replaceNumberedSubstitutions(message, substitutions) return replaceNumberedSubstitutions(message, substitutions);
} };
const getMessage = (extensionId: number, messageName: string, substitutions: string[]) => { const getMessage = (extensionId: number, messageName: string, substitutions: string[]) => {
const messages = getMessages(extensionId) const messages = getMessages(extensionId);
if (Object.prototype.hasOwnProperty.call(messages, messageName)) { if (Object.prototype.hasOwnProperty.call(messages, messageName)) {
const { message, placeholders } = messages[messageName] const { message, placeholders } = messages[messageName];
return replacePlaceholders(message, placeholders, substitutions) return replacePlaceholders(message, placeholders, substitutions);
} }
} };
exports.setup = (extensionId: number) => { exports.setup = (extensionId: number) => {
return { return {
getMessage (messageName: string, substitutions: string[]) { getMessage (messageName: string, substitutions: string[]) {
return getMessage(extensionId, messageName, substitutions) return getMessage(extensionId, messageName, substitutions);
} }
} };
} };

View file

@ -1,86 +1,86 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const getStorage = (storageType: string, extensionId: number, callback: Function) => { const getStorage = (storageType: string, extensionId: number, callback: Function) => {
if (typeof callback !== 'function') throw new TypeError('No callback provided') if (typeof callback !== 'function') throw new TypeError('No callback provided');
ipcRendererInternal.invoke<string>('CHROME_STORAGE_READ', storageType, extensionId) ipcRendererInternal.invoke<string>('CHROME_STORAGE_READ', storageType, extensionId)
.then(data => { .then(data => {
if (data !== null) { if (data !== null) {
callback(JSON.parse(data)) callback(JSON.parse(data));
} else { } else {
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
callback({}) callback({});
} }
}) });
} };
const setStorage = (storageType: string, extensionId: number, storage: Record<string, any>, callback: Function) => { const setStorage = (storageType: string, extensionId: number, storage: Record<string, any>, callback: Function) => {
const json = JSON.stringify(storage) const json = JSON.stringify(storage);
ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json) ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json)
.then(() => { .then(() => {
if (callback) callback() if (callback) callback();
}) });
} };
const getStorageManager = (storageType: string, extensionId: number) => { const getStorageManager = (storageType: string, extensionId: number) => {
return { return {
get (keys: string[], callback: Function) { get (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => { getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (keys == null) return callback(storage) if (keys == null) return callback(storage);
let defaults: Record<string, any> = {} let defaults: Record<string, any> = {};
switch (typeof keys) { switch (typeof keys) {
case 'string': case 'string':
keys = [keys] keys = [keys];
break break;
case 'object': case 'object':
if (!Array.isArray(keys)) { if (!Array.isArray(keys)) {
defaults = keys defaults = keys;
keys = Object.keys(keys) keys = Object.keys(keys);
} }
break break;
} }
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
if (keys.length === 0) return callback({}) if (keys.length === 0) return callback({});
const items: Record<string, any> = {} const items: Record<string, any> = {};
keys.forEach((key: string) => { keys.forEach((key: string) => {
let value = storage[key] let value = storage[key];
if (value == null) value = defaults[key] if (value == null) value = defaults[key];
items[key] = value items[key] = value;
}) });
callback(items) callback(items);
}) });
}, },
set (items: Record<string, any>, callback: Function) { set (items: Record<string, any>, callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => { getStorage(storageType, extensionId, (storage: Record<string, any>) => {
Object.keys(items).forEach(name => { storage[name] = items[name] }) Object.keys(items).forEach(name => { storage[name] = items[name]; });
setStorage(storageType, extensionId, storage, callback) setStorage(storageType, extensionId, storage, callback);
}) });
}, },
remove (keys: string[], callback: Function) { remove (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => { getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (!Array.isArray(keys)) keys = [keys] if (!Array.isArray(keys)) keys = [keys];
keys.forEach((key: string) => { keys.forEach((key: string) => {
delete storage[key] delete storage[key];
}) });
setStorage(storageType, extensionId, storage, callback) setStorage(storageType, extensionId, storage, callback);
}) });
}, },
clear (callback: Function) { clear (callback: Function) {
setStorage(storageType, extensionId, {}, callback) setStorage(storageType, extensionId, {}, callback);
} }
} };
} };
export const setup = (extensionId: number) => ({ export const setup = (extensionId: number) => ({
sync: getStorageManager('sync', extensionId), sync: getStorageManager('sync', extensionId),
local: getStorageManager('local', extensionId) local: getStorageManager('local', extensionId)
}) });

View file

@ -1,5 +1,5 @@
import { Event } from '@electron/internal/renderer/extensions/event' import { Event } from '@electron/internal/renderer/extensions/event';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
class WebNavigation { class WebNavigation {
private onBeforeNavigate = new Event() private onBeforeNavigate = new Event()
@ -7,13 +7,13 @@ class WebNavigation {
constructor () { constructor () {
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => { ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => {
this.onBeforeNavigate.emit(details) this.onBeforeNavigate.emit(details);
}) });
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => { ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => {
this.onCompleted.emit(details) this.onCompleted.emit(details);
}) });
} }
} }
export const setup = () => new WebNavigation() export const setup = () => new WebNavigation();

View file

@ -1,7 +1,7 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import * as path from 'path' import * as path from 'path';
const Module = require('module') const Module = require('module');
// Make sure globals like "process" and "global" are always available in preload // Make sure globals like "process" and "global" are always available in preload
// scripts even after they are deleted in "loaded" script. // scripts even after they are deleted in "loaded" script.
@ -23,42 +23,42 @@ Module.wrapper = [
// code to override "process" and "Buffer" with local variables. // code to override "process" and "Buffer" with local variables.
'return function (exports, require, module, __filename, __dirname) { ', 'return function (exports, require, module, __filename, __dirname) { ',
'\n}.call(this, exports, require, module, __filename, __dirname); });' '\n}.call(this, exports, require, module, __filename, __dirname); });'
] ];
// We modified the original process.argv to let node.js load the // We modified the original process.argv to let node.js load the
// init.js, we need to restore it here. // init.js, we need to restore it here.
process.argv.splice(1, 1) process.argv.splice(1, 1);
// Clear search paths. // Clear search paths.
require('../common/reset-search-paths') require('../common/reset-search-paths');
// Import common settings. // Import common settings.
require('@electron/internal/common/init') require('@electron/internal/common/init');
// The global variable will be used by ipc for event dispatching // The global variable will be used by ipc for event dispatching
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const ipcEmitter = new EventEmitter() const ipcEmitter = new EventEmitter();
const ipcInternalEmitter = new EventEmitter() const ipcInternalEmitter = new EventEmitter();
v8Util.setHiddenValue(global, 'ipc', ipcEmitter) v8Util.setHiddenValue(global, 'ipc', ipcEmitter);
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter) v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter);
v8Util.setHiddenValue(global, 'ipcNative', { v8Util.setHiddenValue(global, 'ipcNative', {
onMessage (internal: boolean, channel: string, ports: any[], args: any[], senderId: number) { onMessage (internal: boolean, channel: string, ports: any[], args: any[], senderId: number) {
const sender = internal ? ipcInternalEmitter : ipcEmitter const sender = internal ? ipcInternalEmitter : ipcEmitter;
sender.emit(channel, { sender, senderId, ports }, ...args) sender.emit(channel, { sender, senderId, ports }, ...args);
} }
}) });
// Use electron module after everything is ready. // Use electron module after everything is ready.
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init') const { webFrameInit } = require('@electron/internal/renderer/web-frame-init');
webFrameInit() webFrameInit();
// Process command line arguments. // Process command line arguments.
const { hasSwitch, getSwitchValue } = process.electronBinding('command_line') const { hasSwitch, getSwitchValue } = process.electronBinding('command_line');
const parseOption = function<T> ( const parseOption = function<T> (
name: string, defaultValue: T, converter?: (value: string) => T name: string, defaultValue: T, converter?: (value: string) => T
@ -69,105 +69,105 @@ const parseOption = function<T> (
? converter(getSwitchValue(name)) ? converter(getSwitchValue(name))
: getSwitchValue(name) : getSwitchValue(name)
) )
: defaultValue : defaultValue;
} };
const contextIsolation = hasSwitch('context-isolation') const contextIsolation = hasSwitch('context-isolation');
const nodeIntegration = hasSwitch('node-integration') const nodeIntegration = hasSwitch('node-integration');
const webviewTag = hasSwitch('webview-tag') const webviewTag = hasSwitch('webview-tag');
const isHiddenPage = hasSwitch('hidden-page') const isHiddenPage = hasSwitch('hidden-page');
const usesNativeWindowOpen = hasSwitch('native-window-open') const usesNativeWindowOpen = hasSwitch('native-window-open');
const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides') const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides');
const preloadScript = parseOption('preload', null) const preloadScript = parseOption('preload', null);
const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)) as string[] const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)) as string[];
const appPath = parseOption('app-path', null) const appPath = parseOption('app-path', null);
const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value)) const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value));
const openerId = parseOption('opener-id', null, value => parseInt(value)) const openerId = parseOption('opener-id', null, value => parseInt(value));
// The arguments to be passed to isolated world. // The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled } const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled };
// The webContents preload script is loaded after the session preload scripts. // The webContents preload script is loaded after the session preload scripts.
if (preloadScript) { if (preloadScript) {
preloadScripts.push(preloadScript) preloadScripts.push(preloadScript);
} }
switch (window.location.protocol) { switch (window.location.protocol) {
case 'devtools:': { case 'devtools:': {
// Override some inspector APIs. // Override some inspector APIs.
require('@electron/internal/renderer/inspector') require('@electron/internal/renderer/inspector');
break break;
} }
case 'chrome-extension:': { case 'chrome-extension:': {
// Inject the chrome.* APIs that chrome extensions require // Inject the chrome.* APIs that chrome extensions require
if (!process.electronBinding('features').isExtensionsEnabled()) { if (!process.electronBinding('features').isExtensionsEnabled()) {
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window) require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window);
} }
break break;
} }
case 'chrome:': case 'chrome:':
break break;
default: { default: {
// Override default web functions. // Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup') const { windowSetup } = require('@electron/internal/renderer/window-setup');
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled) windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled);
// Inject content scripts. // Inject content scripts.
if (!process.electronBinding('features').isExtensionsEnabled()) { if (!process.electronBinding('features').isExtensionsEnabled()) {
const contentScripts = ipcRendererUtils.invokeSync('ELECTRON_GET_CONTENT_SCRIPTS') as Electron.ContentScriptEntry[] const contentScripts = ipcRendererUtils.invokeSync('ELECTRON_GET_CONTENT_SCRIPTS') as Electron.ContentScriptEntry[];
require('@electron/internal/renderer/content-scripts-injector')(contentScripts) require('@electron/internal/renderer/content-scripts-injector')(contentScripts);
} }
} }
} }
// Load webview tag implementation. // Load webview tag implementation.
if (process.isMainFrame) { if (process.isMainFrame) {
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init') const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init');
webViewInit(contextIsolation, webviewTag, guestInstanceId) webViewInit(contextIsolation, webviewTag, guestInstanceId);
} }
// Pass the arguments to isolatedWorld. // Pass the arguments to isolatedWorld.
if (contextIsolation) { if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs) v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs);
} }
if (nodeIntegration) { if (nodeIntegration) {
// Export node bindings to global. // Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line
global.module = new Module('electron/js2c/renderer_init') global.module = new Module('electron/js2c/renderer_init');
global.require = makeRequireFunction(global.module) global.require = makeRequireFunction(global.module);
// Set the __filename to the path of html file if it is file: protocol. // Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') { if (window.location.protocol === 'file:') {
const location = window.location const location = window.location;
let pathname = location.pathname let pathname = location.pathname;
if (process.platform === 'win32') { if (process.platform === 'win32') {
if (pathname[0] === '/') pathname = pathname.substr(1) if (pathname[0] === '/') pathname = pathname.substr(1);
const isWindowsNetworkSharePath = location.hostname.length > 0 && process.resourcesPath.startsWith('\\') const isWindowsNetworkSharePath = location.hostname.length > 0 && process.resourcesPath.startsWith('\\');
if (isWindowsNetworkSharePath) { if (isWindowsNetworkSharePath) {
pathname = `//${location.host}/${pathname}` pathname = `//${location.host}/${pathname}`;
} }
} }
global.__filename = path.normalize(decodeURIComponent(pathname)) global.__filename = path.normalize(decodeURIComponent(pathname));
global.__dirname = path.dirname(global.__filename) global.__dirname = path.dirname(global.__filename);
// Set module's filename so relative require can work as expected. // Set module's filename so relative require can work as expected.
global.module.filename = global.__filename global.module.filename = global.__filename;
// Also search for module under the html file. // Also search for module under the html file.
global.module.paths = Module._nodeModulePaths(global.__dirname) global.module.paths = Module._nodeModulePaths(global.__dirname);
} else { } else {
// For backwards compatibility we fake these two paths here // For backwards compatibility we fake these two paths here
global.__filename = path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js') global.__filename = path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js');
global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'renderer') global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'renderer');
if (appPath) { if (appPath) {
// Search for module under the app directory // Search for module under the app directory
global.module.paths = Module._nodeModulePaths(appPath) global.module.paths = Module._nodeModulePaths(appPath);
} }
} }
@ -177,42 +177,42 @@ if (nodeIntegration) {
// We do not want to add `uncaughtException` to our definitions // We do not want to add `uncaughtException` to our definitions
// because we don't want anyone else (anywhere) to throw that kind // because we don't want anyone else (anywhere) to throw that kind
// of error. // of error.
global.process.emit('uncaughtException' as any, error as any) global.process.emit('uncaughtException' as any, error as any);
return true return true;
} else { } else {
return false return false;
} }
} };
} else { } else {
// Delete Node's symbols after the Environment has been loaded in a // Delete Node's symbols after the Environment has been loaded in a
// non context-isolated environment // non context-isolated environment
if (!contextIsolation) { if (!contextIsolation) {
process.once('loaded', function () { process.once('loaded', function () {
delete global.process delete global.process;
delete global.Buffer delete global.Buffer;
delete global.setImmediate delete global.setImmediate;
delete global.clearImmediate delete global.clearImmediate;
delete global.global delete global.global;
delete global.root delete global.root;
delete global.GLOBAL delete global.GLOBAL;
}) });
} }
} }
// Load the preload scripts. // Load the preload scripts.
for (const preloadScript of preloadScripts) { for (const preloadScript of preloadScripts) {
try { try {
Module._load(preloadScript) Module._load(preloadScript);
} catch (error) { } catch (error) {
console.error(`Unable to load preload script: ${preloadScript}`) console.error(`Unable to load preload script: ${preloadScript}`);
console.error(error) console.error(error);
ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, error) ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, error);
} }
} }
// Warn about security issues // Warn about security issues
if (process.isMainFrame) { if (process.isMainFrame) {
const { securityWarnings } = require('@electron/internal/renderer/security-warnings') const { securityWarnings } = require('@electron/internal/renderer/security-warnings');
securityWarnings(nodeIntegration) securityWarnings(nodeIntegration);
} }

View file

@ -1,61 +1,61 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
window.onload = function () { window.onload = function () {
// Use menu API to show context menu. // Use menu API to show context menu.
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
// correct for Chromium returning undefined for filesystem // correct for Chromium returning undefined for filesystem
window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL;
// Use dialog API to override file chooser dialog. // Use dialog API to override file chooser dialog.
window.UI!.createFileSelectorElement = createFileSelectorElement window.UI!.createFileSelectorElement = createFileSelectorElement;
} };
// Extra / is needed as a result of MacOS requiring absolute paths // Extra / is needed as a result of MacOS requiring absolute paths
function completeURL (project: string, path: string) { function completeURL (project: string, path: string) {
project = 'file:///' project = 'file:///';
return `${project}${path}` return `${project}${path}`;
} }
// The DOM implementation expects (message?: string) => boolean // The DOM implementation expects (message?: string) => boolean
(window.confirm as any) = function (message: string, title: string) { (window.confirm as any) = function (message: string, title: string) {
return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean;
} };
const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) { const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) {
return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) { return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
return element.nodeName === 'INPUT' || return element.nodeName === 'INPUT' ||
element.nodeName === 'TEXTAREA' || element.nodeName === 'TEXTAREA' ||
(element as HTMLElement).isContentEditable (element as HTMLElement).isContentEditable;
}) });
} };
const createMenu = function (x: number, y: number, items: ContextMenuItem[]) { const createMenu = function (x: number, y: number, items: ContextMenuItem[]) {
const isEditMenu = useEditMenuItems(x, y, items) const isEditMenu = useEditMenuItems(x, y, items);
ipcRendererInternal.invoke<number>('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => { ipcRendererInternal.invoke<number>('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => {
if (typeof id === 'number') { if (typeof id === 'number') {
window.DevToolsAPI!.contextMenuItemSelected(id) window.DevToolsAPI!.contextMenuItemSelected(id);
} }
window.DevToolsAPI!.contextMenuCleared() window.DevToolsAPI!.contextMenuCleared();
}) });
} };
const showFileChooserDialog = function (callback: (blob: File) => void) { const showFileChooserDialog = function (callback: (blob: File) => void) {
ipcRendererInternal.invoke<[ string, any ]>('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => { ipcRendererInternal.invoke<[ string, any ]>('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => {
if (path && data) { if (path && data) {
callback(dataToHtml5FileObject(path, data)) callback(dataToHtml5FileObject(path, data));
} }
}) });
} };
const dataToHtml5FileObject = function (path: string, data: any) { const dataToHtml5FileObject = function (path: string, data: any) {
return new File([data], path) return new File([data], path);
} };
const createFileSelectorElement = function (this: any, callback: () => void) { const createFileSelectorElement = function (this: any, callback: () => void) {
const fileSelectorElement = document.createElement('span') const fileSelectorElement = document.createElement('span');
fileSelectorElement.style.display = 'none' fileSelectorElement.style.display = 'none';
fileSelectorElement.click = showFileChooserDialog.bind(this, callback) fileSelectorElement.click = showFileChooserDialog.bind(this, callback);
return fileSelectorElement return fileSelectorElement;
} };

Some files were not shown because too many files have changed in this diff Show more