Changes of Linux stable v0.0.10
This commit is contained in:
parent
9dd45e7cc0
commit
bf5b7c9222
|
@ -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}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -15,4 +15,4 @@ function installDevTools() {
|
|||
}
|
||||
|
||||
exports.default = installDevTools;
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
@ -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);
|
||||
|
|
|
@ -14,4 +14,4 @@ exports.default = [{
|
|||
accelerator: 'Command+Q'
|
||||
}]
|
||||
}];
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -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;
|
|
@ -14,4 +14,4 @@ exports.default = [{
|
|||
accelerator: 'Control+Q'
|
||||
}]
|
||||
}];
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -14,4 +14,4 @@ exports.default = [{
|
|||
accelerator: 'Alt+F4'
|
||||
}]
|
||||
}];
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -90,4 +90,4 @@ class Backoff {
|
|||
}
|
||||
}
|
||||
exports.default = Backoff;
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -26,4 +26,4 @@ class FeatureFlags {
|
|||
}
|
||||
}
|
||||
exports.default = FeatureFlags;
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -68,4 +68,4 @@ class Settings {
|
|||
}
|
||||
}
|
||||
exports.default = Settings;
|
||||
module.exports = exports['default'];
|
||||
module.exports = exports.default;
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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')
|
||||
}
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = process.atomBinding('content_tracing')
|
|
@ -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()
|
|
@ -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)
|
|
@ -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`))
|
||||
})
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = process.atomBinding('global_shortcut').globalShortcut
|
|
@ -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: () => ''
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
|
@ -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
|
|
@ -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' }
|
||||
)
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
}
|
||||
}))
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = process.atomBinding('power_save_blocker').powerSaveBlocker
|
|
@ -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 }
|
||||
}
|
||||
}))
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,8 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { Tray } = process.atomBinding('tray')
|
||||
|
||||
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype)
|
||||
|
||||
module.exports = Tray
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
})
|
|
@ -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)
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -1,11 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
const deprecate = require('electron').deprecate
|
||||
|
||||
exports.setHandler = function (deprecationHandler) {
|
||||
deprecate.setHandler(deprecationHandler)
|
||||
}
|
||||
|
||||
exports.getHandler = function () {
|
||||
return deprecate.getHandler()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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 }
|
||||
]
|
|
@ -1,3 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = process.atomBinding('native_image')
|
|
@ -1,3 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = process.atomBinding('shell')
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
])
|
|
@ -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()
|
|
@ -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
|
||||
})())
|
||||
})
|
||||
}
|
|
@ -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}`))
|
||||
})
|
||||
}
|
|
@ -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
|
|
@ -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' }
|
||||
]
|
|
@ -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)
|
|
@ -1,4 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
|
||||
module.exports = getRemoteForUsage('screen').screen
|
|
@ -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)
|
|
@ -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
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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 })
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue