From efee213eeea1ab996ecf4068f00056eec318181e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 15 Aug 2019 02:58:46 +0200 Subject: [PATCH] Changes of Linux development v0.0.68 --- .../development/app_bootstrap/bootstrap.js | 4 +- electronasar/development/browser/api/app.js | 88 ++++ .../development/browser/api/auto-updater.js | 8 + .../api/auto-updater/auto-updater-native.js | 8 + .../api/auto-updater/auto-updater-win.js | 71 +++ .../api/auto-updater/squirrel-update-win.js | 107 ++++ .../development/browser/api/browser-view.js | 13 + .../development/browser/api/browser-window.js | 171 ++++++ .../browser/api/content-tracing.js | 12 + .../development/browser/api/crash-reporter.js | 10 + .../development/browser/api/dialog.js | 184 +++++++ .../browser/api/exports/electron.js | 19 + .../browser/api/global-shortcut.js | 3 + .../browser/api/in-app-purchase.js | 22 + .../development/browser/api/ipc-main.js | 8 + .../browser/api/menu-item-roles.js | 309 +++++++++++ .../development/browser/api/menu-item.js | 74 +++ .../development/browser/api/menu-utils.js | 155 ++++++ electronasar/development/browser/api/menu.js | 263 ++++++++++ .../development/browser/api/module-list.js | 36 ++ .../development/browser/api/net-log.js | 28 + electronasar/development/browser/api/net.js | 358 +++++++++++++ .../development/browser/api/notification.js | 7 + .../development/browser/api/power-monitor.js | 43 ++ .../browser/api/power-save-blocker.js | 3 + .../development/browser/api/protocol.js | 27 + .../development/browser/api/screen.js | 8 + .../development/browser/api/session.js | 41 ++ .../browser/api/system-preferences.js | 8 + .../browser/api/top-level-window.js | 20 + .../development/browser/api/touch-bar.js | 311 +++++++++++ electronasar/development/browser/api/tray.js | 9 + electronasar/development/browser/api/view.js | 8 + .../browser/api/web-contents-view.js | 11 + .../development/browser/api/web-contents.js | 447 ++++++++++++++++ .../development/browser/chrome-extension.js | 490 ++++++++++++++++++ .../browser/crash-reporter-init.js | 23 + .../development/browser/default-menu.js | 50 ++ .../development/browser/desktop-capturer.js | 86 +++ electronasar/development/browser/devtools.js | 93 ++++ .../development/browser/guest-view-manager.js | 365 +++++++++++++ .../browser/guest-window-manager.js | 331 ++++++++++++ electronasar/development/browser/init.js | 187 +++++++ .../browser/ipc-main-internal-utils.js | 53 ++ .../development/browser/ipc-main-internal.js | 8 + .../browser/navigation-controller.js | 225 ++++++++ .../development/browser/objects-registry.js | 121 +++++ .../development/browser/rpc-server.js | 482 +++++++++++++++++ .../development/common/api/clipboard.js | 26 + .../development/common/api/deprecate.js | 187 +++++++ .../development/common/api/deprecations.js | 9 + .../common/api/exports/electron.js | 34 ++ .../development/common/api/is-promise.js | 12 + .../development/common/api/module-list.js | 12 + .../development/common/api/native-image.js | 4 + electronasar/development/common/api/shell.js | 3 + .../development/common/atom-binding-setup.js | 19 + .../development/common/buffer-utils.js | 64 +++ .../development/common/clipboard-utils.js | 50 ++ .../development/common/crash-reporter.js | 78 +++ .../development/common/error-utils.js | 37 ++ electronasar/development/common/init.js | 61 +++ .../common/parse-features-string.js | 20 + .../development/common/reset-search-paths.js | 44 ++ .../development/common/web-view-methods.js | 69 +++ .../renderer/api/crash-reporter.js | 10 + .../renderer/api/desktop-capturer.js | 38 ++ .../renderer/api/exports/electron.js | 21 + .../development/renderer/api/ipc-renderer.js | 23 + .../development/renderer/api/module-list.js | 18 + .../development/renderer/api/remote.js | 340 ++++++++++++ .../development/renderer/api/web-frame.js | 82 +++ .../renderer/callbacks-registry.js | 52 ++ .../development/renderer/chrome-api.js | 174 +++++++ .../renderer/content-scripts-injector.js | 109 ++++ .../development/renderer/extensions/event.js | 22 + .../development/renderer/extensions/i18n.js | 53 ++ .../renderer/extensions/storage.js | 88 ++++ .../renderer/extensions/web-navigation.js | 19 + electronasar/development/renderer/init.js | 178 +++++++ .../development/renderer/inspector.js | 53 ++ .../renderer/ipc-renderer-internal-utils.js | 42 ++ .../renderer/ipc-renderer-internal.js | 20 + .../development/renderer/security-warnings.js | 239 +++++++++ .../development/renderer/web-frame-init.js | 14 + .../renderer/web-view/guest-view-internal.js | 111 ++++ .../renderer/web-view/web-view-attributes.js | 251 +++++++++ .../renderer/web-view/web-view-constants.js | 3 + .../renderer/web-view/web-view-element.js | 102 ++++ .../renderer/web-view/web-view-impl.js | 233 +++++++++ .../renderer/web-view/web-view-init.js | 33 ++ .../development/renderer/window-setup.js | 247 +++++++++ electronasar/development/worker/init.js | 30 ++ 93 files changed, 8741 insertions(+), 1 deletion(-) create mode 100644 electronasar/development/browser/api/app.js create mode 100644 electronasar/development/browser/api/auto-updater.js create mode 100644 electronasar/development/browser/api/auto-updater/auto-updater-native.js create mode 100644 electronasar/development/browser/api/auto-updater/auto-updater-win.js create mode 100644 electronasar/development/browser/api/auto-updater/squirrel-update-win.js create mode 100644 electronasar/development/browser/api/browser-view.js create mode 100644 electronasar/development/browser/api/browser-window.js create mode 100644 electronasar/development/browser/api/content-tracing.js create mode 100644 electronasar/development/browser/api/crash-reporter.js create mode 100644 electronasar/development/browser/api/dialog.js create mode 100644 electronasar/development/browser/api/exports/electron.js create mode 100644 electronasar/development/browser/api/global-shortcut.js create mode 100644 electronasar/development/browser/api/in-app-purchase.js create mode 100644 electronasar/development/browser/api/ipc-main.js create mode 100644 electronasar/development/browser/api/menu-item-roles.js create mode 100644 electronasar/development/browser/api/menu-item.js create mode 100644 electronasar/development/browser/api/menu-utils.js create mode 100644 electronasar/development/browser/api/menu.js create mode 100644 electronasar/development/browser/api/module-list.js create mode 100644 electronasar/development/browser/api/net-log.js create mode 100644 electronasar/development/browser/api/net.js create mode 100644 electronasar/development/browser/api/notification.js create mode 100644 electronasar/development/browser/api/power-monitor.js create mode 100644 electronasar/development/browser/api/power-save-blocker.js create mode 100644 electronasar/development/browser/api/protocol.js create mode 100644 electronasar/development/browser/api/screen.js create mode 100644 electronasar/development/browser/api/session.js create mode 100644 electronasar/development/browser/api/system-preferences.js create mode 100644 electronasar/development/browser/api/top-level-window.js create mode 100644 electronasar/development/browser/api/touch-bar.js create mode 100644 electronasar/development/browser/api/tray.js create mode 100644 electronasar/development/browser/api/view.js create mode 100644 electronasar/development/browser/api/web-contents-view.js create mode 100644 electronasar/development/browser/api/web-contents.js create mode 100644 electronasar/development/browser/chrome-extension.js create mode 100644 electronasar/development/browser/crash-reporter-init.js create mode 100644 electronasar/development/browser/default-menu.js create mode 100644 electronasar/development/browser/desktop-capturer.js create mode 100644 electronasar/development/browser/devtools.js create mode 100644 electronasar/development/browser/guest-view-manager.js create mode 100644 electronasar/development/browser/guest-window-manager.js create mode 100644 electronasar/development/browser/init.js create mode 100644 electronasar/development/browser/ipc-main-internal-utils.js create mode 100644 electronasar/development/browser/ipc-main-internal.js create mode 100644 electronasar/development/browser/navigation-controller.js create mode 100644 electronasar/development/browser/objects-registry.js create mode 100644 electronasar/development/browser/rpc-server.js create mode 100644 electronasar/development/common/api/clipboard.js create mode 100644 electronasar/development/common/api/deprecate.js create mode 100644 electronasar/development/common/api/deprecations.js create mode 100644 electronasar/development/common/api/exports/electron.js create mode 100644 electronasar/development/common/api/is-promise.js create mode 100644 electronasar/development/common/api/module-list.js create mode 100644 electronasar/development/common/api/native-image.js create mode 100644 electronasar/development/common/api/shell.js create mode 100644 electronasar/development/common/atom-binding-setup.js create mode 100644 electronasar/development/common/buffer-utils.js create mode 100644 electronasar/development/common/clipboard-utils.js create mode 100644 electronasar/development/common/crash-reporter.js create mode 100644 electronasar/development/common/error-utils.js create mode 100644 electronasar/development/common/init.js create mode 100644 electronasar/development/common/parse-features-string.js create mode 100644 electronasar/development/common/reset-search-paths.js create mode 100644 electronasar/development/common/web-view-methods.js create mode 100644 electronasar/development/renderer/api/crash-reporter.js create mode 100644 electronasar/development/renderer/api/desktop-capturer.js create mode 100644 electronasar/development/renderer/api/exports/electron.js create mode 100644 electronasar/development/renderer/api/ipc-renderer.js create mode 100644 electronasar/development/renderer/api/module-list.js create mode 100644 electronasar/development/renderer/api/remote.js create mode 100644 electronasar/development/renderer/api/web-frame.js create mode 100644 electronasar/development/renderer/callbacks-registry.js create mode 100644 electronasar/development/renderer/chrome-api.js create mode 100644 electronasar/development/renderer/content-scripts-injector.js create mode 100644 electronasar/development/renderer/extensions/event.js create mode 100644 electronasar/development/renderer/extensions/i18n.js create mode 100644 electronasar/development/renderer/extensions/storage.js create mode 100644 electronasar/development/renderer/extensions/web-navigation.js create mode 100644 electronasar/development/renderer/init.js create mode 100644 electronasar/development/renderer/inspector.js create mode 100644 electronasar/development/renderer/ipc-renderer-internal-utils.js create mode 100644 electronasar/development/renderer/ipc-renderer-internal.js create mode 100644 electronasar/development/renderer/security-warnings.js create mode 100644 electronasar/development/renderer/web-frame-init.js create mode 100644 electronasar/development/renderer/web-view/guest-view-internal.js create mode 100644 electronasar/development/renderer/web-view/web-view-attributes.js create mode 100644 electronasar/development/renderer/web-view/web-view-constants.js create mode 100644 electronasar/development/renderer/web-view/web-view-element.js create mode 100644 electronasar/development/renderer/web-view/web-view-impl.js create mode 100644 electronasar/development/renderer/web-view/web-view-init.js create mode 100644 electronasar/development/renderer/window-setup.js create mode 100644 electronasar/development/worker/init.js diff --git a/appasar/development/app_bootstrap/bootstrap.js b/appasar/development/app_bootstrap/bootstrap.js index 252b489..2083fb4 100644 --- a/appasar/development/app_bootstrap/bootstrap.js +++ b/appasar/development/app_bootstrap/bootstrap.js @@ -37,8 +37,10 @@ const GPUSettings = require('./GPUSettings'); 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 (!settings.get('enableHardwareAcceleration', true)) { + if (!allowed || !settings.get('enableHardwareAcceleration', true)) { app.disableHardwareAcceleration(); } } diff --git a/electronasar/development/browser/api/app.js b/electronasar/development/browser/api/app.js new file mode 100644 index 0000000..e7d2017 --- /dev/null +++ b/electronasar/development/browser/api/app.js @@ -0,0 +1,88 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const electron = require("electron"); +const events_1 = require("events"); +const bindings = process.electronBinding('app'); +const commandLine = process.electronBinding('command_line'); +const { app, App } = bindings; +// Only one app object permitted. +exports.default = app; +const { deprecate, Menu } = electron; +let dockMenu = null; +// App is an EventEmitter. +Object.setPrototypeOf(App.prototype, events_1.EventEmitter.prototype); +events_1.EventEmitter.call(app); +Object.assign(app, { + // TODO(codebytere): remove in 7.0 + setApplicationMenu(menu) { + return Menu.setApplicationMenu(menu); + }, + // TODO(codebytere): remove in 7.0 + getApplicationMenu() { + return Menu.getApplicationMenu(); + }, + commandLine: { + hasSwitch: (theSwitch) => commandLine.hasSwitch(String(theSwitch)), + getSwitchValue: (theSwitch) => commandLine.getSwitchValue(String(theSwitch)), + appendSwitch: (theSwitch, value) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)), + appendArgument: (arg) => commandLine.appendArgument(String(arg)) + }, + enableMixedSandbox() { + deprecate.log(`'enableMixedSandbox' is deprecated. Mixed-sandbox mode is now enabled by default. You can safely remove the call to enableMixedSandbox().`); + } +}); +// we define this here because it'd be overly complicated to +// do in native land +Object.defineProperty(app, 'applicationMenu', { + get() { + return Menu.getApplicationMenu(); + }, + set(menu) { + return Menu.setApplicationMenu(menu); + } +}); +app.isPackaged = (() => { + const execFile = path.basename(process.execPath).toLowerCase(); + if (process.platform === 'win32') { + return execFile !== 'electron.exe'; + } + return execFile !== 'electron'; +})(); +app._setDefaultAppPaths = (packagePath) => { + // Set the user path according to application's name. + app.setPath('userData', path.join(app.getPath('appData'), app.getName())); + app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); + app.setAppPath(packagePath); + // Add support for --user-data-dir= + const userDataDirFlag = '--user-data-dir='; + const userDataArg = process.argv.find(arg => arg.startsWith(userDataDirFlag)); + if (userDataArg) { + const userDataDir = userDataArg.substr(userDataDirFlag.length); + if (path.isAbsolute(userDataDir)) + app.setPath('userData', userDataDir); + } +}; +if (process.platform === 'darwin') { + const setDockMenu = app.dock.setMenu; + app.dock.setMenu = (menu) => { + dockMenu = menu; + setDockMenu(menu); + }; + app.dock.getMenu = () => dockMenu; +} +// 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); + }); +} +// Function Deprecations +app.getFileIcon = deprecate.promisify(app.getFileIcon); +// Property Deprecations +deprecate.fnToProperty(app, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled'); +// Wrappers for native classes. +const { DownloadItem } = process.electronBinding('download_item'); +Object.setPrototypeOf(DownloadItem.prototype, events_1.EventEmitter.prototype); +//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/auto-updater.js b/electronasar/development/browser/api/auto-updater.js new file mode 100644 index 0000000..cbd3003 --- /dev/null +++ b/electronasar/development/browser/api/auto-updater.js @@ -0,0 +1,8 @@ +'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'); +} +//# sourceMappingURL=auto-updater.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/auto-updater/auto-updater-native.js b/electronasar/development/browser/api/auto-updater/auto-updater-native.js new file mode 100644 index 0000000..4b34107 --- /dev/null +++ b/electronasar/development/browser/api/auto-updater/auto-updater-native.js @@ -0,0 +1,8 @@ +'use strict'; +const EventEmitter = require('events').EventEmitter; +const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater'); +// AutoUpdater is an EventEmitter. +Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype); +EventEmitter.call(autoUpdater); +module.exports = autoUpdater; +//# sourceMappingURL=auto-updater-native.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/auto-updater/auto-updater-win.js b/electronasar/development/browser/api/auto-updater/auto-updater-win.js new file mode 100644 index 0000000..177b630 --- /dev/null +++ b/electronasar/development/browser/api/auto-updater/auto-updater-win.js @@ -0,0 +1,71 @@ +'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(); +//# sourceMappingURL=auto-updater-win.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/auto-updater/squirrel-update-win.js b/electronasar/development/browser/api/auto-updater/squirrel-update-win.js new file mode 100644 index 0000000..97a3230 --- /dev/null +++ b/electronasar/development/browser/api/auto-updater/squirrel-update-win.js @@ -0,0 +1,107 @@ +'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 (_a) { + // Disabled for backwards compatibility: + // eslint-disable-next-line standard/no-callback-literal + return callback(`Invalid result:\n${stdout}`); + } + return callback(null, update); + }); +}; +// Update the application to the latest remote version specified by URL. +exports.update = function (updateURL, callback) { + return spawnUpdate(['--update', updateURL], false, callback); +}; +// Is the Update.exe installed with the current application? +exports.supported = function () { + try { + fs.accessSync(updateExe, fs.R_OK); + return true; + } + catch (_a) { + return false; + } +}; +//# sourceMappingURL=squirrel-update-win.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/browser-view.js b/electronasar/development/browser/api/browser-view.js new file mode 100644 index 0000000..56f62c6 --- /dev/null +++ b/electronasar/development/browser/api/browser-view.js @@ -0,0 +1,13 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { BrowserView } = process.electronBinding('browser_view'); +Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype); +BrowserView.fromWebContents = (webContents) => { + for (const view of BrowserView.getAllViews()) { + if (view.webContents.equal(webContents)) + return view; + } + return null; +}; +module.exports = BrowserView; +//# sourceMappingURL=browser-view.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/browser-window.js b/electronasar/development/browser/api/browser-window.js new file mode 100644 index 0000000..3927adc --- /dev/null +++ b/electronasar/development/browser/api/browser-window.js @@ -0,0 +1,171 @@ +'use strict'; +const electron = require('electron'); +const { WebContentsView, TopLevelWindow } = electron; +const { BrowserWindow } = process.electronBinding('window'); +Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype); +BrowserWindow.prototype._init = function () { + // Call parent class's _init. + TopLevelWindow.prototype._init.call(this); + // Avoid recursive require. + const { app } = electron; + // Create WebContentsView. + this.setContentView(new WebContentsView(this.webContents)); + const nativeSetBounds = this.setBounds; + this.setBounds = (bounds, ...opts) => { + bounds = Object.assign({}, this.getBounds(), bounds); + nativeSetBounds.call(this, bounds, ...opts); + }; + // window.resizeTo(...) + // window.moveTo(...) + this.webContents.on('move', (event, size) => { + this.setBounds(size); + }); + // Hide the auto-hide menu when webContents is focused. + this.webContents.on('activate', () => { + if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) { + this.setMenuBarVisibility(false); + } + }); + // Change window title to page title. + this.webContents.on('page-title-updated', (event, title, ...args) => { + // Route the event to BrowserWindow. + this.emit('page-title-updated', event, title, ...args); + if (!this.isDestroyed() && !event.defaultPrevented) + this.setTitle(title); + }); + // Sometimes the webContents doesn't get focus when window is shown, so we + // have to force focusing on webContents in this case. The safest way is to + // focus it when we first start to load URL, if we do it earlier it won't + // have effect, if we do it later we might move focus in the page. + // + // Though this hack is only needed on macOS when the app is launched from + // Finder, we still do it on all platforms in case of other bugs we don't + // know. + this.webContents.once('load-url', function () { + this.focus(); + }); + // Redirect focus/blur event to app instance too. + this.on('blur', (event) => { + app.emit('browser-window-blur', event, this); + }); + this.on('focus', (event) => { + app.emit('browser-window-focus', event, this); + }); + // Subscribe to visibilityState changes and pass to renderer process. + let isVisible = this.isVisible() && !this.isMinimized(); + const visibilityChanged = () => { + const newState = this.isVisible() && !this.isMinimized(); + if (isVisible !== newState) { + isVisible = newState; + const visibilityState = isVisible ? 'visible' : 'hidden'; + this.webContents.emit('-window-visibility-change', visibilityState); + } + }; + const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']; + for (const event of visibilityEvents) { + this.on(event, visibilityChanged); + } + // 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); + }, + inspectSharedWorker() { + return this.webContents.inspectSharedWorker(); + }, + inspectServiceWorker() { + return this.webContents.inspectServiceWorker(); + }, + showDefinitionForSelection() { + return this.webContents.showDefinitionForSelection(); + }, + capturePage(...args) { + return this.webContents.capturePage(...args); + }, + setTouchBar(touchBar) { + electron.TouchBar._setOnWindow(touchBar, this); + }, + setBackgroundThrottling(allowed) { + this.webContents.setBackgroundThrottling(allowed); + } +}); +module.exports = BrowserWindow; +//# sourceMappingURL=browser-window.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/content-tracing.js b/electronasar/development/browser/api/content-tracing.js new file mode 100644 index 0000000..9e1682c --- /dev/null +++ b/electronasar/development/browser/api/content-tracing.js @@ -0,0 +1,12 @@ +'use strict'; +const { deprecate } = require('electron'); +const contentTracing = process.electronBinding('content_tracing'); +contentTracing.getCategories = deprecate.promisify(contentTracing.getCategories); +contentTracing.startRecording = deprecate.promisify(contentTracing.startRecording); +contentTracing.stopRecording = deprecate.promisify(contentTracing.stopRecording); +contentTracing.getTraceBufferUsage = deprecate.promisifyMultiArg(contentTracing.getTraceBufferUsage +// convertPromiseValue: Temporarily disabled until it's used +/* (value) => [value.paths, value.bookmarks] */ +); +module.exports = contentTracing; +//# sourceMappingURL=content-tracing.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/crash-reporter.js b/electronasar/development/browser/api/crash-reporter.js new file mode 100644 index 0000000..988c0d1 --- /dev/null +++ b/electronasar/development/browser/api/crash-reporter.js @@ -0,0 +1,10 @@ +'use strict'; +const CrashReporter = require('@electron/internal/common/crash-reporter'); +const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init'); +class CrashReporterMain extends CrashReporter { + init(options) { + return crashReporterInit(options); + } +} +module.exports = new CrashReporterMain(); +//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/dialog.js b/electronasar/development/browser/api/dialog.js new file mode 100644 index 0000000..3e5d83d --- /dev/null +++ b/electronasar/development/browser/api/dialog.js @@ -0,0 +1,184 @@ +'use strict'; +const { app, BrowserWindow, deprecate } = require('electron'); +const binding = process.electronBinding('dialog'); +const v8Util = process.electronBinding('v8_util'); +const fileDialogProperties = { + openFile: 1 << 0, + openDirectory: 1 << 1, + multiSelections: 1 << 2, + createDirectory: 1 << 3, + showHiddenFiles: 1 << 4, + promptToCreate: 1 << 5, + noResolveAliases: 1 << 6, + treatPackageAsDirectory: 1 << 7 +}; +const 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'); + } +}; +const saveDialog = (sync, window, options) => { + checkAppInitialized(); + if (window && window.constructor !== BrowserWindow) { + options = window; + window = null; + } + if (options == null) + options = { title: 'Save' }; + const { buttonLabel = '', defaultPath = '', filters = [], title = '', message = '', securityScopedBookmarks = false, nameFieldLabel = '', showsTagField = true } = options; + if (typeof title !== 'string') + throw new TypeError('Title must be a string'); + if (typeof buttonLabel !== 'string') + throw new TypeError('Button label must be a string'); + if (typeof defaultPath !== 'string') + throw new TypeError('Default path must be a string'); + if (typeof message !== 'string') + throw new TypeError('Message must be a string'); + if (typeof nameFieldLabel !== 'string') + throw new TypeError('Name field label must be a string'); + const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window }; + return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings); +}; +const openDialog = (sync, window, options) => { + checkAppInitialized(); + if (window && window.constructor !== BrowserWindow) { + options = window; + window = null; + } + if (options == null) { + options = { + title: 'Open', + properties: ['openFile'] + }; + } + const { buttonLabel = '', defaultPath = '', filters = [], properties = ['openFile'], title = '', message = '', securityScopedBookmarks = false } = options; + if (!Array.isArray(properties)) + throw new TypeError('Properties must be an array'); + let dialogProperties = 0; + for (const prop in fileDialogProperties) { + if (properties.includes(prop)) { + dialogProperties |= fileDialogProperties[prop]; + } + } + if (typeof title !== 'string') + throw new TypeError('Title must be a string'); + if (typeof buttonLabel !== 'string') + throw new TypeError('Button label must be a string'); + if (typeof defaultPath !== 'string') + throw new TypeError('Default path must be a string'); + if (typeof message !== 'string') + throw new TypeError('Message must be a string'); + const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window }; + settings.properties = dialogProperties; + return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings); +}; +const messageBox = (sync, window, options) => { + checkAppInitialized(); + if (window && window.constructor !== BrowserWindow) { + options = window; + window = null; + } + if (options == null) + options = { type: 'none' }; + const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']; + const messageBoxOptions = { noLink: 1 << 0 }; + let { buttons = [], cancelId, checkboxLabel = '', checkboxChecked, defaultId = -1, detail = '', icon = null, message = '', title = '', type = 'none' } = options; + const messageBoxType = messageBoxTypes.indexOf(type); + if (messageBoxType === -1) + throw new TypeError('Invalid message box type'); + if (!Array.isArray(buttons)) + throw new TypeError('Buttons must be an array'); + if (options.normalizeAccessKeys) + buttons = buttons.map(normalizeAccessKey); + if (typeof title !== 'string') + throw new TypeError('Title must be a string'); + if (typeof message !== 'string') + throw new TypeError('Message must be a string'); + if (typeof detail !== 'string') + throw new TypeError('Detail must be a string'); + if (typeof checkboxLabel !== 'string') + throw new TypeError('checkboxLabel must be a string'); + checkboxChecked = !!checkboxChecked; + // Choose a default button to get selected when dialog is cancelled. + if (cancelId == null) { + // If the defaultId is set to 0, ensure the cancel button is a different index (1) + cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0; + for (let i = 0; i < buttons.length; i++) { + const text = buttons[i].toLowerCase(); + if (text === 'cancel' || text === 'no') { + cancelId = i; + break; + } + } + } + const flags = options.noLink ? messageBoxOptions.noLink : 0; + if (sync) { + return binding.showMessageBoxSync(messageBoxType, buttons, defaultId, cancelId, flags, title, message, detail, checkboxLabel, checkboxChecked, icon, window); + } + else { + return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, flags, title, message, detail, checkboxLabel, checkboxChecked, icon, window); + } +}; +module.exports = { + showOpenDialog: function (window, options) { + return openDialog(false, window, options); + }, + showOpenDialogSync: function (window, options) { + return openDialog(true, window, options); + }, + showSaveDialog: function (window, options) { + return saveDialog(false, window, options); + }, + showSaveDialogSync: function (window, options) { + return saveDialog(true, window, options); + }, + showMessageBox: function (window, options) { + return messageBox(false, window, options); + }, + showMessageBoxSync: function (window, options) { + return messageBox(true, window, options); + }, + showErrorBox: function (...args) { + return binding.showErrorBox(...args); + }, + showCertificateTrustDialog: function (window, options) { + if (window && window.constructor !== BrowserWindow) + options = window; + if (options == null || typeof options !== 'object') { + throw new TypeError('options must be an object'); + } + const { certificate, message = '' } = options; + if (certificate == null || typeof certificate !== 'object') { + throw new TypeError('certificate must be an object'); + } + if (typeof message !== 'string') + throw new TypeError('message must be a string'); + return binding.showCertificateTrustDialog(window, certificate, message); + } +}; +module.exports.showMessageBox = deprecate.promisifyMultiArg(module.exports.showMessageBox, ({ response, checkboxChecked }) => [response, checkboxChecked]); +module.exports.showOpenDialog = deprecate.promisifyMultiArg(module.exports.showOpenDialog, ({ filePaths, bookmarks }) => [filePaths, bookmarks]); +module.exports.showSaveDialog = deprecate.promisifyMultiArg(module.exports.showSaveDialog, ({ filePath, bookmarks }) => [filePath, bookmarks]); +module.exports.showCertificateTrustDialog = deprecate.promisify(module.exports.showCertificateTrustDialog); +//# sourceMappingURL=dialog.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/exports/electron.js b/electronasar/development/browser/api/exports/electron.js new file mode 100644 index 0000000..8c94396 --- /dev/null +++ b/electronasar/development/browser/api/exports/electron.js @@ -0,0 +1,19 @@ +'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(() => { + const value = require(`@electron/internal/browser/api/${module.file}.js`); + // Handle Typescript modules with an "export default X" statement + if (value.__esModule) + return value.default; + return value; + }) + }); +} +//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/global-shortcut.js b/electronasar/development/browser/api/global-shortcut.js new file mode 100644 index 0000000..834e35b --- /dev/null +++ b/electronasar/development/browser/api/global-shortcut.js @@ -0,0 +1,3 @@ +'use strict'; +module.exports = process.electronBinding('global_shortcut').globalShortcut; +//# sourceMappingURL=global-shortcut.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/in-app-purchase.js b/electronasar/development/browser/api/in-app-purchase.js new file mode 100644 index 0000000..8a6d71e --- /dev/null +++ b/electronasar/development/browser/api/in-app-purchase.js @@ -0,0 +1,22 @@ +'use strict'; +const { deprecate } = require('electron'); +if (process.platform === 'darwin') { + const { EventEmitter } = require('events'); + const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase'); + // inAppPurchase is an EventEmitter. + Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype); + EventEmitter.call(inAppPurchase); + module.exports = inAppPurchase; +} +else { + module.exports = { + purchaseProduct: (productID, quantity, callback) => { + throw new Error('The inAppPurchase module can only be used on macOS'); + }, + canMakePayments: () => false, + getReceiptURL: () => '' + }; +} +module.exports.purchaseProduct = deprecate.promisify(module.exports.purchaseProduct); +module.exports.getProducts = deprecate.promisify(module.exports.getProducts); +//# sourceMappingURL=in-app-purchase.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/ipc-main.js b/electronasar/development/browser/api/ipc-main.js new file mode 100644 index 0000000..b82b602 --- /dev/null +++ b/electronasar/development/browser/api/ipc-main.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = require("events"); +const emitter = new events_1.EventEmitter(); +// Do not throw exception when channel name is "error". +emitter.on('error', () => { }); +exports.default = emitter; +//# sourceMappingURL=ipc-main.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/menu-item-roles.js b/electronasar/development/browser/api/menu-item-roles.js new file mode 100644 index 0000000..b9ea0ce --- /dev/null +++ b/electronasar/development/browser/api/menu-item-roles.js @@ -0,0 +1,309 @@ +'use strict'; +const { app } = require('electron'); +const isMac = process.platform === 'darwin'; +const isWindows = process.platform === 'win32'; +const isLinux = process.platform === 'linux'; +const roles = { + about: { + get label() { + return isLinux ? 'About' : `About ${app.getName()}`; + } + }, + close: { + label: isMac ? '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: isWindows ? null : 'CommandOrControl+Q', + appMethod: 'quit' + }, + redo: { + label: 'Redo', + accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z', + webContentsMethod: 'redo' + }, + reload: { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + nonNativeMacOSRole: true, + windowMethod: 'reload' + }, + resetzoom: { + label: 'Actual Size', + accelerator: 'CommandOrControl+0', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + webContents.setZoomLevel(0); + } + }, + selectall: { + label: 'Select All', + accelerator: 'CommandOrControl+A', + webContentsMethod: 'selectAll' + }, + services: { + label: 'Services' + }, + recentdocuments: { + label: 'Open Recent' + }, + clearrecentdocuments: { + label: 'Clear Menu' + }, + startspeaking: { + label: 'Start Speaking' + }, + stopspeaking: { + label: 'Stop Speaking' + }, + toggledevtools: { + label: 'Toggle Developer Tools', + accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I', + nonNativeMacOSRole: true, + windowMethod: 'toggleDevTools' + }, + togglefullscreen: { + label: 'Toggle Full Screen', + accelerator: isMac ? 'Control+Command+F' : 'F11', + windowMethod: (window) => { + window.setFullScreen(!window.isFullScreen()); + } + }, + undo: { + label: 'Undo', + accelerator: 'CommandOrControl+Z', + webContentsMethod: 'undo' + }, + unhide: { + label: 'Show All' + }, + window: { + label: 'Window' + }, + zoom: { + label: 'Zoom' + }, + zoomin: { + label: 'Zoom In', + accelerator: 'CommandOrControl+Plus', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + const zoomLevel = webContents.getZoomLevel(); + webContents.setZoomLevel(zoomLevel + 0.5); + } + }, + zoomout: { + label: 'Zoom Out', + accelerator: 'CommandOrControl+-', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + const zoomLevel = webContents.getZoomLevel(); + webContents.setZoomLevel(zoomLevel - 0.5); + } + }, + // App submenu should be used for Mac only + appmenu: { + get label() { + return app.getName(); + }, + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }, + // File submenu + filemenu: { + label: 'File', + submenu: [ + isMac ? { role: 'close' } : { role: 'quit' } + ] + }, + // Edit submenu + editmenu: { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + ...(isMac ? [ + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + { type: 'separator' }, + { + label: 'Speech', + submenu: [ + { role: 'startSpeaking' }, + { role: 'stopSpeaking' } + ] + } + ] : [ + { role: 'delete' }, + { type: 'separator' }, + { role: 'selectAll' } + ]) + ] + }, + // View submenu + viewmenu: { + label: 'View', + submenu: [ + { role: 'reload' }, + { role: 'forceReload' }, + { role: 'toggleDevTools' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + { type: 'separator' }, + { role: 'togglefullscreen' } + ] + }, + // Window submenu + windowmenu: { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(isMac ? [ + { type: 'separator' }, + { role: 'front' } + ] : [ + { role: 'close' } + ]) + ] + } +}; +const canExecuteRole = (role) => { + if (!roles.hasOwnProperty(role)) + return false; + if (!isMac) + return true; + // macOS handles all roles natively except for a few + return roles[role].nonNativeMacOSRole; +}; +exports.getDefaultLabel = (role) => { + return roles.hasOwnProperty(role) ? roles[role].label : ''; +}; +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; +}; +//# sourceMappingURL=menu-item-roles.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/menu-item.js b/electronasar/development/browser/api/menu-item.js new file mode 100644 index 0000000..244621f --- /dev/null +++ b/electronasar/development/browser/api/menu-item.js @@ -0,0 +1,74 @@ +'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('acceleratorWorksWhenHidden', true); + this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role)); + if (!MenuItem.types.includes(this.type)) { + throw new Error(`Unknown menu item type: ${this.type}`); + } + this.overrideReadOnlyProperty('commandId', ++nextCommandId); + const click = options.click; + this.click = (event, focusedWindow, focusedWebContents) => { + // Manually flip the checked flags when clicked. + if (this.type === 'checkbox' || this.type === 'radio') { + this.checked = !this.checked; + } + if (!roles.execute(this.role, focusedWindow, focusedWebContents)) { + if (typeof click === 'function') { + click(this, focusedWindow, event); + } + else if (typeof this.selector === 'string' && process.platform === 'darwin') { + Menu.sendActionToFirstResponder(this.selector); + } + } + }; +}; +MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']; +MenuItem.prototype.getDefaultRoleAccelerator = function () { + return roles.getDefaultAccelerator(this.role); +}; +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; +//# sourceMappingURL=menu-item.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/menu-utils.js b/electronasar/development/browser/api/menu-utils.js new file mode 100644 index 0000000..67f856e --- /dev/null +++ b/electronasar/development/browser/api/menu-utils.js @@ -0,0 +1,155 @@ +'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 }; +//# sourceMappingURL=menu-utils.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/menu.js b/electronasar/development/browser/api/menu.js new file mode 100644 index 0000000..ad0b387 --- /dev/null +++ b/electronasar/development/browser/api/menu.js @@ -0,0 +1,263 @@ +'use strict'; +const { TopLevelWindow, MenuItem, webContents } = require('electron'); +const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils'); +const EventEmitter = require('events').EventEmitter; +const v8Util = process.electronBinding('v8_util'); +const bindings = process.electronBinding('menu'); +const { Menu } = bindings; +let applicationMenu = null; +let groupIdIndex = 0; +Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype); +// Menu Delegate. +// This object should hold no reference to |Menu| to avoid cyclic reference. +const delegate = { + isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined, + isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined, + shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined, + isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, + getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { + const command = menu.commandsMap[id]; + if (!command) + return; + if (command.accelerator != null) + return command.accelerator; + if (useDefaultAccelerator) + return command.getDefaultRoleAccelerator(); + }, + shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined, + executeCommand: (menu, event, id) => { + const command = menu.commandsMap[id]; + if (!command) + return; + command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents()); + }, + menuWillShow: (menu) => { + // Ensure radio groups have at least one menu item selected + for (const id in menu.groupsMap) { + const found = menu.groupsMap[id].find(item => item.checked) || null; + if (!found) + v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true); + } + } +}; +/* Instance Methods */ +Menu.prototype._init = function () { + this.commandsMap = {}; + this.groupsMap = {}; + this.items = []; + this.delegate = delegate; +}; +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'); + } + if (pos < 0) { + throw new RangeError(`Position ${pos} cannot be less than 0`); + } + else if (pos > this.getItemCount()) { + throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`); + } + // insert item depending on its type + insertItemByType.call(this, item, pos); + // set item properties + if (item.sublabel) + this.setSublabel(pos, item.sublabel); + if (item.icon) + this.setIcon(pos, item.icon); + if (item.role) + this.setRole(pos, item.role); + // Make menu accessable to items. + item.overrideReadOnlyProperty('menu', this); + // Remember the items. + this.items.splice(pos, 0, item); + this.commandsMap[item.commandId] = item; +}; +Menu.prototype._callMenuWillShow = function () { + if (this.delegate) + this.delegate.menuWillShow(this); + this.items.forEach(item => { + if (item.submenu) + item.submenu._callMenuWillShow(); + }); +}; +/* 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; + v8Util.setHiddenValue(global, 'applicationMenuSet', true); + if (process.platform === 'darwin') { + if (!menu) + return; + menu._callMenuWillShow(); + bindings.setApplicationMenu(menu); + } + else { + const windows = TopLevelWindow.getAllWindows(); + return windows.map(w => w.setMenu(menu)); + } +}; +Menu.buildFromTemplate = function (template) { + if (!Array.isArray(template)) { + throw new TypeError('Invalid template for Menu: Menu template must be an array'); + } + 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); + const menu = new Menu(); + sorted.forEach(item => { + if (item instanceof MenuItem) { + menu.append(item); + } + else { + menu.append(new MenuItem(item)); + } + }); + return menu; +}; +/* Helper Functions */ +// validate the template against having the wrong attribute +function areValidTemplateItems(template) { + return template.every(item => item != null && + typeof item === 'object' && + (item.hasOwnProperty('label') || + item.hasOwnProperty('role') || + item.type === 'separator')); +} +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; +//# sourceMappingURL=menu.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/module-list.js b/electronasar/development/browser/api/module-list.js new file mode 100644 index 0000000..d54bbf7 --- /dev/null +++ b/electronasar/development/browser/api/module-list.js @@ -0,0 +1,36 @@ +'use strict'; +const features = process.electronBinding('features'); +// Browser side modules, please sort alphabetically. +module.exports = [ + { name: 'app', file: 'app' }, + { name: 'autoUpdater', file: 'auto-updater' }, + { name: 'BrowserView', file: 'browser-view' }, + { name: 'BrowserWindow', file: 'browser-window' }, + { name: 'contentTracing', file: 'content-tracing' }, + { name: 'crashReporter', file: 'crash-reporter' }, + { name: 'dialog', file: 'dialog' }, + { name: 'globalShortcut', file: 'global-shortcut' }, + { name: 'ipcMain', file: 'ipc-main' }, + { name: 'inAppPurchase', file: 'in-app-purchase' }, + { name: 'Menu', file: 'menu' }, + { name: 'MenuItem', file: 'menu-item' }, + { name: 'net', file: 'net' }, + { name: 'netLog', file: 'net-log' }, + { name: 'Notification', file: 'notification' }, + { name: 'powerMonitor', file: 'power-monitor' }, + { name: 'powerSaveBlocker', file: 'power-save-blocker' }, + { name: 'protocol', file: 'protocol' }, + { name: 'screen', file: 'screen' }, + { name: 'session', file: 'session' }, + { name: 'systemPreferences', file: 'system-preferences' }, + { name: 'TopLevelWindow', file: 'top-level-window' }, + { name: 'TouchBar', file: 'touch-bar' }, + { name: 'Tray', file: 'tray' }, + { name: 'View', file: 'view' }, + { name: 'webContents', file: 'web-contents' }, + { name: 'WebContentsView', file: 'web-contents-view' } +]; +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: 'MdTextButton', file: 'views/md-text-button' }, { name: 'ResizeArea', file: 'views/resize-area' }, { name: 'TextField', file: 'views/text-field' }); +} +//# sourceMappingURL=module-list.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/net-log.js b/electronasar/development/browser/api/net-log.js new file mode 100644 index 0000000..5f9f73d --- /dev/null +++ b/electronasar/development/browser/api/net-log.js @@ -0,0 +1,28 @@ +'use strict'; +// TODO(deepak1556): Deprecate and remove standalone netLog module, +// it is now a property of session module. +const { app, session } = require('electron'); +// Fallback to default session. +Object.setPrototypeOf(module.exports, new Proxy({}, { + get(target, property) { + if (!app.isReady()) + return; + const netLog = session.defaultSession.netLog; + if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) + return; + // check for properties on the prototype chain that aren't functions + if (typeof netLog[property] !== 'function') + return netLog[property]; + // Returning a native function directly would throw error. + return (...args) => netLog[property](...args); + }, + ownKeys() { + if (!app.isReady()) + return []; + return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog)); + }, + getOwnPropertyDescriptor(target) { + return { configurable: true, enumerable: true }; + } +})); +//# sourceMappingURL=net-log.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/net.js b/electronasar/development/browser/api/net.js new file mode 100644 index 0000000..6f2dbb1 --- /dev/null +++ b/electronasar/development/browser/api/net.js @@ -0,0 +1,358 @@ +'use strict'; +const url = require('url'); +const { EventEmitter } = require('events'); +const { Readable } = require('stream'); +const { app } = require('electron'); +const { Session } = process.electronBinding('session'); +const { net, Net } = process.electronBinding('net'); +const { URLRequest } = net; +// Net is an EventEmitter. +Object.setPrototypeOf(Net.prototype, EventEmitter.prototype); +EventEmitter.call(net); +Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype); +const kSupportedProtocols = new Set(['http:', 'https:']); +// set of headers that Node.js discards duplicates for +// see https://nodejs.org/api/http.html#http_message_headers +const discardableDuplicateHeaders = new Set([ + 'content-type', + 'content-length', + 'user-agent', + 'referer', + 'host', + 'authorization', + 'proxy-authorization', + 'if-modified-since', + 'if-unmodified-since', + 'from', + 'location', + 'max-forwards', + 'retry-after', + 'etag', + 'last-modified', + 'server', + 'age', + 'expires' +]); +class IncomingMessage extends Readable { + constructor(urlRequest) { + super(); + this.urlRequest = urlRequest; + this.shouldPush = false; + this.data = []; + this.urlRequest.on('data', (event, chunk) => { + this._storeInternalData(chunk); + this._pushInternalData(); + }); + this.urlRequest.on('end', () => { + this._storeInternalData(null); + this._pushInternalData(); + }); + } + get statusCode() { + return this.urlRequest.statusCode; + } + get statusMessage() { + return this.urlRequest.statusMessage; + } + get headers() { + const filteredHeaders = {}; + const rawHeaders = this.urlRequest.rawResponseHeaders; + Object.keys(rawHeaders).forEach(header => { + if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) { + // do nothing with discardable duplicate headers + } + else { + if (header === 'set-cookie') { + // keep set-cookie as an array per Node.js rules + // see https://nodejs.org/api/http.html#http_message_headers + filteredHeaders[header] = rawHeaders[header]; + } + else { + // for non-cookie headers, the values are joined together with ', ' + filteredHeaders[header] = rawHeaders[header].join(', '); + } + } + }); + return filteredHeaders; + } + get httpVersion() { + return `${this.httpVersionMajor}.${this.httpVersionMinor}`; + } + get httpVersionMajor() { + return this.urlRequest.httpVersionMajor; + } + get httpVersionMinor() { + return this.urlRequest.httpVersionMinor; + } + get rawTrailers() { + throw new Error('HTTP trailers are not supported.'); + } + get trailers() { + throw new Error('HTTP trailers are not supported.'); + } + _storeInternalData(chunk) { + this.data.push(chunk); + } + _pushInternalData() { + while (this.shouldPush && this.data.length > 0) { + const chunk = this.data.shift(); + this.shouldPush = this.push(chunk); + } + } + _read() { + this.shouldPush = true; + this._pushInternalData(); + } +} +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; +//# sourceMappingURL=net.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/notification.js b/electronasar/development/browser/api/notification.js new file mode 100644 index 0000000..a6bb64b --- /dev/null +++ b/electronasar/development/browser/api/notification.js @@ -0,0 +1,7 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { Notification, isSupported } = process.electronBinding('notification'); +Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype); +Notification.isSupported = isSupported; +module.exports = Notification; +//# sourceMappingURL=notification.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/power-monitor.js b/electronasar/development/browser/api/power-monitor.js new file mode 100644 index 0000000..428d427 --- /dev/null +++ b/electronasar/development/browser/api/power-monitor.js @@ -0,0 +1,43 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { powerMonitor, PowerMonitor } = process.electronBinding('power_monitor'); +const { deprecate } = require('electron'); +// PowerMonitor is an EventEmitter. +Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype); +EventEmitter.call(powerMonitor); +// 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(); + } + }); +} +// TODO(nitsakh): Remove in 7.0 +powerMonitor.querySystemIdleState = function (threshold, callback) { + deprecate.warn('powerMonitor.querySystemIdleState', 'powerMonitor.getSystemIdleState'); + if (typeof threshold !== 'number') { + throw new Error('Must pass threshold as a number'); + } + if (typeof callback !== 'function') { + throw new Error('Must pass callback as a function argument'); + } + const idleState = this.getSystemIdleState(threshold); + process.nextTick(() => callback(idleState)); +}; +// TODO(nitsakh): Remove in 7.0 +powerMonitor.querySystemIdleTime = function (callback) { + deprecate.warn('powerMonitor.querySystemIdleTime', 'powerMonitor.getSystemIdleTime'); + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument'); + } + const idleTime = this.getSystemIdleTime(); + process.nextTick(() => callback(idleTime)); +}; +module.exports = powerMonitor; +//# sourceMappingURL=power-monitor.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/power-save-blocker.js b/electronasar/development/browser/api/power-save-blocker.js new file mode 100644 index 0000000..648ee3b --- /dev/null +++ b/electronasar/development/browser/api/power-save-blocker.js @@ -0,0 +1,3 @@ +'use strict'; +module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker; +//# sourceMappingURL=power-save-blocker.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/protocol.js b/electronasar/development/browser/api/protocol.js new file mode 100644 index 0000000..d7d8c58 --- /dev/null +++ b/electronasar/development/browser/api/protocol.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +// Global protocol APIs. +const protocol = process.electronBinding('protocol'); +// Fallback protocol APIs of default session. +Object.setPrototypeOf(protocol, new Proxy({}, { + get(_target, property) { + if (!electron_1.app.isReady()) + return; + const protocol = electron_1.session.defaultSession.protocol; + if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) + return; + // Returning a native function directly would throw error. + return (...args) => protocol[property](...args); + }, + ownKeys() { + if (!electron_1.app.isReady()) + return []; + return Object.getOwnPropertyNames(Object.getPrototypeOf(electron_1.session.defaultSession.protocol)); + }, + getOwnPropertyDescriptor() { + return { configurable: true, enumerable: true }; + } +})); +exports.default = protocol; +//# sourceMappingURL=protocol.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/screen.js b/electronasar/development/browser/api/screen.js new file mode 100644 index 0000000..7ea700f --- /dev/null +++ b/electronasar/development/browser/api/screen.js @@ -0,0 +1,8 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { screen, Screen } = process.electronBinding('screen'); +// Screen is an EventEmitter. +Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype); +EventEmitter.call(screen); +module.exports = screen; +//# sourceMappingURL=screen.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/session.js b/electronasar/development/browser/api/session.js new file mode 100644 index 0000000..62eb8f1 --- /dev/null +++ b/electronasar/development/browser/api/session.js @@ -0,0 +1,41 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { app, deprecate } = require('electron'); +const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session'); +// Public API. +Object.defineProperties(exports, { + defaultSession: { + enumerable: true, + get() { return fromPartition(''); } + }, + fromPartition: { + enumerable: true, + value: fromPartition + } +}); +Object.setPrototypeOf(Session.prototype, EventEmitter.prototype); +Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype); +Session.prototype._init = function () { + app.emit('session-created', this); +}; +Session.prototype.clearStorageData = deprecate.promisify(Session.prototype.clearStorageData); +Session.prototype.clearHostResolverCache = deprecate.promisify(Session.prototype.clearHostResolverCache); +Session.prototype.resolveProxy = deprecate.promisify(Session.prototype.resolveProxy); +Session.prototype.setProxy = deprecate.promisify(Session.prototype.setProxy); +Session.prototype.getCacheSize = deprecate.promisify(Session.prototype.getCacheSize); +Session.prototype.clearCache = deprecate.promisify(Session.prototype.clearCache); +Session.prototype.clearAuthCache = deprecate.promisify(Session.prototype.clearAuthCache); +Session.prototype.getBlobData = deprecate.promisifyMultiArg(Session.prototype.getBlobData); +Session.prototype.clearAuthCache = ((clearAuthCache) => function (...args) { + if (args.length > 0) { + deprecate.log(`The 'options' argument to 'clearAuthCache' is deprecated. Beginning with Electron 7, clearAuthCache will clear the entire auth cache unconditionally.`); + } + return clearAuthCache.apply(this, args); +})(Session.prototype.clearAuthCache); +Cookies.prototype.flushStore = deprecate.promisify(Cookies.prototype.flushStore); +Cookies.prototype.get = deprecate.promisify(Cookies.prototype.get); +Cookies.prototype.remove = deprecate.promisify(Cookies.prototype.remove); +Cookies.prototype.set = deprecate.promisify(Cookies.prototype.set); +NetLog.prototype.stopLogging = deprecate.promisify(NetLog.prototype.stopLogging); +Protocol.prototype.isProtocolHandled = deprecate.promisify(Protocol.prototype.isProtocolHandled); +//# sourceMappingURL=session.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/system-preferences.js b/electronasar/development/browser/api/system-preferences.js new file mode 100644 index 0000000..40068f6 --- /dev/null +++ b/electronasar/development/browser/api/system-preferences.js @@ -0,0 +1,8 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences'); +// SystemPreferences is an EventEmitter. +Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype); +EventEmitter.call(systemPreferences); +module.exports = systemPreferences; +//# sourceMappingURL=system-preferences.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/top-level-window.js b/electronasar/development/browser/api/top-level-window.js new file mode 100644 index 0000000..9b73c1c --- /dev/null +++ b/electronasar/development/browser/api/top-level-window.js @@ -0,0 +1,20 @@ +'use strict'; +const electron = require('electron'); +const { EventEmitter } = require('events'); +const { TopLevelWindow } = process.electronBinding('top_level_window'); +Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype); +TopLevelWindow.prototype._init = function () { + // Avoid recursive require. + const { app } = electron; + // Simulate the application menu on platforms other than macOS. + if (process.platform !== 'darwin') { + const menu = app.applicationMenu; + if (menu) + this.setMenu(menu); + } +}; +TopLevelWindow.getFocusedWindow = () => { + return TopLevelWindow.getAllWindows().find((win) => win.isFocused()); +}; +module.exports = TopLevelWindow; +//# sourceMappingURL=top-level-window.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/touch-bar.js b/electronasar/development/browser/api/touch-bar.js new file mode 100644 index 0000000..1fd3f8c --- /dev/null +++ b/electronasar/development/browser/api/touch-bar.js @@ -0,0 +1,311 @@ +'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; + 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; +//# sourceMappingURL=touch-bar.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/tray.js b/electronasar/development/browser/api/tray.js new file mode 100644 index 0000000..7d053dc --- /dev/null +++ b/electronasar/development/browser/api/tray.js @@ -0,0 +1,9 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { deprecate } = require('electron'); +const { Tray } = process.electronBinding('tray'); +Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype); +// Deprecations +Tray.prototype.setHighlightMode = deprecate.removeFunction(Tray.prototype.setHighlightMode, 'setHighlightMode'); +module.exports = Tray; +//# sourceMappingURL=tray.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/view.js b/electronasar/development/browser/api/view.js new file mode 100644 index 0000000..41032f1 --- /dev/null +++ b/electronasar/development/browser/api/view.js @@ -0,0 +1,8 @@ +'use strict'; +const { EventEmitter } = require('events'); +const { View } = process.electronBinding('view'); +Object.setPrototypeOf(View.prototype, EventEmitter.prototype); +View.prototype._init = function () { +}; +module.exports = View; +//# sourceMappingURL=view.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/web-contents-view.js b/electronasar/development/browser/api/web-contents-view.js new file mode 100644 index 0000000..726d6fc --- /dev/null +++ b/electronasar/development/browser/api/web-contents-view.js @@ -0,0 +1,11 @@ +'use strict'; +const electron = require('electron'); +const { View } = electron; +const { WebContentsView } = process.electronBinding('web_contents_view'); +Object.setPrototypeOf(WebContentsView.prototype, View.prototype); +WebContentsView.prototype._init = function () { + // Call parent class's _init. + View.prototype._init.call(this); +}; +module.exports = WebContentsView; +//# sourceMappingURL=web-contents-view.js.map \ No newline at end of file diff --git a/electronasar/development/browser/api/web-contents.js b/electronasar/development/browser/api/web-contents.js new file mode 100644 index 0000000..898fa56 --- /dev/null +++ b/electronasar/development/browser/api/web-contents.js @@ -0,0 +1,447 @@ +'use strict'; +const features = process.electronBinding('features'); +const { EventEmitter } = require('events'); +const electron = require('electron'); +const path = require('path'); +const url = require('url'); +const { app, ipcMain, session, deprecate } = electron; +const NavigationController = require('@electron/internal/browser/navigation-controller'); +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +const errorUtils = require('@electron/internal/common/error-utils'); +// session is not used here, the purpose is to make sure session is initalized +// before the webContents module. +// eslint-disable-next-line +session; +let nextId = 0; +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, + pagesPerSheet: 1, + deviceName: 'Save as PDF', + generateDraftData: true, + fitToPageEnabled: false, + scaleFactor: 1, + dpiHorizontal: 72, + dpiVertical: 72, + rasterizePDF: false, + duplex: 0, + copies: 1, + collate: true, + shouldPrintBackgrounds: false, + shouldPrintSelectionOnly: false +}; +// JavaScript implementations of WebContents. +const binding = process.electronBinding('web_contents'); +const { WebContents } = binding; +Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype); +Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype); +// WebContents::send(channel, args..) +// WebContents::sendToAll(channel, args..) +WebContents.prototype.send = function (channel, ...args) { + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument'); + } + const internal = false; + const sendToAll = false; + return this._send(internal, sendToAll, channel, args); +}; +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); +}; +WebContents.prototype.sendToFrame = function (frameId, channel, ...args) { + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument'); + } + else if (typeof frameId !== 'number') { + throw new Error('Missing required frameId argument'); + } + const internal = false; + const sendToAll = false; + return this._sendToFrame(internal, sendToAll, frameId, channel, args); +}; +WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) { + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument'); + } + else if (typeof frameId !== 'number') { + throw new Error('Missing required frameId argument'); + } + const internal = true; + const sendToAll = false; + return this._sendToFrame(internal, sendToAll, frameId, channel, args); +}; +// Following methods are mapped to webFrame. +const webFrameMethods = [ + 'insertCSS', + 'insertText', + 'setLayoutZoomLevelLimits', + 'setVisualZoomLevelLimits' +]; +for (const method of webFrameMethods) { + WebContents.prototype[method] = function (...args) { + ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args); + }; +} +const executeJavaScript = (contents, code, hasUserGesture) => { + return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture); +}; +// Make sure WebContents::executeJavaScript would run the code only when the +// WebContents has been loaded. +WebContents.prototype.executeJavaScript = function (code, hasUserGesture) { + if (this.getURL() && !this.isLoadingMainFrame()) { + return executeJavaScript(this, code, hasUserGesture); + } + else { + return new Promise((resolve, reject) => { + this.once('did-stop-loading', () => { + executeJavaScript(this, code, hasUserGesture).then(resolve, reject); + }); + }); + } +}; +// TODO(codebytere): remove when promisifications is complete +const nativeZoomLevel = WebContents.prototype.getZoomLevel; +WebContents.prototype.getZoomLevel = function (callback) { + if (callback == null) { + return nativeZoomLevel.call(this); + } + else { + process.nextTick(() => { + callback(nativeZoomLevel.call(this)); + }); + } +}; +// TODO(codebytere): remove when promisifications is complete +const nativeZoomFactor = WebContents.prototype.getZoomFactor; +WebContents.prototype.getZoomFactor = function (callback) { + if (callback == null) { + return nativeZoomFactor.call(this); + } + else { + process.nextTick(() => { + callback(nativeZoomFactor.call(this)); + }); + } +}; +WebContents.prototype.takeHeapSnapshot = function (filePath) { + return new Promise((resolve, reject) => { + this._takeHeapSnapshot(filePath, (success) => { + if (success) { + resolve(); + } + else { + reject(new Error('takeHeapSnapshot failed')); + } + }); + }); +}; +// Translate the options of printToPDF. +WebContents.prototype.printToPDF = function (options) { + const printingSetting = Object.assign({}, defaultPrintingSetting); + if (options.landscape) { + printingSetting.landscape = options.landscape; + } + if (options.marginsType) { + printingSetting.marginsType = options.marginsType; + } + if (options.printSelectionOnly) { + printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly; + } + if (options.printBackground) { + printingSetting.shouldPrintBackgrounds = options.printBackground; + } + if (options.pageSize) { + const pageSize = options.pageSize; + if (typeof pageSize === 'object') { + if (!pageSize.height || !pageSize.width) { + return Promise.reject(new Error('Must define height and width for pageSize')); + } + // Dimensions in Microns + // 1 meter = 10^6 microns + printingSetting.mediaSize = { + name: 'CUSTOM', + custom_display_name: 'Custom', + height_microns: Math.ceil(pageSize.height), + width_microns: Math.ceil(pageSize.width) + }; + } + else if (PDFPageSizes[pageSize]) { + printingSetting.mediaSize = PDFPageSizes[pageSize]; + } + else { + return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`)); + } + } + else { + printingSetting.mediaSize = PDFPageSizes['A4']; + } + // Chromium expects this in a 0-100 range number, not as float + printingSetting.scaleFactor *= 100; + if (features.isPrintingEnabled()) { + return this._printToPDF(printingSetting); + } + else { + return Promise.reject(new Error('Printing feature is disabled')); + } +}; +WebContents.prototype.print = function (...args) { + if (features.isPrintingEnabled()) { + this._print(...args); + } + else { + console.error('Error: Printing feature is disabled.'); + } +}; +WebContents.prototype.getPrinters = function () { + if (features.isPrintingEnabled()) { + return this._getPrinters(); + } + else { + console.error('Error: Printing feature is disabled.'); + } +}; +WebContents.prototype.loadFile = function (filePath, options = {}) { + if (typeof filePath !== 'string') { + throw new Error('Must pass filePath as a string'); + } + const { query, search, hash } = options; + return this.loadURL(url.format({ + protocol: 'file', + slashes: true, + pathname: path.resolve(app.getAppPath(), filePath), + query, + search, + hash + })); +}; +WebContents.prototype.capturePage = deprecate.promisify(WebContents.prototype.capturePage); +WebContents.prototype.executeJavaScript = deprecate.promisify(WebContents.prototype.executeJavaScript); +WebContents.prototype.printToPDF = deprecate.promisify(WebContents.prototype.printToPDF); +WebContents.prototype.savePage = deprecate.promisify(WebContents.prototype.savePage); +const addReplyToEvent = (event) => { + event.reply = (...args) => { + event.sender.sendToFrame(event.frameId, ...args); + }; +}; +const addReplyInternalToEvent = (event) => { + Object.defineProperty(event, '_replyInternal', { + configurable: false, + enumerable: false, + value: (...args) => { + event.sender._sendToFrameInternal(event.frameId, ...args); + } + }); +}; +const addReturnValueToEvent = (event) => { + Object.defineProperty(event, 'returnValue', { + set: (value) => event.sendReply([value]), + get: () => { } + }); +}; +// Add JavaScript wrappers for WebContents class. +WebContents.prototype._init = function () { + // The navigation controller. + NavigationController.call(this, this); + // Every remote callback from renderer process would add a listener to the + // render-view-deleted event, so ignore the listeners warning. + this.setMaxListeners(0); + // Dispatch IPC messages to the ipc module. + this.on('-ipc-message', function (event, internal, channel, args) { + if (internal) { + addReplyInternalToEvent(event); + ipcMainInternal.emit(channel, event, ...args); + } + else { + addReplyToEvent(event); + this.emit('ipc-message', event, channel, ...args); + ipcMain.emit(channel, event, ...args); + } + }); + this.on('-ipc-message-sync', function (event, internal, channel, args) { + addReturnValueToEvent(event); + if (internal) { + addReplyInternalToEvent(event); + ipcMainInternal.emit(channel, event, ...args); + } + else { + addReplyToEvent(event); + this.emit('ipc-message-sync', event, channel, ...args); + ipcMain.emit(channel, event, ...args); + } + }); + // Handle context menu action request from pepper plugin. + this.on('pepper-context-menu', function (event, params, callback) { + // Access Menu via electron.Menu to prevent circular require. + const menu = electron.Menu.buildFromTemplate(params.menu); + menu.popup({ + window: event.sender.getOwnerBrowserWindow(), + x: params.x, + y: params.y, + callback + }); + }); + const forwardedEvents = [ + 'desktop-capturer-get-sources', + 'remote-require', + 'remote-get-global', + 'remote-get-builtin', + 'remote-get-current-window', + 'remote-get-current-web-contents', + 'remote-get-guest-web-contents' + ]; + for (const eventName of forwardedEvents) { + this.on(eventName, (event, ...args) => { + app.emit(eventName, event, this, ...args); + }); + } + this.on('crashed', (event, ...args) => { + app.emit('renderer-process-crashed', event, this, ...args); + }); + deprecate.event(this, 'did-get-response-details', '-did-get-response-details'); + deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request'); + // The devtools requests the webContents to reload. + this.on('devtools-reload-page', function () { + this.reload(); + }); + // Handle window.open for BrowserWindow and BrowserView. + if (['browserView', 'window'].includes(this.getType())) { + // Make new windows requested by links behave like "window.open". + this.on('-new-window', (event, url, frameName, disposition, additionalFeatures, postData, referrer) => { + const options = { + show: true, + width: 800, + height: 600 + }; + ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options, additionalFeatures, postData); + }); + // Create a new browser window for the native implementation of + // "window.open", used in sandbox and nativeWindowOpen mode. + this.on('-add-new-contents', (event, webContents, disposition, userGesture, left, top, width, height, url, frameName) => { + if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && + disposition !== 'background-tab')) { + event.preventDefault(); + return; + } + const options = { + show: true, + x: left, + y: top, + width: width || 800, + height: height || 600, + webContents + }; + const referrer = { url: '', policy: 'default' }; + ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options); + }); + } + app.emit('web-contents-created', {}, this); +}; +// JavaScript wrapper of Debugger. +const { Debugger } = process.electronBinding('debugger'); +Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand); +Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype); +// Public APIs. +module.exports = { + create(options = {}) { + return binding.create(options); + }, + fromId(id) { + return binding.fromId(id); + }, + getFocusedWebContents() { + let focused = null; + for (const contents of binding.getAllWebContents()) { + if (!contents.isFocused()) + continue; + if (focused == null) + focused = contents; + // Return webview web contents which may be embedded inside another + // web contents that is also reporting as focused + if (contents.getType() === 'webview') + return contents; + } + return focused; + }, + getAllWebContents() { + return binding.getAllWebContents(); + } +}; +//# sourceMappingURL=web-contents.js.map \ No newline at end of file diff --git a/electronasar/development/browser/chrome-extension.js b/electronasar/development/browser/chrome-extension.js new file mode 100644 index 0000000..2736389 --- /dev/null +++ b/electronasar/development/browser/chrome-extension.js @@ -0,0 +1,490 @@ +'use strict'; +const { app, webContents, BrowserWindow } = require('electron'); +const { getAllWebContents } = process.electronBinding('web_contents'); +const renderProcessPreferences = process.electronBinding('render_process_preferences').forAllWebContents(); +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +const { Buffer } = require('buffer'); +const fs = require('fs'); +const path = require('path'); +const url = require('url'); +const util = require('util'); +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +// Mapping between extensionId(hostname) and manifest. +const manifestMap = {}; // extensionId => manifest +const manifestNameMap = {}; // name => manifest +const devToolsExtensionNames = new Set(); +const generateExtensionIdFromName = function (name) { + return name.replace(/[\W_]+/g, '-').toLowerCase(); +}; +const isWindowOrWebView = function (webContents) { + const type = webContents.getType(); + return type === 'window' || type === 'webview'; +}; +const isBackgroundPage = function (webContents) { + return webContents.getType() === 'backgroundPage'; +}; +// Create or get manifest object from |srcDirectory|. +const getManifestFromPath = function (srcDirectory) { + let manifest; + let manifestContent; + try { + manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json')); + } + catch (readError) { + console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`); + console.warn(readError.stack || readError); + throw readError; + } + try { + manifest = JSON.parse(manifestContent); + } + catch (parseError) { + console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`); + console.warn(parseError.stack || parseError); + throw parseError; + } + if (!manifestNameMap[manifest.name]) { + const extensionId = generateExtensionIdFromName(manifest.name); + manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest; + Object.assign(manifest, { + srcDirectory: srcDirectory, + extensionId: extensionId, + // We can not use 'file://' directly because all resources in the extension + // will be treated as relative to the root in Chrome. + startPage: url.format({ + 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 ``; + }).join(''); + html = Buffer.from(`${scripts}`); + } + const contents = webContents.create({ + partition: 'persist:__chrome_extension', + isBackgroundPage: true, + sandbox: true, + enableRemoteModule: false + }); + backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }; + contents.loadURL(url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: 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)) { + if (!page.webContents.isDestroyed()) { + page.webContents._sendInternalToAll(...args); + } + } +}; +// Dispatch web contents events to Chrome APIs +const hookWebContentsEvents = function (webContents) { + const tabId = webContents.id; + sendToBackgroundPages('CHROME_TABS_ONCREATED'); + webContents.on('will-navigate', (event, url) => { + sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { + frameId: 0, + parentFrameId: -1, + processId: webContents.getProcessId(), + tabId: tabId, + timeStamp: Date.now(), + url: url + }); + }); + webContents.on('did-navigate', (event, url) => { + sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { + frameId: 0, + parentFrameId: -1, + processId: webContents.getProcessId(), + tabId: tabId, + timeStamp: Date.now(), + url: url + }); + }); + webContents.once('destroyed', () => { + sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId); + }); +}; +// Handle the chrome.* API messages. +let nextId = 0; +ipcMainUtils.handle('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { + if (isBackgroundPage(event.sender)) { + throw new Error('chrome.runtime.connect is not supported in background page'); + } + const page = backgroundPages[extensionId]; + if (!page || page.webContents.isDestroyed()) { + throw new Error(`Connect to unknown extension ${extensionId}`); + } + const tabId = page.webContents.id; + const portId = ++nextId; + event.sender.once('render-view-deleted', () => { + if (page.webContents.isDestroyed()) + return; + page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`); + }); + page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo); + return { tabId, portId }; +}); +ipcMainUtils.handle('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { + const manifest = manifestMap[extensionId]; + if (!manifest) { + throw new Error(`Invalid extensionId: ${extensionId}`); + } + return manifest; +}); +ipcMainUtils.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { + if (isBackgroundPage(event.sender)) { + throw new Error('chrome.runtime.sendMessage is not supported in background page'); + } + const page = backgroundPages[extensionId]; + if (!page || page.webContents.isDestroyed()) { + throw new Error(`Connect to unknown extension ${extensionId}`); + } + return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message); +}); +ipcMainUtils.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) { + const contents = webContents.fromId(tabId); + if (!contents) { + throw new Error(`Sending message to unknown tab ${tabId}`); + } + const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id; + return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message); +}); +const getLanguage = () => { + return app.getLocale().replace(/-.*$/, '').toLowerCase(); +}; +const getMessagesPath = (extensionId) => { + const metadata = manifestMap[extensionId]; + if (!metadata) { + throw new Error(`Invalid extensionId: ${extensionId}`); + } + const localesDirectory = path.join(metadata.srcDirectory, '_locales'); + const language = getLanguage(); + try { + const filename = path.join(localesDirectory, language, 'messages.json'); + fs.accessSync(filename, fs.constants.R_OK); + return filename; + } + catch (_a) { + const defaultLocale = metadata.default_locale || 'en'; + return path.join(localesDirectory, defaultLocale, 'messages.json'); + } +}; +ipcMainUtils.handle('CHROME_GET_MESSAGES', async function (event, extensionId) { + const messagesPath = getMessagesPath(extensionId); + return readFile(messagesPath); +}); +const validStorageTypes = new Set(['sync', 'local']); +const getChromeStoragePath = (storageType, extensionId) => { + if (!validStorageTypes.has(storageType)) { + throw new Error(`Invalid storageType: ${storageType}`); + } + if (!manifestMap[extensionId]) { + throw new Error(`Invalid extensionId: ${extensionId}`); + } + return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`); +}; +const mkdirp = util.promisify((dir, callback) => { + fs.mkdir(dir, (error) => { + if (error && error.code === 'ENOENT') { + mkdirp(path.dirname(dir), (error) => { + if (!error) { + mkdirp(dir, callback); + } + }); + } + else if (error && error.code === 'EEXIST') { + callback(null); + } + else { + callback(error); + } + }); +}); +ipcMainUtils.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { + const filePath = getChromeStoragePath(storageType, extensionId); + try { + return await readFile(filePath, 'utf8'); + } + catch (error) { + if (error.code === 'ENOENT') { + return null; + } + else { + throw error; + } + } +}); +ipcMainUtils.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { + const filePath = getChromeStoragePath(storageType, extensionId); + try { + await mkdirp(path.dirname(filePath)); + } + catch (_a) { + // we just ignore the errors of mkdir or mkdirp + } + return writeFile(filePath, data, 'utf8'); +}); +const isChromeExtension = function (pageURL) { + const { protocol } = url.parse(pageURL); + return protocol === 'chrome-extension:'; +}; +const assertChromeExtension = function (contents, api) { + const pageURL = contents._getURL(); + if (!isChromeExtension(pageURL)) { + console.error(`Blocked ${pageURL} from calling ${api}`); + throw new Error(`Blocked ${api}`); + } +}; +ipcMainUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { + assertChromeExtension(event.sender, 'chrome.tabs.executeScript()'); + const contents = webContents.fromId(tabId); + if (!contents) { + throw new Error(`Sending message to unknown tab ${tabId}`); + } + let code, url; + if (details.file) { + const manifest = manifestMap[extensionId]; + code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))); + url = `chrome-extension://${extensionId}${details.file}`; + } + else { + code = details.code; + url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`; + } + return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code); +}); +// Transfer the content scripts to renderer. +const contentScripts = {}; +const 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', + allFrames: script.all_frames || false + }; + }; + 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(`InspectorFrontendAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`); +}; +app.on('web-contents-created', function (event, webContents) { + if (!isWindowOrWebView(webContents)) + return; + hookWebContentsEvents(webContents); + webContents.on('devtools-opened', function () { + loadDevToolsExtensions(webContents, Object.values(manifestMap)); + }); +}); +// 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 (_a) { + // Ignore error + } + fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)); + } + else { + fs.unlinkSync(loadedDevToolsExtensionsPath); + } + } + catch (_b) { + // Ignore error + } +}); +// We can not use protocol or BrowserWindow until app is ready. +app.once('ready', function () { + // The public API to add/remove extensions. + BrowserWindow.addExtension = function (srcDirectory) { + const manifest = getManifestFromPath(srcDirectory); + if (manifest) { + loadExtension(manifest); + for (const webContents of getAllWebContents()) { + if (isWindowOrWebView(webContents)) { + loadDevToolsExtensions(webContents, [manifest]); + } + } + 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); + } + } +}); +//# sourceMappingURL=chrome-extension.js.map \ No newline at end of file diff --git a/electronasar/development/browser/crash-reporter-init.js b/electronasar/development/browser/crash-reporter-init.js new file mode 100644 index 0000000..917ae67 --- /dev/null +++ b/electronasar/development/browser/crash-reporter-init.js @@ -0,0 +1,23 @@ +'use strict'; +const { app } = require('electron'); +const cp = require('child_process'); +const os = require('os'); +const path = require('path'); +const getTempDirectory = function () { + try { + return app.getPath('temp'); + } + catch (_a) { + return os.tmpdir(); + } +}; +exports.crashReporterInit = function (options) { + const productName = options.productName || app.getName(); + const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`); + return { + productName, + crashesDirectory, + appVersion: app.getVersion() + }; +}; +//# sourceMappingURL=crash-reporter-init.js.map \ No newline at end of file diff --git a/electronasar/development/browser/default-menu.js b/electronasar/development/browser/default-menu.js new file mode 100644 index 0000000..8e7425c --- /dev/null +++ b/electronasar/development/browser/default-menu.js @@ -0,0 +1,50 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const v8Util = process.electronBinding('v8_util'); +const isMac = process.platform === 'darwin'; +exports.setDefaultApplicationMenu = () => { + if (v8Util.getHiddenValue(global, 'applicationMenuSet')) + return; + const helpMenu = { + role: 'help', + submenu: [ + { + label: 'Learn More', + click: async () => { + await electron_1.shell.openExternal('https://electronjs.org'); + } + }, + { + label: 'Documentation', + click: async () => { + await electron_1.shell.openExternal(`https://github.com/electron/electron/tree/v${process.versions.electron}/docs#readme`); + } + }, + { + label: 'Community Discussions', + click: async () => { + await electron_1.shell.openExternal('https://discuss.atom.io/c/electron'); + } + }, + { + label: 'Search Issues', + click: async () => { + await electron_1.shell.openExternal('https://github.com/electron/electron/issues'); + } + } + ] + }; + const macAppMenu = { role: 'appMenu' }; + const template = [ + ...(isMac ? [macAppMenu] : []), + { role: 'fileMenu' }, + { role: 'editMenu' }, + { role: 'viewMenu' }, + { role: 'windowMenu' }, + helpMenu + ]; + const menu = electron_1.Menu.buildFromTemplate(template); + electron_1.Menu.setApplicationMenu(menu); +}; +//# sourceMappingURL=default-menu.js.map \ No newline at end of file diff --git a/electronasar/development/browser/desktop-capturer.js b/electronasar/development/browser/desktop-capturer.js new file mode 100644 index 0000000..b012a0c --- /dev/null +++ b/electronasar/development/browser/desktop-capturer.js @@ -0,0 +1,86 @@ +'use strict'; +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +const { createDesktopCapturer } = process.electronBinding('desktop_capturer'); +const eventBinding = process.electronBinding('event'); +const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); +let currentlyRunning = []; +ipcMainUtils.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, fetchWindowIcons) => { + const customEvent = eventBinding.createWithSender(event.sender); + event.sender.emit('desktop-capturer-get-sources', customEvent); + if (customEvent.defaultPrevented) { + return []; + } + const options = { + captureWindow, + captureScreen, + thumbnailSize, + fetchWindowIcons + }; + for (const running of currentlyRunning) { + if (deepEqual(running.options, options)) { + // If a request is currently running for the same options + // return that promise + return running.getSources; + } + } + const getSources = new Promise((resolve, reject) => { + const stopRunning = () => { + // Remove from currentlyRunning once we resolve or reject + currentlyRunning = currentlyRunning.filter(running => running.options !== options); + }; + const request = { + options, + resolve: (value) => { + stopRunning(); + resolve(value); + }, + reject: (err) => { + stopRunning(); + reject(err); + }, + capturer: createDesktopCapturer() + }; + request.capturer.emit = createCapturerEmitHandler(request.capturer, request); + request.capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons); + // If the WebContents is destroyed before receiving result, just remove the + // reference to resolve, emit and the capturer itself so that it never dispatches + // back to the renderer + event.sender.once('destroyed', () => { + request.resolve = null; + delete request.capturer.emit; + delete request.capturer; + stopRunning(); + }); + }); + currentlyRunning.push({ + options, + getSources + }); + return getSources; +}); +const createCapturerEmitHandler = (capturer, request) => { + return function handlEmitOnCapturer(event, name, sources, fetchWindowIcons) { + // Ensure that this capturer instance can only ever receive a single event + // if we get more than one it is a bug but will also cause strange behavior + // if we still try to handle it + delete capturer.emit; + if (name === 'error') { + const error = sources; + request.reject(error); + return; + } + const result = sources.map(source => { + return { + id: source.id, + name: source.name, + thumbnail: source.thumbnail.toDataURL(), + display_id: source.display_id, + appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null + }; + }); + if (request.resolve) { + request.resolve(result); + } + }; +}; +//# sourceMappingURL=desktop-capturer.js.map \ No newline at end of file diff --git a/electronasar/development/browser/devtools.js b/electronasar/development/browser/devtools.js new file mode 100644 index 0000000..fde2e96 --- /dev/null +++ b/electronasar/development/browser/devtools.js @@ -0,0 +1,93 @@ +'use strict'; +const { dialog, Menu } = require('electron'); +const fs = require('fs'); +const url = require('url'); +const util = require('util'); +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +const readFile = util.promisify(fs.readFile); +const convertToMenuTemplate = function (items, handler) { + return items.map(function (item) { + const transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(item.subItems, handler) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + }; + if (item.id != null) { + transformed.click = () => handler(item.id); + } + return transformed; + }); +}; +const getEditMenuItems = function () { + return [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' } + ]; +}; +const isChromeDevTools = function (pageURL) { + const { protocol } = url.parse(pageURL); + return protocol === 'devtools:'; +}; +const assertChromeDevTools = function (contents, api) { + const pageURL = contents._getURL(); + if (!isChromeDevTools(pageURL)) { + console.error(`Blocked ${pageURL} from calling ${api}`); + throw new Error(`Blocked ${api}`); + } +}; +ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event, items, isEditMenu) { + return new Promise(resolve => { + assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()'); + const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve); + const menu = Menu.buildFromTemplate(template); + const window = event.sender.getOwnerBrowserWindow(); + menu.once('menu-will-close', () => { + // menu-will-close is emitted before the click handler, but needs to be sent after. + // otherwise, DevToolsAPI.contextMenuCleared() would be called before + // DevToolsAPI.contextMenuItemSelected(id) and the menu will not work properly. + setTimeout(() => resolve()); + }); + menu.popup({ window }); + }); +}); +ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event) { + assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()'); + const result = await dialog.showOpenDialog({}); + if (result.canceled) + return []; + const path = result.filePaths[0]; + const data = await readFile(path); + return [path, data]; +}); +ipcMainUtils.handle('ELECTRON_INSPECTOR_CONFIRM', async function (event, message = '', title = '') { + assertChromeDevTools(event.sender, 'window.confirm()'); + const options = { + message: String(message), + title: String(title), + buttons: ['OK', 'Cancel'], + cancelId: 1 + }; + const window = event.sender.getOwnerBrowserWindow(); + const { response } = await dialog.showMessageBox(window, options); + return response === 0; +}); +//# sourceMappingURL=devtools.js.map \ No newline at end of file diff --git a/electronasar/development/browser/guest-view-manager.js b/electronasar/development/browser/guest-view-manager.js new file mode 100644 index 0000000..02b6290 --- /dev/null +++ b/electronasar/development/browser/guest-view-manager.js @@ -0,0 +1,365 @@ +'use strict'; +const { webContents } = require('electron'); +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +const parseFeaturesString = require('@electron/internal/common/parse-features-string'); +const { syncMethods, asyncCallbackMethods, asyncPromiseMethods } = require('@electron/internal/common/web-view-methods'); +// Doesn't exist in early initialization. +let webViewManager = null; +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', + 'plugin-crashed', + 'destroyed', + 'page-title-updated', + 'page-favicon-updated', + 'enter-html-full-screen', + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', + 'found-in-page', + 'did-change-theme-color', + 'update-target-url' +]; +const guestInstances = {}; +const embedderElementsMap = {}; +// Create a new guest instance. +const createGuest = function (embedder, params) { + if (webViewManager == null) { + webViewManager = process.electronBinding('web_view_manager'); + } + const guest = webContents.create({ + isGuest: true, + partition: params.partition, + embedder: embedder + }); + const guestInstanceId = guest.id; + guestInstances[guestInstanceId] = { + guest: guest, + embedder: embedder + }; + // Clear the guest from map when it is destroyed. + // + // The guest WebContents is usually destroyed in 2 cases: + // 1. The embedder frame is closed (reloaded or destroyed), and it + // automatically closes the guest frame. + // 2. The guest frame is detached dynamically via JS, and it is manually + // destroyed when the renderer sends the GUEST_VIEW_MANAGER_DESTROY_GUEST + // message. + // The second case relies on the libcc patch: + // https://github.com/electron/libchromiumcontent/pull/676 + // The patch was introduced to work around a bug in Chromium: + // https://github.com/electron/electron/issues/14211 + // We should revisit the bug to see if we can remove our libcc patch, the + // patch was introduced in Chrome 66. + guest.once('destroyed', () => { + if (guestInstanceId in guestInstances) { + detachGuest(embedder, guestInstanceId); + } + }); + // Init guest web view after attached. + guest.once('did-attach', function (event) { + params = this.attachParams; + delete this.attachParams; + const previouslyAttached = this.viewInstanceId != null; + this.viewInstanceId = params.instanceId; + // Only load URL and set size on first attach + if (previouslyAttached) { + return; + } + if (params.src) { + const opts = {}; + if (params.httpreferrer) { + opts.httpReferrer = params.httpreferrer; + } + if (params.useragent) { + opts.userAgent = params.useragent; + } + this.loadURL(params.src, opts); + } + embedder.emit('did-attach-webview', event, guest); + }); + const sendToEmbedder = (channel, ...args) => { + if (!embedder.isDestroyed()) { + embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args); + } + }; + // Dispatch events to embedder. + const fn = function (event) { + guest.on(event, function (_, ...args) { + sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args); + }); + }; + for (const event of supportedWebViewEvents) { + fn(event); + } + // Dispatch guest's IPC messages to embedder. + guest.on('ipc-message-host', function (_, channel, args) { + sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args); + }); + // Notify guest of embedder window visibility when it is ready + // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed + guest.on('dom-ready', function () { + const guestInstance = guestInstances[guestInstanceId]; + if (guestInstance != null && guestInstance.visibilityState != null) { + guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState); + } + }); + // Forward internal web contents event to embedder to handle + // native window.open setup + guest.on('-add-new-contents', (...args) => { + if (guest.getLastWebPreferences().nativeWindowOpen === true) { + const embedder = getEmbedder(guestInstanceId); + if (embedder != null) { + embedder.emit('-add-new-contents', ...args); + } + } + }); + return guestInstanceId; +}; +// Attach the guest to an element of embedder. +const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { + const embedder = event.sender; + // Destroy the old guest when attaching. + const key = `${embedder.id}-${elementInstanceId}`; + const oldGuestInstanceId = embedderElementsMap[key]; + if (oldGuestInstanceId != null) { + // Reattachment to the same guest is just a no-op. + if (oldGuestInstanceId === guestInstanceId) { + return; + } + const oldGuestInstance = guestInstances[oldGuestInstanceId]; + if (oldGuestInstance) { + oldGuestInstance.guest.detachFromOuterFrame(); + } + } + 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, + nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false, + enableRemoteModule: params.enableremotemodule, + plugins: params.plugins, + zoomFactor: embedder.getZoomFactor(), + disablePopups: !params.allowpopups, + webSecurity: !params.disablewebsecurity, + enableBlinkFeatures: params.blinkfeatures, + disableBlinkFeatures: params.disableblinkfeatures + }; + // parse the 'webpreferences' attribute string, if set + // this uses the same parsing rules as window.open uses for its features + if (typeof params.webpreferences === 'string') { + parseFeaturesString(params.webpreferences, function (key, value) { + if (value === undefined) { + // no value was specified, default it to true + value = true; + } + webPreferences[key] = value; + }); + } + if (params.preload) { + webPreferences.preloadURL = params.preload; + } + // Security options that guest will always inherit from embedder + const inheritedWebPreferences = new Map([ + ['contextIsolation', true], + ['javascript', false], + ['nativeWindowOpen', true], + ['nodeIntegration', false], + ['enableRemoteModule', false], + ['sandbox', true], + ['nodeIntegrationInSubFrames', false] + ]); + // Inherit certain option values from embedder + const lastWebPreferences = embedder.getLastWebPreferences(); + for (const [name, value] of inheritedWebPreferences) { + if (lastWebPreferences[name] === value) { + webPreferences[name] = value; + } + } + embedder.emit('will-attach-webview', event, webPreferences, params); + if (event.defaultPrevented) { + if (guest.viewInstanceId == null) + guest.viewInstanceId = params.instanceId; + guest.destroy(); + return; + } + guest.attachParams = params; + embedderElementsMap[key] = guestInstanceId; + guest.setEmbedder(embedder); + guestInstance.embedder = embedder; + guestInstance.elementInstanceId = elementInstanceId; + watchEmbedder(embedder); + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences); + guest.attachToIframe(embedder, embedderFrameId); +}; +// Remove an guest-embedder relationship. +const detachGuest = function (embedder, guestInstanceId) { + const guestInstance = guestInstances[guestInstanceId]; + if (embedder !== guestInstance.embedder) { + return; + } + webViewManager.removeGuest(embedder, guestInstanceId); + delete guestInstances[guestInstanceId]; + const key = `${embedder.id}-${guestInstance.elementInstanceId}`; + delete embedderElementsMap[key]; +}; +// 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 webPreferences = contents.getLastWebPreferences() || {}; + isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag); + } + return isWebViewTagEnabledCache.get(contents); +}; +const handleMessage = function (channel, handler) { + ipcMainUtils.handle(channel, (event, ...args) => { + if (isWebViewTagEnabled(event.sender)) { + return handler(event, ...args); + } + else { + console.error(` IPC message ${channel} sent by WebContents with disabled (${event.sender.id})`); + throw new Error(' disabled'); + } + }); +}; +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) { + return createGuest(event.sender, params); +}); +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) { + try { + const guest = getGuestForWebContents(guestInstanceId, event.sender); + guest.detachFromOuterFrame(); + } + catch (error) { + console.error(`Guest destroy failed: ${error}`); + } +}); +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { + try { + attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params); + } + catch (error) { + console.error(`Guest attach failed: ${error}`); + } +}); +// this message is sent by the actual +ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) { + const guest = getGuest(guestInstanceId); + if (guest === event.sender) { + event.sender.emit('focus-change', {}, focus, guestInstanceId); + } + else { + console.error(`focus-change for guestInstanceId: ${guestInstanceId}`); + } +}); +const allMethods = new Set([ + ...syncMethods, + ...asyncCallbackMethods, + ...asyncPromiseMethods +]); +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { + const guest = getGuestForWebContents(guestInstanceId, event.sender); + if (!allMethods.has(method)) { + throw new Error(`Invalid method: ${method}`); + } + return guest[method](...args); +}); +// Returns WebContents from its guest id hosted in given webContents. +const getGuestForWebContents = function (guestInstanceId, contents) { + const guest = getGuest(guestInstanceId); + if (!guest) { + throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`); + } + if (guest.hostWebContents !== contents) { + throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`); + } + return guest; +}; +// 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; +exports.isWebViewTagEnabled = isWebViewTagEnabled; +//# sourceMappingURL=guest-view-manager.js.map \ No newline at end of file diff --git a/electronasar/development/browser/guest-window-manager.js b/electronasar/development/browser/guest-window-manager.js new file mode 100644 index 0000000..6cb2fc8 --- /dev/null +++ b/electronasar/development/browser/guest-window-manager.js @@ -0,0 +1,331 @@ +'use strict'; +const { BrowserWindow, webContents } = require('electron'); +const { isSameOrigin } = process.electronBinding('v8_util'); +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); +const parseFeaturesString = require('@electron/internal/common/parse-features-string'); +const hasProp = {}.hasOwnProperty; +const frameToGuest = new Map(); +// Security options that child windows will always inherit from parent windows +const inheritedWebPreferences = new Map([ + ['contextIsolation', true], + ['javascript', false], + ['nativeWindowOpen', true], + ['nodeIntegration', false], + ['enableRemoteModule', false], + ['sandbox', true], + ['webviewTag', false], + ['nodeIntegrationInSubFrames', false] +]); +// Copy attribute of |parent| to |child| if it is not defined in |child|. +const mergeOptions = function (child, parent, visited) { + // Check for circular reference. + if (visited == null) + visited = new Set(); + if (visited.has(parent)) + return; + visited.add(parent); + for (const key in parent) { + if (key === 'isBrowserView') + continue; + if (!hasProp.call(parent, key)) + continue; + if (key in child && key !== 'webPreferences') + continue; + const value = parent[key]; + if (typeof value === 'object' && !Array.isArray(value)) { + 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 = Object.assign({}, 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('current-render-view-deleted', closedByEmbedder); + }; + embedder.once('current-render-view-deleted', closedByEmbedder); + guest.once('closed', closedByUser); + if (frameName) { + frameToGuest.set(frameName, guest); + guest.frameName = frameName; + guest.once('closed', function () { + frameToGuest.delete(frameName); + }); + } + return guestId; +}; +// Create a new guest created by |embedder| with |options|. +const createGuest = function (embedder, url, referrer, frameName, options, postData) { + let guest = frameToGuest.get(frameName); + if (frameName && (guest != null)) { + guest.loadURL(url); + return guest.webContents.id; + } + // Remember the embedder window's id. + if (options.webPreferences == null) { + options.webPreferences = {}; + } + guest = new BrowserWindow(options); + if (!options.webContents) { + // We should not call `loadURL` if the window was constructed from an + // existing webContents (window.open in a sandboxed renderer). + // + // Navigating to the url when creating the window from an existing + // webContents is not necessary (it will navigate there anyway). + const loadOptions = { + httpReferrer: referrer + }; + if (postData != null) { + loadOptions.postData = postData; + loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'; + if (postData.length > 0) { + const postDataFront = postData[0].bytes.toString(); + const boundary = /^--.*[^-\r\n]/.exec(postDataFront); + if (boundary != null) { + loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`; + } + } + } + guest.loadURL(url, loadOptions); + } + 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; +}; +const isChildWindow = function (sender, target) { + return target.getLastWebPreferences().openerId === sender.id; +}; +const isRelatedWindow = function (sender, target) { + return isChildWindow(sender, target) || isChildWindow(target, sender); +}; +const isScriptableWindow = function (sender, target) { + return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL()); +}; +const isNodeIntegrationEnabled = function (sender) { + return sender.getLastWebPreferences().nodeIntegration === true; +}; +// Checks whether |sender| can access the |target|: +const canAccessWindow = function (sender, target) { + return isChildWindow(sender, target) || + isScriptableWindow(sender, target) || + isNodeIntegrationEnabled(sender); +}; +// Routed window.open messages with raw options +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { + if (url == null || url === '') + url = 'about:blank'; + if (frameName == null) + frameName = ''; + if (features == null) + features = ''; + const options = {}; + const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']; + const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag']; + const disposition = 'new-window'; + // Used to store additional features + const additionalFeatures = []; + // Parse the features + parseFeaturesString(features, function (key, value) { + if (value === undefined) { + additionalFeatures.push(key); + } + else { + // Don't allow webPreferences to be set since it must be an object + // that cannot be directly overridden + if (key === 'webPreferences') + return; + if (webPreferences.includes(key)) { + if (options.webPreferences == null) { + options.webPreferences = {}; + } + options.webPreferences[key] = value; + } + else { + options[key] = value; + } + } + }); + if (options.left) { + if (options.x == null) { + options.x = options.left; + } + } + if (options.top) { + if (options.y == null) { + options.y = options.top; + } + } + if (options.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' }; + ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, referrer, frameName, disposition, options, additionalFeatures); +}); +// Routed window.open messages with fully parsed options +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) { + options = mergeBrowserWindowOptions(event.sender, options); + event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer); + const { newGuest } = event; + if ((event.sender.isGuest() && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { + if (newGuest != null) { + if (options.webContents === newGuest.webContents) { + // the webContents is not changed, so set defaultPrevented to false to + // stop the callers of this event from destroying the webContents. + event.defaultPrevented = false; + } + event.returnValue = setupGuest(event.sender, frameName, newGuest, options); + } + else { + event.returnValue = null; + } + } + else { + event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData); + } +}); +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) { + const guestContents = webContents.fromId(guestId); + if (guestContents == null) + return; + if (!canAccessWindow(event.sender, guestContents)) { + console.error(`Blocked ${event.sender.getURL()} from closing its opener.`); + return; + } + const guestWindow = getGuestWindow(guestContents); + if (guestWindow != null) + guestWindow.destroy(); +}); +const windowMethods = new Set([ + 'focus', + 'blur' +]); +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId); + if (guestContents == null) { + event.returnValue = null; + return; + } + if (!canAccessWindow(event.sender, guestContents) || !windowMethods.has(method)) { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`); + event.returnValue = null; + return; + } + const guestWindow = getGuestWindow(guestContents); + if (guestWindow != null) { + event.returnValue = guestWindow[method](...args); + } + else { + event.returnValue = null; + } +}); +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { + if (targetOrigin == null) { + targetOrigin = '*'; + } + const guestContents = webContents.fromId(guestId); + if (guestContents == null) + return; + // The W3C does not seem to have word on how postMessage should work when the + // origins do not match, so we do not do |canAccessWindow| check here since + // postMessage across origins is useful and not harmful. + if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) { + const sourceId = event.sender.id; + guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin); + } +}); +const webContentsMethods = new Set([ + 'print', + 'executeJavaScript' +]); +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId); + if (guestContents == null) + return; + if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) { + guestContents[method](...args); + } + else { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`); + } +}); +const webContentsSyncMethods = new Set([ + 'getURL', + 'loadURL' +]); +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId); + if (guestContents == null) { + event.returnValue = null; + return; + } + if (canAccessWindow(event.sender, guestContents) && webContentsSyncMethods.has(method)) { + event.returnValue = guestContents[method](...args); + } + else { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`); + event.returnValue = null; + } +}); +//# sourceMappingURL=guest-window-manager.js.map \ No newline at end of file diff --git a/electronasar/development/browser/init.js b/electronasar/development/browser/init.js new file mode 100644 index 0000000..7d92c1c --- /dev/null +++ b/electronasar/development/browser/init.js @@ -0,0 +1,187 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const buffer_1 = require("buffer"); +const fs = require("fs"); +const path = require("path"); +const util = require("util"); +const v8 = require("v8"); +const Module = require('module'); +// We modified the original process.argv to let node.js load the init.js, +// we need to restore it here. +process.argv.splice(1, 1); +// 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 = (format, ...args) => { + return process.log(util.format(format, ...args) + '\n'); + }; + const streamWrite = function (chunk, encoding, callback) { + if (buffer_1.Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding); + } + process.log(chunk); + if (callback) { + callback(); + } + return true; + }; + console.log = console.error = console.warn = consoleLog; + process.stdout.write = process.stderr.write = streamWrite; +} +// Don't quit on fatal error. +process.on('uncaughtException', function (error) { + // Do nothing if the user has a custom uncaught exception handler. + if (process.listeners('uncaughtException').length > 1) { + return; + } + // Show error in GUI. + // We can't import { dialog } at the top of this file as this file is + // responsible for setting up the require hook for the "electron" module + // so we import it inside the handler down here + Promise.resolve().then(() => require('electron')).then(({ dialog }) => { + const stack = error.stack ? error.stack : `${error.name}: ${error.message}`; + const message = 'Uncaught Exception:\n' + stack; + dialog.showErrorBox('A JavaScript error occurred in the main process', message); + }); +}); +// Emit 'exit' event on quit. +const { app } = require('electron'); +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']; +if (process.resourcesPath) { + for (packagePath of searchPaths) { + try { + packagePath = path.join(process.resourcesPath, packagePath); + packageJson = require(path.join(packagePath, 'package.json')); + break; + } + catch (_a) { + continue; + } + } +} +if (packageJson == null) { + process.nextTick(function () { + return process.exit(1); + }); + throw new Error('Unable to find a valid app'); +} +// 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); +} +app._setDefaultAppPaths(packagePath); +// Load the chrome devtools support. +require('@electron/internal/browser/devtools'); +// Load the chrome extension support. +require('@electron/internal/browser/chrome-extension'); +const features = process.electronBinding('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'; +} +// Quit when all windows are closed and no other one is listening to this. +app.on('window-all-closed', () => { + if (app.listenerCount('window-all-closed') === 1) { + app.quit(); + } +}); +Promise.all([ + Promise.resolve().then(() => require('@electron/internal/browser/default-menu')), + app.whenReady +]).then(([{ setDefaultApplicationMenu }]) => { + // Create default menu + setDefaultApplicationMenu(); +}); +if (packagePath) { + // Finally load app's main.js and transfer control to C++. + Module._load(path.join(packagePath, mainStartupScript), Module, true); +} +else { + console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)'); + console.error('This normally means you\'ve damaged the Electron package somehow'); +} +//# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/electronasar/development/browser/ipc-main-internal-utils.js b/electronasar/development/browser/ipc-main-internal-utils.js new file mode 100644 index 0000000..6c57a46 --- /dev/null +++ b/electronasar/development/browser/ipc-main-internal-utils.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_main_internal_1 = require("@electron/internal/browser/ipc-main-internal"); +const errorUtils = require("@electron/internal/common/error-utils"); +const callHandler = async function (handler, event, args, reply) { + try { + const result = await handler(event, ...args); + reply([null, result]); + } + catch (error) { + reply([errorUtils.serialize(error)]); + } +}; +exports.handle = function (channel, handler) { + ipc_main_internal_1.ipcMainInternal.on(channel, (event, requestId, ...args) => { + callHandler(handler, event, args, responseArgs => { + if (requestId) { + event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs); + } + else { + event.returnValue = responseArgs; + } + }); + }); +}; +let nextId = 0; +function invokeInWebContents(sender, sendToAll, command, ...args) { + return new Promise((resolve, reject) => { + const requestId = ++nextId; + const channel = `${command}_RESPONSE_${requestId}`; + ipc_main_internal_1.ipcMainInternal.on(channel, function handler(event, error, result) { + if (event.sender !== sender) { + console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`); + return; + } + ipc_main_internal_1.ipcMainInternal.removeListener(channel, handler); + if (error) { + reject(errorUtils.deserialize(error)); + } + else { + resolve(result); + } + }); + if (sendToAll) { + sender._sendInternalToAll(command, requestId, ...args); + } + else { + sender._sendInternal(command, requestId, ...args); + } + }); +} +exports.invokeInWebContents = invokeInWebContents; +//# sourceMappingURL=ipc-main-internal-utils.js.map \ No newline at end of file diff --git a/electronasar/development/browser/ipc-main-internal.js b/electronasar/development/browser/ipc-main-internal.js new file mode 100644 index 0000000..9ec98d1 --- /dev/null +++ b/electronasar/development/browser/ipc-main-internal.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = require("events"); +const emitter = new events_1.EventEmitter(); +// Do not throw exception when channel name is "error". +emitter.on('error', () => { }); +exports.ipcMainInternal = emitter; +//# sourceMappingURL=ipc-main-internal.js.map \ No newline at end of file diff --git a/electronasar/development/browser/navigation-controller.js b/electronasar/development/browser/navigation-controller.js new file mode 100644 index 0000000..a76da41 --- /dev/null +++ b/electronasar/development/browser/navigation-controller.js @@ -0,0 +1,225 @@ +'use strict'; +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); +// The history operation in renderer is redirected to browser. +ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) { + event.sender.goBack(); +}); +ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) { + event.sender.goForward(); +}); +ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) { + event.sender.goToOffset(offset); +}); +ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) { + event.returnValue = event.sender.length(); +}); +// JavaScript implementation of Chromium's NavigationController. +// Instead of relying on Chromium for history control, we compeletely do history +// control on user land, and only rely on WebContents.loadURL for navigation. +// This helps us avoid Chromium's various optimizations so we can ensure renderer +// process is restarted everytime. +const NavigationController = (function () { + function NavigationController(webContents) { + this.webContents = webContents; + this.clearHistory(); + // webContents may have already navigated to a page. + if (this.webContents._getURL()) { + this.currentIndex++; + this.history.push(this.webContents._getURL()); + } + this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => { + if (this.inPageIndex > -1 && !inPage) { + // Navigated to a new page, clear in-page mark. + this.inPageIndex = -1; + } + else if (this.inPageIndex === -1 && inPage && !replaceEntry) { + // Started in-page navigations. + this.inPageIndex = this.currentIndex; + } + if (this.pendingIndex >= 0) { + // Go to index. + this.currentIndex = this.pendingIndex; + this.pendingIndex = -1; + this.history[this.currentIndex] = url; + } + else if (replaceEntry) { + // Non-user initialized navigation. + this.history[this.currentIndex] = url; + } + else { + // Normal navigation. Clear history. + this.history = this.history.slice(0, this.currentIndex + 1); + this.currentIndex++; + this.history.push(url); + } + }); + } + NavigationController.prototype.loadURL = function (url, options) { + if (options == null) { + options = {}; + } + const p = new Promise((resolve, reject) => { + const resolveAndCleanup = () => { + removeListeners(); + resolve(); + }; + const rejectAndCleanup = (errorCode, errorDescription, url) => { + const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`); + Object.assign(err, { errno: errorCode, code: errorDescription, url }); + removeListeners(); + reject(err); + }; + const finishListener = () => { + resolveAndCleanup(); + }; + const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => { + if (isMainFrame) { + rejectAndCleanup(errorCode, errorDescription, validatedURL); + } + }; + let navigationStarted = false; + const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => { + if (isMainFrame) { + if (navigationStarted && !isSameDocument) { + // the webcontents has started another unrelated navigation in the + // main frame (probably from the app calling `loadURL` again); reject + // the promise + // We should only consider the request aborted if the "navigation" is + // actually navigating and not simply transitioning URL state in the + // current context. E.g. pushState and `location.hash` changes are + // considered navigation events but are triggered with isSameDocument. + // We can ignore these to allow virtual routing on page load as long + // as the routing does not leave the document + return rejectAndCleanup(-3, 'ERR_ABORTED', url); + } + navigationStarted = true; + } + }; + const stopLoadingListener = () => { + // By the time we get here, either 'finish' or 'fail' should have fired + // if the navigation occurred. However, in some situations (e.g. when + // attempting to load a page with a bad scheme), loading will stop + // without emitting finish or fail. In this case, we reject the promise + // with a generic failure. + // TODO(jeremy): enumerate all the cases in which this can happen. If + // the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT + // would be more appropriate. + rejectAndCleanup(-2, 'ERR_FAILED', url); + }; + const removeListeners = () => { + this.webContents.removeListener('did-finish-load', finishListener); + this.webContents.removeListener('did-fail-load', failListener); + this.webContents.removeListener('did-start-navigation', navigationListener); + this.webContents.removeListener('did-stop-loading', stopLoadingListener); + }; + this.webContents.on('did-finish-load', finishListener); + this.webContents.on('did-fail-load', failListener); + this.webContents.on('did-start-navigation', navigationListener); + this.webContents.on('did-stop-loading', stopLoadingListener); + }); + // Add a no-op rejection handler to silence the unhandled rejection error. + p.catch(() => { }); + this.pendingIndex = -1; + this.webContents._loadURL(url, options); + this.webContents.emit('load-url', url, options); + return p; + }; + NavigationController.prototype.getURL = function () { + if (this.currentIndex === -1) { + return ''; + } + else { + return this.history[this.currentIndex]; + } + }; + NavigationController.prototype.stop = function () { + this.pendingIndex = -1; + return this.webContents._stop(); + }; + NavigationController.prototype.reload = function () { + this.pendingIndex = this.currentIndex; + return this.webContents._loadURL(this.getURL(), {}); + }; + NavigationController.prototype.reloadIgnoringCache = function () { + this.pendingIndex = this.currentIndex; + return this.webContents._loadURL(this.getURL(), { + extraHeaders: 'pragma: no-cache\n' + }); + }; + NavigationController.prototype.canGoBack = function () { + return this.getActiveIndex() > 0; + }; + NavigationController.prototype.canGoForward = function () { + return this.getActiveIndex() < this.history.length - 1; + }; + NavigationController.prototype.canGoToIndex = function (index) { + return index >= 0 && index < this.history.length; + }; + NavigationController.prototype.canGoToOffset = function (offset) { + return this.canGoToIndex(this.currentIndex + offset); + }; + NavigationController.prototype.clearHistory = function () { + this.history = []; + this.currentIndex = -1; + this.pendingIndex = -1; + this.inPageIndex = -1; + }; + NavigationController.prototype.goBack = function () { + if (!this.canGoBack()) { + return; + } + this.pendingIndex = this.getActiveIndex() - 1; + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goBack(); + } + else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + } + }; + NavigationController.prototype.goForward = function () { + if (!this.canGoForward()) { + return; + } + this.pendingIndex = this.getActiveIndex() + 1; + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goForward(); + } + else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + } + }; + NavigationController.prototype.goToIndex = function (index) { + if (!this.canGoToIndex(index)) { + return; + } + this.pendingIndex = index; + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + }; + NavigationController.prototype.goToOffset = function (offset) { + if (!this.canGoToOffset(offset)) { + return; + } + const pendingIndex = this.currentIndex + offset; + if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { + this.pendingIndex = pendingIndex; + return this.webContents._goToOffset(offset); + } + else { + return this.goToIndex(pendingIndex); + } + }; + NavigationController.prototype.getActiveIndex = function () { + if (this.pendingIndex === -1) { + return this.currentIndex; + } + else { + return this.pendingIndex; + } + }; + NavigationController.prototype.length = function () { + return this.history.length; + }; + return NavigationController; +})(); +module.exports = NavigationController; +//# sourceMappingURL=navigation-controller.js.map \ No newline at end of file diff --git a/electronasar/development/browser/objects-registry.js b/electronasar/development/browser/objects-registry.js new file mode 100644 index 0000000..5e28043 --- /dev/null +++ b/electronasar/development/browser/objects-registry.js @@ -0,0 +1,121 @@ +'use strict'; +const v8Util = process.electronBinding('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 + refCounts of objects referenced by WebContents. + // (ownerKey) => { id: refCount } + 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 Map(); + this.registerDeleteListener(webContents, contextId); + } + if (!owner.has(id)) { + owner.set(id, 0); + // Increase reference count if not referenced before. + this.storage[id].count++; + } + owner.set(id, owner.get(id) + 1); + return id; + } + // 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). + // rendererSideRefCount is the ref count that the renderer process reported + // at time of GC if this is different to the number of references we sent to + // the given owner then a GC occurred between a ref being sent and the value + // being pulled out of the weak map. + // In this case we decrement out ref count and do not delete the stored + // object + // For more details on why we do renderer side ref counting see + // https://github.com/electron/electron/pull/17464 + remove(webContents, contextId, id, rendererSideRefCount) { + const ownerKey = getOwnerKey(webContents, contextId); + const owner = this.owners[ownerKey]; + if (owner && owner.has(id)) { + const newRefCount = owner.get(id) - rendererSideRefCount; + // Only completely remove if the number of references GCed in the + // renderer is the same as the number of references we sent them + if (newRefCount <= 0) { + // Remove the reference in owner. + owner.delete(id); + // Dereference from the storage. + this.dereference(id); + } + else { + owner.set(id, newRefCount); + } + } + } + // Clear all references to objects refrenced by the WebContents. + clear(webContents, contextId) { + const ownerKey = getOwnerKey(webContents, contextId); + const owner = this.owners[ownerKey]; + if (!owner) + return; + for (const id of owner.keys()) + this.dereference(id); + delete this.owners[ownerKey]; + } + // 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(); +//# sourceMappingURL=objects-registry.js.map \ No newline at end of file diff --git a/electronasar/development/browser/rpc-server.js b/electronasar/development/browser/rpc-server.js new file mode 100644 index 0000000..1886c7c --- /dev/null +++ b/electronasar/development/browser/rpc-server.js @@ -0,0 +1,482 @@ +'use strict'; +const electron = require('electron'); +const { EventEmitter } = require('events'); +const fs = require('fs'); +const util = require('util'); +const v8Util = process.electronBinding('v8_util'); +const eventBinding = process.electronBinding('event'); +const clipboard = process.electronBinding('clipboard'); +const { isPromise } = electron; +const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init'); +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); +const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); +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 clipboardUtils = require('@electron/internal/common/clipboard-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 (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, frameId, 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, frameId, 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) { + let succeed = false; + if (!sender.isDestroyed()) { + succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args)); + } + if (!succeed) { + 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); +}; +const isRemoteModuleEnabledCache = new WeakMap(); +const isRemoteModuleEnabled = function (contents) { + if (!isRemoteModuleEnabledCache.has(contents)) { + isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled()); + } + return isRemoteModuleEnabledCache.get(contents); +}; +const handleRemoteCommand = function (channel, handler) { + ipcMainInternal.on(channel, (event, contextId, ...args) => { + let returnValue; + if (!isRemoteModuleEnabled(event.sender)) { + event.returnValue = null; + return; + } + try { + returnValue = handler(event, contextId, ...args); + } + catch (error) { + returnValue = exceptionToMeta(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, event.frameId, 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, event.frameId, contextId, args); + const func = objectsRegistry.get(id); + if (func == null) { + throwRPCError(`Cannot call function on missing remote object ${id}`); + } + try { + return valueToMeta(event.sender, contextId, func(...args), true); + } + catch (error) { + const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); + err.cause = error; + throw err; + } +}); +handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { + args = unwrapArgs(event.sender, event.frameId, 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, event.frameId, contextId, args); + const object = objectsRegistry.get(id); + if (object == null) { + throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`); + } + try { + return valueToMeta(event.sender, contextId, object[method](...args), true); + } + catch (error) { + const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); + err.cause = error; + throw err; + } +}); +handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { + args = unwrapArgs(event.sender, event.frameId, 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, rendererSideRefCount) { + objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount); +}); +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() +ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { + const window = event.sender.getOwnerBrowserWindow(); + if (window) { + window.close(); + } + event.returnValue = null; +}); +ipcMainUtils.handle('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { + return crashReporterInit(options); +}); +ipcMainUtils.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { + return event.sender.getLastWebPreferences(); +}); +// Methods not listed in this set are called directly in the renderer process. +const allowedClipboardMethods = (() => { + switch (process.platform) { + case 'darwin': + return new Set(['readFindText', 'writeFindText']); + case 'linux': + return new Set(Object.keys(clipboard)); + default: + return new Set(); + } +})(); +ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { + if (!allowedClipboardMethods.has(method)) { + throw new Error(`Invalid method: ${method}`); + } + return clipboardUtils.serialize(electron.clipboard[method](...clipboardUtils.deserialize(args))); +}); +const readFile = util.promisify(fs.readFile); +const getPreloadScript = async function (preloadPath) { + let preloadSrc = null; + let preloadError = null; + if (preloadPath) { + try { + preloadSrc = (await readFile(preloadPath)).toString(); + } + catch (err) { + preloadError = errorUtils.serialize(err); + } + } + return { preloadPath, preloadSrc, preloadError }; +}; +ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { + const preloadPaths = [ + ...(event.sender.session ? event.sender.session.getPreloads() : []), + event.sender._getPreloadPath() + ]; + return { + preloadScripts: await Promise.all(preloadPaths.map(path => getPreloadScript(path))), + isRemoteModuleEnabled: isRemoteModuleEnabled(event.sender), + isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender), + process: { + arch: process.arch, + platform: process.platform, + env: process.env, + version: process.version, + versions: process.versions, + execPath: process.helperExecPath + } + }; +}); +ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) { + event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error)); +}); +//# sourceMappingURL=rpc-server.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/clipboard.js b/electronasar/development/common/api/clipboard.js new file mode 100644 index 0000000..21a5197 --- /dev/null +++ b/electronasar/development/common/api/clipboard.js @@ -0,0 +1,26 @@ +'use strict'; +const clipboard = process.electronBinding('clipboard'); +if (process.type === 'renderer') { + const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); + const clipboardUtils = require('@electron/internal/common/clipboard-utils'); + const makeRemoteMethod = function (method) { + return (...args) => { + args = clipboardUtils.serialize(args); + const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args); + return clipboardUtils.deserialize(result); + }; + }; + if (process.platform === 'linux') { + // On Linux we could not access clipboard in renderer process. + for (const method of Object.keys(clipboard)) { + clipboard[method] = makeRemoteMethod(method); + } + } + else if (process.platform === 'darwin') { + // Read/write to find pasteboard over IPC since only main process is notified of changes + clipboard.readFindText = makeRemoteMethod('readFindText'); + clipboard.writeFindText = makeRemoteMethod('writeFindText'); + } +} +module.exports = clipboard; +//# sourceMappingURL=clipboard.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/deprecate.js b/electronasar/development/common/api/deprecate.js new file mode 100644 index 0000000..986c472 --- /dev/null +++ b/electronasar/development/common/api/deprecate.js @@ -0,0 +1,187 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +let deprecationHandler = null; +function warnOnce(oldName, newName) { + let warned = false; + const msg = newName + ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.` + : `'${oldName}' is deprecated and will be removed.`; + return () => { + if (!warned && !process.noDeprecation) { + warned = true; + deprecate.log(msg); + } + }; +} +const deprecate = { + setHandler: (handler) => { deprecationHandler = handler; }, + getHandler: () => deprecationHandler, + warn: (oldName, newName) => { + if (!process.noDeprecation) { + deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`); + } + }, + log: (message) => { + if (typeof deprecationHandler === 'function') { + deprecationHandler(message); + } + else if (process.throwDeprecation) { + throw new Error(message); + } + else if (process.traceDeprecation) { + return console.trace(message); + } + else { + return console.warn(`(electron) ${message}`); + } + }, + // remove a function with no replacement + removeFunction: (fn, removedName) => { + if (!fn) { + throw Error(`'${removedName} function' is invalid or does not exist.`); + } + // wrap the deprecated function to warn user + const warn = warnOnce(`${fn.name} function`); + return function () { + warn(); + fn.apply(this, arguments); + }; + }, + // change the name of a function + renameFunction: (fn, newName) => { + const warn = warnOnce(`${fn.name} function`, `${newName} function`); + return function () { + warn(); + fn.apply(this, arguments); + }; + }, + // change the name of an event + event: (emitter, oldName, newName) => { + const warn = newName.startsWith('-') /* internal event */ + ? warnOnce(`${oldName} event`) + : warnOnce(`${oldName} event`, `${newName} event`); + return emitter.on(newName, function (...args) { + if (this.listenerCount(oldName) !== 0) { + warn(); + this.emit(oldName, ...args); + } + }); + }, + // deprecate a getter/setter function pair in favor of a property + fnToProperty: (module, prop, getter, setter) => { + const withWarnOnce = (obj, key, oldName, newName) => { + const warn = warnOnce(oldName, newName); + return (...args) => { + warn(); + return obj[key](...args); + }; + }; + module[getter.substr(1)] = withWarnOnce(module, getter, `${getter.substr(1)} function`, `${prop} property`); + module[setter.substr(1)] = withWarnOnce(module, setter, `${setter.substr(1)} function`, `${prop} property`); + }, + // remove a property with no replacement + removeProperty: (o, removedName) => { + // if the property's already been removed, warn about it + if (!(removedName in o)) { + deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`); + } + // wrap the deprecated property in an accessor to warn + const warn = warnOnce(removedName); + let val = o[removedName]; + return Object.defineProperty(o, removedName, { + configurable: true, + get: () => { + warn(); + return val; + }, + set: newVal => { + warn(); + val = newVal; + } + }); + }, + // deprecate a callback-based function in favor of one returning a Promise + promisify: (fn) => { + const fnName = fn.name || 'function'; + const oldName = `${fnName} with callbacks`; + const newName = `${fnName} with Promises`; + const warn = warnOnce(oldName, newName); + return function (...params) { + let cb; + if (params.length > 0 && typeof params[params.length - 1] === 'function') { + cb = params.pop(); + } + const promise = fn.apply(this, params); + if (!cb) + return promise; + if (process.enablePromiseAPIs) + warn(); + return promise + .then((res) => { + process.nextTick(() => { + cb.length === 2 ? cb(null, res) : cb(res); + }); + return res; + }, (err) => { + process.nextTick(() => { + cb.length === 2 ? cb(err) : cb(); + }); + throw err; + }); + }; + }, + // deprecate a callback-based function in favor of one returning a Promise + promisifyMultiArg: (fn, convertPromiseValue) => { + const fnName = fn.name || 'function'; + const oldName = `${fnName} with callbacks`; + const newName = `${fnName} with Promises`; + const warn = warnOnce(oldName, newName); + return function (...params) { + let cb; + if (params.length > 0 && typeof params[params.length - 1] === 'function') { + cb = params.pop(); + } + const promise = fn.apply(this, params); + if (!cb) + return promise; + if (process.enablePromiseAPIs) + warn(); + return promise + .then((res) => { + if (convertPromiseValue) { + res = convertPromiseValue(res); + } + process.nextTick(() => { + // eslint-disable-next-line standard/no-callback-literal + cb.length > 2 ? cb(null, ...res) : cb(...res); + }); + }, (err) => { + process.nextTick(() => cb(err)); + }); + }; + }, + // change the name of a property + renameProperty: (o, oldName, newName) => { + const warn = warnOnce(oldName, newName); + // if the new property isn't there yet, + // inject it and warn about it + if ((oldName in o) && !(newName in o)) { + warn(); + o[newName] = o[oldName]; + } + // wrap the deprecated property in an accessor to warn + // and redirect to the new property + return Object.defineProperty(o, oldName, { + get: () => { + warn(); + return o[newName]; + }, + set: value => { + warn(); + o[newName] = value; + } + }); + } +}; +exports.default = deprecate; +//# sourceMappingURL=deprecate.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/deprecations.js b/electronasar/development/common/api/deprecations.js new file mode 100644 index 0000000..cf54b25 --- /dev/null +++ b/electronasar/development/common/api/deprecations.js @@ -0,0 +1,9 @@ +'use strict'; +const deprecate = require('electron').deprecate; +exports.setHandler = function (deprecationHandler) { + deprecate.setHandler(deprecationHandler); +}; +exports.getHandler = function () { + return deprecate.getHandler(); +}; +//# sourceMappingURL=deprecations.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/exports/electron.js b/electronasar/development/common/api/exports/electron.js new file mode 100644 index 0000000..6ed49e0 --- /dev/null +++ b/electronasar/development/common/api/exports/electron.js @@ -0,0 +1,34 @@ +'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(() => { + const value = require(`@electron/internal/common/api/${module.file}.js`); + // Handle Typescript modules with an "export default X" statement + if (value.__esModule) + return value.default; + return value; + }) + }; + } + return Object.defineProperties(targetExports, descriptors); +}; +//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/is-promise.js b/electronasar/development/common/api/is-promise.js new file mode 100644 index 0000000..df119d4 --- /dev/null +++ b/electronasar/development/common/api/is-promise.js @@ -0,0 +1,12 @@ +'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); +}; +//# sourceMappingURL=is-promise.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/module-list.js b/electronasar/development/common/api/module-list.js new file mode 100644 index 0000000..f88500d --- /dev/null +++ b/electronasar/development/common/api/module-list.js @@ -0,0 +1,12 @@ +'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 } +]; +//# sourceMappingURL=module-list.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/native-image.js b/electronasar/development/common/api/native-image.js new file mode 100644 index 0000000..8a373b0 --- /dev/null +++ b/electronasar/development/common/api/native-image.js @@ -0,0 +1,4 @@ +'use strict'; +const { nativeImage } = process.electronBinding('native_image'); +module.exports = nativeImage; +//# sourceMappingURL=native-image.js.map \ No newline at end of file diff --git a/electronasar/development/common/api/shell.js b/electronasar/development/common/api/shell.js new file mode 100644 index 0000000..8889c22 --- /dev/null +++ b/electronasar/development/common/api/shell.js @@ -0,0 +1,3 @@ +'use strict'; +module.exports = process.electronBinding('shell'); +//# sourceMappingURL=shell.js.map \ No newline at end of file diff --git a/electronasar/development/common/atom-binding-setup.js b/electronasar/development/common/atom-binding-setup.js new file mode 100644 index 0000000..d074421 --- /dev/null +++ b/electronasar/development/common/atom-binding-setup.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function electronBindingSetup(binding, processType) { + return function electronBinding(name) { + try { + return binding(`atom_${processType}_${name}`); + } + catch (error) { + if (/No such module/.test(error.message)) { + return binding(`atom_common_${name}`); + } + else { + throw error; + } + } + }; +} +exports.electronBindingSetup = electronBindingSetup; +//# sourceMappingURL=atom-binding-setup.js.map \ No newline at end of file diff --git a/electronasar/development/common/buffer-utils.js b/electronasar/development/common/buffer-utils.js new file mode 100644 index 0000000..7b08164 --- /dev/null +++ b/electronasar/development/common/buffer-utils.js @@ -0,0 +1,64 @@ +'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; + } +}; +//# sourceMappingURL=buffer-utils.js.map \ No newline at end of file diff --git a/electronasar/development/common/clipboard-utils.js b/electronasar/development/common/clipboard-utils.js new file mode 100644 index 0000000..d6d89f0 --- /dev/null +++ b/electronasar/development/common/clipboard-utils.js @@ -0,0 +1,50 @@ +'use strict'; +const { nativeImage, NativeImage } = process.electronBinding('native_image'); +const objectMap = function (source, mapper) { + const sourceEntries = Object.entries(source); + const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]); + return Object.fromEntries(targetEntries); +}; +const serialize = function (value) { + if (value instanceof NativeImage) { + return { + buffer: value.toBitmap(), + size: value.getSize(), + __ELECTRON_SERIALIZED_NativeImage__: true + }; + } + else if (Array.isArray(value)) { + return value.map(serialize); + } + else if (value instanceof Buffer) { + return value; + } + else if (value instanceof Object) { + return objectMap(value, serialize); + } + else { + return value; + } +}; +const deserialize = function (value) { + if (value && value.__ELECTRON_SERIALIZED_NativeImage__) { + return nativeImage.createFromBitmap(value.buffer, value.size); + } + else if (Array.isArray(value)) { + return value.map(deserialize); + } + else if (value instanceof Buffer) { + return value; + } + else if (value instanceof Object) { + return objectMap(value, deserialize); + } + else { + return value; + } +}; +module.exports = { + serialize, + deserialize +}; +//# sourceMappingURL=clipboard-utils.js.map \ No newline at end of file diff --git a/electronasar/development/common/crash-reporter.js b/electronasar/development/common/crash-reporter.js new file mode 100644 index 0000000..923feca --- /dev/null +++ b/electronasar/development/common/crash-reporter.js @@ -0,0 +1,78 @@ +'use strict'; +const binding = process.electronBinding('crash_reporter'); +class CrashReporter { + contructor() { + this.productName = null; + this.crashesDirectory = null; + } + init(options) { + throw new Error('Not implemented'); + } + start(options) { + if (options == null) + options = {}; + const { productName, companyName, extra = {}, ignoreSystemCrashHandler = false, submitURL, uploadToServer = true } = options; + if (companyName == null) + throw new Error('companyName is a required option to crashReporter.start'); + if (submitURL == null) + throw new Error('submitURL is a required option to crashReporter.start'); + const ret = this.init({ + submitURL, + productName + }); + this.productName = ret.productName; + this.crashesDirectory = ret.crashesDirectory; + if (extra._productName == null) + extra._productName = ret.productName; + if (extra._companyName == null) + extra._companyName = companyName; + if (extra._version == null) + extra._version = ret.appVersion; + binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra); + } + 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() { + return binding.getParameters(); + } +} +module.exports = CrashReporter; +//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file diff --git a/electronasar/development/common/error-utils.js b/electronasar/development/common/error-utils.js new file mode 100644 index 0000000..ecfd055 --- /dev/null +++ b/electronasar/development/common/error-utils.js @@ -0,0 +1,37 @@ +'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; +}; +//# sourceMappingURL=error-utils.js.map \ No newline at end of file diff --git a/electronasar/development/common/init.js b/electronasar/development/common/init.js new file mode 100644 index 0000000..3c233ea --- /dev/null +++ b/electronasar/development/common/init.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const util = require("util"); +const atom_binding_setup_1 = require("@electron/internal/common/atom-binding-setup"); +const timers = require('timers'); +process.electronBinding = atom_binding_setup_1.electronBindingSetup(process._linkedBinding, process.type); +// setImmediate and process.nextTick makes use of uv_check and uv_prepare to +// run the callbacks, however since we only run uv loop on requests, the +// callbacks wouldn't be called until something else activated the uv loop, +// 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 (...args) { + process.activateUvLoop(); + return func.apply(this, args); + }; + }); +}; +/** + * Casts to any below for func are due to Typescript not supporting symbols + * in index signatures + * + * Refs: https://github.com/Microsoft/TypeScript/issues/1863 + */ +function wrap(func, wrapper) { + const wrapped = wrapper(func); + if (func[util.promisify.custom]) { + wrapped[util.promisify.custom] = wrapper(func[util.promisify.custom]); + } + return wrapped; +} +process.nextTick = wrapWithActivateUvLoop(process.nextTick); +global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate); +global.clearImmediate = timers.clearImmediate; +// setTimeout needs to update the polling timeout of the event loop, when +// called under Chromium's event loop the node's event loop won't get a chance +// to update the timeout, so we have to force the node's event loop to +// recalculate the timeout in browser process. +timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout); +timers.setInterval = wrapWithActivateUvLoop(timers.setInterval); +// Only override the global setTimeout/setInterval impls in the browser process +if (process.type === 'browser') { + global.setTimeout = timers.setTimeout; + global.setInterval = timers.setInterval; +} +if (process.platform === 'win32') { + // Always returns EOF for stdin stream. + const { Readable } = require('stream'); + const stdin = new Readable(); + stdin.push(null); + Object.defineProperty(process, 'stdin', { + configurable: false, + enumerable: true, + get() { + return stdin; + } + }); +} +//# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/electronasar/development/common/parse-features-string.js b/electronasar/development/common/parse-features-string.js new file mode 100644 index 0000000..26ca6ff --- /dev/null +++ b/electronasar/development/common/parse-features-string.js @@ -0,0 +1,20 @@ +'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}`.trim(); + // split the string by ',' + features.split(/\s*,\s*/).forEach((feature) => { + // expected form is either a key by itself or a key/value pair in the form of + // 'key=value' + let [key, value] = feature.split(/\s*=\s*/); + if (!key) + return; + // interpret the value as a boolean, if possible + value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value; + // emit the parsed pair + emit(key, value); + }); +}; +//# sourceMappingURL=parse-features-string.js.map \ No newline at end of file diff --git a/electronasar/development/common/reset-search-paths.js b/electronasar/development/common/reset-search-paths.js new file mode 100644 index 0000000..acb19bb --- /dev/null +++ b/electronasar/development/common/reset-search-paths.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const Module = require('module'); +// Clear Node's global search paths. +Module.globalPaths.length = 0; +// 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); + } +}; +//# sourceMappingURL=reset-search-paths.js.map \ No newline at end of file diff --git a/electronasar/development/common/web-view-methods.js b/electronasar/development/common/web-view-methods.js new file mode 100644 index 0000000..b9f548f --- /dev/null +++ b/electronasar/development/common/web-view-methods.js @@ -0,0 +1,69 @@ +'use strict'; +// Public-facing API methods. +exports.syncMethods = new Set([ + 'getURL', + 'getTitle', + 'isLoading', + 'isLoadingMainFrame', + 'isWaitingForResponse', + 'stop', + 'reload', + 'reloadIgnoringCache', + 'canGoBack', + 'canGoForward', + 'canGoToOffset', + 'clearHistory', + 'goBack', + 'goForward', + 'goToIndex', + 'goToOffset', + 'isCrashed', + 'setUserAgent', + 'getUserAgent', + 'openDevTools', + 'closeDevTools', + 'isDevToolsOpened', + 'isDevToolsFocused', + 'inspectElement', + 'setAudioMuted', + 'isAudioMuted', + 'isCurrentlyAudible', + 'undo', + 'redo', + 'cut', + 'copy', + 'paste', + 'pasteAndMatchStyle', + 'delete', + 'selectAll', + 'unselect', + 'replace', + 'replaceMisspelling', + 'findInPage', + 'stopFindInPage', + 'downloadURL', + 'inspectSharedWorker', + 'inspectServiceWorker', + 'showDefinitionForSelection', + 'getZoomFactor', + 'getZoomLevel', + 'setZoomFactor', + 'setZoomLevel', + 'sendImeEvent' +]); +exports.asyncCallbackMethods = new Set([ + 'insertCSS', + 'insertText', + 'send', + 'sendInputEvent', + 'setLayoutZoomLevelLimits', + 'setVisualZoomLevelLimits', + 'print' +]); +exports.asyncPromiseMethods = new Set([ + 'loadURL', + 'capturePage', + 'executeJavaScript', + 'printToPDF' +]); +//# sourceMappingURL=web-view-methods.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/crash-reporter.js b/electronasar/development/renderer/api/crash-reporter.js new file mode 100644 index 0000000..59ea3b5 --- /dev/null +++ b/electronasar/development/renderer/api/crash-reporter.js @@ -0,0 +1,10 @@ +'use strict'; +const CrashReporter = require('@electron/internal/common/crash-reporter'); +const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +class CrashReporterRenderer extends CrashReporter { + init(options) { + return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options); + } +} +module.exports = new CrashReporterRenderer(); +//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/desktop-capturer.js b/electronasar/development/renderer/api/desktop-capturer.js new file mode 100644 index 0000000..f77c21a --- /dev/null +++ b/electronasar/development/renderer/api/desktop-capturer.js @@ -0,0 +1,38 @@ +'use strict'; +const { nativeImage, deprecate } = require('electron'); +const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +// |options.types| can't be empty and must be an array +function isValid(options) { + const types = options ? options.types : undefined; + return Array.isArray(types); +} +function mapSources(sources) { + return sources.map(source => ({ + id: source.id, + name: source.name, + thumbnail: nativeImage.createFromDataURL(source.thumbnail), + display_id: source.display_id, + appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null + })); +} +const getSources = (options) => { + return new Promise((resolve, reject) => { + if (!isValid(options)) + throw new Error('Invalid options'); + const captureWindow = options.types.includes('window'); + const captureScreen = options.types.includes('screen'); + if (options.thumbnailSize == null) { + options.thumbnailSize = { + width: 150, + height: 150 + }; + } + if (options.fetchWindowIcons == null) { + options.fetchWindowIcons = false; + } + ipcRendererUtils.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, options.fetchWindowIcons) + .then(sources => resolve(mapSources(sources)), reject); + }); +}; +exports.getSources = deprecate.promisify(getSources); +//# sourceMappingURL=desktop-capturer.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/exports/electron.js b/electronasar/development/renderer/api/exports/electron.js new file mode 100644 index 0000000..3702ebe --- /dev/null +++ b/electronasar/development/renderer/api/exports/electron.js @@ -0,0 +1,21 @@ +'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(() => { + const value = require(`@electron/internal/renderer/api/${file}.js`); + // Handle Typescript modules with an "export default X" statement + if (value.__esModule) + return value.default; + return value; + }) + }); +} +//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/ipc-renderer.js b/electronasar/development/renderer/api/ipc-renderer.js new file mode 100644 index 0000000..fa5e2b9 --- /dev/null +++ b/electronasar/development/renderer/api/ipc-renderer.js @@ -0,0 +1,23 @@ +'use strict'; +const { ipc } = process.electronBinding('ipc'); +const v8Util = process.electronBinding('v8_util'); +// Created by init.js. +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); +const internal = false; +ipcRenderer.send = function (channel, ...args) { + return ipc.send(internal, channel, args); +}; +ipcRenderer.sendSync = function (channel, ...args) { + return ipc.sendSync(internal, channel, args)[0]; +}; +ipcRenderer.sendToHost = function (channel, ...args) { + return ipc.sendToHost(channel, args); +}; +ipcRenderer.sendTo = function (webContentsId, channel, ...args) { + return ipc.sendTo(internal, false, webContentsId, channel, args); +}; +ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { + return ipc.sendTo(internal, true, webContentsId, channel, args); +}; +module.exports = ipcRenderer; +//# sourceMappingURL=ipc-renderer.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/module-list.js b/electronasar/development/renderer/api/module-list.js new file mode 100644 index 0000000..c27561b --- /dev/null +++ b/electronasar/development/renderer/api/module-list.js @@ -0,0 +1,18 @@ +'use strict'; +const features = process.electronBinding('features'); +const v8Util = process.electronBinding('v8_util'); +const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule'); +// Renderer side modules, please sort alphabetically. +// A module is `enabled` if there is no explicit condition defined. +module.exports = [ + { name: 'crashReporter', file: 'crash-reporter', enabled: true }, + { + name: 'desktopCapturer', + file: 'desktop-capturer', + enabled: features.isDesktopCapturerEnabled() + }, + { name: 'ipcRenderer', file: 'ipc-renderer' }, + { name: 'remote', file: 'remote', enabled: enableRemoteModule }, + { name: 'webFrame', file: 'web-frame' } +]; +//# sourceMappingURL=module-list.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/remote.js b/electronasar/development/renderer/api/remote.js new file mode 100644 index 0000000..57a75d3 --- /dev/null +++ b/electronasar/development/renderer/api/remote.js @@ -0,0 +1,340 @@ +'use strict'; +const v8Util = process.electronBinding('v8_util'); +const { isPromise } = require('electron'); +const resolvePromise = Promise.resolve.bind(Promise); +const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry'); +const bufferUtils = require('@electron/internal/common/buffer-utils'); +const errorUtils = require('@electron/internal/common/error-utils'); +const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); +const callbacksRegistry = new CallbacksRegistry(); +const remoteObjectCache = v8Util.createIDWeakMap(); +// An unique ID that can represent current context. +const contextId = v8Util.getHiddenValue(global, 'contextId'); +// 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'; + ipcRendererInternal.sendSync(command, contextId); +}); +// Convert the arguments object into an array of meta data. +function wrapArgs(args, visited = new Set()) { + const valueToMeta = (value) => { + // Check for circular reference. + if (visited.has(value)) { + return { + type: 'value', + value: null + }; + } + if (Array.isArray(value)) { + visited.add(value); + const meta = { + type: 'array', + value: wrapArgs(value, visited) + }; + visited.delete(value); + return meta; + } + else if (bufferUtils.isBuffer(value)) { + return { + type: 'buffer', + value: bufferUtils.bufferToMeta(value) + }; + } + else if (value instanceof Date) { + return { + type: 'date', + value: value.getTime() + }; + } + else if ((value != null) && typeof value === 'object') { + if (isPromise(value)) { + return { + type: 'promise', + then: valueToMeta(function (onFulfilled, onRejected) { + value.then(onFulfilled, onRejected); + }) + }; + } + 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 = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args)); + return metaToValue(ret); + }; + let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name); + descriptor.get = () => { + descriptorFunction.ref = ref; // The member should reference its object. + return descriptorFunction; + }; + // Enable monkey-patch the method + descriptor.set = (value) => { + descriptorFunction = value; + return value; + }; + descriptor.configurable = true; + } + else if (member.type === 'get') { + descriptor.get = () => { + const command = 'ELECTRON_BROWSER_MEMBER_GET'; + const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name); + return metaToValue(meta); + }; + if (member.writable) { + descriptor.set = (value) => { + const args = wrapArgs([value]); + const command = 'ELECTRON_BROWSER_MEMBER_SET'; + const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args); + if (meta != null) + metaToValue(meta); + return value; + }; + } + } + Object.defineProperty(object, member.name, descriptor); + } +} +// 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 = ipcRendererInternal.sendSync(command, contextId, metaId, name); + setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members); + }; + return new Proxy(remoteMemberFunction, { + set: (target, property, value, receiver) => { + if (property !== 'ref') + loadRemoteProperties(); + target[property] = value; + return true; + }, + get: (target, property, receiver) => { + if (!target.hasOwnProperty(property)) + loadRemoteProperties(); + const value = target[property]; + if (property === 'toString' && typeof value === 'function') { + return value.bind(target); + } + return value; + }, + ownKeys: (target) => { + loadRemoteProperties(); + return Object.getOwnPropertyNames(target); + }, + getOwnPropertyDescriptor: (target, property) => { + const descriptor = Object.getOwnPropertyDescriptor(target, property); + if (descriptor) + return descriptor; + loadRemoteProperties(); + return Object.getOwnPropertyDescriptor(target, property); + } + }); +} +// Convert meta data from browser into real value. +function metaToValue(meta) { + const types = { + value: () => meta.value, + array: () => meta.members.map((member) => metaToValue(member)), + buffer: () => bufferUtils.metaToBuffer(meta.value), + promise: () => resolvePromise({ then: metaToValue(meta.then) }), + error: () => metaToPlainObject(meta), + date: () => new Date(meta.value), + exception: () => { throw errorUtils.deserialize(meta.value); } + }; + if (meta.type in types) { + return types[meta.type](); + } + else { + let ret; + if (remoteObjectCache.has(meta.id)) { + v8Util.addRemoteObjectRef(contextId, meta.id); + return remoteObjectCache.get(meta.id); + } + // A shadow class to represent the remote function object. + if (meta.type === 'function') { + const remoteFunction = function (...args) { + let command; + if (this && this.constructor === remoteFunction) { + command = 'ELECTRON_BROWSER_CONSTRUCTOR'; + } + else { + command = 'ELECTRON_BROWSER_FUNCTION_CALL'; + } + const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args)); + return metaToValue(obj); + }; + ret = remoteFunction; + } + else { + ret = {}; + } + setObjectMembers(ret, ret, meta.id, meta.members); + setObjectPrototype(ret, ret, meta.id, meta.proto); + Object.defineProperty(ret.constructor, 'name', { value: meta.name }); + // Track delegate obj's lifetime & tell browser to clean up when object is GCed. + v8Util.setRemoteObjectFreer(ret, contextId, meta.id); + v8Util.setHiddenValue(ret, 'atomId', meta.id); + v8Util.addRemoteObjectRef(contextId, meta.id); + remoteObjectCache.set(meta.id, ret); + return ret; + } +} +// Construct a plain object from the meta. +function metaToPlainObject(meta) { + const obj = (() => meta.type === 'error' ? new Error() : {})(); + for (let i = 0; i < meta.members.length; i++) { + const { name, value } = meta.members[i]; + obj[name] = value; + } + return obj; +} +function handleMessage(channel, handler) { + ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => { + if (passedContextId === contextId) { + handler(id, ...args); + } + else { + // Message sent to an un-exist context, notify the error to main process. + ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id); + } + }); +} +// Browser calls a callback in renderer. +handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => { + callbacksRegistry.apply(id, metaToValue(args)); +}); +// 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 = ipcRendererInternal.sendSync(command, contextId, module); + return metaToValue(meta); +}; +// Alias to remote.require('electron').xxx. +exports.getBuiltin = (module) => { + const command = 'ELECTRON_BROWSER_GET_BUILTIN'; + const meta = ipcRendererInternal.sendSync(command, contextId, module); + return metaToValue(meta); +}; +exports.getCurrentWindow = () => { + const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'; + const meta = ipcRendererInternal.sendSync(command, contextId); + return metaToValue(meta); +}; +// Get current WebContents object. +exports.getCurrentWebContents = () => { + const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'; + const meta = ipcRendererInternal.sendSync(command, contextId); + return metaToValue(meta); +}; +// Get a global object in browser. +exports.getGlobal = (name) => { + const command = 'ELECTRON_BROWSER_GLOBAL'; + const meta = ipcRendererInternal.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 = ipcRendererInternal.sendSync(command, contextId, guestInstanceId); + return metaToValue(meta); +}; +const addBuiltinProperty = (name) => { + Object.defineProperty(exports, name, { + get: () => exports.getBuiltin(name) + }); +}; +const browserModules = require('@electron/internal/common/api/module-list').concat(require('@electron/internal/browser/api/module-list')); +// And add a helper receiver for each one. +browserModules + .filter((m) => !m.private) + .map((m) => m.name) + .forEach(addBuiltinProperty); +//# sourceMappingURL=remote.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/api/web-frame.js b/electronasar/development/renderer/api/web-frame.js new file mode 100644 index 0000000..7f902cb --- /dev/null +++ b/electronasar/development/renderer/api/web-frame.js @@ -0,0 +1,82 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = require("events"); +const electron_1 = require("electron"); +const binding = process.electronBinding('web_frame'); +class WebFrame extends events_1.EventEmitter { + constructor(context) { + super(); + this.context = context; + // Lots of webview would subscribe to webFrame's events. + this.setMaxListeners(0); + } + findFrameByRoutingId(...args) { + return getWebFrame(binding._findFrameByRoutingId(this.context, ...args)); + } + getFrameForSelector(...args) { + return getWebFrame(binding._getFrameForSelector(this.context, ...args)); + } + findFrameByName(...args) { + return getWebFrame(binding._findFrameByName(this.context, ...args)); + } + get opener() { + return getWebFrame(binding._getOpener(this.context)); + } + get parent() { + return getWebFrame(binding._getParent(this.context)); + } + get top() { + return getWebFrame(binding._getTop(this.context)); + } + get firstChild() { + return getWebFrame(binding._getFirstChild(this.context)); + } + get nextSibling() { + return getWebFrame(binding._getNextSibling(this.context)); + } + get routingId() { + return binding._getRoutingId(this.context); + } + // Deprecations + // TODO(nitsakh): Remove in 6.0 + setIsolatedWorldSecurityOrigin(worldId, securityOrigin) { + electron_1.deprecate.warn('webFrame.setIsolatedWorldSecurityOrigin', 'webFrame.setIsolatedWorldInfo'); + binding.setIsolatedWorldInfo(this.context, worldId, { securityOrigin }); + } + setIsolatedWorldContentSecurityPolicy(worldId, csp) { + electron_1.deprecate.warn('webFrame.setIsolatedWorldContentSecurityPolicy', 'webFrame.setIsolatedWorldInfo'); + binding.setIsolatedWorldInfo(this.context, worldId, { + securityOrigin: window.location.origin, + csp + }); + } + setIsolatedWorldHumanReadableName(worldId, name) { + electron_1.deprecate.warn('webFrame.setIsolatedWorldHumanReadableName', 'webFrame.setIsolatedWorldInfo'); + binding.setIsolatedWorldInfo(this.context, worldId, { name }); + } +} +// Populate the methods. +for (const name in binding) { + if (!name.startsWith('_')) { // some methods are manually populated above + // TODO(felixrieseberg): Once we can type web_frame natives, we could + // use a neat `keyof` here + WebFrame.prototype[name] = function (...args) { + return binding[name](this.context, ...args); + }; + } +} +// Helper to return WebFrame or null depending on context. +// TODO(zcbenz): Consider returning same WebFrame for the same frame. +function getWebFrame(context) { + return context ? new WebFrame(context) : null; +} +const promisifiedMethods = new Set([ + 'executeJavaScript', + 'executeJavaScriptInIsolatedWorld' +]); +for (const method of promisifiedMethods) { + WebFrame.prototype[method] = electron_1.deprecate.promisify(WebFrame.prototype[method]); +} +const _webFrame = new WebFrame(window); +exports.default = _webFrame; +//# sourceMappingURL=web-frame.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/callbacks-registry.js b/electronasar/development/renderer/callbacks-registry.js new file mode 100644 index 0000000..20060c1 --- /dev/null +++ b/electronasar/development/renderer/callbacks-registry.js @@ -0,0 +1,52 @@ +'use strict'; +const v8Util = process.electronBinding('v8_util'); +class CallbacksRegistry { + constructor() { + this.nextId = 0; + this.callbacks = {}; + } + add(callback) { + // The callback is already added. + let id = v8Util.getHiddenValue(callback, 'callbackId'); + if (id != null) + return id; + id = this.nextId += 1; + // Capture the location of the function and put it in the ID string, + // so that release errors can be tracked down easily. + const regexp = /at (.*)/gi; + const stackString = (new Error()).stack; + let filenameAndLine; + let match; + while ((match = regexp.exec(stackString)) !== null) { + const location = match[1]; + if (location.includes('(native)')) + continue; + if (location.includes('()')) + 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; +//# sourceMappingURL=callbacks-registry.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/chrome-api.js b/electronasar/development/renderer/chrome-api.js new file mode 100644 index 0000000..c33d707 --- /dev/null +++ b/electronasar/development/renderer/chrome-api.js @@ -0,0 +1,174 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal"); +const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +const url = require("url"); +// Todo: Import once extensions have been turned into TypeScript +const Event = require('@electron/internal/renderer/extensions/event'); +class Tab { + constructor(tabId) { + this.id = tabId; + } +} +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.name = name; + this.disconnected = false; + this.onDisconnect = new Event(); + this.onMessage = new Event(); + this.onDisconnect = new Event(); + this.onMessage = new Event(); + this.sender = new MessageSender(tabId, extensionId); + ipc_renderer_internal_1.ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => { + this._onDisconnect(); + }); + ipc_renderer_internal_1.ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (_event, message) => { + const sendResponse = function () { console.error('sendResponse is not implemented'); }; + this.onMessage.emit(JSON.parse(message), this.sender, sendResponse); + }); + } + disconnect() { + if (this.disconnected) + return; + ipc_renderer_internal_1.ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`); + this._onDisconnect(); + } + postMessage(message) { + ipc_renderer_internal_1.ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message)); + } + _onDisconnect() { + this.disconnected = true; + ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`); + this.onDisconnect.emit(); + } +} +// Inject chrome API to the |context| +function injectTo(extensionId, context) { + const chrome = context.chrome = context.chrome || {}; + ipc_renderer_internal_1.ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (_event, tabId, portId, connectInfo) => { + chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name)); + }); + ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (_event, tabId, message) => { + return new Promise(resolve => { + chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve); + }); + }); + ipc_renderer_internal_1.ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event, tabId) => { + chrome.tabs.onCreated.emit(new Tab(tabId)); + }); + ipc_renderer_internal_1.ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event, tabId) => { + chrome.tabs.onRemoved.emit(tabId); + }); + chrome.runtime = { + id: extensionId, + // https://developer.chrome.com/extensions/runtime#method-getURL + getURL: function (path) { + return url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: extensionId, + pathname: path + }); + }, + // https://developer.chrome.com/extensions/runtime#method-getManifest + getManifest: function () { + const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId); + return manifest; + }, + // https://developer.chrome.com/extensions/runtime#method-connect + connect(...args) { + // Parse the optional args. + let targetExtensionId = extensionId; + let connectInfo = { name: '' }; + if (args.length === 1) { + connectInfo = args[0]; + } + else if (args.length === 2) { + [targetExtensionId, connectInfo] = args; + } + const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo); + return new Port(tabId, portId, extensionId, connectInfo.name); + }, + // https://developer.chrome.com/extensions/runtime#method-sendMessage + sendMessage(...args) { + // Parse the optional args. + const targetExtensionId = extensionId; + let message; + let options; + let responseCallback = () => { }; + if (typeof args[args.length - 1] === 'function') { + responseCallback = args.pop(); + } + if (args.length === 1) { + [message] = args; + } + else if (args.length === 2) { + if (typeof args[0] === 'string') { + [extensionId, message] = args; + } + else { + [message, options] = args; + } + } + else { + [extensionId, message, options] = args; + } + if (options) { + console.error('options are not supported'); + } + ipcRendererUtils.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback); + }, + onConnect: new Event(), + onMessage: new Event(), + onInstalled: new Event() + }; + chrome.tabs = { + // https://developer.chrome.com/extensions/tabs#method-executeScript + executeScript(tabId, details, resultCallback = () => { }) { + ipcRendererUtils.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details) + .then((result) => resultCallback([result])); + }, + // https://developer.chrome.com/extensions/tabs#method-sendMessage + sendMessage(tabId, message, _options, responseCallback = () => { }) { + ipcRendererUtils.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback); + }, + onUpdated: new Event(), + onCreated: new Event(), + onRemoved: new Event() + }; + chrome.extension = { + getURL: chrome.runtime.getURL, + connect: chrome.runtime.connect, + onConnect: chrome.runtime.onConnect, + sendMessage: chrome.runtime.sendMessage, + onMessage: chrome.runtime.onMessage + }; + chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId); + chrome.pageAction = { + show() { }, + hide() { }, + setTitle() { }, + getTitle() { }, + setIcon() { }, + setPopup() { }, + getPopup() { } + }; + chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId); + chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup(); + // Electron has no concept of a browserAction but we should stub these APIs for compatibility + chrome.browserAction = { + setIcon() { }, + setPopup() { } + }; +} +exports.injectTo = injectTo; +//# sourceMappingURL=chrome-api.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/content-scripts-injector.js b/electronasar/development/renderer/content-scripts-injector.js new file mode 100644 index 0000000..a1c5b1e --- /dev/null +++ b/electronasar/development/renderer/content-scripts-injector.js @@ -0,0 +1,109 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +const v8Util = process.electronBinding('v8_util'); +const IsolatedWorldIDs = { + /** + * Start of extension isolated world IDs, as defined in + * atom_render_frame_observer.h + */ + ISOLATED_WORLD_EXTENSIONS: 1 << 20 +}; +let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS; +const extensionWorldId = {}; +// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52 +const getIsolatedWorldIdForInstance = () => { + // TODO(samuelmaddock): allocate and cleanup IDs + return isolatedWorldIds++; +}; +const escapePattern = function (pattern) { + return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&'); +}; +// Check whether pattern matches. +// https://developer.chrome.com/extensions/match_patterns +const matchesPattern = function (pattern) { + if (pattern === '') + return true; + const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`); + const url = `${location.protocol}//${location.host}${location.pathname}`; + return url.match(regexp); +}; +// Run the code with chrome API integrated. +const runContentScript = function (extensionId, url, code) { + // Assign unique world ID to each extension + const worldId = extensionWorldId[extensionId] || + (extensionWorldId[extensionId] = getIsolatedWorldIdForInstance()); + // store extension ID for content script to read in isolated world + v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId); + electron_1.webFrame.setIsolatedWorldInfo(worldId, { + name: `${extensionId} [${worldId}]` + // TODO(samuelmaddock): read `content_security_policy` from extension manifest + // csp: manifest.content_security_policy, + }); + const sources = [{ code, url }]; + return electron_1.webFrame.executeJavaScriptInIsolatedWorld(worldId, sources); +}; +const runAllContentScript = function (scripts, extensionId) { + for (const { url, code } of scripts) { + runContentScript.call(window, extensionId, url, code); + } +}; +const runStylesheet = function (url, code) { + electron_1.webFrame.insertCSS(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 (!process.isMainFrame && !script.allFrames) + return; + if (!script.matches.some(matchesPattern)) + return; + if (script.js) { + const fire = runAllContentScript.bind(window, script.js, extensionId); + if (script.runAt === 'document_start') { + process.once('document-start', fire); + } + else if (script.runAt === 'document_end') { + process.once('document-end', fire); + } + else { + document.addEventListener('DOMContentLoaded', fire); + } + } + if (script.css) { + const fire = runAllStylesheet.bind(window, script.css); + if (script.runAt === 'document_start') { + process.once('document-start', fire); + } + else if (script.runAt === 'document_end') { + process.once('document-end', fire); + } + else { + document.addEventListener('DOMContentLoaded', fire); + } + } +}; +// Handle the request of chrome.tabs.executeJavaScript. +ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (event, extensionId, url, code) { + return runContentScript.call(window, extensionId, url, code); +}); +module.exports = (getRenderProcessPreferences) => { + // Read the renderer process preferences. + const preferences = getRenderProcessPreferences(); + if (preferences) { + for (const pref of preferences) { + if (pref.contentScripts) { + for (const script of pref.contentScripts) { + injectContentScript(pref.extensionId, script); + } + } + } + } +}; +//# sourceMappingURL=content-scripts-injector.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/extensions/event.js b/electronasar/development/renderer/extensions/event.js new file mode 100644 index 0000000..9d6c1cd --- /dev/null +++ b/electronasar/development/renderer/extensions/event.js @@ -0,0 +1,22 @@ +'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; +//# sourceMappingURL=event.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/extensions/i18n.js b/electronasar/development/renderer/extensions/i18n.js new file mode 100644 index 0000000..83c4101 --- /dev/null +++ b/electronasar/development/renderer/extensions/i18n.js @@ -0,0 +1,53 @@ +'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 ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +const getMessages = (extensionId) => { + try { + const data = ipcRendererUtils.invokeSync('CHROME_GET_MESSAGES', extensionId); + return JSON.parse(data) || {}; + } + catch (_a) { + return {}; + } +}; +const replaceNumberedSubstitutions = (message, substitutions) => { + return message.replace(/\$(\d+)/, (_, number) => { + const index = parseInt(number, 10) - 1; + return substitutions[index] || ''; + }); +}; +const replacePlaceholders = (message, placeholders, substitutions) => { + if (typeof substitutions === 'string') { + substitutions = [substitutions]; + } + if (!Array.isArray(substitutions)) { + substitutions = []; + } + if (placeholders) { + Object.keys(placeholders).forEach((name) => { + let { content } = placeholders[name]; + content = replaceNumberedSubstitutions(content, substitutions); + message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content); + }); + } + return replaceNumberedSubstitutions(message, substitutions); +}; +const getMessage = (extensionId, messageName, substitutions) => { + const messages = getMessages(extensionId); + if (messages.hasOwnProperty(messageName)) { + const { message, placeholders } = messages[messageName]; + return replacePlaceholders(message, placeholders, substitutions); + } +}; +exports.setup = (extensionId) => { + return { + getMessage(messageName, substitutions) { + return getMessage(extensionId, messageName, substitutions); + } + }; +}; +//# sourceMappingURL=i18n.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/extensions/storage.js b/electronasar/development/renderer/extensions/storage.js new file mode 100644 index 0000000..f649571 --- /dev/null +++ b/electronasar/development/renderer/extensions/storage.js @@ -0,0 +1,88 @@ +'use strict'; +const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +const getStorage = (storageType, extensionId, callback) => { + if (typeof callback !== 'function') + throw new TypeError('No callback provided'); + ipcRendererUtils.invoke('CHROME_STORAGE_READ', storageType, extensionId) + .then(data => { + if (data !== null) { + callback(JSON.parse(data)); + } + else { + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({}); + } + }); +}; +const setStorage = (storageType, extensionId, storage, callback) => { + const json = JSON.stringify(storage); + ipcRendererUtils.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json) + .then(() => { + if (callback) + callback(); + }); +}; +const getStorageManager = (storageType, extensionId) => { + return { + get(keys, callback) { + getStorage(storageType, extensionId, storage => { + if (keys == null) + return callback(storage); + let defaults = {}; + switch (typeof keys) { + case 'string': + keys = [keys]; + break; + case 'object': + if (!Array.isArray(keys)) { + defaults = keys; + keys = Object.keys(keys); + } + break; + } + // 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) + }) +}; +//# sourceMappingURL=storage.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/extensions/web-navigation.js b/electronasar/development/renderer/extensions/web-navigation.js new file mode 100644 index 0000000..87cae98 --- /dev/null +++ b/electronasar/development/renderer/extensions/web-navigation.js @@ -0,0 +1,19 @@ +'use strict'; +const Event = require('@electron/internal/renderer/extensions/event'); +const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); +class WebNavigation { + constructor() { + this.onBeforeNavigate = new Event(); + this.onCompleted = new Event(); + ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => { + this.onBeforeNavigate.emit(details); + }); + ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => { + this.onCompleted.emit(details); + }); + } +} +exports.setup = () => { + return new WebNavigation(); +}; +//# sourceMappingURL=web-navigation.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/init.js b/electronasar/development/renderer/init.js new file mode 100644 index 0000000..0403cd1 --- /dev/null +++ b/electronasar/development/renderer/init.js @@ -0,0 +1,178 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = require("events"); +const path = require("path"); +const Module = require('module'); +// Make sure globals like "process" and "global" are always available in preload +// scripts even after they are deleted in "loaded" script. +// +// Note 1: We rely on a Node patch to actually pass "process" and "global" and +// other arguments to the wrapper. +// +// Note 2: Node introduced a new code path to use native code to wrap module +// code, which does not work with this hack. However by modifying the +// "Module.wrapper" we can force Node to use the old code path to wrap module +// code with JavaScript. +Module.wrapper = [ + '(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' + + // By running the code in a new closure, it would be possible for the module + // code to override "process" and "Buffer" with local variables. + 'return function (exports, require, module, __filename, __dirname) { ', + '\n}.call(this, exports, require, module, __filename, __dirname); });' +]; +// We modified the original process.argv to let node.js load the +// init.js, we need to restore it here. +process.argv.splice(1, 1); +// 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.electronBinding('v8_util'); +const ipcEmitter = new events_1.EventEmitter(); +const ipcInternalEmitter = new events_1.EventEmitter(); +v8Util.setHiddenValue(global, 'ipc', ipcEmitter); +v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter); +const savedProcess = process; +v8Util.setHiddenValue(global, 'ipcNative', { + onMessage(internal, channel, args, senderId) { + const sender = internal ? ipcInternalEmitter : ipcEmitter; + sender.emit(channel, { sender, senderId }, ...args); + savedProcess.activateUvLoop(); + } +}); +// Use electron module after everything is ready. +const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); +const { webFrameInit } = require('@electron/internal/renderer/web-frame-init'); +webFrameInit(); +// Process command line arguments. +const { hasSwitch, getSwitchValue } = process.electronBinding('command_line'); +const parseOption = function (name, defaultValue, converter) { + return hasSwitch(name) + ? (converter + ? converter(getSwitchValue(name)) + : getSwitchValue(name)) + : defaultValue; +}; +const contextIsolation = hasSwitch('context-isolation'); +const nodeIntegration = hasSwitch('node-integration'); +const webviewTag = hasSwitch('webview-tag'); +const isHiddenPage = hasSwitch('hidden-page'); +const usesNativeWindowOpen = hasSwitch('native-window-open'); +const preloadScript = parseOption('preload', null); +const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)); +const appPath = parseOption('app-path', null); +const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value)); +const openerId = parseOption('opener-id', null, value => parseInt(value)); +// The arguments to be passed to isolated world. +const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen }; +// The webContents preload script is loaded after the session preload scripts. +if (preloadScript) { + preloadScripts.push(preloadScript); +} +switch (window.location.protocol) { + case 'devtools:': { + // Override some inspector APIs. + require('@electron/internal/renderer/inspector'); + break; + } + case 'chrome-extension:': { + // Inject the chrome.* APIs that chrome extensions require + require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window); + break; + } + case 'chrome:': + break; + default: { + // Override default web functions. + const { windowSetup } = require('@electron/internal/renderer/window-setup'); + windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen); + // Inject content scripts. + require('@electron/internal/renderer/content-scripts-injector')(process.getRenderProcessPreferences); + } +} +// Load webview tag implementation. +if (process.isMainFrame) { + const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init'); + webViewInit(contextIsolation, webviewTag, guestInstanceId); +} +// Pass the arguments to isolatedWorld. +if (contextIsolation) { + v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs); +} +if (nodeIntegration) { + // Export node bindings to global. + global.require = require; + global.module = module; + // Set the __filename to the path of html file if it is file: protocol. + if (window.location.protocol === 'file:') { + const location = window.location; + let pathname = location.pathname; + if (process.platform === 'win32') { + if (pathname[0] === '/') + pathname = pathname.substr(1); + const isWindowsNetworkSharePath = location.hostname.length > 0 && globalPaths[0].startsWith('\\'); + if (isWindowsNetworkSharePath) { + pathname = `//${location.host}/${pathname}`; + } + } + 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) { + // We do not want to add `uncaughtException` to our definitions + // because we don't want anyone else (anywhere) to throw that kind + // of error. + global.process.emit('uncaughtException', error); + return true; + } + else { + return false; + } + }; +} +else { + // Delete Node's symbols after the Environment has been loaded. + process.once('loaded', function () { + delete global.process; + delete global.Buffer; + delete global.setImmediate; + delete global.clearImmediate; + delete global.global; + }); +} +const errorUtils = require('@electron/internal/common/error-utils'); +// Load the preload scripts. +for (const preloadScript of preloadScripts) { + try { + require(preloadScript); + } + catch (error) { + console.error(`Unable to load preload script: ${preloadScript}`); + console.error(`${error}`); + ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, errorUtils.serialize(error)); + } +} +// Warn about security issues +if (process.isMainFrame) { + const { securityWarnings } = require('@electron/internal/renderer/security-warnings'); + securityWarnings(nodeIntegration); +} +//# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/inspector.js b/electronasar/development/renderer/inspector.js new file mode 100644 index 0000000..3c1f84e --- /dev/null +++ b/electronasar/development/renderer/inspector.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_renderer_internal_utils_1 = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +window.onload = function () { + // Use menu API to show context menu. + window.InspectorFrontendHost.showContextMenuAtPoint = createMenu; + // correct for Chromium returning undefined for filesystem + window.Persistence.FileSystemWorkspaceBinding.completeURL = completeURL; + // Use dialog API to override file chooser dialog. + window.UI.createFileSelectorElement = createFileSelectorElement; +}; +// Extra / is needed as a result of MacOS requiring absolute paths +function completeURL(project, path) { + project = 'file:///'; + return `${project}${path}`; +} +// The DOM implementation expects (message?: string) => boolean +window.confirm = function (message, title) { + return ipc_renderer_internal_utils_1.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title); +}; +const useEditMenuItems = function (x, y, items) { + return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) { + return element.nodeName === 'INPUT' || + element.nodeName === 'TEXTAREA' || + element.isContentEditable; + }); +}; +const createMenu = function (x, y, items) { + const isEditMenu = useEditMenuItems(x, y, items); + ipc_renderer_internal_utils_1.invoke('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => { + if (typeof id === 'number') { + window.DevToolsAPI.contextMenuItemSelected(id); + } + window.DevToolsAPI.contextMenuCleared(); + }); +}; +const showFileChooserDialog = function (callback) { + ipc_renderer_internal_utils_1.invoke('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => { + if (path && data) { + callback(dataToHtml5FileObject(path, data)); + } + }); +}; +const dataToHtml5FileObject = function (path, data) { + return new File([data], path); +}; +const createFileSelectorElement = function (callback) { + const fileSelectorElement = document.createElement('span'); + fileSelectorElement.style.display = 'none'; + fileSelectorElement.click = showFileChooserDialog.bind(this, callback); + return fileSelectorElement; +}; +//# sourceMappingURL=inspector.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/ipc-renderer-internal-utils.js b/electronasar/development/renderer/ipc-renderer-internal-utils.js new file mode 100644 index 0000000..2dc6e8d --- /dev/null +++ b/electronasar/development/renderer/ipc-renderer-internal-utils.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal"); +const errorUtils = require("@electron/internal/common/error-utils"); +exports.handle = function (channel, handler) { + ipc_renderer_internal_1.ipcRendererInternal.on(channel, (event, requestId, ...args) => { + new Promise(resolve => resolve(handler(event, ...args))).then(result => { + return [null, result]; + }, error => { + return [errorUtils.serialize(error)]; + }).then(responseArgs => { + event.sender.send(`${channel}_RESPONSE_${requestId}`, ...responseArgs); + }); + }); +}; +let nextId = 0; +function invoke(command, ...args) { + return new Promise((resolve, reject) => { + const requestId = ++nextId; + ipc_renderer_internal_1.ipcRendererInternal.once(`${command}_RESPONSE_${requestId}`, (_event, error, result) => { + if (error) { + reject(errorUtils.deserialize(error)); + } + else { + resolve(result); + } + }); + ipc_renderer_internal_1.ipcRendererInternal.send(command, requestId, ...args); + }); +} +exports.invoke = invoke; +function invokeSync(command, ...args) { + const [error, result] = ipc_renderer_internal_1.ipcRendererInternal.sendSync(command, null, ...args); + if (error) { + throw errorUtils.deserialize(error); + } + else { + return result; + } +} +exports.invokeSync = invokeSync; +//# sourceMappingURL=ipc-renderer-internal-utils.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/ipc-renderer-internal.js b/electronasar/development/renderer/ipc-renderer-internal.js new file mode 100644 index 0000000..8e1ca4f --- /dev/null +++ b/electronasar/development/renderer/ipc-renderer-internal.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const binding = process.electronBinding('ipc'); +const v8Util = process.electronBinding('v8_util'); +// Created by init.js. +exports.ipcRendererInternal = v8Util.getHiddenValue(global, 'ipc-internal'); +const internal = true; +exports.ipcRendererInternal.send = function (channel, ...args) { + return binding.ipc.send(internal, channel, args); +}; +exports.ipcRendererInternal.sendSync = function (channel, ...args) { + return binding.ipc.sendSync(internal, channel, args)[0]; +}; +exports.ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) { + return binding.ipc.sendTo(internal, false, webContentsId, channel, args); +}; +exports.ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) { + return binding.ipc.sendTo(internal, true, webContentsId, channel, args); +}; +//# sourceMappingURL=ipc-renderer-internal.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/security-warnings.js b/electronasar/development/renderer/security-warnings.js new file mode 100644 index 0000000..5dfc97e --- /dev/null +++ b/electronasar/development/renderer/security-warnings.js @@ -0,0 +1,239 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const ipc_renderer_internal_utils_1 = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +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 () { + return electron_1.webFrame.executeJavaScript(`(${(() => { + try { + new Function(''); // eslint-disable-line no-new,no-new-func + } + catch (_a) { + return false; + } + return true; + }).toString()})()`, false); +}; +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 || + !webPreferences.hasOwnProperty('enableBlinkFeatures') || + (webPreferences.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 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); + } +}; +// Currently missing since we can't easily programmatically check for it: +// #12WebViews: Verify the options and params of all `` tags +const logSecurityWarnings = function (webPreferences, nodeIntegration) { + warnAboutNodeWithRemoteContent(nodeIntegration); + warnAboutDisabledWebSecurity(webPreferences); + warnAboutInsecureResources(); + warnAboutInsecureContentAllowed(webPreferences); + warnAboutExperimentalFeatures(webPreferences); + warnAboutEnableBlinkFeatures(webPreferences); + warnAboutInsecureCSP(); + warnAboutAllowedPopups(); +}; +const getWebPreferences = function () { + try { + return ipc_renderer_internal_utils_1.invokeSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES'); + } + catch (error) { + console.warn(`getLastWebPreferences() failed: ${error}`); + } +}; +function securityWarnings(nodeIntegration) { + const loadHandler = function () { + if (shouldLogSecurityWarnings()) { + const webPreferences = getWebPreferences(); + logSecurityWarnings(webPreferences, nodeIntegration); + } + }; + window.addEventListener('load', loadHandler, { once: true }); +} +exports.securityWarnings = securityWarnings; +//# sourceMappingURL=security-warnings.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-frame-init.js b/electronasar/development/renderer/web-frame-init.js new file mode 100644 index 0000000..29b03f1 --- /dev/null +++ b/electronasar/development/renderer/web-frame-init.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +exports.webFrameInit = () => { + // Call webFrame method + ipcRendererUtils.handle('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, ...args) => { + // The TypeScript compiler cannot handle the sheer number of + // call signatures here and simply gives up. Incorrect invocations + // will be caught by "keyof WebFrameMethod" though. + return electron_1.webFrame[method](...args); + }); +}; +//# sourceMappingURL=web-frame-init.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/guest-view-internal.js b/electronasar/development/renderer/web-view/guest-view-internal.js new file mode 100644 index 0000000..c16720c --- /dev/null +++ b/electronasar/development/renderer/web-view/guest-view-internal.js @@ -0,0 +1,111 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal"); +const ipc_renderer_internal_utils_1 = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +const WEB_VIEW_EVENTS = { + 'load-commit': ['url', 'isMainFrame'], + 'did-attach': [], + 'did-finish-load': [], + 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame', 'frameProcessId', 'frameRoutingId'], + 'did-frame-finish-load': ['isMainFrame', 'frameProcessId', 'frameRoutingId'], + 'did-start-loading': [], + 'did-stop-loading': [], + 'dom-ready': [], + 'console-message': ['level', 'message', 'line', 'sourceId'], + 'context-menu': ['params'], + 'devtools-opened': [], + 'devtools-closed': [], + 'devtools-focused': [], + 'new-window': ['url', 'frameName', 'disposition', 'options'], + 'will-navigate': ['url'], + 'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'], + 'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'], + 'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'], + 'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'], + 'focus-change': ['focus', 'guestInstanceId'], + 'close': [], + 'crashed': [], + 'plugin-crashed': ['name', 'version'], + 'destroyed': [], + 'page-title-updated': ['title', 'explicitSet'], + 'page-favicon-updated': ['favicons'], + 'enter-html-full-screen': [], + 'leave-html-full-screen': [], + 'media-started-playing': [], + 'media-paused': [], + 'found-in-page': ['result'], + 'did-change-theme-color': ['themeColor'], + 'update-target-url': ['url'] +}; +const DEPRECATED_EVENTS = { + 'page-title-updated': 'page-title-set' +}; +const dispatchEvent = function (webView, eventName, eventKey, ...args) { + if (DEPRECATED_EVENTS[eventName] != null) { + dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args); + } + const domEvent = new Event(eventName); + WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => { + domEvent[prop] = args[index]; + }); + webView.dispatchEvent(domEvent); + if (eventName === 'load-commit') { + webView.onLoadCommit(domEvent); + } + else if (eventName === 'focus-change') { + webView.onFocusChange(); + } +}; +function registerEvents(webView, viewInstanceId) { + ipc_renderer_internal_1.ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () { + webView.guestInstanceId = undefined; + webView.reset(); + const domEvent = new Event('destroyed'); + webView.dispatchEvent(domEvent); + }); + ipc_renderer_internal_1.ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) { + dispatchEvent(webView, eventName, eventName, ...args); + }); + ipc_renderer_internal_1.ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) { + const domEvent = new Event('ipc-message'); + domEvent.channel = channel; + domEvent.args = args; + webView.dispatchEvent(domEvent); + }); +} +exports.registerEvents = registerEvents; +function deregisterEvents(viewInstanceId) { + ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`); + ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`); + ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`); +} +exports.deregisterEvents = deregisterEvents; +function createGuest(params) { + return ipc_renderer_internal_utils_1.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params); +} +exports.createGuest = createGuest; +function createGuestSync(params) { + return ipc_renderer_internal_utils_1.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params); +} +exports.createGuestSync = createGuestSync; +function destroyGuest(guestInstanceId) { + ipc_renderer_internal_utils_1.invoke('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId); +} +exports.destroyGuest = destroyGuest; +function attachGuest(elementInstanceId, guestInstanceId, params, contentWindow) { + const embedderFrameId = electron_1.webFrame.getWebFrameId(contentWindow); + if (embedderFrameId < 0) { // this error should not happen. + throw new Error('Invalid embedder frame'); + } + ipc_renderer_internal_utils_1.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params); +} +exports.attachGuest = attachGuest; +exports.guestViewInternalModule = { + deregisterEvents, + createGuest, + createGuestSync, + destroyGuest, + attachGuest +}; +//# sourceMappingURL=guest-view-internal.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/web-view-attributes.js b/electronasar/development/renderer/web-view/web-view-attributes.js new file mode 100644 index 0000000..c3a139f --- /dev/null +++ b/electronasar/development/renderer/web-view/web-view-attributes.js @@ -0,0 +1,251 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +const web_view_impl_1 = require("@electron/internal/renderer/web-view/web-view-impl"); +// Helper function to resolve url set in attribute. +const a = document.createElement('a'); +const resolveURL = function (url) { + if (!url) + return ''; + a.href = url; + return a.href; +}; +// Attribute objects. +// Default implementation of a WebView attribute. +class WebViewAttribute { + constructor(name, webViewImpl) { + this.name = name; + this.webViewImpl = webViewImpl; + this.ignoreMutation = false; + // Called when the attribute's value changes. + this.handleMutation = () => undefined; + this.name = name; + this.value = webViewImpl.webviewNode[name] || ''; + this.webViewImpl = webViewImpl; + this.defineProperty(); + } + // Retrieves and returns the attribute's value. + getValue() { + return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value; + } + // Sets the attribute's value. + setValue(value) { + this.webViewImpl.webviewNode.setAttribute(this.name, value || ''); + } + // Changes the attribute's value without triggering its mutation handler. + setValueIgnoreMutation(value) { + this.ignoreMutation = true; + this.setValue(value); + this.ignoreMutation = false; + } + // Defines this attribute as a property on the webview node. + defineProperty() { + return Object.defineProperty(this.webViewImpl.webviewNode, this.name, { + get: () => { + return this.getValue(); + }, + set: (value) => { + return this.setValue(value); + }, + enumerable: true + }); + } +} +// An attribute that is treated as a Boolean. +class BooleanAttribute extends WebViewAttribute { + getValue() { + return this.webViewImpl.webviewNode.hasAttribute(this.name); + } + setValue(value) { + if (value) { + this.webViewImpl.webviewNode.setAttribute(this.name, ''); + } + else { + this.webViewImpl.webviewNode.removeAttribute(this.name); + } + } +} +// Attribute representing the state of the storage partition. +class PartitionAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("partition" /* ATTRIBUTE_PARTITION */, webViewImpl); + this.webViewImpl = webViewImpl; + this.validPartitionId = true; + this.handleMutation = (oldValue, newValue) => { + newValue = newValue || ''; + // The partition cannot change if the webview has already navigated. + if (!this.webViewImpl.beforeFirstNavigation) { + console.error("The object has already navigated, so its partition cannot be changed." /* ERROR_MSG_ALREADY_NAVIGATED */); + this.setValueIgnoreMutation(oldValue); + return; + } + if (newValue === 'persist:') { + this.validPartitionId = false; + console.error("Invalid partition attribute." /* ERROR_MSG_INVALID_PARTITION_ATTRIBUTE */); + } + }; + } +} +// Attribute that handles the location and navigation of the webview. +class SrcAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("src" /* ATTRIBUTE_SRC */, webViewImpl); + this.webViewImpl = webViewImpl; + this.handleMutation = (oldValue, newValue) => { + // Once we have navigated, we don't allow clearing the src attribute. + // Once enters a navigated state, it cannot return to a + // placeholder state. + if (!newValue && oldValue) { + // src attribute changes normally initiate a navigation. We suppress + // the next src attribute handler call to avoid reloading the page + // on every guest-initiated navigation. + this.setValueIgnoreMutation(oldValue); + return; + } + this.parse(); + }; + this.setupMutationObserver(); + } + getValue() { + if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + } + else { + return this.value; + } + } + setValueIgnoreMutation(value) { + super.setValueIgnoreMutation(value); + // takeRecords() is needed to clear queued up src mutations. Without it, it + // is possible for this change to get picked up asyncronously by src's + // mutation observer |observer|, and then get handled even though we do not + // want to handle this mutation. + this.observer.takeRecords(); + } + // The purpose of this mutation observer is to catch assignment to the src + // attribute without any changes to its value. This is useful in the case + // where the webview guest has crashed and navigating to the same address + // spawns off a new process. + setupMutationObserver() { + this.observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + const { oldValue } = mutation; + const newValue = this.getValue(); + if (oldValue !== newValue) { + return; + } + this.handleMutation(oldValue, newValue); + } + }); + const params = { + attributes: true, + attributeOldValue: true, + attributeFilter: [this.name] + }; + this.observer.observe(this.webViewImpl.webviewNode, params); + } + parse() { + if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes["partition" /* ATTRIBUTE_PARTITION */].validPartitionId || !this.getValue()) { + return; + } + if (this.webViewImpl.guestInstanceId == null) { + if (this.webViewImpl.beforeFirstNavigation) { + this.webViewImpl.beforeFirstNavigation = false; + this.webViewImpl.createGuest(); + } + return; + } + // Navigate to |this.src|. + const opts = {}; + const httpreferrer = this.webViewImpl.attributes["httpreferrer" /* ATTRIBUTE_HTTPREFERRER */].getValue(); + if (httpreferrer) { + opts.httpReferrer = httpreferrer; + } + const useragent = this.webViewImpl.attributes["useragent" /* ATTRIBUTE_USERAGENT */].getValue(); + if (useragent) { + opts.userAgent = useragent; + } + const guestInstanceId = this.webViewImpl.guestInstanceId; + const method = 'loadURL'; + const args = [this.getValue(), opts]; + ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args); + } +} +// Attribute specifies HTTP referrer. +class HttpReferrerAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("httpreferrer" /* ATTRIBUTE_HTTPREFERRER */, webViewImpl); + } +} +// Attribute specifies user agent +class UserAgentAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("useragent" /* ATTRIBUTE_USERAGENT */, webViewImpl); + } +} +// Attribute that set preload script. +class PreloadAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("preload" /* ATTRIBUTE_PRELOAD */, webViewImpl); + } + getValue() { + if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return this.value; + } + let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + const protocol = preload.substr(0, 5); + if (protocol !== 'file:') { + console.error("Only \"file:\" protocol is supported in \"preload\" attribute." /* ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE */); + preload = ''; + } + return preload; + } +} +// Attribute that specifies the blink features to be enabled. +class BlinkFeaturesAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("blinkfeatures" /* ATTRIBUTE_BLINKFEATURES */, webViewImpl); + } +} +// Attribute that specifies the blink features to be disabled. +class DisableBlinkFeaturesAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("disableblinkfeatures" /* ATTRIBUTE_DISABLEBLINKFEATURES */, webViewImpl); + } +} +// Attribute that specifies the web preferences to be enabled. +class WebPreferencesAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("webpreferences" /* ATTRIBUTE_WEBPREFERENCES */, webViewImpl); + } +} +class EnableRemoteModuleAttribute extends WebViewAttribute { + constructor(webViewImpl) { + super("enableremotemodule" /* ATTRIBUTE_ENABLEREMOTEMODULE */, webViewImpl); + } + getValue() { + return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false'; + } + setValue(value) { + this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false'); + } +} +// Sets up all of the webview attributes. +web_view_impl_1.WebViewImpl.prototype.setupWebViewAttributes = function () { + this.attributes = {}; + this.attributes["partition" /* ATTRIBUTE_PARTITION */] = new PartitionAttribute(this); + this.attributes["src" /* ATTRIBUTE_SRC */] = new SrcAttribute(this); + this.attributes["httpreferrer" /* ATTRIBUTE_HTTPREFERRER */] = new HttpReferrerAttribute(this); + this.attributes["useragent" /* ATTRIBUTE_USERAGENT */] = new UserAgentAttribute(this); + this.attributes["nodeintegration" /* ATTRIBUTE_NODEINTEGRATION */] = new BooleanAttribute("nodeintegration" /* ATTRIBUTE_NODEINTEGRATION */, this); + this.attributes["nodeintegrationinsubframes" /* ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES */] = new BooleanAttribute("nodeintegrationinsubframes" /* ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES */, this); + this.attributes["plugins" /* ATTRIBUTE_PLUGINS */] = new BooleanAttribute("plugins" /* ATTRIBUTE_PLUGINS */, this); + this.attributes["disablewebsecurity" /* ATTRIBUTE_DISABLEWEBSECURITY */] = new BooleanAttribute("disablewebsecurity" /* ATTRIBUTE_DISABLEWEBSECURITY */, this); + this.attributes["allowpopups" /* ATTRIBUTE_ALLOWPOPUPS */] = new BooleanAttribute("allowpopups" /* ATTRIBUTE_ALLOWPOPUPS */, this); + this.attributes["enableremotemodule" /* ATTRIBUTE_ENABLEREMOTEMODULE */] = new EnableRemoteModuleAttribute(this); + this.attributes["preload" /* ATTRIBUTE_PRELOAD */] = new PreloadAttribute(this); + this.attributes["blinkfeatures" /* ATTRIBUTE_BLINKFEATURES */] = new BlinkFeaturesAttribute(this); + this.attributes["disableblinkfeatures" /* ATTRIBUTE_DISABLEBLINKFEATURES */] = new DisableBlinkFeaturesAttribute(this); + this.attributes["webpreferences" /* ATTRIBUTE_WEBPREFERENCES */] = new WebPreferencesAttribute(this); +}; +//# sourceMappingURL=web-view-attributes.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/web-view-constants.js b/electronasar/development/renderer/web-view/web-view-constants.js new file mode 100644 index 0000000..97cfb5f --- /dev/null +++ b/electronasar/development/renderer/web-view/web-view-constants.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=web-view-constants.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/web-view-element.js b/electronasar/development/renderer/web-view/web-view-element.js new file mode 100644 index 0000000..6c7092e --- /dev/null +++ b/electronasar/development/renderer/web-view/web-view-element.js @@ -0,0 +1,102 @@ +"use strict"; +// When using context isolation, the WebViewElement and the custom element +// methods have to be defined in the main world to be able to be registered. +// +// Note: The hidden values can only be read/set inside the same context, all +// methods that access the "internal" hidden value must be put in this file. +// +// Note: This file could be loaded in the main world of contextIsolation page, +// which runs in browserify environment instead of Node environment, all native +// modules must be passed from outside, all included files must be plain JS. +Object.defineProperty(exports, "__esModule", { value: true }); +// Return a WebViewElement class that is defined in this context. +const defineWebViewElement = (v8Util, webViewImpl) => { + const { guestViewInternal, WebViewImpl } = webViewImpl; + return class WebViewElement extends HTMLElement { + static get observedAttributes() { + return [ + "partition" /* ATTRIBUTE_PARTITION */, + "src" /* ATTRIBUTE_SRC */, + "httpreferrer" /* ATTRIBUTE_HTTPREFERRER */, + "useragent" /* ATTRIBUTE_USERAGENT */, + "nodeintegration" /* ATTRIBUTE_NODEINTEGRATION */, + "nodeintegrationinsubframes" /* ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES */, + "plugins" /* ATTRIBUTE_PLUGINS */, + "disablewebsecurity" /* ATTRIBUTE_DISABLEWEBSECURITY */, + "allowpopups" /* ATTRIBUTE_ALLOWPOPUPS */, + "enableremotemodule" /* ATTRIBUTE_ENABLEREMOTEMODULE */, + "preload" /* ATTRIBUTE_PRELOAD */, + "blinkfeatures" /* ATTRIBUTE_BLINKFEATURES */, + "disableblinkfeatures" /* ATTRIBUTE_DISABLEBLINKFEATURES */, + "webpreferences" /* ATTRIBUTE_WEBPREFERENCES */ + ]; + } + constructor() { + super(); + v8Util.setHiddenValue(this, 'internal', new WebViewImpl(this)); + } + connectedCallback() { + const internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + if (!internal.elementAttached) { + guestViewInternal.registerEvents(internal, internal.viewInstanceId); + internal.elementAttached = true; + internal.attributes["src" /* ATTRIBUTE_SRC */].parse(); + } + } + attributeChangedCallback(name, oldValue, newValue) { + const internal = v8Util.getHiddenValue(this, 'internal'); + if (internal) { + internal.handleWebviewAttributeMutation(name, oldValue, newValue); + } + } + disconnectedCallback() { + const internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + guestViewInternal.deregisterEvents(internal.viewInstanceId); + internal.elementAttached = false; + this.internalInstanceId = 0; + internal.reset(); + } + }; +}; +// Register custom element. +const registerWebViewElement = (v8Util, webViewImpl) => { + // I wish eslint wasn't so stupid, but it is + // eslint-disable-next-line + const WebViewElement = defineWebViewElement(v8Util, webViewImpl); + webViewImpl.setupMethods(WebViewElement); + // The customElements.define has to be called in a special scope. + const webFrame = webViewImpl.webFrame; + webFrame.allowGuestViewElementDefinition(window, () => { + window.customElements.define('webview', WebViewElement); + window.WebView = WebViewElement; + // Delete the callbacks so developers cannot call them and produce unexpected + // behavior. + delete WebViewElement.prototype.connectedCallback; + delete WebViewElement.prototype.disconnectedCallback; + delete WebViewElement.prototype.attributeChangedCallback; + // Now that |observedAttributes| has been retrieved, we can hide it from + // user code as well. + // TypeScript is concerned that we're deleting a read-only attribute + delete WebViewElement.observedAttributes; + }); +}; +// Prepare to register the element. +exports.setupWebView = (v8Util, webViewImpl) => { + const useCapture = true; + const listener = (event) => { + if (document.readyState === 'loading') { + return; + } + webViewImpl.setupAttributes(); + registerWebViewElement(v8Util, webViewImpl); + window.removeEventListener(event.type, listener, useCapture); + }; + window.addEventListener('readystatechange', listener, useCapture); +}; +//# sourceMappingURL=web-view-element.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/web-view-impl.js b/electronasar/development/renderer/web-view/web-view-impl.js new file mode 100644 index 0000000..3bb03e5 --- /dev/null +++ b/electronasar/development/renderer/web-view/web-view-impl.js @@ -0,0 +1,233 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const ipcRendererUtils = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +const guestViewInternal = require("@electron/internal/renderer/web-view/guest-view-internal"); +const web_view_methods_1 = require("@electron/internal/common/web-view-methods"); +const v8Util = process.electronBinding('v8_util'); +// ID generator. +let nextId = 0; +const getNextId = function () { + return ++nextId; +}; +// Represents the internal state of the WebView node. +class WebViewImpl { + constructor(webviewNode) { + this.webviewNode = webviewNode; + this.beforeFirstNavigation = true; + this.elementAttached = false; + this.hasFocus = false; + // on* Event handlers. + this.on = {}; + // Replaced in web-view-attributes + this.attributes = {}; + // Create internal iframe element. + this.internalElement = this.createInternalElement(); + const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = ''; + this.setupWebViewAttributes(); + this.viewInstanceId = getNextId(); + shadowRoot.appendChild(this.internalElement); + // Provide access to contentWindow. + Object.defineProperty(this.webviewNode, 'contentWindow', { + get: () => { + return this.internalElement.contentWindow; + }, + enumerable: true + }); + } + setupWebViewAttributes() { } + createInternalElement() { + const iframeElement = document.createElement('iframe'); + iframeElement.style.flex = '1 1 auto'; + iframeElement.style.width = '100%'; + iframeElement.style.border = '0'; + v8Util.setHiddenValue(iframeElement, 'internal', this); + return iframeElement; + } + // Resets some state upon reattaching element to the DOM. + reset() { + // If guestInstanceId is defined then the has navigated and has + // already picked up a partition ID. Thus, we need to reset the initialization + // state. However, it may be the case that beforeFirstNavigation is false BUT + // guestInstanceId has yet to be initialized. This means that we have not + // heard back from createGuest yet. We will not reset the flag in this case so + // that we don't end up allocating a second guest. + if (this.guestInstanceId) { + guestViewInternal.destroyGuest(this.guestInstanceId); + this.guestInstanceId = void 0; + } + this.beforeFirstNavigation = true; + this.attributes["partition" /* ATTRIBUTE_PARTITION */].validPartitionId = true; + // Since attachment swaps a local frame for a remote frame, we need our + // internal iframe element to be local again before we can reattach. + const newFrame = this.createInternalElement(); + const oldFrame = this.internalElement; + this.internalElement = newFrame; + if (oldFrame && oldFrame.parentNode) { + oldFrame.parentNode.replaceChild(newFrame, oldFrame); + } + } + // This observer monitors mutations to attributes of the and + // updates the BrowserPlugin properties accordingly. In turn, updating + // a BrowserPlugin property will update the corresponding BrowserPlugin + // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more + // details. + handleWebviewAttributeMutation(attributeName, oldValue, newValue) { + if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { + return; + } + // Let the changed attribute handle its own mutation + this.attributes[attributeName].handleMutation(oldValue, newValue); + } + onElementResize() { + const resizeEvent = new Event('resize'); + resizeEvent.newWidth = this.webviewNode.clientWidth; + resizeEvent.newHeight = this.webviewNode.clientHeight; + this.dispatchEvent(resizeEvent); + } + createGuest() { + guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => { + this.attachGuestInstance(guestInstanceId); + }); + } + createGuestSync() { + this.beforeFirstNavigation = false; + this.attachGuestInstance(guestViewInternal.createGuestSync(this.buildParams())); + } + dispatchEvent(webViewEvent) { + this.webviewNode.dispatchEvent(webViewEvent); + } + // Adds an 'on' property on the webview, which can be used to set/unset + // an event handler. + setupEventProperty(eventName) { + const propertyName = `on${eventName.toLowerCase()}`; + return Object.defineProperty(this.webviewNode, propertyName, { + get: () => { + return this.on[propertyName]; + }, + set: (value) => { + if (this.on[propertyName]) { + this.webviewNode.removeEventListener(eventName, this.on[propertyName]); + } + this.on[propertyName] = value; + if (value) { + return this.webviewNode.addEventListener(eventName, value); + } + }, + enumerable: true + }); + } + // Updates state upon loadcommit. + onLoadCommit(webViewEvent) { + const oldValue = this.webviewNode.getAttribute("src" /* ATTRIBUTE_SRC */); + const newValue = webViewEvent.url; + if (webViewEvent.isMainFrame && (oldValue !== newValue)) { + // Touching the src attribute triggers a navigation. To avoid + // triggering a page reload on every guest-initiated navigation, + // we do not handle this mutation. + this.attributes["src" /* ATTRIBUTE_SRC */].setValueIgnoreMutation(newValue); + } + } + // Emits focus/blur events. + onFocusChange() { + const hasFocus = document.activeElement === this.webviewNode; + if (hasFocus !== this.hasFocus) { + this.hasFocus = hasFocus; + this.dispatchEvent(new Event(hasFocus ? 'focus' : 'blur')); + } + } + onAttach(storagePartitionId) { + return this.attributes["partition" /* ATTRIBUTE_PARTITION */].setValue(storagePartitionId); + } + buildParams() { + const params = { + instanceId: this.viewInstanceId, + userAgentOverride: this.userAgentOverride + }; + for (const attributeName in this.attributes) { + if (this.attributes.hasOwnProperty(attributeName)) { + params[attributeName] = this.attributes[attributeName].getValue(); + } + } + return params; + } + attachGuestInstance(guestInstanceId) { + if (!this.elementAttached) { + // The element could be detached before we got response from browser. + return; + } + this.internalInstanceId = getNextId(); + this.guestInstanceId = guestInstanceId; + guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams(), this.internalElement.contentWindow); + // ResizeObserver is a browser global not recognized by "standard". + /* globals ResizeObserver */ + // TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not + // even documented. + this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this)); + this.resizeObserver.observe(this.internalElement); + } +} +exports.WebViewImpl = WebViewImpl; +exports.setupAttributes = () => { + require('@electron/internal/renderer/web-view/web-view-attributes'); +}; +// I wish eslint wasn't so stupid, but it is +// eslint-disable-next-line +exports.setupMethods = (WebViewElement) => { + WebViewElement.prototype.getWebContentsId = function () { + const internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal.guestInstanceId) { + throw new Error('The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.'); + } + return internal.guestInstanceId; + }; + // WebContents associated with this webview. + WebViewElement.prototype.getWebContents = function () { + if (!electron_1.remote) { + throw new Error('getGuestWebContents requires remote, which is not enabled'); + } + const internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal.guestInstanceId) { + internal.createGuestSync(); + } + return electron_1.remote.getGuestWebContents(internal.guestInstanceId); + }; + // Focusing the webview should move page focus to the underlying iframe. + WebViewElement.prototype.focus = function () { + this.contentWindow.focus(); + }; + // Forward proto.foo* method calls to WebViewImpl.foo*. + const createBlockHandler = function (method) { + return function (...args) { + return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args); + }; + }; + for (const method of web_view_methods_1.syncMethods) { + WebViewElement.prototype[method] = createBlockHandler(method); + } + const createNonBlockHandler = function (method) { + return function (...args) { + ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args); + }; + }; + for (const method of web_view_methods_1.asyncCallbackMethods) { + WebViewElement.prototype[method] = createNonBlockHandler(method); + } + const createPromiseHandler = function (method) { + return function (...args) { + return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args); + }; + }; + for (const method of web_view_methods_1.asyncPromiseMethods) { + WebViewElement.prototype[method] = electron_1.deprecate.promisify(createPromiseHandler(method)); + } +}; +exports.webViewImplModule = { + setupAttributes: exports.setupAttributes, + setupMethods: exports.setupMethods, + guestViewInternal, + webFrame: electron_1.webFrame, + WebViewImpl +}; +//# sourceMappingURL=web-view-impl.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/web-view/web-view-init.js b/electronasar/development/renderer/web-view/web-view-init.js new file mode 100644 index 0000000..e1951a7 --- /dev/null +++ b/electronasar/development/renderer/web-view/web-view-init.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal"); +const v8Util = process.electronBinding('v8_util'); +function handleFocusBlur(guestInstanceId) { + // Note that while Chromium content APIs have observer for focus/blur, they + // unfortunately do not work for webview. + window.addEventListener('focus', () => { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId); + }); + window.addEventListener('blur', () => { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId); + }); +} +function webViewInit(contextIsolation, webviewTag, guestInstanceId) { + // Don't allow recursive ``. + if (webviewTag && guestInstanceId == null) { + const { webViewImplModule } = require('@electron/internal/renderer/web-view/web-view-impl'); + if (contextIsolation) { + v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule); + } + else { + const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element'); + setupWebView(v8Util, webViewImplModule); + } + } + if (guestInstanceId) { + // Report focus/blur events of webview to browser. + handleFocusBlur(guestInstanceId); + } +} +exports.webViewInit = webViewInit; +//# sourceMappingURL=web-view-init.js.map \ No newline at end of file diff --git a/electronasar/development/renderer/window-setup.js b/electronasar/development/renderer/window-setup.js new file mode 100644 index 0000000..cbc2ebf --- /dev/null +++ b/electronasar/development/renderer/window-setup.js @@ -0,0 +1,247 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-renderer-internal"); +// This file implements the following APIs: +// - window.history.back() +// - window.history.forward() +// - window.history.go() +// - window.history.length +// - window.open() +// - window.opener.blur() +// - window.opener.close() +// - window.opener.eval() +// - window.opener.focus() +// - window.opener.location +// - window.opener.print() +// - window.opener.postMessage() +// - window.prompt() +// - document.hidden +// - document.visibilityState +const { defineProperty } = Object; +// Helper function to resolve relative url. +const a = window.document.createElement('a'); +const resolveURL = function (url) { + a.href = url; + return a.href; +}; +// Use this method to ensure values expected as strings in the main process +// are convertible to strings in the renderer process. This ensures exceptions +// converting values to strings are thrown in this process. +const toString = (value) => { + return value != null ? `${value}` : value; +}; +const windowProxies = {}; +const getOrCreateProxy = (guestId) => { + let proxy = windowProxies[guestId]; + if (proxy == null) { + proxy = new BrowserWindowProxy(guestId); + windowProxies[guestId] = proxy; + } + return proxy; +}; +const removeProxy = (guestId) => { + delete windowProxies[guestId]; +}; +class LocationProxy { + constructor(guestId) { + // eslint will consider the constructor "useless" + // unless we assign them in the body. It's fine, that's what + // TS would do anyway. + this.guestId = guestId; + this.getGuestURL = this.getGuestURL.bind(this); + } + /** + * Beware: This decorator will have the _prototype_ as the `target`. It defines properties + * commonly found in URL on the LocationProxy. + */ + static ProxyProperty(target, propertyKey) { + Object.defineProperty(target, propertyKey, { + get: function () { + const guestURL = this.getGuestURL(); + const value = guestURL ? guestURL[propertyKey] : ''; + return value === undefined ? '' : value; + }, + set: function (newVal) { + const guestURL = this.getGuestURL(); + if (guestURL) { + // TypeScript doesn't want us to assign to read-only variables. + // It's right, that's bad, but we're doing it anway. + guestURL[propertyKey] = newVal; + return ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'loadURL', guestURL.toString()); + } + } + }); + } + toString() { + return this.href; + } + getGuestURL() { + const urlString = ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'getURL'); + try { + return new URL(urlString); + } + catch (e) { + console.error('LocationProxy: failed to parse string', urlString, e); + } + return null; + } +} +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "hash", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "href", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "host", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "hostname", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "origin", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "pathname", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "port", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "protocol", void 0); +__decorate([ + LocationProxy.ProxyProperty +], LocationProxy.prototype, "search", void 0); +class BrowserWindowProxy { + constructor(guestId) { + this.closed = false; + this.guestId = guestId; + this._location = new LocationProxy(guestId); + ipc_renderer_internal_1.ipcRendererInternal.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { + removeProxy(guestId); + this.closed = true; + }); + } + // TypeScript doesn't allow getters/accessors with different types, + // so for now, we'll have to make do with an "any" in the mix. + // https://github.com/Microsoft/TypeScript/issues/2521 + get location() { + return this._location; + } + set location(url) { + url = resolveURL(url); + ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'loadURL', url); + } + close() { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId); + } + focus() { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus'); + } + blur() { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur'); + } + print() { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print'); + } + postMessage(message, targetOrigin) { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin); + } + eval(...args) { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript', ...args); + } +} +exports.windowSetup = (guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen) => { + if (guestInstanceId == null) { + // Override default window.close. + window.close = function () { + ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE'); + }; + } + if (!usesNativeWindowOpen) { + // Make the browser window or guest view emit "new-window" event. + window.open = function (url, frameName, features) { + if (url != null && url !== '') { + url = resolveURL(url); + } + const guestId = ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features)); + if (guestId != null) { + return getOrCreateProxy(guestId); + } + else { + return null; + } + }; + if (openerId != null) { + window.opener = getOrCreateProxy(openerId); + } + } + // But we do not support prompt(). + window.prompt = function () { + throw new Error('prompt() is and will not be supported.'); + }; + ipc_renderer_internal_1.ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (_event, sourceId, message, sourceOrigin) { + // Manually dispatch event instead of using postMessage because we also need to + // set event.source. + // + // Why any? We can't construct a MessageEvent and we can't + // use `as MessageEvent` because you're not supposed to override + // data, origin, and source + const event = document.createEvent('Event'); + event.initEvent('message', false, false); + event.data = message; + event.origin = sourceOrigin; + event.source = getOrCreateProxy(sourceId); + window.dispatchEvent(event); + }); + window.history.back = function () { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK'); + }; + window.history.forward = function () { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD'); + }; + window.history.go = function (offset) { + ipc_renderer_internal_1.ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset); + }; + defineProperty(window.history, 'length', { + get: function () { + return ipc_renderer_internal_1.ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH'); + } + }); + if (guestInstanceId != null) { + // Webview `document.visibilityState` tracks window visibility (and ignores + // the actual element visibility) for backwards compatibility. + // See discussion in #9178. + // + // Note that this results in duplicate visibilitychange events (since + // Chromium also fires them) and potentially incorrect visibility change. + // We should reconsider this decision for Electron 2.0. + let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible'; + // Subscribe to visibilityState changes. + ipc_renderer_internal_1.ipcRendererInternal.on('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState) { + if (cachedVisibilityState !== visibilityState) { + cachedVisibilityState = visibilityState; + document.dispatchEvent(new Event('visibilitychange')); + } + }); + // Make document.hidden and document.visibilityState return the correct value. + defineProperty(document, 'hidden', { + get: function () { + return cachedVisibilityState !== 'visible'; + } + }); + defineProperty(document, 'visibilityState', { + get: function () { + return cachedVisibilityState; + } + }); + } +}; +//# sourceMappingURL=window-setup.js.map \ No newline at end of file diff --git a/electronasar/development/worker/init.js b/electronasar/development/worker/init.js new file mode 100644 index 0000000..492e6a1 --- /dev/null +++ b/electronasar/development/worker/init.js @@ -0,0 +1,30 @@ +'use strict'; +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'); +// Expose public APIs. +Module.globalPaths.push(path.join(__dirname, 'api', 'exports')); +// 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 (self.location.protocol === 'file:') { + const pathname = process.platform === 'win32' && self.location.pathname[0] === '/' ? self.location.pathname.substr(1) : self.location.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; +} +//# sourceMappingURL=init.js.map \ No newline at end of file