build: enable JS semicolons (#22783)
This commit is contained in:
parent
24e21467b9
commit
5d657dece4
354 changed files with 21512 additions and 21510 deletions
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
}
|
};
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
})
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
'use strict'
|
'use strict';
|
||||||
module.exports = process.electronBinding('content_tracing')
|
module.exports = process.electronBinding('content_tracing');
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
'use strict'
|
'use strict';
|
||||||
|
|
||||||
module.exports = process.electronBinding('global_shortcut').globalShortcut
|
module.exports = process.electronBinding('global_shortcut').globalShortcut;
|
||||||
|
|
|
@ -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: () => ''
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' }
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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') }
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
'use strict'
|
'use strict';
|
||||||
|
|
||||||
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker
|
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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');
|
||||||
})
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
|
@ -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()
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
})
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', () => {});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
return this.webContents._loadURL(this.history[this.pendingIndex], {})
|
|
||||||
}
|
}
|
||||||
|
this.pendingIndex = index;
|
||||||
|
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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
}
|
};
|
||||||
|
|
|
@ -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);
|
||||||
})
|
});
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 }
|
||||||
]
|
];
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
@ -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'));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
])
|
]);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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') });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
|
||||||
return metaToValue(ret)
|
|
||||||
}
|
}
|
||||||
|
const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
|
||||||
|
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 () {}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -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)
|
||||||
})
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue