Changes of Linux stable v0.0.10

This commit is contained in:
root 2020-02-25 19:24:06 +01:00
parent 9dd45e7cc0
commit bf5b7c9222
106 changed files with 604 additions and 35182 deletions

View File

@ -31,7 +31,8 @@ const exePath = _electron.app.getPath('exe');
const exeDir = _path2.default.dirname(exePath);
const iconPath = _path2.default.join(exeDir, 'discord.png');
const autostartDir = _path2.default.join(_electron.app.getPath('appData'), 'autostart');
const autostartFileName = _path2.default.join(autostartDir, _electron.app.getName() + '-' + _buildInfo2.default.releaseChannel + '.desktop');
const electronAppName = _electron.app.name ? _electron.app.name : _electron.app.getName();
const autostartFileName = _path2.default.join(autostartDir, electronAppName + '-' + _buildInfo2.default.releaseChannel + '.desktop');
const desktopFile = `[Desktop Entry]
Type=Application
Exec=${exePath}

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

@ -4,22 +4,24 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
var _events = require('events');
var _squirrelUpdate = require('./squirrelUpdate');
var squirrelUpdate = _interopRequireWildcard(_squirrelUpdate);
var _electron = require('electron');
var _events = require('events');
var _request = require('./request');
var _request2 = _interopRequireDefault(_request);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _squirrelUpdate = require('./squirrelUpdate');
var squirrelUpdate = _interopRequireWildcard(_squirrelUpdate);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function versionParse(verString) {
return verString.split('.').map(i => parseInt(i));
}
@ -127,43 +129,50 @@ class AutoUpdaterLinux extends _events.EventEmitter {
this.updateUrl = url;
}
quitAndInstall() {
// Just restart. The splash screen will hit the update manually state and
// prompt the user to download the new package.
_electron.app.relaunch();
_electron.app.quit();
}
checkForUpdates() {
const currVersion = versionParse(_electron.app.getVersion());
this.emit('checking-for-update');
var _this = this;
_request2.default.get({ url: this.updateUrl, encoding: null }, (error, response, body) => {
if (error) {
console.error('[Updates] Error fetching ' + this.updateUrl + ': ' + error);
this.emit('error', error);
return;
}
return _asyncToGenerator(function* () {
const currVersion = versionParse(_electron.app.getVersion());
_this.emit('checking-for-update');
try {
const response = yield _request2.default.get(_this.updateUrl);
if (response.statusCode === 204) {
// you are up to date
_this.emit('update-not-available');
return;
}
if (response.statusCode === 204) {
// you are up to date
this.emit('update-not-available');
} else if (response.statusCode === 200) {
let latestVerStr = '';
let latestVersion = [];
try {
const latestMetadata = JSON.parse(body);
const latestMetadata = JSON.parse(response.body);
latestVerStr = latestMetadata.name;
latestVersion = versionParse(latestVerStr);
} catch (e) {}
} catch (_) {}
if (versionNewer(latestVersion, currVersion)) {
console.log('[Updates] You are out of date!');
// you need to update
this.emit('update-manually', latestVerStr);
_this.emit('update-manually', latestVerStr);
} else {
console.log('[Updates] You are living in the future!');
this.emit('update-not-available');
_this.emit('update-not-available');
}
} else {
// something is wrong
console.error(`[Updates] Error: fetch returned: ${response.statusCode}`);
this.emit('update-not-available');
} catch (err) {
console.error('[Updates] Error fetching ' + _this.updateUrl + ': ' + err.message);
_this.emit('error', err);
}
});
})();
}
}
@ -186,4 +195,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

@ -6,6 +6,84 @@ Object.defineProperty(exports, "__esModule", {
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
let electronRequest = (() => {
var _ref = _asyncToGenerator(function* ({ method, url, headers, qs, timeout, body, stream }) {
yield _electron.app.whenReady();
const { net, session } = require('electron');
const req = net.request({
method,
url: `${url}${qs != null ? `?${_querystring2.default.stringify(qs)}` : ''}`,
redirect: 'follow',
session: session.defaultSession
});
if (headers != null) {
for (const headerKey of Object.keys(headers)) {
req.setHeader(headerKey, headers[headerKey]);
}
}
if (body != null) {
req.write(body, 'utf-8');
}
return new Promise(function (resolve, reject) {
const reqTimeout = setTimeout(function () {
req.abort();
reject(new Error(`network timeout: ${url}`));
}, timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT);
req.on('login', function (authInfo, callback) {
return callback();
});
req.on('response', function (response) {
clearTimeout(reqTimeout);
handleHTTPResponse(resolve, reject, response, stream);
});
req.on('error', function (err) {
clearTimeout(reqTimeout);
reject(err);
});
req.end();
});
});
return function electronRequest(_x) {
return _ref.apply(this, arguments);
};
})();
let requestWithMethod = (() => {
var _ref2 = _asyncToGenerator(function* (method, options) {
if (typeof options === 'string') {
options = { url: options };
}
options = _extends({}, options, { method });
try {
return yield electronRequest(options);
} catch (err) {
console.log(`Error downloading with electron net: ${err.message}`);
console.log('Falling back to node net library..');
}
return nodeRequest(options);
});
return function requestWithMethod(_x2, _x3) {
return _ref2.apply(this, arguments);
};
})();
// only supports get for now, since retrying is non-idempotent and
// we'd want to grovel the errors to make sure it's safe to retry
var _electron = require('electron');
var _querystring = require('querystring');
@ -16,101 +94,89 @@ var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _events = require('events');
var _events2 = _interopRequireDefault(_events);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _log(_msg) {
// console.log('[Request] ' + _msg);
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const DEFAULT_REQUEST_TIMEOUT = 30000;
function makeHTTPResponse({ method, url, headers, statusCode, statusMessage }, body) {
return {
method,
url,
headers,
statusCode,
statusMessage,
body
};
}
function requestWithMethod(method, origOpts, origCallback) {
if (typeof origOpts == 'string') {
origOpts = { url: origOpts };
}
const opts = _extends({}, origOpts, { method });
let callback;
if (origCallback || opts.callback) {
const origOptsCallback = opts.callback;
delete opts.callback;
callback = (...args) => {
if (origCallback) {
origCallback.apply(this, args);
}
if (origOptsCallback) {
origOptsCallback.apply(this, args);
}
};
}
const strictOpts = _extends({}, opts, { strictSSL: true });
const laxOpts = _extends({}, opts, { strictSSL: false });
const rv = new _events2.default();
if (callback) {
_log('have callback, so wrapping');
rv.on('response', response => {
const chunks = [];
response.on('data', chunk => chunks.push(chunk));
response.on('end', () => {
callback(null, response, Buffer.concat(chunks));
});
});
rv.on('error', error => callback(error));
}
const requestTypes = [{
factory: function () {
return (0, _request2.default)(strictOpts);
},
method: 'node_request_strict'
}, {
factory: function () {
const qs = _querystring2.default.stringify(strictOpts.qs);
const nr = _electron.net.request(_extends({}, strictOpts, { url: `${strictOpts.url}?${qs}` }));
nr.end();
return nr;
},
method: 'electron_net_request_strict'
}, {
factory: function () {
return (0, _request2.default)(laxOpts);
},
method: 'node_request_lax'
}];
function attempt(index) {
const { factory, method } = requestTypes[index];
_log(`Attempt #${index + 1}: ${method}`);
factory().on('response', response => {
_log(`${method} success! emitting response ${response}`);
rv.emit('response', response);
}).on('error', error => {
if (index + 1 < requestTypes.length) {
_log(`${method} failure, trying next option`);
attempt(index + 1);
} else {
_log(`${method} failure, out of options`);
rv.emit('error', error);
}
});
}
attempt(0);
return rv;
function makeHTTPStatusError(response) {
const err = new Error(`HTTP Error: Status Code ${response.statusCode}`);
err.response = response;
return err;
}
function handleHTTPResponse(resolve, reject, response, stream) {
const totalBytes = parseInt(response.headers['content-length'] || 1, 10);
let receivedBytes = 0;
const chunks = [];
// don't stream response if it's a failure
if (response.statusCode >= 300) {
stream = null;
}
response.on('data', chunk => {
if (stream != null) {
receivedBytes += chunk.length;
stream.write(chunk);
stream.emit('progress', { totalBytes, receivedBytes });
return;
}
chunks.push(chunk);
});
response.on('end', () => {
if (stream != null) {
stream.on('finish', () => resolve(makeHTTPResponse(response, null)));
stream.end();
return;
}
const res = makeHTTPResponse(response, Buffer.concat(chunks));
if (res.statusCode >= 300) {
reject(makeHTTPStatusError(res));
return;
}
resolve(res);
});
}
function nodeRequest({ method, url, headers, qs, timeout, body, stream }) {
return new Promise((resolve, reject) => {
const req = (0, _request2.default)({
method,
url,
qs,
headers,
followAllRedirects: true,
encoding: null,
timeout: timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT,
body
});
req.on('response', response => handleHTTPResponse(resolve, reject, response, stream));
req.on('error', err => reject(err));
});
}
// only supports get for now, since retrying is non-idempotent and
// we'd want to grovel the errors to make sure it's safe to retry
for (const method of ['get']) {
requestWithMethod[method] = requestWithMethod.bind(null, method);
requestWithMethod[method] = requestWithMethod.bind(null, method.toUpperCase());
}
exports.default = requestWithMethod;
module.exports = exports['default'];
module.exports = exports.default;

View File

@ -90,9 +90,9 @@ function tryStart(socketPath, callback, otherAppFound) {
}
function makeSocketPath() {
let name = _electron.app.getName();
let name = _electron.app.name ? _electron.app.name : _electron.app.getName();
if (_buildInfo2.default.releaseChannel !== 'stable') {
name = _electron.app.getName() + _buildInfo2.default.releaseChannel;
name += _buildInfo2.default.releaseChannel;
}
if (process.platform === 'win32') {

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,12 @@ exports.pageReady = pageReady;
var _electron = require('electron');
var _events = require('events');
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
@ -21,23 +27,17 @@ var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _events = require('events');
var _moduleUpdater = require('../common/moduleUpdater');
var moduleUpdater = _interopRequireWildcard(_moduleUpdater);
var _ipcMain = require('./ipcMain');
var _ipcMain2 = _interopRequireDefault(_ipcMain);
var _paths = require('../common/paths');
var paths = _interopRequireWildcard(_paths);
var _fs = require('fs');
var _ipcMain = require('./ipcMain');
var _fs2 = _interopRequireDefault(_fs);
var _ipcMain2 = _interopRequireDefault(_ipcMain);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
@ -86,6 +86,7 @@ let updateAttempt;
let splashState;
let launchedMainWindow;
let quoteCachePath;
let restartRequired = false;
function initSplash(startMinimized = false) {
modulesListeners = {};
@ -98,56 +99,68 @@ function initSplash(startMinimized = false) {
updateSplashState(CHECKING_FOR_UPDATES);
});
addModulesListener(UPDATE_CHECK_FINISHED, (succeeded, updateCount, manualRequired) => {
addModulesListener(UPDATE_CHECK_FINISHED, ({ succeeded, updateCount, manualRequired }) => {
stopUpdateTimeout();
if (!succeeded) {
scheduleUpdateCheck();
updateSplashState(UPDATE_FAILURE);
} else if (updateCount === 0) {
moduleUpdater.setInBackground();
launchMainWindow();
updateSplashState(LAUNCHING);
}
});
addModulesListener(DOWNLOADING_MODULE, (name, current, total) => {
addModulesListener(DOWNLOADING_MODULE, ({ name, current, total }) => {
stopUpdateTimeout();
splashState = { current, total };
updateSplashState(DOWNLOADING_UPDATES);
});
addModulesListener(DOWNLOADING_MODULE_PROGRESS, (name, progress) => {
addModulesListener(DOWNLOADING_MODULE_PROGRESS, ({ name, progress }) => {
splashState.progress = progress;
updateSplashState(DOWNLOADING_UPDATES);
});
addModulesListener(DOWNLOADED_MODULE, (name, current, total, succeeded) => delete splashState.progress);
addModulesListener(DOWNLOADED_MODULE, ({ name, current, total, succeeded }) => {
delete splashState.progress;
if (name === 'host') {
restartRequired = true;
}
});
addModulesListener(DOWNLOADING_MODULES_FINISHED, (succeeded, failed) => {
addModulesListener(DOWNLOADING_MODULES_FINISHED, ({ succeeded, failed }) => {
if (failed > 0) {
scheduleUpdateCheck();
updateSplashState(UPDATE_FAILURE);
} else {
process.nextTick(() => moduleUpdater.quitAndInstallUpdates());
process.nextTick(() => {
if (restartRequired) {
moduleUpdater.quitAndInstallUpdates();
} else {
moduleUpdater.installPendingUpdates();
}
});
}
});
addModulesListener(NO_PENDING_UPDATES, () => moduleUpdater.checkForUpdates());
addModulesListener(INSTALLING_MODULE, (name, current, total) => {
addModulesListener(INSTALLING_MODULE, ({ name, current, total }) => {
splashState = { current, total };
updateSplashState(INSTALLING_UPDATES);
});
addModulesListener(INSTALLED_MODULE, (name, current, total, succeeded) => delete splashState.progress);
addModulesListener(INSTALLED_MODULE, ({ name, current, total, succeeded }) => delete splashState.progress);
addModulesListener(INSTALLING_MODULE_PROGRESS, (name, progress) => {
addModulesListener(INSTALLING_MODULE_PROGRESS, ({ name, progress }) => {
splashState.progress = progress;
updateSplashState(INSTALLING_UPDATES);
});
addModulesListener(INSTALLING_MODULES_FINISHED, (succeeded, failed) => moduleUpdater.checkForUpdates());
addModulesListener(INSTALLING_MODULES_FINISHED, ({ succeeded, failed }) => moduleUpdater.checkForUpdates());
addModulesListener(UPDATE_MANUALLY, newVersion => {
addModulesListener(UPDATE_MANUALLY, ({ newVersion }) => {
splashState.newVersion = newVersion;
updateSplashState(UPDATE_MANUALLY);
});
@ -159,7 +172,6 @@ function initSplash(startMinimized = false) {
}
function destroySplash() {
removeModulesListeners();
stopUpdateTimeout();
if (splashWindow) {
@ -243,7 +255,7 @@ function launchSplashWindow(startMinimized) {
}
_ipcMain2.default.on('SPLASH_SCREEN_READY', () => {
let cachedQuote = chooseCachedQuote();
const cachedQuote = chooseCachedQuote();
if (cachedQuote) {
webContentsSend(splashWindow, 'SPLASH_SCREEN_QUOTE', cachedQuote);
}
@ -265,6 +277,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

@ -3,17 +3,152 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.events = exports.NO_PENDING_UPDATES = exports.INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULE = exports.INSTALLING_MODULES_FINISHED = exports.DOWNLOADED_MODULE = exports.UPDATE_MANUALLY = exports.DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULE = exports.UPDATE_CHECK_FINISHED = exports.INSTALLED_MODULE = exports.CHECKING_FOR_UPDATES = undefined;
exports.supportsEventObjects = exports.events = exports.NO_PENDING_UPDATES = exports.INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULE = exports.INSTALLING_MODULES_FINISHED = exports.DOWNLOADED_MODULE = exports.UPDATE_MANUALLY = exports.DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULE = exports.UPDATE_CHECK_FINISHED = exports.INSTALLED_MODULE = exports.CHECKING_FOR_UPDATES = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // Manages additional module installation and management.
// We add the module folder path to require() lookup paths here.
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
// undocumented node API
let checkForModuleUpdates = (() => {
var _ref = _asyncToGenerator(function* () {
const query = _extends({}, remoteQuery, { _: Math.floor(Date.now() / 1000 / 60 / 5) });
const url = `${remoteBaseURL}/versions.json`;
logger.log(`Checking for module updates at ${url}`);
let response;
try {
response = yield request.get({ url, qs: query, timeout: REQUEST_TIMEOUT });
checkingForUpdates = false;
} catch (err) {
checkingForUpdates = false;
logger.log(`Failed fetching module versions: ${String(err)}`);
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: false,
updateCount: 0,
manualRequired: false
});
return;
}
remoteModuleVersions = JSON.parse(response.body);
if (settings.get(USE_LOCAL_MODULE_VERSIONS)) {
try {
remoteModuleVersions = JSON.parse(_fs2.default.readFileSync(localModuleVersionsFilePath));
console.log('Using local module versions: ', remoteModuleVersions);
} catch (err) {
console.warn('Failed to parse local module versions: ', err);
}
}
const updatesToDownload = [];
for (const moduleName of Object.keys(installedModules)) {
const installedModule = installedModules[moduleName];
const installed = installedModule.installedVersion;
if (installed === null) {
continue;
}
const update = installedModule.updateVersion || 0;
const remote = remoteModuleVersions[getRemoteModuleName(moduleName)] || 0;
if (installed !== remote && update !== remote) {
logger.log(`Module update available: ${moduleName}@${remote} [installed: ${installed}]`);
updatesToDownload.push({ name: moduleName, version: remote });
}
}
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: true,
updateCount: updatesToDownload.length,
manualRequired: false
});
if (updatesToDownload.length === 0) {
logger.log(`No module updates available.`);
} else {
updatesToDownload.forEach(function (e) {
return addModuleToDownloadQueue(e.name, e.version);
});
}
});
return function checkForModuleUpdates() {
return _ref.apply(this, arguments);
};
})();
let processDownloadQueue = (() => {
var _ref2 = _asyncToGenerator(function* () {
if (download.active) return;
if (download.queue.length === 0) return;
download.active = true;
const queuedModule = download.queue[download.next];
download.next += 1;
events.append({
type: DOWNLOADING_MODULE,
name: queuedModule.name,
current: download.next,
total: download.queue.length,
foreground: !runningInBackground
});
let progress = 0;
let receivedBytes = 0;
const url = `${remoteBaseURL}/${encodeURIComponent(getRemoteModuleName(queuedModule.name))}/${encodeURIComponent(queuedModule.version)}`;
logger.log(`Fetching ${queuedModule.name}@${queuedModule.version} from ${url}`);
const headers = {};
if (queuedModule.authToken) {
headers['Authorization'] = queuedModule.authToken;
}
const moduleZipPath = _path2.default.join(moduleDownloadPath, `${queuedModule.name}-${queuedModule.version}.zip`);
const stream = _fs2.default.createWriteStream(moduleZipPath);
stream.on('progress', function ({ receivedBytes: newReceivedBytes, totalBytes }) {
receivedBytes = newReceivedBytes;
const newProgress = Math.min(Math.floor(100 * (receivedBytes / totalBytes)), 100);
if (progress !== newProgress) {
progress = newProgress;
logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}: ${progress}%`);
events.append({
type: DOWNLOADING_MODULE_PROGRESS,
name: queuedModule.name,
progress: progress
});
}
});
logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}`);
try {
const response = yield request.get({
url,
qs: remoteQuery,
headers,
timeout: REQUEST_TIMEOUT,
stream
});
finishModuleDownload(queuedModule.name, queuedModule.version, moduleZipPath, receivedBytes, response.statusCode === 200);
} catch (err) {
logger.log(`Failed fetching module ${queuedModule.name}@${queuedModule.version}: ${String(err)}`);
finishModuleDownload(queuedModule.name, queuedModule.version, null, false);
}
});
return function processDownloadQueue() {
return _ref2.apply(this, arguments);
};
})();
exports.initPathsOnly = initPathsOnly;
exports.init = init;
exports.checkForUpdates = checkForUpdates;
exports.setInBackground = setInBackground;
exports.quitAndInstallUpdates = quitAndInstallUpdates;
exports.isInstalled = isInstalled;
exports.getInstalled = getInstalled;
@ -38,27 +173,71 @@ var _mkdirp = require('mkdirp');
var _mkdirp2 = _interopRequireDefault(_mkdirp);
var _process = require('process');
var _yauzl = require('yauzl');
var _yauzl2 = _interopRequireDefault(_yauzl);
var _paths = require('./paths');
var paths = _interopRequireWildcard(_paths);
var _Backoff = require('./Backoff');
var _Backoff2 = _interopRequireDefault(_Backoff);
var _paths = require('./paths');
var paths = _interopRequireWildcard(_paths);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } // Manages additional module installation and management.
// We add the module folder path to require() lookup paths here.
// undocumented node API
const originalFs = require('original-fs');
// events
const CHECKING_FOR_UPDATES = exports.CHECKING_FOR_UPDATES = 'checking-for-updates';
const INSTALLED_MODULE = exports.INSTALLED_MODULE = 'installed-module';
const UPDATE_CHECK_FINISHED = exports.UPDATE_CHECK_FINISHED = 'update-check-finished';
const DOWNLOADING_MODULE = exports.DOWNLOADING_MODULE = 'downloading-module';
const DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress';
const DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished';
const UPDATE_MANUALLY = exports.UPDATE_MANUALLY = 'update-manually';
const DOWNLOADED_MODULE = exports.DOWNLOADED_MODULE = 'downloaded-module';
const INSTALLING_MODULES_FINISHED = exports.INSTALLING_MODULES_FINISHED = 'installing-modules-finished';
const INSTALLING_MODULE = exports.INSTALLING_MODULE = 'installing-module';
const INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULE_PROGRESS = 'installing-module-progress';
const NO_PENDING_UPDATES = exports.NO_PENDING_UPDATES = 'no-pending-updates';
// settings
const ALWAYS_ALLOW_UPDATES = 'ALWAYS_ALLOW_UPDATES';
const SKIP_HOST_UPDATE = 'SKIP_HOST_UPDATE';
const SKIP_MODULE_UPDATE = 'SKIP_MODULE_UPDATE';
const ALWAYS_BOOTSTRAP_MODULES = 'ALWAYS_BOOTSTRAP_MODULES';
const USE_LOCAL_MODULE_VERSIONS = 'USE_LOCAL_MODULE_VERSIONS';
class Events extends _events.EventEmitter {
emit(...args) {
process.nextTick(() => super.emit.apply(this, args));
constructor() {
super();
this.history = [];
}
append(evt) {
evt.now = String(_process.hrtime.bigint());
if (this._eventIsInteresting(evt)) {
this.history.push(evt);
}
process.nextTick(() => this.emit(evt.type, evt));
}
_eventIsInteresting(evt) {
return evt.type !== DOWNLOADING_MODULE_PROGRESS && evt.type !== INSTALLING_MODULE_PROGRESS;
}
}
@ -88,31 +267,11 @@ class LogStream {
}
}
// events
const CHECKING_FOR_UPDATES = exports.CHECKING_FOR_UPDATES = 'checking-for-updates';
const INSTALLED_MODULE = exports.INSTALLED_MODULE = 'installed-module';
const UPDATE_CHECK_FINISHED = exports.UPDATE_CHECK_FINISHED = 'update-check-finished';
const DOWNLOADING_MODULE = exports.DOWNLOADING_MODULE = 'downloading-module';
const DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress';
const DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished';
const UPDATE_MANUALLY = exports.UPDATE_MANUALLY = 'update-manually';
const DOWNLOADED_MODULE = exports.DOWNLOADED_MODULE = 'downloaded-module';
const INSTALLING_MODULES_FINISHED = exports.INSTALLING_MODULES_FINISHED = 'installing-modules-finished';
const INSTALLING_MODULE = exports.INSTALLING_MODULE = 'installing-module';
const INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULE_PROGRESS = 'installing-module-progress';
const NO_PENDING_UPDATES = exports.NO_PENDING_UPDATES = 'no-pending-updates';
// settings
const ALWAYS_ALLOW_UPDATES = 'ALWAYS_ALLOW_UPDATES';
const SKIP_HOST_UPDATE = 'SKIP_HOST_UPDATE';
const SKIP_MODULE_UPDATE = 'SKIP_MODULE_UPDATE';
const ALWAYS_BOOTSTRAP_MODULES = 'ALWAYS_BOOTSTRAP_MODULES';
const USE_LOCAL_MODULE_VERSIONS = 'USE_LOCAL_MODULE_VERSIONS';
const request = require('../app_bootstrap/request');
const REQUEST_TIMEOUT = 15000;
const backoff = new _Backoff2.default(1000, 20000);
const events = exports.events = new Events();
const supportsEventObjects = exports.supportsEventObjects = true;
let logger;
let locallyInstalledModules;
@ -136,6 +295,7 @@ let newInstallInProgress;
let localModuleVersionsFilePath;
let updatable;
let bootstrapManifestFilePath;
let runningInBackground = false;
function initPathsOnly(_buildInfo) {
if (locallyInstalledModules || moduleInstallPath) {
@ -229,14 +389,16 @@ function init(_endpoint, _settings, _buildInfo) {
hostUpdater = require('../app_bootstrap/hostUpdater');
// TODO: hostUpdater constants
hostUpdater.on('checking-for-update', () => events.emit(CHECKING_FOR_UPDATES));
hostUpdater.on('checking-for-update', () => events.append({
type: CHECKING_FOR_UPDATES
}));
hostUpdater.on('update-available', () => hostOnUpdateAvailable());
hostUpdater.on('update-progress', progress => hostOnUpdateProgress(progress));
hostUpdater.on('update-not-available', () => hostOnUpdateNotAvailable());
hostUpdater.on('update-manually', newVersion => hostOnUpdateManually(newVersion));
hostUpdater.on('update-downloaded', () => hostOnUpdateDownloaded());
hostUpdater.on('error', err => hostOnError(err));
let setFeedURL = hostUpdater.setFeedURL.bind(hostUpdater);
const setFeedURL = hostUpdater.setFeedURL.bind(hostUpdater);
remoteBaseURL = `${endpoint}/modules/${buildInfo.releaseChannel}`;
// eslint-disable-next-line camelcase
@ -286,13 +448,28 @@ function cleanDownloadedModules(installedModules) {
function hostOnUpdateAvailable() {
logger.log(`Host update is available.`);
hostUpdateAvailable = true;
events.emit(UPDATE_CHECK_FINISHED, true, 1, false);
events.emit(DOWNLOADING_MODULE, 'host', 1, 1);
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: true,
updateCount: 1,
manualRequired: false
});
events.append({
type: DOWNLOADING_MODULE,
name: 'host',
current: 1,
total: 1,
foreground: !runningInBackground
});
}
function hostOnUpdateProgress(progress) {
logger.log(`Host update progress: ${progress}%`);
events.emit(DOWNLOADING_MODULE_PROGRESS, 'host', progress);
events.append({
type: DOWNLOADING_MODULE_PROGRESS,
name: 'host',
progress: progress
});
}
function hostOnUpdateNotAvailable() {
@ -300,7 +477,12 @@ function hostOnUpdateNotAvailable() {
if (!skipModuleUpdate) {
checkForModuleUpdates();
} else {
events.emit(UPDATE_CHECK_FINISHED, true, 0, false);
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: true,
updateCount: 0,
manualRequired: false
});
}
}
@ -308,15 +490,33 @@ function hostOnUpdateManually(newVersion) {
logger.log(`Host update is available. Manual update required!`);
hostUpdateAvailable = true;
checkingForUpdates = false;
events.emit(UPDATE_MANUALLY, newVersion);
events.emit(UPDATE_CHECK_FINISHED, true, 1, true);
events.append({
type: UPDATE_MANUALLY,
newVersion: newVersion
});
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: true,
updateCount: 1,
manualRequired: true
});
}
function hostOnUpdateDownloaded() {
logger.log(`Host update downloaded.`);
checkingForUpdates = false;
events.emit(DOWNLOADED_MODULE, 'host', 1, 1, true);
events.emit(DOWNLOADING_MODULES_FINISHED, 1, 0);
events.append({
type: DOWNLOADED_MODULE,
name: 'host',
current: 1,
total: 1,
succeeded: true
});
events.append({
type: DOWNLOADING_MODULES_FINISHED,
succeeded: 1,
failed: 0
});
}
function hostOnError(err) {
@ -331,10 +531,25 @@ function hostOnError(err) {
checkingForUpdates = false;
if (!hostUpdateAvailable) {
events.emit(UPDATE_CHECK_FINISHED, false, 0, false);
events.append({
type: UPDATE_CHECK_FINISHED,
succeeded: false,
updateCount: 0,
manualRequired: false
});
} else {
events.emit(DOWNLOADED_MODULE, 'host', 1, 1, false);
events.emit(DOWNLOADING_MODULES_FINISHED, 0, 1);
events.append({
type: DOWNLOADED_MODULE,
name: 'host',
current: 1,
total: 1,
succeeded: false
});
events.append({
type: DOWNLOADING_MODULES_FINISHED,
succeeded: 0,
failed: 1
});
}
}
@ -344,7 +559,7 @@ function checkForUpdates() {
checkingForUpdates = true;
hostUpdateAvailable = false;
if (skipHostUpdate) {
events.emit(CHECKING_FOR_UPDATES);
events.append({ type: CHECKING_FOR_UPDATES });
hostOnUpdateNotAvailable();
} else {
logger.log('Checking for host updates.');
@ -352,6 +567,14 @@ function checkForUpdates() {
}
}
// Indicates that the initial update process is complete and that future updates
// are background updates. This merely affects the content of the events sent to
// the app so that analytics can correctly attribute module download/installs
// depending on whether they were ui-blocking or not.
function setInBackground() {
runningInBackground = true;
}
function getRemoteModuleName(name) {
if (process.platform === 'win32' && process.arch === 'x64') {
return `${name}.x64`;
@ -360,142 +583,17 @@ function getRemoteModuleName(name) {
return name;
}
function checkForModuleUpdates() {
const query = _extends({}, remoteQuery, { _: Math.floor(Date.now() / 1000 / 60 / 5) });
const url = `${remoteBaseURL}/versions.json`;
logger.log(`Checking for module updates at ${url}`);
request.get({
url,
agent: false,
encoding: null,
qs: query,
timeout: REQUEST_TIMEOUT,
strictSSL: false
}, (err, response, body) => {
checkingForUpdates = false;
if (!err && response.statusCode !== 200) {
err = new Error(`Non-200 response code: ${response.statusCode}`);
}
if (err) {
logger.log(`Failed fetching module versions: ${String(err)}`);
events.emit(UPDATE_CHECK_FINISHED, false, 0, false);
return;
}
remoteModuleVersions = JSON.parse(body);
if (settings.get(USE_LOCAL_MODULE_VERSIONS)) {
try {
remoteModuleVersions = JSON.parse(_fs2.default.readFileSync(localModuleVersionsFilePath));
console.log('Using local module versions: ', remoteModuleVersions);
} catch (err) {
console.warn('Failed to parse local module versions: ', err);
}
}
const updatesToDownload = [];
for (const moduleName of Object.keys(installedModules)) {
const installedModule = installedModules[moduleName];
const installed = installedModule.installedVersion;
if (installed === null) {
continue;
}
const update = installedModule.updateVersion || 0;
const remote = remoteModuleVersions[getRemoteModuleName(moduleName)] || 0;
// TODO: strict equality?
if (installed != remote && update != remote) {
logger.log(`Module update available: ${moduleName}@${remote} [installed: ${installed}]`);
updatesToDownload.push({ name: moduleName, version: remote });
}
}
events.emit(UPDATE_CHECK_FINISHED, true, updatesToDownload.length, false);
if (updatesToDownload.length === 0) {
logger.log(`No module updates available.`);
} else {
updatesToDownload.forEach(e => addModuleToDownloadQueue(e.name, e.version));
}
});
}
function addModuleToDownloadQueue(name, version, authToken) {
download.queue.push({ name, version, authToken });
process.nextTick(() => processDownloadQueue());
}
function processDownloadQueue() {
if (download.active) return;
if (download.queue.length === 0) return;
download.active = true;
const queuedModule = download.queue[download.next];
download.next += 1;
events.emit(DOWNLOADING_MODULE, queuedModule.name, download.next, download.queue.length);
let totalBytes = 1;
let receivedBytes = 0;
let progress = 0;
let hasErrored = false;
const url = `${remoteBaseURL}/${getRemoteModuleName(queuedModule.name)}/${queuedModule.version}`;
logger.log(`Fetching ${queuedModule.name}@${queuedModule.version} from ${url}`);
const headers = {};
if (queuedModule.authToken) {
headers['Authorization'] = queuedModule.authToken;
}
request.get({
url,
agent: false,
encoding: null,
followAllRedirects: true,
qs: remoteQuery,
timeout: REQUEST_TIMEOUT,
strictSSL: false,
headers
}).on('error', err => {
if (hasErrored) return;
hasErrored = true;
logger.log(`Failed fetching ${queuedModule.name}@${queuedModule.version}: ${String(err)}`);
finishModuleDownload(queuedModule.name, queuedModule.version, null, false);
}).on('response', response => {
totalBytes = response.headers['content-length'] || 1;
const moduleZipPath = _path2.default.join(moduleDownloadPath, `${queuedModule.name}-${queuedModule.version}.zip`);
logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} [${totalBytes} bytes] to ${moduleZipPath}`);
const stream = _fs2.default.createWriteStream(moduleZipPath);
stream.on('finish', () => finishModuleDownload(queuedModule.name, queuedModule.version, moduleZipPath, response.statusCode === 200));
response.on('data', chunk => {
receivedBytes += chunk.length;
stream.write(chunk);
const fraction = receivedBytes / totalBytes;
const newProgress = Math.min(Math.floor(100 * fraction), 100);
if (progress != newProgress) {
progress = newProgress;
events.emit(DOWNLOADING_MODULE_PROGRESS, queuedModule.name, progress);
}
});
// TODO: on response error
// TODO: on stream error
response.on('end', () => stream.end());
});
}
function commitInstalledModules() {
const data = JSON.stringify(installedModules, null, 2);
_fs2.default.writeFileSync(installedModulesFilePath, data);
}
function finishModuleDownload(name, version, zipfile, succeeded) {
function finishModuleDownload(name, version, zipfile, receivedBytes, succeeded) {
if (!installedModules[name]) {
installedModules[name] = {};
}
@ -508,12 +606,23 @@ function finishModuleDownload(name, version, zipfile, succeeded) {
download.failures += 1;
}
events.emit(DOWNLOADED_MODULE, name, download.next, download.queue.length, succeeded);
events.append({
type: DOWNLOADED_MODULE,
name: name,
current: download.next,
total: download.queue.length,
succeeded: succeeded,
receivedBytes: receivedBytes
});
if (download.next >= download.queue.length) {
const successes = download.queue.length - download.failures;
logger.log(`Finished module downloads. [success: ${successes}] [failure: ${download.failures}]`);
events.emit(DOWNLOADING_MODULES_FINISHED, successes, download.failures);
events.append({
type: DOWNLOADING_MODULES_FINISHED,
succeeded: successes,
failed: download.failures
});
download.queue = [];
download.next = 0;
download.failures = 0;
@ -550,9 +659,19 @@ function processUnzipQueue() {
unzip.active = true;
const queuedModule = unzip.queue[unzip.next];
const installedModule = installedModules[queuedModule.name];
const installedVersion = installedModule != null ? installedModule.installedVersion : null;
unzip.next += 1;
events.emit(INSTALLING_MODULE, queuedModule.name, unzip.next, unzip.queue.length);
events.append({
type: INSTALLING_MODULE,
name: queuedModule.name,
current: unzip.next,
total: unzip.queue.length,
foreground: !runningInBackground,
oldVersion: installedVersion,
newVersion: queuedModule.version
});
let hasErrored = false;
const onError = (error, zipfile) => {
@ -583,7 +702,11 @@ function processUnzipQueue() {
zipfile.on('entry', entry => {
processedEntries += 1;
const percent = Math.min(Math.floor(processedEntries / totalEntries * 100), 100);
events.emit(INSTALLING_MODULE_PROGRESS, queuedModule.name, percent);
events.append({
type: INSTALLING_MODULE_PROGRESS,
name: queuedModule.name,
progress: percent
});
// skip directories
if (/\/$/.test(entry.fileName)) {
@ -668,7 +791,13 @@ function finishModuleUnzip(unzippedModule, succeeded) {
unzip.failures += 1;
}
events.emit(INSTALLED_MODULE, unzippedModule.name, unzip.next, unzip.queue.length, succeeded);
events.append({
type: INSTALLED_MODULE,
name: unzippedModule.name,
current: unzip.next,
total: unzip.queue.length,
succeeded: succeeded
});
if (unzip.next >= unzip.queue.length) {
const successes = unzip.queue.length - unzip.failures;
@ -678,7 +807,11 @@ function finishModuleUnzip(unzippedModule, succeeded) {
unzip.next = 0;
unzip.failures = 0;
unzip.active = false;
events.emit(INSTALLING_MODULES_FINISHED, successes, unzip.failures);
events.append({
type: INSTALLING_MODULES_FINISHED,
succeeded: successes,
failed: unzip.failures
});
return;
}
@ -722,7 +855,13 @@ function install(name, defer, options) {
let { version, authToken } = options || {};
if (isInstalled(name, version)) {
if (!defer) {
events.emit(INSTALLED_MODULE, name, 1, 1, true);
events.append({
type: INSTALLED_MODULE,
name: name,
current: 1,
total: 1,
succeeded: true
});
}
return;
}
@ -780,6 +919,8 @@ function installPendingUpdates() {
updatesToInstall.forEach(e => addModuleToUnzipQueue(e.moduleName, e.update, e.zipfile));
} else {
logger.log('No updates to install');
events.emit(NO_PENDING_UPDATES);
events.append({
type: NO_PENDING_UPDATES
});
}
}

View File

@ -1,117 +0,0 @@
'use strict'
const bindings = process.atomBinding('app')
const path = require('path')
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
// App is an EventEmitter.
Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
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)
},
appendArgument (...args) {
const castedArgs = args.map((arg) => {
return typeof arg !== 'string' ? `${arg}` : arg
})
return bindings.appendArgument(...castedArgs)
}
}
})
const nativeFn = app.getAppMetrics
app.getAppMetrics = () => {
const metrics = nativeFn.call(app)
for (const metric of metrics) {
if ('memory' in metric) {
deprecate.removeProperty(metric, 'memory')
}
}
return metrics
}
app.isPackaged = (() => {
const execFile = path.basename(process.execPath).toLowerCase()
if (process.platform === 'win32') {
return execFile !== 'electron.exe'
}
return execFile !== 'electron'
})()
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
}
}
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']
for (const name of events) {
app.on(name, (event, webContents, ...args) => {
webContents.emit(name, event, ...args)
})
}
// Wrappers for native classes.
const { DownloadItem } = process.atomBinding('download_item')
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype)

View File

@ -1,7 +0,0 @@
'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')
}

View File

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

View File

@ -1,74 +0,0 @@
'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)
}
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()

View File

@ -1,119 +0,0 @@
'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)
// 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])
// 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 || []
}
} 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
}
// 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 () {})
}
// 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)
})
}
// Update the application to the latest remote version specified by URL.
exports.update = function (updateURL, 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
}
}

View File

@ -1,16 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const { BrowserView } = process.atomBinding('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

View File

@ -1,191 +0,0 @@
'use strict'
const electron = require('electron')
const { WebContentsView, TopLevelWindow } = electron
const { BrowserWindow } = process.atomBinding('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
}
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
}
})
}
const isBrowserWindow = (win) => {
return win && win.constructor.name === 'BrowserWindow'
}
BrowserWindow.fromId = (id) => {
const win = TopLevelWindow.fromId(id)
return isBrowserWindow(win) ? win : null
}
BrowserWindow.getAllWindows = () => {
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
}
}
}
// 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

View File

@ -1,3 +0,0 @@
'use strict'
module.exports = process.atomBinding('content_tracing')

View File

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

View File

@ -1,312 +0,0 @@
'use strict'
const { app, BrowserWindow } = require('electron')
const binding = process.atomBinding('dialog')
const v8Util = process.atomBinding('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]
}
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
}
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 (options == null) {
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
for (const prop in fileDialogProperties) {
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 (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
}
// 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
}
}
}
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')
}
let { certificate, message } = options
if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object')
}
if (message == null) {
message = ''
} else if (typeof message !== 'string') {
throw new TypeError('message must be a string')
}
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)

View File

@ -1,15 +0,0 @@
'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')
// Import common modules.
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`))
})
}

View File

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

View File

@ -1,20 +0,0 @@
'use strict'
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: () => ''
}
}

View File

@ -1,10 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const emitter = new EventEmitter()
// Do not throw exception when channel name is "error".
emitter.on('error', () => {})
module.exports = emitter

View File

@ -1,297 +0,0 @@
'use strict'
const { app } = require('electron')
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()}`
},
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: 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)
}
},
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
}
exports.getDefaultLabel = (role) => {
return roles.hasOwnProperty(role) ? roles[role].label : ''
}
exports.getDefaultAccelerator = (role) => {
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
}
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
}
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]()
}
return true
}
if (webContentsMethod && focusedWebContents != null) {
if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents)
} else {
focusedWebContents[webContentsMethod]()
}
return true
}
return false
}

View File

@ -1,85 +0,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
}
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)
}
MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
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

View File

@ -1,176 +0,0 @@
'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 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' })
}
}
return joined.concat(arr)
}, [])
}
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
)
)
}
// 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 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) {
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.afterGroupContaining) {
for (const id of item.afterGroupContaining) {
const to = indexOfGroupContainingID(groups, id, group)
if (to !== -1) {
pushOntoMultiMap(edges, i, to)
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)
}
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
}
module.exports = { sortMenuItems }

View File

@ -1,258 +0,0 @@
'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)
// 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)
}
}
}
/* Instance Methods */
Menu.prototype._init = function () {
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 (!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 }
}
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)
}
}
return found
}
Menu.prototype.append = function (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
}
Menu.prototype._callMenuWillShow = function () {
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
// 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))
}
}
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)
}
}
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 +0,0 @@
'use strict'
const features = process.atomBinding('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 }
]
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' }
)
}

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 +0,0 @@
'use strict'
// TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of sessio 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 }
}
}))

View File

@ -1,372 +0,0 @@
'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
// 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:'])
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)
}
}
_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)
}
}
URLRequest.prototype._emitResponseEvent = function (isAsync, ...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'
}
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)
}
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 = ''
}
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)
}
}
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.')
}
this.chunkedEncodingEnabled = 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())
}
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]
}
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)
}
_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
}
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)
}
Net.prototype.request = function (options, callback) {
return new ClientRequest(options, callback)
}
net.ClientRequest = ClientRequest
module.exports = net

View File

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

View File

@ -1,25 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const { powerMonitor, PowerMonitor } = process.atomBinding('power_monitor')
// PowerMonitor is an EventEmitter.
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()
}
})
}
module.exports = powerMonitor

View File

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

View File

@ -1,29 +0,0 @@
'use strict'
const { app, session } = require('electron')
// Global protocol APIs.
module.exports = process.atomBinding('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 }
}
}))

View File

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

View File

@ -1,24 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const { app } = require('electron')
const { fromPartition, Session, Cookies } = process.atomBinding('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)
Session.prototype._init = function () {
app.emit('session-created', this)
}

View File

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

View File

@ -1,24 +0,0 @@
'use strict'
const electron = require('electron')
const { EventEmitter } = require('events')
const { TopLevelWindow } = process.atomBinding('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)
}
}
TopLevelWindow.getFocusedWindow = () => {
return TopLevelWindow.getAllWindows().find((win) => win.isFocused())
}
module.exports = TopLevelWindow

View File

@ -1,343 +0,0 @@
'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)
}
}
}
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()
}
}
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
})
}
}
}
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 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const { Tray } = process.atomBinding('tray')
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype)
module.exports = Tray

View File

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

View File

@ -1,15 +0,0 @@
'use strict'
const electron = require('electron')
const { View } = electron
const { WebContentsView } = process.atomBinding('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

View File

@ -1,507 +0,0 @@
'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')
// 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
const getNextId = function () {
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'
}
}
// 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
}
// 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)
// 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)
}
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)
}
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)
}
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)
}
// 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)
})
}
for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args) {
this._sendInternal('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args)
}
}
// 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)
}
})
}
// 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 {
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.')
}
}
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
}))
}
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)
}
// 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)
}
}
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)
}
// JavaScript wrapper of Debugger.
const { Debugger } = process.atomBinding('debugger')
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
}
return focused
},
getAllWebContents () {
return binding.getAllWebContents()
}
}

View File

@ -1,446 +0,0 @@
'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')
// Mapping between extensionId(hostname) and manifest.
const manifestMap = {} // extensionId => manifest
const manifestNameMap = {} // name => manifest
const devToolsExtensionNames = new Set()
const generateExtensionIdFromName = function (name) {
return name.replace(/[\W_]+/g, '-').toLowerCase()
}
const isWindowOrWebView = function (webContents) {
const type = webContents.getType()
return type === 'window' || type === 'webview'
}
// 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({
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
}))
}
const removeBackgroundPages = function (manifest) {
if (!backgroundPages[manifest.extensionId]) return
backgroundPages[manifest.extensionId].webContents.destroy()
delete backgroundPages[manifest.extensionId]
}
const sendToBackgroundPages = function (...args) {
for (const page of Object.values(backgroundPages)) {
page.webContents._sendInternalToAll(...args)
}
}
// Dispatch web contents events to Chrome APIs
const hookWebContentsEvents = function (webContents) {
const tabId = webContents.id
sendToBackgroundPages('CHROME_TABS_ONCREATED')
webContents.on('will-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
frameId: 0,
parentFrameId: -1,
processId: webContents.getProcessId(),
tabId: tabId,
timeStamp: Date.now(),
url: url
})
})
webContents.on('did-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
frameId: 0,
parentFrameId: -1,
processId: webContents.getProcessId(),
tabId: tabId,
timeStamp: Date.now(),
url: url
})
})
webContents.once('destroyed', () => {
sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
})
}
// Handle the chrome.* API messages.
let nextId = 0
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++
})
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)
})
// Transfer the content scripts to renderer.
const contentScripts = {}
const injectContentScripts = function (manifest) {
if (contentScripts[manifest.name] || !manifest.content_scripts) return
const readArrayOfFiles = function (relativePath) {
return {
url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
}
}
const contentScriptToEntry = function (script) {
return {
matches: script.matches,
js: script.js ? script.js.map(readArrayOfFiles) : [],
css: script.css ? script.css.map(readArrayOfFiles) : [],
runAt: script.run_at || 'document_idle'
}
}
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]
}
// Transfer the |manifest| to a format that can be recognized by the
// |DevToolsAPI.addExtensions|.
const manifestToExtensionInfo = function (manifest) {
return {
startPage: manifest.startPage,
srcDirectory: manifest.srcDirectory,
name: manifest.name,
exposeExperimentalAPIs: true
}
}
// Load the extensions for the window.
const loadExtension = function (manifest) {
startBackgroundPages(manifest)
injectContentScripts(manifest)
}
const loadDevToolsExtensions = function (win, manifests) {
if (!win.devToolsWebContents) return
manifests.forEach(loadExtension)
const extensionInfoArray = manifests.map(manifestToExtensionInfo)
extensionInfoArray.forEach((extension) => {
win.devToolsWebContents._grantOriginAccess(extension.startPage)
})
win.devToolsWebContents.executeJavaScript(`DevToolsAPI.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))
})
})
// The chrome-extension: can map a extension URL request to real file path.
const chromeExtensionHandler = function (request, callback) {
const parsed = url.parse(request.url)
if (!parsed.hostname || !parsed.path) return callback()
const manifest = manifestMap[parsed.hostname]
if (!manifest) return callback()
const page = backgroundPages[parsed.hostname]
if (page && parsed.path === `/${page.name}`) {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
return callback({
mimeType: 'text/html',
data: page.html
})
}
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
if (err) {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
return callback(-6) // FILE_NOT_FOUND
} else {
return callback(content)
}
})
}
app.on('session-created', function (ses) {
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
if (error) {
console.error(`Unable to register chrome-extension protocol: ${error}`)
}
})
})
// The persistent path of "DevTools Extensions" preference file.
let loadedDevToolsExtensionsPath = null
app.on('will-quit', function () {
try {
const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
.map(name => manifestNameMap[name].srcDirectory)
if (loadedDevToolsExtensions.length > 0) {
try {
fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath))
} catch (error) {
// Ignore error
}
fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions))
} else {
fs.unlinkSync(loadedDevToolsExtensionsPath)
}
} catch (error) {
// 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])
}
}
return manifest.name
}
}
BrowserWindow.removeExtension = function (name) {
const manifest = manifestNameMap[name]
if (!manifest) return
removeBackgroundPages(manifest)
removeContentScripts(manifest)
delete manifestMap[manifest.extensionId]
delete manifestNameMap[name]
}
BrowserWindow.getExtensions = function () {
const extensions = {}
Object.keys(manifestNameMap).forEach(function (name) {
const manifest = manifestNameMap[name]
extensions[name] = { name: manifest.name, version: manifest.version }
})
return extensions
}
BrowserWindow.addDevToolsExtension = function (srcDirectory) {
const manifestName = BrowserWindow.addExtension(srcDirectory)
if (manifestName) {
devToolsExtensionNames.add(manifestName)
}
return manifestName
}
BrowserWindow.removeDevToolsExtension = function (name) {
BrowserWindow.removeExtension(name)
devToolsExtensionNames.delete(name)
}
BrowserWindow.getDevToolsExtensions = function () {
const extensions = BrowserWindow.getExtensions()
const devExtensions = {}
Array.from(devToolsExtensionNames).forEach(function (name) {
if (!extensions[name]) return
devExtensions[name] = extensions[name]
})
return devExtensions
}
// Load persisted extensions.
loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
try {
const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath))
if (Array.isArray(loadedDevToolsExtensions)) {
for (const srcDirectory of loadedDevToolsExtensions) {
// Start background pages and set content scripts.
BrowserWindow.addDevToolsExtension(srcDirectory)
}
}
} catch (error) {
if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') {
console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath)
console.error(error)
}
}
})

View File

@ -1,73 +0,0 @@
'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
}
})
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)
}
})
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)
}
}

View File

@ -1,451 +0,0 @@
'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')
// Doesn't exist in early initialization.
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
}
// 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)
}
})
// 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)
}
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
}
// 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 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 { 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 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
}
}
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]
}
// Once an embedder has had a guest attached we watch it for destruction to
// destroy any remaining guests.
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)
}
}
}
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)
}
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)
})
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}`)
}
})
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}`)
}
})
// 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}`)
}
if (hasCallback) {
guest[method](...args, resolve)
} else {
resolve(guest[method](...args))
}
}).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}`)
}
event.returnValue = [null, guest[method](...args)]
} catch (error) {
event.returnValue = [errorUtils.serialize(error)]
}
})
// 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
}
// Returns WebContents from its guest id.
const getGuest = function (guestInstanceId) {
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

View File

@ -1,368 +0,0 @@
'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()
// 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]
])
// 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
}
}
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() }
}
// 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
}
}
// 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
}
// 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
}
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)
}
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 = {}
}
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.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)
}
}
const referrer = { url: '', policy: 'default' }
ipcMain.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
}
} 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()
})
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)
}
})
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.`)
}
})
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
}
})

View File

@ -1,188 +0,0 @@
'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')
// 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)
// Clear search paths.
require('../common/reset-search-paths')
// Import common settings.
require('@electron/internal/common/init')
const globalPaths = Module.globalPaths
// Expose public APIs.
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
}
// 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)
})
// Emit 'exit' event on quit.
const { app } = require('electron')
app.on('quit', function (event, 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}`)
}
}
// Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit
// Load the 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')
// 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
}
}
if (packageJson == null) {
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)
}
// Set application's name.
if (packageJson.productName != null) {
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')
}
// Set v8 flags
if (packageJson.v8Flags != null) {
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)
// Load the chrome extension support.
require('@electron/internal/browser/chrome-extension')
const features = process.atomBinding('features')
if (features.isDesktopCapturerEnabled()) {
// 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')
// 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
}
// Workaround for electron/electron#5050 and electron/electron#9046
if (currentPlatformSupportsAppIndicator()) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
// Finally load app's main.js and transfer control to C++.
Module._load(path.join(packagePath, mainStartupScript), Module, true)

View File

@ -1,10 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const emitter = new EventEmitter()
// Do not throw exception when channel name is "error".
emitter.on('error', () => {})
module.exports = emitter

View File

@ -1,116 +0,0 @@
'use strict'
const v8Util = process.atomBinding('v8_util')
const getOwnerKey = (webContents, 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)
}
if (!owner.has(id)) {
owner.add(id)
// Increase reference count if not referenced before.
this.storage[id].count++
}
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)
}
}
// 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)
}
return id
}
// 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)
}
}
module.exports = new ObjectsRegistry()

View File

@ -1,558 +0,0 @@
'use strict'
const { spawn } = require('child_process')
const electron = require('electron')
const { EventEmitter } = require('events')
const fs = require('fs')
const os = require('os')
const path = require('path')
const v8Util = process.atomBinding('v8_util')
const eventBinding = process.atomBinding('event')
const { isPromise } = electron
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const objectsRegistry = require('@electron/internal/browser/objects-registry')
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
const bufferUtils = require('@electron/internal/common/buffer-utils')
const errorUtils = require('@electron/internal/common/error-utils')
const hasProp = {}.hasOwnProperty
// The internal properties of Function.
const FUNCTION_PROPERTIES = [
'length', 'name', 'arguments', 'caller', 'prototype'
]
// The remote functions in renderer processes.
// id => Function
const rendererFunctions = v8Util.createDoubleIDWeakMap()
// Return the description of object's members:
const getObjectMembers = function (object) {
let names = Object.getOwnPropertyNames(object)
// For Function, we should not override following properties even though they
// are "own" properties.
if (typeof object === 'function') {
names = names.filter((name) => {
return !FUNCTION_PROPERTIES.includes(name)
})
}
// Map properties to descriptors.
return names.map((name) => {
const descriptor = Object.getOwnPropertyDescriptor(object, name)
const member = { name, enumerable: descriptor.enumerable, writable: false }
if (descriptor.get === undefined && typeof object[name] === 'function') {
member.type = 'method'
} else {
if (descriptor.set || descriptor.writable) member.writable = true
member.type = 'get'
}
return member
})
}
// Return the description of object's prototype.
const getObjectPrototype = function (object) {
const proto = Object.getPrototypeOf(object)
if (proto === null || proto === Object.prototype) return null
return {
members: getObjectMembers(proto),
proto: getObjectPrototype(proto)
}
}
// Convert a real value into meta data.
const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
// Determine the type of value.
const meta = { type: typeof value }
if (meta.type === 'object') {
// Recognize certain types of objects.
if (value === null) {
meta.type = 'value'
} else if (bufferUtils.isBuffer(value)) {
meta.type = 'buffer'
} else if (Array.isArray(value)) {
meta.type = 'array'
} else if (value instanceof Error) {
meta.type = 'error'
} else if (value instanceof Date) {
meta.type = 'date'
} else if (isPromise(value)) {
meta.type = 'promise'
} else if (hasProp.call(value, 'callee') && value.length != null) {
// Treat the arguments object as array.
meta.type = 'array'
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
// Treat simple objects as value.
meta.type = 'value'
}
}
// Fill the meta object according to value's type.
if (meta.type === 'array') {
meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
} else if (meta.type === 'object' || meta.type === 'function') {
meta.name = value.constructor ? value.constructor.name : ''
// Reference the original value if it's an object, because when it's
// passed to renderer we would assume the renderer keeps a reference of
// it.
meta.id = objectsRegistry.add(sender, contextId, value)
meta.members = getObjectMembers(value)
meta.proto = getObjectPrototype(value)
} else if (meta.type === 'buffer') {
meta.value = bufferUtils.bufferToMeta(value)
} else if (meta.type === 'promise') {
// Add default handler to prevent unhandled rejections in main process
// Instead they should appear in the renderer process
value.then(function () {}, function () {})
meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected)
})
} else if (meta.type === 'error') {
meta.members = plainObjectToMeta(value)
// Error.name is not part of own properties.
meta.members.push({
name: 'name',
value: value.name
})
} else if (meta.type === 'date') {
meta.value = value.getTime()
} else {
meta.type = 'value'
meta.value = value
}
return meta
}
// Convert object to meta by value.
const plainObjectToMeta = function (obj) {
return Object.getOwnPropertyNames(obj).map(function (name) {
return {
name: name,
value: obj[name]
}
})
}
// Convert Error into meta data.
const exceptionToMeta = function (sender, contextId, error) {
return {
type: 'exception',
value: errorUtils.serialize(error)
}
}
const throwRPCError = function (message) {
const error = new Error(message)
error.code = 'EBADRPC'
error.errno = -72
throw error
}
const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
let message = `Attempting to call a function in a renderer window that has been closed or released.` +
`\nFunction provided here: ${location}`
if (sender instanceof EventEmitter) {
const remoteEvents = sender.eventNames().filter((eventName) => {
return sender.listeners(eventName).includes(callIntoRenderer)
})
if (remoteEvents.length > 0) {
message += `\nRemote event names: ${remoteEvents.join(', ')}`
remoteEvents.forEach((eventName) => {
sender.removeListener(eventName, callIntoRenderer)
})
}
}
console.warn(message)
}
// Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender, contextId, args) {
const metaToValue = function (meta) {
switch (meta.type) {
case 'value':
return meta.value
case 'remote-object':
return objectsRegistry.get(meta.id)
case 'array':
return unwrapArgs(sender, contextId, meta.value)
case 'buffer':
return bufferUtils.metaToBuffer(meta.value)
case 'date':
return new Date(meta.value)
case 'promise':
return Promise.resolve({
then: metaToValue(meta.then)
})
case 'object': {
const ret = {}
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
for (const { name, value } of meta.members) {
ret[name] = metaToValue(value)
}
return ret
}
case 'function-with-return-value':
const returnValue = metaToValue(meta.value)
return function () {
return returnValue
}
case 'function': {
// Merge contextId and meta.id, since meta.id can be the same in
// different webContents.
const objectId = [contextId, meta.id]
// Cache the callbacks in renderer.
if (rendererFunctions.has(objectId)) {
return rendererFunctions.get(objectId)
}
const callIntoRenderer = function (...args) {
if (!sender.isDestroyed()) {
sender._sendInternal('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
} else {
removeRemoteListenersAndLogWarning(this, callIntoRenderer)
}
}
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
rendererFunctions.set(objectId, callIntoRenderer)
return callIntoRenderer
}
default:
throw new TypeError(`Unknown type: ${meta.type}`)
}
}
return args.map(metaToValue)
}
// Call a function and send reply asynchronously if it's a an asynchronous
// style function and the caller didn't pass a callback.
const callFunction = function (event, contextId, func, caller, args) {
const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
const funcPassedCallback = typeof args[args.length - 1] === 'function'
try {
if (funcMarkedAsync && !funcPassedCallback) {
args.push(function (ret) {
event.returnValue = valueToMeta(event.sender, contextId, ret, true)
})
func.apply(caller, args)
} else {
const ret = func.apply(caller, args)
return valueToMeta(event.sender, contextId, ret, true)
}
} catch (error) {
// Catch functions thrown further down in function invocation and wrap
// them with the function name so it's easier to trace things like
// `Error processing argument -1.`
const funcName = func.name || 'anonymous'
const err = new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`)
err.cause = error
throw err
}
}
const handleRemoteCommand = function (channel, handler) {
ipcMain.on(channel, (event, contextId, ...args) => {
let returnValue
if (!event.sender._isRemoteModuleEnabled()) {
event.returnValue = null
return
}
try {
returnValue = handler(event, contextId, ...args)
} catch (error) {
returnValue = exceptionToMeta(event.sender, contextId, error)
}
if (returnValue !== undefined) {
event.returnValue = returnValue
}
})
}
handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
const objectId = [passedContextId, id]
if (!rendererFunctions.has(objectId)) {
// Do nothing if the error has already been reported before.
return
}
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
})
handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-require', customEvent, moduleName)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.require('${moduleName}')`)
} else {
customEvent.returnValue = process.mainModule.require(moduleName)
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-get-builtin', customEvent, moduleName)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
} else {
customEvent.returnValue = electron[moduleName]
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-get-global', customEvent, globalName)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getGlobal('${globalName}')`)
} else {
customEvent.returnValue = global[globalName]
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-get-current-window', customEvent)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWindow()')
} else {
customEvent.returnValue = event.sender.getOwnerBrowserWindow()
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-get-current-web-contents', customEvent)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWebContents()')
} else {
customEvent.returnValue = event.sender
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, contextId, args)
const constructor = objectsRegistry.get(id)
if (constructor == null) {
throwRPCError(`Cannot call constructor on missing remote object ${id}`)
}
return valueToMeta(event.sender, contextId, new constructor(...args))
})
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, contextId, args)
const func = objectsRegistry.get(id)
if (func == null) {
throwRPCError(`Cannot call function on missing remote object ${id}`)
}
return callFunction(event, contextId, func, global, args)
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, contextId, args)
const object = objectsRegistry.get(id)
if (object == null) {
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
}
return valueToMeta(event.sender, contextId, new object[method](...args))
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, contextId, args)
const obj = objectsRegistry.get(id)
if (obj == null) {
throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
}
return callFunction(event, contextId, obj[method], obj, args)
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
args = unwrapArgs(event.sender, contextId, args)
const obj = objectsRegistry.get(id)
if (obj == null) {
throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
}
obj[name] = args[0]
return null
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
const obj = objectsRegistry.get(id)
if (obj == null) {
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
}
return valueToMeta(event.sender, contextId, obj[name])
})
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
objectsRegistry.remove(event.sender, contextId, id)
})
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
objectsRegistry.clear(event.sender, contextId)
return null
})
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('remote-get-guest-web-contents', customEvent, guest)
if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getGuestForWebContents()`)
} else {
customEvent.returnValue = guest
}
}
return valueToMeta(event.sender, contextId, customEvent.returnValue)
})
// Implements window.close()
ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
const window = event.sender.getOwnerBrowserWindow()
if (window) {
window.close()
}
event.returnValue = null
})
const getTempDirectory = function () {
try {
return electron.app.getPath('temp')
} catch (error) {
return os.tmpdir()
}
}
const crashReporterInit = function (options) {
const productName = options.productName || electron.app.getName()
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
let crashServicePid
if (process.platform === 'win32') {
const env = {
ELECTRON_INTERNAL_CRASH_SERVICE: 1
}
const args = [
'--reporter-url=' + options.submitURL,
'--application-name=' + productName,
'--crashes-directory=' + crashesDirectory,
'--v=1'
]
const crashServiceProcess = spawn(process.helperExecPath, args, {
env,
detached: true
})
crashServicePid = crashServiceProcess.pid
}
return {
productName,
crashesDirectory,
crashServicePid,
appVersion: electron.app.getVersion()
}
}
const setReturnValue = function (event, getValue) {
try {
event.returnValue = [null, getValue()]
} catch (error) {
event.returnValue = [errorUtils.serialize(error)]
}
}
ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
setReturnValue(event, () => crashReporterInit(options))
})
ipcMain.on('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
setReturnValue(event, () => event.sender.getLastWebPreferences())
})
ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', function (event) {
setReturnValue(event, () => electron.clipboard.readFindText())
})
ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text) {
setReturnValue(event, () => electron.clipboard.writeFindText(text))
})
ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
const preloadPath = event.sender._getPreloadPath()
let preloadSrc = null
let preloadError = null
if (preloadPath) {
try {
preloadSrc = fs.readFileSync(preloadPath).toString()
} catch (err) {
preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack }
}
}
event.returnValue = {
preloadSrc,
preloadError,
isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(),
process: {
arch: process.arch,
platform: process.platform,
env: process.env,
version: process.version,
versions: process.versions
}
}
})

View File

@ -1,31 +0,0 @@
'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
}
}
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
}

View File

@ -1,95 +0,0 @@
'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)
}
}
}
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}`)
}
},
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

View File

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

View File

@ -1,31 +0,0 @@
'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
}
}
// 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}`))
}
}
return Object.defineProperties(targetExports, descriptors)
}

View File

@ -1,14 +0,0 @@
'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 +0,0 @@
'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 }
]

View File

@ -1,3 +0,0 @@
'use strict'
module.exports = process.atomBinding('native_image')

View File

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

View File

@ -1,15 +0,0 @@
'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
}
}
}
}

View File

@ -1,66 +0,0 @@
'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 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
}
}
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)
}
}
exports.isBuffer = function (value) {
return ArrayBuffer.isView(value) || value instanceof ArrayBuffer
}
exports.bufferToMeta = function (value) {
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
}
}

View File

@ -1,123 +0,0 @@
'use strict'
const binding = process.atomBinding('crash_reporter')
const errorUtils = require('@electron/internal/common/error-utils')
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)
}
return result
}
start (options) {
if (options == null) options = {}
let {
productName,
companyName,
extra,
ignoreSystemCrashHandler,
submitURL,
uploadToServer
} = options
if (uploadToServer == null) {
uploadToServer = true
}
if (ignoreSystemCrashHandler == null) {
ignoreSystemCrashHandler = false
}
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.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')
}
}
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 (key, value) {
return binding.getParameters()
}
}
module.exports = CrashReporter

View File

@ -1,39 +0,0 @@
'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]
])
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
}
}
return error
}

View File

@ -1,53 +0,0 @@
'use strict'
const timers = require('timers')
const util = require('util')
process.atomBinding = require('@electron/internal/common/atom-binding-setup')(process.binding, 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,
// which would delay the callbacks for arbitrary long time. So we should
// 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)
}
})
}
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
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)
}
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
})
}

View File

@ -1,21 +0,0 @@
'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)
})
}

View File

@ -1,45 +0,0 @@
'use strict'
const path = require('path')
const Module = require('module')
// Clear Node's global search paths.
Module.globalPaths.length = 0
// Clear current and parent(init.js)'s search 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
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/'
// 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
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)
}
}

View File

@ -1,70 +0,0 @@
'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'
])
exports.asyncCallbackMethods = new Set([
'insertCSS',
'insertText',
'send',
'sendInputEvent',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits',
'getZoomFactor',
'getZoomLevel',
'print',
'printToPDF'
])
exports.asyncPromiseMethods = new Set([
'capturePage',
'executeJavaScript'
])

View File

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

View File

@ -1,48 +0,0 @@
'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
}
// |options.types| can't be empty and must be an array
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
})())
})
}

View File

@ -1,23 +0,0 @@
'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}`))
})
}

View File

@ -1,30 +0,0 @@
'use strict'
const binding = process.atomBinding('ipc')
const v8Util = process.atomBinding('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)
}
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

View File

@ -1,21 +0,0 @@
'use strict'
const features = process.atomBinding('features')
const v8Util = process.atomBinding('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' }
]

View File

@ -1,355 +0,0 @@
'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()
// An unique ID that can represent current context.
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)
})
// 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)
})
}
} 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'
}
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
}
}
}
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)
}
// 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'
}
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
}
}
// 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)
}
})
}
// Browser calls a callback in renderer.
handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
callbacksRegistry.apply(id, metaToValue(args))
})
// A callback in browser is released.
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
callbacksRegistry.remove(id)
})
exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE'
const meta = ipcRenderer.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)
}
exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
const meta = ipcRenderer.sendSync(command, contextId)
return metaToValue(meta)
}
// Get current WebContents object.
exports.getCurrentWebContents = () => {
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
}
// Get a global object in browser.
exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRenderer.sendSync(command, contextId, name)
return metaToValue(meta)
}
// Get the process object in browser.
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
}
// 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 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'))
// And add a helper receiver for each one.
browserModules
.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 +0,0 @@
'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)
}
}
// 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)
}
}
}
// 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
}
module.exports = new WebFrame(window)

View File

@ -1,59 +0,0 @@
'use strict'
const v8Util = process.atomBinding('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
}
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

View File

@ -1,190 +0,0 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const Event = require('@electron/internal/renderer/extensions/event')
const url = require('url')
class Tab {
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}`
}
}
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()
}
}
// 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()
}

View File

@ -1,101 +0,0 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const { runInThisContext } = require('vm')
// 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)
}
// 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)
}
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);
}
document.addEventListener('DOMContentLoaded', init);
})`
const compiledWrapper = runInThisContext(wrapper, {
filename: url,
lineOffset: 1,
displayErrors: true
})
return compiledWrapper.call(this, code)
}
const runAllStylesheet = function (css) {
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 (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)
}
}
}
}

View File

@ -1,26 +0,0 @@
'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)
}
}
emit (...args) {
for (const listener of this.listeners) {
listener(...args)
}
}
}
module.exports = Event

View File

@ -1,88 +0,0 @@
'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)
}
}
}

View File

@ -1,138 +0,0 @@
'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)
}
})
} 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()
})
}
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
}
// 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)
})
}

View File

@ -1,23 +0,0 @@
'use strict'
const Event = require('@electron/internal/renderer/extensions/event')
const ipcRenderer = 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)
})
}
}
exports.setup = () => {
return new WebNavigation()
}

View File

@ -1,176 +0,0 @@
'use strict'
const { EventEmitter } = require('events')
const path = require('path')
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)
// Clear search paths.
require('../common/reset-search-paths')
// Import common settings.
require('@electron/internal/common/init')
const globalPaths = Module.globalPaths
// Expose public APIs.
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())
// Use electron module after everything is ready.
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
require('@electron/internal/renderer/web-frame-init')()
// 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)
}
}
// The webContents preload script is loaded after the session preload scripts.
if (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)
}
}
}
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}`
}
}
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))
}
}
// 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
})
}
// 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)
}
}
// 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)
})
}

View File

@ -1,138 +0,0 @@
'use strict'
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
}
// Extra / is needed as a result of MacOS requiring absolute paths
function completeURL (project, path) {
project = 'file:///'
return `${project}${path}`
}
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() })
})
}
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'
}
]
}
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
}
const createFileSelectorElement = function (callback) {
const fileSelectorElement = document.createElement('span')
fileSelectorElement.style.display = 'none'
fileSelectorElement.click = showFileChooserDialog.bind(this, callback)
return fileSelectorElement
}

View File

@ -1,26 +0,0 @@
'use strict'
const binding = process.atomBinding('ipc')
const v8Util = process.atomBinding('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

View File

@ -1,18 +0,0 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const v8Util = process.atomBinding('v8_util')
const { guestInstanceId, openerId } = process
const hiddenPage = process.argv.includes('--hidden-page')
const usesNativeWindowOpen = process.argv.includes('--native-window-open')
const contextIsolation = process.argv.includes('--context-isolation')
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
const isolatedWorldArgs = { ipcRenderer, guestInstanceId, hiddenPage, openerId, usesNativeWindowOpen }
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
}
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen)

View File

@ -1,10 +0,0 @@
'use strict'
const { remote } = require('electron')
exports.getRemoteForUsage = function (usage) {
if (!remote) {
throw new Error(`${usage} requires remote, which is not enabled`)
}
return remote
}

View File

@ -1,339 +0,0 @@
'use strict'
let shouldLog = null
/**
* This method checks if a security message should be logged.
* It does so by determining whether we're running as Electron,
* which indicates that a developer is currently looking at the
* app.
*
* @returns {boolean} - Should we log?
*/
const shouldLogSecurityWarnings = function () {
if (shouldLog !== null) {
return shouldLog
}
const { platform, execPath, env } = process
switch (platform) {
case 'darwin':
shouldLog = execPath.endsWith('MacOS/Electron') ||
execPath.includes('Electron.app/Contents/Frameworks/')
break
case 'freebsd':
case 'linux':
shouldLog = execPath.endsWith('/electron')
break
case 'win32':
shouldLog = execPath.endsWith('\\electron.exe')
break
default:
shouldLog = false
}
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
shouldLog = false
}
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
shouldLog = true
}
return shouldLog
}
/**
* Checks if the current window is remote.
*
* @returns {boolean} - Is this a remote protocol?
*/
const getIsRemoteProtocol = function () {
if (window && window.location && window.location.protocol) {
return /^(http|ftp)s?/gi.test(window.location.protocol)
}
}
/**
* Tries to determine whether a CSP without `unsafe-eval` is set.
*
* @returns {boolean} Is a CSP with `unsafe-eval` set?
*/
const isUnsafeEvalEnabled = function () {
const { webFrame } = require('electron')
return new Promise((resolve) => {
webFrame.executeJavaScript(`(${(() => {
try {
new Function('') // eslint-disable-line no-new,no-new-func
} catch (err) {
return false
}
return true
}).toString()})()`, resolve)
})
}
const moreInformation = `\nFor more information and help, consult
https://electronjs.org/docs/tutorial/security.\n This warning will not show up
once the app is packaged.`
/**
* #1 Only load secure content
*
* Checks the loaded resources on the current page and logs a
* message about all resources loaded over HTTP or FTP.
*/
const warnAboutInsecureResources = function () {
if (!window || !window.performance || !window.performance.getEntriesByType) {
return
}
const resources = window.performance
.getEntriesByType('resource')
.filter(({ name }) => /^(http|ftp):/gi.test(name || ''))
.map(({ name }) => `- ${name}`)
.join('\n')
if (!resources || resources.length === 0) {
return
}
const warning = `This renderer process loads resources using insecure
protocols.This exposes users of this app to unnecessary security risks.
Consider loading the following resources over HTTPS or FTPS. \n ${resources}
\n ${moreInformation}`
console.warn('%cElectron Security Warning (Insecure Resources)',
'font-weight: bold;', warning)
}
/**
* #2 on the checklist: Disable the Node.js integration in all renderers that
* display remote content
*
* Logs a warning message about Node integration.
*/
const warnAboutNodeWithRemoteContent = function (nodeIntegration) {
if (!nodeIntegration) return
if (getIsRemoteProtocol()) {
const warning = `This renderer process has Node.js integration enabled
and attempted to load remote content from '${window.location}'. This
exposes users of this app to severe security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
'font-weight: bold;', warning)
}
}
// Currently missing since it has ramifications and is still experimental:
// #3 Enable context isolation in all renderers that display remote content
//
// Currently missing since we can't easily programmatically check for those cases:
// #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content
/**
* #5 on the checklist: Do not disable websecurity
*
* Logs a warning message about disabled webSecurity.
*/
const warnAboutDisabledWebSecurity = function (webPreferences) {
if (!webPreferences || webPreferences.webSecurity !== false) return
const warning = `This renderer process has "webSecurity" disabled. This
exposes users of this app to severe security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Disabled webSecurity)',
'font-weight: bold;', warning)
}
/**
* #6 on the checklist: Define a Content-Security-Policy and use restrictive
* rules (i.e. script-src 'self')
*
* #7 on the checklist: Disable eval
*
* Logs a warning message about unset or insecure CSP
*/
const warnAboutInsecureCSP = function () {
isUnsafeEvalEnabled().then((enabled) => {
if (!enabled) return
const warning = `This renderer process has either no Content Security
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
this app to unnecessary security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
'font-weight: bold;', warning)
})
}
/**
* #8 on the checklist: Do not set allowRunningInsecureContent to true
*
* Logs a warning message about disabled webSecurity.
*/
const warnAboutInsecureContentAllowed = function (webPreferences) {
if (!webPreferences || !webPreferences.allowRunningInsecureContent) return
const warning = `This renderer process has "allowRunningInsecureContent"
enabled. This exposes users of this app to severe security risks.\n
${moreInformation}`
console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
'font-weight: bold;', warning)
}
/**
* #9 on the checklist: Do not enable experimental features
*
* Logs a warning message about experimental features.
*/
const warnAboutExperimentalFeatures = function (webPreferences) {
if (!webPreferences || (!webPreferences.experimentalFeatures)) {
return
}
const warning = `This renderer process has "experimentalFeatures" enabled.
This exposes users of this app to some security risk. If you do not need
this feature, you should disable it.\n ${moreInformation}`
console.warn('%cElectron Security Warning (experimentalFeatures)',
'font-weight: bold;', warning)
}
/**
* #10 on the checklist: Do not use enableBlinkFeatures
*
* Logs a warning message about enableBlinkFeatures
*/
const warnAboutEnableBlinkFeatures = function (webPreferences) {
if (webPreferences === null ||
!webPreferences.hasOwnProperty('enableBlinkFeatures') ||
webPreferences.enableBlinkFeatures.length === 0) {
return
}
const warning = `This renderer process has additional "enableBlinkFeatures"
enabled. This exposes users of this app to some security risk. If you do not
need this feature, you should disable it.\n ${moreInformation}`
console.warn('%cElectron Security Warning (enableBlinkFeatures)',
'font-weight: bold;', warning)
}
/**
* #11 on the checklist: Do Not Use allowpopups
*
* Logs a warning message about allowed popups
*/
const warnAboutAllowedPopups = function () {
if (document && document.querySelectorAll) {
const domElements = document.querySelectorAll('[allowpopups]')
if (!domElements || domElements.length === 0) {
return
}
const warning = `A <webview> has "allowpopups" set to true. This exposes
users of this app to some security risk, since popups are just
BrowserWindows. If you do not need this feature, you should disable it.\n
${moreInformation}`
console.warn('%cElectron Security Warning (allowpopups)',
'font-weight: bold;', warning)
}
}
const warnAboutNodeIntegrationDefault = function (webPreferences) {
if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) {
const warning = `This window has node integration enabled by default. In ` +
`Electron 5.0.0, node integration will be disabled by default. To prepare ` +
`for this change, set {nodeIntegration: true} in the webPreferences for ` +
`this window, or ensure that this window does not rely on node integration ` +
`and set {nodeIntegration: false}.`
console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning)
}
}
const warnAboutContextIsolationDefault = function (webPreferences) {
if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) {
const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content'
const warning = `This window has context isolation disabled by default. In ` +
`Electron 5.0.0, context isolation will be enabled by default. To prepare ` +
`for this change, set {contextIsolation: false} in the webPreferences for ` +
`this window, or ensure that this window does not rely on context ` +
`isolation being disabled, and set {contextIsolation: true}.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning)
}
}
const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) {
if (!webPreferences) {
return
}
if (webPreferences.webviewTagWasExplicitlyEnabled) {
return
}
if (!document || !document.getElementsByTagName) {
return
}
const webviews = document.getElementsByTagName('webview')
if (webviews && webviews.length > 0) {
const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-'
const warning = `This window has the <webview> tag enabled by default. In ` +
`Electron 5.0.0, <webview> tags will be disabled by default. To prepare ` +
`for this change, set {webviewTag: true} in the webPreferences for ` +
`this window.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (webviewTag default change)',
'font-weight: bold;', warning)
}
}
// Currently missing since we can't easily programmatically check for it:
// #12WebViews: Verify the options and params of all `<webview>` tags
const logSecurityWarnings = function (webPreferences, nodeIntegration) {
warnAboutNodeWithRemoteContent(nodeIntegration)
warnAboutDisabledWebSecurity(webPreferences)
warnAboutInsecureResources()
warnAboutInsecureContentAllowed(webPreferences)
warnAboutExperimentalFeatures(webPreferences)
warnAboutEnableBlinkFeatures(webPreferences)
warnAboutInsecureCSP()
warnAboutAllowedPopups()
warnAboutNodeIntegrationDefault(webPreferences)
warnAboutContextIsolationDefault(webPreferences)
warnAboutDeprecatedWebviewTagDefault(webPreferences)
}
const getWebPreferences = function () {
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const errorUtils = require('@electron/internal/common/error-utils')
const [ error, result ] = ipcRenderer.sendSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
if (error) {
console.warn(`getLastWebPreferences() failed: ${errorUtils.deserialize(error)}`)
return null
} else {
return result
}
}
module.exports = function (nodeIntegration) {
const loadHandler = function () {
if (shouldLogSecurityWarnings()) {
const webPreferences = getWebPreferences()
logSecurityWarnings(webPreferences, nodeIntegration)
}
}
window.addEventListener('load', loadHandler, { once: true })
}

View File

@ -1,24 +0,0 @@
'use strict'
const { webFrame } = require('electron')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const errorUtils = require('@electron/internal/common/error-utils')
module.exports = () => {
// Call webFrame method
ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => {
webFrame[method](...args)
})
ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => {
new Promise(resolve =>
webFrame[method](...args, resolve)
).then(result => {
return [null, result]
}, error => {
return [errorUtils.serialize(error)]
}).then(responseArgs => {
event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, ...responseArgs)
})
})
}

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