Changes of Linux ptb v0.0.16

This commit is contained in:
DisTok 2019-10-01 15:49:56 +02:00
parent 8f349e0a2d
commit a1a13dd5af
113 changed files with 7916 additions and 8375 deletions

View File

@ -35,14 +35,17 @@ appSettings.init();
const Constants = require('./Constants');
const GPUSettings = require('./GPUSettings');
const settings = appSettings.getSettings();
// TODO: this is a copy of gpuSettings.getEnableHardwareAcceleration
if (!settings.get('enableHardwareAcceleration', true)) {
app.disableHardwareAcceleration();
function setupHardwareAcceleration() {
const settings = appSettings.getSettings();
const electronMajor = parseInt(process.versions.electron.split('.')[0]);
const allowed = process.env.DISCORD_ENABLE_HARDWARE_ACCELERATION || buildInfo.releaseChannel === 'development' || !(electronMajor === 7 && process.platform === 'darwin');
// TODO: this is a copy of gpuSettings.getEnableHardwareAcceleration
if (!allowed || !settings.get('enableHardwareAcceleration', true)) {
app.disableHardwareAcceleration();
}
}
// [adill] disables color correction based on monitor's color profile
app.commandLine.appendSwitch('force-color-profile', 'srgb');
setupHardwareAcceleration();
// [adill] work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');

View File

@ -13,4 +13,4 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
const buildInfo = require(_path2.default.join(process.resourcesPath, 'build_info.json'));
exports.default = buildInfo;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -28,11 +28,16 @@ function init() {
// show a similar error message to the error handler, except exit out the app
// after the error message has been closed
function fatal(err) {
_electron.dialog.showMessageBox({
const options = {
type: 'error',
message: 'A fatal Javascript error occured',
detail: err && err.stack ? err.stack : String(err)
}, () => {
_electron.app.quit();
});
};
const callback = _ => _electron.app.quit();
const electronMajor = parseInt(process.versions.electron.split('.')[0]);
if (electronMajor >= 6) {
_electron.dialog.showMessageBox(null, options).then(callback);
} else {
_electron.dialog.showMessageBox(options, callback);
}
}

View File

@ -186,4 +186,4 @@ switch (process.platform) {
}
exports.default = autoUpdater;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -15,4 +15,4 @@ function installDevTools() {
}
exports.default = installDevTools;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -10,4 +10,4 @@ exports.default = {
on: (event, callback) => _electron.ipcMain.on(`DISCORD_${event}`, callback),
removeListener: (event, callback) => _electron.ipcMain.removeListener(`DISCORD_${event}`, callback)
};
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -113,4 +113,4 @@ for (const method of ['get']) {
}
exports.default = requestWithMethod;
module.exports = exports['default'];
module.exports = exports.default;

File diff suppressed because one or more lines are too long

View File

@ -159,7 +159,6 @@ function initSplash(startMinimized = false) {
}
function destroySplash() {
removeModulesListeners();
stopUpdateTimeout();
if (splashWindow) {
@ -265,6 +264,7 @@ function launchSplashWindow(startMinimized) {
}
function launchMainWindow() {
removeModulesListeners();
if (!launchedMainWindow && splashWindow != null) {
launchedMainWindow = true;
events.emit(APP_SHOULD_LAUNCH);

View File

@ -14,4 +14,4 @@ exports.default = [{
accelerator: 'Command+Q'
}]
}];
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -9,4 +9,4 @@ var _electron = require('electron');
const menu = require('./' + process.platform);
exports.default = _electron.Menu.buildFromTemplate(menu);
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -14,4 +14,4 @@ exports.default = [{
accelerator: 'Control+Q'
}]
}];
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -14,4 +14,4 @@ exports.default = [{
accelerator: 'Alt+F4'
}]
}];
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -90,4 +90,4 @@ class Backoff {
}
}
exports.default = Backoff;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -26,4 +26,4 @@ class FeatureFlags {
}
}
exports.default = FeatureFlags;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -68,4 +68,4 @@ class Settings {
}
}
exports.default = Settings;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -1,117 +1,87 @@
'use strict'
const bindings = process.atomBinding('app')
const path = require('path')
const { app, App } = bindings
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const electron = require("electron");
const events_1 = require("events");
const bindings = process.electronBinding('app');
const commandLine = process.electronBinding('command_line');
const { app, App } = bindings;
// Only one app object permitted.
module.exports = app
const electron = require('electron')
const { deprecate, Menu } = electron
const { EventEmitter } = require('events')
let dockMenu = null
exports.default = app;
const { deprecate, Menu } = electron;
let dockMenu = null;
// App is an EventEmitter.
Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
EventEmitter.call(app)
Object.setPrototypeOf(App.prototype, events_1.EventEmitter.prototype);
events_1.EventEmitter.call(app);
Object.assign(app, {
setApplicationMenu (menu) {
return Menu.setApplicationMenu(menu)
},
getApplicationMenu () {
return Menu.getApplicationMenu()
},
commandLine: {
appendSwitch (...args) {
const castedArgs = args.map((arg) => {
return typeof arg !== 'string' ? `${arg}` : arg
})
return bindings.appendSwitch(...castedArgs)
// TODO(codebytere): remove in 7.0
setApplicationMenu(menu) {
return Menu.setApplicationMenu(menu);
},
appendArgument (...args) {
const castedArgs = args.map((arg) => {
return typeof arg !== 'string' ? `${arg}` : arg
})
return bindings.appendArgument(...castedArgs)
// TODO(codebytere): remove in 7.0
getApplicationMenu() {
return Menu.getApplicationMenu();
},
commandLine: {
hasSwitch: (theSwitch) => commandLine.hasSwitch(String(theSwitch)),
getSwitchValue: (theSwitch) => commandLine.getSwitchValue(String(theSwitch)),
appendSwitch: (theSwitch, value) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
appendArgument: (arg) => commandLine.appendArgument(String(arg))
},
enableMixedSandbox() {
deprecate.log(`'enableMixedSandbox' is deprecated. Mixed-sandbox mode is now enabled by default. You can safely remove the call to enableMixedSandbox().`);
}
}
})
const nativeFn = app.getAppMetrics
app.getAppMetrics = () => {
const metrics = nativeFn.call(app)
for (const metric of metrics) {
if ('memory' in metric) {
deprecate.removeProperty(metric, 'memory')
});
// we define this here because it'd be overly complicated to
// do in native land
Object.defineProperty(app, 'applicationMenu', {
get() {
return Menu.getApplicationMenu();
},
set(menu) {
return Menu.setApplicationMenu(menu);
}
}
return metrics
}
});
app.isPackaged = (() => {
const execFile = path.basename(process.execPath).toLowerCase()
if (process.platform === 'win32') {
return execFile !== 'electron.exe'
}
return execFile !== 'electron'
})()
const execFile = path.basename(process.execPath).toLowerCase();
if (process.platform === 'win32') {
return execFile !== 'electron.exe';
}
return execFile !== 'electron';
})();
app._setDefaultAppPaths = (packagePath) => {
// Set the user path according to application's name.
app.setPath('userData', path.join(app.getPath('appData'), app.getName()));
app.setPath('userCache', path.join(app.getPath('cache'), app.getName()));
app.setAppPath(packagePath);
// Add support for --user-data-dir=
const userDataDirFlag = '--user-data-dir=';
const userDataArg = process.argv.find(arg => arg.startsWith(userDataDirFlag));
if (userDataArg) {
const userDataDir = userDataArg.substr(userDataDirFlag.length);
if (path.isAbsolute(userDataDir))
app.setPath('userData', userDataDir);
}
};
if (process.platform === 'darwin') {
app.dock = {
bounce (type = 'informational') {
return bindings.dockBounce(type)
},
cancelBounce: bindings.dockCancelBounce,
downloadFinished: bindings.dockDownloadFinished,
setBadge: bindings.dockSetBadgeText,
getBadge: bindings.dockGetBadgeText,
hide: bindings.dockHide,
show: bindings.dockShow,
isVisible: bindings.dockIsVisible,
setMenu (menu) {
dockMenu = menu
bindings.dockSetMenu(menu)
},
getMenu () {
return dockMenu
},
setIcon: bindings.dockSetIcon
}
const setDockMenu = app.dock.setMenu;
app.dock.setMenu = (menu) => {
dockMenu = menu;
setDockMenu(menu);
};
app.dock.getMenu = () => dockMenu;
}
if (process.platform === 'linux') {
app.launcher = {
setBadgeCount: bindings.unityLauncherSetBadgeCount,
getBadgeCount: bindings.unityLauncherGetBadgeCount,
isCounterBadgeAvailable: bindings.unityLauncherAvailable,
isUnityRunning: bindings.unityLauncherAvailable
}
}
app.allowNTLMCredentialsForAllDomains = function (allow) {
if (!process.noDeprecations) {
deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains')
}
const domains = allow ? '*' : ''
if (!this.isReady()) {
this.commandLine.appendSwitch('auth-server-whitelist', domains)
} else {
electron.session.defaultSession.allowNTLMCredentialsForDomains(domains)
}
}
// Routes the events to webContents.
const events = ['login', 'certificate-error', 'select-client-certificate']
const events = ['login', 'certificate-error', 'select-client-certificate'];
for (const name of events) {
app.on(name, (event, webContents, ...args) => {
webContents.emit(name, event, ...args)
})
app.on(name, (event, webContents, ...args) => {
webContents.emit(name, event, ...args);
});
}
// Function Deprecations
app.getFileIcon = deprecate.promisify(app.getFileIcon);
// Property Deprecations
deprecate.fnToProperty(app, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled');
// Wrappers for native classes.
const { DownloadItem } = process.atomBinding('download_item')
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype)
const { DownloadItem } = process.electronBinding('download_item');
Object.setPrototypeOf(DownloadItem.prototype, events_1.EventEmitter.prototype);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,191 +1,170 @@
'use strict'
const electron = require('electron')
const { WebContentsView, TopLevelWindow } = electron
const { BrowserWindow } = process.atomBinding('window')
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype)
'use strict';
const electron = require('electron');
const { WebContentsView, TopLevelWindow } = electron;
const { BrowserWindow } = process.electronBinding('window');
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype);
BrowserWindow.prototype._init = function () {
// Call parent class's _init.
TopLevelWindow.prototype._init.call(this)
// Avoid recursive require.
const { app } = electron
// Create WebContentsView.
this.setContentView(new WebContentsView(this.webContents))
const nativeSetBounds = this.setBounds
this.setBounds = (bounds, ...opts) => {
bounds = {
...this.getBounds(),
...bounds
// Call parent class's _init.
TopLevelWindow.prototype._init.call(this);
// Avoid recursive require.
const { app } = electron;
// Create WebContentsView.
this.setContentView(new WebContentsView(this.webContents));
const nativeSetBounds = this.setBounds;
this.setBounds = (bounds, ...opts) => {
bounds = Object.assign({}, this.getBounds(), bounds);
nativeSetBounds.call(this, bounds, ...opts);
};
// window.resizeTo(...)
// window.moveTo(...)
this.webContents.on('move', (event, size) => {
this.setBounds(size);
});
// Hide the auto-hide menu when webContents is focused.
this.webContents.on('activate', () => {
if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) {
this.setMenuBarVisibility(false);
}
});
// Change window title to page title.
this.webContents.on('page-title-updated', (event, title, ...args) => {
// Route the event to BrowserWindow.
this.emit('page-title-updated', event, title, ...args);
if (!this.isDestroyed() && !event.defaultPrevented)
this.setTitle(title);
});
// 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
// focus it when we first start to load URL, if we do it earlier it won't
// have effect, if we do it later we might move focus in the page.
//
// Though this hack is only needed on macOS when the app is launched from
// Finder, we still do it on all platforms in case of other bugs we don't
// know.
this.webContents.once('load-url', function () {
this.focus();
});
// Redirect focus/blur event to app instance too.
this.on('blur', (event) => {
app.emit('browser-window-blur', event, this);
});
this.on('focus', (event) => {
app.emit('browser-window-focus', event, this);
});
// Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized();
const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized();
if (isVisible !== newState) {
isVisible = newState;
const visibilityState = isVisible ? 'visible' : 'hidden';
this.webContents.emit('-window-visibility-change', visibilityState);
}
};
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
for (const event of visibilityEvents) {
this.on(event, visibilityChanged);
}
nativeSetBounds.call(this, bounds, ...opts)
}
// window.resizeTo(...)
// window.moveTo(...)
this.webContents.on('move', (event, size) => {
this.setBounds(size)
})
// Hide the auto-hide menu when webContents is focused.
this.webContents.on('activate', () => {
if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) {
this.setMenuBarVisibility(false)
}
})
// Change window title to page title.
this.webContents.on('page-title-updated', (event, title) => {
// Route the event to BrowserWindow.
this.emit('page-title-updated', event, title)
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
})
// 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
// focus it when we first start to load URL, if we do it earlier it won't
// have effect, if we do it later we might move focus in the page.
//
// Though this hack is only needed on macOS when the app is launched from
// Finder, we still do it on all platforms in case of other bugs we don't
// know.
this.webContents.once('load-url', function () {
this.focus()
})
// Redirect focus/blur event to app instance too.
this.on('blur', (event) => {
app.emit('browser-window-blur', event, this)
})
this.on('focus', (event) => {
app.emit('browser-window-focus', event, this)
})
// Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized()
const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized()
if (isVisible !== newState) {
isVisible = newState
const visibilityState = isVisible ? 'visible' : 'hidden'
this.webContents.emit('-window-visibility-change', visibilityState)
}
}
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']
for (const event of visibilityEvents) {
this.on(event, visibilityChanged)
}
// Notify the creation of the window.
app.emit('browser-window-created', {}, this)
Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true,
configurable: false,
get () {
return this.webContents.devToolsWebContents
}
})
}
// Notify the creation of the window.
app.emit('browser-window-created', {}, this);
Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true,
configurable: false,
get() {
return this.webContents.devToolsWebContents;
}
});
};
const isBrowserWindow = (win) => {
return win && win.constructor.name === 'BrowserWindow'
}
return win && win.constructor.name === 'BrowserWindow';
};
BrowserWindow.fromId = (id) => {
const win = TopLevelWindow.fromId(id)
return isBrowserWindow(win) ? win : null
}
const win = TopLevelWindow.fromId(id);
return isBrowserWindow(win) ? win : null;
};
BrowserWindow.getAllWindows = () => {
return TopLevelWindow.getAllWindows().filter(isBrowserWindow)
}
return TopLevelWindow.getAllWindows().filter(isBrowserWindow);
};
BrowserWindow.getFocusedWindow = () => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused()) return window
}
return null
}
BrowserWindow.fromWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents.equal(webContents)) return window
}
}
BrowserWindow.fromBrowserView = (browserView) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView) return window
}
return null
}
BrowserWindow.fromDevToolsWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) {
const { devToolsWebContents } = window
if (devToolsWebContents != null && devToolsWebContents.equal(webContents)) {
return window
for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused())
return window;
}
}
}
return null;
};
BrowserWindow.fromWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents.equal(webContents))
return window;
}
};
BrowserWindow.fromBrowserView = (browserView) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView)
return window;
}
return null;
};
BrowserWindow.fromDevToolsWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) {
const { devToolsWebContents } = window;
if (devToolsWebContents != null && devToolsWebContents.equal(webContents)) {
return window;
}
}
};
// Helpers.
Object.assign(BrowserWindow.prototype, {
loadURL (...args) {
return this.webContents.loadURL(...args)
},
getURL (...args) {
return this.webContents.getURL()
},
loadFile (...args) {
return this.webContents.loadFile(...args)
},
reload (...args) {
return this.webContents.reload(...args)
},
send (...args) {
return this.webContents.send(...args)
},
openDevTools (...args) {
return this.webContents.openDevTools(...args)
},
closeDevTools () {
return this.webContents.closeDevTools()
},
isDevToolsOpened () {
return this.webContents.isDevToolsOpened()
},
isDevToolsFocused () {
return this.webContents.isDevToolsFocused()
},
toggleDevTools () {
return this.webContents.toggleDevTools()
},
inspectElement (...args) {
return this.webContents.inspectElement(...args)
},
inspectServiceWorker () {
return this.webContents.inspectServiceWorker()
},
showDefinitionForSelection () {
return this.webContents.showDefinitionForSelection()
},
capturePage (...args) {
return this.webContents.capturePage(...args)
},
setTouchBar (touchBar) {
electron.TouchBar._setOnWindow(touchBar, this)
},
setBackgroundThrottling (allowed) {
this.webContents.setBackgroundThrottling(allowed)
}
})
module.exports = BrowserWindow
loadURL(...args) {
return this.webContents.loadURL(...args);
},
getURL(...args) {
return this.webContents.getURL();
},
loadFile(...args) {
return this.webContents.loadFile(...args);
},
reload(...args) {
return this.webContents.reload(...args);
},
send(...args) {
return this.webContents.send(...args);
},
openDevTools(...args) {
return this.webContents.openDevTools(...args);
},
closeDevTools() {
return this.webContents.closeDevTools();
},
isDevToolsOpened() {
return this.webContents.isDevToolsOpened();
},
isDevToolsFocused() {
return this.webContents.isDevToolsFocused();
},
toggleDevTools() {
return this.webContents.toggleDevTools();
},
inspectElement(...args) {
return this.webContents.inspectElement(...args);
},
inspectSharedWorker() {
return this.webContents.inspectSharedWorker();
},
inspectServiceWorker() {
return this.webContents.inspectServiceWorker();
},
showDefinitionForSelection() {
return this.webContents.showDefinitionForSelection();
},
capturePage(...args) {
return this.webContents.capturePage(...args);
},
setTouchBar(touchBar) {
electron.TouchBar._setOnWindow(touchBar, this);
},
setBackgroundThrottling(allowed) {
this.webContents.setBackgroundThrottling(allowed);
}
});
module.exports = BrowserWindow;

View File

@ -1,3 +1,11 @@
'use strict'
module.exports = process.atomBinding('content_tracing')
'use strict';
const { deprecate } = require('electron');
const contentTracing = process.electronBinding('content_tracing');
contentTracing.getCategories = deprecate.promisify(contentTracing.getCategories);
contentTracing.startRecording = deprecate.promisify(contentTracing.startRecording);
contentTracing.stopRecording = deprecate.promisify(contentTracing.stopRecording);
contentTracing.getTraceBufferUsage = deprecate.promisifyMultiArg(contentTracing.getTraceBufferUsage
// convertPromiseValue: Temporarily disabled until it's used
/* (value) => [value.paths, value.bookmarks] */
);
module.exports = contentTracing;

View File

@ -1,14 +1,9 @@
'use strict'
const CrashReporter = require('@electron/internal/common/crash-reporter')
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
'use strict';
const CrashReporter = require('@electron/internal/common/crash-reporter');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
class CrashReporterMain extends CrashReporter {
sendSync (channel, ...args) {
const event = {}
ipcMain.emit(channel, event, ...args)
return event.returnValue
}
init(options) {
return crashReporterInit(options);
}
}
module.exports = new CrashReporterMain()
module.exports = new CrashReporterMain();

View File

@ -1,312 +1,183 @@
'use strict'
const { app, BrowserWindow } = require('electron')
const binding = process.atomBinding('dialog')
const v8Util = process.atomBinding('v8_util')
'use strict';
const { app, BrowserWindow, deprecate } = require('electron');
const binding = process.electronBinding('dialog');
const v8Util = process.electronBinding('v8_util');
const fileDialogProperties = {
openFile: 1 << 0,
openDirectory: 1 << 1,
multiSelections: 1 << 2,
createDirectory: 1 << 3,
showHiddenFiles: 1 << 4,
promptToCreate: 1 << 5,
noResolveAliases: 1 << 6,
treatPackageAsDirectory: 1 << 7
}
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']
const messageBoxOptions = {
noLink: 1 << 0
}
const parseArgs = function (window, options, callback, ...args) {
if (window != null && window.constructor !== BrowserWindow) {
// Shift.
[callback, options, window] = [options, window, null]
}
if ((callback == null) && typeof options === 'function') {
// Shift.
[callback, options] = [options, null]
}
// Fallback to using very last argument as the callback function
const lastArgument = args[args.length - 1]
if ((callback == null) && typeof lastArgument === 'function') {
callback = lastArgument
}
return [window, options, callback]
}
openFile: 1 << 0,
openDirectory: 1 << 1,
multiSelections: 1 << 2,
createDirectory: 1 << 3,
showHiddenFiles: 1 << 4,
promptToCreate: 1 << 5,
noResolveAliases: 1 << 6,
treatPackageAsDirectory: 1 << 7
};
const normalizeAccessKey = (text) => {
if (typeof text !== 'string') return text
// macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand
if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1')
}
// Linux uses a single underscore as an access key prefix so escape
// existing single underscores with a second underscore, replace double
// ampersands with a single ampersand, and replace a single ampersand with
// a single underscore
if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&') return after
return `_${after}`
})
}
return text
}
if (typeof text !== 'string')
return text;
// macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand
if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1');
}
// Linux uses a single underscore as an access key prefix so escape
// existing single underscores with a second underscore, replace double
// ampersands with a single ampersand, and replace a single ampersand with
// a single underscore
if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&')
return after;
return `_${after}`;
});
}
return text;
};
const checkAppInitialized = function () {
if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready')
}
}
module.exports = {
showOpenDialog: function (...args) {
checkAppInitialized()
let [window, options, callback] = parseArgs(...args)
if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready');
}
};
const saveDialog = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (options == null)
options = { title: 'Save' };
const { buttonLabel = '', defaultPath = '', filters = [], title = '', message = '', securityScopedBookmarks = false, nameFieldLabel = '', showsTagField = true } = options;
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 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 nameFieldLabel !== 'string')
throw new TypeError('Name field label must be a string');
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
};
const openDialog = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (options == null) {
options = {
title: 'Open',
properties: ['openFile']
}
options = {
title: 'Open',
properties: ['openFile']
};
}
let { buttonLabel, defaultPath, filters, properties, title, message, securityScopedBookmarks = false } = options
if (properties == null) {
properties = ['openFile']
} else if (!Array.isArray(properties)) {
throw new TypeError('Properties must be an array')
}
let dialogProperties = 0
const { buttonLabel = '', defaultPath = '', filters = [], properties = ['openFile'], title = '', message = '', securityScopedBookmarks = false } = options;
if (!Array.isArray(properties))
throw new TypeError('Properties must be an array');
let dialogProperties = 0;
for (const prop in fileDialogProperties) {
if (properties.includes(prop)) {
dialogProperties |= fileDialogProperties[prop]
}
if (properties.includes(prop)) {
dialogProperties |= fileDialogProperties[prop];
}
}
if (title == null) {
title = ''
} else 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 defaultPath !== 'string')
throw new TypeError('Default path 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 };
settings.properties = dialogProperties;
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
};
const messageBox = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (buttonLabel == null) {
buttonLabel = ''
} else if (typeof buttonLabel !== 'string') {
throw new TypeError('Button label must be a string')
}
if (defaultPath == null) {
defaultPath = ''
} else if (typeof defaultPath !== 'string') {
throw new TypeError('Default path must be a string')
}
if (filters == null) {
filters = []
}
if (message == null) {
message = ''
} else if (typeof message !== 'string') {
throw new TypeError('Message must be a string')
}
const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) {
return success ? callback(result, bookmarkData) : callback()
} : null
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window }
settings.properties = dialogProperties
return binding.showOpenDialog(settings, wrappedCallback)
},
showSaveDialog: function (...args) {
checkAppInitialized()
let [window, options, callback] = parseArgs(...args)
if (options == null) {
options = {
title: 'Save'
}
}
let { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks = false, nameFieldLabel, showsTagField } = options
if (title == null) {
title = ''
} else if (typeof title !== 'string') {
throw new TypeError('Title must be a string')
}
if (buttonLabel == null) {
buttonLabel = ''
} else if (typeof buttonLabel !== 'string') {
throw new TypeError('Button label must be a string')
}
if (defaultPath == null) {
defaultPath = ''
} else if (typeof defaultPath !== 'string') {
throw new TypeError('Default path must be a string')
}
if (filters == null) {
filters = []
}
if (message == null) {
message = ''
} else if (typeof message !== 'string') {
throw new TypeError('Message must be a string')
}
if (nameFieldLabel == null) {
nameFieldLabel = ''
} else if (typeof nameFieldLabel !== 'string') {
throw new TypeError('Name field label must be a string')
}
if (showsTagField == null) {
showsTagField = true
}
const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) {
return success ? callback(result, bookmarkData) : callback()
} : null
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window }
return binding.showSaveDialog(settings, wrappedCallback)
},
showMessageBox: function (...args) {
checkAppInitialized()
let [window, options, callback] = parseArgs(...args)
if (options == null) {
options = {
type: 'none'
}
}
let {
buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail,
icon, message, title, type
} = options
if (type == null) {
type = 'none'
}
const messageBoxType = messageBoxTypes.indexOf(type)
if (messageBoxType === -1) {
throw new TypeError('Invalid message box type')
}
if (buttons == null) {
buttons = []
} else if (!Array.isArray(buttons)) {
throw new TypeError('Buttons must be an array')
}
if (options.normalizeAccessKeys) {
buttons = buttons.map(normalizeAccessKey)
}
if (title == null) {
title = ''
} else if (typeof title !== 'string') {
throw new TypeError('Title must be a string')
}
if (message == null) {
message = ''
} else if (typeof message !== 'string') {
throw new TypeError('Message must be a string')
}
if (detail == null) {
detail = ''
} else if (typeof detail !== 'string') {
throw new TypeError('Detail must be a string')
}
checkboxChecked = !!checkboxChecked
if (checkboxLabel == null) {
checkboxLabel = ''
} else if (typeof checkboxLabel !== 'string') {
throw new TypeError('checkboxLabel must be a string')
}
if (icon == null) {
icon = null
}
if (defaultId == null) {
defaultId = -1
}
if (options == null)
options = { type: 'none' };
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
const messageBoxOptions = { noLink: 1 << 0 };
let { buttons = [], cancelId, checkboxLabel = '', checkboxChecked, defaultId = -1, detail = '', icon = null, message = '', title = '', type = 'none' } = options;
const messageBoxType = messageBoxTypes.indexOf(type);
if (messageBoxType === -1)
throw new TypeError('Invalid message box type');
if (!Array.isArray(buttons))
throw new TypeError('Buttons must be an array');
if (options.normalizeAccessKeys)
buttons = buttons.map(normalizeAccessKey);
if (typeof title !== 'string')
throw new TypeError('Title 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 checkboxLabel !== 'string')
throw new TypeError('checkboxLabel must be a string');
checkboxChecked = !!checkboxChecked;
// Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) {
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0
for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase()
if (text === 'cancel' || text === 'no') {
cancelId = i
break
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') {
cancelId = i;
break;
}
}
}
}
const flags = options.noLink ? messageBoxOptions.noLink : 0
return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
flags, title, message, detail, checkboxLabel,
checkboxChecked, icon, window, callback)
},
showErrorBox: function (...args) {
return binding.showErrorBox(...args)
},
showCertificateTrustDialog: function (...args) {
const [window, options, callback] = parseArgs(...args)
if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object')
const flags = options.noLink ? messageBoxOptions.noLink : 0;
if (sync) {
return binding.showMessageBoxSync(messageBoxType, buttons, defaultId, cancelId, flags, title, message, detail, checkboxLabel, checkboxChecked, icon, window);
}
let { certificate, message } = options
if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object')
else {
return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, flags, title, message, detail, checkboxLabel, checkboxChecked, icon, window);
}
if (message == null) {
message = ''
} else if (typeof message !== 'string') {
throw new TypeError('message must be a string')
};
module.exports = {
showOpenDialog: function (window, options) {
return openDialog(false, window, options);
},
showOpenDialogSync: function (window, options) {
return openDialog(true, window, options);
},
showSaveDialog: function (window, options) {
return saveDialog(false, window, options);
},
showSaveDialogSync: function (window, options) {
return saveDialog(true, window, options);
},
showMessageBox: function (window, options) {
return messageBox(false, window, options);
},
showMessageBoxSync: function (window, options) {
return messageBox(true, window, options);
},
showErrorBox: function (...args) {
return binding.showErrorBox(...args);
},
showCertificateTrustDialog: function (window, options) {
if (window && window.constructor !== BrowserWindow)
options = window;
if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const { certificate, message = '' } = options;
if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object');
}
if (typeof message !== 'string')
throw new TypeError('message must be a string');
return binding.showCertificateTrustDialog(window, certificate, message);
}
return binding.showCertificateTrustDialog(window, certificate, message, callback)
}
}
// Mark standard asynchronous functions.
v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true)
};
module.exports.showMessageBox = deprecate.promisifyMultiArg(module.exports.showMessageBox, ({ response, checkboxChecked }) => [response, checkboxChecked]);
module.exports.showOpenDialog = deprecate.promisifyMultiArg(module.exports.showOpenDialog, ({ filePaths, bookmarks }) => [filePaths, bookmarks]);
module.exports.showSaveDialog = deprecate.promisifyMultiArg(module.exports.showSaveDialog, ({ filePath, bookmarks }) => [filePath, bookmarks]);
module.exports.showCertificateTrustDialog = deprecate.promisify(module.exports.showCertificateTrustDialog);

View File

@ -1,15 +1,18 @@
'use strict'
const common = require('@electron/internal/common/api/exports/electron')
'use strict';
const common = require('@electron/internal/common/api/exports/electron');
// since browser module list is also used in renderer, keep it separate.
const moduleList = require('@electron/internal/browser/api/module-list')
const moduleList = require('@electron/internal/browser/api/module-list');
// Import common modules.
common.defineProperties(exports)
common.defineProperties(exports);
for (const module of moduleList) {
Object.defineProperty(exports, module.name, {
enumerable: !module.private,
get: common.memoizedGetter(() => require(`@electron/internal/browser/api/${module.file}.js`))
})
Object.defineProperty(exports, module.name, {
enumerable: !module.private,
get: common.memoizedGetter(() => {
const value = require(`@electron/internal/browser/api/${module.file}.js`);
// Handle Typescript modules with an "export default X" statement
if (value.__esModule)
return value.default;
return value;
})
});
}

View File

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

View File

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

View File

@ -1,10 +1,7 @@
'use strict'
const { EventEmitter } = require('events')
const emitter = new EventEmitter()
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const emitter = new events_1.EventEmitter();
// Do not throw exception when channel name is "error".
emitter.on('error', () => {})
module.exports = emitter
emitter.on('error', () => { });
exports.default = emitter;

View File

@ -1,297 +1,308 @@
'use strict'
const { app } = require('electron')
'use strict';
const { app } = require('electron');
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
const roles = {
about: {
get label () {
return process.platform === 'linux' ? 'About' : `About ${app.getName()}`
}
},
close: {
label: process.platform === 'darwin' ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: 'close'
},
copy: {
label: 'Copy',
accelerator: 'CommandOrControl+C',
webContentsMethod: 'copy',
registerAccelerator: false
},
cut: {
label: 'Cut',
accelerator: 'CommandOrControl+X',
webContentsMethod: 'cut',
registerAccelerator: false
},
delete: {
label: 'Delete',
webContentsMethod: 'delete'
},
forcereload: {
label: 'Force Reload',
accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: (window) => {
window.webContents.reloadIgnoringCache()
}
},
front: {
label: 'Bring All to Front'
},
help: {
label: 'Help'
},
hide: {
get label () {
return `Hide ${app.getName()}`
about: {
get label() {
return isLinux ? 'About' : `About ${app.getName()}`;
}
},
accelerator: 'Command+H'
},
hideothers: {
label: 'Hide Others',
accelerator: 'Command+Alt+H'
},
minimize: {
label: 'Minimize',
accelerator: 'CommandOrControl+M',
windowMethod: 'minimize'
},
paste: {
label: 'Paste',
accelerator: 'CommandOrControl+V',
webContentsMethod: 'paste',
registerAccelerator: false
},
pasteandmatchstyle: {
label: 'Paste and Match Style',
accelerator: 'Shift+CommandOrControl+V',
webContentsMethod: 'pasteAndMatchStyle',
registerAccelerator: false
},
quit: {
get label () {
switch (process.platform) {
case 'darwin': return `Quit ${app.getName()}`
case 'win32': return 'Exit'
default: return 'Quit'
}
close: {
label: isMac ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: 'close'
},
accelerator: process.platform === 'win32' ? null : 'CommandOrControl+Q',
appMethod: 'quit'
},
redo: {
label: 'Redo',
accelerator: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z',
webContentsMethod: 'redo'
},
reload: {
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: 'reload'
},
resetzoom: {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.setZoomLevel(0)
copy: {
label: 'Copy',
accelerator: 'CommandOrControl+C',
webContentsMethod: 'copy',
registerAccelerator: false
},
cut: {
label: 'Cut',
accelerator: 'CommandOrControl+X',
webContentsMethod: 'cut',
registerAccelerator: false
},
delete: {
label: 'Delete',
webContentsMethod: 'delete'
},
forcereload: {
label: 'Force Reload',
accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: (window) => {
window.webContents.reloadIgnoringCache();
}
},
front: {
label: 'Bring All to Front'
},
help: {
label: 'Help'
},
hide: {
get label() {
return `Hide ${app.getName()}`;
},
accelerator: 'Command+H'
},
hideothers: {
label: 'Hide Others',
accelerator: 'Command+Alt+H'
},
minimize: {
label: 'Minimize',
accelerator: 'CommandOrControl+M',
windowMethod: 'minimize'
},
paste: {
label: 'Paste',
accelerator: 'CommandOrControl+V',
webContentsMethod: 'paste',
registerAccelerator: false
},
pasteandmatchstyle: {
label: 'Paste and Match Style',
accelerator: 'Shift+CommandOrControl+V',
webContentsMethod: 'pasteAndMatchStyle',
registerAccelerator: false
},
quit: {
get label() {
switch (process.platform) {
case 'darwin': return `Quit ${app.getName()}`;
case 'win32': return 'Exit';
default: return 'Quit';
}
},
accelerator: isWindows ? null : 'CommandOrControl+Q',
appMethod: 'quit'
},
redo: {
label: 'Redo',
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
webContentsMethod: 'redo'
},
reload: {
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: 'reload'
},
resetzoom: {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.setZoomLevel(0);
}
},
selectall: {
label: 'Select All',
accelerator: 'CommandOrControl+A',
webContentsMethod: 'selectAll'
},
services: {
label: 'Services'
},
recentdocuments: {
label: 'Open Recent'
},
clearrecentdocuments: {
label: 'Clear Menu'
},
startspeaking: {
label: 'Start Speaking'
},
stopspeaking: {
label: 'Stop Speaking'
},
toggledevtools: {
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
nonNativeMacOSRole: true,
windowMethod: 'toggleDevTools'
},
togglefullscreen: {
label: 'Toggle Full Screen',
accelerator: isMac ? 'Control+Command+F' : 'F11',
windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen());
}
},
undo: {
label: 'Undo',
accelerator: 'CommandOrControl+Z',
webContentsMethod: 'undo'
},
unhide: {
label: 'Show All'
},
window: {
label: 'Window'
},
zoom: {
label: 'Zoom'
},
zoomin: {
label: 'Zoom In',
accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
const zoomLevel = webContents.getZoomLevel();
webContents.setZoomLevel(zoomLevel + 0.5);
}
},
zoomout: {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
const zoomLevel = webContents.getZoomLevel();
webContents.setZoomLevel(zoomLevel - 0.5);
}
},
// App submenu should be used for Mac only
appmenu: {
get label() {
return app.getName();
},
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
// File submenu
filemenu: {
label: 'File',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// Edit submenu
editmenu: {
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
},
// View submenu
viewmenu: {
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// Window submenu
windowmenu: {
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' }
] : [
{ role: 'close' }
])
]
}
},
selectall: {
label: 'Select All',
accelerator: 'CommandOrControl+A',
webContentsMethod: 'selectAll'
},
services: {
label: 'Services'
},
recentdocuments: {
label: 'Open Recent'
},
clearrecentdocuments: {
label: 'Clear Menu'
},
startspeaking: {
label: 'Start Speaking'
},
stopspeaking: {
label: 'Stop Speaking'
},
toggledevtools: {
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
nonNativeMacOSRole: true,
windowMethod: 'toggleDevTools'
},
togglefullscreen: {
label: 'Toggle Full Screen',
accelerator: process.platform === 'darwin' ? 'Control+Command+F' : 'F11',
windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen())
}
},
undo: {
label: 'Undo',
accelerator: 'CommandOrControl+Z',
webContentsMethod: 'undo'
},
unhide: {
label: 'Show All'
},
window: {
label: 'Window'
},
zoom: {
label: 'Zoom'
},
zoomin: {
label: 'Zoom In',
accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.getZoomLevel((zoomLevel) => {
webContents.setZoomLevel(zoomLevel + 0.5)
})
}
},
zoomout: {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.getZoomLevel((zoomLevel) => {
webContents.setZoomLevel(zoomLevel - 0.5)
})
}
},
// Edit submenu (should fit both Mac & Windows)
editmenu: {
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
process.platform === 'darwin' ? {
role: 'pasteAndMatchStyle'
} : null,
{
role: 'delete'
},
process.platform === 'win32' ? {
type: 'separator'
} : null,
{
role: 'selectAll'
}
]
},
// Window submenu should be used for Mac only
windowmenu: {
label: 'Window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
},
process.platform === 'darwin' ? {
type: 'separator'
} : null,
process.platform === 'darwin' ? {
role: 'front'
} : null
]
}
}
};
const canExecuteRole = (role) => {
if (!roles.hasOwnProperty(role)) return false
if (process.platform !== 'darwin') return true
// macOS handles all roles natively except for a few
return roles[role].nonNativeMacOSRole
}
if (!roles.hasOwnProperty(role))
return false;
if (!isMac)
return true;
// macOS handles all roles natively except for a few
return roles[role].nonNativeMacOSRole;
};
exports.getDefaultLabel = (role) => {
return roles.hasOwnProperty(role) ? roles[role].label : ''
}
return roles.hasOwnProperty(role) ? roles[role].label : '';
};
exports.getDefaultAccelerator = (role) => {
if (roles.hasOwnProperty(role)) return roles[role].accelerator
}
if (roles.hasOwnProperty(role))
return roles[role].accelerator;
};
exports.shouldRegisterAccelerator = (role) => {
const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined
return hasRoleRegister ? roles[role].registerAccelerator : true
}
const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined;
return hasRoleRegister ? roles[role].registerAccelerator : true;
};
exports.getDefaultSubmenu = (role) => {
if (!roles.hasOwnProperty(role)) return
let { submenu } = roles[role]
// remove null items from within the submenu
if (Array.isArray(submenu)) {
submenu = submenu.filter((item) => item != null)
}
return submenu
}
if (!roles.hasOwnProperty(role))
return;
let { submenu } = roles[role];
// remove null items from within the submenu
if (Array.isArray(submenu)) {
submenu = submenu.filter((item) => item != null);
}
return submenu;
};
exports.execute = (role, focusedWindow, focusedWebContents) => {
if (!canExecuteRole(role)) return false
const { appMethod, webContentsMethod, windowMethod } = roles[role]
if (appMethod) {
app[appMethod]()
return true
}
if (windowMethod && focusedWindow != null) {
if (typeof windowMethod === 'function') {
windowMethod(focusedWindow)
} else {
focusedWindow[windowMethod]()
if (!canExecuteRole(role))
return false;
const { appMethod, webContentsMethod, windowMethod } = roles[role];
if (appMethod) {
app[appMethod]();
return true;
}
return true
}
if (webContentsMethod && focusedWebContents != null) {
if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents)
} else {
focusedWebContents[webContentsMethod]()
if (windowMethod && focusedWindow != null) {
if (typeof windowMethod === 'function') {
windowMethod(focusedWindow);
}
else {
focusedWindow[windowMethod]();
}
return true;
}
return true
}
return false
}
if (webContentsMethod && focusedWebContents != null) {
if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents);
}
else {
focusedWebContents[webContentsMethod]();
}
return true;
}
return false;
};

View File

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

View File

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

View File

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

View File

@ -1,46 +1,35 @@
'use strict'
const features = process.atomBinding('features')
'use strict';
const features = process.electronBinding('features');
// Browser side modules, please sort alphabetically.
module.exports = [
{ name: 'app', file: 'app' },
{ name: 'autoUpdater', file: 'auto-updater' },
{ name: 'BrowserView', file: 'browser-view' },
{ name: 'BrowserWindow', file: 'browser-window' },
{ name: 'contentTracing', file: 'content-tracing' },
{ name: 'crashReporter', file: 'crash-reporter' },
{ name: 'dialog', file: 'dialog' },
{ name: 'globalShortcut', file: 'global-shortcut' },
{ name: 'ipcMain', file: 'ipc-main' },
{ name: 'inAppPurchase', file: 'in-app-purchase' },
{ name: 'Menu', file: 'menu' },
{ name: 'MenuItem', file: 'menu-item' },
{ name: 'net', file: 'net' },
{ name: 'netLog', file: 'net-log' },
{ name: 'Notification', file: 'notification' },
{ name: 'powerMonitor', file: 'power-monitor' },
{ name: 'powerSaveBlocker', file: 'power-save-blocker' },
{ name: 'protocol', file: 'protocol' },
{ name: 'screen', file: 'screen' },
{ name: 'session', file: 'session' },
{ name: 'systemPreferences', file: 'system-preferences' },
{ name: 'TopLevelWindow', file: 'top-level-window' },
{ name: 'TouchBar', file: 'touch-bar' },
{ name: 'Tray', file: 'tray' },
{ name: 'View', file: 'view' },
{ name: 'webContents', file: 'web-contents' },
{ name: 'WebContentsView', file: 'web-contents-view' },
// The internal modules, invisible unless you know their names.
{ name: 'NavigationController', file: 'navigation-controller', private: true }
]
{ name: 'app', file: 'app' },
{ name: 'autoUpdater', file: 'auto-updater' },
{ name: 'BrowserView', file: 'browser-view' },
{ name: 'BrowserWindow', file: 'browser-window' },
{ name: 'contentTracing', file: 'content-tracing' },
{ name: 'crashReporter', file: 'crash-reporter' },
{ name: 'dialog', file: 'dialog' },
{ name: 'globalShortcut', file: 'global-shortcut' },
{ name: 'ipcMain', file: 'ipc-main' },
{ name: 'inAppPurchase', file: 'in-app-purchase' },
{ name: 'Menu', file: 'menu' },
{ name: 'MenuItem', file: 'menu-item' },
{ name: 'net', file: 'net' },
{ name: 'netLog', file: 'net-log' },
{ name: 'Notification', file: 'notification' },
{ name: 'powerMonitor', file: 'power-monitor' },
{ name: 'powerSaveBlocker', file: 'power-save-blocker' },
{ name: 'protocol', file: 'protocol' },
{ name: 'screen', file: 'screen' },
{ name: 'session', file: 'session' },
{ name: 'systemPreferences', file: 'system-preferences' },
{ name: 'TopLevelWindow', file: 'top-level-window' },
{ name: 'TouchBar', file: 'touch-bar' },
{ name: 'Tray', file: 'tray' },
{ name: 'View', file: 'view' },
{ name: 'webContents', file: 'web-contents' },
{ name: 'WebContentsView', file: 'web-contents-view' }
];
if (features.isViewApiEnabled()) {
module.exports.push(
{ name: 'BoxLayout', file: 'views/box-layout' },
{ name: 'Button', file: 'views/button' },
{ name: 'LabelButton', file: 'views/label-button' },
{ name: 'LayoutManager', file: 'views/layout-manager' },
{ name: 'TextField', file: 'views/text-field' }
)
module.exports.push({ name: 'BoxLayout', file: 'views/box-layout' }, { name: 'Button', file: 'views/button' }, { name: 'LabelButton', file: 'views/label-button' }, { name: 'LayoutManager', file: 'views/layout-manager' }, { name: 'MdTextButton', file: 'views/md-text-button' }, { name: 'ResizeArea', file: 'views/resize-area' }, { name: 'TextField', file: 'views/text-field' });
}

View File

@ -1,179 +0,0 @@
'use strict'
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
// The history operation in renderer is redirected to browser.
ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) {
event.sender.goBack()
})
ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) {
event.sender.goForward()
})
ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) {
event.sender.goToOffset(offset)
})
ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
event.returnValue = event.sender.length()
})
// JavaScript implementation of Chromium's NavigationController.
// Instead of relying on Chromium for history control, we compeletely do history
// control on user land, and only rely on WebContents.loadURL for navigation.
// This helps us avoid Chromium's various optimizations so we can ensure renderer
// process is restarted everytime.
const NavigationController = (function () {
function NavigationController (webContents) {
this.webContents = webContents
this.clearHistory()
// webContents may have already navigated to a page.
if (this.webContents._getURL()) {
this.currentIndex++
this.history.push(this.webContents._getURL())
}
this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => {
if (this.inPageIndex > -1 && !inPage) {
// Navigated to a new page, clear in-page mark.
this.inPageIndex = -1
} else if (this.inPageIndex === -1 && inPage && !replaceEntry) {
// Started in-page navigations.
this.inPageIndex = this.currentIndex
}
if (this.pendingIndex >= 0) {
// Go to index.
this.currentIndex = this.pendingIndex
this.pendingIndex = -1
this.history[this.currentIndex] = url
} else if (replaceEntry) {
// Non-user initialized navigation.
this.history[this.currentIndex] = url
} else {
// Normal navigation. Clear history.
this.history = this.history.slice(0, this.currentIndex + 1)
this.currentIndex++
this.history.push(url)
}
})
}
NavigationController.prototype.loadURL = function (url, options) {
if (options == null) {
options = {}
}
this.pendingIndex = -1
this.webContents._loadURL(url, options)
return this.webContents.emit('load-url', url, options)
}
NavigationController.prototype.getURL = function () {
if (this.currentIndex === -1) {
return ''
} else {
return this.history[this.currentIndex]
}
}
NavigationController.prototype.stop = function () {
this.pendingIndex = -1
return this.webContents._stop()
}
NavigationController.prototype.reload = function () {
this.pendingIndex = this.currentIndex
return this.webContents._loadURL(this.getURL(), {})
}
NavigationController.prototype.reloadIgnoringCache = function () {
this.pendingIndex = this.currentIndex
return this.webContents._loadURL(this.getURL(), {
extraHeaders: 'pragma: no-cache\n'
})
}
NavigationController.prototype.canGoBack = function () {
return this.getActiveIndex() > 0
}
NavigationController.prototype.canGoForward = function () {
return this.getActiveIndex() < this.history.length - 1
}
NavigationController.prototype.canGoToIndex = function (index) {
return index >= 0 && index < this.history.length
}
NavigationController.prototype.canGoToOffset = function (offset) {
return this.canGoToIndex(this.currentIndex + offset)
}
NavigationController.prototype.clearHistory = function () {
this.history = []
this.currentIndex = -1
this.pendingIndex = -1
this.inPageIndex = -1
}
NavigationController.prototype.goBack = function () {
if (!this.canGoBack()) {
return
}
this.pendingIndex = this.getActiveIndex() - 1
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goBack()
} else {
return this.webContents._loadURL(this.history[this.pendingIndex], {})
}
}
NavigationController.prototype.goForward = function () {
if (!this.canGoForward()) {
return
}
this.pendingIndex = this.getActiveIndex() + 1
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goForward()
} else {
return this.webContents._loadURL(this.history[this.pendingIndex], {})
}
}
NavigationController.prototype.goToIndex = function (index) {
if (!this.canGoToIndex(index)) {
return
}
this.pendingIndex = index
return this.webContents._loadURL(this.history[this.pendingIndex], {})
}
NavigationController.prototype.goToOffset = function (offset) {
if (!this.canGoToOffset(offset)) {
return
}
const pendingIndex = this.currentIndex + offset
if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) {
this.pendingIndex = pendingIndex
return this.webContents._goToOffset(offset)
} else {
return this.goToIndex(pendingIndex)
}
}
NavigationController.prototype.getActiveIndex = function () {
if (this.pendingIndex === -1) {
return this.currentIndex
} else {
return this.pendingIndex
}
}
NavigationController.prototype.length = function () {
return this.history.length
}
return NavigationController
})()
module.exports = NavigationController

View File

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

View File

@ -1,372 +1,357 @@
'use strict'
const url = require('url')
const { EventEmitter } = require('events')
const { Readable } = require('stream')
const { app } = require('electron')
const { Session } = process.atomBinding('session')
const { net, Net } = process.atomBinding('net')
const { URLRequest } = net
'use strict';
const url = require('url');
const { EventEmitter } = require('events');
const { Readable } = require('stream');
const { app } = require('electron');
const { Session } = process.electronBinding('session');
const { net, Net } = process.electronBinding('net');
const { URLRequest } = net;
// Net is an EventEmitter.
Object.setPrototypeOf(Net.prototype, EventEmitter.prototype)
EventEmitter.call(net)
Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
const kSupportedProtocols = new Set(['http:', 'https:'])
Object.setPrototypeOf(Net.prototype, EventEmitter.prototype);
EventEmitter.call(net);
Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype);
const kSupportedProtocols = new Set(['http:', 'https:']);
// set of headers that Node.js discards duplicates for
// see https://nodejs.org/api/http.html#http_message_headers
const discardableDuplicateHeaders = new Set([
'content-type',
'content-length',
'user-agent',
'referer',
'host',
'authorization',
'proxy-authorization',
'if-modified-since',
'if-unmodified-since',
'from',
'location',
'max-forwards',
'retry-after',
'etag',
'last-modified',
'server',
'age',
'expires'
]);
class IncomingMessage extends Readable {
constructor (urlRequest) {
super()
this.urlRequest = urlRequest
this.shouldPush = false
this.data = []
this.urlRequest.on('data', (event, chunk) => {
this._storeInternalData(chunk)
this._pushInternalData()
})
this.urlRequest.on('end', () => {
this._storeInternalData(null)
this._pushInternalData()
})
}
get statusCode () {
return this.urlRequest.statusCode
}
get statusMessage () {
return this.urlRequest.statusMessage
}
get headers () {
return this.urlRequest.rawResponseHeaders
}
get httpVersion () {
return `${this.httpVersionMajor}.${this.httpVersionMinor}`
}
get httpVersionMajor () {
return this.urlRequest.httpVersionMajor
}
get httpVersionMinor () {
return this.urlRequest.httpVersionMinor
}
get rawTrailers () {
throw new Error('HTTP trailers are not supported.')
}
get trailers () {
throw new Error('HTTP trailers are not supported.')
}
_storeInternalData (chunk) {
this.data.push(chunk)
}
_pushInternalData () {
while (this.shouldPush && this.data.length > 0) {
const chunk = this.data.shift()
this.shouldPush = this.push(chunk)
constructor(urlRequest) {
super();
this.urlRequest = urlRequest;
this.shouldPush = false;
this.data = [];
this.urlRequest.on('data', (event, chunk) => {
this._storeInternalData(chunk);
this._pushInternalData();
});
this.urlRequest.on('end', () => {
this._storeInternalData(null);
this._pushInternalData();
});
}
get statusCode() {
return this.urlRequest.statusCode;
}
get statusMessage() {
return this.urlRequest.statusMessage;
}
get headers() {
const filteredHeaders = {};
const rawHeaders = this.urlRequest.rawResponseHeaders;
Object.keys(rawHeaders).forEach(header => {
if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
// do nothing with discardable duplicate headers
}
else {
if (header === 'set-cookie') {
// keep set-cookie as an array per Node.js rules
// see https://nodejs.org/api/http.html#http_message_headers
filteredHeaders[header] = rawHeaders[header];
}
else {
// for non-cookie headers, the values are joined together with ', '
filteredHeaders[header] = rawHeaders[header].join(', ');
}
}
});
return filteredHeaders;
}
get httpVersion() {
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
}
get httpVersionMajor() {
return this.urlRequest.httpVersionMajor;
}
get httpVersionMinor() {
return this.urlRequest.httpVersionMinor;
}
get rawTrailers() {
throw new Error('HTTP trailers are not supported.');
}
get trailers() {
throw new Error('HTTP trailers are not supported.');
}
_storeInternalData(chunk) {
this.data.push(chunk);
}
_pushInternalData() {
while (this.shouldPush && this.data.length > 0) {
const chunk = this.data.shift();
this.shouldPush = this.push(chunk);
}
}
_read() {
this.shouldPush = true;
this._pushInternalData();
}
}
_read () {
this.shouldPush = true
this._pushInternalData()
}
}
URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) {
if (isAsync) {
process.nextTick(() => {
this.clientRequest.emit(...rest)
})
} else {
this.clientRequest.emit(...rest)
}
}
if (isAsync) {
process.nextTick(() => {
this.clientRequest.emit(...rest);
});
}
else {
this.clientRequest.emit(...rest);
}
};
URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) {
if (isAsync) {
process.nextTick(() => {
this._response.emit(...rest)
})
} else {
this._response.emit(...rest)
}
}
if (isAsync) {
process.nextTick(() => {
this._response.emit(...rest);
});
}
else {
this._response.emit(...rest);
}
};
class ClientRequest extends EventEmitter {
constructor (options, callback) {
super()
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready')
}
if (typeof options === 'string') {
options = url.parse(options)
} else {
options = Object.assign({}, options)
}
const method = (options.method || 'GET').toUpperCase()
let urlStr = options.url
if (!urlStr) {
const urlObj = {}
const protocol = options.protocol || 'http:'
if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported. ')
}
urlObj.protocol = protocol
if (options.host) {
urlObj.host = options.host
} else {
if (options.hostname) {
urlObj.hostname = options.hostname
} else {
urlObj.hostname = 'localhost'
constructor(options, callback) {
super();
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready');
}
if (options.port) {
urlObj.port = options.port
if (typeof options === 'string') {
options = url.parse(options);
}
}
if (options.path && / /.test(options.path)) {
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
// with an additional rule for ignoring percentage-escaped characters
// but that's a) hard to capture in a regular expression that performs
// well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create
// an invalid request.
throw new TypeError('Request path contains unescaped characters.')
}
const pathObj = url.parse(options.path || '/')
urlObj.pathname = pathObj.pathname
urlObj.search = pathObj.search
urlObj.hash = pathObj.hash
urlStr = url.format(urlObj)
}
const redirectPolicy = options.redirect || 'follow'
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual')
}
const urlRequestOptions = {
method: method,
url: urlStr,
redirect: redirectPolicy
}
if (options.session) {
if (options.session instanceof Session) {
urlRequestOptions.session = options.session
} else {
throw new TypeError('`session` should be an instance of the Session class.')
}
} else if (options.partition) {
if (typeof options.partition === 'string') {
urlRequestOptions.partition = options.partition
} else {
throw new TypeError('`partition` should be an a string.')
}
}
const urlRequest = new URLRequest(urlRequestOptions)
// Set back and forward links.
this.urlRequest = urlRequest
urlRequest.clientRequest = this
// This is a copy of the extra headers structure held by the native
// net::URLRequest. The main reason is to keep the getHeader API synchronous
// after the request starts.
this.extraHeaders = {}
if (options.headers) {
for (const key in options.headers) {
this.setHeader(key, options.headers[key])
}
}
// Set when the request uses chunked encoding. Can be switched
// to true only once and never set back to false.
this.chunkedEncodingEnabled = false
urlRequest.on('response', () => {
const response = new IncomingMessage(urlRequest)
urlRequest._response = response
this.emit('response', response)
})
urlRequest.on('login', (event, authInfo, callback) => {
this.emit('login', authInfo, (username, password) => {
// If null or undefined username/password, force to empty string.
if (username === null || username === undefined) {
username = ''
else {
options = Object.assign({}, options);
}
if (typeof username !== 'string') {
throw new Error('username must be a string')
const method = (options.method || 'GET').toUpperCase();
let urlStr = options.url;
if (!urlStr) {
const urlObj = {};
const protocol = options.protocol || 'http:';
if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported. ');
}
urlObj.protocol = protocol;
if (options.host) {
urlObj.host = options.host;
}
else {
if (options.hostname) {
urlObj.hostname = options.hostname;
}
else {
urlObj.hostname = 'localhost';
}
if (options.port) {
urlObj.port = options.port;
}
}
if (options.path && / /.test(options.path)) {
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
// with an additional rule for ignoring percentage-escaped characters
// but that's a) hard to capture in a regular expression that performs
// well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create
// an invalid request.
throw new TypeError('Request path contains unescaped characters.');
}
const pathObj = url.parse(options.path || '/');
urlObj.pathname = pathObj.pathname;
urlObj.search = pathObj.search;
urlObj.hash = pathObj.hash;
urlStr = url.format(urlObj);
}
if (password === null || password === undefined) {
password = ''
const redirectPolicy = options.redirect || 'follow';
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual');
}
if (typeof password !== 'string') {
throw new Error('password must be a string')
const urlRequestOptions = {
method: method,
url: urlStr,
redirect: redirectPolicy
};
if (options.session) {
if (options.session instanceof Session) {
urlRequestOptions.session = options.session;
}
else {
throw new TypeError('`session` should be an instance of the Session class.');
}
}
else if (options.partition) {
if (typeof options.partition === 'string') {
urlRequestOptions.partition = options.partition;
}
else {
throw new TypeError('`partition` should be an a string.');
}
}
const urlRequest = new URLRequest(urlRequestOptions);
// Set back and forward links.
this.urlRequest = urlRequest;
urlRequest.clientRequest = this;
// This is a copy of the extra headers structure held by the native
// net::URLRequest. The main reason is to keep the getHeader API synchronous
// after the request starts.
this.extraHeaders = {};
if (options.headers) {
for (const key in options.headers) {
this.setHeader(key, options.headers[key]);
}
}
// Set when the request uses chunked encoding. Can be switched
// to true only once and never set back to false.
this.chunkedEncodingEnabled = false;
urlRequest.on('response', () => {
const response = new IncomingMessage(urlRequest);
urlRequest._response = response;
this.emit('response', response);
});
urlRequest.on('login', (event, authInfo, callback) => {
this.emit('login', authInfo, (username, password) => {
// If null or undefined username/password, force to empty string.
if (username === null || username === undefined) {
username = '';
}
if (typeof username !== 'string') {
throw new Error('username must be a string');
}
if (password === null || password === undefined) {
password = '';
}
if (typeof password !== 'string') {
throw new Error('password must be a string');
}
callback(username, password);
});
});
if (callback) {
this.once('response', callback);
}
callback(username, password)
})
})
if (callback) {
this.once('response', callback)
}
}
get chunkedEncoding () {
return this.chunkedEncodingEnabled
}
set chunkedEncoding (value) {
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t set the transfer encoding, headers have been sent.')
get chunkedEncoding() {
return this.chunkedEncodingEnabled;
}
this.chunkedEncodingEnabled = value
}
setHeader (name, value) {
if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value).')
set chunkedEncoding(value) {
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t set the transfer encoding, headers have been sent.');
}
this.chunkedEncodingEnabled = value;
}
if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value).')
setHeader(name, value) {
if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value).');
}
if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value).');
}
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t set headers after they are sent.');
}
const key = name.toLowerCase();
this.extraHeaders[key] = value;
this.urlRequest.setExtraHeader(name, value.toString());
}
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t set headers after they are sent.')
getHeader(name) {
if (name == null) {
throw new Error('`name` is required for getHeader(name).');
}
if (!this.extraHeaders) {
return;
}
const key = name.toLowerCase();
return this.extraHeaders[key];
}
const key = name.toLowerCase()
this.extraHeaders[key] = value
this.urlRequest.setExtraHeader(name, value.toString())
}
getHeader (name) {
if (name == null) {
throw new Error('`name` is required for getHeader(name).')
removeHeader(name) {
if (name == null) {
throw new Error('`name` is required for removeHeader(name).');
}
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t remove headers after they are sent.');
}
const key = name.toLowerCase();
delete this.extraHeaders[key];
this.urlRequest.removeExtraHeader(name);
}
if (!this.extraHeaders) {
return
_write(chunk, encoding, callback, isLast) {
const chunkIsString = typeof chunk === 'string';
const chunkIsBuffer = chunk instanceof Buffer;
if (!chunkIsString && !chunkIsBuffer) {
throw new TypeError('First argument must be a string or Buffer.');
}
if (chunkIsString) {
// We convert all strings into binary buffers.
chunk = Buffer.from(chunk, encoding);
}
// Since writing to the network is asynchronous, we conservatively
// assume that request headers are written after delivering the first
// buffer to the network IO thread.
if (this.urlRequest.notStarted) {
this.urlRequest.setChunkedUpload(this.chunkedEncoding);
}
// Headers are assumed to be sent on first call to _writeBuffer,
// i.e. after the first call to write or end.
const result = this.urlRequest.write(chunk, isLast);
// The write callback is fired asynchronously to mimic Node.js.
if (callback) {
process.nextTick(callback);
}
return result;
}
const key = name.toLowerCase()
return this.extraHeaders[key]
}
removeHeader (name) {
if (name == null) {
throw new Error('`name` is required for removeHeader(name).')
write(data, encoding, callback) {
if (this.urlRequest.finished) {
const error = new Error('Write after end.');
process.nextTick(writeAfterEndNT, this, error, callback);
return true;
}
return this._write(data, encoding, callback, false);
}
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t remove headers after they are sent.')
end(data, encoding, callback) {
if (this.urlRequest.finished) {
return false;
}
if (typeof data === 'function') {
callback = data;
encoding = null;
data = null;
}
else if (typeof encoding === 'function') {
callback = encoding;
encoding = null;
}
data = data || '';
return this._write(data, encoding, callback, true);
}
const key = name.toLowerCase()
delete this.extraHeaders[key]
this.urlRequest.removeExtraHeader(name)
}
_write (chunk, encoding, callback, isLast) {
const chunkIsString = typeof chunk === 'string'
const chunkIsBuffer = chunk instanceof Buffer
if (!chunkIsString && !chunkIsBuffer) {
throw new TypeError('First argument must be a string or Buffer.')
followRedirect() {
this.urlRequest.followRedirect();
}
if (chunkIsString) {
// We convert all strings into binary buffers.
chunk = Buffer.from(chunk, encoding)
abort() {
this.urlRequest.cancel();
}
// Since writing to the network is asynchronous, we conservatively
// assume that request headers are written after delivering the first
// buffer to the network IO thread.
if (this.urlRequest.notStarted) {
this.urlRequest.setChunkedUpload(this.chunkedEncoding)
getUploadProgress() {
return this.urlRequest.getUploadProgress();
}
// Headers are assumed to be sent on first call to _writeBuffer,
// i.e. after the first call to write or end.
const result = this.urlRequest.write(chunk, isLast)
// The write callback is fired asynchronously to mimic Node.js.
if (callback) {
process.nextTick(callback)
}
return result
}
write (data, encoding, callback) {
if (this.urlRequest.finished) {
const error = new Error('Write after end.')
process.nextTick(writeAfterEndNT, this, error, callback)
return true
}
return this._write(data, encoding, callback, false)
}
end (data, encoding, callback) {
if (this.urlRequest.finished) {
return false
}
if (typeof data === 'function') {
callback = data
encoding = null
data = null
} else if (typeof encoding === 'function') {
callback = encoding
encoding = null
}
data = data || ''
return this._write(data, encoding, callback, true)
}
followRedirect () {
this.urlRequest.followRedirect()
}
abort () {
this.urlRequest.cancel()
}
getUploadProgress () {
return this.urlRequest.getUploadProgress()
}
}
function writeAfterEndNT (self, error, callback) {
self.emit('error', error)
if (callback) callback(error)
function writeAfterEndNT(self, error, callback) {
self.emit('error', error);
if (callback)
callback(error);
}
Net.prototype.request = function (options, callback) {
return new ClientRequest(options, callback)
}
net.ClientRequest = ClientRequest
module.exports = net
return new ClientRequest(options, callback);
};
net.ClientRequest = ClientRequest;
module.exports = net;

View File

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

View File

@ -1,25 +1,42 @@
'use strict'
const { EventEmitter } = require('events')
const { powerMonitor, PowerMonitor } = process.atomBinding('power_monitor')
'use strict';
const { EventEmitter } = require('events');
const { powerMonitor, PowerMonitor } = process.electronBinding('power_monitor');
const { deprecate } = require('electron');
// PowerMonitor is an EventEmitter.
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
EventEmitter.call(powerMonitor)
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype);
EventEmitter.call(powerMonitor);
// On Linux we need to call blockShutdown() to subscribe to shutdown event.
if (process.platform === 'linux') {
powerMonitor.on('newListener', (event) => {
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.blockShutdown()
}
})
powerMonitor.on('removeListener', (event) => {
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.unblockShutdown()
}
})
powerMonitor.on('newListener', (event) => {
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.blockShutdown();
}
});
powerMonitor.on('removeListener', (event) => {
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.unblockShutdown();
}
});
}
module.exports = powerMonitor
// TODO(nitsakh): Remove in 7.0
powerMonitor.querySystemIdleState = function (threshold, callback) {
deprecate.warn('powerMonitor.querySystemIdleState', 'powerMonitor.getSystemIdleState');
if (typeof threshold !== 'number') {
throw new Error('Must pass threshold as a number');
}
if (typeof callback !== 'function') {
throw new Error('Must pass callback as a function argument');
}
const idleState = this.getSystemIdleState(threshold);
process.nextTick(() => callback(idleState));
};
// TODO(nitsakh): Remove in 7.0
powerMonitor.querySystemIdleTime = function (callback) {
deprecate.warn('powerMonitor.querySystemIdleTime', 'powerMonitor.getSystemIdleTime');
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument');
}
const idleTime = this.getSystemIdleTime();
process.nextTick(() => callback(idleTime));
};
module.exports = powerMonitor;

View File

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

View File

@ -1,29 +1,26 @@
'use strict'
const { app, session } = require('electron')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
// Global protocol APIs.
module.exports = process.atomBinding('protocol')
const protocol = process.electronBinding('protocol');
// Fallback protocol APIs of default session.
Object.setPrototypeOf(module.exports, new Proxy({}, {
get (target, property) {
if (!app.isReady()) return
const protocol = session.defaultSession.protocol
if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return
// Returning a native function directly would throw error.
return (...args) => protocol[property](...args)
},
ownKeys () {
if (!app.isReady()) return []
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.protocol))
},
getOwnPropertyDescriptor (target) {
return { configurable: true, enumerable: true }
}
}))
Object.setPrototypeOf(protocol, new Proxy({}, {
get(_target, property) {
if (!electron_1.app.isReady())
return;
const protocol = electron_1.session.defaultSession.protocol;
if (!Object.getPrototypeOf(protocol).hasOwnProperty(property))
return;
// Returning a native function directly would throw error.
return (...args) => protocol[property](...args);
},
ownKeys() {
if (!electron_1.app.isReady())
return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(electron_1.session.defaultSession.protocol));
},
getOwnPropertyDescriptor() {
return { configurable: true, enumerable: true };
}
}));
exports.default = protocol;

View File

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

View File

@ -1,24 +1,40 @@
'use strict'
const { EventEmitter } = require('events')
const { app } = require('electron')
const { fromPartition, Session, Cookies } = process.atomBinding('session')
'use strict';
const { EventEmitter } = require('events');
const { app, deprecate } = require('electron');
const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session');
// Public API.
Object.defineProperties(exports, {
defaultSession: {
enumerable: true,
get () { return fromPartition('') }
},
fromPartition: {
enumerable: true,
value: fromPartition
}
})
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype)
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype)
defaultSession: {
enumerable: true,
get() { return fromPartition(''); }
},
fromPartition: {
enumerable: true,
value: fromPartition
}
});
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype);
Session.prototype._init = function () {
app.emit('session-created', this)
}
app.emit('session-created', this);
};
Session.prototype.clearStorageData = deprecate.promisify(Session.prototype.clearStorageData);
Session.prototype.clearHostResolverCache = deprecate.promisify(Session.prototype.clearHostResolverCache);
Session.prototype.resolveProxy = deprecate.promisify(Session.prototype.resolveProxy);
Session.prototype.setProxy = deprecate.promisify(Session.prototype.setProxy);
Session.prototype.getCacheSize = deprecate.promisify(Session.prototype.getCacheSize);
Session.prototype.clearCache = deprecate.promisify(Session.prototype.clearCache);
Session.prototype.clearAuthCache = deprecate.promisify(Session.prototype.clearAuthCache);
Session.prototype.getBlobData = deprecate.promisifyMultiArg(Session.prototype.getBlobData);
Session.prototype.clearAuthCache = ((clearAuthCache) => function (...args) {
if (args.length > 0) {
deprecate.log(`The 'options' argument to 'clearAuthCache' is deprecated. Beginning with Electron 7, clearAuthCache will clear the entire auth cache unconditionally.`);
}
return clearAuthCache.apply(this, args);
})(Session.prototype.clearAuthCache);
Cookies.prototype.flushStore = deprecate.promisify(Cookies.prototype.flushStore);
Cookies.prototype.get = deprecate.promisify(Cookies.prototype.get);
Cookies.prototype.remove = deprecate.promisify(Cookies.prototype.remove);
Cookies.prototype.set = deprecate.promisify(Cookies.prototype.set);
NetLog.prototype.stopLogging = deprecate.promisify(NetLog.prototype.stopLogging);
Protocol.prototype.isProtocolHandled = deprecate.promisify(Protocol.prototype.isProtocolHandled);

View File

@ -1,10 +1,7 @@
'use strict'
const { EventEmitter } = require('events')
const { systemPreferences, SystemPreferences } = process.atomBinding('system_preferences')
'use strict';
const { EventEmitter } = require('events');
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences');
// SystemPreferences is an EventEmitter.
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
EventEmitter.call(systemPreferences)
module.exports = systemPreferences
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype);
EventEmitter.call(systemPreferences);
module.exports = systemPreferences;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,507 +1,446 @@
'use strict'
const features = process.atomBinding('features')
const { EventEmitter } = require('events')
const electron = require('electron')
const path = require('path')
const url = require('url')
const v8Util = process.atomBinding('v8_util')
const { app, ipcMain, session, NavigationController, deprecate } = electron
const ipcMainInternal = require('@electron/internal/browser/ipc-main-internal')
const errorUtils = require('@electron/internal/common/error-utils')
'use strict';
const features = process.electronBinding('features');
const { EventEmitter } = require('events');
const electron = require('electron');
const path = require('path');
const url = require('url');
const { app, ipcMain, session, deprecate } = electron;
const NavigationController = require('@electron/internal/browser/navigation-controller');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const errorUtils = require('@electron/internal/common/error-utils');
// session is not used here, the purpose is to make sure session is initalized
// before the webContents module.
// eslint-disable-next-line
session
let nextId = 0
session;
let nextId = 0;
const getNextId = function () {
return ++nextId
}
return ++nextId;
};
// Stock page sizes
const PDFPageSizes = {
A5: {
custom_display_name: 'A5',
height_microns: 210000,
name: 'ISO_A5',
width_microns: 148000
},
A4: {
custom_display_name: 'A4',
height_microns: 297000,
name: 'ISO_A4',
is_default: 'true',
width_microns: 210000
},
A3: {
custom_display_name: 'A3',
height_microns: 420000,
name: 'ISO_A3',
width_microns: 297000
},
Legal: {
custom_display_name: 'Legal',
height_microns: 355600,
name: 'NA_LEGAL',
width_microns: 215900
},
Letter: {
custom_display_name: 'Letter',
height_microns: 279400,
name: 'NA_LETTER',
width_microns: 215900
},
Tabloid: {
height_microns: 431800,
name: 'NA_LEDGER',
width_microns: 279400,
custom_display_name: 'Tabloid'
}
}
A5: {
custom_display_name: 'A5',
height_microns: 210000,
name: 'ISO_A5',
width_microns: 148000
},
A4: {
custom_display_name: 'A4',
height_microns: 297000,
name: 'ISO_A4',
is_default: 'true',
width_microns: 210000
},
A3: {
custom_display_name: 'A3',
height_microns: 420000,
name: 'ISO_A3',
width_microns: 297000
},
Legal: {
custom_display_name: 'Legal',
height_microns: 355600,
name: 'NA_LEGAL',
width_microns: 215900
},
Letter: {
custom_display_name: 'Letter',
height_microns: 279400,
name: 'NA_LETTER',
width_microns: 215900
},
Tabloid: {
height_microns: 431800,
name: 'NA_LEDGER',
width_microns: 279400,
custom_display_name: 'Tabloid'
}
};
// Default printing setting
const defaultPrintingSetting = {
pageRage: [],
mediaSize: {},
landscape: false,
color: 2,
headerFooterEnabled: false,
marginsType: 0,
isFirstRequest: false,
requestID: getNextId(),
previewUIID: 0,
previewModifiable: true,
printToPDF: true,
printWithCloudPrint: false,
printWithPrivet: false,
printWithExtension: false,
deviceName: 'Save as PDF',
generateDraftData: true,
fitToPageEnabled: false,
scaleFactor: 1,
dpiHorizontal: 72,
dpiVertical: 72,
rasterizePDF: false,
duplex: 0,
copies: 1,
collate: true,
shouldPrintBackgrounds: false,
shouldPrintSelectionOnly: false
}
pageRage: [],
mediaSize: {},
landscape: false,
color: 2,
headerFooterEnabled: false,
marginsType: 0,
isFirstRequest: false,
requestID: getNextId(),
previewUIID: 0,
previewModifiable: true,
printToPDF: true,
printWithCloudPrint: false,
printWithPrivet: false,
printWithExtension: false,
pagesPerSheet: 1,
deviceName: 'Save as PDF',
generateDraftData: true,
fitToPageEnabled: false,
scaleFactor: 1,
dpiHorizontal: 72,
dpiVertical: 72,
rasterizePDF: false,
duplex: 0,
copies: 1,
collate: true,
shouldPrintBackgrounds: false,
shouldPrintSelectionOnly: false
};
// JavaScript implementations of WebContents.
const binding = process.atomBinding('web_contents')
const { WebContents } = binding
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype)
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype)
const binding = process.electronBinding('web_contents');
const { WebContents } = binding;
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype);
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype);
// WebContents::send(channel, args..)
// WebContents::sendToAll(channel, args..)
WebContents.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
}
const internal = false
const sendToAll = false
return this._send(internal, sendToAll, channel, args)
}
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
const internal = false;
const sendToAll = false;
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype.sendToAll = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
}
const internal = false
const sendToAll = true
return this._send(internal, sendToAll, channel, args)
}
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
const internal = false;
const sendToAll = true;
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
}
const internal = true
const sendToAll = false
return this._send(internal, sendToAll, channel, args)
}
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
const internal = true;
const sendToAll = false;
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype._sendInternalToAll = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
}
const internal = true
const sendToAll = true
return this._send(internal, sendToAll, channel, args)
}
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
const internal = true;
const sendToAll = true;
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument');
}
const internal = false;
const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
};
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument');
}
const internal = true;
const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
};
// Following methods are mapped to webFrame.
const webFrameMethods = [
'insertCSS',
'insertText',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits'
]
const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
return new Promise((resolve, reject) => {
ipcMainInternal.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) {
if (error == null) {
if (typeof callback === 'function') callback(result)
resolve(result)
} else {
reject(errorUtils.deserialize(error))
}
})
this._sendInternal('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args)
})
}
'insertCSS',
'insertText',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits'
];
for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args) {
this._sendInternal('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args)
}
WebContents.prototype[method] = function (...args) {
ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
};
}
const executeJavaScript = (contents, code, hasUserGesture) => {
return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture);
};
// Make sure WebContents::executeJavaScript would run the code only when the
// WebContents has been loaded.
WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) {
const requestId = getNextId()
if (typeof hasUserGesture === 'function') {
// Shift.
callback = hasUserGesture
hasUserGesture = null
}
if (hasUserGesture == null) {
hasUserGesture = false
}
if (this.getURL() && !this.isLoadingMainFrame()) {
return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture)
} else {
return new Promise((resolve, reject) => {
this.once('did-stop-loading', () => {
asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject)
})
})
}
}
WebContents.prototype.takeHeapSnapshot = function (filePath) {
return new Promise((resolve, reject) => {
const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
ipcMainInternal.once(channel, (event, success) => {
if (success) {
resolve()
} else {
reject(new Error('takeHeapSnapshot failed'))
}
})
if (!this._takeHeapSnapshot(filePath, channel)) {
ipcMainInternal.emit(channel, false)
WebContents.prototype.executeJavaScript = function (code, hasUserGesture) {
if (this.getURL() && !this.isLoadingMainFrame()) {
return executeJavaScript(this, code, hasUserGesture);
}
})
}
// Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options, callback) {
const printingSetting = Object.assign({}, defaultPrintingSetting)
if (options.landscape) {
printingSetting.landscape = options.landscape
}
if (options.marginsType) {
printingSetting.marginsType = options.marginsType
}
if (options.printSelectionOnly) {
printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
}
if (options.printBackground) {
printingSetting.shouldPrintBackgrounds = options.printBackground
}
if (options.pageSize) {
const pageSize = options.pageSize
if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) {
return callback(new Error('Must define height and width for pageSize'))
}
// Dimensions in Microns
// 1 meter = 10^6 microns
printingSetting.mediaSize = {
name: 'CUSTOM',
custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width)
}
} else if (PDFPageSizes[pageSize]) {
printingSetting.mediaSize = PDFPageSizes[pageSize]
} else {
return callback(new Error(`Does not support pageSize with ${pageSize}`))
else {
return new Promise((resolve, reject) => {
this.once('did-stop-loading', () => {
executeJavaScript(this, code, hasUserGesture).then(resolve, reject);
});
});
}
} else {
printingSetting.mediaSize = PDFPageSizes['A4']
}
// Chromium expects this in a 0-100 range number, not as float
printingSetting.scaleFactor *= 100
if (features.isPrintingEnabled()) {
this._printToPDF(printingSetting, callback)
} else {
console.error('Error: Printing feature is disabled.')
}
}
WebContents.prototype.print = function (...args) {
if (features.isPrintingEnabled()) {
this._print(...args)
} else {
console.error('Error: Printing feature is disabled.')
}
}
WebContents.prototype.getPrinters = function () {
if (features.isPrintingEnabled()) {
return this._getPrinters()
} else {
console.error('Error: Printing feature is disabled.')
}
}
};
// TODO(codebytere): remove when promisifications is complete
const nativeZoomLevel = WebContents.prototype.getZoomLevel;
WebContents.prototype.getZoomLevel = function (callback) {
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument')
}
process.nextTick(() => {
const zoomLevel = this._getZoomLevel()
callback(zoomLevel)
})
}
WebContents.prototype.loadFile = function (filePath, options = {}) {
if (typeof filePath !== 'string') {
throw new Error('Must pass filePath as a string')
}
const { query, search, hash } = options
return this.loadURL(url.format({
protocol: 'file',
slashes: true,
pathname: path.resolve(app.getAppPath(), filePath),
query,
search,
hash
}))
}
if (callback == null) {
return nativeZoomLevel.call(this);
}
else {
process.nextTick(() => {
callback(nativeZoomLevel.call(this));
});
}
};
// TODO(codebytere): remove when promisifications is complete
const nativeZoomFactor = WebContents.prototype.getZoomFactor;
WebContents.prototype.getZoomFactor = function (callback) {
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument')
}
process.nextTick(() => {
const zoomFactor = this._getZoomFactor()
callback(zoomFactor)
})
}
WebContents.prototype.findInPage = function (text, options = {}) {
// TODO (nitsakh): Remove in 5.0
if (options.wordStart != null || options.medialCapitalAtWordStart != null) {
deprecate.log('wordStart and medialCapitalAtWordStart options are deprecated')
}
return this._findInPage(text, options)
}
const safeProtocols = new Set([
'chrome-devtools:',
'chrome-extension:'
])
const isWebContentsTrusted = function (contents) {
const pageURL = contents._getURL()
const { protocol } = url.parse(pageURL)
return safeProtocols.has(protocol)
}
if (callback == null) {
return nativeZoomFactor.call(this);
}
else {
process.nextTick(() => {
callback(nativeZoomFactor.call(this));
});
}
};
WebContents.prototype.takeHeapSnapshot = function (filePath) {
return new Promise((resolve, reject) => {
this._takeHeapSnapshot(filePath, (success) => {
if (success) {
resolve();
}
else {
reject(new Error('takeHeapSnapshot failed'));
}
});
});
};
// Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options) {
const printingSetting = Object.assign({}, defaultPrintingSetting);
if (options.landscape) {
printingSetting.landscape = options.landscape;
}
if (options.marginsType) {
printingSetting.marginsType = options.marginsType;
}
if (options.printSelectionOnly) {
printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly;
}
if (options.printBackground) {
printingSetting.shouldPrintBackgrounds = options.printBackground;
}
if (options.pageSize) {
const pageSize = options.pageSize;
if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) {
return Promise.reject(new Error('Must define height and width for pageSize'));
}
// Dimensions in Microns
// 1 meter = 10^6 microns
printingSetting.mediaSize = {
name: 'CUSTOM',
custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width)
};
}
else if (PDFPageSizes[pageSize]) {
printingSetting.mediaSize = PDFPageSizes[pageSize];
}
else {
return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`));
}
}
else {
printingSetting.mediaSize = PDFPageSizes['A4'];
}
// Chromium expects this in a 0-100 range number, not as float
printingSetting.scaleFactor *= 100;
if (features.isPrintingEnabled()) {
return this._printToPDF(printingSetting);
}
else {
return Promise.reject(new Error('Printing feature is disabled'));
}
};
WebContents.prototype.print = function (...args) {
if (features.isPrintingEnabled()) {
this._print(...args);
}
else {
console.error('Error: Printing feature is disabled.');
}
};
WebContents.prototype.getPrinters = function () {
if (features.isPrintingEnabled()) {
return this._getPrinters();
}
else {
console.error('Error: Printing feature is disabled.');
}
};
WebContents.prototype.loadFile = function (filePath, options = {}) {
if (typeof filePath !== 'string') {
throw new Error('Must pass filePath as a string');
}
const { query, search, hash } = options;
return this.loadURL(url.format({
protocol: 'file',
slashes: true,
pathname: path.resolve(app.getAppPath(), filePath),
query,
search,
hash
}));
};
WebContents.prototype.capturePage = deprecate.promisify(WebContents.prototype.capturePage);
WebContents.prototype.executeJavaScript = deprecate.promisify(WebContents.prototype.executeJavaScript);
WebContents.prototype.printToPDF = deprecate.promisify(WebContents.prototype.printToPDF);
WebContents.prototype.savePage = deprecate.promisify(WebContents.prototype.savePage);
const addReplyToEvent = (event) => {
event.reply = (...args) => {
event.sender.sendToFrame(event.frameId, ...args);
};
};
const addReplyInternalToEvent = (event) => {
Object.defineProperty(event, '_replyInternal', {
configurable: false,
enumerable: false,
value: (...args) => {
event.sender._sendToFrameInternal(event.frameId, ...args);
}
});
};
const addReturnValueToEvent = (event) => {
Object.defineProperty(event, 'returnValue', {
set: (value) => event.sendReply([value]),
get: () => { }
});
};
// Add JavaScript wrappers for WebContents class.
WebContents.prototype._init = function () {
// The navigation controller.
NavigationController.call(this, this)
// Every remote callback from renderer process would add a listenter to the
// render-view-deleted event, so ignore the listenters warning.
this.setMaxListeners(0)
// Dispatch IPC messages to the ipc module.
this.on('ipc-message', function (event, [channel, ...args]) {
ipcMain.emit(channel, event, ...args)
})
this.on('ipc-message-sync', function (event, [channel, ...args]) {
Object.defineProperty(event, 'returnValue', {
set: function (value) {
return event.sendReply([value])
},
get: function () {}
})
ipcMain.emit(channel, event, ...args)
})
this.on('ipc-internal-message', function (event, [channel, ...args]) {
ipcMainInternal.emit(channel, event, ...args)
})
this.on('ipc-internal-message-sync', function (event, [channel, ...args]) {
Object.defineProperty(event, 'returnValue', {
set: function (value) {
return event.sendReply([value])
},
get: function () {}
})
ipcMainInternal.emit(channel, event, ...args)
})
// Handle context menu action request from pepper plugin.
this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require.
const menu = electron.Menu.buildFromTemplate(params.menu)
menu.popup({
window: event.sender.getOwnerBrowserWindow(),
x: params.x,
y: params.y,
callback
})
})
const forwardedEvents = [
'remote-require',
'remote-get-global',
'remote-get-builtin',
'remote-get-current-window',
'remote-get-current-web-contents',
'remote-get-guest-web-contents'
]
for (const eventName of forwardedEvents) {
this.on(eventName, (event, ...args) => {
if (!isWebContentsTrusted(event.sender)) {
app.emit(eventName, event, this, ...args)
}
})
}
deprecate.event(this, 'did-get-response-details', '-did-get-response-details')
deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request')
// The devtools requests the webContents to reload.
this.on('devtools-reload-page', function () {
this.reload()
})
// Handle window.open for BrowserWindow and BrowserView.
if (['browserView', 'window'].includes(this.getType())) {
// Make new windows requested by links behave like "window.open"
this.webContents.on('-new-window', (event, url, frameName, disposition,
additionalFeatures, postData,
referrer) => {
const options = {
show: true,
width: 800,
height: 600
}
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
event, url, referrer, frameName, disposition,
options, additionalFeatures, postData)
})
this.webContents.on('-web-contents-created', (event, webContents, url,
frameName) => {
v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName })
})
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode
this.webContents.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width,
height) => {
const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab') || !urlFrameName) {
event.preventDefault()
return
}
if (webContents.getLastWebPreferences().nodeIntegration === true) {
const message =
'Enabling Node.js integration in child windows opened with the ' +
'"nativeWindowOpen" option will cause memory leaks, please turn off ' +
'the "nodeIntegration" option.\\n' +
'From 5.x child windows opened with the "nativeWindowOpen" option ' +
'will always have Node.js integration disabled.\\n' +
'See https://github.com/electron/electron/pull/15076 for more.'
// console is only available after DOM is created.
const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
if (this.webContents.isDomReady()) {
printWarning()
} else {
this.webContents.once('dom-ready', printWarning)
// The navigation controller.
NavigationController.call(this, this);
// Every remote callback from renderer process would add a listener to the
// render-view-deleted event, so ignore the listeners warning.
this.setMaxListeners(0);
// Dispatch IPC messages to the ipc module.
this.on('-ipc-message', function (event, internal, channel, args) {
if (internal) {
addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args);
}
}
const { url, frameName } = urlFrameName
v8Util.deleteHiddenValue(webContents, 'url-framename')
const options = {
show: true,
x: left,
y: top,
width: width || 800,
height: height || 600,
webContents
}
const referrer = { url: '', policy: 'default' }
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
event, url, referrer, frameName, disposition, options)
})
}
app.emit('web-contents-created', {}, this)
}
else {
addReplyToEvent(event);
this.emit('ipc-message', event, channel, ...args);
ipcMain.emit(channel, event, ...args);
}
});
this.on('-ipc-message-sync', function (event, internal, channel, args) {
addReturnValueToEvent(event);
if (internal) {
addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args);
}
else {
addReplyToEvent(event);
this.emit('ipc-message-sync', event, channel, ...args);
ipcMain.emit(channel, event, ...args);
}
});
// Handle context menu action request from pepper plugin.
this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require.
const menu = electron.Menu.buildFromTemplate(params.menu);
menu.popup({
window: event.sender.getOwnerBrowserWindow(),
x: params.x,
y: params.y,
callback
});
});
const forwardedEvents = [
'desktop-capturer-get-sources',
'remote-require',
'remote-get-global',
'remote-get-builtin',
'remote-get-current-window',
'remote-get-current-web-contents',
'remote-get-guest-web-contents'
];
for (const eventName of forwardedEvents) {
this.on(eventName, (event, ...args) => {
app.emit(eventName, event, this, ...args);
});
}
this.on('crashed', (event, ...args) => {
app.emit('renderer-process-crashed', event, this, ...args);
});
deprecate.event(this, 'did-get-response-details', '-did-get-response-details');
deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request');
// The devtools requests the webContents to reload.
this.on('devtools-reload-page', function () {
this.reload();
});
// Handle window.open for BrowserWindow and BrowserView.
if (['browserView', 'window'].includes(this.getType())) {
// Make new windows requested by links behave like "window.open".
this.on('-new-window', (event, url, frameName, disposition, additionalFeatures, postData, referrer) => {
const options = {
show: true,
width: 800,
height: 600
};
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options, additionalFeatures, postData);
});
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode.
this.on('-add-new-contents', (event, webContents, disposition, userGesture, left, top, width, height, url, frameName) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab')) {
event.preventDefault();
return;
}
const options = {
show: true,
x: left,
y: top,
width: width || 800,
height: height || 600,
webContents
};
const referrer = { url: '', policy: 'default' };
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options);
});
}
app.emit('web-contents-created', {}, this);
};
// JavaScript wrapper of Debugger.
const { Debugger } = process.atomBinding('debugger')
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
const { Debugger } = process.electronBinding('debugger');
Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand);
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype);
// Public APIs.
module.exports = {
create (options = {}) {
return binding.create(options)
},
fromId (id) {
return binding.fromId(id)
},
getFocusedWebContents () {
let focused = null
for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue
if (focused == null) focused = contents
// Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused
if (contents.getType() === 'webview') return contents
create(options = {}) {
return binding.create(options);
},
fromId(id) {
return binding.fromId(id);
},
getFocusedWebContents() {
let focused = null;
for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused())
continue;
if (focused == null)
focused = contents;
// Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused
if (contents.getType() === 'webview')
return contents;
}
return focused;
},
getAllWebContents() {
return binding.getAllWebContents();
}
return focused
},
getAllWebContents () {
return binding.getAllWebContents()
}
}
};

View File

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

View File

@ -0,0 +1,22 @@
'use strict';
const { app } = require('electron');
const cp = require('child_process');
const os = require('os');
const path = require('path');
const getTempDirectory = function () {
try {
return app.getPath('temp');
}
catch (_a) {
return os.tmpdir();
}
};
exports.crashReporterInit = function (options) {
const productName = options.productName || app.getName();
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`);
return {
productName,
crashesDirectory,
appVersion: app.getVersion()
};
};

View File

@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
const v8Util = process.electronBinding('v8_util');
const isMac = process.platform === 'darwin';
exports.setDefaultApplicationMenu = () => {
if (v8Util.getHiddenValue(global, 'applicationMenuSet'))
return;
const helpMenu = {
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
await electron_1.shell.openExternal('https://electronjs.org');
}
},
{
label: 'Documentation',
click: async () => {
await electron_1.shell.openExternal(`https://github.com/electron/electron/tree/v${process.versions.electron}/docs#readme`);
}
},
{
label: 'Community Discussions',
click: async () => {
await electron_1.shell.openExternal('https://discuss.atom.io/c/electron');
}
},
{
label: 'Search Issues',
click: async () => {
await electron_1.shell.openExternal('https://github.com/electron/electron/issues');
}
}
]
};
const macAppMenu = { role: 'appMenu' };
const template = [
...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
];
const menu = electron_1.Menu.buildFromTemplate(template);
electron_1.Menu.setApplicationMenu(menu);
};

View File

@ -1,73 +1,85 @@
'use strict'
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const { desktopCapturer } = process.atomBinding('desktop_capturer')
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b)
// A queue for holding all requests from renderer process.
let requestsQueue = []
const electronSources = 'ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES'
const capturerResult = (id) => `ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`
ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, id) => {
const request = {
id,
options: {
captureWindow,
captureScreen,
thumbnailSize
},
webContents: event.sender
}
requestsQueue.push(request)
if (requestsQueue.length === 1) {
desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
}
// If the WebContents is destroyed before receiving result, just remove the
// reference from requestsQueue to make the module not send the result to it.
event.sender.once('destroyed', () => {
request.webContents = null
})
})
desktopCapturer.emit = (event, name, sources) => {
// Receiving sources result from main process, now send them back to renderer.
const handledRequest = requestsQueue.shift()
const handledWebContents = handledRequest.webContents
const unhandledRequestsQueue = []
const result = sources.map(source => {
return {
id: source.id,
name: source.name,
thumbnail: source.thumbnail.toDataURL(),
display_id: source.display_id
'use strict';
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { createDesktopCapturer } = process.electronBinding('desktop_capturer');
const eventBinding = process.electronBinding('event');
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
let currentlyRunning = [];
ipcMainUtils.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, fetchWindowIcons) => {
const customEvent = eventBinding.createWithSender(event.sender);
event.sender.emit('desktop-capturer-get-sources', customEvent);
if (customEvent.defaultPrevented) {
return [];
}
})
if (handledWebContents) {
handledWebContents._sendInternal(capturerResult(handledRequest.id), result)
}
// Check the queue to see whether there is another identical request & handle
requestsQueue.forEach(request => {
const webContents = request.webContents
if (deepEqual(handledRequest.options, request.options)) {
if (webContents) {
webContents._sendInternal(capturerResult(request.id), result)
}
} else {
unhandledRequestsQueue.push(request)
const options = {
captureWindow,
captureScreen,
thumbnailSize,
fetchWindowIcons
};
for (const running of currentlyRunning) {
if (deepEqual(running.options, options)) {
// If a request is currently running for the same options
// return that promise
return running.getSources;
}
}
})
requestsQueue = unhandledRequestsQueue
// If the requestsQueue is not empty, start a new request handling.
if (requestsQueue.length > 0) {
const { captureWindow, captureScreen, thumbnailSize } = requestsQueue[0].options
return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
}
}
const getSources = new Promise((resolve, reject) => {
const stopRunning = () => {
// Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
};
const request = {
options,
resolve: (value) => {
stopRunning();
resolve(value);
},
reject: (err) => {
stopRunning();
reject(err);
},
capturer: createDesktopCapturer()
};
request.capturer.emit = createCapturerEmitHandler(request.capturer, request);
request.capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
// If the WebContents is destroyed before receiving result, just remove the
// reference to resolve, emit and the capturer itself so that it never dispatches
// back to the renderer
event.sender.once('destroyed', () => {
request.resolve = null;
delete request.capturer.emit;
delete request.capturer;
stopRunning();
});
});
currentlyRunning.push({
options,
getSources
});
return getSources;
});
const createCapturerEmitHandler = (capturer, request) => {
return function handlEmitOnCapturer(event, name, sources, fetchWindowIcons) {
// Ensure that this capturer instance can only ever receive a single event
// if we get more than one it is a bug but will also cause strange behavior
// if we still try to handle it
delete capturer.emit;
if (name === 'error') {
const error = sources;
request.reject(error);
return;
}
const result = sources.map(source => {
return {
id: source.id,
name: source.name,
thumbnail: source.thumbnail.toDataURL(),
display_id: source.display_id,
appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null
};
});
if (request.resolve) {
request.resolve(result);
}
};
};

View File

@ -0,0 +1,92 @@
'use strict';
const { dialog, Menu } = require('electron');
const fs = require('fs');
const url = require('url');
const util = require('util');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const readFile = util.promisify(fs.readFile);
const convertToMenuTemplate = function (items, handler) {
return items.map(function (item) {
const transformed = item.type === 'subMenu' ? {
type: 'submenu',
label: item.label,
enabled: item.enabled,
submenu: convertToMenuTemplate(item.subItems, handler)
} : item.type === 'separator' ? {
type: 'separator'
} : item.type === 'checkbox' ? {
type: 'checkbox',
label: item.label,
enabled: item.enabled,
checked: item.checked
} : {
type: 'normal',
label: item.label,
enabled: item.enabled
};
if (item.id != null) {
transformed.click = () => handler(item.id);
}
return transformed;
});
};
const getEditMenuItems = function () {
return [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' }
];
};
const isChromeDevTools = function (pageURL) {
const { protocol } = url.parse(pageURL);
return protocol === 'devtools:';
};
const assertChromeDevTools = function (contents, api) {
const pageURL = contents._getURL();
if (!isChromeDevTools(pageURL)) {
console.error(`Blocked ${pageURL} from calling ${api}`);
throw new Error(`Blocked ${api}`);
}
};
ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event, items, isEditMenu) {
return new Promise(resolve => {
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
const menu = Menu.buildFromTemplate(template);
const window = event.sender.getOwnerBrowserWindow();
menu.once('menu-will-close', () => {
// menu-will-close is emitted before the click handler, but needs to be sent after.
// otherwise, DevToolsAPI.contextMenuCleared() would be called before
// DevToolsAPI.contextMenuItemSelected(id) and the menu will not work properly.
setTimeout(() => resolve());
});
menu.popup({ window });
});
});
ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event) {
assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()');
const result = await dialog.showOpenDialog({});
if (result.canceled)
return [];
const path = result.filePaths[0];
const data = await readFile(path);
return [path, data];
});
ipcMainUtils.handle('ELECTRON_INSPECTOR_CONFIRM', async function (event, message = '', title = '') {
assertChromeDevTools(event.sender, 'window.confirm()');
const options = {
message: String(message),
title: String(title),
buttons: ['OK', 'Cancel'],
cancelId: 1
};
const window = event.sender.getOwnerBrowserWindow();
const { response } = await dialog.showMessageBox(window, options);
return response === 0;
});

View File

@ -1,451 +1,364 @@
'use strict'
const { webContents } = require('electron')
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
const errorUtils = require('@electron/internal/common/error-utils')
const {
syncMethods,
asyncCallbackMethods,
asyncPromiseMethods
} = require('@electron/internal/common/web-view-methods')
'use strict';
const { webContents } = require('electron');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const { syncMethods, asyncCallbackMethods, asyncPromiseMethods } = require('@electron/internal/common/web-view-methods');
// Doesn't exist in early initialization.
let webViewManager = null
let webViewManager = null;
const supportedWebViewEvents = [
'load-commit',
'did-attach',
'did-finish-load',
'did-fail-load',
'did-frame-finish-load',
'did-start-loading',
'did-stop-loading',
'dom-ready',
'console-message',
'context-menu',
'devtools-opened',
'devtools-closed',
'devtools-focused',
'new-window',
'will-navigate',
'did-start-navigation',
'did-navigate',
'did-frame-navigate',
'did-navigate-in-page',
'focus-change',
'close',
'crashed',
'gpu-crashed',
'plugin-crashed',
'destroyed',
'page-title-updated',
'page-favicon-updated',
'enter-html-full-screen',
'leave-html-full-screen',
'media-started-playing',
'media-paused',
'found-in-page',
'did-change-theme-color',
'update-target-url'
]
let nextGuestInstanceId = 0
const guestInstances = {}
const embedderElementsMap = {}
// Generate guestInstanceId.
const getNextGuestInstanceId = function () {
return ++nextGuestInstanceId
}
'load-commit',
'did-attach',
'did-finish-load',
'did-fail-load',
'did-frame-finish-load',
'did-start-loading',
'did-stop-loading',
'dom-ready',
'console-message',
'context-menu',
'devtools-opened',
'devtools-closed',
'devtools-focused',
'new-window',
'will-navigate',
'did-start-navigation',
'did-navigate',
'did-frame-navigate',
'did-navigate-in-page',
'focus-change',
'close',
'crashed',
'plugin-crashed',
'destroyed',
'page-title-updated',
'page-favicon-updated',
'enter-html-full-screen',
'leave-html-full-screen',
'media-started-playing',
'media-paused',
'found-in-page',
'did-change-theme-color',
'update-target-url'
];
const guestInstances = {};
const embedderElementsMap = {};
// Create a new guest instance.
const createGuest = function (embedder, params) {
if (webViewManager == null) {
webViewManager = process.atomBinding('web_view_manager')
}
const guestInstanceId = getNextGuestInstanceId(embedder)
const guest = webContents.create({
isGuest: true,
partition: params.partition,
embedder: embedder
})
guestInstances[guestInstanceId] = {
guest: guest,
embedder: embedder
}
// Clear the guest from map when it is destroyed.
//
// The guest WebContents is usually destroyed in 2 cases:
// 1. The embedder frame is closed (reloaded or destroyed), and it
// automatically closes the guest frame.
// 2. The guest frame is detached dynamically via JS, and it is manually
// destroyed when the renderer sends the GUEST_VIEW_MANAGER_DESTROY_GUEST
// message.
// The second case relies on the libcc patch:
// https://github.com/electron/libchromiumcontent/pull/676
// The patch was introduced to work around a bug in Chromium:
// https://github.com/electron/electron/issues/14211
// We should revisit the bug to see if we can remove our libcc patch, the
// patch was introduced in Chrome 66.
guest.once('destroyed', () => {
if (guestInstanceId in guestInstances) {
detachGuest(embedder, guestInstanceId)
if (webViewManager == null) {
webViewManager = process.electronBinding('web_view_manager');
}
})
// Init guest web view after attached.
guest.once('did-attach', function (event) {
params = this.attachParams
delete this.attachParams
const previouslyAttached = this.viewInstanceId != null
this.viewInstanceId = params.instanceId
// Only load URL and set size on first attach
if (previouslyAttached) {
return
const guest = webContents.create({
isGuest: true,
partition: params.partition,
embedder: embedder
});
const guestInstanceId = guest.id;
guestInstances[guestInstanceId] = {
guest: guest,
embedder: embedder
};
// Clear the guest from map when it is destroyed.
//
// The guest WebContents is usually destroyed in 2 cases:
// 1. The embedder frame is closed (reloaded or destroyed), and it
// automatically closes the guest frame.
// 2. The guest frame is detached dynamically via JS, and it is manually
// destroyed when the renderer sends the GUEST_VIEW_MANAGER_DESTROY_GUEST
// message.
// The second case relies on the libcc patch:
// https://github.com/electron/libchromiumcontent/pull/676
// The patch was introduced to work around a bug in Chromium:
// https://github.com/electron/electron/issues/14211
// We should revisit the bug to see if we can remove our libcc patch, the
// patch was introduced in Chrome 66.
guest.once('destroyed', () => {
if (guestInstanceId in guestInstances) {
detachGuest(embedder, guestInstanceId);
}
});
// Init guest web view after attached.
guest.once('did-attach', function (event) {
params = this.attachParams;
delete this.attachParams;
const previouslyAttached = this.viewInstanceId != null;
this.viewInstanceId = params.instanceId;
// Only load URL and set size on first attach
if (previouslyAttached) {
return;
}
if (params.src) {
const opts = {};
if (params.httpreferrer) {
opts.httpReferrer = params.httpreferrer;
}
if (params.useragent) {
opts.userAgent = params.useragent;
}
this.loadURL(params.src, opts);
}
embedder.emit('did-attach-webview', event, guest);
});
const sendToEmbedder = (channel, ...args) => {
if (!embedder.isDestroyed()) {
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
}
};
// Dispatch events to embedder.
const fn = function (event) {
guest.on(event, function (_, ...args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args);
});
};
for (const event of supportedWebViewEvents) {
fn(event);
}
if (params.src) {
const opts = {}
if (params.httpreferrer) {
opts.httpReferrer = params.httpreferrer
}
if (params.useragent) {
opts.userAgent = params.useragent
}
this.loadURL(params.src, opts)
}
guest.allowPopups = params.allowpopups
embedder.emit('did-attach-webview', event, guest)
})
const sendToEmbedder = (channel, ...args) => {
if (!embedder.isDestroyed()) {
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args)
}
}
// Dispatch events to embedder.
const fn = function (event) {
guest.on(event, function (_, ...args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args)
})
}
for (const event of supportedWebViewEvents) {
fn(event)
}
// Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, [channel, ...args]) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)
})
// Notify guest of embedder window visibility when it is ready
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
guest.on('dom-ready', function () {
const guestInstance = guestInstances[guestInstanceId]
if (guestInstance != null && guestInstance.visibilityState != null) {
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState)
}
})
// Forward internal web contents event to embedder to handle
// native window.open setup
guest.on('-add-new-contents', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId)
if (embedder != null) {
embedder.emit('-add-new-contents', ...args)
}
}
})
guest.on('-web-contents-created', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId)
if (embedder != null) {
embedder.emit('-web-contents-created', ...args)
}
}
})
return guestInstanceId
}
// Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, channel, args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args);
});
// Notify guest of embedder window visibility when it is ready
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
guest.on('dom-ready', function () {
const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null && guestInstance.visibilityState != null) {
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState);
}
});
// Forward internal web contents event to embedder to handle
// native window.open setup
guest.on('-add-new-contents', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId);
if (embedder != null) {
embedder.emit('-add-new-contents', ...args);
}
}
});
return guestInstanceId;
};
// Attach the guest to an element of embedder.
const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
const embedder = event.sender
// Destroy the old guest when attaching.
const key = `${embedder.id}-${elementInstanceId}`
const oldGuestInstanceId = embedderElementsMap[key]
if (oldGuestInstanceId != null) {
// Reattachment to the same guest is just a no-op.
if (oldGuestInstanceId === guestInstanceId) {
return
const embedder = event.sender;
// Destroy the old guest when attaching.
const key = `${embedder.id}-${elementInstanceId}`;
const oldGuestInstanceId = embedderElementsMap[key];
if (oldGuestInstanceId != null) {
// Reattachment to the same guest is just a no-op.
if (oldGuestInstanceId === guestInstanceId) {
return;
}
const oldGuestInstance = guestInstances[oldGuestInstanceId];
if (oldGuestInstance) {
oldGuestInstance.guest.detachFromOuterFrame();
}
}
const oldGuestInstance = guestInstances[oldGuestInstanceId]
if (oldGuestInstance) {
oldGuestInstance.guest.destroy()
const guestInstance = guestInstances[guestInstanceId];
// If this isn't a valid guest instance then do nothing.
if (!guestInstance) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
}
}
const guestInstance = guestInstances[guestInstanceId]
// If this isn't a valid guest instance then do nothing.
if (!guestInstance) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
}
const { guest } = guestInstance
if (guest.hostWebContents !== event.sender) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
}
// If this guest is already attached to an element then remove it
if (guestInstance.elementInstanceId) {
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`
delete embedderElementsMap[oldKey]
// Remove guest from embedder if moving across web views
if (guest.viewInstanceId !== params.instanceId) {
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`)
const { guest } = guestInstance;
if (guest.hostWebContents !== event.sender) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
}
}
const webPreferences = {
guestInstanceId: guestInstanceId,
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
enableRemoteModule: params.enableremotemodule,
plugins: params.plugins,
zoomFactor: embedder._getZoomFactor(),
webSecurity: !params.disablewebsecurity,
enableBlinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures
}
// parse the 'webpreferences' attribute string, if set
// this uses the same parsing rules as window.open uses for its features
if (typeof params.webpreferences === 'string') {
parseFeaturesString(params.webpreferences, function (key, value) {
if (value === undefined) {
// no value was specified, default it to true
value = true
}
webPreferences[key] = value
})
}
if (params.preload) {
webPreferences.preloadURL = params.preload
}
// Return null from native window.open if allowpopups is unset
if (webPreferences.nativeWindowOpen === true && !params.allowpopups) {
webPreferences.disablePopups = true
}
// Security options that guest will always inherit from embedder
const inheritedWebPreferences = new Map([
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true]
])
// Inherit certain option values from embedder
const lastWebPreferences = embedder.getLastWebPreferences()
for (const [name, value] of inheritedWebPreferences) {
if (lastWebPreferences[name] === value) {
webPreferences[name] = value
// If this guest is already attached to an element then remove it
if (guestInstance.elementInstanceId) {
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[oldKey];
// Remove guest from embedder if moving across web views
if (guest.viewInstanceId !== params.instanceId) {
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`);
}
}
}
embedder.emit('will-attach-webview', event, webPreferences, params)
if (event.defaultPrevented) {
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId
guest.destroy()
return
}
guest.attachParams = params
embedderElementsMap[key] = guestInstanceId
guest.setEmbedder(embedder)
guestInstance.embedder = embedder
guestInstance.elementInstanceId = elementInstanceId
watchEmbedder(embedder)
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
guest.attachToIframe(embedder, embedderFrameId)
}
const webPreferences = {
guestInstanceId: guestInstanceId,
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false,
enableRemoteModule: params.enableremotemodule,
plugins: params.plugins,
zoomFactor: embedder.getZoomFactor(),
disablePopups: !params.allowpopups,
webSecurity: !params.disablewebsecurity,
enableBlinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures
};
// parse the 'webpreferences' attribute string, if set
// this uses the same parsing rules as window.open uses for its features
if (typeof params.webpreferences === 'string') {
parseFeaturesString(params.webpreferences, function (key, value) {
if (value === undefined) {
// no value was specified, default it to true
value = true;
}
webPreferences[key] = value;
});
}
if (params.preload) {
webPreferences.preloadURL = params.preload;
}
// Security options that guest will always inherit from embedder
const inheritedWebPreferences = new Map([
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true],
['nodeIntegrationInSubFrames', false]
]);
// Inherit certain option values from embedder
const lastWebPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) {
if (lastWebPreferences[name] === value) {
webPreferences[name] = value;
}
}
embedder.emit('will-attach-webview', event, webPreferences, params);
if (event.defaultPrevented) {
if (guest.viewInstanceId == null)
guest.viewInstanceId = params.instanceId;
guest.destroy();
return;
}
guest.attachParams = params;
embedderElementsMap[key] = guestInstanceId;
guest.setEmbedder(embedder);
guestInstance.embedder = embedder;
guestInstance.elementInstanceId = elementInstanceId;
watchEmbedder(embedder);
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences);
guest.attachToIframe(embedder, embedderFrameId);
};
// Remove an guest-embedder relationship.
const detachGuest = function (embedder, guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId]
if (embedder !== guestInstance.embedder) {
return
}
webViewManager.removeGuest(embedder, guestInstanceId)
delete guestInstances[guestInstanceId]
const key = `${embedder.id}-${guestInstance.elementInstanceId}`
delete embedderElementsMap[key]
}
const guestInstance = guestInstances[guestInstanceId];
if (embedder !== guestInstance.embedder) {
return;
}
webViewManager.removeGuest(embedder, guestInstanceId);
delete guestInstances[guestInstanceId];
const key = `${embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[key];
};
// Once an embedder has had a guest attached we watch it for destruction to
// destroy any remaining guests.
const watchedEmbedders = new Set()
const watchedEmbedders = new Set();
const watchEmbedder = function (embedder) {
if (watchedEmbedders.has(embedder)) {
return
}
watchedEmbedders.add(embedder)
// Forward embedder window visiblity change events to guest
const onVisibilityChange = function (visibilityState) {
for (const guestInstanceId in guestInstances) {
const guestInstance = guestInstances[guestInstanceId]
guestInstance.visibilityState = visibilityState
if (guestInstance.embedder === embedder) {
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState)
}
if (watchedEmbedders.has(embedder)) {
return;
}
}
embedder.on('-window-visibility-change', onVisibilityChange)
embedder.once('will-destroy', () => {
// Usually the guestInstances is cleared when guest is destroyed, but it
// may happen that the embedder gets manually destroyed earlier than guest,
// and the embedder will be invalid in the usual code path.
for (const guestInstanceId in guestInstances) {
const guestInstance = guestInstances[guestInstanceId]
if (guestInstance.embedder === embedder) {
detachGuest(embedder, parseInt(guestInstanceId))
}
}
// Clear the listeners.
embedder.removeListener('-window-visibility-change', onVisibilityChange)
watchedEmbedders.delete(embedder)
})
}
const isWebViewTagEnabledCache = new WeakMap()
watchedEmbedders.add(embedder);
// Forward embedder window visiblity change events to guest
const onVisibilityChange = function (visibilityState) {
for (const guestInstanceId in guestInstances) {
const guestInstance = guestInstances[guestInstanceId];
guestInstance.visibilityState = visibilityState;
if (guestInstance.embedder === embedder) {
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState);
}
}
};
embedder.on('-window-visibility-change', onVisibilityChange);
embedder.once('will-destroy', () => {
// Usually the guestInstances is cleared when guest is destroyed, but it
// may happen that the embedder gets manually destroyed earlier than guest,
// and the embedder will be invalid in the usual code path.
for (const guestInstanceId in guestInstances) {
const guestInstance = guestInstances[guestInstanceId];
if (guestInstance.embedder === embedder) {
detachGuest(embedder, parseInt(guestInstanceId));
}
}
// Clear the listeners.
embedder.removeListener('-window-visibility-change', onVisibilityChange);
watchedEmbedders.delete(embedder);
});
};
const isWebViewTagEnabledCache = new WeakMap();
const isWebViewTagEnabled = function (contents) {
if (!isWebViewTagEnabledCache.has(contents)) {
const value = contents.getLastWebPreferences().webviewTag
isWebViewTagEnabledCache.set(contents, value)
}
return isWebViewTagEnabledCache.get(contents)
}
if (!isWebViewTagEnabledCache.has(contents)) {
const webPreferences = contents.getLastWebPreferences() || {};
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag);
}
return isWebViewTagEnabledCache.get(contents);
};
const handleMessage = function (channel, handler) {
ipcMain.on(channel, (event, ...args) => {
if (isWebViewTagEnabled(event.sender)) {
handler(event, ...args)
} else {
event.returnValue = null
}
})
}
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
event.sender._sendInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
})
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event, params) {
event.returnValue = createGuest(event.sender, params)
})
ipcMainUtils.handle(channel, (event, ...args) => {
if (isWebViewTagEnabled(event.sender)) {
return handler(event, ...args);
}
else {
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
throw new Error('<webview> disabled');
}
});
};
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) {
return createGuest(event.sender, params);
});
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
try {
const guest = getGuestForWebContents(guestInstanceId, event.sender)
guest.destroy()
} catch (error) {
console.error(`Guest destroy failed: ${error}`)
}
})
try {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
guest.detachFromOuterFrame();
}
catch (error) {
console.error(`Guest destroy failed: ${error}`);
}
});
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
try {
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params)
} catch (error) {
console.error(`Guest attach failed: ${error}`)
}
})
try {
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params);
}
catch (error) {
console.error(`Guest attach failed: ${error}`);
}
});
// this message is sent by the actual <webview>
ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
const guest = getGuest(guestInstanceId)
if (guest === event.sender) {
event.sender.emit('focus-change', {}, focus, guestInstanceId)
} else {
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`)
}
})
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, requestId, guestInstanceId, method, args, hasCallback) {
new Promise(resolve => {
const guest = getGuestForWebContents(guestInstanceId, event.sender)
if (!asyncCallbackMethods.has(method) && !asyncPromiseMethods.has(method)) {
throw new Error(`Invalid method: ${method}`)
ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
const guest = getGuest(guestInstanceId);
if (guest === event.sender) {
event.sender.emit('focus-change', {}, focus, guestInstanceId);
}
if (hasCallback) {
guest[method](...args, resolve)
} else {
resolve(guest[method](...args))
else {
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`);
}
}).then(result => {
return [null, result]
}, error => {
return [errorUtils.serialize(error)]
}).then(responseArgs => {
event.sender._sendInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
})
})
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_SYNC_CALL', function (event, guestInstanceId, method, args) {
try {
const guest = getGuestForWebContents(guestInstanceId, event.sender)
if (!syncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`)
});
const allMethods = new Set([
...syncMethods,
...asyncCallbackMethods,
...asyncPromiseMethods
]);
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!allMethods.has(method)) {
throw new Error(`Invalid method: ${method}`);
}
event.returnValue = [null, guest[method](...args)]
} catch (error) {
event.returnValue = [errorUtils.serialize(error)]
}
})
return guest[method](...args);
});
// Returns WebContents from its guest id hosted in given webContents.
const getGuestForWebContents = function (guestInstanceId, contents) {
const guest = getGuest(guestInstanceId)
if (!guest) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
}
if (guest.hostWebContents !== contents) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
}
return guest
}
const guest = getGuest(guestInstanceId);
if (!guest) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
}
if (guest.hostWebContents !== contents) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
}
return guest;
};
// Returns WebContents from its guest id.
const getGuest = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId]
if (guestInstance != null) return guestInstance.guest
}
const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null)
return guestInstance.guest;
};
// Returns the embedder of the guest.
const getEmbedder = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId]
if (guestInstance != null) return guestInstance.embedder
}
exports.getGuestForWebContents = getGuestForWebContents
const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null)
return guestInstance.embedder;
};
exports.getGuestForWebContents = getGuestForWebContents;
exports.isWebViewTagEnabled = isWebViewTagEnabled;

View File

@ -1,368 +1,330 @@
'use strict'
const { BrowserWindow, webContents } = require('electron')
const { isSameOrigin } = process.atomBinding('v8_util')
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
const hasProp = {}.hasOwnProperty
const frameToGuest = new Map()
'use strict';
const { BrowserWindow, webContents } = require('electron');
const { isSameOrigin } = process.electronBinding('v8_util');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const hasProp = {}.hasOwnProperty;
const frameToGuest = new Map();
// Security options that child windows will always inherit from parent windows
const inheritedWebPreferences = new Map([
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true],
['webviewTag', false]
])
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true],
['webviewTag', false],
['nodeIntegrationInSubFrames', false]
]);
// Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent, visited) {
// Check for circular reference.
if (visited == null) visited = new Set()
if (visited.has(parent)) return
visited.add(parent)
for (const key in parent) {
if (key === 'isBrowserView') continue
if (!hasProp.call(parent, key)) continue
if (key in child && key !== 'webPreferences') continue
const value = parent[key]
if (typeof value === 'object') {
child[key] = mergeOptions(child[key] || {}, value, visited)
} else {
child[key] = value
// Check for circular reference.
if (visited == null)
visited = new Set();
if (visited.has(parent))
return;
visited.add(parent);
for (const key in parent) {
if (key === 'isBrowserView')
continue;
if (!hasProp.call(parent, key))
continue;
if (key in child && key !== 'webPreferences')
continue;
const value = parent[key];
if (typeof value === 'object' && !Array.isArray(value)) {
child[key] = mergeOptions(child[key] || {}, value, visited);
}
else {
child[key] = value;
}
}
}
visited.delete(parent)
return child
}
visited.delete(parent);
return child;
};
// Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) {
if (options.webPreferences == null) {
options.webPreferences = {}
}
if (embedder.browserWindowOptions != null) {
let parentOptions = embedder.browserWindowOptions
// if parent's visibility is available, that overrides 'show' flag (#12125)
const win = BrowserWindow.fromWebContents(embedder.webContents)
if (win != null) {
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() }
if (options.webPreferences == null) {
options.webPreferences = {};
}
// Inherit the original options if it is a BrowserWindow.
mergeOptions(options, parentOptions)
} else {
// Or only inherit webPreferences if it is a webview.
mergeOptions(options.webPreferences, embedder.getLastWebPreferences())
}
// Inherit certain option values from parent window
const webPreferences = embedder.getLastWebPreferences()
for (const [name, value] of inheritedWebPreferences) {
if (webPreferences[name] === value) {
options.webPreferences[name] = value
if (embedder.browserWindowOptions != null) {
let parentOptions = embedder.browserWindowOptions;
// if parent's visibility is available, that overrides 'show' flag (#12125)
const win = BrowserWindow.fromWebContents(embedder.webContents);
if (win != null) {
parentOptions = Object.assign({}, embedder.browserWindowOptions, { show: win.isVisible() });
}
// Inherit the original options if it is a BrowserWindow.
mergeOptions(options, parentOptions);
}
}
// Sets correct openerId here to give correct options to 'new-window' event handler
options.webPreferences.openerId = embedder.id
return options
}
else {
// Or only inherit webPreferences if it is a webview.
mergeOptions(options.webPreferences, embedder.getLastWebPreferences());
}
// Inherit certain option values from parent window
const webPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) {
if (webPreferences[name] === value) {
options.webPreferences[name] = value;
}
}
// Sets correct openerId here to give correct options to 'new-window' event handler
options.webPreferences.openerId = embedder.id;
return options;
};
// Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) {
// When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double
// closing guest.
const guestId = guest.webContents.id
const closedByEmbedder = function () {
guest.removeListener('closed', closedByUser)
guest.destroy()
}
const closedByUser = function () {
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
embedder.removeListener('render-view-deleted', closedByEmbedder)
}
embedder.once('render-view-deleted', closedByEmbedder)
guest.once('closed', closedByUser)
if (frameName) {
frameToGuest.set(frameName, guest)
guest.frameName = frameName
guest.once('closed', function () {
frameToGuest.delete(frameName)
})
}
return guestId
}
// When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double
// closing guest.
const guestId = guest.webContents.id;
const closedByEmbedder = function () {
guest.removeListener('closed', closedByUser);
guest.destroy();
};
const closedByUser = function () {
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId);
embedder.removeListener('current-render-view-deleted', closedByEmbedder);
};
embedder.once('current-render-view-deleted', closedByEmbedder);
guest.once('closed', closedByUser);
if (frameName) {
frameToGuest.set(frameName, guest);
guest.frameName = frameName;
guest.once('closed', function () {
frameToGuest.delete(frameName);
});
}
return guestId;
};
// Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, referrer, frameName, options, postData) {
let guest = frameToGuest.get(frameName)
if (frameName && (guest != null)) {
guest.loadURL(url)
return guest.webContents.id
}
// Remember the embedder window's id.
if (options.webPreferences == null) {
options.webPreferences = {}
}
guest = new BrowserWindow(options)
if (!options.webContents || url !== 'about:blank') {
// We should not call `loadURL` if the window was constructed from an
// existing webContents(window.open in a sandboxed renderer) and if the url
// is not 'about:blank'.
//
// Navigating to the url when creating the window from an existing
// webContents would not be necessary(it will navigate there anyway), but
// apparently there's a bug that allows the child window to be scripted by
// the opener, even when the child window is from another origin.
//
// That's why the second condition(url !== "about:blank") is required: to
// force `OverrideSiteInstanceForNavigation` to be called and consequently
// spawn a new renderer if the new window is targeting a different origin.
//
// If the URL is "about:blank", then it is very likely that the opener just
// wants to synchronously script the popup, for example:
//
// let popup = window.open()
// popup.document.body.write('<h1>hello</h1>')
//
// The above code would not work if a navigation to "about:blank" is done
// here, since the window would be cleared of all changes in the next tick.
const loadOptions = {
httpReferrer: referrer
let guest = frameToGuest.get(frameName);
if (frameName && (guest != null)) {
guest.loadURL(url);
return guest.webContents.id;
}
if (postData != null) {
loadOptions.postData = postData
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
if (postData.length > 0) {
const postDataFront = postData[0].bytes.toString()
const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
if (boundary != null) {
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
// Remember the embedder window's id.
if (options.webPreferences == null) {
options.webPreferences = {};
}
guest = new BrowserWindow(options);
if (!options.webContents) {
// We should not call `loadURL` if the window was constructed from an
// existing webContents (window.open in a sandboxed renderer).
//
// Navigating to the url when creating the window from an existing
// webContents is not necessary (it will navigate there anyway).
const loadOptions = {
httpReferrer: referrer
};
if (postData != null) {
loadOptions.postData = postData;
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded';
if (postData.length > 0) {
const postDataFront = postData[0].bytes.toString();
const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
if (boundary != null) {
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) {
let guestWindow = BrowserWindow.fromWebContents(guestContents)
if (guestWindow == null) {
const hostContents = guestContents.hostWebContents
if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents)
}
}
return guestWindow
}
// Checks whether |sender| can access the |target|:
// 1. Check whether |sender| is the parent of |target|.
// 2. Check whether |sender| has node integration, if so it is allowed to
// do anything it wants.
// 3. Check whether the origins match.
//
// However it allows a child window without node integration but with same
// origin to do anything it wants, when its opener window has node integration.
// The W3C does not have anything on this, but from my understanding of the
// security model of |window.opener|, this should be fine.
const canAccessWindow = function (sender, target) {
return (target.getLastWebPreferences().openerId === sender.id) ||
(sender.getLastWebPreferences().nodeIntegration === true) ||
isSameOrigin(sender.getURL(), target.getURL())
}
// Routed window.open messages with raw options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
if (url == null || url === '') url = 'about:blank'
if (frameName == null) frameName = ''
if (features == null) features = ''
const options = {}
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag']
const disposition = 'new-window'
// Used to store additional features
const additionalFeatures = []
// Parse the features
parseFeaturesString(features, function (key, value) {
if (value === undefined) {
additionalFeatures.push(key)
} else {
// Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden
if (key === 'webPreferences') return
if (webPreferences.includes(key)) {
if (options.webPreferences == null) {
options.webPreferences = {}
let guestWindow = BrowserWindow.fromWebContents(guestContents);
if (guestWindow == null) {
const hostContents = guestContents.hostWebContents;
if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents);
}
options.webPreferences[key] = value
} else {
options[key] = value
}
}
})
if (options.left) {
if (options.x == null) {
options.x = options.left
return guestWindow;
};
const isChildWindow = function (sender, target) {
return target.getLastWebPreferences().openerId === sender.id;
};
const isRelatedWindow = function (sender, target) {
return isChildWindow(sender, target) || isChildWindow(target, sender);
};
const isScriptableWindow = function (sender, target) {
return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL());
};
const isNodeIntegrationEnabled = function (sender) {
return sender.getLastWebPreferences().nodeIntegration === true;
};
// Checks whether |sender| can access the |target|:
const canAccessWindow = function (sender, target) {
return isChildWindow(sender, target) ||
isScriptableWindow(sender, target) ||
isNodeIntegrationEnabled(sender);
};
// Routed window.open messages with raw options
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
if (url == null || url === '')
url = 'about:blank';
if (frameName == null)
frameName = '';
if (features == null)
features = '';
const options = {};
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'];
const disposition = 'new-window';
// Used to store additional features
const additionalFeatures = [];
// Parse the features
parseFeaturesString(features, function (key, value) {
if (value === undefined) {
additionalFeatures.push(key);
}
else {
// Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden
if (key === 'webPreferences')
return;
if (webPreferences.includes(key)) {
if (options.webPreferences == null) {
options.webPreferences = {};
}
options.webPreferences[key] = value;
}
else {
options[key] = value;
}
}
});
if (options.left) {
if (options.x == null) {
options.x = options.left;
}
}
}
if (options.top) {
if (options.y == null) {
options.y = options.top
if (options.top) {
if (options.y == null) {
options.y = options.top;
}
}
}
if (options.title == null) {
options.title = frameName
}
if (options.width == null) {
options.width = 800
}
if (options.height == null) {
options.height = 600
}
for (const name of ints) {
if (options[name] != null) {
options[name] = parseInt(options[name], 10)
if (options.title == null) {
options.title = frameName;
}
}
const referrer = { url: '', policy: 'default' }
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
url, referrer, frameName, disposition, options, additionalFeatures)
})
if (options.width == null) {
options.width = 800;
}
if (options.height == null) {
options.height = 600;
}
for (const name of ints) {
if (options[name] != null) {
options[name] = parseInt(options[name], 10);
}
}
const referrer = { url: '', policy: 'default' };
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options, additionalFeatures);
});
// Routed window.open messages with fully parsed options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer,
frameName, disposition, options,
additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options)
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer)
const { newGuest } = event
if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
if (newGuest != null) {
if (options.webContents === newGuest.webContents) {
// the webContents is not changed, so set defaultPrevented to false to
// stop the callers of this event from destroying the webContents.
event.defaultPrevented = false
}
event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
} else {
event.returnValue = null
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options);
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer);
const { newGuest } = event;
if ((event.sender.isGuest() && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) {
if (newGuest != null) {
if (options.webContents === newGuest.webContents) {
// the webContents is not changed, so set defaultPrevented to false to
// stop the callers of this event from destroying the webContents.
event.defaultPrevented = false;
}
event.returnValue = setupGuest(event.sender, frameName, newGuest, options);
}
else {
event.returnValue = null;
}
}
} else {
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData)
}
})
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
const guestContents = webContents.fromId(guestId)
if (guestContents == null) return
if (!canAccessWindow(event.sender, guestContents)) {
console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
return
}
const guestWindow = getGuestWindow(guestContents)
if (guestWindow != null) guestWindow.destroy()
})
else {
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData);
}
});
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
const guestContents = webContents.fromId(guestId);
if (guestContents == null)
return;
if (!canAccessWindow(event.sender, guestContents)) {
console.error(`Blocked ${event.sender.getURL()} from closing its opener.`);
return;
}
const guestWindow = getGuestWindow(guestContents);
if (guestWindow != null)
guestWindow.destroy();
});
const windowMethods = new Set([
'focus',
'blur'
])
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId)
if (guestContents == null) {
event.returnValue = null
return
}
if (!canAccessWindow(event.sender, guestContents) || !windowMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
event.returnValue = null
return
}
const guestWindow = getGuestWindow(guestContents)
if (guestWindow != null) {
event.returnValue = guestWindow[method](...args)
} else {
event.returnValue = null
}
})
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
if (targetOrigin == null) {
targetOrigin = '*'
}
const guestContents = webContents.fromId(guestId)
if (guestContents == null) return
// 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
// postMessage across origins is useful and not harmful.
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
const sourceId = event.sender.id
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
}
})
'focus',
'blur'
]);
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId);
if (guestContents == null) {
event.returnValue = null;
return;
}
if (!canAccessWindow(event.sender, guestContents) || !windowMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`);
event.returnValue = null;
return;
}
const guestWindow = getGuestWindow(guestContents);
if (guestWindow != null) {
event.returnValue = guestWindow[method](...args);
}
else {
event.returnValue = null;
}
});
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
if (targetOrigin == null) {
targetOrigin = '*';
}
const guestContents = webContents.fromId(guestId);
if (guestContents == null)
return;
// 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
// postMessage across origins is useful and not harmful.
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
const sourceId = event.sender.id;
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin);
}
});
const webContentsMethods = new Set([
'print',
'executeJavaScript'
])
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId)
if (guestContents == null) return
if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) {
guestContents[method](...args)
} else {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
}
})
'print',
'executeJavaScript'
]);
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId);
if (guestContents == null)
return;
if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) {
guestContents[method](...args);
}
else {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`);
}
});
const webContentsSyncMethods = new Set([
'getURL',
'loadURL'
])
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId)
if (guestContents == null) {
event.returnValue = null
return
}
if (canAccessWindow(event.sender, guestContents) && webContentsSyncMethods.has(method)) {
event.returnValue = guestContents[method](...args)
} else {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
event.returnValue = null
}
})
'getURL',
'loadURL'
]);
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
const guestContents = webContents.fromId(guestId);
if (guestContents == null) {
event.returnValue = null;
return;
}
if (canAccessWindow(event.sender, guestContents) && webContentsSyncMethods.has(method)) {
event.returnValue = guestContents[method](...args);
}
else {
console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`);
event.returnValue = null;
}
});

View File

@ -1,188 +1,186 @@
'use strict'
const { Buffer } = require('buffer')
const fs = require('fs')
const path = require('path')
const util = require('util')
const Module = require('module')
const v8 = require('v8')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const buffer_1 = require("buffer");
const fs = require("fs");
const path = require("path");
const util = require("util");
const v8 = require("v8");
const Module = require('module');
// We modified the original process.argv to let node.js load the init.js,
// we need to restore it here.
process.argv.splice(1, 1)
process.argv.splice(1, 1);
// Clear search paths.
require('../common/reset-search-paths')
require('../common/reset-search-paths');
// Import common settings.
require('@electron/internal/common/init')
const globalPaths = Module.globalPaths
require('@electron/internal/common/init');
const globalPaths = Module.globalPaths;
// Expose public APIs.
globalPaths.push(path.join(__dirname, 'api', 'exports'))
globalPaths.push(path.join(__dirname, 'api', 'exports'));
if (process.platform === 'win32') {
// Redirect node's console to use our own implementations, since node can not
// handle console output when running as GUI program.
const consoleLog = function (...args) {
return process.log(util.format(...args) + '\n')
}
const streamWrite = function (chunk, encoding, callback) {
if (Buffer.isBuffer(chunk)) {
chunk = chunk.toString(encoding)
}
process.log(chunk)
if (callback) {
callback()
}
return true
}
console.log = console.error = console.warn = consoleLog
process.stdout.write = process.stderr.write = streamWrite
// Redirect node's console to use our own implementations, since node can not
// handle console output when running as GUI program.
const consoleLog = (format, ...args) => {
return process.log(util.format(format, ...args) + '\n');
};
const streamWrite = function (chunk, encoding, callback) {
if (buffer_1.Buffer.isBuffer(chunk)) {
chunk = chunk.toString(encoding);
}
process.log(chunk);
if (callback) {
callback();
}
return true;
};
console.log = console.error = console.warn = consoleLog;
process.stdout.write = process.stderr.write = streamWrite;
}
// Don't quit on fatal error.
process.on('uncaughtException', function (error) {
// Do nothing if the user has a custom uncaught exception handler.
if (process.listeners('uncaughtException').length > 1) {
return
}
// Show error in GUI.
const dialog = require('electron').dialog
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`
const message = 'Uncaught Exception:\n' + stack
dialog.showErrorBox('A JavaScript error occurred in the main process', message)
})
// Do nothing if the user has a custom uncaught exception handler.
if (process.listeners('uncaughtException').length > 1) {
return;
}
// Show error in GUI.
// We can't import { dialog } at the top of this file as this file is
// responsible for setting up the require hook for the "electron" module
// so we import it inside the handler down here
Promise.resolve().then(() => require('electron')).then(({ dialog }) => {
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
const message = 'Uncaught Exception:\n' + stack;
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
});
});
// Emit 'exit' event on quit.
const { app } = require('electron')
const { app } = require('electron');
app.on('quit', function (event, exitCode) {
process.emit('exit', exitCode)
})
process.emit('exit', exitCode);
});
if (process.platform === 'win32') {
// If we are a Squirrel.Windows-installed app, set app user model ID
// so that users don't have to do this.
//
// Squirrel packages are always of the form:
//
// PACKAGE-NAME
// - Update.exe
// - app-VERSION
// - OUREXE.exe
//
// Squirrel itself will always set the shortcut's App User Model ID to the
// form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
// app.setAppUserModelId with a matching identifier so that renderer processes
// will inherit this value.
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe')
if (fs.existsSync(updateDotExe)) {
const packageDir = path.dirname(path.resolve(updateDotExe))
const packageName = path.basename(packageDir).replace(/\s/g, '')
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '')
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`)
}
// If we are a Squirrel.Windows-installed app, set app user model ID
// so that users don't have to do this.
//
// Squirrel packages are always of the form:
//
// PACKAGE-NAME
// - Update.exe
// - app-VERSION
// - OUREXE.exe
//
// Squirrel itself will always set the shortcut's App User Model ID to the
// form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
// app.setAppUserModelId with a matching identifier so that renderer processes
// will inherit this value.
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');
if (fs.existsSync(updateDotExe)) {
const packageDir = path.dirname(path.resolve(updateDotExe));
const packageName = path.basename(packageDir).replace(/\s/g, '');
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '');
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
}
}
// Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit
process.exit = app.exit;
// Load the RPC server.
require('@electron/internal/browser/rpc-server')
require('@electron/internal/browser/rpc-server');
// Load the guest view manager.
require('@electron/internal/browser/guest-view-manager')
require('@electron/internal/browser/guest-window-manager')
require('@electron/internal/browser/guest-view-manager');
require('@electron/internal/browser/guest-window-manager');
// Now we try to load app's package.json.
let packagePath = null
let packageJson = null
const searchPaths = ['app', 'app.asar', 'default_app.asar']
for (packagePath of searchPaths) {
try {
packagePath = path.join(process.resourcesPath, packagePath)
packageJson = require(path.join(packagePath, 'package.json'))
break
} catch (error) {
continue
}
let packagePath = null;
let packageJson = null;
const searchPaths = ['app', 'app.asar', 'default_app.asar'];
if (process.resourcesPath) {
for (packagePath of searchPaths) {
try {
packagePath = path.join(process.resourcesPath, packagePath);
packageJson = require(path.join(packagePath, 'package.json'));
break;
}
catch (_a) {
continue;
}
}
}
if (packageJson == null) {
process.nextTick(function () {
return process.exit(1)
})
throw new Error('Unable to find a valid app')
process.nextTick(function () {
return process.exit(1);
});
throw new Error('Unable to find a valid app');
}
// Set application's version.
if (packageJson.version != null) {
app.setVersion(packageJson.version)
app.setVersion(packageJson.version);
}
// Set application's name.
if (packageJson.productName != null) {
app.setName(`${packageJson.productName}`.trim())
} else if (packageJson.name != null) {
app.setName(`${packageJson.name}`.trim())
app.setName(`${packageJson.productName}`.trim());
}
else if (packageJson.name != null) {
app.setName(`${packageJson.name}`.trim());
}
// Set application's desktop name.
if (packageJson.desktopName != null) {
app.setDesktopName(packageJson.desktopName)
} else {
app.setDesktopName((app.getName()) + '.desktop')
app.setDesktopName(packageJson.desktopName);
}
else {
app.setDesktopName((app.getName()) + '.desktop');
}
// Set v8 flags
if (packageJson.v8Flags != null) {
v8.setFlagsFromString(packageJson.v8Flags)
v8.setFlagsFromString(packageJson.v8Flags);
}
// Set the user path according to application's name.
app.setPath('userData', path.join(app.getPath('appData'), app.getName()))
app.setPath('userCache', path.join(app.getPath('cache'), app.getName()))
app.setAppPath(packagePath)
app._setDefaultAppPaths(packagePath);
// Load the chrome devtools support.
require('@electron/internal/browser/devtools');
// Load the chrome extension support.
require('@electron/internal/browser/chrome-extension')
const features = process.atomBinding('features')
require('@electron/internal/browser/chrome-extension');
const features = process.electronBinding('features');
if (features.isDesktopCapturerEnabled()) {
// Load internal desktop-capturer module.
require('@electron/internal/browser/desktop-capturer')
// Load internal desktop-capturer module.
require('@electron/internal/browser/desktop-capturer');
}
// 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.
const mainStartupScript = packageJson.main || 'index.js'
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME']
function currentPlatformSupportsAppIndicator () {
if (process.platform !== 'linux') return false
const currentDesktop = process.env.XDG_CURRENT_DESKTOP
if (!currentDesktop) return false
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true
// ubuntu based or derived session (default ubuntu one, communitheme…) supports
// indicator too.
if (/ubuntu/ig.test(currentDesktop)) return true
return false
const mainStartupScript = packageJson.main || 'index.js';
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'];
function currentPlatformSupportsAppIndicator() {
if (process.platform !== 'linux')
return false;
const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
if (!currentDesktop)
return false;
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop))
return true;
// ubuntu based or derived session (default ubuntu one, communitheme…) supports
// indicator too.
if (/ubuntu/ig.test(currentDesktop))
return true;
return false;
}
// Workaround for electron/electron#5050 and electron/electron#9046
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.
app.on('window-all-closed', () => {
if (app.listenerCount('window-all-closed') === 1) {
app.quit();
}
});
Promise.all([
Promise.resolve().then(() => require('@electron/internal/browser/default-menu')),
app.whenReady
]).then(([{ setDefaultApplicationMenu }]) => {
// Create default menu
setDefaultApplicationMenu();
});
if (packagePath) {
// Finally load app's main.js and transfer control to C++.
Module._load(path.join(packagePath, mainStartupScript), Module, true);
}
else {
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');
}
// Finally load app's main.js and transfer control to C++.
Module._load(path.join(packagePath, mainStartupScript), Module, true)

View File

@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ipc_main_internal_1 = require("@electron/internal/browser/ipc-main-internal");
const errorUtils = require("@electron/internal/common/error-utils");
const callHandler = async function (handler, event, args, reply) {
try {
const result = await handler(event, ...args);
reply([null, result]);
}
catch (error) {
reply([errorUtils.serialize(error)]);
}
};
exports.handle = function (channel, handler) {
ipc_main_internal_1.ipcMainInternal.on(channel, (event, requestId, ...args) => {
callHandler(handler, event, args, responseArgs => {
if (requestId) {
event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs);
}
else {
event.returnValue = responseArgs;
}
});
});
};
let nextId = 0;
function invokeInWebContents(sender, sendToAll, command, ...args) {
return new Promise((resolve, reject) => {
const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}`;
ipc_main_internal_1.ipcMainInternal.on(channel, function handler(event, error, result) {
if (event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
return;
}
ipc_main_internal_1.ipcMainInternal.removeListener(channel, handler);
if (error) {
reject(errorUtils.deserialize(error));
}
else {
resolve(result);
}
});
if (sendToAll) {
sender._sendInternalToAll(command, requestId, ...args);
}
else {
sender._sendInternal(command, requestId, ...args);
}
});
}
exports.invokeInWebContents = invokeInWebContents;

View File

@ -1,10 +1,7 @@
'use strict'
const { EventEmitter } = require('events')
const emitter = new EventEmitter()
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const emitter = new events_1.EventEmitter();
// Do not throw exception when channel name is "error".
emitter.on('error', () => {})
module.exports = emitter
emitter.on('error', () => { });
exports.ipcMainInternal = emitter;

View File

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

View File

@ -1,116 +1,120 @@
'use strict'
const v8Util = process.atomBinding('v8_util')
'use strict';
const v8Util = process.electronBinding('v8_util');
const getOwnerKey = (webContents, contextId) => {
return `${webContents.id}-${contextId}`
}
return `${webContents.id}-${contextId}`;
};
class ObjectsRegistry {
constructor () {
this.nextId = 0
// Stores all objects by ref-counting.
// (id) => {object, count}
this.storage = {}
// Stores the IDs of objects referenced by WebContents.
// (ownerKey) => [id]
this.owners = {}
}
// Register a new object and return its assigned ID. If the object is already
// registered then the already assigned ID would be returned.
add (webContents, contextId, obj) {
// Get or assign an ID to the object.
const id = this.saveToStorage(obj)
// Add object to the set of referenced objects.
const ownerKey = getOwnerKey(webContents, contextId)
let owner = this.owners[ownerKey]
if (!owner) {
owner = this.owners[ownerKey] = new Set()
this.registerDeleteListener(webContents, contextId)
constructor() {
this.nextId = 0;
// Stores all objects by ref-counting.
// (id) => {object, count}
this.storage = {};
// Stores the IDs + refCounts of objects referenced by WebContents.
// (ownerKey) => { id: refCount }
this.owners = {};
}
if (!owner.has(id)) {
owner.add(id)
// Increase reference count if not referenced before.
this.storage[id].count++
// Register a new object and return its assigned ID. If the object is already
// registered then the already assigned ID would be returned.
add(webContents, contextId, obj) {
// Get or assign an ID to the object.
const id = this.saveToStorage(obj);
// Add object to the set of referenced objects.
const ownerKey = getOwnerKey(webContents, contextId);
let owner = this.owners[ownerKey];
if (!owner) {
owner = this.owners[ownerKey] = new Map();
this.registerDeleteListener(webContents, contextId);
}
if (!owner.has(id)) {
owner.set(id, 0);
// Increase reference count if not referenced before.
this.storage[id].count++;
}
owner.set(id, owner.get(id) + 1);
return id;
}
return id
}
// Get an object according to its ID.
get (id) {
const pointer = this.storage[id]
if (pointer != null) return pointer.object
}
// Dereference an object according to its ID.
// Note that an object may be double-freed (cleared when page is reloaded, and
// then garbage collected in old page).
remove (webContents, contextId, id) {
const ownerKey = getOwnerKey(webContents, contextId)
const owner = this.owners[ownerKey]
if (owner) {
// Remove the reference in owner.
owner.delete(id)
// Dereference from the storage.
this.dereference(id)
// Get an object according to its ID.
get(id) {
const pointer = this.storage[id];
if (pointer != null)
return pointer.object;
}
}
// Clear all references to objects refrenced by the WebContents.
clear (webContents, contextId) {
const ownerKey = getOwnerKey(webContents, contextId)
const owner = this.owners[ownerKey]
if (!owner) return
for (const id of owner) this.dereference(id)
delete this.owners[ownerKey]
}
// Private: Saves the object into storage and assigns an ID for it.
saveToStorage (object) {
let id = v8Util.getHiddenValue(object, 'atomId')
if (!id) {
id = ++this.nextId
this.storage[id] = {
count: 0,
object: object
}
v8Util.setHiddenValue(object, 'atomId', id)
// Dereference an object according to its ID.
// Note that an object may be double-freed (cleared when page is reloaded, and
// then garbage collected in old page).
// rendererSideRefCount is the ref count that the renderer process reported
// at time of GC if this is different to the number of references we sent to
// the given owner then a GC occurred between a ref being sent and the value
// being pulled out of the weak map.
// In this case we decrement out ref count and do not delete the stored
// object
// For more details on why we do renderer side ref counting see
// https://github.com/electron/electron/pull/17464
remove(webContents, contextId, id, rendererSideRefCount) {
const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey];
if (owner && owner.has(id)) {
const newRefCount = owner.get(id) - rendererSideRefCount;
// Only completely remove if the number of references GCed in the
// renderer is the same as the number of references we sent them
if (newRefCount <= 0) {
// Remove the reference in owner.
owner.delete(id);
// Dereference from the storage.
this.dereference(id);
}
else {
owner.set(id, newRefCount);
}
}
}
return id
}
// Private: Dereference the object from store.
dereference (id) {
const pointer = this.storage[id]
if (pointer == null) {
return
// Clear all references to objects refrenced by the WebContents.
clear(webContents, contextId) {
const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey];
if (!owner)
return;
for (const id of owner.keys())
this.dereference(id);
delete this.owners[ownerKey];
}
pointer.count -= 1
if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId')
delete this.storage[id]
// Private: Saves the object into storage and assigns an ID for it.
saveToStorage(object) {
let id = v8Util.getHiddenValue(object, 'atomId');
if (!id) {
id = ++this.nextId;
this.storage[id] = {
count: 0,
object: object
};
v8Util.setHiddenValue(object, 'atomId', id);
}
return id;
}
}
// Private: Clear the storage when renderer process is destroyed.
registerDeleteListener (webContents, contextId) {
// contextId => ${processHostId}-${contextCount}
const processHostId = contextId.split('-')[0]
const listener = (event, deletedProcessHostId) => {
if (deletedProcessHostId &&
deletedProcessHostId.toString() === processHostId) {
webContents.removeListener('render-view-deleted', listener)
this.clear(webContents, contextId)
}
// Private: Dereference the object from store.
dereference(id) {
const pointer = this.storage[id];
if (pointer == null) {
return;
}
pointer.count -= 1;
if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId');
delete this.storage[id];
}
}
// Private: Clear the storage when renderer process is destroyed.
registerDeleteListener(webContents, contextId) {
// contextId => ${processHostId}-${contextCount}
const processHostId = contextId.split('-')[0];
const listener = (event, deletedProcessHostId) => {
if (deletedProcessHostId &&
deletedProcessHostId.toString() === processHostId) {
webContents.removeListener('render-view-deleted', listener);
this.clear(webContents, contextId);
}
};
webContents.on('render-view-deleted', listener);
}
webContents.on('render-view-deleted', listener)
}
}
module.exports = new ObjectsRegistry()
module.exports = new ObjectsRegistry();

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,95 +1,186 @@
'use strict'
let deprecationHandler = null
function warnOnce (oldName, newName) {
let warned = false
const msg = newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.`
return () => {
if (!warned && !process.noDeprecation) {
warned = true
deprecate.log(msg)
}
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
let deprecationHandler = null;
function warnOnce(oldName, newName) {
let warned = false;
const msg = newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.`;
return () => {
if (!warned && !process.noDeprecation) {
warned = true;
deprecate.log(msg);
}
};
}
const deprecate = {
setHandler: (handler) => { deprecationHandler = handler },
getHandler: () => deprecationHandler,
warn: (oldName, newName) => {
return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
},
log: (message) => {
if (typeof deprecationHandler === 'function') {
deprecationHandler(message)
} else if (process.throwDeprecation) {
throw new Error(message)
} else if (process.traceDeprecation) {
return console.trace(message)
} else {
return console.warn(`(electron) ${message}`)
setHandler: (handler) => { deprecationHandler = handler; },
getHandler: () => deprecationHandler,
warn: (oldName, newName) => {
if (!process.noDeprecation) {
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
}
},
log: (message) => {
if (typeof deprecationHandler === 'function') {
deprecationHandler(message);
}
else if (process.throwDeprecation) {
throw new Error(message);
}
else if (process.traceDeprecation) {
return console.trace(message);
}
else {
return console.warn(`(electron) ${message}`);
}
},
// remove a function with no replacement
removeFunction: (fn, removedName) => {
if (!fn) {
throw Error(`'${removedName} function' is invalid or does not exist.`);
}
// wrap the deprecated function to warn user
const warn = warnOnce(`${fn.name} function`);
return function () {
warn();
fn.apply(this, arguments);
};
},
// change the name of a function
renameFunction: (fn, newName) => {
const warn = warnOnce(`${fn.name} function`, `${newName} function`);
return function () {
warn();
fn.apply(this, arguments);
};
},
// change the name of an event
event: (emitter, oldName, newName) => {
const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`)
: warnOnce(`${oldName} event`, `${newName} event`);
return emitter.on(newName, function (...args) {
if (this.listenerCount(oldName) !== 0) {
warn();
this.emit(oldName, ...args);
}
});
},
// deprecate a getter/setter function pair in favor of a property
fnToProperty: (module, prop, getter, setter) => {
const withWarnOnce = (obj, key, oldName, newName) => {
const warn = warnOnce(oldName, newName);
return (...args) => {
warn();
return obj[key](...args);
};
};
module[getter.substr(1)] = withWarnOnce(module, getter, `${getter.substr(1)} function`, `${prop} property`);
module[setter.substr(1)] = withWarnOnce(module, setter, `${setter.substr(1)} function`, `${prop} property`);
},
// remove a property with no replacement
removeProperty: (o, removedName) => {
// if the property's already been removed, warn about it
if (!(removedName in o)) {
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`);
}
// wrap the deprecated property in an accessor to warn
const warn = warnOnce(removedName);
let val = o[removedName];
return Object.defineProperty(o, removedName, {
configurable: true,
get: () => {
warn();
return val;
},
set: newVal => {
warn();
val = newVal;
}
});
},
// deprecate a callback-based function in favor of one returning a Promise
promisify: (fn) => {
const fnName = fn.name || 'function';
const oldName = `${fnName} with callbacks`;
const newName = `${fnName} with Promises`;
const warn = warnOnce(oldName, newName);
return function (...params) {
let cb;
if (params.length > 0 && typeof params[params.length - 1] === 'function') {
cb = params.pop();
}
const promise = fn.apply(this, params);
if (!cb)
return promise;
if (process.enablePromiseAPIs)
warn();
return promise
.then((res) => {
process.nextTick(() => {
cb.length === 2 ? cb(null, res) : cb(res);
});
return res;
}, (err) => {
process.nextTick(() => {
cb.length === 2 ? cb(err) : cb();
});
throw err;
});
};
},
// deprecate a callback-based function in favor of one returning a Promise
promisifyMultiArg: (fn, convertPromiseValue) => {
const fnName = fn.name || 'function';
const oldName = `${fnName} with callbacks`;
const newName = `${fnName} with Promises`;
const warn = warnOnce(oldName, newName);
return function (...params) {
let cb;
if (params.length > 0 && typeof params[params.length - 1] === 'function') {
cb = params.pop();
}
const promise = fn.apply(this, params);
if (!cb)
return promise;
if (process.enablePromiseAPIs)
warn();
return promise
.then((res) => {
if (convertPromiseValue) {
res = convertPromiseValue(res);
}
process.nextTick(() => {
// eslint-disable-next-line standard/no-callback-literal
cb.length > 2 ? cb(null, ...res) : cb(...res);
});
}, (err) => {
process.nextTick(() => cb(err));
});
};
},
// change the name of a property
renameProperty: (o, oldName, newName) => {
const warn = warnOnce(oldName, newName);
// if the new property isn't there yet,
// inject it and warn about it
if ((oldName in o) && !(newName in o)) {
warn();
o[newName] = o[oldName];
}
// wrap the deprecated property in an accessor to warn
// and redirect to the new property
return Object.defineProperty(o, oldName, {
get: () => {
warn();
return o[newName];
},
set: value => {
warn();
o[newName] = value;
}
});
}
},
event: (emitter, oldName, newName) => {
const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`)
: warnOnce(`${oldName} event`, `${newName} event`)
return emitter.on(newName, function (...args) {
if (this.listenerCount(oldName) !== 0) {
warn()
this.emit(oldName, ...args)
}
})
},
removeProperty: (o, removedName) => {
// if the property's already been removed, warn about it
if (!(removedName in o)) {
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`)
}
// wrap the deprecated property in an accessor to warn
const warn = warnOnce(removedName)
let val = o[removedName]
return Object.defineProperty(o, removedName, {
configurable: true,
get: () => {
warn()
return val
},
set: newVal => {
warn()
val = newVal
}
})
},
renameProperty: (o, oldName, newName) => {
const warn = warnOnce(oldName, newName)
// if the new property isn't there yet,
// inject it and warn about it
if ((oldName in o) && !(newName in o)) {
warn()
o[newName] = o[oldName]
}
// wrap the deprecated property in an accessor to warn
// and redirect to the new property
return Object.defineProperty(o, oldName, {
get: () => {
warn()
return o[newName]
},
set: value => {
warn()
o[newName] = value
}
})
}
}
module.exports = deprecate
};
exports.default = deprecate;

View File

@ -1,11 +1,8 @@
'use strict'
const deprecate = require('electron').deprecate
'use strict';
const deprecate = require('electron').deprecate;
exports.setHandler = function (deprecationHandler) {
deprecate.setHandler(deprecationHandler)
}
deprecate.setHandler(deprecationHandler);
};
exports.getHandler = function () {
return deprecate.getHandler()
}
return deprecate.getHandler();
};

View File

@ -1,31 +1,33 @@
'use strict'
const moduleList = require('@electron/internal/common/api/module-list')
'use strict';
const moduleList = require('@electron/internal/common/api/module-list');
exports.memoizedGetter = (getter) => {
/*
* It's ok to leak this value as it would be leaked by the global
* node module cache anyway at `Module._cache`. This memoization
* is dramatically faster than relying on nodes module cache however
*/
let memoizedValue = null
return () => {
if (memoizedValue === null) {
memoizedValue = getter()
}
return memoizedValue
}
}
/*
* It's ok to leak this value as it would be leaked by the global
* node module cache anyway at `Module._cache`. This memoization
* is dramatically faster than relying on nodes module cache however
*/
let memoizedValue = null;
return () => {
if (memoizedValue === null) {
memoizedValue = getter();
}
return memoizedValue;
};
};
// Attaches properties to |targetExports|.
exports.defineProperties = function (targetExports) {
const descriptors = {}
for (const module of moduleList) {
descriptors[module.name] = {
enumerable: !module.private,
get: exports.memoizedGetter(() => require(`@electron/internal/common/api/${module.file}`))
const descriptors = {};
for (const module of moduleList) {
descriptors[module.name] = {
enumerable: !module.private,
get: exports.memoizedGetter(() => {
const value = require(`@electron/internal/common/api/${module.file}.js`);
// Handle Typescript modules with an "export default X" statement
if (value.__esModule)
return value.default;
return value;
})
};
}
}
return Object.defineProperties(targetExports, descriptors)
}
return Object.defineProperties(targetExports, descriptors);
};

View File

@ -1,14 +1,11 @@
'use strict'
module.exports = function isPromise (val) {
return (
val &&
val.then &&
val.then instanceof Function &&
val.constructor &&
val.constructor.reject &&
val.constructor.reject instanceof Function &&
val.constructor.resolve &&
val.constructor.resolve instanceof Function
)
}
'use strict';
module.exports = function isPromise(val) {
return (val &&
val.then &&
val.then instanceof Function &&
val.constructor &&
val.constructor.reject &&
val.constructor.reject instanceof Function &&
val.constructor.resolve &&
val.constructor.resolve instanceof Function);
};

View File

@ -1,12 +1,11 @@
'use strict'
'use strict';
// Common modules, please sort alphabetically
module.exports = [
{ name: 'clipboard', file: 'clipboard' },
{ name: 'nativeImage', file: 'native-image' },
{ name: 'shell', file: 'shell' },
// The internal modules, invisible unless you know their names.
{ name: 'deprecate', file: 'deprecate', private: true },
{ name: 'deprecations', file: 'deprecations', private: true },
{ name: 'isPromise', file: 'is-promise', private: true }
]
{ name: 'clipboard', file: 'clipboard' },
{ name: 'nativeImage', file: 'native-image' },
{ name: 'shell', file: 'shell' },
// The internal modules, invisible unless you know their names.
{ name: 'deprecate', file: 'deprecate', private: true },
{ name: 'deprecations', file: 'deprecations', private: true },
{ name: 'isPromise', file: 'is-promise', private: true }
];

View File

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

View File

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

View File

@ -1,15 +1,18 @@
'use strict'
module.exports = function atomBindingSetup (binding, processType) {
return function atomBinding (name) {
try {
return binding(`atom_${processType}_${name}`)
} catch (error) {
if (/No such module/.test(error.message)) {
return binding(`atom_common_${name}`)
} else {
throw error
}
}
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function electronBindingSetup(binding, processType) {
return function electronBinding(name) {
try {
return binding(`atom_${processType}_${name}`);
}
catch (error) {
if (/No such module/.test(error.message)) {
return binding(`atom_common_${name}`);
}
else {
throw error;
}
}
};
}
exports.electronBindingSetup = electronBindingSetup;

View File

@ -1,66 +1,63 @@
'use strict'
'use strict';
// Note: Don't use destructuring assignment for `Buffer`, or we'll hit a
// browserify bug that makes the statement invalid, throwing an error in
// sandboxed renderer.
const Buffer = require('buffer').Buffer
const Buffer = require('buffer').Buffer;
const typedArrays = {
Buffer,
ArrayBuffer,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
}
function getType (value) {
for (const type of Object.keys(typedArrays)) {
if (value instanceof typedArrays[type]) {
return type
Buffer,
ArrayBuffer,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
};
function getType(value) {
for (const type of Object.keys(typedArrays)) {
if (value instanceof typedArrays[type]) {
return type;
}
}
}
return null
return null;
}
function getBuffer (value) {
if (value instanceof Buffer) {
return value
} else if (value instanceof ArrayBuffer) {
return Buffer.from(value)
} else {
return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
}
function getBuffer(value) {
if (value instanceof Buffer) {
return value;
}
else if (value instanceof ArrayBuffer) {
return Buffer.from(value);
}
else {
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
}
}
exports.isBuffer = function (value) {
return ArrayBuffer.isView(value) || value instanceof ArrayBuffer
}
return ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
};
exports.bufferToMeta = function (value) {
return {
type: getType(value),
data: getBuffer(value),
length: value.length
}
}
return {
type: getType(value),
data: getBuffer(value),
length: value.length
};
};
exports.metaToBuffer = function (value) {
const constructor = typedArrays[value.type]
const data = getBuffer(value.data)
if (constructor === Buffer) {
return data
} else if (constructor === ArrayBuffer) {
return data.buffer
} else if (constructor) {
return new constructor(data.buffer, data.byteOffset, value.length)
} else {
return data
}
}
const constructor = typedArrays[value.type];
const data = getBuffer(value.data);
if (constructor === Buffer) {
return data;
}
else if (constructor === ArrayBuffer) {
return data.buffer;
}
else if (constructor) {
return new constructor(data.buffer, data.byteOffset, value.length);
}
else {
return data;
}
};

View File

@ -0,0 +1,49 @@
'use strict';
const { nativeImage, NativeImage } = process.electronBinding('native_image');
const objectMap = function (source, mapper) {
const sourceEntries = Object.entries(source);
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]);
return Object.fromEntries(targetEntries);
};
const serialize = function (value) {
if (value instanceof NativeImage) {
return {
buffer: value.toBitmap(),
size: value.getSize(),
__ELECTRON_SERIALIZED_NativeImage__: true
};
}
else if (Array.isArray(value)) {
return value.map(serialize);
}
else if (value instanceof Buffer) {
return value;
}
else if (value instanceof Object) {
return objectMap(value, serialize);
}
else {
return value;
}
};
const deserialize = function (value) {
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
return nativeImage.createFromBitmap(value.buffer, value.size);
}
else if (Array.isArray(value)) {
return value.map(deserialize);
}
else if (value instanceof Buffer) {
return value;
}
else if (value instanceof Object) {
return objectMap(value, deserialize);
}
else {
return value;
}
};
module.exports = {
serialize,
deserialize
};

View File

@ -1,123 +1,77 @@
'use strict'
const binding = process.atomBinding('crash_reporter')
const errorUtils = require('@electron/internal/common/error-utils')
'use strict';
const binding = process.electronBinding('crash_reporter');
class CrashReporter {
contructor () {
this.productName = null
this.crashesDirectory = null
}
sendSync (channel, ...args) {
throw new Error('Not implemented')
}
invoke (command, ...args) {
const [ error, result ] = this.sendSync(command, ...args)
if (error) {
throw errorUtils.deserialize(error)
contructor() {
this.productName = null;
this.crashesDirectory = null;
}
return result
}
start (options) {
if (options == null) options = {}
let {
productName,
companyName,
extra,
ignoreSystemCrashHandler,
submitURL,
uploadToServer
} = options
if (uploadToServer == null) {
uploadToServer = true
init(options) {
throw new Error('Not implemented');
}
if (ignoreSystemCrashHandler == null) {
ignoreSystemCrashHandler = false
start(options) {
if (options == null)
options = {};
const { productName, companyName, extra = {}, ignoreSystemCrashHandler = false, submitURL, uploadToServer = true } = options;
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');
const ret = this.init({
submitURL,
productName
});
this.productName = ret.productName;
this.crashesDirectory = ret.crashesDirectory;
if (extra._productName == null)
extra._productName = ret.productName;
if (extra._companyName == null)
extra._companyName = companyName;
if (extra._version == null)
extra._version = ret.appVersion;
binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra);
}
if (companyName == null) {
throw new Error('companyName is a required option to crashReporter.start')
getLastCrashReport() {
const reports = this.getUploadedReports()
.sort((a, b) => {
const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
return bts - ats;
});
return (reports.length > 0) ? reports[0] : null;
}
if (submitURL == null) {
throw new Error('submitURL is a required option to crashReporter.start')
getUploadedReports() {
return binding.getUploadedReports(this.getCrashesDirectory());
}
const ret = this.invoke('ELECTRON_CRASH_REPORTER_INIT', {
submitURL,
productName
})
this.productName = ret.productName
this.crashesDirectory = ret.crashesDirectory
this.crashServicePid = ret.crashServicePid
if (extra == null) extra = {}
if (extra._productName == null) extra._productName = ret.productName
if (extra._companyName == null) extra._companyName = companyName
if (extra._version == null) extra._version = ret.appVersion
binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra)
}
getLastCrashReport () {
const reports = this.getUploadedReports()
.sort((a, b) => {
const ats = (a && a.date) ? new Date(a.date).getTime() : 0
const bts = (b && b.date) ? new Date(b.date).getTime() : 0
return bts - ats
})
return (reports.length > 0) ? reports[0] : null
}
getUploadedReports () {
return binding.getUploadedReports(this.getCrashesDirectory())
}
getCrashesDirectory () {
return this.crashesDirectory
}
getProductName () {
return this.productName
}
getUploadToServer () {
if (process.type === 'browser') {
return binding.getUploadToServer()
} else {
throw new Error('getUploadToServer can only be called from the main process')
getCrashesDirectory() {
return this.crashesDirectory;
}
}
setUploadToServer (uploadToServer) {
if (process.type === 'browser') {
return binding.setUploadToServer(uploadToServer)
} else {
throw new Error('setUploadToServer can only be called from the main process')
getProductName() {
return this.productName;
}
getUploadToServer() {
if (process.type === 'browser') {
return binding.getUploadToServer();
}
else {
throw new Error('getUploadToServer can only be called from the main process');
}
}
setUploadToServer(uploadToServer) {
if (process.type === 'browser') {
return binding.setUploadToServer(uploadToServer);
}
else {
throw new Error('setUploadToServer can only be called from the main process');
}
}
addExtraParameter(key, value) {
binding.addExtraParameter(key, value);
}
removeExtraParameter(key) {
binding.removeExtraParameter(key);
}
getParameters() {
return binding.getParameters();
}
}
addExtraParameter (key, value) {
binding.addExtraParameter(key, value)
}
removeExtraParameter (key) {
binding.removeExtraParameter(key)
}
getParameters (key, value) {
return binding.getParameters()
}
}
module.exports = CrashReporter
module.exports = CrashReporter;

View File

@ -1,39 +1,36 @@
'use strict'
'use strict';
const constructors = new Map([
[Error.name, Error],
[EvalError.name, EvalError],
[RangeError.name, RangeError],
[ReferenceError.name, ReferenceError],
[SyntaxError.name, SyntaxError],
[TypeError.name, TypeError],
[URIError.name, URIError]
])
[Error.name, Error],
[EvalError.name, EvalError],
[RangeError.name, RangeError],
[ReferenceError.name, ReferenceError],
[SyntaxError.name, SyntaxError],
[TypeError.name, TypeError],
[URIError.name, URIError]
]);
exports.deserialize = function (error) {
if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) {
const constructor = constructors.get(error.name)
const deserializedError = new constructor(error.message)
deserializedError.stack = error.stack
deserializedError.from = error.from
deserializedError.cause = exports.deserialize(error.cause)
return deserializedError
}
return error
}
exports.serialize = function (error) {
if (error instanceof Error) {
// Errors get lost, because: JSON.stringify(new Error('Message')) === {}
// Take the serializable properties and construct a generic object
return {
message: error.message,
stack: error.stack,
name: error.name,
from: process.type,
cause: exports.serialize(error.cause),
__ELECTRON_SERIALIZED_ERROR__: true
if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) {
const constructor = constructors.get(error.name);
const deserializedError = new constructor(error.message);
deserializedError.stack = error.stack;
deserializedError.from = error.from;
deserializedError.cause = exports.deserialize(error.cause);
return deserializedError;
}
}
return error
}
return error;
};
exports.serialize = function (error) {
if (error instanceof Error) {
// Errors get lost, because: JSON.stringify(new Error('Message')) === {}
// Take the serializable properties and construct a generic object
return {
message: error.message,
stack: error.stack,
name: error.name,
from: process.type,
cause: exports.serialize(error.cause),
__ELECTRON_SERIALIZED_ERROR__: true
};
}
return error;
};

View File

@ -1,10 +1,9 @@
'use strict'
const timers = require('timers')
const util = require('util')
process.atomBinding = require('@electron/internal/common/atom-binding-setup')(process.binding, process.type)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util = require("util");
const atom_binding_setup_1 = require("@electron/internal/common/atom-binding-setup");
const timers = require('timers');
process.electronBinding = atom_binding_setup_1.electronBindingSetup(process._linkedBinding, process.type);
// setImmediate and process.nextTick makes use of uv_check and uv_prepare to
// run the callbacks, however since we only run uv loop on requests, the
// callbacks wouldn't be called until something else activated the uv loop,
@ -12,42 +11,50 @@ process.atomBinding = require('@electron/internal/common/atom-binding-setup')(pr
// initiatively activate the uv loop once setImmediate and process.nextTick is
// called.
const wrapWithActivateUvLoop = function (func) {
return wrap(func, function (func) {
return function () {
process.activateUvLoop()
return func.apply(this, arguments)
return wrap(func, function (func) {
return function (...args) {
process.activateUvLoop();
return func.apply(this, args);
};
});
};
/**
* Casts to any below for func are due to Typescript not supporting symbols
* in index signatures
*
* Refs: https://github.com/Microsoft/TypeScript/issues/1863
*/
function wrap(func, wrapper) {
const wrapped = wrapper(func);
if (func[util.promisify.custom]) {
wrapped[util.promisify.custom] = wrapper(func[util.promisify.custom]);
}
})
return wrapped;
}
function wrap (func, wrapper) {
const wrapped = wrapper(func)
if (func[util.promisify.custom]) {
wrapped[util.promisify.custom] = wrapper(func[util.promisify.custom])
}
return wrapped
}
process.nextTick = wrapWithActivateUvLoop(process.nextTick)
global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate)
global.clearImmediate = timers.clearImmediate
process.nextTick = wrapWithActivateUvLoop(process.nextTick);
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.clearImmediate = timers.clearImmediate;
// 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
// to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in browser process.
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// Only override the global setTimeout/setInterval impls in the browser process
if (process.type === 'browser') {
// 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
// to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in browser process.
global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout)
global.setInterval = wrapWithActivateUvLoop(timers.setInterval)
global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval;
}
if (process.platform === 'win32') {
// Always returns EOF for stdin stream.
const { Readable } = require('stream')
const stdin = new Readable()
stdin.push(null)
process.__defineGetter__('stdin', function () {
return stdin
})
// Always returns EOF for stdin stream.
const { Readable } = require('stream');
const stdin = new Readable();
stdin.push(null);
Object.defineProperty(process, 'stdin', {
configurable: false,
enumerable: true,
get() {
return stdin;
}
});
}

View File

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

View File

@ -1,45 +1,43 @@
'use strict'
const path = require('path')
const Module = require('module')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const Module = require('module');
// Clear Node's global search paths.
Module.globalPaths.length = 0
Module.globalPaths.length = 0;
// Clear current and parent(init.js)'s search paths.
module.paths = []
module.parent.paths = []
module.paths = [];
module.parent.paths = [];
// Prevent Node from adding paths outside this app to search paths.
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
const originalNodeModulePaths = Module._nodeModulePaths
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep;
const originalNodeModulePaths = Module._nodeModulePaths;
Module._nodeModulePaths = function (from) {
const paths = originalNodeModulePaths(from)
const fromPath = path.resolve(from) + path.sep
// If "from" is outside the app then we do nothing.
if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
return paths.filter(function (candidate) {
return candidate.startsWith(resourcesPathWithTrailingSlash)
})
} else {
return paths
}
}
const BASE_INTERNAL_PATH = path.resolve(__dirname, '..')
const INTERNAL_MODULE_PREFIX = '@electron/internal/'
const paths = originalNodeModulePaths(from);
const fromPath = path.resolve(from) + path.sep;
// If "from" is outside the app then we do nothing.
if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
return paths.filter(function (candidate) {
return candidate.startsWith(resourcesPathWithTrailingSlash);
});
}
else {
return paths;
}
};
const BASE_INTERNAL_PATH = path.resolve(__dirname, '..');
const INTERNAL_MODULE_PREFIX = '@electron/internal/';
// Patch Module._resolveFilename to always require the Electron API when
// require('electron') is done.
const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js')
const originalResolveFilename = Module._resolveFilename
const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js');
const originalResolveFilename = Module._resolveFilename;
Module._resolveFilename = function (request, parent, isMain) {
if (request === 'electron') {
return electronPath
} else if (request.startsWith(INTERNAL_MODULE_PREFIX) && request.length > INTERNAL_MODULE_PREFIX.length) {
const slicedRequest = request.slice(INTERNAL_MODULE_PREFIX.length)
return path.resolve(BASE_INTERNAL_PATH, `${slicedRequest}${slicedRequest.endsWith('.js') ? '' : '.js'}`)
} else {
return originalResolveFilename(request, parent, isMain)
}
}
if (request === 'electron') {
return electronPath;
}
else if (request.startsWith(INTERNAL_MODULE_PREFIX) && request.length > INTERNAL_MODULE_PREFIX.length) {
const slicedRequest = request.slice(INTERNAL_MODULE_PREFIX.length);
return path.resolve(BASE_INTERNAL_PATH, `${slicedRequest}${slicedRequest.endsWith('.js') ? '' : '.js'}`);
}
else {
return originalResolveFilename(request, parent, isMain);
}
};

View File

@ -1,70 +1,68 @@
'use strict'
'use strict';
// Public-facing API methods.
exports.syncMethods = new Set([
'getURL',
'loadURL',
'getTitle',
'isLoading',
'isLoadingMainFrame',
'isWaitingForResponse',
'stop',
'reload',
'reloadIgnoringCache',
'canGoBack',
'canGoForward',
'canGoToOffset',
'clearHistory',
'goBack',
'goForward',
'goToIndex',
'goToOffset',
'isCrashed',
'setUserAgent',
'getUserAgent',
'openDevTools',
'closeDevTools',
'isDevToolsOpened',
'isDevToolsFocused',
'inspectElement',
'setAudioMuted',
'isAudioMuted',
'isCurrentlyAudible',
'undo',
'redo',
'cut',
'copy',
'paste',
'pasteAndMatchStyle',
'delete',
'selectAll',
'unselect',
'replace',
'replaceMisspelling',
'findInPage',
'stopFindInPage',
'downloadURL',
'inspectServiceWorker',
'showDefinitionForSelection',
'setZoomFactor',
'setZoomLevel',
'sendImeEvent'
])
'getURL',
'getTitle',
'isLoading',
'isLoadingMainFrame',
'isWaitingForResponse',
'stop',
'reload',
'reloadIgnoringCache',
'canGoBack',
'canGoForward',
'canGoToOffset',
'clearHistory',
'goBack',
'goForward',
'goToIndex',
'goToOffset',
'isCrashed',
'setUserAgent',
'getUserAgent',
'openDevTools',
'closeDevTools',
'isDevToolsOpened',
'isDevToolsFocused',
'inspectElement',
'setAudioMuted',
'isAudioMuted',
'isCurrentlyAudible',
'undo',
'redo',
'cut',
'copy',
'paste',
'pasteAndMatchStyle',
'delete',
'selectAll',
'unselect',
'replace',
'replaceMisspelling',
'findInPage',
'stopFindInPage',
'downloadURL',
'inspectSharedWorker',
'inspectServiceWorker',
'showDefinitionForSelection',
'getZoomFactor',
'getZoomLevel',
'setZoomFactor',
'setZoomLevel',
'sendImeEvent'
]);
exports.asyncCallbackMethods = new Set([
'insertCSS',
'insertText',
'send',
'sendInputEvent',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits',
'getZoomFactor',
'getZoomLevel',
'print',
'printToPDF'
])
'insertCSS',
'insertText',
'send',
'sendInputEvent',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits',
'print'
]);
exports.asyncPromiseMethods = new Set([
'capturePage',
'executeJavaScript'
])
'loadURL',
'capturePage',
'executeJavaScript',
'printToPDF'
]);

View File

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

View File

@ -1,48 +1,37 @@
'use strict'
const { nativeImage } = require('electron')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const includes = [].includes
let currentId = 0
const incrementId = () => {
currentId += 1
return currentId
}
'use strict';
const { nativeImage, deprecate } = require('electron');
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
// |options.types| can't be empty and must be an array
function isValid (options) {
const types = options ? options.types : undefined
return Array.isArray(types)
function isValid(options) {
const types = options ? options.types : undefined;
return Array.isArray(types);
}
exports.getSources = function (options, callback) {
if (!isValid(options)) return callback(new Error('Invalid options'))
const captureWindow = includes.call(options.types, 'window')
const captureScreen = includes.call(options.types, 'screen')
if (options.thumbnailSize == null) {
options.thumbnailSize = {
width: 150,
height: 150
}
}
const id = incrementId()
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id)
return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => {
callback(null, (() => {
const results = []
sources.forEach(source => {
results.push({
id: source.id,
name: source.name,
thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id
})
})
return results
})())
})
function mapSources(sources) {
return sources.map(source => ({
id: source.id,
name: source.name,
thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id,
appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null
}));
}
const getSources = (options) => {
return new Promise((resolve, reject) => {
if (!isValid(options))
throw new Error('Invalid options');
const captureWindow = options.types.includes('window');
const captureScreen = options.types.includes('screen');
if (options.thumbnailSize == null) {
options.thumbnailSize = {
width: 150,
height: 150
};
}
if (options.fetchWindowIcons == null) {
options.fetchWindowIcons = false;
}
ipcRendererUtils.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, options.fetchWindowIcons)
.then(sources => resolve(mapSources(sources)), reject);
});
};
exports.getSources = deprecate.promisify(getSources);

View File

@ -1,23 +1,20 @@
'use strict'
const common = require('@electron/internal/common/api/exports/electron')
const moduleList = require('@electron/internal/renderer/api/module-list')
'use strict';
const common = require('@electron/internal/common/api/exports/electron');
const moduleList = require('@electron/internal/renderer/api/module-list');
// Import common modules.
common.defineProperties(exports)
for (const {
name,
file,
enabled = true,
private: isPrivate = false
} of moduleList) {
if (!enabled) {
continue
}
Object.defineProperty(exports, name, {
enumerable: !isPrivate,
get: common.memoizedGetter(() => require(`@electron/internal/renderer/api/${file}`))
})
common.defineProperties(exports);
for (const { name, file, enabled = true, private: isPrivate = false } of moduleList) {
if (!enabled) {
continue;
}
Object.defineProperty(exports, name, {
enumerable: !isPrivate,
get: common.memoizedGetter(() => {
const value = require(`@electron/internal/renderer/api/${file}.js`);
// Handle Typescript modules with an "export default X" statement
if (value.__esModule)
return value.default;
return value;
})
});
}

View File

@ -1,30 +1,22 @@
'use strict'
const binding = process.atomBinding('ipc')
const v8Util = process.atomBinding('v8_util')
'use strict';
const { ipc } = process.electronBinding('ipc');
const v8Util = process.electronBinding('v8_util');
// Created by init.js.
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
const internal = false
ipcRenderer.send = function (...args) {
return binding.send('ipc-message', args)
}
ipcRenderer.sendSync = function (...args) {
return binding.sendSync('ipc-message-sync', args)[0]
}
ipcRenderer.sendToHost = function (...args) {
return binding.send('ipc-message-host', args)
}
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc');
const internal = false;
ipcRenderer.send = function (channel, ...args) {
return ipc.send(internal, channel, args);
};
ipcRenderer.sendSync = function (channel, ...args) {
return ipc.sendSync(internal, channel, args)[0];
};
ipcRenderer.sendToHost = function (channel, ...args) {
return ipc.sendToHost(channel, args);
};
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
return binding.sendTo(internal, false, webContentsId, channel, args)
}
return ipc.sendTo(internal, false, webContentsId, channel, args);
};
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
return binding.sendTo(internal, true, webContentsId, channel, args)
}
module.exports = ipcRenderer
return ipc.sendTo(internal, true, webContentsId, channel, args);
};
module.exports = ipcRenderer;

View File

@ -1,21 +1,17 @@
'use strict'
const features = process.atomBinding('features')
const v8Util = process.atomBinding('v8_util')
const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule')
'use strict';
const features = process.electronBinding('features');
const v8Util = process.electronBinding('v8_util');
const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule');
// Renderer side modules, please sort alphabetically.
// A module is `enabled` if there is no explicit condition defined.
module.exports = [
{ name: 'crashReporter', file: 'crash-reporter', enabled: true },
{
name: 'desktopCapturer',
file: 'desktop-capturer',
enabled: features.isDesktopCapturerEnabled()
},
{ name: 'ipcRenderer', file: 'ipc-renderer' },
{ name: 'remote', file: 'remote', enabled: enableRemoteModule },
{ name: 'screen', file: 'screen' },
{ name: 'webFrame', file: 'web-frame' }
]
{ name: 'crashReporter', file: 'crash-reporter', enabled: true },
{
name: 'desktopCapturer',
file: 'desktop-capturer',
enabled: features.isDesktopCapturerEnabled()
},
{ name: 'ipcRenderer', file: 'ipc-renderer' },
{ name: 'remote', file: 'remote', enabled: enableRemoteModule },
{ name: 'webFrame', file: 'web-frame' }
];

View File

@ -1,355 +1,339 @@
'use strict'
const v8Util = process.atomBinding('v8_util')
const { isPromise } = require('electron')
const resolvePromise = Promise.resolve.bind(Promise)
const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry')
const bufferUtils = require('@electron/internal/common/buffer-utils')
const errorUtils = require('@electron/internal/common/error-utils')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap()
'use strict';
const v8Util = process.electronBinding('v8_util');
const { isPromise } = require('electron');
const resolvePromise = Promise.resolve.bind(Promise);
const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry');
const bufferUtils = require('@electron/internal/common/buffer-utils');
const errorUtils = require('@electron/internal/common/error-utils');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const callbacksRegistry = new CallbacksRegistry();
const remoteObjectCache = v8Util.createIDWeakMap();
// 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.
// 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
// to guard that situation.
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRenderer.sendSync(command, contextId)
})
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE';
ipcRendererInternal.sendSync(command, contextId);
});
// Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) {
const valueToMeta = (value) => {
// Check for circular reference.
if (visited.has(value)) {
return {
type: 'value',
value: null
}
}
if (Array.isArray(value)) {
visited.add(value)
const meta = {
type: 'array',
value: wrapArgs(value, visited)
}
visited.delete(value)
return meta
} else if (bufferUtils.isBuffer(value)) {
return {
type: 'buffer',
value: bufferUtils.bufferToMeta(value)
}
} else if (value instanceof Date) {
return {
type: 'date',
value: value.getTime()
}
} else if ((value != null) && typeof value === 'object') {
if (isPromise(value)) {
return {
type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected)
})
function wrapArgs(args, visited = new Set()) {
const valueToMeta = (value) => {
// Check for circular reference.
if (visited.has(value)) {
return {
type: 'value',
value: null
};
}
} else if (v8Util.getHiddenValue(value, 'atomId')) {
return {
type: 'remote-object',
id: v8Util.getHiddenValue(value, 'atomId')
if (Array.isArray(value)) {
visited.add(value);
const meta = {
type: 'array',
value: wrapArgs(value, visited)
};
visited.delete(value);
return meta;
}
}
const meta = {
type: 'object',
name: value.constructor ? value.constructor.name : '',
members: []
}
visited.add(value)
for (const prop in value) {
meta.members.push({
name: prop,
value: valueToMeta(value[prop])
})
}
visited.delete(value)
return meta
} else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
return {
type: 'function-with-return-value',
value: valueToMeta(value())
}
} else if (typeof value === 'function') {
return {
type: 'function',
id: callbacksRegistry.add(value),
location: v8Util.getHiddenValue(value, 'location'),
length: value.length
}
} else {
return {
type: 'value',
value: value
}
}
}
return args.map(valueToMeta)
else if (bufferUtils.isBuffer(value)) {
return {
type: 'buffer',
value: bufferUtils.bufferToMeta(value)
};
}
else if (value instanceof Date) {
return {
type: 'date',
value: value.getTime()
};
}
else if ((value != null) && typeof value === 'object') {
if (isPromise(value)) {
return {
type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected);
})
};
}
else if (v8Util.getHiddenValue(value, 'atomId')) {
return {
type: 'remote-object',
id: v8Util.getHiddenValue(value, 'atomId')
};
}
const meta = {
type: 'object',
name: value.constructor ? value.constructor.name : '',
members: []
};
visited.add(value);
for (const prop in value) {
meta.members.push({
name: prop,
value: valueToMeta(value[prop])
});
}
visited.delete(value);
return meta;
}
else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
return {
type: 'function-with-return-value',
value: valueToMeta(value())
};
}
else if (typeof value === 'function') {
return {
type: 'function',
id: callbacksRegistry.add(value),
location: v8Util.getHiddenValue(value, 'location'),
length: value.length
};
}
else {
return {
type: 'value',
value: value
};
}
};
return args.map(valueToMeta);
}
// Populate object's members from descriptors.
// The |ref| will be kept referenced by |members|.
// This matches |getObjectMemebers| in rpc-server.
function setObjectMembers (ref, object, metaId, members) {
if (!Array.isArray(members)) return
for (const member of members) {
if (object.hasOwnProperty(member.name)) continue
const descriptor = { enumerable: member.enumerable }
if (member.type === 'method') {
const remoteMemberFunction = function (...args) {
let command
if (this && this.constructor === remoteMemberFunction) {
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR'
} else {
command = 'ELECTRON_BROWSER_MEMBER_CALL'
function setObjectMembers(ref, object, metaId, members) {
if (!Array.isArray(members))
return;
for (const member of members) {
if (object.hasOwnProperty(member.name))
continue;
const descriptor = { enumerable: member.enumerable };
if (member.type === 'method') {
const remoteMemberFunction = function (...args) {
let command;
if (this && this.constructor === remoteMemberFunction) {
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
}
else {
command = 'ELECTRON_BROWSER_MEMBER_CALL';
}
const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
return metaToValue(ret);
};
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name);
descriptor.get = () => {
descriptorFunction.ref = ref; // The member should reference its object.
return descriptorFunction;
};
// Enable monkey-patch the method
descriptor.set = (value) => {
descriptorFunction = value;
return value;
};
descriptor.configurable = true;
}
const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args))
return metaToValue(ret)
}
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name)
descriptor.get = () => {
descriptorFunction.ref = ref // The member should reference its object.
return descriptorFunction
}
// Enable monkey-patch the method
descriptor.set = (value) => {
descriptorFunction = value
return value
}
descriptor.configurable = true
} else if (member.type === 'get') {
descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name)
return metaToValue(meta)
}
if (member.writable) {
descriptor.set = (value) => {
const args = wrapArgs([value])
const command = 'ELECTRON_BROWSER_MEMBER_SET'
const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args)
if (meta != null) metaToValue(meta)
return value
else if (member.type === 'get') {
descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name);
return metaToValue(meta);
};
if (member.writable) {
descriptor.set = (value) => {
const args = wrapArgs([value]);
const command = 'ELECTRON_BROWSER_MEMBER_SET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args);
if (meta != null)
metaToValue(meta);
return value;
};
}
}
}
Object.defineProperty(object, member.name, descriptor);
}
Object.defineProperty(object, member.name, descriptor)
}
}
// Populate object's prototype from descriptor.
// This matches |getObjectPrototype| in rpc-server.
function setObjectPrototype (ref, object, metaId, descriptor) {
if (descriptor === null) return
const proto = {}
setObjectMembers(ref, proto, metaId, descriptor.members)
setObjectPrototype(ref, proto, metaId, descriptor.proto)
Object.setPrototypeOf(object, proto)
function setObjectPrototype(ref, object, metaId, descriptor) {
if (descriptor === null)
return;
const proto = {};
setObjectMembers(ref, proto, metaId, descriptor.members);
setObjectPrototype(ref, proto, metaId, descriptor.proto);
Object.setPrototypeOf(object, proto);
}
// Wrap function in Proxy for accessing remote properties
function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
let loaded = false
// Lazily load function properties
const loadRemoteProperties = () => {
if (loaded) return
loaded = true
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRenderer.sendSync(command, contextId, metaId, name)
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
}
return new Proxy(remoteMemberFunction, {
set: (target, property, value, receiver) => {
if (property !== 'ref') loadRemoteProperties()
target[property] = value
return true
},
get: (target, property, receiver) => {
if (!target.hasOwnProperty(property)) loadRemoteProperties()
const value = target[property]
if (property === 'toString' && typeof value === 'function') {
return value.bind(target)
}
return value
},
ownKeys: (target) => {
loadRemoteProperties()
return Object.getOwnPropertyNames(target)
},
getOwnPropertyDescriptor: (target, property) => {
const descriptor = Object.getOwnPropertyDescriptor(target, property)
if (descriptor) return descriptor
loadRemoteProperties()
return Object.getOwnPropertyDescriptor(target, property)
}
})
}
// Convert meta data from browser into real value.
function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
buffer: () => bufferUtils.metaToBuffer(meta.value),
promise: () => resolvePromise({ then: metaToValue(meta.then) }),
error: () => metaToPlainObject(meta),
date: () => new Date(meta.value),
exception: () => { throw errorUtils.deserialize(meta.value) }
}
if (meta.type in types) {
return types[meta.type]()
} else {
let ret
if (remoteObjectCache.has(meta.id)) {
return remoteObjectCache.get(meta.id)
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
let command
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR'
} else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL'
function proxyFunctionProperties(remoteMemberFunction, metaId, name) {
let loaded = false;
// Lazily load function properties
const loadRemoteProperties = () => {
if (loaded)
return;
loaded = true;
const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name);
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
};
return new Proxy(remoteMemberFunction, {
set: (target, property, value, receiver) => {
if (property !== 'ref')
loadRemoteProperties();
target[property] = value;
return true;
},
get: (target, property, receiver) => {
if (!target.hasOwnProperty(property))
loadRemoteProperties();
const value = target[property];
if (property === 'toString' && typeof value === 'function') {
return value.bind(target);
}
return value;
},
ownKeys: (target) => {
loadRemoteProperties();
return Object.getOwnPropertyNames(target);
},
getOwnPropertyDescriptor: (target, property) => {
const descriptor = Object.getOwnPropertyDescriptor(target, property);
if (descriptor)
return descriptor;
loadRemoteProperties();
return Object.getOwnPropertyDescriptor(target, property);
}
const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args))
return metaToValue(obj)
}
ret = remoteFunction
} else {
ret = {}
}
setObjectMembers(ret, ret, meta.id, meta.members)
setObjectPrototype(ret, ret, meta.id, meta.proto)
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, contextId, meta.id)
v8Util.setHiddenValue(ret, 'atomId', meta.id)
remoteObjectCache.set(meta.id, ret)
return ret
}
});
}
// Convert meta data from browser into real value.
function metaToValue(meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
buffer: () => bufferUtils.metaToBuffer(meta.value),
promise: () => resolvePromise({ then: metaToValue(meta.then) }),
error: () => metaToPlainObject(meta),
date: () => new Date(meta.value),
exception: () => { throw errorUtils.deserialize(meta.value); }
};
if (meta.type in types) {
return types[meta.type]();
}
else {
let ret;
if (remoteObjectCache.has(meta.id)) {
v8Util.addRemoteObjectRef(contextId, meta.id);
return remoteObjectCache.get(meta.id);
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
let command;
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR';
}
else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL';
}
const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args));
return metaToValue(obj);
};
ret = remoteFunction;
}
else {
ret = {};
}
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, contextId, meta.id);
v8Util.setHiddenValue(ret, 'atomId', meta.id);
v8Util.addRemoteObjectRef(contextId, meta.id);
remoteObjectCache.set(meta.id, ret);
return ret;
}
}
// Construct a plain object from the meta.
function metaToPlainObject (meta) {
const obj = (() => meta.type === 'error' ? new Error() : {})()
for (let i = 0; i < meta.members.length; i++) {
const { name, value } = meta.members[i]
obj[name] = value
}
return obj
}
function handleMessage (channel, handler) {
ipcRenderer.on(channel, (event, passedContextId, id, ...args) => {
if (passedContextId === contextId) {
handler(id, ...args)
} else {
// Message sent to an un-exist context, notify the error to main process.
ipcRenderer.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id)
function metaToPlainObject(meta) {
const obj = (() => meta.type === 'error' ? new Error() : {})();
for (let i = 0; i < meta.members.length; i++) {
const { name, value } = meta.members[i];
obj[name] = value;
}
})
return obj;
}
function handleMessage(channel, handler) {
ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
if (passedContextId === contextId) {
handler(id, ...args);
}
else {
// Message sent to an un-exist context, notify the error to main process.
ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id);
}
});
}
// Browser calls a callback in renderer.
handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
callbacksRegistry.apply(id, metaToValue(args))
})
callbacksRegistry.apply(id, metaToValue(args));
});
// A callback in browser is released.
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
callbacksRegistry.remove(id)
})
callbacksRegistry.remove(id);
});
exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE'
const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_REQUIRE';
const meta = ipcRendererInternal.sendSync(command, contextId, module);
return metaToValue(meta);
};
// Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_GET_BUILTIN';
const meta = ipcRendererInternal.sendSync(command, contextId, module);
return metaToValue(meta);
};
exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
const meta = ipcRenderer.sendSync(command, contextId)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
const meta = ipcRendererInternal.sendSync(command, contextId);
return metaToValue(meta);
};
// Get current WebContents object.
exports.getCurrentWebContents = () => {
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
}
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
const meta = ipcRendererInternal.sendSync(command, contextId);
return metaToValue(meta);
};
// Get a global object in browser.
exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRenderer.sendSync(command, contextId, name)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_GLOBAL';
const meta = ipcRendererInternal.sendSync(command, contextId, name);
return metaToValue(meta);
};
// Get the process object in browser.
exports.__defineGetter__('process', () => exports.getGlobal('process'))
exports.__defineGetter__('process', () => exports.getGlobal('process'));
// Create a function that will return the specified value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) => {
const func = () => returnValue
v8Util.setHiddenValue(func, 'returnValue', true)
return func
}
const func = () => returnValue;
v8Util.setHiddenValue(func, 'returnValue', true);
return func;
};
// Get the guest WebContents from guestInstanceId.
exports.getGuestWebContents = (guestInstanceId) => {
const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'
const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS';
const meta = ipcRendererInternal.sendSync(command, contextId, guestInstanceId);
return metaToValue(meta);
};
const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
})
}
const browserModules =
require('@electron/internal/common/api/module-list').concat(
require('@electron/internal/browser/api/module-list'))
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
});
};
const browserModules = require('@electron/internal/common/api/module-list').concat(require('@electron/internal/browser/api/module-list'));
// And add a helper receiver for each one.
browserModules
.filter((m) => !m.private)
.map((m) => m.name)
.forEach(addBuiltinProperty)
.filter((m) => !m.private)
.map((m) => m.name)
.forEach(addBuiltinProperty);

View File

@ -1,4 +0,0 @@
'use strict'
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
module.exports = getRemoteForUsage('screen').screen

View File

@ -1,67 +1,81 @@
'use strict'
const { EventEmitter } = require('events')
const binding = process.atomBinding('web_frame')
class WebFrame extends EventEmitter {
constructor (context) {
super()
this.context = context
// Lots of webview would subscribe to webFrame's events.
this.setMaxListeners(0)
}
findFrameByRoutingId (...args) {
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args))
}
getFrameForSelector (...args) {
return getWebFrame(binding._getFrameForSelector(this.context, ...args))
}
findFrameByName (...args) {
return getWebFrame(binding._findFrameByName(this.context, ...args))
}
get opener () {
return getWebFrame(binding._getOpener(this.context))
}
get parent () {
return getWebFrame(binding._getParent(this.context))
}
get top () {
return getWebFrame(binding._getTop(this.context))
}
get firstChild () {
return getWebFrame(binding._getFirstChild(this.context))
}
get nextSibling () {
return getWebFrame(binding._getNextSibling(this.context))
}
get routingId () {
return binding._getRoutingId(this.context)
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const electron_1 = require("electron");
const binding = process.electronBinding('web_frame');
class WebFrame extends events_1.EventEmitter {
constructor(context) {
super();
this.context = context;
// Lots of webview would subscribe to webFrame's events.
this.setMaxListeners(0);
}
findFrameByRoutingId(...args) {
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args));
}
getFrameForSelector(...args) {
return getWebFrame(binding._getFrameForSelector(this.context, ...args));
}
findFrameByName(...args) {
return getWebFrame(binding._findFrameByName(this.context, ...args));
}
get opener() {
return getWebFrame(binding._getOpener(this.context));
}
get parent() {
return getWebFrame(binding._getParent(this.context));
}
get top() {
return getWebFrame(binding._getTop(this.context));
}
get firstChild() {
return getWebFrame(binding._getFirstChild(this.context));
}
get nextSibling() {
return getWebFrame(binding._getNextSibling(this.context));
}
get routingId() {
return binding._getRoutingId(this.context);
}
// Deprecations
// TODO(nitsakh): Remove in 6.0
setIsolatedWorldSecurityOrigin(worldId, securityOrigin) {
electron_1.deprecate.warn('webFrame.setIsolatedWorldSecurityOrigin', 'webFrame.setIsolatedWorldInfo');
binding.setIsolatedWorldInfo(this.context, worldId, { securityOrigin });
}
setIsolatedWorldContentSecurityPolicy(worldId, csp) {
electron_1.deprecate.warn('webFrame.setIsolatedWorldContentSecurityPolicy', 'webFrame.setIsolatedWorldInfo');
binding.setIsolatedWorldInfo(this.context, worldId, {
securityOrigin: window.location.origin,
csp
});
}
setIsolatedWorldHumanReadableName(worldId, name) {
electron_1.deprecate.warn('webFrame.setIsolatedWorldHumanReadableName', 'webFrame.setIsolatedWorldInfo');
binding.setIsolatedWorldInfo(this.context, worldId, { name });
}
}
// Populate the methods.
for (const name in binding) {
if (!name.startsWith('_')) { // some methods are manully populated above
WebFrame.prototype[name] = function (...args) {
return binding[name](this.context, ...args)
if (!name.startsWith('_')) { // some methods are manually populated above
// TODO(felixrieseberg): Once we can type web_frame natives, we could
// use a neat `keyof` here
WebFrame.prototype[name] = function (...args) {
return binding[name](this.context, ...args);
};
}
}
}
// Helper to return WebFrame or null depending on context.
// TODO(zcbenz): Consider returning same WebFrame for the same context.
function getWebFrame (context) {
return context ? new WebFrame(context) : null
// TODO(zcbenz): Consider returning same WebFrame for the same frame.
function getWebFrame(context) {
return context ? new WebFrame(context) : null;
}
module.exports = new WebFrame(window)
const promisifiedMethods = new Set([
'executeJavaScript',
'executeJavaScriptInIsolatedWorld'
]);
for (const method of promisifiedMethods) {
WebFrame.prototype[method] = electron_1.deprecate.promisify(WebFrame.prototype[method]);
}
const _webFrame = new WebFrame(window);
exports.default = _webFrame;

View File

@ -1,59 +1,51 @@
'use strict'
const v8Util = process.atomBinding('v8_util')
'use strict';
const v8Util = process.electronBinding('v8_util');
class CallbacksRegistry {
constructor () {
this.nextId = 0
this.callbacks = {}
}
add (callback) {
// The callback is already added.
let id = v8Util.getHiddenValue(callback, 'callbackId')
if (id != null) return id
id = this.nextId += 1
// Capture the location of the function and put it in the ID string,
// so that release errors can be tracked down easily.
const regexp = /at (.*)/gi
const stackString = (new Error()).stack
let filenameAndLine
let match
while ((match = regexp.exec(stackString)) !== null) {
const location = match[1]
if (location.includes('(native)')) continue
if (location.includes('(<anonymous>)')) continue
if (location.includes('electron.asar')) continue
const ref = /([^/^)]*)\)?$/gi.exec(location)
filenameAndLine = ref[1]
break
constructor() {
this.nextId = 0;
this.callbacks = {};
}
this.callbacks[id] = callback
v8Util.setHiddenValue(callback, 'callbackId', id)
v8Util.setHiddenValue(callback, 'location', filenameAndLine)
return id
}
get (id) {
return this.callbacks[id] || function () {}
}
apply (id, ...args) {
return this.get(id).apply(global, ...args)
}
remove (id) {
const callback = this.callbacks[id]
if (callback) {
v8Util.deleteHiddenValue(callback, 'callbackId')
delete this.callbacks[id]
add(callback) {
// The callback is already added.
let id = v8Util.getHiddenValue(callback, 'callbackId');
if (id != null)
return id;
id = this.nextId += 1;
// Capture the location of the function and put it in the ID string,
// so that release errors can be tracked down easily.
const regexp = /at (.*)/gi;
const stackString = (new Error()).stack;
let filenameAndLine;
let match;
while ((match = regexp.exec(stackString)) !== null) {
const location = match[1];
if (location.includes('(native)'))
continue;
if (location.includes('(<anonymous>)'))
continue;
if (location.includes('electron.asar'))
continue;
const ref = /([^/^)]*)\)?$/gi.exec(location);
filenameAndLine = ref[1];
break;
}
this.callbacks[id] = callback;
v8Util.setHiddenValue(callback, 'callbackId', id);
v8Util.setHiddenValue(callback, 'location', filenameAndLine);
return id;
}
get(id) {
return this.callbacks[id] || function () { };
}
apply(id, ...args) {
return this.get(id).apply(global, ...args);
}
remove(id) {
const callback = this.callbacks[id];
if (callback) {
v8Util.deleteHiddenValue(callback, 'callbackId');
delete this.callbacks[id];
}
}
}
}
module.exports = CallbacksRegistry
module.exports = CallbacksRegistry;

View File

@ -1,190 +1,173 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const Event = require('@electron/internal/renderer/extensions/event')
const url = require('url')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal");
const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils");
const url = require("url");
// Todo: Import once extensions have been turned into TypeScript
const Event = require('@electron/internal/renderer/extensions/event');
class Tab {
constructor (tabId) {
this.id = tabId
}
constructor(tabId) {
this.id = tabId;
}
}
class MessageSender {
constructor (tabId, extensionId) {
this.tab = tabId ? new Tab(tabId) : null
this.id = extensionId
this.url = `chrome-extension://${extensionId}`
}
constructor(tabId, extensionId) {
this.tab = tabId ? new Tab(tabId) : null;
this.id = extensionId;
this.url = `chrome-extension://${extensionId}`;
}
}
class Port {
constructor (tabId, portId, extensionId, name) {
this.tabId = tabId
this.portId = portId
this.disconnected = false
this.name = name
this.onDisconnect = new Event()
this.onMessage = new Event()
this.sender = new MessageSender(tabId, extensionId)
ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
this._onDisconnect()
})
ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => {
const sendResponse = function () { console.error('sendResponse is not implemented') }
this.onMessage.emit(message, this.sender, sendResponse)
})
}
disconnect () {
if (this.disconnected) return
ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
this._onDisconnect()
}
postMessage (message) {
ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message)
}
_onDisconnect () {
this.disconnected = true
ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
this.onDisconnect.emit()
}
constructor(tabId, portId, extensionId, name) {
this.tabId = tabId;
this.portId = portId;
this.name = name;
this.disconnected = false;
this.onDisconnect = new Event();
this.onMessage = new Event();
this.onDisconnect = new Event();
this.onMessage = new Event();
this.sender = new MessageSender(tabId, extensionId);
ipc_renderer_internal_1.ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
this._onDisconnect();
});
ipc_renderer_internal_1.ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (_event, message) => {
const sendResponse = function () { console.error('sendResponse is not implemented'); };
this.onMessage.emit(JSON.parse(message), this.sender, sendResponse);
});
}
disconnect() {
if (this.disconnected)
return;
ipc_renderer_internal_1.ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`);
this._onDisconnect();
}
postMessage(message) {
ipc_renderer_internal_1.ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message));
}
_onDisconnect() {
this.disconnected = true;
ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`);
this.onDisconnect.emit();
}
}
// Inject chrome API to the |context|
exports.injectTo = function (extensionId, isBackgroundPage, context) {
const chrome = context.chrome = context.chrome || {}
let originResultID = 1
ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => {
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
})
ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message, resultID) => {
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), (messageResult) => {
ipcRenderer.send(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, messageResult)
})
})
ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => {
chrome.tabs.onCreated.emit(new Tab(tabId))
})
ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => {
chrome.tabs.onRemoved.emit(tabId)
})
chrome.runtime = {
id: extensionId,
getURL: function (path) {
return url.format({
protocol: 'chrome-extension',
slashes: true,
hostname: extensionId,
pathname: path
})
},
connect (...args) {
if (isBackgroundPage) {
console.error('chrome.runtime.connect is not supported in background page')
return
}
// Parse the optional args.
let targetExtensionId = extensionId
let connectInfo = { name: '' }
if (args.length === 1) {
connectInfo = args[0]
} else if (args.length === 2) {
[targetExtensionId, connectInfo] = args
}
const { tabId, portId } = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
return new Port(tabId, portId, extensionId, connectInfo.name)
},
sendMessage (...args) {
if (isBackgroundPage) {
console.error('chrome.runtime.sendMessage is not supported in background page')
return
}
// Parse the optional args.
let targetExtensionId = extensionId
let message
if (args.length === 1) {
message = args[0]
} else if (args.length === 2) {
// A case of not provide extension-id: (message, responseCallback)
if (typeof args[1] === 'function') {
ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[1](result))
message = args[0]
} else {
[targetExtensionId, message] = args
}
} else {
console.error('options is not supported')
ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[2](result))
}
ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message, originResultID)
originResultID++
},
onConnect: new Event(),
onMessage: new Event(),
onInstalled: new Event()
}
chrome.tabs = {
executeScript (tabId, details, resultCallback) {
if (resultCallback) {
ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${originResultID}`, (event, result) => resultCallback([result]))
}
ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', originResultID, tabId, extensionId, details)
originResultID++
},
sendMessage (tabId, message, options, responseCallback) {
if (responseCallback) {
ipcRenderer.on(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, (event, result) => responseCallback(result))
}
ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message, originResultID)
originResultID++
},
onUpdated: new Event(),
onCreated: new Event(),
onRemoved: new Event()
}
chrome.extension = {
getURL: chrome.runtime.getURL,
connect: chrome.runtime.connect,
onConnect: chrome.runtime.onConnect,
sendMessage: chrome.runtime.sendMessage,
onMessage: chrome.runtime.onMessage
}
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId)
chrome.pageAction = {
show () {},
hide () {},
setTitle () {},
getTitle () {},
setIcon () {},
setPopup () {},
getPopup () {}
}
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId)
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup()
function injectTo(extensionId, context) {
const chrome = context.chrome = context.chrome || {};
ipc_renderer_internal_1.ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (_event, tabId, portId, connectInfo) => {
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name));
});
ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (_event, tabId, message) => {
return new Promise(resolve => {
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve);
});
});
ipc_renderer_internal_1.ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event, tabId) => {
chrome.tabs.onCreated.emit(new Tab(tabId));
});
ipc_renderer_internal_1.ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event, tabId) => {
chrome.tabs.onRemoved.emit(tabId);
});
chrome.runtime = {
id: extensionId,
// https://developer.chrome.com/extensions/runtime#method-getURL
getURL: function (path) {
return url.format({
protocol: 'chrome-extension',
slashes: true,
hostname: extensionId,
pathname: path
});
},
// https://developer.chrome.com/extensions/runtime#method-getManifest
getManifest: function () {
const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId);
return manifest;
},
// https://developer.chrome.com/extensions/runtime#method-connect
connect(...args) {
// Parse the optional args.
let targetExtensionId = extensionId;
let connectInfo = { name: '' };
if (args.length === 1) {
connectInfo = args[0];
}
else if (args.length === 2) {
[targetExtensionId, connectInfo] = args;
}
const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo);
return new Port(tabId, portId, extensionId, connectInfo.name);
},
// https://developer.chrome.com/extensions/runtime#method-sendMessage
sendMessage(...args) {
// Parse the optional args.
const targetExtensionId = extensionId;
let message;
let options;
let responseCallback = () => { };
if (typeof args[args.length - 1] === 'function') {
responseCallback = args.pop();
}
if (args.length === 1) {
[message] = args;
}
else if (args.length === 2) {
if (typeof args[0] === 'string') {
[extensionId, message] = args;
}
else {
[message, options] = args;
}
}
else {
[extensionId, message, options] = args;
}
if (options) {
console.error('options are not supported');
}
ipcRendererUtils.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback);
},
onConnect: new Event(),
onMessage: new Event(),
onInstalled: new Event()
};
chrome.tabs = {
// https://developer.chrome.com/extensions/tabs#method-executeScript
executeScript(tabId, details, resultCallback = () => { }) {
ipcRendererUtils.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details)
.then((result) => resultCallback([result]));
},
// https://developer.chrome.com/extensions/tabs#method-sendMessage
sendMessage(tabId, message, _options, responseCallback = () => { }) {
ipcRendererUtils.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback);
},
onUpdated: new Event(),
onCreated: new Event(),
onRemoved: new Event()
};
chrome.extension = {
getURL: chrome.runtime.getURL,
connect: chrome.runtime.connect,
onConnect: chrome.runtime.onConnect,
sendMessage: chrome.runtime.sendMessage,
onMessage: chrome.runtime.onMessage
};
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId);
chrome.pageAction = {
show() { },
hide() { },
setTitle() { },
getTitle() { },
setIcon() { },
setPopup() { },
getPopup() { }
};
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId);
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup();
// Electron has no concept of a browserAction but we should stub these APIs for compatibility
chrome.browserAction = {
setIcon() { },
setPopup() { }
};
}
exports.injectTo = injectTo;

View File

@ -1,101 +1,108 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const { runInThisContext } = require('vm')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils");
const v8Util = process.electronBinding('v8_util');
const IsolatedWorldIDs = {
/**
* Start of extension isolated world IDs, as defined in
* atom_render_frame_observer.h
*/
ISOLATED_WORLD_EXTENSIONS: 1 << 20
};
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS;
const extensionWorldId = {};
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
const getIsolatedWorldIdForInstance = () => {
// TODO(samuelmaddock): allocate and cleanup IDs
return isolatedWorldIds++;
};
const escapePattern = function (pattern) {
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
};
// Check whether pattern matches.
// https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern) {
if (pattern === '<all_urls>') return true
const regexp = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)
const url = `${location.protocol}//${location.host}${location.pathname}`
return url.match(regexp)
}
if (pattern === '<all_urls>')
return true;
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
const url = `${location.protocol}//${location.host}${location.pathname}`;
return url.match(regexp);
};
// Run the code with chrome API integrated.
const runContentScript = function (extensionId, url, code) {
const context = {}
require('@electron/internal/renderer/chrome-api').injectTo(extensionId, false, context)
const wrapper = `((chrome) => {\n ${code}\n })`
const compiledWrapper = runInThisContext(wrapper, {
filename: url,
lineOffset: 1,
displayErrors: true
})
return compiledWrapper.call(this, context.chrome)
}
// Assign unique world ID to each extension
const worldId = extensionWorldId[extensionId] ||
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance());
// store extension ID for content script to read in isolated world
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId);
electron_1.webFrame.setIsolatedWorldInfo(worldId, {
name: `${extensionId} [${worldId}]`
// TODO(samuelmaddock): read `content_security_policy` from extension manifest
// csp: manifest.content_security_policy,
});
const sources = [{ code, url }];
return electron_1.webFrame.executeJavaScriptInIsolatedWorld(worldId, sources);
};
const runAllContentScript = function (scripts, extensionId) {
for (const { url, code } of scripts) {
runContentScript.call(window, extensionId, url, code)
}
}
const runStylesheet = function (url, code) {
const wrapper = `((code) => {
function init() {
const styleElement = document.createElement('style');
styleElement.textContent = code;
document.head.append(styleElement);
for (const { url, code } of scripts) {
runContentScript.call(window, extensionId, url, code);
}
document.addEventListener('DOMContentLoaded', init);
})`
const compiledWrapper = runInThisContext(wrapper, {
filename: url,
lineOffset: 1,
displayErrors: true
})
return compiledWrapper.call(this, code)
}
};
const runStylesheet = function (url, code) {
electron_1.webFrame.insertCSS(code);
};
const runAllStylesheet = function (css) {
for (const { url, code } of css) {
runStylesheet.call(window, url, code)
}
}
for (const { url, code } of css) {
runStylesheet.call(window, url, code);
}
};
// Run injected scripts.
// https://developer.chrome.com/extensions/content_scripts
const injectContentScript = function (extensionId, script) {
if (!script.matches.some(matchesPattern)) return
if (script.js) {
const fire = runAllContentScript.bind(window, script.js, extensionId)
if (script.runAt === 'document_start') {
process.once('document-start', fire)
} else if (script.runAt === 'document_end') {
process.once('document-end', fire)
} else {
document.addEventListener('DOMContentLoaded', fire)
if (!process.isMainFrame && !script.allFrames)
return;
if (!script.matches.some(matchesPattern))
return;
if (script.js) {
const fire = runAllContentScript.bind(window, script.js, extensionId);
if (script.runAt === 'document_start') {
process.once('document-start', fire);
}
else if (script.runAt === 'document_end') {
process.once('document-end', fire);
}
else {
document.addEventListener('DOMContentLoaded', fire);
}
}
}
if (script.css) {
const fire = runAllStylesheet.bind(window, script.css)
if (script.runAt === 'document_start') {
process.once('document-start', fire)
} else if (script.runAt === 'document_end') {
process.once('document-end', fire)
} else {
document.addEventListener('DOMContentLoaded', fire)
if (script.css) {
const fire = runAllStylesheet.bind(window, script.css);
if (script.runAt === 'document_start') {
process.once('document-start', fire);
}
else if (script.runAt === 'document_end') {
process.once('document-end', fire);
}
else {
document.addEventListener('DOMContentLoaded', fire);
}
}
}
}
};
// Handle the request of chrome.tabs.executeJavaScript.
ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) {
const result = runContentScript.call(window, extensionId, url, code)
ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result)
})
// Read the renderer process preferences.
const preferences = process.getRenderProcessPreferences()
if (preferences) {
for (const pref of preferences) {
if (pref.contentScripts) {
for (const script of pref.contentScripts) {
injectContentScript(pref.extensionId, script)
}
ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (event, extensionId, url, code) {
return runContentScript.call(window, extensionId, url, code);
});
module.exports = (getRenderProcessPreferences) => {
// Read the renderer process preferences.
const preferences = getRenderProcessPreferences();
if (preferences) {
for (const pref of preferences) {
if (pref.contentScripts) {
for (const script of pref.contentScripts) {
injectContentScript(pref.extensionId, script);
}
}
}
}
}
}
};

View File

@ -1,26 +1,21 @@
'use strict'
'use strict';
class Event {
constructor () {
this.listeners = []
}
addListener (callback) {
this.listeners.push(callback)
}
removeListener (callback) {
const index = this.listeners.indexOf(callback)
if (index !== -1) {
this.listeners.splice(index, 1)
constructor() {
this.listeners = [];
}
}
emit (...args) {
for (const listener of this.listeners) {
listener(...args)
addListener(callback) {
this.listeners.push(callback);
}
removeListener(callback) {
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
}
emit(...args) {
for (const listener of this.listeners) {
listener(...args);
}
}
}
}
module.exports = Event
module.exports = Event;

View File

@ -1,88 +1,52 @@
'use strict'
'use strict';
// Implementation of chrome.i18n.getMessage
// https://developer.chrome.com/extensions/i18n#method-getMessage
//
// Does not implement predefined messages:
// https://developer.chrome.com/extensions/i18n#overview-predefined
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const fs = require('fs')
const path = require('path')
let metadata
const getExtensionMetadata = (extensionId) => {
if (!metadata) {
metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId)
}
return metadata
}
const getMessagesPath = (extensionId, language) => {
const metadata = getExtensionMetadata(extensionId)
const localesDirectory = path.join(metadata.srcDirectory, '_locales')
try {
const filename = path.join(localesDirectory, language, 'messages.json')
fs.accessSync(filename, fs.constants.R_OK)
return filename
} catch (err) {
const defaultLocale = metadata.default_locale || 'en'
return path.join(localesDirectory, defaultLocale, 'messages.json')
}
}
const getMessages = (extensionId, language) => {
try {
const messagesPath = getMessagesPath(extensionId, language)
return JSON.parse(fs.readFileSync(messagesPath)) || {}
} catch (error) {
return {}
}
}
const getLanguage = () => {
return navigator.language.replace(/-.*$/, '').toLowerCase()
}
const replaceNumberedSubstitutions = (message, substitutions) => {
return message.replace(/\$(\d+)/, (_, number) => {
const index = parseInt(number, 10) - 1
return substitutions[index] || ''
})
}
const replacePlaceholders = (message, placeholders, substitutions) => {
if (typeof substitutions === 'string') {
substitutions = [substitutions]
}
if (!Array.isArray(substitutions)) {
substitutions = []
}
if (placeholders) {
Object.keys(placeholders).forEach((name) => {
let { content } = placeholders[name]
content = replaceNumberedSubstitutions(content, substitutions)
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content)
})
}
return replaceNumberedSubstitutions(message, substitutions)
}
const getMessage = (extensionId, messageName, substitutions) => {
const messages = getMessages(extensionId, getLanguage())
if (messages.hasOwnProperty(messageName)) {
const { message, placeholders } = messages[messageName]
return replacePlaceholders(message, placeholders, substitutions)
}
}
exports.setup = (extensionId) => {
return {
getMessage (messageName, substitutions) {
return getMessage(extensionId, messageName, substitutions)
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const getMessages = (extensionId) => {
try {
const data = ipcRendererUtils.invokeSync('CHROME_GET_MESSAGES', extensionId);
return JSON.parse(data) || {};
}
}
}
catch (_a) {
return {};
}
};
const replaceNumberedSubstitutions = (message, substitutions) => {
return message.replace(/\$(\d+)/, (_, number) => {
const index = parseInt(number, 10) - 1;
return substitutions[index] || '';
});
};
const replacePlaceholders = (message, placeholders, substitutions) => {
if (typeof substitutions === 'string') {
substitutions = [substitutions];
}
if (!Array.isArray(substitutions)) {
substitutions = [];
}
if (placeholders) {
Object.keys(placeholders).forEach((name) => {
let { content } = placeholders[name];
content = replaceNumberedSubstitutions(content, substitutions);
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content);
});
}
return replaceNumberedSubstitutions(message, substitutions);
};
const getMessage = (extensionId, messageName, substitutions) => {
const messages = getMessages(extensionId);
if (messages.hasOwnProperty(messageName)) {
const { message, placeholders } = messages[messageName];
return replacePlaceholders(message, placeholders, substitutions);
}
};
exports.setup = (extensionId) => {
return {
getMessage(messageName, substitutions) {
return getMessage(extensionId, messageName, substitutions);
}
};
};

View File

@ -1,138 +1,87 @@
'use strict'
const fs = require('fs')
const path = require('path')
const { remote } = require('electron')
const { app } = remote
const getChromeStoragePath = (storageType, extensionId) => {
return path.join(
app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`)
}
const mkdirp = (dir, callback) => {
fs.mkdir(dir, (error) => {
if (error && error.code === 'ENOENT') {
mkdirp(path.dirname(dir), (error) => {
if (!error) {
mkdirp(dir, callback)
'use strict';
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const getStorage = (storageType, extensionId, callback) => {
if (typeof callback !== 'function')
throw new TypeError('No callback provided');
ipcRendererUtils.invoke('CHROME_STORAGE_READ', storageType, extensionId)
.then(data => {
if (data !== null) {
callback(JSON.parse(data));
}
})
} else if (error && error.code === 'EEXIST') {
callback(null)
} else {
callback(error)
}
})
}
const readChromeStorageFile = (storageType, extensionId, cb) => {
const filePath = getChromeStoragePath(storageType, extensionId)
fs.readFile(filePath, 'utf8', (err, data) => {
if (err && err.code === 'ENOENT') {
return cb(null, null)
}
cb(err, data)
})
}
const writeChromeStorageFile = (storageType, extensionId, data, cb) => {
const filePath = getChromeStoragePath(storageType, extensionId)
mkdirp(path.dirname(filePath), err => {
if (err) { /* we just ignore the errors of mkdir or mkdirp */ }
fs.writeFile(filePath, data, cb)
})
}
const getStorage = (storageType, extensionId, cb) => {
readChromeStorageFile(storageType, extensionId, (err, data) => {
if (err) throw err
if (!cb) throw new TypeError('No callback provided')
if (data !== null) {
cb(JSON.parse(data))
} else {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
cb({})
}
})
}
const setStorage = (storageType, extensionId, storage, cb) => {
const json = JSON.stringify(storage)
writeChromeStorageFile(storageType, extensionId, json, err => {
if (err) throw err
if (cb) cb()
})
}
else {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
callback({});
}
});
};
const setStorage = (storageType, extensionId, storage, callback) => {
const json = JSON.stringify(storage);
ipcRendererUtils.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json)
.then(() => {
if (callback)
callback();
});
};
const getStorageManager = (storageType, extensionId) => {
return {
get (keys, callback) {
getStorage(storageType, extensionId, storage => {
if (keys == null) return callback(storage)
let defaults = {}
switch (typeof keys) {
case 'string':
keys = [keys]
break
case 'object':
if (!Array.isArray(keys)) {
defaults = keys
keys = Object.keys(keys)
}
break
return {
get(keys, callback) {
getStorage(storageType, extensionId, storage => {
if (keys == null)
return callback(storage);
let defaults = {};
switch (typeof keys) {
case 'string':
keys = [keys];
break;
case 'object':
if (!Array.isArray(keys)) {
defaults = keys;
keys = Object.keys(keys);
}
break;
}
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
if (keys.length === 0)
return callback({});
const items = {};
keys.forEach(function (key) {
let value = storage[key];
if (value == null)
value = defaults[key];
items[key] = value;
});
callback(items);
});
},
set(items, callback) {
getStorage(storageType, extensionId, storage => {
Object.keys(items).forEach(function (name) {
storage[name] = items[name];
});
setStorage(storageType, extensionId, storage, callback);
});
},
remove(keys, callback) {
getStorage(storageType, extensionId, storage => {
if (!Array.isArray(keys)) {
keys = [keys];
}
keys.forEach(function (key) {
delete storage[key];
});
setStorage(storageType, extensionId, storage, callback);
});
},
clear(callback) {
setStorage(storageType, extensionId, {}, callback);
}
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
if (keys.length === 0) return callback({})
const items = {}
keys.forEach(function (key) {
let value = storage[key]
if (value == null) value = defaults[key]
items[key] = value
})
callback(items)
})
},
set (items, callback) {
getStorage(storageType, extensionId, storage => {
Object.keys(items).forEach(function (name) {
storage[name] = items[name]
})
setStorage(storageType, extensionId, storage, callback)
})
},
remove (keys, callback) {
getStorage(storageType, extensionId, storage => {
if (!Array.isArray(keys)) {
keys = [keys]
}
keys.forEach(function (key) {
delete storage[key]
})
setStorage(storageType, extensionId, storage, callback)
})
},
clear (callback) {
setStorage(storageType, extensionId, {}, callback)
}
}
}
};
};
module.exports = {
setup: extensionId => ({
sync: getStorageManager('sync', extensionId),
local: getStorageManager('local', extensionId)
})
}
setup: extensionId => ({
sync: getStorageManager('sync', extensionId),
local: getStorageManager('local', extensionId)
})
};

View File

@ -1,23 +1,18 @@
'use strict'
const Event = require('@electron/internal/renderer/extensions/event')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
'use strict';
const Event = require('@electron/internal/renderer/extensions/event');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
class WebNavigation {
constructor () {
this.onBeforeNavigate = new Event()
this.onCompleted = new Event()
ipcRenderer.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => {
this.onBeforeNavigate.emit(details)
})
ipcRenderer.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => {
this.onCompleted.emit(details)
})
}
constructor() {
this.onBeforeNavigate = new Event();
this.onCompleted = new Event();
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => {
this.onBeforeNavigate.emit(details);
});
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => {
this.onCompleted.emit(details);
});
}
}
exports.setup = () => {
return new WebNavigation()
}
return new WebNavigation();
};

View File

@ -1,176 +1,175 @@
'use strict'
const { EventEmitter } = require('events')
const path = require('path')
const Module = require('module')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const path = require("path");
const Module = require('module');
// Make sure globals like "process" and "global" are always available in preload
// scripts even after they are deleted in "loaded" script.
//
// Note 1: We rely on a Node patch to actually pass "process" and "global" and
// other arguments to the wrapper.
//
// Note 2: Node introduced a new code path to use native code to wrap module
// code, which does not work with this hack. However by modifying the
// "Module.wrapper" we can force Node to use the old code path to wrap module
// code with JavaScript.
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' +
// By running the code in a new closure, it would be possible for the module
// code to override "process" and "Buffer" with local variables.
'return function (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
// init.js, we need to restore it here.
process.argv.splice(1, 1)
process.argv.splice(1, 1);
// Clear search paths.
require('../common/reset-search-paths')
require('../common/reset-search-paths');
// Import common settings.
require('@electron/internal/common/init')
const globalPaths = Module.globalPaths
require('@electron/internal/common/init');
const globalPaths = Module.globalPaths;
// Expose public APIs.
globalPaths.push(path.join(__dirname, 'api', 'exports'))
globalPaths.push(path.join(__dirname, 'api', 'exports'));
// The global variable will be used by ipc for event dispatching
const v8Util = process.atomBinding('v8_util')
v8Util.setHiddenValue(global, 'ipc', new EventEmitter())
v8Util.setHiddenValue(global, 'ipc-internal', new EventEmitter())
const v8Util = process.electronBinding('v8_util');
const ipcEmitter = new events_1.EventEmitter();
const ipcInternalEmitter = new events_1.EventEmitter();
v8Util.setHiddenValue(global, 'ipc', ipcEmitter);
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter);
v8Util.setHiddenValue(global, 'ipcNative', {
onMessage(internal, channel, args, senderId) {
const sender = internal ? ipcInternalEmitter : ipcEmitter;
sender.emit(channel, { sender, senderId }, ...args);
}
});
// Use electron module after everything is ready.
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
require('@electron/internal/renderer/web-frame-init')()
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init');
webFrameInit();
// Process command line arguments.
let nodeIntegration = false
let webviewTag = false
let preloadScript = null
let preloadScripts = []
let isBackgroundPage = false
let appPath = null
for (const arg of process.argv) {
if (arg.indexOf('--guest-instance-id=') === 0) {
// This is a guest web view.
process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
} else if (arg.indexOf('--opener-id=') === 0) {
// This is a guest BrowserWindow.
process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
} else if (arg.indexOf('--node-integration=') === 0) {
nodeIntegration = arg.substr(arg.indexOf('=') + 1) === 'true'
} else if (arg.indexOf('--preload=') === 0) {
preloadScript = arg.substr(arg.indexOf('=') + 1)
} else if (arg === '--background-page') {
isBackgroundPage = true
} else if (arg.indexOf('--app-path=') === 0) {
appPath = arg.substr(arg.indexOf('=') + 1)
} else if (arg.indexOf('--webview-tag=') === 0) {
webviewTag = arg.substr(arg.indexOf('=') + 1) === 'true'
} else if (arg.indexOf('--preload-scripts') === 0) {
preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter)
}
}
const { hasSwitch, getSwitchValue } = process.electronBinding('command_line');
const parseOption = function (name, defaultValue, converter) {
return hasSwitch(name)
? (converter
? converter(getSwitchValue(name))
: getSwitchValue(name))
: defaultValue;
};
const contextIsolation = hasSwitch('context-isolation');
const nodeIntegration = hasSwitch('node-integration');
const webviewTag = hasSwitch('webview-tag');
const isHiddenPage = hasSwitch('hidden-page');
const usesNativeWindowOpen = hasSwitch('native-window-open');
const preloadScript = parseOption('preload', null);
const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter));
const appPath = parseOption('app-path', null);
const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value));
const openerId = parseOption('opener-id', null, value => parseInt(value));
// The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen };
// The webContents preload script is loaded after the session preload scripts.
if (preloadScript) {
preloadScripts.push(preloadScript)
preloadScripts.push(preloadScript);
}
if (window.location.protocol === 'chrome-devtools:') {
// Override some inspector APIs.
require('@electron/internal/renderer/inspector')
nodeIntegration = false
} else if (window.location.protocol === 'chrome-extension:') {
// Add implementations of chrome API.
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
nodeIntegration = false
} else if (window.location.protocol === 'chrome:') {
// Disable node integration for chrome UI scheme.
nodeIntegration = false
} else {
// Override default web functions.
require('@electron/internal/renderer/override')
// Inject content scripts.
require('@electron/internal/renderer/content-scripts-injector')
// Load webview tag implementation.
if (webviewTag && process.guestInstanceId == null) {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view')
if (process.argv.includes('--context-isolation')) {
v8Util.setHiddenValue(window, 'setup-webview', setupWebView)
} else {
setupWebView(window)
switch (window.location.protocol) {
case 'devtools:': {
// Override some inspector APIs.
require('@electron/internal/renderer/inspector');
break;
}
case 'chrome-extension:': {
// Inject the chrome.* APIs that chrome extensions require
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window);
break;
}
case 'chrome:':
break;
default: {
// Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup');
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen);
// Inject content scripts.
require('@electron/internal/renderer/content-scripts-injector')(process.getRenderProcessPreferences);
}
}
}
// Load webview tag implementation.
if (process.isMainFrame) {
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init');
webViewInit(contextIsolation, webviewTag, guestInstanceId);
}
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs);
}
if (nodeIntegration) {
// Export node bindings to global.
global.require = require
global.module = module
// Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') {
const location = window.location
let pathname = location.pathname
if (process.platform === 'win32') {
if (pathname[0] === '/') pathname = pathname.substr(1)
const isWindowsNetworkSharePath = location.hostname.length > 0 && globalPaths[0].startsWith('\\')
if (isWindowsNetworkSharePath) {
pathname = `//${location.host}/${pathname}`
}
// Export node bindings to global.
global.require = require;
global.module = module;
// Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') {
const location = window.location;
let pathname = location.pathname;
if (process.platform === 'win32') {
if (pathname[0] === '/')
pathname = pathname.substr(1);
const isWindowsNetworkSharePath = location.hostname.length > 0 && globalPaths[0].startsWith('\\');
if (isWindowsNetworkSharePath) {
pathname = `//${location.host}/${pathname}`;
}
}
global.__filename = path.normalize(decodeURIComponent(pathname));
global.__dirname = path.dirname(global.__filename);
// Set module's filename so relative require can work as expected.
module.filename = global.__filename;
// Also search for module under the html file.
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname));
}
global.__filename = path.normalize(decodeURIComponent(pathname))
global.__dirname = path.dirname(global.__filename)
// Set module's filename so relative require can work as expected.
module.filename = global.__filename
// Also search for module under the html file.
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
} else {
global.__filename = __filename
global.__dirname = __dirname
if (appPath) {
// Search for module under the app directory
module.paths = module.paths.concat(Module._nodeModulePaths(appPath))
else {
global.__filename = __filename;
global.__dirname = __dirname;
if (appPath) {
// Search for module under the app directory
module.paths = module.paths.concat(Module._nodeModulePaths(appPath));
}
}
}
// Redirect window.onerror to uncaughtException.
window.onerror = function (message, filename, lineno, colno, error) {
if (global.process.listeners('uncaughtException').length > 0) {
global.process.emit('uncaughtException', error)
return true
} else {
return false
}
}
} else {
// Delete Node's symbols after the Environment has been loaded.
process.once('loaded', function () {
delete global.process
delete global.Buffer
delete global.setImmediate
delete global.clearImmediate
delete global.global
})
// Redirect window.onerror to uncaughtException.
window.onerror = function (_message, _filename, _lineno, _colno, error) {
if (global.process.listeners('uncaughtException').length > 0) {
// We do not want to add `uncaughtException` to our definitions
// because we don't want anyone else (anywhere) to throw that kind
// of error.
global.process.emit('uncaughtException', error);
return true;
}
else {
return false;
}
};
}
else {
// Delete Node's symbols after the Environment has been loaded.
process.once('loaded', function () {
delete global.process;
delete global.Buffer;
delete global.setImmediate;
delete global.clearImmediate;
delete global.global;
});
}
const errorUtils = require('@electron/internal/common/error-utils');
// Load the preload scripts.
for (const preloadScript of preloadScripts) {
try {
require(preloadScript)
} catch (error) {
console.error('Unable to load preload script: ' + preloadScript)
console.error(error.stack || error.message)
}
try {
require(preloadScript);
}
catch (error) {
console.error(`Unable to load preload script: ${preloadScript}`);
console.error(`${error}`);
ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, errorUtils.serialize(error));
}
}
// Warn about security issues
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
// Report focus/blur events of webview to browser.
// Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview.
if (process.guestInstanceId) {
window.addEventListener('focus', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, process.guestInstanceId)
})
window.addEventListener('blur', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, process.guestInstanceId)
})
if (process.isMainFrame) {
const { securityWarnings } = require('@electron/internal/renderer/security-warnings');
securityWarnings(nodeIntegration);
}

View File

@ -1,138 +1,52 @@
'use strict'
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ipc_renderer_internal_utils_1 = require("@electron/internal/renderer/ipc-renderer-internal-utils");
window.onload = function () {
// Use menu API to show context menu.
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
// correct for Chromium returning undefined for filesystem
window.Persistence.FileSystemWorkspaceBinding.completeURL = completeURL
// Use dialog API to override file chooser dialog.
window.UI.createFileSelectorElement = createFileSelectorElement
}
// Use menu API to show context menu.
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
// correct for Chromium returning undefined for filesystem
window.Persistence.FileSystemWorkspaceBinding.completeURL = completeURL;
// Use dialog API to override file chooser dialog.
window.UI.createFileSelectorElement = createFileSelectorElement;
};
// Extra / is needed as a result of MacOS requiring absolute paths
function completeURL (project, path) {
project = 'file:///'
return `${project}${path}`
function completeURL(project, path) {
project = 'file:///';
return `${project}${path}`;
}
// The DOM implementation expects (message?: string) => boolean
window.confirm = function (message, title) {
const { dialog } = require('electron').remote
if (title == null) {
title = ''
}
return !dialog.showMessageBox({
message: message,
title: title,
buttons: ['OK', 'Cancel'],
cancelId: 1
})
}
const convertToMenuTemplate = function (items) {
return items.map(function (item) {
const transformed = item.type === 'subMenu' ? {
type: 'submenu',
label: item.label,
enabled: item.enabled,
submenu: convertToMenuTemplate(item.subItems)
} : item.type === 'separator' ? {
type: 'separator'
} : item.type === 'checkbox' ? {
type: 'checkbox',
label: item.label,
enabled: item.enabled,
checked: item.checked
} : {
type: 'normal',
label: item.label,
enabled: item.enabled
}
if (item.id != null) {
transformed.click = function () {
window.DevToolsAPI.contextMenuItemSelected(item.id)
return window.DevToolsAPI.contextMenuCleared()
}
}
return transformed
})
}
const createMenu = function (x, y, items) {
const { remote } = require('electron')
const { Menu } = remote
let template = convertToMenuTemplate(items)
if (useEditMenuItems(x, y, template)) {
template = getEditMenuItems()
}
const menu = Menu.buildFromTemplate(template)
// The menu is expected to show asynchronously.
setTimeout(function () {
menu.popup({ window: remote.getCurrentWindow() })
})
}
return ipc_renderer_internal_utils_1.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title);
};
const useEditMenuItems = function (x, y, items) {
return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
return element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || element.isContentEditable
})
}
const getEditMenuItems = function () {
return [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteAndMatchStyle'
},
{
role: 'delete'
},
{
role: 'selectAll'
}
]
}
return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
return element.nodeName === 'INPUT' ||
element.nodeName === 'TEXTAREA' ||
element.isContentEditable;
});
};
const createMenu = function (x, y, items) {
const isEditMenu = useEditMenuItems(x, y, items);
ipc_renderer_internal_utils_1.invoke('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => {
if (typeof id === 'number') {
window.DevToolsAPI.contextMenuItemSelected(id);
}
window.DevToolsAPI.contextMenuCleared();
});
};
const showFileChooserDialog = function (callback) {
const { dialog } = require('electron').remote
const files = dialog.showOpenDialog({})
if (files != null) {
callback(pathToHtml5FileObject(files[0]))
}
}
const pathToHtml5FileObject = function (path) {
const fs = require('fs')
const blob = new Blob([fs.readFileSync(path)])
blob.name = path
return blob
}
ipc_renderer_internal_utils_1.invoke('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => {
if (path && data) {
callback(dataToHtml5FileObject(path, data));
}
});
};
const dataToHtml5FileObject = function (path, data) {
return new File([data], path);
};
const createFileSelectorElement = function (callback) {
const fileSelectorElement = document.createElement('span')
fileSelectorElement.style.display = 'none'
fileSelectorElement.click = showFileChooserDialog.bind(this, callback)
return fileSelectorElement
}
const fileSelectorElement = document.createElement('span');
fileSelectorElement.style.display = 'none';
fileSelectorElement.click = showFileChooserDialog.bind(this, callback);
return fileSelectorElement;
};

View File

@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal");
const errorUtils = require("@electron/internal/common/error-utils");
exports.handle = function (channel, handler) {
ipc_renderer_internal_1.ipcRendererInternal.on(channel, (event, requestId, ...args) => {
new Promise(resolve => resolve(handler(event, ...args))).then(result => {
return [null, result];
}, error => {
return [errorUtils.serialize(error)];
}).then(responseArgs => {
event.sender.send(`${channel}_RESPONSE_${requestId}`, ...responseArgs);
});
});
};
let nextId = 0;
function invoke(command, ...args) {
return new Promise((resolve, reject) => {
const requestId = ++nextId;
ipc_renderer_internal_1.ipcRendererInternal.once(`${command}_RESPONSE_${requestId}`, (_event, error, result) => {
if (error) {
reject(errorUtils.deserialize(error));
}
else {
resolve(result);
}
});
ipc_renderer_internal_1.ipcRendererInternal.send(command, requestId, ...args);
});
}
exports.invoke = invoke;
function invokeSync(command, ...args) {
const [error, result] = ipc_renderer_internal_1.ipcRendererInternal.sendSync(command, null, ...args);
if (error) {
throw errorUtils.deserialize(error);
}
else {
return result;
}
}
exports.invokeSync = invokeSync;

View File

@ -1,26 +1,19 @@
'use strict'
const binding = process.atomBinding('ipc')
const v8Util = process.atomBinding('v8_util')
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const binding = process.electronBinding('ipc');
const v8Util = process.electronBinding('v8_util');
// Created by init.js.
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc-internal')
const internal = true
ipcRenderer.send = function (...args) {
return binding.send('ipc-internal-message', args)
}
ipcRenderer.sendSync = function (...args) {
return binding.sendSync('ipc-internal-message-sync', args)[0]
}
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
return binding.sendTo(internal, false, webContentsId, channel, args)
}
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
return binding.sendTo(internal, true, webContentsId, channel, args)
}
module.exports = ipcRenderer
exports.ipcRendererInternal = v8Util.getHiddenValue(global, 'ipc-internal');
const internal = true;
exports.ipcRendererInternal.send = function (channel, ...args) {
return binding.ipc.send(internal, channel, args);
};
exports.ipcRendererInternal.sendSync = function (channel, ...args) {
return binding.ipc.sendSync(internal, channel, args)[0];
};
exports.ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {
return binding.ipc.sendTo(internal, false, webContentsId, channel, args);
};
exports.ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {
return binding.ipc.sendTo(internal, true, webContentsId, channel, args);
};

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