diff --git a/electronasar/canary/browser/api/app.js b/electronasar/canary/browser/api/app.js index e7d2017..4cef536 100644 --- a/electronasar/canary/browser/api/app.js +++ b/electronasar/canary/browser/api/app.js @@ -1,88 +1,117 @@ -"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; +'use strict' + +const bindings = process.atomBinding('app') +const path = require('path') +const { app, App } = bindings + // Only one app object permitted. -exports.default = app; -const { deprecate, Menu } = electron; -let dockMenu = null; +module.exports = app + +const electron = require('electron') +const { deprecate, Menu } = electron +const { EventEmitter } = require('events') + +let dockMenu = null + // App is an EventEmitter. -Object.setPrototypeOf(App.prototype, events_1.EventEmitter.prototype); -events_1.EventEmitter.call(app); +Object.setPrototypeOf(App.prototype, EventEmitter.prototype) +EventEmitter.call(app) + Object.assign(app, { - // TODO(codebytere): remove in 7.0 - setApplicationMenu(menu) { - return Menu.setApplicationMenu(menu); + setApplicationMenu (menu) { + return Menu.setApplicationMenu(menu) + }, + getApplicationMenu () { + return Menu.getApplicationMenu() + }, + commandLine: { + appendSwitch (...args) { + const castedArgs = args.map((arg) => { + return typeof arg !== 'string' ? `${arg}` : arg + }) + return bindings.appendSwitch(...castedArgs) }, - // TODO(codebytere): remove in 7.0 - 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().`); + appendArgument (...args) { + const castedArgs = args.map((arg) => { + return typeof arg !== 'string' ? `${arg}` : arg + }) + return bindings.appendArgument(...castedArgs) } -}); -// 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); + } +}) + +const nativeFn = app.getAppMetrics +app.getAppMetrics = () => { + const metrics = nativeFn.call(app) + for (const metric of metrics) { + if ('memory' in metric) { + deprecate.removeProperty(metric, 'memory') } -}); + } + + return metrics +} + app.isPackaged = (() => { - const execFile = path.basename(process.execPath).toLowerCase(); - if (process.platform === 'win32') { - return execFile !== 'electron.exe'; - } - return execFile !== 'electron'; -})(); -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); - } -}; + const execFile = path.basename(process.execPath).toLowerCase() + if (process.platform === 'win32') { + return execFile !== 'electron.exe' + } + return execFile !== 'electron' +})() + if (process.platform === 'darwin') { - const setDockMenu = app.dock.setMenu; - app.dock.setMenu = (menu) => { - dockMenu = menu; - setDockMenu(menu); - }; - app.dock.getMenu = () => dockMenu; + app.dock = { + bounce (type = 'informational') { + return bindings.dockBounce(type) + }, + cancelBounce: bindings.dockCancelBounce, + downloadFinished: bindings.dockDownloadFinished, + setBadge: bindings.dockSetBadgeText, + getBadge: bindings.dockGetBadgeText, + hide: bindings.dockHide, + show: bindings.dockShow, + isVisible: bindings.dockIsVisible, + setMenu (menu) { + dockMenu = menu + bindings.dockSetMenu(menu) + }, + getMenu () { + return dockMenu + }, + setIcon: bindings.dockSetIcon + } } + +if (process.platform === 'linux') { + app.launcher = { + setBadgeCount: bindings.unityLauncherSetBadgeCount, + getBadgeCount: bindings.unityLauncherGetBadgeCount, + isCounterBadgeAvailable: bindings.unityLauncherAvailable, + isUnityRunning: bindings.unityLauncherAvailable + } +} + +app.allowNTLMCredentialsForAllDomains = function (allow) { + if (!process.noDeprecations) { + deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains') + } + const domains = allow ? '*' : '' + if (!this.isReady()) { + this.commandLine.appendSwitch('auth-server-whitelist', domains) + } else { + electron.session.defaultSession.allowNTLMCredentialsForDomains(domains) + } +} + // Routes the events to webContents. -const events = ['login', 'certificate-error', 'select-client-certificate']; +const events = ['login', 'certificate-error', 'select-client-certificate'] for (const name of events) { - app.on(name, (event, webContents, ...args) => { - webContents.emit(name, event, ...args); - }); + app.on(name, (event, webContents, ...args) => { + webContents.emit(name, event, ...args) + }) } -// Function Deprecations -app.getFileIcon = deprecate.promisify(app.getFileIcon); -// Property Deprecations -deprecate.fnToProperty(app, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled'); + // Wrappers for native classes. -const { DownloadItem } = process.electronBinding('download_item'); -Object.setPrototypeOf(DownloadItem.prototype, events_1.EventEmitter.prototype); -//# sourceMappingURL=app.js.map \ No newline at end of file +const { DownloadItem } = process.atomBinding('download_item') +Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) diff --git a/electronasar/canary/browser/api/auto-updater.js b/electronasar/canary/browser/api/auto-updater.js index cbd3003..db3e8da 100644 --- a/electronasar/canary/browser/api/auto-updater.js +++ b/electronasar/canary/browser/api/auto-updater.js @@ -1,8 +1,7 @@ -'use strict'; +'use strict' + if (process.platform === 'win32') { - module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win'); + module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win') +} else { + module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native') } -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/canary/browser/api/auto-updater/auto-updater-native.js b/electronasar/canary/browser/api/auto-updater/auto-updater-native.js index 4b34107..d15a3c2 100644 --- a/electronasar/canary/browser/api/auto-updater/auto-updater-native.js +++ b/electronasar/canary/browser/api/auto-updater/auto-updater-native.js @@ -1,8 +1,10 @@ -'use strict'; -const EventEmitter = require('events').EventEmitter; -const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater'); +'use strict' + +const EventEmitter = require('events').EventEmitter +const { autoUpdater, AutoUpdater } = process.atomBinding('auto_updater') + // AutoUpdater is an EventEmitter. -Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype); -EventEmitter.call(autoUpdater); -module.exports = autoUpdater; -//# sourceMappingURL=auto-updater-native.js.map \ No newline at end of file +Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype) +EventEmitter.call(autoUpdater) + +module.exports = autoUpdater diff --git a/electronasar/canary/browser/api/auto-updater/auto-updater-win.js b/electronasar/canary/browser/api/auto-updater/auto-updater-win.js index 177b630..cb8e6a0 100644 --- a/electronasar/canary/browser/api/auto-updater/auto-updater-win.js +++ b/electronasar/canary/browser/api/auto-updater/auto-updater-win.js @@ -1,71 +1,74 @@ -'use strict'; -const { app } = require('electron'); -const { EventEmitter } = require('events'); -const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win'); +'use strict' + +const { app } = require('electron') +const { EventEmitter } = require('events') +const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win') + class AutoUpdater extends EventEmitter { - quitAndInstall() { - if (!this.updateAvailable) { - return this.emitError('No update available, can\'t quit and install'); - } - squirrelUpdate.processStart(); - app.quit(); + quitAndInstall () { + if (!this.updateAvailable) { + return this.emitError('No update available, can\'t quit and install') } - getFeedURL() { - return this.updateURL; + 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') } - 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; + this.updateURL = updateURL + } + + checkForUpdates () { + if (!this.updateURL) { + return this.emitError('Update URL is not set') } - 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); + 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 + +module.exports = new AutoUpdater() diff --git a/electronasar/canary/browser/api/auto-updater/squirrel-update-win.js b/electronasar/canary/browser/api/auto-updater/squirrel-update-win.js index 97a3230..4954c67 100644 --- a/electronasar/canary/browser/api/auto-updater/squirrel-update-win.js +++ b/electronasar/canary/browser/api/auto-updater/squirrel-update-win.js @@ -1,107 +1,119 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); -const spawn = require('child_process').spawn; +'use strict' + +const fs = require('fs') +const path = require('path') +const spawn = require('child_process').spawn + // i.e. my-app/app-0.1.13/ -const appFolder = path.dirname(process.execPath); +const appFolder = path.dirname(process.execPath) + // i.e. my-app/Update.exe -const updateExe = path.resolve(appFolder, '..', 'Update.exe'); -const exeName = path.basename(process.execPath); -let spawnedArgs = []; -let spawnedProcess; -const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]); +const updateExe = path.resolve(appFolder, '..', 'Update.exe') +const exeName = path.basename(process.execPath) +let spawnedArgs = [] +let spawnedProcess + +const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]) + // Spawn a command and invoke the callback when it completes with an error // and the output from standard out. const spawnUpdate = function (args, detached, callback) { - let error, errorEmitted, stderr, stdout; - try { - // Ensure we don't spawn multiple squirrel processes - // Process spawned, same args: Attach events to alread running process - // Process spawned, different args: Return with error - // No process spawned: Spawn new process - if (spawnedProcess && !isSameArgs(args)) { - // Disabled for backwards compatibility: - // eslint-disable-next-line standard/no-callback-literal - return callback(`AutoUpdater process with arguments ${args} is already running`); - } - else if (!spawnedProcess) { - spawnedProcess = spawn(updateExe, args, { - detached: detached, - windowsHide: true - }); - spawnedArgs = args || []; - } + let error, errorEmitted, stderr, stdout + + try { + // Ensure we don't spawn multiple squirrel processes + // Process spawned, same args: Attach events to alread running process + // Process spawned, different args: Return with error + // No process spawned: Spawn new process + if (spawnedProcess && !isSameArgs(args)) { + // Disabled for backwards compatibility: + // eslint-disable-next-line standard/no-callback-literal + return callback(`AutoUpdater process with arguments ${args} is already running`) + } else if (!spawnedProcess) { + spawnedProcess = spawn(updateExe, args, { + detached: detached, + windowsHide: true + }) + spawnedArgs = args || [] } - catch (error1) { - error = error1; - // Shouldn't happen, but still guard it. - process.nextTick(function () { - return callback(error); - }); - return; + } 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 } - 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); - }); -}; + + // Process terminated with error. + if (code !== 0) { + // Disabled for backwards compatibility: + // eslint-disable-next-line standard/no-callback-literal + return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`) + } + + // Success. + callback(null, stdout) + }) +} + // Start an instance of the installed app. exports.processStart = function () { - return spawnUpdate(['--processStartAndWait', exeName], true, function () { }); -}; + return spawnUpdate(['--processStartAndWait', exeName], true, function () {}) +} + // Download the releases specified by the URL and write new results to stdout. exports.checkForUpdate = function (updateURL, callback) { - return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) { - let ref, ref1, update; - if (error != null) { - return callback(error); - } - try { - // Last line of output is the JSON details about the releases - const json = stdout.trim().split('\n').pop(); - update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0; - } - catch (_a) { - // Disabled for backwards compatibility: - // eslint-disable-next-line standard/no-callback-literal - return callback(`Invalid result:\n${stdout}`); - } - return callback(null, update); - }); -}; + return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) { + let ref, ref1, update + if (error != null) { + return callback(error) + } + try { + // Last line of output is the JSON details about the releases + const json = stdout.trim().split('\n').pop() + update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0 + } catch (jsonError) { + // Disabled for backwards compatibility: + // eslint-disable-next-line standard/no-callback-literal + return callback(`Invalid result:\n${stdout}`) + } + return callback(null, update) + }) +} + // Update the application to the latest remote version specified by URL. exports.update = function (updateURL, callback) { - return spawnUpdate(['--update', updateURL], false, callback); -}; + 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 + try { + fs.accessSync(updateExe, fs.R_OK) + return true + } catch (error) { + return false + } +} diff --git a/electronasar/canary/browser/api/browser-view.js b/electronasar/canary/browser/api/browser-view.js index 56f62c6..0779f5f 100644 --- a/electronasar/canary/browser/api/browser-view.js +++ b/electronasar/canary/browser/api/browser-view.js @@ -1,13 +1,16 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { BrowserView } = process.electronBinding('browser_view'); -Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype); +'use strict' + +const { EventEmitter } = require('events') +const { BrowserView } = process.atomBinding('browser_view') + +Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype) + BrowserView.fromWebContents = (webContents) => { - for (const view of BrowserView.getAllViews()) { - if (view.webContents.equal(webContents)) - return view; - } - return null; -}; -module.exports = BrowserView; -//# sourceMappingURL=browser-view.js.map \ No newline at end of file + for (const view of BrowserView.getAllViews()) { + if (view.webContents.equal(webContents)) return view + } + + return null +} + +module.exports = BrowserView diff --git a/electronasar/canary/browser/api/browser-window.js b/electronasar/canary/browser/api/browser-window.js index 3927adc..3694acd 100644 --- a/electronasar/canary/browser/api/browser-window.js +++ b/electronasar/canary/browser/api/browser-window.js @@ -1,171 +1,191 @@ -'use strict'; -const electron = require('electron'); -const { WebContentsView, TopLevelWindow } = electron; -const { BrowserWindow } = process.electronBinding('window'); -Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype); +'use strict' + +const electron = require('electron') +const { WebContentsView, TopLevelWindow } = electron +const { BrowserWindow } = process.atomBinding('window') + +Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype) + BrowserWindow.prototype._init = function () { - // Call parent class's _init. - TopLevelWindow.prototype._init.call(this); - // Avoid recursive require. - const { app } = electron; - // Create WebContentsView. - this.setContentView(new WebContentsView(this.webContents)); - const nativeSetBounds = this.setBounds; - this.setBounds = (bounds, ...opts) => { - bounds = 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); + // Call parent class's _init. + TopLevelWindow.prototype._init.call(this) + + // Avoid recursive require. + const { app } = electron + + // Create WebContentsView. + this.setContentView(new WebContentsView(this.webContents)) + + const nativeSetBounds = this.setBounds + this.setBounds = (bounds, ...opts) => { + bounds = { + ...this.getBounds(), + ...bounds } - // 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; - } - }); -}; + nativeSetBounds.call(this, bounds, ...opts) + } + + // window.resizeTo(...) + // window.moveTo(...) + this.webContents.on('move', (event, size) => { + this.setBounds(size) + }) + + // Hide the auto-hide menu when webContents is focused. + this.webContents.on('activate', () => { + if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) { + this.setMenuBarVisibility(false) + } + }) + + // Change window title to page title. + this.webContents.on('page-title-updated', (event, title) => { + // Route the event to BrowserWindow. + this.emit('page-title-updated', event, title) + if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title) + }) + + // Sometimes the webContents doesn't get focus when window is shown, so we + // have to force focusing on webContents in this case. The safest way is to + // focus it when we first start to load URL, if we do it earlier it won't + // have effect, if we do it later we might move focus in the page. + // + // Though this hack is only needed on macOS when the app is launched from + // Finder, we still do it on all platforms in case of other bugs we don't + // know. + this.webContents.once('load-url', function () { + this.focus() + }) + + // Redirect focus/blur event to app instance too. + this.on('blur', (event) => { + app.emit('browser-window-blur', event, this) + }) + this.on('focus', (event) => { + app.emit('browser-window-focus', event, this) + }) + + // Subscribe to visibilityState changes and pass to renderer process. + let isVisible = this.isVisible() && !this.isMinimized() + const visibilityChanged = () => { + const newState = this.isVisible() && !this.isMinimized() + if (isVisible !== newState) { + isVisible = newState + const visibilityState = isVisible ? 'visible' : 'hidden' + this.webContents.emit('-window-visibility-change', visibilityState) + } + } + + const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'] + for (const event of visibilityEvents) { + this.on(event, visibilityChanged) + } + + // Notify the creation of the window. + app.emit('browser-window-created', {}, this) + + Object.defineProperty(this, 'devToolsWebContents', { + enumerable: true, + configurable: false, + get () { + return this.webContents.devToolsWebContents + } + }) +} + const isBrowserWindow = (win) => { - return win && win.constructor.name === 'BrowserWindow'; -}; + return win && win.constructor.name === 'BrowserWindow' +} + BrowserWindow.fromId = (id) => { - const win = TopLevelWindow.fromId(id); - return isBrowserWindow(win) ? win : null; -}; + const win = TopLevelWindow.fromId(id) + return isBrowserWindow(win) ? win : null +} + BrowserWindow.getAllWindows = () => { - return TopLevelWindow.getAllWindows().filter(isBrowserWindow); -}; + return TopLevelWindow.getAllWindows().filter(isBrowserWindow) +} + BrowserWindow.getFocusedWindow = () => { - for (const window of BrowserWindow.getAllWindows()) { - if (window.isFocused() || window.isDevToolsFocused()) - return window; - } - return null; -}; + 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; - } -}; + 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; -}; + for (const window of BrowserWindow.getAllWindows()) { + if (window.getBrowserView() === browserView) return window + } + + return null +} + BrowserWindow.fromDevToolsWebContents = (webContents) => { - for (const window of BrowserWindow.getAllWindows()) { - const { devToolsWebContents } = window; - if (devToolsWebContents != null && devToolsWebContents.equal(webContents)) { - return window; - } + for (const window of BrowserWindow.getAllWindows()) { + 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 + loadURL (...args) { + return this.webContents.loadURL(...args) + }, + getURL (...args) { + return this.webContents.getURL() + }, + loadFile (...args) { + return this.webContents.loadFile(...args) + }, + reload (...args) { + return this.webContents.reload(...args) + }, + send (...args) { + return this.webContents.send(...args) + }, + openDevTools (...args) { + return this.webContents.openDevTools(...args) + }, + closeDevTools () { + return this.webContents.closeDevTools() + }, + isDevToolsOpened () { + return this.webContents.isDevToolsOpened() + }, + isDevToolsFocused () { + return this.webContents.isDevToolsFocused() + }, + toggleDevTools () { + return this.webContents.toggleDevTools() + }, + inspectElement (...args) { + return this.webContents.inspectElement(...args) + }, + inspectServiceWorker () { + return this.webContents.inspectServiceWorker() + }, + showDefinitionForSelection () { + return this.webContents.showDefinitionForSelection() + }, + capturePage (...args) { + return this.webContents.capturePage(...args) + }, + setTouchBar (touchBar) { + electron.TouchBar._setOnWindow(touchBar, this) + }, + setBackgroundThrottling (allowed) { + this.webContents.setBackgroundThrottling(allowed) + } +}) + +module.exports = BrowserWindow diff --git a/electronasar/canary/browser/api/content-tracing.js b/electronasar/canary/browser/api/content-tracing.js index 9e1682c..81bd70f 100644 --- a/electronasar/canary/browser/api/content-tracing.js +++ b/electronasar/canary/browser/api/content-tracing.js @@ -1,12 +1,3 @@ -'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 +'use strict' + +module.exports = process.atomBinding('content_tracing') diff --git a/electronasar/canary/browser/api/crash-reporter.js b/electronasar/canary/browser/api/crash-reporter.js index 988c0d1..a963be8 100644 --- a/electronasar/canary/browser/api/crash-reporter.js +++ b/electronasar/canary/browser/api/crash-reporter.js @@ -1,10 +1,14 @@ -'use strict'; -const CrashReporter = require('@electron/internal/common/crash-reporter'); -const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init'); +'use strict' + +const CrashReporter = require('@electron/internal/common/crash-reporter') +const ipcMain = require('@electron/internal/browser/ipc-main-internal') + class CrashReporterMain extends CrashReporter { - init(options) { - return crashReporterInit(options); - } + sendSync (channel, ...args) { + const event = {} + ipcMain.emit(channel, event, ...args) + return event.returnValue + } } -module.exports = new CrashReporterMain(); -//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file + +module.exports = new CrashReporterMain() diff --git a/electronasar/canary/browser/api/dialog.js b/electronasar/canary/browser/api/dialog.js index 3e5d83d..7659800 100644 --- a/electronasar/canary/browser/api/dialog.js +++ b/electronasar/canary/browser/api/dialog.js @@ -1,184 +1,312 @@ -'use strict'; -const { app, BrowserWindow, deprecate } = require('electron'); -const binding = process.electronBinding('dialog'); -const v8Util = process.electronBinding('v8_util'); +'use strict' + +const { app, BrowserWindow } = require('electron') +const binding = process.atomBinding('dialog') +const v8Util = process.atomBinding('v8_util') + const fileDialogProperties = { - openFile: 1 << 0, - openDirectory: 1 << 1, - multiSelections: 1 << 2, - createDirectory: 1 << 3, - showHiddenFiles: 1 << 4, - promptToCreate: 1 << 5, - noResolveAliases: 1 << 6, - treatPackageAsDirectory: 1 << 7 -}; + openFile: 1 << 0, + openDirectory: 1 << 1, + multiSelections: 1 << 2, + createDirectory: 1 << 3, + showHiddenFiles: 1 << 4, + promptToCreate: 1 << 5, + noResolveAliases: 1 << 6, + treatPackageAsDirectory: 1 << 7 +} + +const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] + +const messageBoxOptions = { + noLink: 1 << 0 +} + +const parseArgs = function (window, options, callback, ...args) { + if (window != null && window.constructor !== BrowserWindow) { + // Shift. + [callback, options, window] = [options, window, null] + } + + if ((callback == null) && typeof options === 'function') { + // Shift. + [callback, options] = [options, null] + } + + // Fallback to using very last argument as the callback function + const lastArgument = args[args.length - 1] + if ((callback == null) && typeof lastArgument === 'function') { + callback = lastArgument + } + + return [window, options, callback] +} + const normalizeAccessKey = (text) => { - if (typeof text !== 'string') - return text; - // macOS does not have access keys so remove single ampersands - // and replace double ampersands with a single ampersand - if (process.platform === 'darwin') { - return text.replace(/&(&?)/g, '$1'); - } - // Linux uses a single underscore as an access key prefix so escape - // existing single underscores with a second underscore, replace double - // ampersands with a single ampersand, and replace a single ampersand with - // a single underscore - if (process.platform === 'linux') { - return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => { - if (after === '&') - return after; - return `_${after}`; - }); - } - return text; -}; + 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 (!app.isReady()) { + throw new Error('dialog module can only be used after app is ready') + } +} + +module.exports = { + showOpenDialog: function (...args) { + checkAppInitialized() + + let [window, options, callback] = parseArgs(...args) + if (options == null) { - options = { - title: 'Open', - properties: ['openFile'] - }; + 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; + + let { buttonLabel, defaultPath, filters, properties, title, message, securityScopedBookmarks = false } = options + + if (properties == null) { + properties = ['openFile'] + } else if (!Array.isArray(properties)) { + throw new TypeError('Properties must be an array') + } + + let dialogProperties = 0 for (const prop in fileDialogProperties) { - if (properties.includes(prop)) { - dialogProperties |= fileDialogProperties[prop]; - } + if (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 (title == null) { + title = '' + } else if (typeof title !== 'string') { + throw new TypeError('Title must be a string') } - 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; + + if (buttonLabel == null) { + buttonLabel = '' + } else if (typeof buttonLabel !== 'string') { + throw new TypeError('Button label must be a string') + } + + if (defaultPath == null) { + defaultPath = '' + } else if (typeof defaultPath !== 'string') { + throw new TypeError('Default path must be a string') + } + + if (filters == null) { + filters = [] + } + + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + + const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) { + return success ? callback(result, bookmarkData) : callback() + } : null + const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window } + settings.properties = dialogProperties + return binding.showOpenDialog(settings, wrappedCallback) + }, + + showSaveDialog: function (...args) { + checkAppInitialized() + + let [window, options, callback] = parseArgs(...args) + + if (options == null) { + options = { + title: 'Save' + } + } + + let { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks = false, nameFieldLabel, showsTagField } = options + + if (title == null) { + title = '' + } else if (typeof title !== 'string') { + throw new TypeError('Title must be a string') + } + + if (buttonLabel == null) { + buttonLabel = '' + } else if (typeof buttonLabel !== 'string') { + throw new TypeError('Button label must be a string') + } + + if (defaultPath == null) { + defaultPath = '' + } else if (typeof defaultPath !== 'string') { + throw new TypeError('Default path must be a string') + } + + if (filters == null) { + filters = [] + } + + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + + if (nameFieldLabel == null) { + nameFieldLabel = '' + } else if (typeof nameFieldLabel !== 'string') { + throw new TypeError('Name field label must be a string') + } + + if (showsTagField == null) { + showsTagField = true + } + + const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) { + return success ? callback(result, bookmarkData) : callback() + } : null + const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } + return binding.showSaveDialog(settings, wrappedCallback) + }, + + showMessageBox: function (...args) { + checkAppInitialized() + + let [window, options, callback] = parseArgs(...args) + + if (options == null) { + options = { + type: 'none' + } + } + + let { + buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail, + icon, message, title, type + } = options + + if (type == null) { + type = 'none' + } + + const messageBoxType = messageBoxTypes.indexOf(type) + if (messageBoxType === -1) { + throw new TypeError('Invalid message box type') + } + + if (buttons == null) { + buttons = [] + } else if (!Array.isArray(buttons)) { + throw new TypeError('Buttons must be an array') + } + + if (options.normalizeAccessKeys) { + buttons = buttons.map(normalizeAccessKey) + } + + if (title == null) { + title = '' + } else if (typeof title !== 'string') { + throw new TypeError('Title must be a string') + } + + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + + if (detail == null) { + detail = '' + } else if (typeof detail !== 'string') { + throw new TypeError('Detail must be a string') + } + + checkboxChecked = !!checkboxChecked + + if (checkboxLabel == null) { + checkboxLabel = '' + } else if (typeof checkboxLabel !== 'string') { + throw new TypeError('checkboxLabel must be a string') + } + + if (icon == null) { + icon = null + } + + if (defaultId == null) { + defaultId = -1 + } + // Choose a default button to get selected when dialog is cancelled. if (cancelId == null) { - // If the defaultId is set to 0, ensure the cancel button is a different index (1) - cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0; - for (let i = 0; i < buttons.length; i++) { - const text = buttons[i].toLowerCase(); - if (text === 'cancel' || text === 'no') { - cancelId = i; - break; - } + // 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); + + const flags = options.noLink ? messageBoxOptions.noLink : 0 + return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, + flags, title, message, detail, checkboxLabel, + checkboxChecked, icon, window, callback) + }, + + showErrorBox: function (...args) { + return binding.showErrorBox(...args) + }, + + showCertificateTrustDialog: function (...args) { + const [window, options, callback] = parseArgs(...args) + + if (options == null || typeof options !== 'object') { + throw new TypeError('options must be an object') } - else { - return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, flags, title, message, detail, checkboxLabel, checkboxChecked, icon, window); + + let { certificate, message } = options + if (certificate == null || typeof certificate !== 'object') { + throw new TypeError('certificate must be an object') } -}; -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); + + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('message must be a string') } -}; -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 + + return binding.showCertificateTrustDialog(window, certificate, message, callback) + } +} + +// Mark standard asynchronous functions. +v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true) +v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true) +v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true) diff --git a/electronasar/canary/browser/api/exports/electron.js b/electronasar/canary/browser/api/exports/electron.js index 8c94396..0cf6c26 100644 --- a/electronasar/canary/browser/api/exports/electron.js +++ b/electronasar/canary/browser/api/exports/electron.js @@ -1,19 +1,15 @@ -'use strict'; -const common = require('@electron/internal/common/api/exports/electron'); +'use strict' + +const common = require('@electron/internal/common/api/exports/electron') // since browser module list is also used in renderer, keep it separate. -const moduleList = require('@electron/internal/browser/api/module-list'); +const moduleList = require('@electron/internal/browser/api/module-list') + // Import common modules. -common.defineProperties(exports); +common.defineProperties(exports) + for (const module of moduleList) { - Object.defineProperty(exports, module.name, { - enumerable: !module.private, - get: common.memoizedGetter(() => { - 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; - }) - }); + Object.defineProperty(exports, module.name, { + enumerable: !module.private, + get: common.memoizedGetter(() => require(`@electron/internal/browser/api/${module.file}.js`)) + }) } -//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/electronasar/canary/browser/api/global-shortcut.js b/electronasar/canary/browser/api/global-shortcut.js index 834e35b..ec9878e 100644 --- a/electronasar/canary/browser/api/global-shortcut.js +++ b/electronasar/canary/browser/api/global-shortcut.js @@ -1,3 +1,3 @@ -'use strict'; -module.exports = process.electronBinding('global_shortcut').globalShortcut; -//# sourceMappingURL=global-shortcut.js.map \ No newline at end of file +'use strict' + +module.exports = process.atomBinding('global_shortcut').globalShortcut diff --git a/electronasar/canary/browser/api/in-app-purchase.js b/electronasar/canary/browser/api/in-app-purchase.js index 8a6d71e..95f4589 100644 --- a/electronasar/canary/browser/api/in-app-purchase.js +++ b/electronasar/canary/browser/api/in-app-purchase.js @@ -1,22 +1,20 @@ -'use strict'; -const { deprecate } = require('electron'); +'use strict' + 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; + const { EventEmitter } = require('events') + const { inAppPurchase, InAppPurchase } = process.atomBinding('in_app_purchase') + + // inAppPurchase is an EventEmitter. + Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) + EventEmitter.call(inAppPurchase) + + module.exports = inAppPurchase +} else { + module.exports = { + purchaseProduct: (productID, quantity, callback) => { + throw new Error('The inAppPurchase module can only be used on macOS') + }, + canMakePayments: () => false, + getReceiptURL: () => '' + } } -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/canary/browser/api/ipc-main.js b/electronasar/canary/browser/api/ipc-main.js index b82b602..9ab9569 100644 --- a/electronasar/canary/browser/api/ipc-main.js +++ b/electronasar/canary/browser/api/ipc-main.js @@ -1,8 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const events_1 = require("events"); -const emitter = new events_1.EventEmitter(); +'use strict' + +const { EventEmitter } = require('events') + +const emitter = new 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 +emitter.on('error', () => {}) + +module.exports = emitter diff --git a/electronasar/canary/browser/api/menu-item-roles.js b/electronasar/canary/browser/api/menu-item-roles.js index 28cc1c5..826ad83 100644 --- a/electronasar/canary/browser/api/menu-item-roles.js +++ b/electronasar/canary/browser/api/menu-item-roles.js @@ -1,309 +1,297 @@ -'use strict'; -const { app } = require('electron'); -const isMac = process.platform === 'darwin'; -const isWindows = process.platform === 'win32'; -const isLinux = process.platform === 'linux'; +'use strict' + +const { app } = require('electron') + 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' } - ]) - ] + about: { + get label () { + return process.platform === 'linux' ? 'About' : `About ${app.getName()}` } -}; + }, + close: { + label: process.platform === 'darwin' ? 'Close Window' : 'Close', + accelerator: 'CommandOrControl+W', + windowMethod: 'close' + }, + copy: { + label: 'Copy', + accelerator: 'CommandOrControl+C', + webContentsMethod: 'copy', + registerAccelerator: false + }, + cut: { + label: 'Cut', + accelerator: 'CommandOrControl+X', + webContentsMethod: 'cut', + registerAccelerator: false + }, + delete: { + label: 'Delete', + webContentsMethod: 'delete' + }, + forcereload: { + label: 'Force Reload', + accelerator: 'Shift+CmdOrCtrl+R', + nonNativeMacOSRole: true, + windowMethod: (window) => { + window.webContents.reloadIgnoringCache() + } + }, + front: { + label: 'Bring All to Front' + }, + help: { + label: 'Help' + }, + hide: { + get label () { + return `Hide ${app.getName()}` + }, + accelerator: 'Command+H' + }, + hideothers: { + label: 'Hide Others', + accelerator: 'Command+Alt+H' + }, + minimize: { + label: 'Minimize', + accelerator: 'CommandOrControl+M', + windowMethod: 'minimize' + }, + paste: { + label: 'Paste', + accelerator: 'CommandOrControl+V', + webContentsMethod: 'paste', + registerAccelerator: false + }, + pasteandmatchstyle: { + label: 'Paste and Match Style', + accelerator: 'Shift+CommandOrControl+V', + webContentsMethod: 'pasteAndMatchStyle', + registerAccelerator: false + }, + quit: { + get label () { + switch (process.platform) { + case 'darwin': return `Quit ${app.getName()}` + case 'win32': return 'Exit' + default: return 'Quit' + } + }, + accelerator: process.platform === 'win32' ? null : 'CommandOrControl+Q', + appMethod: 'quit' + }, + redo: { + label: 'Redo', + accelerator: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z', + webContentsMethod: 'redo' + }, + reload: { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + nonNativeMacOSRole: true, + windowMethod: 'reload' + }, + resetzoom: { + label: 'Actual Size', + accelerator: 'CommandOrControl+0', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + webContents.setZoomLevel(0) + } + }, + selectall: { + label: 'Select All', + accelerator: 'CommandOrControl+A', + webContentsMethod: 'selectAll' + }, + services: { + label: 'Services' + }, + recentdocuments: { + label: 'Open Recent' + }, + clearrecentdocuments: { + label: 'Clear Menu' + }, + startspeaking: { + label: 'Start Speaking' + }, + stopspeaking: { + label: 'Stop Speaking' + }, + toggledevtools: { + label: 'Toggle Developer Tools', + accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', + nonNativeMacOSRole: true, + windowMethod: 'toggleDevTools' + }, + togglefullscreen: { + label: 'Toggle Full Screen', + accelerator: process.platform === 'darwin' ? 'Control+Command+F' : 'F11', + windowMethod: (window) => { + window.setFullScreen(!window.isFullScreen()) + } + }, + undo: { + label: 'Undo', + accelerator: 'CommandOrControl+Z', + webContentsMethod: 'undo' + }, + unhide: { + label: 'Show All' + }, + window: { + label: 'Window' + }, + zoom: { + label: 'Zoom' + }, + zoomin: { + label: 'Zoom In', + accelerator: 'CommandOrControl+Plus', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + webContents.getZoomLevel((zoomLevel) => { + webContents.setZoomLevel(zoomLevel + 0.5) + }) + } + }, + zoomout: { + label: 'Zoom Out', + accelerator: 'CommandOrControl+-', + nonNativeMacOSRole: true, + webContentsMethod: (webContents) => { + webContents.getZoomLevel((zoomLevel) => { + webContents.setZoomLevel(zoomLevel - 0.5) + }) + } + }, + // Edit submenu (should fit both Mac & Windows) + editmenu: { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + + process.platform === 'darwin' ? { + role: 'pasteAndMatchStyle' + } : null, + + { + role: 'delete' + }, + + process.platform === 'win32' ? { + type: 'separator' + } : null, + + { + role: 'selectAll' + } + ] + }, + + // Window submenu should be used for Mac only + windowmenu: { + label: 'Window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + }, + + process.platform === 'darwin' ? { + type: 'separator' + } : null, + + process.platform === 'darwin' ? { + role: 'front' + } : null + + ] + } +} + const canExecuteRole = (role) => { - if (!roles.hasOwnProperty(role)) - return false; - if (!isMac) - return true; - // macOS handles all roles natively except for a few - return roles[role].nonNativeMacOSRole; -}; + if (!roles.hasOwnProperty(role)) return false + if (process.platform !== 'darwin') return true + + // macOS handles all roles natively except for a few + return roles[role].nonNativeMacOSRole +} + exports.getDefaultLabel = (role) => { - return roles.hasOwnProperty(role) ? roles[role].label : ''; -}; + return roles.hasOwnProperty(role) ? roles[role].label : '' +} + exports.getDefaultAccelerator = (role) => { - if (roles.hasOwnProperty(role)) - return roles[role].accelerator; -}; + if (roles.hasOwnProperty(role)) return roles[role].accelerator +} + exports.shouldRegisterAccelerator = (role) => { - const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined; - return hasRoleRegister ? roles[role].registerAccelerator : true; -}; + const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined + return hasRoleRegister ? roles[role].registerAccelerator : true +} + exports.getDefaultSubmenu = (role) => { - if (!roles.hasOwnProperty(role)) - return; - let { submenu } = roles[role]; - // remove null items from within the submenu - if (Array.isArray(submenu)) { - submenu = submenu.filter((item) => item != null); - } - return submenu; -}; + if (!roles.hasOwnProperty(role)) return + + let { submenu } = roles[role] + + // remove null items from within the submenu + if (Array.isArray(submenu)) { + submenu = submenu.filter((item) => item != null) + } + + return submenu +} + exports.execute = (role, focusedWindow, focusedWebContents) => { - if (!canExecuteRole(role)) - return false; - const { appMethod, webContentsMethod, windowMethod } = roles[role]; - if (appMethod) { - app[appMethod](); - return true; + if (!canExecuteRole(role)) return false + + const { appMethod, webContentsMethod, windowMethod } = roles[role] + + if (appMethod) { + app[appMethod]() + return true + } + + if (windowMethod && focusedWindow != null) { + if (typeof windowMethod === 'function') { + windowMethod(focusedWindow) + } else { + focusedWindow[windowMethod]() } - if (windowMethod && focusedWindow != null) { - if (typeof windowMethod === 'function') { - windowMethod(focusedWindow); - } - else { - focusedWindow[windowMethod](); - } - return true; + return true + } + + if (webContentsMethod && focusedWebContents != null) { + if (typeof webContentsMethod === 'function') { + webContentsMethod(focusedWebContents) + } else { + focusedWebContents[webContentsMethod]() } - 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 + return true + } + + return false +} diff --git a/electronasar/canary/browser/api/menu-item.js b/electronasar/canary/browser/api/menu-item.js index 244621f..33388cb 100644 --- a/electronasar/canary/browser/api/menu-item.js +++ b/electronasar/canary/browser/api/menu-item.js @@ -1,74 +1,85 @@ -'use strict'; -const roles = require('@electron/internal/browser/api/menu-item-roles'); -let nextCommandId = 0; +'use strict' + +const roles = require('@electron/internal/browser/api/menu-item-roles') + +let nextCommandId = 0 + const MenuItem = function (options) { - const { Menu } = require('electron'); - // Preserve extra fields specified by user - for (const key in options) { - if (!(key in this)) - this[key] = options[key]; + const { Menu } = require('electron') + + // Preserve extra fields specified by user + for (const key in options) { + if (!(key in this)) this[key] = options[key] + } + if (typeof this.role === 'string' || this.role instanceof String) { + this.role = this.role.toLowerCase() + } + this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) + if (this.submenu != null && this.submenu.constructor !== Menu) { + this.submenu = Menu.buildFromTemplate(this.submenu) + } + if (this.type == null && this.submenu != null) { + this.type = 'submenu' + } + if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) { + throw new Error('Invalid submenu') + } + + this.overrideReadOnlyProperty('type', 'normal') + this.overrideReadOnlyProperty('role') + this.overrideReadOnlyProperty('accelerator') + this.overrideReadOnlyProperty('icon') + this.overrideReadOnlyProperty('submenu') + + this.overrideProperty('label', roles.getDefaultLabel(this.role)) + this.overrideProperty('sublabel', '') + this.overrideProperty('enabled', true) + this.overrideProperty('visible', true) + this.overrideProperty('checked', false) + this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role)) + + if (!MenuItem.types.includes(this.type)) { + throw new Error(`Unknown menu item type: ${this.type}`) + } + + this.overrideReadOnlyProperty('commandId', ++nextCommandId) + + const click = options.click + this.click = (event, focusedWindow, focusedWebContents) => { + // Manually flip the checked flags when clicked. + if (this.type === 'checkbox' || this.type === 'radio') { + this.checked = !this.checked } - if (typeof this.role === 'string' || this.role instanceof String) { - this.role = this.role.toLowerCase(); + + 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) + } } - 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.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] + MenuItem.prototype.getDefaultRoleAccelerator = function () { - return roles.getDefaultAccelerator(this.role); -}; + return roles.getDefaultAccelerator(this.role) +} + MenuItem.prototype.overrideProperty = function (name, defaultValue = null) { - if (this[name] == null) { - this[name] = defaultValue; - } -}; + if (this[name] == null) { + this[name] = defaultValue + } +} + MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) { - this.overrideProperty(name, defaultValue); - Object.defineProperty(this, name, { - enumerable: true, - writable: false, - value: this[name] - }); -}; -module.exports = MenuItem; -//# sourceMappingURL=menu-item.js.map \ No newline at end of file + this.overrideProperty(name, defaultValue) + Object.defineProperty(this, name, { + enumerable: true, + writable: false, + value: this[name] + }) +} + +module.exports = MenuItem diff --git a/electronasar/canary/browser/api/menu-utils.js b/electronasar/canary/browser/api/menu-utils.js index 67f856e..42636bf 100644 --- a/electronasar/canary/browser/api/menu-utils.js +++ b/electronasar/canary/browser/api/menu-utils.js @@ -1,155 +1,176 @@ -'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); +'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 result; + 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, []); + +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' }) + } } - map.get(key).push(value); + return joined.concat(arr) + }, []) } -function indexOfGroupContainingID(groups, id, ignoreGroup) { - return groups.findIndex(candidateGroup => candidateGroup !== ignoreGroup && - candidateGroup.some(candidateItem => candidateItem.id === id)); + +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; - } - } +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) } - return false; + sorted.push(mark) + } + + originalOrder.forEach(visit) + return sorted } -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]; + +function attemptToMergeAGroup (groups) { + for (let i = 0; i < groups.length; i++) { + 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; - } - } - } + 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 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); + +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) + } + }) } - const sortedGroupIndexes = sortTopologically(originalOrder, edges); - return sortedGroupIndexes.map(i => groups[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 sortMenuItems(menuItems) { - const isSeparator = (item) => item.type === 'separator'; - const separators = menuItems.filter(i => i.type === 'separator'); - // Split the items into their implicit groups based upon separators. - const groups = splitArray(menuItems, isSeparator); - const mergedGroups = mergeGroups(groups); - const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup); - const sortedGroups = sortGroups(mergedGroupsWithSortedItems); - const joined = joinArrays(sortedGroups, separators); - return joined; + +function 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 + } + } + } + } } -module.exports = { sortMenuItems }; -//# sourceMappingURL=menu-utils.js.map \ No newline at end of file + +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 } diff --git a/electronasar/canary/browser/api/menu.js b/electronasar/canary/browser/api/menu.js index ad0b387..14fcaba 100644 --- a/electronasar/canary/browser/api/menu.js +++ b/electronasar/canary/browser/api/menu.js @@ -1,263 +1,258 @@ -'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); +'use strict' + +const { TopLevelWindow, MenuItem, webContents } = require('electron') +const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils') +const EventEmitter = require('events').EventEmitter +const v8Util = process.atomBinding('v8_util') +const bindings = process.atomBinding('menu') + +const { Menu } = bindings +let applicationMenu = null +let groupIdIndex = 0 + +Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) + // Menu Delegate. // This object should hold no reference to |Menu| to avoid cyclic reference. const delegate = { - isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined, - isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined, - 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); - } + isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined, + isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined, + isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, + getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { + const command = menu.commandsMap[id] + if (!command) return + if (command.accelerator != null) return command.accelerator + if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() + }, + shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined, + executeCommand: (menu, event, id) => { + const command = menu.commandsMap[id] + if (!command) return + command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents()) + }, + menuWillShow: (menu) => { + // Ensure radio groups have at least one menu item seleted + for (const id in menu.groupsMap) { + const found = menu.groupsMap[id].find(item => item.checked) || null + if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true) } -}; + } +} + /* Instance Methods */ + Menu.prototype._init = function () { - this.commandsMap = {}; - this.groupsMap = {}; - this.items = []; - this.delegate = delegate; -}; + 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'); + 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] } - 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`); - } + if (!window) { + throw new Error(`Cannot open Menu without a TopLevelWindow present`) } - this.popupAt(window, x, y, positioningItem, callback); - return { browserWindow: window, x, y, position: positioningItem }; -}; + } + + this.popupAt(window, x, y, positioningItem, callback) + return { browserWindow: window, x, y, position: positioningItem } +} + Menu.prototype.closePopup = function (window) { - if (window instanceof TopLevelWindow) { - this.closePopupAt(window.id); - } - else { - // Passing -1 (invalid) would make closePopupAt close the all menu runners - // belong to this menu. - this.closePopupAt(-1); - } -}; + 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); - } + 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; -}; + } + return found +} + Menu.prototype.append = function (item) { - return this.insert(this.getItemCount(), item); -}; + return this.insert(this.getItemCount(), item) +} + Menu.prototype.insert = function (pos, item) { - if ((item ? item.constructor : void 0) !== MenuItem) { - throw new TypeError('Invalid item'); - } - 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; -}; + if ((item ? item.constructor : void 0) !== MenuItem) { + throw new TypeError('Invalid item') + } + + // insert item depending on its type + insertItemByType.call(this, item, pos) + + // set item properties + if (item.sublabel) this.setSublabel(pos, item.sublabel) + if (item.icon) this.setIcon(pos, item.icon) + if (item.role) this.setRole(pos, item.role) + + // Make menu accessable to items. + item.overrideReadOnlyProperty('menu', this) + + // Remember the items. + this.items.splice(pos, 0, item) + this.commandsMap[item.commandId] = item +} + Menu.prototype._callMenuWillShow = function () { - if (this.delegate) - this.delegate.menuWillShow(this); - this.items.forEach(item => { - if (item.submenu) - item.submenu._callMenuWillShow(); - }); -}; + if (this.delegate) this.delegate.menuWillShow(this) + this.items.forEach(item => { + if (item.submenu) item.submenu._callMenuWillShow() + }) +} + /* Static Methods */ -Menu.getApplicationMenu = () => applicationMenu; -Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder; + +Menu.getApplicationMenu = () => applicationMenu + +Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder + // set application menu with a preexisting menu Menu.setApplicationMenu = function (menu) { - if (menu && menu.constructor !== Menu) { - throw new TypeError('Invalid menu'); - } - applicationMenu = menu; - 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)); - } -}; + if (menu && menu.constructor !== Menu) { + throw new TypeError('Invalid menu') + } + + applicationMenu = menu + if (process.platform === 'darwin') { + if (!menu) return + menu._callMenuWillShow() + bindings.setApplicationMenu(menu) + } else { + const windows = TopLevelWindow.getAllWindows() + return windows.map(w => w.setMenu(menu)) + } +} + Menu.buildFromTemplate = function (template) { - if (!Array.isArray(template)) { - throw new TypeError('Invalid template for Menu: Menu template must be an array'); - } - 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; -}; + if (!Array.isArray(template)) { + throw new TypeError('Invalid template for Menu: Menu template must be an array') + } + const menu = new Menu() + if (!areValidTemplateItems(template)) { + throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type') + } + const filtered = removeExtraSeparators(template) + const sorted = sortTemplate(filtered) + + sorted.forEach((item) => menu.append(new MenuItem(item))) + + return menu +} + /* Helper Functions */ + // validate the template against having the wrong attribute -function areValidTemplateItems(template) { - return template.every(item => item != null && - typeof item === 'object' && - (item.hasOwnProperty('label') || - item.hasOwnProperty('role') || - item.type === 'separator')); +function 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); - } + +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; + } + 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; - } +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; - } + } 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; + } + 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 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); + +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) } - }; - types[item.type](); + }) + 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 + +module.exports = Menu diff --git a/electronasar/canary/browser/api/module-list.js b/electronasar/canary/browser/api/module-list.js index d54bbf7..8ec7bda 100644 --- a/electronasar/canary/browser/api/module-list.js +++ b/electronasar/canary/browser/api/module-list.js @@ -1,36 +1,46 @@ -'use strict'; -const features = process.electronBinding('features'); +'use strict' + +const features = process.atomBinding('features') + // Browser side modules, please sort alphabetically. module.exports = [ - { name: 'app', file: 'app' }, - { name: 'autoUpdater', file: 'auto-updater' }, - { name: 'BrowserView', file: 'browser-view' }, - { name: 'BrowserWindow', file: 'browser-window' }, - { name: 'contentTracing', file: 'content-tracing' }, - { name: 'crashReporter', file: 'crash-reporter' }, - { name: 'dialog', file: 'dialog' }, - { name: 'globalShortcut', file: 'global-shortcut' }, - { name: 'ipcMain', file: 'ipc-main' }, - { name: 'inAppPurchase', file: 'in-app-purchase' }, - { name: 'Menu', file: 'menu' }, - { name: 'MenuItem', file: 'menu-item' }, - { name: 'net', file: 'net' }, - { name: 'netLog', file: 'net-log' }, - { name: 'Notification', file: 'notification' }, - { name: 'powerMonitor', file: 'power-monitor' }, - { name: 'powerSaveBlocker', file: 'power-save-blocker' }, - { name: 'protocol', file: 'protocol' }, - { name: 'screen', file: 'screen' }, - { name: 'session', file: 'session' }, - { name: 'systemPreferences', file: 'system-preferences' }, - { name: 'TopLevelWindow', file: 'top-level-window' }, - { name: 'TouchBar', file: 'touch-bar' }, - { name: 'Tray', file: 'tray' }, - { name: 'View', file: 'view' }, - { name: 'webContents', file: 'web-contents' }, - { name: 'WebContentsView', file: 'web-contents-view' } -]; + { name: 'app', file: 'app' }, + { name: 'autoUpdater', file: 'auto-updater' }, + { name: 'BrowserView', file: 'browser-view' }, + { name: 'BrowserWindow', file: 'browser-window' }, + { name: 'contentTracing', file: 'content-tracing' }, + { name: 'crashReporter', file: 'crash-reporter' }, + { name: 'dialog', file: 'dialog' }, + { name: 'globalShortcut', file: 'global-shortcut' }, + { name: 'ipcMain', file: 'ipc-main' }, + { name: 'inAppPurchase', file: 'in-app-purchase' }, + { name: 'Menu', file: 'menu' }, + { name: 'MenuItem', file: 'menu-item' }, + { name: 'net', file: 'net' }, + { name: 'netLog', file: 'net-log' }, + { name: 'Notification', file: 'notification' }, + { name: 'powerMonitor', file: 'power-monitor' }, + { name: 'powerSaveBlocker', file: 'power-save-blocker' }, + { name: 'protocol', file: 'protocol' }, + { name: 'screen', file: 'screen' }, + { name: 'session', file: 'session' }, + { name: 'systemPreferences', file: 'system-preferences' }, + { name: 'TopLevelWindow', file: 'top-level-window' }, + { name: 'TouchBar', file: 'touch-bar' }, + { name: 'Tray', file: 'tray' }, + { name: 'View', file: 'view' }, + { name: 'webContents', file: 'web-contents' }, + { name: 'WebContentsView', file: 'web-contents-view' }, + // The internal modules, invisible unless you know their names. + { name: 'NavigationController', file: 'navigation-controller', private: true } +] + if (features.isViewApiEnabled()) { - module.exports.push({ name: 'BoxLayout', file: 'views/box-layout' }, { name: 'Button', file: 'views/button' }, { name: 'LabelButton', file: 'views/label-button' }, { name: 'LayoutManager', file: 'views/layout-manager' }, { name: 'MdTextButton', file: 'views/md-text-button' }, { name: 'ResizeArea', file: 'views/resize-area' }, { name: 'TextField', file: 'views/text-field' }); + module.exports.push( + { name: 'BoxLayout', file: 'views/box-layout' }, + { name: 'Button', file: 'views/button' }, + { name: 'LabelButton', file: 'views/label-button' }, + { name: 'LayoutManager', file: 'views/layout-manager' }, + { name: 'TextField', file: 'views/text-field' } + ) } -//# sourceMappingURL=module-list.js.map \ No newline at end of file diff --git a/electronasar/canary/browser/api/navigation-controller.js b/electronasar/canary/browser/api/navigation-controller.js new file mode 100644 index 0000000..a997714 --- /dev/null +++ b/electronasar/canary/browser/api/navigation-controller.js @@ -0,0 +1,179 @@ +'use strict' + +const ipcMain = require('@electron/internal/browser/ipc-main-internal') + +// The history operation in renderer is redirected to browser. +ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) { + event.sender.goBack() +}) + +ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) { + event.sender.goForward() +}) + +ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) { + event.sender.goToOffset(offset) +}) + +ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) { + event.returnValue = event.sender.length() +}) + +// JavaScript implementation of Chromium's NavigationController. +// Instead of relying on Chromium for history control, we compeletely do history +// control on user land, and only rely on WebContents.loadURL for navigation. +// This helps us avoid Chromium's various optimizations so we can ensure renderer +// process is restarted everytime. +const NavigationController = (function () { + function NavigationController (webContents) { + this.webContents = webContents + this.clearHistory() + + // webContents may have already navigated to a page. + if (this.webContents._getURL()) { + this.currentIndex++ + this.history.push(this.webContents._getURL()) + } + this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => { + if (this.inPageIndex > -1 && !inPage) { + // Navigated to a new page, clear in-page mark. + this.inPageIndex = -1 + } else if (this.inPageIndex === -1 && inPage && !replaceEntry) { + // Started in-page navigations. + this.inPageIndex = this.currentIndex + } + if (this.pendingIndex >= 0) { + // Go to index. + this.currentIndex = this.pendingIndex + this.pendingIndex = -1 + this.history[this.currentIndex] = url + } else if (replaceEntry) { + // Non-user initialized navigation. + this.history[this.currentIndex] = url + } else { + // Normal navigation. Clear history. + this.history = this.history.slice(0, this.currentIndex + 1) + this.currentIndex++ + this.history.push(url) + } + }) + } + + NavigationController.prototype.loadURL = function (url, options) { + if (options == null) { + options = {} + } + this.pendingIndex = -1 + this.webContents._loadURL(url, options) + return this.webContents.emit('load-url', url, options) + } + + NavigationController.prototype.getURL = function () { + if (this.currentIndex === -1) { + return '' + } else { + return this.history[this.currentIndex] + } + } + + NavigationController.prototype.stop = function () { + this.pendingIndex = -1 + return this.webContents._stop() + } + + NavigationController.prototype.reload = function () { + this.pendingIndex = this.currentIndex + return this.webContents._loadURL(this.getURL(), {}) + } + + NavigationController.prototype.reloadIgnoringCache = function () { + this.pendingIndex = this.currentIndex + return this.webContents._loadURL(this.getURL(), { + extraHeaders: 'pragma: no-cache\n' + }) + } + + NavigationController.prototype.canGoBack = function () { + return this.getActiveIndex() > 0 + } + + NavigationController.prototype.canGoForward = function () { + return this.getActiveIndex() < this.history.length - 1 + } + + NavigationController.prototype.canGoToIndex = function (index) { + return index >= 0 && index < this.history.length + } + + NavigationController.prototype.canGoToOffset = function (offset) { + return this.canGoToIndex(this.currentIndex + offset) + } + + NavigationController.prototype.clearHistory = function () { + this.history = [] + this.currentIndex = -1 + this.pendingIndex = -1 + this.inPageIndex = -1 + } + + NavigationController.prototype.goBack = function () { + if (!this.canGoBack()) { + return + } + this.pendingIndex = this.getActiveIndex() - 1 + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goBack() + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + } + + NavigationController.prototype.goForward = function () { + if (!this.canGoForward()) { + return + } + this.pendingIndex = this.getActiveIndex() + 1 + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goForward() + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + } + + NavigationController.prototype.goToIndex = function (index) { + if (!this.canGoToIndex(index)) { + return + } + this.pendingIndex = index + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + + NavigationController.prototype.goToOffset = function (offset) { + if (!this.canGoToOffset(offset)) { + return + } + const pendingIndex = this.currentIndex + offset + if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { + this.pendingIndex = pendingIndex + return this.webContents._goToOffset(offset) + } else { + return this.goToIndex(pendingIndex) + } + } + + NavigationController.prototype.getActiveIndex = function () { + if (this.pendingIndex === -1) { + return this.currentIndex + } else { + return this.pendingIndex + } + } + + NavigationController.prototype.length = function () { + return this.history.length + } + + return NavigationController +})() + +module.exports = NavigationController diff --git a/electronasar/canary/browser/api/net-log.js b/electronasar/canary/browser/api/net-log.js index 5f9f73d..1649c63 100644 --- a/electronasar/canary/browser/api/net-log.js +++ b/electronasar/canary/browser/api/net-log.js @@ -1,28 +1,28 @@ -'use strict'; +'use strict' + // TODO(deepak1556): Deprecate and remove standalone netLog module, -// it is now a property of session module. -const { app, session } = require('electron'); +// it is now a property of sessio module. +const { app, session } = require('electron') + // Fallback to default session. Object.setPrototypeOf(module.exports, new Proxy({}, { - get(target, property) { - if (!app.isReady()) - return; - const netLog = session.defaultSession.netLog; - if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) - return; - // 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 + get (target, property) { + if (!app.isReady()) return + + const netLog = session.defaultSession.netLog + if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return + + // Returning a native function directly would throw error. + return (...args) => netLog[property](...args) + }, + + ownKeys () { + if (!app.isReady()) return [] + + return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog)) + }, + + getOwnPropertyDescriptor (target) { + return { configurable: true, enumerable: true } + } +})) diff --git a/electronasar/canary/browser/api/net.js b/electronasar/canary/browser/api/net.js index 6f2dbb1..0d8cb71 100644 --- a/electronasar/canary/browser/api/net.js +++ b/electronasar/canary/browser/api/net.js @@ -1,358 +1,372 @@ -'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; +'use strict' + +const url = require('url') +const { EventEmitter } = require('events') +const { Readable } = require('stream') +const { app } = require('electron') +const { Session } = process.atomBinding('session') +const { net, Net } = process.atomBinding('net') +const { URLRequest } = net + // Net is an EventEmitter. -Object.setPrototypeOf(Net.prototype, EventEmitter.prototype); -EventEmitter.call(net); -Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype); -const kSupportedProtocols = new Set(['http:', 'https:']); -// 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' -]); +Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) +EventEmitter.call(net) + +Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) + +const kSupportedProtocols = new Set(['http:', 'https:']) + class IncomingMessage extends Readable { - constructor(urlRequest) { - super(); - this.urlRequest = urlRequest; - this.shouldPush = false; - this.data = []; - this.urlRequest.on('data', (event, chunk) => { - this._storeInternalData(chunk); - this._pushInternalData(); - }); - this.urlRequest.on('end', () => { - this._storeInternalData(null); - this._pushInternalData(); - }); - } - get statusCode() { - return this.urlRequest.statusCode; - } - get statusMessage() { - return this.urlRequest.statusMessage; - } - get headers() { - 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(); + constructor (urlRequest) { + super() + this.urlRequest = urlRequest + this.shouldPush = false + this.data = [] + this.urlRequest.on('data', (event, chunk) => { + this._storeInternalData(chunk) + this._pushInternalData() + }) + this.urlRequest.on('end', () => { + this._storeInternalData(null) + this._pushInternalData() + }) + } + + get statusCode () { + return this.urlRequest.statusCode + } + + get statusMessage () { + return this.urlRequest.statusMessage + } + + get headers () { + return this.urlRequest.rawResponseHeaders + } + + get httpVersion () { + return `${this.httpVersionMajor}.${this.httpVersionMinor}` + } + + get httpVersionMajor () { + return this.urlRequest.httpVersionMajor + } + + get httpVersionMinor () { + return this.urlRequest.httpVersionMinor + } + + get rawTrailers () { + throw new Error('HTTP trailers are not supported.') + } + + get trailers () { + throw new Error('HTTP trailers are not supported.') + } + + _storeInternalData (chunk) { + this.data.push(chunk) + } + + _pushInternalData () { + while (this.shouldPush && this.data.length > 0) { + const chunk = this.data.shift() + this.shouldPush = this.push(chunk) } + } + + _read () { + this.shouldPush = true + this._pushInternalData() + } } + URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) { - if (isAsync) { - process.nextTick(() => { - this.clientRequest.emit(...rest); - }); - } - else { - this.clientRequest.emit(...rest); - } -}; + if (isAsync) { + process.nextTick(() => { + this.clientRequest.emit(...rest) + }) + } else { + this.clientRequest.emit(...rest) + } +} + URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) { - if (isAsync) { - process.nextTick(() => { - this._response.emit(...rest); - }); - } - else { - this._response.emit(...rest); - } -}; + if (isAsync) { + process.nextTick(() => { + this._response.emit(...rest) + }) + } else { + this._response.emit(...rest) + } +} + class ClientRequest extends EventEmitter { - constructor(options, callback) { - super(); - if (!app.isReady()) { - throw new Error('net module can only be used after app is ready'); - } - if (typeof options === 'string') { - options = url.parse(options); - } - else { - options = Object.assign({}, options); - } - const method = (options.method || 'GET').toUpperCase(); - let urlStr = options.url; - if (!urlStr) { - const urlObj = {}; - const protocol = options.protocol || 'http:'; - if (!kSupportedProtocols.has(protocol)) { - throw new Error('Protocol "' + protocol + '" not supported. '); - } - urlObj.protocol = protocol; - if (options.host) { - urlObj.host = options.host; - } - else { - if (options.hostname) { - urlObj.hostname = options.hostname; - } - else { - urlObj.hostname = 'localhost'; - } - 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); - } + constructor (options, callback) { + super() + + if (!app.isReady()) { + throw new Error('net module can only be used after app is ready') } - get chunkedEncoding() { - return this.chunkedEncodingEnabled; + + if (typeof options === 'string') { + options = url.parse(options) + } else { + options = Object.assign({}, options) } - set chunkedEncoding(value) { - if (!this.urlRequest.notStarted) { - throw new Error('Can\'t set the transfer encoding, headers have been sent.'); + + 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' } - this.chunkedEncodingEnabled = value; + + 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) } - 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()); + + const redirectPolicy = options.redirect || 'follow' + if (!['follow', 'error', 'manual'].includes(redirectPolicy)) { + throw new Error('redirect mode should be one of follow, error or manual') } - getHeader(name) { - if (name == null) { - throw new Error('`name` is required for getHeader(name).'); - } - if (!this.extraHeaders) { - return; - } - const key = name.toLowerCase(); - return this.extraHeaders[key]; + + const urlRequestOptions = { + method: method, + url: urlStr, + redirect: redirectPolicy } - removeHeader(name) { - if (name == null) { - throw new Error('`name` is required for removeHeader(name).'); - } - if (!this.urlRequest.notStarted) { - throw new Error('Can\'t remove headers after they are sent.'); - } - const key = name.toLowerCase(); - delete this.extraHeaders[key]; - this.urlRequest.removeExtraHeader(name); + if (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.') + } } - _write(chunk, encoding, callback, isLast) { - const chunkIsString = typeof chunk === 'string'; - const chunkIsBuffer = chunk instanceof Buffer; - if (!chunkIsString && !chunkIsBuffer) { - throw new TypeError('First argument must be a string or Buffer.'); - } - if (chunkIsString) { - // We convert all strings into binary buffers. - chunk = Buffer.from(chunk, encoding); - } - // Since writing to the network is asynchronous, we conservatively - // assume that request headers are written after delivering the first - // buffer to the network IO thread. - if (this.urlRequest.notStarted) { - this.urlRequest.setChunkedUpload(this.chunkedEncoding); - } - // Headers are assumed to be sent on first call to _writeBuffer, - // i.e. after the first call to write or end. - const result = this.urlRequest.write(chunk, isLast); - // The write callback is fired asynchronously to mimic Node.js. - if (callback) { - process.nextTick(callback); - } - return result; + + const 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]) + } } - write(data, encoding, callback) { - if (this.urlRequest.finished) { - const error = new Error('Write after end.'); - process.nextTick(writeAfterEndNT, this, error, callback); - return true; + + // 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 = '' } - return this._write(data, encoding, callback, false); - } - end(data, encoding, callback) { - if (this.urlRequest.finished) { - return false; + if (typeof username !== 'string') { + throw new Error('username must be a string') } - if (typeof data === 'function') { - callback = data; - encoding = null; - data = null; + if (password === null || password === undefined) { + password = '' } - else if (typeof encoding === 'function') { - callback = encoding; - encoding = null; + if (typeof password !== 'string') { + throw new Error('password must be a string') } - data = data || ''; - return this._write(data, encoding, callback, true); + callback(username, password) + }) + }) + + if (callback) { + this.once('response', callback) } - followRedirect() { - this.urlRequest.followRedirect(); + } + + 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.') } - abort() { - this.urlRequest.cancel(); + this.chunkedEncodingEnabled = value + } + + setHeader (name, value) { + if (typeof name !== 'string') { + throw new TypeError('`name` should be a string in setHeader(name, value).') } - getUploadProgress() { - return this.urlRequest.getUploadProgress(); + 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); + +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 + return new ClientRequest(options, callback) +} + +net.ClientRequest = ClientRequest + +module.exports = net diff --git a/electronasar/canary/browser/api/notification.js b/electronasar/canary/browser/api/notification.js index a6bb64b..cc2374c 100644 --- a/electronasar/canary/browser/api/notification.js +++ b/electronasar/canary/browser/api/notification.js @@ -1,7 +1,10 @@ -'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 +'use strict' + +const { EventEmitter } = require('events') +const { Notification, isSupported } = process.atomBinding('notification') + +Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype) + +Notification.isSupported = isSupported + +module.exports = Notification diff --git a/electronasar/canary/browser/api/power-monitor.js b/electronasar/canary/browser/api/power-monitor.js index 428d427..5e3371d 100644 --- a/electronasar/canary/browser/api/power-monitor.js +++ b/electronasar/canary/browser/api/power-monitor.js @@ -1,43 +1,25 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { powerMonitor, PowerMonitor } = process.electronBinding('power_monitor'); -const { deprecate } = require('electron'); +'use strict' + +const { EventEmitter } = require('events') +const { powerMonitor, PowerMonitor } = process.atomBinding('power_monitor') + // PowerMonitor is an EventEmitter. -Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype); -EventEmitter.call(powerMonitor); +Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) +EventEmitter.call(powerMonitor) + // On Linux we need to call blockShutdown() to subscribe to shutdown event. if (process.platform === 'linux') { - powerMonitor.on('newListener', (event) => { - if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { - powerMonitor.blockShutdown(); - } - }); - powerMonitor.on('removeListener', (event) => { - if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { - powerMonitor.unblockShutdown(); - } - }); + powerMonitor.on('newListener', (event) => { + if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { + powerMonitor.blockShutdown() + } + }) + + powerMonitor.on('removeListener', (event) => { + if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { + powerMonitor.unblockShutdown() + } + }) } -// 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 + +module.exports = powerMonitor diff --git a/electronasar/canary/browser/api/power-save-blocker.js b/electronasar/canary/browser/api/power-save-blocker.js index 648ee3b..964ead8 100644 --- a/electronasar/canary/browser/api/power-save-blocker.js +++ b/electronasar/canary/browser/api/power-save-blocker.js @@ -1,3 +1,3 @@ -'use strict'; -module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker; -//# sourceMappingURL=power-save-blocker.js.map \ No newline at end of file +'use strict' + +module.exports = process.atomBinding('power_save_blocker').powerSaveBlocker diff --git a/electronasar/canary/browser/api/protocol.js b/electronasar/canary/browser/api/protocol.js index d7d8c58..0f1b0e6 100644 --- a/electronasar/canary/browser/api/protocol.js +++ b/electronasar/canary/browser/api/protocol.js @@ -1,27 +1,29 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const electron_1 = require("electron"); +'use strict' + +const { app, session } = require('electron') + // Global protocol APIs. -const protocol = process.electronBinding('protocol'); +module.exports = process.atomBinding('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 +Object.setPrototypeOf(module.exports, new Proxy({}, { + get (target, property) { + if (!app.isReady()) return + + const protocol = session.defaultSession.protocol + if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return + + // Returning a native function directly would throw error. + return (...args) => protocol[property](...args) + }, + + ownKeys () { + if (!app.isReady()) return [] + + return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.protocol)) + }, + + getOwnPropertyDescriptor (target) { + return { configurable: true, enumerable: true } + } +})) diff --git a/electronasar/canary/browser/api/screen.js b/electronasar/canary/browser/api/screen.js index 7ea700f..a91df7d 100644 --- a/electronasar/canary/browser/api/screen.js +++ b/electronasar/canary/browser/api/screen.js @@ -1,8 +1,10 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { screen, Screen } = process.electronBinding('screen'); +'use strict' + +const { EventEmitter } = require('events') +const { screen, Screen } = process.atomBinding('screen') + // Screen is an EventEmitter. -Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype); -EventEmitter.call(screen); -module.exports = screen; -//# sourceMappingURL=screen.js.map \ No newline at end of file +Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype) +EventEmitter.call(screen) + +module.exports = screen diff --git a/electronasar/canary/browser/api/session.js b/electronasar/canary/browser/api/session.js index 62eb8f1..08c10ee 100644 --- a/electronasar/canary/browser/api/session.js +++ b/electronasar/canary/browser/api/session.js @@ -1,41 +1,24 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { app, deprecate } = require('electron'); -const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session'); +'use strict' + +const { EventEmitter } = require('events') +const { app } = require('electron') +const { fromPartition, Session, Cookies } = process.atomBinding('session') + // Public API. Object.defineProperties(exports, { - defaultSession: { - enumerable: true, - get() { return fromPartition(''); } - }, - fromPartition: { - enumerable: true, - value: fromPartition - } -}); -Object.setPrototypeOf(Session.prototype, EventEmitter.prototype); -Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype); + 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 + app.emit('session-created', this) +} diff --git a/electronasar/canary/browser/api/system-preferences.js b/electronasar/canary/browser/api/system-preferences.js index 40068f6..bb663e8 100644 --- a/electronasar/canary/browser/api/system-preferences.js +++ b/electronasar/canary/browser/api/system-preferences.js @@ -1,8 +1,10 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences'); +'use strict' + +const { EventEmitter } = require('events') +const { systemPreferences, SystemPreferences } = process.atomBinding('system_preferences') + // SystemPreferences is an EventEmitter. -Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype); -EventEmitter.call(systemPreferences); -module.exports = systemPreferences; -//# sourceMappingURL=system-preferences.js.map \ No newline at end of file +Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype) +EventEmitter.call(systemPreferences) + +module.exports = systemPreferences diff --git a/electronasar/canary/browser/api/top-level-window.js b/electronasar/canary/browser/api/top-level-window.js index 9b73c1c..c7770ad 100644 --- a/electronasar/canary/browser/api/top-level-window.js +++ b/electronasar/canary/browser/api/top-level-window.js @@ -1,20 +1,24 @@ -'use strict'; -const electron = require('electron'); -const { EventEmitter } = require('events'); -const { TopLevelWindow } = process.electronBinding('top_level_window'); -Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype); +'use strict' + +const electron = require('electron') +const { EventEmitter } = require('events') +const { TopLevelWindow } = process.atomBinding('top_level_window') + +Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype) + TopLevelWindow.prototype._init = function () { - // Avoid recursive require. - const { app } = electron; - // Simulate the application menu on platforms other than macOS. - if (process.platform !== 'darwin') { - const menu = app.applicationMenu; - if (menu) - this.setMenu(menu); - } -}; + // Avoid recursive require. + const { app } = electron + + // Simulate the application menu on platforms other than macOS. + if (process.platform !== 'darwin') { + const menu = app.getApplicationMenu() + 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 + return TopLevelWindow.getAllWindows().find((win) => win.isFocused()) +} + +module.exports = TopLevelWindow diff --git a/electronasar/canary/browser/api/touch-bar.js b/electronasar/canary/browser/api/touch-bar.js index 1fd3f8c..ddacc73 100644 --- a/electronasar/canary/browser/api/touch-bar.js +++ b/electronasar/canary/browser/api/touch-bar.js @@ -1,311 +1,343 @@ -'use strict'; -const { EventEmitter } = require('events'); -let nextItemID = 1; +'use strict' + +const { EventEmitter } = require('events') + +let nextItemID = 1 + class TouchBar extends EventEmitter { - // Bind a touch bar to a window - static _setOnWindow(touchBar, window) { - if (window._touchBar != null) { - window._touchBar._removeFromWindow(window); - } - if (touchBar == null) { - window._setTouchBarItems([]); - return; - } - if (Array.isArray(touchBar)) { - touchBar = new TouchBar(touchBar); - } - touchBar._addToWindow(window); + // Bind a touch bar to a window + static _setOnWindow (touchBar, window) { + if (window._touchBar != null) { + window._touchBar._removeFromWindow(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); - }); + + if (touchBar == null) { + window._setTouchBarItems([]) + return } - set escapeItem(item) { - if (item != null && !(item instanceof TouchBarItem)) { - throw new Error('Escape item must be an instance of TouchBarItem'); + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + touchBar._addToWindow(window) + } + + constructor (options) { + super() + + if (options == null) { + throw new Error('Must specify options object as first argument') + } + + let { items, escapeItem } = options + + // FIXME Support array as first argument, remove in 2.0 + if (Array.isArray(options)) { + items = options + escapeItem = null + } + + if (!Array.isArray(items)) { + items = [] + } + + this.changeListener = (item) => { + this.emit('change', item.id, item.type) + } + + this.windowListeners = {} + this.items = {} + this.ordereredItems = [] + this.escapeItem = escapeItem + + const registerItem = (item) => { + this.items[item.id] = item + item.on('change', this.changeListener) + if (item.child instanceof TouchBar) { + item.child.ordereredItems.forEach(registerItem) + } + } + items.forEach((item) => { + if (!(item instanceof TouchBarItem)) { + throw new Error('Each item must be an instance of TouchBarItem') + } + this.ordereredItems.push(item) + registerItem(item) + }) + } + + set escapeItem (item) { + if (item != null && !(item instanceof TouchBarItem)) { + throw new Error('Escape item must be an instance of TouchBarItem') + } + if (this.escapeItem != null) { + this.escapeItem.removeListener('change', this.changeListener) + } + this._escapeItem = item + if (this.escapeItem != null) { + this.escapeItem.on('change', this.changeListener) + } + this.emit('escape-item-change', item) + } + + get escapeItem () { + return this._escapeItem + } + + _addToWindow (window) { + const { id } = window + + // Already added to window + if (this.windowListeners.hasOwnProperty(id)) return + + window._touchBar = this + + const changeListener = (itemID) => { + window._refreshTouchBarItem(itemID) + } + this.on('change', changeListener) + + const escapeItemListener = (item) => { + window._setEscapeTouchBarItem(item != null ? item : {}) + } + this.on('escape-item-change', escapeItemListener) + + const interactionListener = (event, itemID, details) => { + let item = this.items[itemID] + if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { + item = this.escapeItem + } + if (item != null && item.onInteraction != null) { + item.onInteraction(details) + } + } + window.on('-touch-bar-interaction', interactionListener) + + const removeListeners = () => { + this.removeListener('change', changeListener) + this.removeListener('escape-item-change', escapeItemListener) + window.removeListener('-touch-bar-interaction', interactionListener) + window.removeListener('closed', removeListeners) + window._touchBar = null + delete this.windowListeners[id] + const unregisterItems = (items) => { + for (const item of items) { + item.removeListener('change', this.changeListener) + if (item.child instanceof TouchBar) { + unregisterItems(item.child.ordereredItems) + } } - 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(); + } + 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 - }); - } + 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(); - }); - } + 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); - }); - } + 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)); - } -}; + 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); - } -}; + 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)); - } -}; + 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); - }); - } + 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); - } -}; + 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); - }); - } + 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); - } - }); + 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 + } +} + +module.exports = TouchBar diff --git a/electronasar/canary/browser/api/tray.js b/electronasar/canary/browser/api/tray.js index 2cdf06e..1c6c108 100644 --- a/electronasar/canary/browser/api/tray.js +++ b/electronasar/canary/browser/api/tray.js @@ -1,6 +1,8 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { Tray } = process.electronBinding('tray'); -Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype); -module.exports = Tray; -//# sourceMappingURL=tray.js.map \ No newline at end of file +'use strict' + +const { EventEmitter } = require('events') +const { Tray } = process.atomBinding('tray') + +Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype) + +module.exports = Tray diff --git a/electronasar/canary/browser/api/view.js b/electronasar/canary/browser/api/view.js index 41032f1..cf39f8b 100644 --- a/electronasar/canary/browser/api/view.js +++ b/electronasar/canary/browser/api/view.js @@ -1,8 +1,11 @@ -'use strict'; -const { EventEmitter } = require('events'); -const { View } = process.electronBinding('view'); -Object.setPrototypeOf(View.prototype, EventEmitter.prototype); +'use strict' + +const { EventEmitter } = require('events') +const { View } = process.atomBinding('view') + +Object.setPrototypeOf(View.prototype, EventEmitter.prototype) + View.prototype._init = function () { -}; -module.exports = View; -//# sourceMappingURL=view.js.map \ No newline at end of file +} + +module.exports = View diff --git a/electronasar/canary/browser/api/web-contents-view.js b/electronasar/canary/browser/api/web-contents-view.js index 726d6fc..89f36e8 100644 --- a/electronasar/canary/browser/api/web-contents-view.js +++ b/electronasar/canary/browser/api/web-contents-view.js @@ -1,11 +1,15 @@ -'use strict'; -const electron = require('electron'); -const { View } = electron; -const { WebContentsView } = process.electronBinding('web_contents_view'); -Object.setPrototypeOf(WebContentsView.prototype, View.prototype); +'use strict' + +const electron = require('electron') + +const { View } = electron +const { WebContentsView } = process.atomBinding('web_contents_view') + +Object.setPrototypeOf(WebContentsView.prototype, View.prototype) + WebContentsView.prototype._init = function () { - // Call parent class's _init. - View.prototype._init.call(this); -}; -module.exports = WebContentsView; -//# sourceMappingURL=web-contents-view.js.map \ No newline at end of file + // Call parent class's _init. + View.prototype._init.call(this) +} + +module.exports = WebContentsView diff --git a/electronasar/canary/browser/api/web-contents.js b/electronasar/canary/browser/api/web-contents.js index 898fa56..b3a244e 100644 --- a/electronasar/canary/browser/api/web-contents.js +++ b/electronasar/canary/browser/api/web-contents.js @@ -1,447 +1,507 @@ -'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'); +'use strict' + +const features = process.atomBinding('features') +const { EventEmitter } = require('events') +const electron = require('electron') +const path = require('path') +const url = require('url') +const v8Util = process.atomBinding('v8_util') +const { app, ipcMain, session, NavigationController, deprecate } = electron + +const ipcMainInternal = require('@electron/internal/browser/ipc-main-internal') +const errorUtils = require('@electron/internal/common/error-utils') + // session is not used here, the purpose is to make sure session is initalized // before the webContents module. // eslint-disable-next-line -session; -let nextId = 0; +session + +let nextId = 0 const getNextId = function () { - return ++nextId; -}; + return ++nextId +} + // Stock page sizes const PDFPageSizes = { - A5: { - custom_display_name: 'A5', - height_microns: 210000, - name: 'ISO_A5', - width_microns: 148000 - }, - A4: { - custom_display_name: 'A4', - height_microns: 297000, - name: 'ISO_A4', - is_default: 'true', - width_microns: 210000 - }, - A3: { - custom_display_name: 'A3', - height_microns: 420000, - name: 'ISO_A3', - width_microns: 297000 - }, - Legal: { - custom_display_name: 'Legal', - height_microns: 355600, - name: 'NA_LEGAL', - width_microns: 215900 - }, - Letter: { - custom_display_name: 'Letter', - height_microns: 279400, - name: 'NA_LETTER', - width_microns: 215900 - }, - Tabloid: { - height_microns: 431800, - name: 'NA_LEDGER', - width_microns: 279400, - custom_display_name: 'Tabloid' - } -}; + A5: { + custom_display_name: 'A5', + height_microns: 210000, + name: 'ISO_A5', + width_microns: 148000 + }, + A4: { + custom_display_name: 'A4', + height_microns: 297000, + name: 'ISO_A4', + is_default: 'true', + width_microns: 210000 + }, + A3: { + custom_display_name: 'A3', + height_microns: 420000, + name: 'ISO_A3', + width_microns: 297000 + }, + Legal: { + custom_display_name: 'Legal', + height_microns: 355600, + name: 'NA_LEGAL', + width_microns: 215900 + }, + Letter: { + custom_display_name: 'Letter', + height_microns: 279400, + name: 'NA_LETTER', + width_microns: 215900 + }, + Tabloid: { + height_microns: 431800, + name: 'NA_LEDGER', + width_microns: 279400, + custom_display_name: 'Tabloid' + } +} + // Default printing setting const defaultPrintingSetting = { - pageRage: [], - mediaSize: {}, - landscape: false, - color: 2, - headerFooterEnabled: false, - marginsType: 0, - isFirstRequest: false, - requestID: getNextId(), - previewUIID: 0, - previewModifiable: true, - printToPDF: true, - printWithCloudPrint: false, - printWithPrivet: false, - printWithExtension: false, - 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 -}; + pageRage: [], + mediaSize: {}, + landscape: false, + color: 2, + headerFooterEnabled: false, + marginsType: 0, + isFirstRequest: false, + requestID: getNextId(), + previewUIID: 0, + previewModifiable: true, + printToPDF: true, + printWithCloudPrint: false, + printWithPrivet: false, + printWithExtension: false, + deviceName: 'Save as PDF', + generateDraftData: true, + fitToPageEnabled: false, + scaleFactor: 1, + dpiHorizontal: 72, + dpiVertical: 72, + rasterizePDF: false, + duplex: 0, + copies: 1, + collate: true, + shouldPrintBackgrounds: false, + shouldPrintSelectionOnly: false +} + // JavaScript implementations of WebContents. -const binding = process.electronBinding('web_contents'); -const { WebContents } = binding; -Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype); -Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype); +const binding = process.atomBinding('web_contents') +const { WebContents } = binding + +Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype) +Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype) + // WebContents::send(channel, args..) // WebContents::sendToAll(channel, args..) WebContents.prototype.send = function (channel, ...args) { - if (typeof channel !== 'string') { - throw new Error('Missing required channel argument'); - } - const internal = false; - const sendToAll = false; - return this._send(internal, sendToAll, channel, args); -}; + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument') + } + + const internal = false + const sendToAll = false + + return this._send(internal, sendToAll, channel, args) +} WebContents.prototype.sendToAll = function (channel, ...args) { - if (typeof channel !== 'string') { - throw new Error('Missing required channel argument'); - } - const internal = false; - const sendToAll = true; - return this._send(internal, sendToAll, channel, args); -}; + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument') + } + + const internal = false + const sendToAll = true + + return this._send(internal, sendToAll, channel, args) +} + WebContents.prototype._sendInternal = function (channel, ...args) { - if (typeof channel !== 'string') { - throw new Error('Missing required channel argument'); - } - const internal = true; - const sendToAll = false; - return this._send(internal, sendToAll, channel, args); -}; + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument') + } + + const internal = true + const sendToAll = false + + return this._send(internal, sendToAll, channel, args) +} WebContents.prototype._sendInternalToAll = function (channel, ...args) { - if (typeof channel !== 'string') { - throw new Error('Missing required channel argument'); - } - const internal = true; - const sendToAll = true; - return this._send(internal, sendToAll, channel, args); -}; -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); -}; + if (typeof channel !== 'string') { + throw new Error('Missing required channel argument') + } + + const internal = true + const sendToAll = true + + return this._send(internal, sendToAll, channel, args) +} + // Following methods are mapped to webFrame. const webFrameMethods = [ - 'insertCSS', - 'insertText', - 'setLayoutZoomLevelLimits', - 'setVisualZoomLevelLimits' -]; -for (const method of webFrameMethods) { - WebContents.prototype[method] = function (...args) { - ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args); - }; + 'insertCSS', + 'insertText', + 'setLayoutZoomLevelLimits', + 'setVisualZoomLevelLimits' +] + +const asyncWebFrameMethods = function (requestId, method, callback, ...args) { + return new Promise((resolve, reject) => { + ipcMainInternal.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { + if (error == null) { + if (typeof callback === 'function') callback(result) + resolve(result) + } else { + reject(errorUtils.deserialize(error)) + } + }) + this._sendInternal('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) + }) } -const executeJavaScript = (contents, code, hasUserGesture) => { - return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture); -}; + +for (const method of webFrameMethods) { + WebContents.prototype[method] = function (...args) { + this._sendInternal('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args) + } +} + // Make sure WebContents::executeJavaScript would run the code only when the // WebContents has been loaded. -WebContents.prototype.executeJavaScript = function (code, hasUserGesture) { - 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) { +WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) { + const requestId = getNextId() + + if (typeof hasUserGesture === 'function') { + // Shift. + callback = hasUserGesture + hasUserGesture = null + } + + if (hasUserGesture == null) { + hasUserGesture = false + } + + if (this.getURL() && !this.isLoadingMainFrame()) { + return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) + } else { return new Promise((resolve, reject) => { - this._takeHeapSnapshot(filePath, (success) => { - if (success) { - resolve(); - } - else { - reject(new Error('takeHeapSnapshot failed')); - } - }); - }); -}; + this.once('did-stop-loading', () => { + asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject) + }) + }) + } +} + +WebContents.prototype.takeHeapSnapshot = function (filePath) { + return new Promise((resolve, reject) => { + const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}` + ipcMainInternal.once(channel, (event, success) => { + if (success) { + resolve() + } else { + reject(new Error('takeHeapSnapshot failed')) + } + }) + if (!this._takeHeapSnapshot(filePath, channel)) { + ipcMainInternal.emit(channel, false) + } + }) +} + // Translate the options of printToPDF. -WebContents.prototype.printToPDF = function (options) { - const printingSetting = Object.assign({}, defaultPrintingSetting); - if (options.landscape) { - printingSetting.landscape = options.landscape; +WebContents.prototype.printToPDF = function (options, callback) { + const printingSetting = Object.assign({}, defaultPrintingSetting) + if (options.landscape) { + printingSetting.landscape = options.landscape + } + if (options.marginsType) { + printingSetting.marginsType = options.marginsType + } + if (options.printSelectionOnly) { + printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly + } + if (options.printBackground) { + printingSetting.shouldPrintBackgrounds = options.printBackground + } + + if (options.pageSize) { + const pageSize = options.pageSize + if (typeof pageSize === 'object') { + if (!pageSize.height || !pageSize.width) { + return callback(new Error('Must define height and width for pageSize')) + } + // Dimensions in Microns + // 1 meter = 10^6 microns + printingSetting.mediaSize = { + name: 'CUSTOM', + custom_display_name: 'Custom', + height_microns: Math.ceil(pageSize.height), + width_microns: Math.ceil(pageSize.width) + } + } else if (PDFPageSizes[pageSize]) { + printingSetting.mediaSize = PDFPageSizes[pageSize] + } else { + return callback(new Error(`Does not support pageSize with ${pageSize}`)) } - 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')); - } -}; + } else { + printingSetting.mediaSize = PDFPageSizes['A4'] + } + + // Chromium expects this in a 0-100 range number, not as float + printingSetting.scaleFactor *= 100 + if (features.isPrintingEnabled()) { + this._printToPDF(printingSetting, callback) + } else { + console.error('Error: Printing feature is disabled.') + } +} + WebContents.prototype.print = function (...args) { - if (features.isPrintingEnabled()) { - this._print(...args); - } - else { - console.error('Error: Printing feature is disabled.'); - } -}; + 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.'); - } -}; + if (features.isPrintingEnabled()) { + return this._getPrinters() + } else { + console.error('Error: Printing feature is disabled.') + } +} + +WebContents.prototype.getZoomLevel = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomLevel = this._getZoomLevel() + callback(zoomLevel) + }) +} + WebContents.prototype.loadFile = function (filePath, options = {}) { - if (typeof filePath !== 'string') { - throw new Error('Must pass filePath as a string'); - } - const { query, search, hash } = options; - return this.loadURL(url.format({ - protocol: 'file', - slashes: true, - pathname: path.resolve(app.getAppPath(), filePath), - query, - search, - hash - })); -}; -WebContents.prototype.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: () => { } - }); -}; + if (typeof filePath !== 'string') { + throw new Error('Must pass filePath as a string') + } + const { query, search, hash } = options + + return this.loadURL(url.format({ + protocol: 'file', + slashes: true, + pathname: path.resolve(app.getAppPath(), filePath), + query, + search, + hash + })) +} + +WebContents.prototype.getZoomFactor = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomFactor = this._getZoomFactor() + callback(zoomFactor) + }) +} + +WebContents.prototype.findInPage = function (text, options = {}) { + // TODO (nitsakh): Remove in 5.0 + if (options.wordStart != null || options.medialCapitalAtWordStart != null) { + deprecate.log('wordStart and medialCapitalAtWordStart options are deprecated') + } + return this._findInPage(text, options) +} + +const safeProtocols = new Set([ + 'chrome-devtools:', + 'chrome-extension:' +]) + +const isWebContentsTrusted = function (contents) { + const pageURL = contents._getURL() + const { protocol } = url.parse(pageURL) + return safeProtocols.has(protocol) +} + // Add JavaScript wrappers for WebContents class. WebContents.prototype._init = function () { - // The navigation controller. - NavigationController.call(this, this); - // Every remote callback from renderer process would add a 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); + // The navigation controller. + NavigationController.call(this, this) + + // Every remote callback from renderer process would add a listenter to the + // render-view-deleted event, so ignore the listenters warning. + this.setMaxListeners(0) + + // Dispatch IPC messages to the ipc module. + this.on('ipc-message', function (event, [channel, ...args]) { + ipcMain.emit(channel, event, ...args) + }) + this.on('ipc-message-sync', function (event, [channel, ...args]) { + Object.defineProperty(event, 'returnValue', { + set: function (value) { + return event.sendReply([value]) + }, + get: function () {} + }) + ipcMain.emit(channel, event, ...args) + }) + + this.on('ipc-internal-message', function (event, [channel, ...args]) { + ipcMainInternal.emit(channel, event, ...args) + }) + this.on('ipc-internal-message-sync', function (event, [channel, ...args]) { + Object.defineProperty(event, 'returnValue', { + set: function (value) { + return event.sendReply([value]) + }, + get: function () {} + }) + ipcMainInternal.emit(channel, event, ...args) + }) + + // Handle context menu action request from pepper plugin. + this.on('pepper-context-menu', function (event, params, callback) { + // Access Menu via electron.Menu to prevent circular require. + const menu = electron.Menu.buildFromTemplate(params.menu) + menu.popup({ + window: event.sender.getOwnerBrowserWindow(), + x: params.x, + y: params.y, + callback + }) + }) + + const forwardedEvents = [ + 'remote-require', + 'remote-get-global', + 'remote-get-builtin', + 'remote-get-current-window', + 'remote-get-current-web-contents', + 'remote-get-guest-web-contents' + ] + + for (const eventName of forwardedEvents) { + this.on(eventName, (event, ...args) => { + if (!isWebContentsTrusted(event.sender)) { + app.emit(eventName, event, this, ...args) + } + }) + } + + deprecate.event(this, 'did-get-response-details', '-did-get-response-details') + deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request') + + // The devtools requests the webContents to reload. + this.on('devtools-reload-page', function () { + this.reload() + }) + + // Handle window.open for BrowserWindow and BrowserView. + if (['browserView', 'window'].includes(this.getType())) { + // Make new windows requested by links behave like "window.open" + this.webContents.on('-new-window', (event, url, frameName, disposition, + additionalFeatures, postData, + referrer) => { + const options = { + show: true, + width: 800, + height: 600 + } + ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', + event, url, referrer, frameName, disposition, + options, additionalFeatures, postData) + }) + + this.webContents.on('-web-contents-created', (event, webContents, url, + frameName) => { + v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName }) + }) + + // Create a new browser window for the native implementation of + // "window.open", used in sandbox and nativeWindowOpen mode + this.webContents.on('-add-new-contents', (event, webContents, disposition, + userGesture, left, top, width, + height) => { + const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename') + if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && + disposition !== 'background-tab') || !urlFrameName) { + event.preventDefault() + return + } + + if (webContents.getLastWebPreferences().nodeIntegration === true) { + const message = + 'Enabling Node.js integration in child windows opened with the ' + + '"nativeWindowOpen" option will cause memory leaks, please turn off ' + + 'the "nodeIntegration" option.\\n' + + 'From 5.x child windows opened with the "nativeWindowOpen" option ' + + 'will always have Node.js integration disabled.\\n' + + 'See https://github.com/electron/electron/pull/15076 for more.' + // console is only available after DOM is created. + const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`) + if (this.webContents.isDomReady()) { + printWarning() + } else { + this.webContents.once('dom-ready', printWarning) } - 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); -}; + } + + const { url, frameName } = urlFrameName + v8Util.deleteHiddenValue(webContents, 'url-framename') + const options = { + show: true, + x: left, + y: top, + width: width || 800, + height: height || 600, + webContents + } + const referrer = { url: '', policy: 'default' } + ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', + event, url, referrer, frameName, disposition, options) + }) + } + + app.emit('web-contents-created', {}, this) +} + // JavaScript wrapper of Debugger. -const { Debugger } = process.electronBinding('debugger'); -Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand); -Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype); +const { Debugger } = process.atomBinding('debugger') + +Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype) + // Public APIs. module.exports = { - create(options = {}) { - return binding.create(options); - }, - fromId(id) { - return binding.fromId(id); - }, - getFocusedWebContents() { - let focused = null; - for (const contents of binding.getAllWebContents()) { - if (!contents.isFocused()) - continue; - if (focused == null) - focused = contents; - // Return webview web contents which may be embedded inside another - // web contents that is also reporting as focused - if (contents.getType() === 'webview') - return contents; - } - return focused; - }, - getAllWebContents() { - return binding.getAllWebContents(); + 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 } -}; -//# sourceMappingURL=web-contents.js.map \ No newline at end of file + return focused + }, + + getAllWebContents () { + return binding.getAllWebContents() + } +} diff --git a/electronasar/canary/browser/chrome-extension.js b/electronasar/canary/browser/chrome-extension.js index 2736389..3f4561f 100644 --- a/electronasar/canary/browser/chrome-extension.js +++ b/electronasar/canary/browser/chrome-extension.js @@ -1,490 +1,446 @@ -'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); +'use strict' + +const { app, webContents, BrowserWindow } = require('electron') +const { getAllWebContents } = process.atomBinding('web_contents') +const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents() +const ipcMain = require('@electron/internal/browser/ipc-main-internal') + +const { Buffer } = require('buffer') +const fs = require('fs') +const path = require('path') +const url = require('url') + // Mapping between extensionId(hostname) and manifest. -const manifestMap = {}; // extensionId => manifest -const manifestNameMap = {}; // name => manifest -const devToolsExtensionNames = new Set(); +const manifestMap = {} // extensionId => manifest +const manifestNameMap = {} // name => manifest +const devToolsExtensionNames = new Set() + const generateExtensionIdFromName = function (name) { - return name.replace(/[\W_]+/g, '-').toLowerCase(); -}; + return name.replace(/[\W_]+/g, '-').toLowerCase() +} + const isWindowOrWebView = function (webContents) { - const type = webContents.getType(); - return type === 'window' || type === 'webview'; -}; -const isBackgroundPage = function (webContents) { - return webContents.getType() === 'backgroundPage'; -}; + const type = webContents.getType() + return type === 'window' || type === 'webview' +} + // Create or get manifest object from |srcDirectory|. const getManifestFromPath = function (srcDirectory) { - let manifest; - let manifestContent; - try { - manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json')); - } - catch (readError) { - console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`); - console.warn(readError.stack || readError); - throw readError; - } - try { - manifest = JSON.parse(manifestContent); - } - catch (parseError) { - console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`); - console.warn(parseError.stack || parseError); - throw parseError; - } - if (!manifestNameMap[manifest.name]) { - const extensionId = generateExtensionIdFromName(manifest.name); - manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest; - Object.assign(manifest, { - srcDirectory: srcDirectory, - extensionId: extensionId, - // We can not use 'file://' directly because all resources in the extension - // will be treated as relative to the root in Chrome. - startPage: url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: extensionId, - pathname: manifest.devtools_page - }) - }); - return manifest; - } - else if (manifest && manifest.name) { - console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`); - return manifest; - } -}; -// Manage the background pages. -const backgroundPages = {}; -const startBackgroundPages = function (manifest) { - if (backgroundPages[manifest.extensionId] || !manifest.background) - return; - let html; - let name; - if (manifest.background.page) { - name = manifest.background.page; - html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page)); - } - else { - name = '_generated_background_page.html'; - const scripts = manifest.background.scripts.map((name) => { - return ``; - }).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({ + 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: manifest.extensionId, - pathname: name - })); -}; + 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, + commandLineSwitches: ['--background-page'] + }) + backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name } + contents.loadURL(url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: manifest.extensionId, + pathname: name + })) +} + const removeBackgroundPages = function (manifest) { - if (!backgroundPages[manifest.extensionId]) - return; - backgroundPages[manifest.extensionId].webContents.destroy(); - delete backgroundPages[manifest.extensionId]; -}; + 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); - } - } -}; + for (const page of Object.values(backgroundPages)) { + page.webContents._sendInternalToAll(...args) + } +} + // Dispatch web contents events to Chrome APIs const hookWebContentsEvents = function (webContents) { - const tabId = webContents.id; - sendToBackgroundPages('CHROME_TABS_ONCREATED'); - webContents.on('will-navigate', (event, url) => { - sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { - frameId: 0, - parentFrameId: -1, - processId: webContents.getProcessId(), - tabId: tabId, - timeStamp: Date.now(), - url: url - }); - }); - webContents.on('did-navigate', (event, url) => { - sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { - frameId: 0, - parentFrameId: -1, - processId: webContents.getProcessId(), - tabId: tabId, - timeStamp: Date.now(), - url: url - }); - }); - webContents.once('destroyed', () => { - sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId); - }); -}; + 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'); -}); +let nextId = 0 + +ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { + const page = backgroundPages[extensionId] + if (!page) { + console.error(`Connect to unknown extension ${extensionId}`) + return + } + + const portId = ++nextId + event.returnValue = { tabId: page.webContents.id, portId: portId } + + event.sender.once('render-view-deleted', () => { + if (page.webContents.isDestroyed()) return + page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`) + }) + page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo) +}) + +ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) { + event.returnValue = manifestMap[extensionId] +}) + +let resultID = 1 +ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message, originResultID) { + const page = backgroundPages[extensionId] + if (!page) { + console.error(`Connect to unknown extension ${extensionId}`) + return + } + + page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message, resultID) + ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => { + event.sender._sendInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result) + }) + resultID++ +}) + +ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message, originResultID) { + const contents = webContents.fromId(tabId) + if (!contents) { + console.error(`Sending message to unknown tab ${tabId}`) + return + } + + const senderTabId = isBackgroundPage ? null : event.sender.id + + contents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message, resultID) + ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => { + event.sender._sendInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result) + }) + resultID++ +}) + const isChromeExtension = function (pageURL) { - const { protocol } = url.parse(pageURL); - return protocol === 'chrome-extension:'; -}; -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); -}); + const { protocol } = url.parse(pageURL) + return protocol === 'chrome-extension:' +} + +ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) { + const pageURL = event.sender._getURL() + if (!isChromeExtension(pageURL)) { + console.error(`Blocked ${pageURL} from calling chrome.tabs.executeScript()`) + return + } + + const contents = webContents.fromId(tabId) + if (!contents) { + console.error(`Sending message to unknown tab ${tabId}`) + return + } + + let code, url + if (details.file) { + const manifest = manifestMap[extensionId] + code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))) + url = `chrome-extension://${extensionId}${details.file}` + } else { + code = details.code + url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js` + } + + contents._sendInternal('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code) +}) + // Transfer the content scripts to renderer. -const contentScripts = {}; +const 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); + 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))) } - catch (e) { - console.error('Failed to read content scripts', e); + } + + const contentScriptToEntry = function (script) { + return { + matches: script.matches, + js: script.js ? script.js.map(readArrayOfFiles) : [], + css: script.css ? script.css.map(readArrayOfFiles) : [], + runAt: script.run_at || 'document_idle' } -}; + } + + try { + const entry = { + extensionId: manifest.extensionId, + contentScripts: manifest.content_scripts.map(contentScriptToEntry) + } + contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry) + } catch (e) { + console.error('Failed to read content scripts', e) + } +} + const removeContentScripts = function (manifest) { - if (!contentScripts[manifest.name]) - return; - renderProcessPreferences.removeEntry(contentScripts[manifest.name]); - delete contentScripts[manifest.name]; -}; + if (!contentScripts[manifest.name]) return + + renderProcessPreferences.removeEntry(contentScripts[manifest.name]) + delete contentScripts[manifest.name] +} + // Transfer the |manifest| to a format that can be recognized by the // |DevToolsAPI.addExtensions|. const manifestToExtensionInfo = function (manifest) { - return { - startPage: manifest.startPage, - srcDirectory: manifest.srcDirectory, - name: manifest.name, - exposeExperimentalAPIs: true - }; -}; + return { + startPage: manifest.startPage, + srcDirectory: manifest.srcDirectory, + name: manifest.name, + exposeExperimentalAPIs: true + } +} + // Load the extensions for the window. const loadExtension = function (manifest) { - startBackgroundPages(manifest); - injectContentScripts(manifest); -}; + startBackgroundPages(manifest) + injectContentScripts(manifest) +} + const loadDevToolsExtensions = function (win, manifests) { - if (!win.devToolsWebContents) - return; - manifests.forEach(loadExtension); - const extensionInfoArray = manifests.map(manifestToExtensionInfo); - extensionInfoArray.forEach((extension) => { - win.devToolsWebContents._grantOriginAccess(extension.startPage); - }); - win.devToolsWebContents.executeJavaScript(`InspectorFrontendAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`); -}; + if (!win.devToolsWebContents) return + + manifests.forEach(loadExtension) + + const extensionInfoArray = manifests.map(manifestToExtensionInfo) + extensionInfoArray.forEach((extension) => { + win.devToolsWebContents._grantOriginAccess(extension.startPage) + }) + win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`) +} + app.on('web-contents-created', function (event, webContents) { - if (!isWindowOrWebView(webContents)) - return; - hookWebContentsEvents(webContents); - webContents.on('devtools-opened', function () { - loadDevToolsExtensions(webContents, Object.values(manifestMap)); - }); -}); + 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 - }); + 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) } - fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) { - if (err) { - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - return callback(-6); // FILE_NOT_FOUND - } - else { - return callback(content); - } - }); -}; + }) +} + app.on('session-created', function (ses) { - ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { - if (error) { - console.error(`Unable to register chrome-extension protocol: ${error}`); - } - }); -}); + ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) { + if (error) { + console.error(`Unable to register chrome-extension protocol: ${error}`) + } + }) +}) + // The persistent path of "DevTools Extensions" preference file. -let loadedDevToolsExtensionsPath = null; +let loadedDevToolsExtensionsPath = null + app.on('will-quit', function () { - try { - const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) - .map(name => manifestNameMap[name].srcDirectory); - if (loadedDevToolsExtensions.length > 0) { - try { - fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)); - } - catch (_a) { - // Ignore error - } - fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)); - } - else { - fs.unlinkSync(loadedDevToolsExtensionsPath); - } - } - catch (_b) { + try { + const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) + .map(name => manifestNameMap[name].srcDirectory) + if (loadedDevToolsExtensions.length > 0) { + try { + fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)) + } catch (error) { // Ignore error + } + fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)) + } else { + fs.unlinkSync(loadedDevToolsExtensionsPath) } -}); + } catch (error) { + // Ignore error + } +}) + // We can not use protocol or BrowserWindow until app is ready. app.once('ready', function () { - // The public API to add/remove extensions. - BrowserWindow.addExtension = function (srcDirectory) { - const manifest = getManifestFromPath(srcDirectory); - if (manifest) { - loadExtension(manifest); - for (const webContents of getAllWebContents()) { - if (isWindowOrWebView(webContents)) { - loadDevToolsExtensions(webContents, [manifest]); - } - } - return manifest.name; - } - }; - BrowserWindow.removeExtension = function (name) { - const manifest = manifestNameMap[name]; - if (!manifest) - return; - removeBackgroundPages(manifest); - removeContentScripts(manifest); - delete manifestMap[manifest.extensionId]; - delete manifestNameMap[name]; - }; - BrowserWindow.getExtensions = function () { - const extensions = {}; - Object.keys(manifestNameMap).forEach(function (name) { - const manifest = manifestNameMap[name]; - extensions[name] = { name: manifest.name, version: manifest.version }; - }); - return extensions; - }; - BrowserWindow.addDevToolsExtension = function (srcDirectory) { - const manifestName = BrowserWindow.addExtension(srcDirectory); - if (manifestName) { - devToolsExtensionNames.add(manifestName); - } - return manifestName; - }; - BrowserWindow.removeDevToolsExtension = function (name) { - BrowserWindow.removeExtension(name); - devToolsExtensionNames.delete(name); - }; - BrowserWindow.getDevToolsExtensions = function () { - const extensions = BrowserWindow.getExtensions(); - const devExtensions = {}; - Array.from(devToolsExtensionNames).forEach(function (name) { - if (!extensions[name]) - return; - devExtensions[name] = extensions[name]; - }); - return devExtensions; - }; - // Load persisted extensions. - loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions'); - try { - const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath)); - if (Array.isArray(loadedDevToolsExtensions)) { - for (const srcDirectory of loadedDevToolsExtensions) { - // Start background pages and set content scripts. - BrowserWindow.addDevToolsExtension(srcDirectory); - } + // 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 } - catch (error) { - if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') { - console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath); - console.error(error); - } + } + + 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) } -}); -//# sourceMappingURL=chrome-extension.js.map \ No newline at end of file + 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) + } + } +}) diff --git a/electronasar/canary/browser/crash-reporter-init.js b/electronasar/canary/browser/crash-reporter-init.js deleted file mode 100644 index 917ae67..0000000 --- a/electronasar/canary/browser/crash-reporter-init.js +++ /dev/null @@ -1,23 +0,0 @@ -'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/canary/browser/default-menu.js b/electronasar/canary/browser/default-menu.js deleted file mode 100644 index 8e7425c..0000000 --- a/electronasar/canary/browser/default-menu.js +++ /dev/null @@ -1,50 +0,0 @@ -"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/canary/browser/desktop-capturer.js b/electronasar/canary/browser/desktop-capturer.js index b012a0c..62e68bb 100644 --- a/electronasar/canary/browser/desktop-capturer.js +++ b/electronasar/canary/browser/desktop-capturer.js @@ -1,86 +1,73 @@ -'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 []; +'use strict' + +const ipcMain = require('@electron/internal/browser/ipc-main-internal') +const { desktopCapturer } = process.atomBinding('desktop_capturer') + +const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b) + +// A queue for holding all requests from renderer process. +let requestsQueue = [] + +const electronSources = 'ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES' +const capturerResult = (id) => `ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}` + +ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, id) => { + const request = { + id, + options: { + captureWindow, + captureScreen, + thumbnailSize + }, + webContents: event.sender + } + requestsQueue.push(request) + if (requestsQueue.length === 1) { + desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) + } + + // If the WebContents is destroyed before receiving result, just remove the + // reference from requestsQueue to make the module not send the result to it. + event.sender.once('destroyed', () => { + request.webContents = null + }) +}) + +desktopCapturer.emit = (event, name, sources) => { + // Receiving sources result from main process, now send them back to renderer. + const handledRequest = requestsQueue.shift() + const handledWebContents = handledRequest.webContents + const unhandledRequestsQueue = [] + + const result = sources.map(source => { + return { + id: source.id, + name: source.name, + thumbnail: source.thumbnail.toDataURL(), + display_id: source.display_id } - 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; - } + }) + + if (handledWebContents) { + handledWebContents._sendInternal(capturerResult(handledRequest.id), result) + } + + // Check the queue to see whether there is another identical request & handle + requestsQueue.forEach(request => { + const webContents = request.webContents + if (deepEqual(handledRequest.options, request.options)) { + if (webContents) { + webContents._sendInternal(capturerResult(request.id), result) + } + } else { + unhandledRequestsQueue.push(request) } - const 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 + }) + requestsQueue = unhandledRequestsQueue + + // If the requestsQueue is not empty, start a new request handling. + if (requestsQueue.length > 0) { + const { captureWindow, captureScreen, thumbnailSize } = requestsQueue[0].options + return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) + } +} diff --git a/electronasar/canary/browser/devtools.js b/electronasar/canary/browser/devtools.js deleted file mode 100644 index fde2e96..0000000 --- a/electronasar/canary/browser/devtools.js +++ /dev/null @@ -1,93 +0,0 @@ -'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/canary/browser/guest-view-manager.js b/electronasar/canary/browser/guest-view-manager.js index 02b6290..b329947 100644 --- a/electronasar/canary/browser/guest-view-manager.js +++ b/electronasar/canary/browser/guest-view-manager.js @@ -1,365 +1,451 @@ -'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'); +'use strict' + +const { webContents } = require('electron') +const ipcMain = require('@electron/internal/browser/ipc-main-internal') +const parseFeaturesString = require('@electron/internal/common/parse-features-string') +const errorUtils = require('@electron/internal/common/error-utils') +const { + syncMethods, + asyncCallbackMethods, + asyncPromiseMethods +} = require('@electron/internal/common/web-view-methods') + // Doesn't exist in early initialization. -let webViewManager = null; +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 = {}; + 'load-commit', + 'did-attach', + 'did-finish-load', + 'did-fail-load', + 'did-frame-finish-load', + 'did-start-loading', + 'did-stop-loading', + 'dom-ready', + 'console-message', + 'context-menu', + 'devtools-opened', + 'devtools-closed', + 'devtools-focused', + 'new-window', + 'will-navigate', + 'did-start-navigation', + 'did-navigate', + 'did-frame-navigate', + 'did-navigate-in-page', + 'focus-change', + 'close', + 'crashed', + 'gpu-crashed', + 'plugin-crashed', + 'destroyed', + 'page-title-updated', + 'page-favicon-updated', + 'enter-html-full-screen', + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', + 'found-in-page', + 'did-change-theme-color', + 'update-target-url' +] + +let nextGuestInstanceId = 0 +const guestInstances = {} +const embedderElementsMap = {} + +// Generate guestInstanceId. +const getNextGuestInstanceId = function () { + return ++nextGuestInstanceId +} + // Create a new guest instance. const createGuest = function (embedder, params) { - if (webViewManager == null) { - webViewManager = process.electronBinding('web_view_manager'); + if (webViewManager == null) { + webViewManager = process.atomBinding('web_view_manager') + } + + const guestInstanceId = getNextGuestInstanceId(embedder) + const guest = webContents.create({ + isGuest: true, + partition: params.partition, + embedder: embedder + }) + guestInstances[guestInstanceId] = { + guest: guest, + embedder: embedder + } + + // Clear the guest from map when it is destroyed. + // + // The guest WebContents is usually destroyed in 2 cases: + // 1. The embedder frame is closed (reloaded or destroyed), and it + // automatically closes the guest frame. + // 2. The guest frame is detached dynamically via JS, and it is manually + // destroyed when the renderer sends the GUEST_VIEW_MANAGER_DESTROY_GUEST + // message. + // The second case relies on the libcc patch: + // https://github.com/electron/libchromiumcontent/pull/676 + // The patch was introduced to work around a bug in Chromium: + // https://github.com/electron/electron/issues/14211 + // We should revisit the bug to see if we can remove our libcc patch, the + // patch was introduced in Chrome 66. + guest.once('destroyed', () => { + if (guestInstanceId in guestInstances) { + detachGuest(embedder, guestInstanceId) } - 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); + }) + + // 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 } - // 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; -}; + + if (params.src) { + const opts = {} + if (params.httpreferrer) { + opts.httpReferrer = params.httpreferrer + } + if (params.useragent) { + opts.userAgent = params.useragent + } + this.loadURL(params.src, opts) + } + guest.allowPopups = params.allowpopups + embedder.emit('did-attach-webview', event, guest) + }) + + const sendToEmbedder = (channel, ...args) => { + if (!embedder.isDestroyed()) { + embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args) + } + } + + // Dispatch events to embedder. + const fn = function (event) { + guest.on(event, function (_, ...args) { + sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args) + }) + } + for (const event of supportedWebViewEvents) { + fn(event) + } + + // Dispatch guest's IPC messages to embedder. + guest.on('ipc-message-host', function (_, [channel, ...args]) { + sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args) + }) + + // Notify guest of embedder window visibility when it is ready + // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed + guest.on('dom-ready', function () { + const guestInstance = guestInstances[guestInstanceId] + if (guestInstance != null && guestInstance.visibilityState != null) { + guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState) + } + }) + + // Forward internal web contents event to embedder to handle + // native window.open setup + guest.on('-add-new-contents', (...args) => { + if (guest.getLastWebPreferences().nativeWindowOpen === true) { + const embedder = getEmbedder(guestInstanceId) + if (embedder != null) { + embedder.emit('-add-new-contents', ...args) + } + } + }) + guest.on('-web-contents-created', (...args) => { + if (guest.getLastWebPreferences().nativeWindowOpen === true) { + const embedder = getEmbedder(guestInstanceId) + if (embedder != null) { + embedder.emit('-web-contents-created', ...args) + } + } + }) + + return guestInstanceId +} + // Attach the guest to an element of embedder. const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { - const embedder = event.sender; - // Destroy the old guest when attaching. - const key = `${embedder.id}-${elementInstanceId}`; - const oldGuestInstanceId = embedderElementsMap[key]; - if (oldGuestInstanceId != null) { - // Reattachment to the same guest is just a no-op. - if (oldGuestInstanceId === guestInstanceId) { - return; - } - const oldGuestInstance = guestInstances[oldGuestInstanceId]; - if (oldGuestInstance) { - oldGuestInstance.guest.detachFromOuterFrame(); - } + 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 guestInstance = guestInstances[guestInstanceId]; - // If this isn't a valid guest instance then do nothing. - if (!guestInstance) { - throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`); + + const oldGuestInstance = guestInstances[oldGuestInstanceId] + if (oldGuestInstance) { + oldGuestInstance.guest.destroy() } - const { guest } = guestInstance; - if (guest.hostWebContents !== event.sender) { - throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`); + } + + const guestInstance = guestInstances[guestInstanceId] + // If this isn't a valid guest instance then do nothing. + if (!guestInstance) { + throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) + } + const { guest } = guestInstance + if (guest.hostWebContents !== event.sender) { + throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) + } + + // If this guest is already attached to an element then remove it + if (guestInstance.elementInstanceId) { + const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}` + delete embedderElementsMap[oldKey] + + // Remove guest from embedder if moving across web views + if (guest.viewInstanceId !== params.instanceId) { + webViewManager.removeGuest(guestInstance.embedder, guestInstanceId) + guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`) } - // If this guest is already attached to an element then remove it - if (guestInstance.elementInstanceId) { - const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`; - delete embedderElementsMap[oldKey]; - // Remove guest from embedder if moving across web views - if (guest.viewInstanceId !== params.instanceId) { - webViewManager.removeGuest(guestInstance.embedder, guestInstanceId); - guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`); - } + } + + const webPreferences = { + guestInstanceId: guestInstanceId, + nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, + enableRemoteModule: params.enableremotemodule, + plugins: params.plugins, + zoomFactor: embedder._getZoomFactor(), + webSecurity: !params.disablewebsecurity, + enableBlinkFeatures: params.blinkfeatures, + disableBlinkFeatures: params.disableblinkfeatures + } + + // parse the 'webpreferences' attribute string, if set + // this uses the same parsing rules as window.open uses for its features + if (typeof params.webpreferences === 'string') { + parseFeaturesString(params.webpreferences, function (key, value) { + if (value === undefined) { + // no value was specified, default it to true + value = true + } + webPreferences[key] = value + }) + } + + if (params.preload) { + webPreferences.preloadURL = params.preload + } + + // Return null from native window.open if allowpopups is unset + if (webPreferences.nativeWindowOpen === true && !params.allowpopups) { + webPreferences.disablePopups = true + } + + // Security options that guest will always inherit from embedder + const inheritedWebPreferences = new Map([ + ['contextIsolation', true], + ['javascript', false], + ['nativeWindowOpen', true], + ['nodeIntegration', false], + ['enableRemoteModule', false], + ['sandbox', true] + ]) + + // Inherit certain option values from embedder + const lastWebPreferences = embedder.getLastWebPreferences() + for (const [name, value] of inheritedWebPreferences) { + if (lastWebPreferences[name] === value) { + webPreferences[name] = value } - 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); -}; + } + + embedder.emit('will-attach-webview', event, webPreferences, params) + if (event.defaultPrevented) { + if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId + guest.destroy() + return + } + + guest.attachParams = params + embedderElementsMap[key] = guestInstanceId + + guest.setEmbedder(embedder) + guestInstance.embedder = embedder + guestInstance.elementInstanceId = elementInstanceId + + watchEmbedder(embedder) + + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) + guest.attachToIframe(embedder, embedderFrameId) +} + // Remove an guest-embedder relationship. const detachGuest = function (embedder, guestInstanceId) { - const guestInstance = guestInstances[guestInstanceId]; - if (embedder !== guestInstance.embedder) { - return; - } - webViewManager.removeGuest(embedder, guestInstanceId); - delete guestInstances[guestInstanceId]; - const key = `${embedder.id}-${guestInstance.elementInstanceId}`; - delete embedderElementsMap[key]; -}; + const guestInstance = guestInstances[guestInstanceId] + if (embedder !== guestInstance.embedder) { + return + } + + webViewManager.removeGuest(embedder, guestInstanceId) + delete guestInstances[guestInstanceId] + + const key = `${embedder.id}-${guestInstance.elementInstanceId}` + delete embedderElementsMap[key] +} + // Once an embedder has had a guest attached we watch it for destruction to // destroy any remaining guests. -const watchedEmbedders = new Set(); +const watchedEmbedders = new Set() const watchEmbedder = function (embedder) { - if (watchedEmbedders.has(embedder)) { - return; + 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) + } } - 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(); + } + 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); -}; + if (!isWebViewTagEnabledCache.has(contents)) { + const value = contents.getLastWebPreferences().webviewTag + isWebViewTagEnabledCache.set(contents, value) + } + + 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); -}); + ipcMain.on(channel, (event, ...args) => { + if (isWebViewTagEnabled(event.sender)) { + handler(event, ...args) + } else { + event.returnValue = null + } + }) +} + +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) { + event.sender._sendInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params)) +}) + +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event, params) { + event.returnValue = createGuest(event.sender, params) +}) + handleMessage('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) { - try { - const guest = getGuestForWebContents(guestInstanceId, event.sender); - guest.detachFromOuterFrame(); - } - catch (error) { - console.error(`Guest destroy failed: ${error}`); - } -}); + try { + const guest = getGuestForWebContents(guestInstanceId, event.sender) + guest.destroy() + } catch (error) { + console.error(`Guest destroy failed: ${error}`) + } +}) + handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { - try { - attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params); - } - catch (error) { - console.error(`Guest attach failed: ${error}`); - } -}); + 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); +ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) { + const guest = getGuest(guestInstanceId) + if (guest === event.sender) { + event.sender.emit('focus-change', {}, focus, guestInstanceId) + } else { + console.error(`focus-change for guestInstanceId: ${guestInstanceId}`) + } +}) + +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, requestId, guestInstanceId, method, args, hasCallback) { + new Promise(resolve => { + const guest = getGuestForWebContents(guestInstanceId, event.sender) + if (!asyncCallbackMethods.has(method) && !asyncPromiseMethods.has(method)) { + throw new Error(`Invalid method: ${method}`) } - else { - console.error(`focus-change for guestInstanceId: ${guestInstanceId}`); + if (hasCallback) { + guest[method](...args, resolve) + } else { + resolve(guest[method](...args)) } -}); -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}`); + }).then(result => { + return [null, result] + }, error => { + return [errorUtils.serialize(error)] + }).then(responseArgs => { + event.sender._sendInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs) + }) +}) + +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_SYNC_CALL', function (event, guestInstanceId, method, args) { + try { + const guest = getGuestForWebContents(guestInstanceId, event.sender) + if (!syncMethods.has(method)) { + throw new Error(`Invalid method: ${method}`) } - return guest[method](...args); -}); + event.returnValue = [null, guest[method](...args)] + } catch (error) { + event.returnValue = [errorUtils.serialize(error)] + } +}) + // Returns WebContents from its guest id hosted in given webContents. const getGuestForWebContents = function (guestInstanceId, contents) { - const guest = getGuest(guestInstanceId); - if (!guest) { - throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`); - } - if (guest.hostWebContents !== contents) { - throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`); - } - return guest; -}; + const guest = getGuest(guestInstanceId) + if (!guest) { + throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) + } + if (guest.hostWebContents !== contents) { + throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) + } + return guest +} + // Returns WebContents from its guest id. const getGuest = function (guestInstanceId) { - const guestInstance = guestInstances[guestInstanceId]; - if (guestInstance != null) - return guestInstance.guest; -}; + const guestInstance = guestInstances[guestInstanceId] + if (guestInstance != null) return guestInstance.guest +} + // Returns the embedder of the guest. const getEmbedder = function (guestInstanceId) { - const guestInstance = guestInstances[guestInstanceId]; - if (guestInstance != null) - return guestInstance.embedder; -}; -exports.getGuestForWebContents = getGuestForWebContents; -exports.isWebViewTagEnabled = isWebViewTagEnabled; -//# sourceMappingURL=guest-view-manager.js.map \ No newline at end of file + const guestInstance = guestInstances[guestInstanceId] + if (guestInstance != null) return guestInstance.embedder +} + +exports.getGuestForWebContents = getGuestForWebContents diff --git a/electronasar/canary/browser/guest-window-manager.js b/electronasar/canary/browser/guest-window-manager.js index 4955b28..6a71e94 100644 --- a/electronasar/canary/browser/guest-window-manager.js +++ b/electronasar/canary/browser/guest-window-manager.js @@ -1,148 +1,176 @@ -'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(); +'use strict' + +const { BrowserWindow, webContents } = require('electron') +const { isSameOrigin } = process.atomBinding('v8_util') +const ipcMain = require('@electron/internal/browser/ipc-main-internal') +const parseFeaturesString = require('@electron/internal/common/parse-features-string') + +const hasProp = {}.hasOwnProperty +const frameToGuest = new Map() + // Security options that child windows will always inherit from parent windows const inheritedWebPreferences = new Map([ - ['contextIsolation', true], - ['javascript', false], - ['nativeWindowOpen', true], - ['nodeIntegration', false], - ['enableRemoteModule', false], - ['sandbox', true], - ['webviewTag', false], - ['nodeIntegrationInSubFrames', false] -]); + ['contextIsolation', true], + ['javascript', false], + ['nativeWindowOpen', true], + ['nodeIntegration', false], + ['enableRemoteModule', false], + ['sandbox', true], + ['webviewTag', false] +]) + // Copy attribute of |parent| to |child| if it is not defined in |child|. const mergeOptions = function (child, parent, visited) { - // Check for circular reference. - if (visited == null) - visited = new Set(); - if (visited.has(parent)) - return; - visited.add(parent); - for (const key in parent) { - if (key === 'isBrowserView') - continue; - if (!hasProp.call(parent, key)) - continue; - if (key in child && key !== 'webPreferences') - continue; - const value = parent[key]; - if (typeof value === 'object') { - child[key] = mergeOptions(child[key] || {}, value, visited); - } - else { - child[key] = value; - } + // Check for circular reference. + if (visited == null) visited = new Set() + if (visited.has(parent)) return + + visited.add(parent) + for (const key in parent) { + if (key === 'isBrowserView') continue + if (!hasProp.call(parent, key)) continue + if (key in child && key !== 'webPreferences') continue + + const value = parent[key] + if (typeof value === 'object') { + child[key] = mergeOptions(child[key] || {}, value, visited) + } else { + child[key] = value } - visited.delete(parent); - return child; -}; + } + 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 (options.webPreferences == null) { + options.webPreferences = {} + } + if (embedder.browserWindowOptions != null) { + let parentOptions = embedder.browserWindowOptions + + // if parent's visibility is available, that overrides 'show' flag (#12125) + const win = BrowserWindow.fromWebContents(embedder.webContents) + if (win != null) { + parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() } } - if (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); + + // 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 } - 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; -}; + } + + // 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; -}; + // When |embedder| is destroyed we should also destroy attached guest, and if + // guest is closed by user then we should prevent |embedder| from double + // closing guest. + const guestId = guest.webContents.id + const closedByEmbedder = function () { + guest.removeListener('closed', closedByUser) + guest.destroy() + } + const closedByUser = function () { + embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) + embedder.removeListener('render-view-deleted', closedByEmbedder) + } + embedder.once('render-view-deleted', closedByEmbedder) + guest.once('closed', closedByUser) + if (frameName) { + frameToGuest.set(frameName, guest) + guest.frameName = frameName + guest.once('closed', function () { + frameToGuest.delete(frameName) + }) + } + return guestId +} + // Create a new guest created by |embedder| with |options|. const createGuest = function (embedder, url, referrer, frameName, options, postData) { - let guest = frameToGuest.get(frameName); - if (frameName && (guest != null)) { - guest.loadURL(url); - return guest.webContents.id; + let guest = frameToGuest.get(frameName) + if (frameName && (guest != null)) { + guest.loadURL(url) + return guest.webContents.id + } + + // Remember the embedder window's id. + if (options.webPreferences == null) { + options.webPreferences = {} + } + + guest = new BrowserWindow(options) + if (!options.webContents || url !== 'about:blank') { + // We should not call `loadURL` if the window was constructed from an + // existing webContents(window.open in a sandboxed renderer) and if the url + // is not 'about:blank'. + // + // Navigating to the url when creating the window from an existing + // webContents would not be necessary(it will navigate there anyway), but + // apparently there's a bug that allows the child window to be scripted by + // the opener, even when the child window is from another origin. + // + // That's why the second condition(url !== "about:blank") is required: to + // force `OverrideSiteInstanceForNavigation` to be called and consequently + // spawn a new renderer if the new window is targeting a different origin. + // + // If the URL is "about:blank", then it is very likely that the opener just + // wants to synchronously script the popup, for example: + // + // let popup = window.open() + // popup.document.body.write('

hello

') + // + // The above code would not work if a navigation to "about:blank" is done + // here, since the window would be cleared of all changes in the next tick. + const loadOptions = { + httpReferrer: referrer } - // 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)}`; - } - } + 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); -}; + 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); - } + let guestWindow = BrowserWindow.fromWebContents(guestContents) + if (guestWindow == null) { + const hostContents = guestContents.hostWebContents + if (hostContents != null) { + guestWindow = BrowserWindow.fromWebContents(hostContents) } - return guestWindow; -}; + } + return guestWindow +} + // Checks whether |sender| can access the |target|: // 1. Check whether |sender| is the parent of |target|. // 2. Check whether |sender| has node integration, if so it is allowed to @@ -154,175 +182,187 @@ const getGuestWindow = function (guestContents) { // The W3C does not have anything on this, but from my understanding of the // security model of |window.opener|, this should be fine. const canAccessWindow = function (sender, target) { - return (target.getLastWebPreferences().openerId === sender.id) || - (sender.getLastWebPreferences().nodeIntegration === true) || - isSameOrigin(sender.getURL(), target.getURL()); -}; + return (target.getLastWebPreferences().openerId === sender.id) || + (sender.getLastWebPreferences().nodeIntegration === true) || + isSameOrigin(sender.getURL(), target.getURL()) +} + // 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; +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { + if (url == null || url === '') url = 'about:blank' + if (frameName == null) frameName = '' + if (features == null) features = '' + + const options = {} + + const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] + const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'] + const disposition = 'new-window' + + // Used to store additional features + const additionalFeatures = [] + + // Parse the features + parseFeaturesString(features, function (key, value) { + if (value === undefined) { + additionalFeatures.push(key) + } else { + // Don't allow webPreferences to be set since it must be an object + // that cannot be directly overridden + if (key === 'webPreferences') return + + if (webPreferences.includes(key)) { + if (options.webPreferences == null) { + options.webPreferences = {} } + options.webPreferences[key] = value + } else { + options[key] = value + } } - if (options.top) { - if (options.y == null) { - options.y = options.top; - } + }) + if (options.left) { + if (options.x == null) { + options.x = options.left } - if (options.title == null) { - options.title = frameName; + } + if (options.top) { + if (options.y == null) { + options.y = options.top } - if (options.width == null) { - options.width = 800; + } + if (options.title == null) { + options.title = frameName + } + if (options.width == null) { + options.width = 800 + } + if (options.height == null) { + options.height = 600 + } + + for (const name of ints) { + if (options[name] != null) { + options[name] = parseInt(options[name], 10) } - if (options.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); -}); + } + + const referrer = { url: '', policy: 'default' } + ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, + url, referrer, frameName, disposition, options, additionalFeatures) +}) + // Routed window.open messages with fully parsed options -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; - } +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer, + frameName, disposition, options, + additionalFeatures, postData) { + options = mergeBrowserWindowOptions(event.sender, options) + event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer) + const { newGuest } = event + if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { + if (newGuest != null) { + if (options.webContents === newGuest.webContents) { + // the webContents is not changed, so set defaultPrevented to false to + // stop the callers of this event from destroying the webContents. + event.defaultPrevented = false + } + event.returnValue = setupGuest(event.sender, frameName, newGuest, options) + } else { + event.returnValue = null } - else { - event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData); - } -}); -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(); -}); + } else { + event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData) + } +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) { + const guestContents = webContents.fromId(guestId) + if (guestContents == null) return + + if (!canAccessWindow(event.sender, guestContents)) { + console.error(`Blocked ${event.sender.getURL()} from closing its opener.`) + return + } + + const guestWindow = getGuestWindow(guestContents) + if (guestWindow != null) guestWindow.destroy() +}) + const windowMethods = new Set([ - 'focus', - 'blur' -]); -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); - } -}); + 'focus', + 'blur' +]) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId) + if (guestContents == null) { + event.returnValue = null + return + } + + if (!canAccessWindow(event.sender, guestContents) || !windowMethods.has(method)) { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`) + event.returnValue = null + return + } + + const guestWindow = getGuestWindow(guestContents) + if (guestWindow != null) { + event.returnValue = guestWindow[method](...args) + } else { + event.returnValue = null + } +}) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { + if (targetOrigin == null) { + targetOrigin = '*' + } + + const guestContents = webContents.fromId(guestId) + if (guestContents == null) return + + // The W3C does not seem to have word on how postMessage should work when the + // origins do not match, so we do not do |canAccessWindow| check here since + // postMessage across origins is useful and not harmful. + if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) { + const sourceId = event.sender.id + guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) + } +}) + const webContentsMethods = new Set([ - 'print', - 'executeJavaScript' -]); -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.`); - } -}); + 'print', + 'executeJavaScript' +]) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId) + if (guestContents == null) return + + if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) { + guestContents[method](...args) + } else { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`) + } +}) + const webContentsSyncMethods = new Set([ - 'getURL', - 'loadURL' -]); -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 + 'getURL', + 'loadURL' +]) + +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) { + const guestContents = webContents.fromId(guestId) + if (guestContents == null) { + event.returnValue = null + return + } + + if (canAccessWindow(event.sender, guestContents) && webContentsSyncMethods.has(method)) { + event.returnValue = guestContents[method](...args) + } else { + console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`) + event.returnValue = null + } +}) diff --git a/electronasar/canary/browser/init.js b/electronasar/canary/browser/init.js index 7d92c1c..fc526e1 100644 --- a/electronasar/canary/browser/init.js +++ b/electronasar/canary/browser/init.js @@ -1,187 +1,188 @@ -"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'); +'use strict' + +const { Buffer } = require('buffer') +const fs = require('fs') +const path = require('path') +const util = require('util') +const Module = require('module') +const v8 = require('v8') + // We modified the original process.argv to let node.js load the init.js, // we need to restore it here. -process.argv.splice(1, 1); +process.argv.splice(1, 1) + // Clear search paths. -require('../common/reset-search-paths'); +require('../common/reset-search-paths') + // Import common settings. -require('@electron/internal/common/init'); -const globalPaths = Module.globalPaths; +require('@electron/internal/common/init') + +const globalPaths = Module.globalPaths + // Expose public APIs. -globalPaths.push(path.join(__dirname, 'api', 'exports')); +globalPaths.push(path.join(__dirname, 'api', 'exports')) + if (process.platform === 'win32') { - // Redirect node's console to use our own implementations, since node can not - // handle console output when running as GUI program. - const consoleLog = (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; + // Redirect node's console to use our own implementations, since node can not + // handle console output when running as GUI program. + const consoleLog = function (...args) { + return process.log(util.format(...args) + '\n') + } + const streamWrite = function (chunk, encoding, callback) { + if (Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding) + } + process.log(chunk) + if (callback) { + callback() + } + return true + } + console.log = console.error = console.warn = consoleLog + process.stdout.write = process.stderr.write = streamWrite } + // Don't quit on fatal error. process.on('uncaughtException', function (error) { - // Do nothing if the user has a custom uncaught exception handler. - if (process.listeners('uncaughtException').length > 1) { - return; - } - // Show error in GUI. - // 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); - }); -}); + // Do nothing if the user has a custom uncaught exception handler. + if (process.listeners('uncaughtException').length > 1) { + return + } + + // Show error in GUI. + const dialog = require('electron').dialog + const stack = error.stack ? error.stack : `${error.name}: ${error.message}` + const message = 'Uncaught Exception:\n' + stack + dialog.showErrorBox('A JavaScript error occurred in the main process', message) +}) + // Emit 'exit' event on quit. -const { app } = require('electron'); +const { app } = require('electron') + app.on('quit', function (event, exitCode) { - process.emit('exit', exitCode); -}); + process.emit('exit', exitCode) +}) + if (process.platform === 'win32') { - // If we are a Squirrel.Windows-installed app, set app user model ID - // so that users don't have to do this. - // - // Squirrel packages are always of the form: - // - // PACKAGE-NAME - // - Update.exe - // - app-VERSION - // - OUREXE.exe - // - // Squirrel itself will always set the shortcut's App User Model ID to the - // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call - // app.setAppUserModelId with a matching identifier so that renderer processes - // will inherit this value. - const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe'); - if (fs.existsSync(updateDotExe)) { - const packageDir = path.dirname(path.resolve(updateDotExe)); - const packageName = path.basename(packageDir).replace(/\s/g, ''); - const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, ''); - app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`); - } + // If we are a Squirrel.Windows-installed app, set app user model ID + // so that users don't have to do this. + // + // Squirrel packages are always of the form: + // + // PACKAGE-NAME + // - Update.exe + // - app-VERSION + // - OUREXE.exe + // + // Squirrel itself will always set the shortcut's App User Model ID to the + // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call + // app.setAppUserModelId with a matching identifier so that renderer processes + // will inherit this value. + const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe') + + if (fs.existsSync(updateDotExe)) { + const packageDir = path.dirname(path.resolve(updateDotExe)) + const packageName = path.basename(packageDir).replace(/\s/g, '') + const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '') + + app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) + } } + // Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit; +process.exit = app.exit + // Load the RPC server. -require('@electron/internal/browser/rpc-server'); +require('@electron/internal/browser/rpc-server') + // Load the guest view manager. -require('@electron/internal/browser/guest-view-manager'); -require('@electron/internal/browser/guest-window-manager'); +require('@electron/internal/browser/guest-view-manager') +require('@electron/internal/browser/guest-window-manager') + // Now we try to load app's package.json. -let packagePath = null; -let packageJson = null; -const searchPaths = ['app', 'app.asar', 'default_app.asar']; -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; - } - } +let packagePath = null +let packageJson = null +const searchPaths = ['app', 'app.asar', 'default_app.asar'] +for (packagePath of searchPaths) { + try { + packagePath = path.join(process.resourcesPath, packagePath) + packageJson = require(path.join(packagePath, 'package.json')) + break + } catch (error) { + continue + } } + if (packageJson == null) { - process.nextTick(function () { - return process.exit(1); - }); - throw new Error('Unable to find a valid app'); + process.nextTick(function () { + return process.exit(1) + }) + throw new Error('Unable to find a valid app') } + // Set application's version. if (packageJson.version != null) { - app.setVersion(packageJson.version); + app.setVersion(packageJson.version) } + // Set application's name. if (packageJson.productName != null) { - app.setName(`${packageJson.productName}`.trim()); -} -else if (packageJson.name != null) { - app.setName(`${packageJson.name}`.trim()); + app.setName(`${packageJson.productName}`.trim()) +} else if (packageJson.name != null) { + app.setName(`${packageJson.name}`.trim()) } + // Set application's desktop name. if (packageJson.desktopName != null) { - app.setDesktopName(packageJson.desktopName); -} -else { - app.setDesktopName((app.getName()) + '.desktop'); + app.setDesktopName(packageJson.desktopName) +} else { + app.setDesktopName((app.getName()) + '.desktop') } + // Set v8 flags if (packageJson.v8Flags != null) { - v8.setFlagsFromString(packageJson.v8Flags); + v8.setFlagsFromString(packageJson.v8Flags) } -app._setDefaultAppPaths(packagePath); -// Load the chrome devtools support. -require('@electron/internal/browser/devtools'); + +// Set the user path according to application's name. +app.setPath('userData', path.join(app.getPath('appData'), app.getName())) +app.setPath('userCache', path.join(app.getPath('cache'), app.getName())) +app.setAppPath(packagePath) + // Load the chrome extension support. -require('@electron/internal/browser/chrome-extension'); -const features = process.electronBinding('features'); +require('@electron/internal/browser/chrome-extension') + +const features = process.atomBinding('features') if (features.isDesktopCapturerEnabled()) { - // Load internal desktop-capturer module. - require('@electron/internal/browser/desktop-capturer'); + // Load internal desktop-capturer module. + require('@electron/internal/browser/desktop-capturer') } + // Load protocol module to ensure it is populated on app ready -require('@electron/internal/browser/api/protocol'); +require('@electron/internal/browser/api/protocol') + // Set main startup script of the app. -const mainStartupScript = packageJson.main || 'index.js'; -const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME']; -function currentPlatformSupportsAppIndicator() { - if (process.platform !== 'linux') - return false; - const currentDesktop = process.env.XDG_CURRENT_DESKTOP; - if (!currentDesktop) - return false; - if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) - return true; - // ubuntu based or derived session (default ubuntu one, communitheme…) supports - // indicator too. - if (/ubuntu/ig.test(currentDesktop)) - return true; - return false; +const mainStartupScript = packageJson.main || 'index.js' + +const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'] + +function currentPlatformSupportsAppIndicator () { + if (process.platform !== 'linux') return false + const currentDesktop = process.env.XDG_CURRENT_DESKTOP + + if (!currentDesktop) return false + if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true + // ubuntu based or derived session (default ubuntu one, communitheme…) supports + // indicator too. + if (/ubuntu/ig.test(currentDesktop)) return true + + return false } + // Workaround for electron/electron#5050 and electron/electron#9046 if (currentPlatformSupportsAppIndicator()) { - process.env.XDG_CURRENT_DESKTOP = 'Unity'; + process.env.XDG_CURRENT_DESKTOP = 'Unity' } -// Quit when all windows are closed and no other one is listening to this. -app.on('window-all-closed', () => { - if (app.listenerCount('window-all-closed') === 1) { - app.quit(); - } -}); -Promise.all([ - Promise.resolve().then(() => require('@electron/internal/browser/default-menu')), - app.whenReady -]).then(([{ setDefaultApplicationMenu }]) => { - // Create default menu - setDefaultApplicationMenu(); -}); -if (packagePath) { - // Finally load app's main.js and transfer control to C++. - Module._load(path.join(packagePath, mainStartupScript), Module, true); -} -else { - console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)'); - console.error('This normally means you\'ve damaged the Electron package somehow'); -} -//# sourceMappingURL=init.js.map \ No newline at end of file + +// Finally load app's main.js and transfer control to C++. +Module._load(path.join(packagePath, mainStartupScript), Module, true) diff --git a/electronasar/canary/browser/ipc-main-internal-utils.js b/electronasar/canary/browser/ipc-main-internal-utils.js deleted file mode 100644 index 6c57a46..0000000 --- a/electronasar/canary/browser/ipc-main-internal-utils.js +++ /dev/null @@ -1,53 +0,0 @@ -"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/canary/browser/ipc-main-internal.js b/electronasar/canary/browser/ipc-main-internal.js index 9ec98d1..9ab9569 100644 --- a/electronasar/canary/browser/ipc-main-internal.js +++ b/electronasar/canary/browser/ipc-main-internal.js @@ -1,8 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const events_1 = require("events"); -const emitter = new events_1.EventEmitter(); +'use strict' + +const { EventEmitter } = require('events') + +const emitter = new 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 +emitter.on('error', () => {}) + +module.exports = emitter diff --git a/electronasar/canary/browser/navigation-controller.js b/electronasar/canary/browser/navigation-controller.js deleted file mode 100644 index a76da41..0000000 --- a/electronasar/canary/browser/navigation-controller.js +++ /dev/null @@ -1,225 +0,0 @@ -'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/canary/browser/objects-registry.js b/electronasar/canary/browser/objects-registry.js index 5e28043..c3bbfae 100644 --- a/electronasar/canary/browser/objects-registry.js +++ b/electronasar/canary/browser/objects-registry.js @@ -1,121 +1,116 @@ -'use strict'; -const v8Util = process.electronBinding('v8_util'); +'use strict' + +const v8Util = process.atomBinding('v8_util') + const getOwnerKey = (webContents, contextId) => { - return `${webContents.id}-${contextId}`; -}; -class ObjectsRegistry { - constructor() { - this.nextId = 0; - // Stores all objects by ref-counting. - // (id) => {object, count} - this.storage = {}; - // Stores the IDs + 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); - } + return `${webContents.id}-${contextId}` } -module.exports = new ObjectsRegistry(); -//# sourceMappingURL=objects-registry.js.map \ No newline at end of file + +class ObjectsRegistry { + constructor () { + this.nextId = 0 + + // Stores all objects by ref-counting. + // (id) => {object, count} + this.storage = {} + + // Stores the IDs of objects referenced by WebContents. + // (ownerKey) => [id] + this.owners = {} + } + + // Register a new object and return its assigned ID. If the object is already + // registered then the already assigned ID would be returned. + add (webContents, contextId, obj) { + // Get or assign an ID to the object. + const id = this.saveToStorage(obj) + + // Add object to the set of referenced objects. + const ownerKey = getOwnerKey(webContents, contextId) + let owner = this.owners[ownerKey] + if (!owner) { + owner = this.owners[ownerKey] = new Set() + this.registerDeleteListener(webContents, contextId) + } + if (!owner.has(id)) { + owner.add(id) + // Increase reference count if not referenced before. + this.storage[id].count++ + } + return id + } + + // Get an object according to its ID. + get (id) { + const pointer = this.storage[id] + if (pointer != null) return pointer.object + } + + // Dereference an object according to its ID. + // Note that an object may be double-freed (cleared when page is reloaded, and + // then garbage collected in old page). + remove (webContents, contextId, id) { + const ownerKey = getOwnerKey(webContents, contextId) + const owner = this.owners[ownerKey] + if (owner) { + // Remove the reference in owner. + owner.delete(id) + // Dereference from the storage. + this.dereference(id) + } + } + + // Clear all references to objects refrenced by the WebContents. + clear (webContents, contextId) { + const ownerKey = getOwnerKey(webContents, contextId) + const owner = this.owners[ownerKey] + if (!owner) return + + for (const id of owner) this.dereference(id) + + delete this.owners[ownerKey] + } + + // Private: Saves the object into storage and assigns an ID for it. + saveToStorage (object) { + let id = v8Util.getHiddenValue(object, 'atomId') + if (!id) { + id = ++this.nextId + this.storage[id] = { + count: 0, + object: object + } + v8Util.setHiddenValue(object, 'atomId', id) + } + return id + } + + // Private: Dereference the object from store. + dereference (id) { + const pointer = this.storage[id] + if (pointer == null) { + return + } + pointer.count -= 1 + if (pointer.count === 0) { + v8Util.deleteHiddenValue(pointer.object, 'atomId') + delete this.storage[id] + } + } + + // Private: Clear the storage when renderer process is destroyed. + registerDeleteListener (webContents, contextId) { + // contextId => ${processHostId}-${contextCount} + const processHostId = contextId.split('-')[0] + const listener = (event, deletedProcessHostId) => { + if (deletedProcessHostId && + deletedProcessHostId.toString() === processHostId) { + webContents.removeListener('render-view-deleted', listener) + this.clear(webContents, contextId) + } + } + webContents.on('render-view-deleted', listener) + } +} + +module.exports = new ObjectsRegistry() diff --git a/electronasar/canary/browser/rpc-server.js b/electronasar/canary/browser/rpc-server.js index 7a04093..0e31d22 100644 --- a/electronasar/canary/browser/rpc-server.js +++ b/electronasar/canary/browser/rpc-server.js @@ -1,495 +1,558 @@ -'use strict'; -const electron = require('electron'); -const { EventEmitter } = require('events'); -const fs = require('fs'); -const path = require('path'); -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 { isParentDir } = require('@electron/internal/common/path-utils'); -const hasProp = {}.hasOwnProperty; +'use strict' + +const { spawn } = require('child_process') +const electron = require('electron') +const { EventEmitter } = require('events') +const fs = require('fs') +const os = require('os') +const path = require('path') +const v8Util = process.atomBinding('v8_util') +const eventBinding = process.atomBinding('event') + +const { isPromise } = electron + +const ipcMain = require('@electron/internal/browser/ipc-main-internal') +const objectsRegistry = require('@electron/internal/browser/objects-registry') +const guestViewManager = require('@electron/internal/browser/guest-view-manager') +const bufferUtils = require('@electron/internal/common/buffer-utils') +const errorUtils = require('@electron/internal/common/error-utils') + +const hasProp = {}.hasOwnProperty + // The internal properties of Function. const FUNCTION_PROPERTIES = [ - 'length', 'name', 'arguments', 'caller', 'prototype' -]; + 'length', 'name', 'arguments', 'caller', 'prototype' +] + // The remote functions in renderer processes. // id => Function -const rendererFunctions = v8Util.createDoubleIDWeakMap(); +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); - }); + 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' } - // 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 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) - }; -}; + 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'; - } + // 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; -}; + } + + // 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] - }; - }); -}; + 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 exceptionToMeta = function (sender, contextId, error) { + return { + type: 'exception', + value: errorUtils.serialize(error) + } +} + const throwRPCError = function (message) { - const error = new Error(message); - error.code = 'EBADRPC'; - error.errno = -72; - throw error; -}; + const 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); - }); - } + 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); -}; + } + + 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}`); +const unwrapArgs = function (sender, contextId, args) { + const metaToValue = function (meta) { + switch (meta.type) { + case 'value': + return meta.value + case 'remote-object': + return objectsRegistry.get(meta.id) + case 'array': + return unwrapArgs(sender, contextId, meta.value) + case 'buffer': + return bufferUtils.metaToBuffer(meta.value) + case 'date': + return new Date(meta.value) + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }) + case 'object': { + const ret = {} + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + for (const { name, value } of meta.members) { + ret[name] = metaToValue(value) } - }; - return args.map(metaToValue); -}; -const isRemoteModuleEnabledCache = new WeakMap(); -const isRemoteModuleEnabled = function (contents) { - if (!isRemoteModuleEnabledCache.has(contents)) { - isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled()); + return ret + } + case 'function-with-return-value': + const returnValue = metaToValue(meta.value) + return function () { + return returnValue + } + case 'function': { + // Merge contextId and meta.id, since meta.id can be the same in + // different webContents. + const objectId = [contextId, meta.id] + + // Cache the callbacks in renderer. + if (rendererFunctions.has(objectId)) { + return rendererFunctions.get(objectId) + } + + const callIntoRenderer = function (...args) { + if (!sender.isDestroyed()) { + sender._sendInternal('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args)) + } else { + removeRemoteListenersAndLogWarning(this, callIntoRenderer) + } + } + v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location) + Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) + + v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender) + rendererFunctions.set(objectId, callIntoRenderer) + return callIntoRenderer + } + default: + throw new TypeError(`Unknown type: ${meta.type}`) } - return isRemoteModuleEnabledCache.get(contents); -}; + } + return args.map(metaToValue) +} + +// Call a function and send reply asynchronously if it's a an asynchronous +// style function and the caller didn't pass a callback. +const callFunction = function (event, contextId, func, caller, args) { + const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') + const funcPassedCallback = typeof args[args.length - 1] === 'function' + try { + if (funcMarkedAsync && !funcPassedCallback) { + args.push(function (ret) { + event.returnValue = valueToMeta(event.sender, contextId, ret, true) + }) + func.apply(caller, args) + } else { + const ret = func.apply(caller, args) + return valueToMeta(event.sender, contextId, ret, true) + } + } catch (error) { + // Catch functions thrown further down in function invocation and wrap + // them with the function name so it's easier to trace things like + // `Error processing argument -1.` + const funcName = func.name || 'anonymous' + const err = new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`) + err.cause = error + throw err + } +} + const handleRemoteCommand = function (channel, handler) { - 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; - } - }); -}; + ipcMain.on(channel, (event, contextId, ...args) => { + let returnValue + if (!event.sender._isRemoteModuleEnabled()) { + event.returnValue = null + return + } + + try { + returnValue = handler(event, contextId, ...args) + } catch (error) { + returnValue = exceptionToMeta(event.sender, contextId, error) + } + + if (returnValue !== undefined) { + event.returnValue = returnValue + } + }) +} + handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) { - const objectId = [passedContextId, id]; - if (!rendererFunctions.has(objectId)) { - // Do nothing if the error has already been reported before. - return; - } - removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)); -}); + 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); - } + 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); -}); + } + + 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]; - } + 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); -}); + } + + 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]; - } + 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); -}); + } + + 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(); - } + 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); -}); + } + + 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; - } + 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); -}); + } + + 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)); -}); + args = unwrapArgs(event.sender, contextId, args) + const constructor = objectsRegistry.get(id) + + if (constructor == null) { + throwRPCError(`Cannot call constructor on missing remote object ${id}`) + } + + return valueToMeta(event.sender, contextId, new constructor(...args)) +}) + handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { - args = unwrapArgs(event.sender, 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; - } -}); + args = unwrapArgs(event.sender, contextId, args) + const func = objectsRegistry.get(id) + + if (func == null) { + throwRPCError(`Cannot call function on missing remote object ${id}`) + } + + return callFunction(event, contextId, func, global, args) +}) + handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, 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)); -}); + args = unwrapArgs(event.sender, contextId, args) + const object = objectsRegistry.get(id) + + if (object == null) { + throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`) + } + + return valueToMeta(event.sender, contextId, new object[method](...args)) +}) + handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, 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; - } -}); + args = unwrapArgs(event.sender, contextId, args) + const obj = objectsRegistry.get(id) + + if (obj == null) { + throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`) + } + + return callFunction(event, contextId, obj[method], obj, args) +}) + handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { - args = unwrapArgs(event.sender, 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; -}); + args = unwrapArgs(event.sender, contextId, args) + const obj = objectsRegistry.get(id) + + if (obj == null) { + throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`) + } + + obj[name] = args[0] + return null +}) + handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) { - const obj = objectsRegistry.get(id); - if (obj == null) { - throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`); - } - return valueToMeta(event.sender, contextId, obj[name]); -}); -handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) { - objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount); -}); + const obj = objectsRegistry.get(id) + + if (obj == null) { + throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`) + } + + return valueToMeta(event.sender, contextId, obj[name]) +}) + +handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) { + objectsRegistry.remove(event.sender, contextId, id) +}) + handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => { - objectsRegistry.clear(event.sender, contextId); - return null; -}); + 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; - } + 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); -}); + } + + 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(); +ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { + const window = event.sender.getOwnerBrowserWindow() + if (window) { + window.close() + } + event.returnValue = null +}) + +const getTempDirectory = function () { + try { + return electron.app.getPath('temp') + } catch (error) { + return os.tmpdir() + } +} + +const crashReporterInit = function (options) { + const productName = options.productName || electron.app.getName() + const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`) + let crashServicePid + + if (process.platform === 'win32') { + const env = { + ELECTRON_INTERNAL_CRASH_SERVICE: 1 } - 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(); + const args = [ + '--reporter-url=' + options.submitURL, + '--application-name=' + productName, + '--crashes-directory=' + crashesDirectory, + '--v=1' + ] + + const crashServiceProcess = spawn(process.helperExecPath, args, { + env, + detached: true + }) + + crashServicePid = crashServiceProcess.pid + } + + return { + productName, + crashesDirectory, + crashServicePid, + appVersion: electron.app.getVersion() + } +} + +const setReturnValue = function (event, getValue) { + try { + event.returnValue = [null, getValue()] + } catch (error) { + event.returnValue = [errorUtils.serialize(error)] + } +} + +ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { + setReturnValue(event, () => crashReporterInit(options)) +}) + +ipcMain.on('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { + setReturnValue(event, () => event.sender.getLastWebPreferences()) +}) + +ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', function (event) { + setReturnValue(event, () => electron.clipboard.readFindText()) +}) + +ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text) { + setReturnValue(event, () => electron.clipboard.writeFindText(text)) +}) + +ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { + const preloadPath = event.sender._getPreloadPath() + let preloadSrc = null + let preloadError = null + if (preloadPath) { + try { + preloadSrc = fs.readFileSync(preloadPath).toString() + } catch (err) { + preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack } } -})(); -ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { - if (!allowedClipboardMethods.has(method)) { - throw new Error(`Invalid method: ${method}`); + } + event.returnValue = { + preloadSrc, + preloadError, + isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(), + process: { + arch: process.arch, + platform: process.platform, + env: process.env, + version: process.version, + versions: process.versions } - return clipboardUtils.serialize(electron.clipboard[method](...clipboardUtils.deserialize(args))); -}); -const readFile = util.promisify(fs.readFile); -const realpath = util.promisify(fs.realpath); -let absoluteAppPath; -const getAppPath = async function () { - if (absoluteAppPath === undefined) { - absoluteAppPath = await realpath(electron.app.getAppPath()); - } - return absoluteAppPath; -}; -const getPreloadScript = async function (preloadPath) { - let preloadSrc = null; - let preloadError = null; - if (preloadPath) { - try { - if (!isParentDir(await getAppPath(), await realpath(preloadPath))) { - throw new Error('Preload scripts outside of app path are not allowed'); - } - 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/canary/common/api/clipboard.js b/electronasar/canary/common/api/clipboard.js index 21a5197..e0c9921 100644 --- a/electronasar/canary/common/api/clipboard.js +++ b/electronasar/canary/common/api/clipboard.js @@ -1,26 +1,31 @@ -'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'); +'use strict' + +if (process.platform === 'linux' && process.type === 'renderer') { + // On Linux we could not access clipboard in renderer process. + const { getRemoteForUsage } = require('@electron/internal/renderer/remote') + module.exports = getRemoteForUsage('clipboard').clipboard +} else { + const clipboard = process.atomBinding('clipboard') + + // Read/write to find pasteboard over IPC since only main process is notified + // of changes + if (process.platform === 'darwin' && process.type === 'renderer') { + const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + const errorUtils = require('@electron/internal/common/error-utils') + + const invoke = function (command, ...args) { + const [ error, result ] = ipcRenderer.sendSync(command, ...args) + + if (error) { + throw errorUtils.deserialize(error) + } else { + return result + } } + + clipboard.readFindText = (...args) => invoke('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', ...args) + clipboard.writeFindText = (...args) => invoke('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', ...args) + } + + module.exports = clipboard } -module.exports = clipboard; -//# sourceMappingURL=clipboard.js.map \ No newline at end of file diff --git a/electronasar/canary/common/api/deprecate.js b/electronasar/canary/common/api/deprecate.js index 35fee4e..f72939b 100644 --- a/electronasar/canary/common/api/deprecate.js +++ b/electronasar/canary/common/api/deprecate.js @@ -1,175 +1,95 @@ -"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}`); - } - }, - // change the name of a function - function: (fn, newName) => { - const warn = warnOnce(fn.name, newName); - 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; - } - }); +'use strict' + +let deprecationHandler = null + +function warnOnce (oldName, newName) { + let warned = false + const msg = newName + ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.` + : `'${oldName}' is deprecated and will be removed.` + return () => { + if (!warned && !process.noDeprecation) { + warned = true + deprecate.log(msg) } -}; -exports.default = deprecate; -//# sourceMappingURL=deprecate.js.map \ No newline at end of file + } +} + +const deprecate = { + setHandler: (handler) => { deprecationHandler = handler }, + getHandler: () => deprecationHandler, + warn: (oldName, newName) => { + return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`) + }, + log: (message) => { + if (typeof deprecationHandler === 'function') { + deprecationHandler(message) + } else if (process.throwDeprecation) { + throw new Error(message) + } else if (process.traceDeprecation) { + return console.trace(message) + } else { + return console.warn(`(electron) ${message}`) + } + }, + + event: (emitter, oldName, newName) => { + const warn = newName.startsWith('-') /* internal event */ + ? warnOnce(`${oldName} event`) + : warnOnce(`${oldName} event`, `${newName} event`) + return emitter.on(newName, function (...args) { + if (this.listenerCount(oldName) !== 0) { + warn() + this.emit(oldName, ...args) + } + }) + }, + + removeProperty: (o, removedName) => { + // if the property's already been removed, warn about it + if (!(removedName in o)) { + deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`) + } + + // wrap the deprecated property in an accessor to warn + const warn = warnOnce(removedName) + let val = o[removedName] + return Object.defineProperty(o, removedName, { + configurable: true, + get: () => { + warn() + return val + }, + set: newVal => { + warn() + val = newVal + } + }) + }, + + renameProperty: (o, oldName, newName) => { + const warn = warnOnce(oldName, newName) + + // if the new property isn't there yet, + // inject it and warn about it + if ((oldName in o) && !(newName in o)) { + warn() + o[newName] = o[oldName] + } + + // wrap the deprecated property in an accessor to warn + // and redirect to the new property + return Object.defineProperty(o, oldName, { + get: () => { + warn() + return o[newName] + }, + set: value => { + warn() + o[newName] = value + } + }) + } +} + +module.exports = deprecate diff --git a/electronasar/canary/common/api/deprecations.js b/electronasar/canary/common/api/deprecations.js index cf54b25..f4c4d74 100644 --- a/electronasar/canary/common/api/deprecations.js +++ b/electronasar/canary/common/api/deprecations.js @@ -1,9 +1,11 @@ -'use strict'; -const deprecate = require('electron').deprecate; +'use strict' + +const deprecate = require('electron').deprecate + exports.setHandler = function (deprecationHandler) { - deprecate.setHandler(deprecationHandler); -}; + deprecate.setHandler(deprecationHandler) +} + exports.getHandler = function () { - return deprecate.getHandler(); -}; -//# sourceMappingURL=deprecations.js.map \ No newline at end of file + return deprecate.getHandler() +} diff --git a/electronasar/canary/common/api/exports/electron.js b/electronasar/canary/common/api/exports/electron.js index 6ed49e0..49c6702 100644 --- a/electronasar/canary/common/api/exports/electron.js +++ b/electronasar/canary/common/api/exports/electron.js @@ -1,34 +1,31 @@ -'use strict'; -const moduleList = require('@electron/internal/common/api/module-list'); +'use strict' + +const moduleList = require('@electron/internal/common/api/module-list') + exports.memoizedGetter = (getter) => { - /* - * It's ok to leak this value as it would be leaked by the global - * node module cache anyway at `Module._cache`. This memoization - * is dramatically faster than relying on nodes module cache however - */ - let memoizedValue = null; - return () => { - if (memoizedValue === null) { - memoizedValue = getter(); - } - return memoizedValue; - }; -}; + /* + * It's ok to leak this value as it would be leaked by the global + * node module cache anyway at `Module._cache`. This memoization + * is dramatically faster than relying on nodes module cache however + */ + let memoizedValue = null + + return () => { + if (memoizedValue === null) { + memoizedValue = getter() + } + return memoizedValue + } +} + // Attaches properties to |targetExports|. exports.defineProperties = function (targetExports) { - const descriptors = {}; - for (const module of moduleList) { - descriptors[module.name] = { - enumerable: !module.private, - get: exports.memoizedGetter(() => { - 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; - }) - }; + const descriptors = {} + for (const module of moduleList) { + descriptors[module.name] = { + enumerable: !module.private, + get: exports.memoizedGetter(() => require(`@electron/internal/common/api/${module.file}`)) } - return Object.defineProperties(targetExports, descriptors); -}; -//# sourceMappingURL=electron.js.map \ No newline at end of file + } + return Object.defineProperties(targetExports, descriptors) +} diff --git a/electronasar/canary/common/api/is-promise.js b/electronasar/canary/common/api/is-promise.js index df119d4..d6115a1 100644 --- a/electronasar/canary/common/api/is-promise.js +++ b/electronasar/canary/common/api/is-promise.js @@ -1,12 +1,14 @@ -'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 +'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 + ) +} diff --git a/electronasar/canary/common/api/module-list.js b/electronasar/canary/common/api/module-list.js index f88500d..3741f17 100644 --- a/electronasar/canary/common/api/module-list.js +++ b/electronasar/canary/common/api/module-list.js @@ -1,12 +1,12 @@ -'use strict'; +'use strict' + // Common modules, please sort alphabetically module.exports = [ - { name: 'clipboard', file: 'clipboard' }, - { name: 'nativeImage', file: 'native-image' }, - { name: 'shell', file: 'shell' }, - // The internal modules, invisible unless you know their names. - { name: 'deprecate', file: 'deprecate', private: true }, - { name: 'deprecations', file: 'deprecations', private: true }, - { name: 'isPromise', file: 'is-promise', private: true } -]; -//# sourceMappingURL=module-list.js.map \ No newline at end of file + { 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 } +] diff --git a/electronasar/canary/common/api/native-image.js b/electronasar/canary/common/api/native-image.js index 8a373b0..f703f94 100644 --- a/electronasar/canary/common/api/native-image.js +++ b/electronasar/canary/common/api/native-image.js @@ -1,4 +1,3 @@ -'use strict'; -const { nativeImage } = process.electronBinding('native_image'); -module.exports = nativeImage; -//# sourceMappingURL=native-image.js.map \ No newline at end of file +'use strict' + +module.exports = process.atomBinding('native_image') diff --git a/electronasar/canary/common/api/shell.js b/electronasar/canary/common/api/shell.js index 8889c22..6907d90 100644 --- a/electronasar/canary/common/api/shell.js +++ b/electronasar/canary/common/api/shell.js @@ -1,3 +1,3 @@ -'use strict'; -module.exports = process.electronBinding('shell'); -//# sourceMappingURL=shell.js.map \ No newline at end of file +'use strict' + +module.exports = process.atomBinding('shell') diff --git a/electronasar/canary/common/atom-binding-setup.js b/electronasar/canary/common/atom-binding-setup.js index d074421..36954f5 100644 --- a/electronasar/canary/common/atom-binding-setup.js +++ b/electronasar/canary/common/atom-binding-setup.js @@ -1,19 +1,15 @@ -"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; - } - } - }; +'use strict' + +module.exports = function atomBindingSetup (binding, processType) { + return function atomBinding (name) { + try { + return binding(`atom_${processType}_${name}`) + } catch (error) { + if (/No such module/.test(error.message)) { + return binding(`atom_common_${name}`) + } else { + throw error + } + } + } } -exports.electronBindingSetup = electronBindingSetup; -//# sourceMappingURL=atom-binding-setup.js.map \ No newline at end of file diff --git a/electronasar/canary/common/buffer-utils.js b/electronasar/canary/common/buffer-utils.js index 7b08164..8bfdf66 100644 --- a/electronasar/canary/common/buffer-utils.js +++ b/electronasar/canary/common/buffer-utils.js @@ -1,64 +1,66 @@ -'use strict'; +'use strict' + // Note: Don't use destructuring assignment for `Buffer`, or we'll hit a // browserify bug that makes the statement invalid, throwing an error in // sandboxed renderer. -const Buffer = require('buffer').Buffer; +const Buffer = require('buffer').Buffer + const typedArrays = { - Buffer, - ArrayBuffer, - Int8Array, - Uint8Array, - Uint8ClampedArray, - Int16Array, - Uint16Array, - Int32Array, - Uint32Array, - Float32Array, - Float64Array -}; -function getType(value) { - for (const type of Object.keys(typedArrays)) { - if (value instanceof typedArrays[type]) { - return type; - } - } - return null; + Buffer, + ArrayBuffer, + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array } -function getBuffer(value) { - if (value instanceof Buffer) { - return value; - } - else if (value instanceof ArrayBuffer) { - return Buffer.from(value); - } - else { - return Buffer.from(value.buffer, value.byteOffset, value.byteLength); + +function 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; -}; + return ArrayBuffer.isView(value) || value instanceof ArrayBuffer +} + exports.bufferToMeta = function (value) { - return { - type: getType(value), - data: getBuffer(value), - length: value.length - }; -}; + return { + type: getType(value), + data: getBuffer(value), + length: value.length + } +} + exports.metaToBuffer = function (value) { - const constructor = typedArrays[value.type]; - const data = getBuffer(value.data); - if (constructor === Buffer) { - return data; - } - else if (constructor === ArrayBuffer) { - return data.buffer; - } - else if (constructor) { - return new constructor(data.buffer, data.byteOffset, value.length); - } - else { - return data; - } -}; -//# sourceMappingURL=buffer-utils.js.map \ No newline at end of file + 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 + } +} diff --git a/electronasar/canary/common/clipboard-utils.js b/electronasar/canary/common/clipboard-utils.js deleted file mode 100644 index d6d89f0..0000000 --- a/electronasar/canary/common/clipboard-utils.js +++ /dev/null @@ -1,50 +0,0 @@ -'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/canary/common/crash-reporter.js b/electronasar/canary/common/crash-reporter.js index 923feca..eed2c24 100644 --- a/electronasar/canary/common/crash-reporter.js +++ b/electronasar/canary/common/crash-reporter.js @@ -1,78 +1,123 @@ -'use strict'; -const binding = process.electronBinding('crash_reporter'); +'use strict' + +const binding = process.atomBinding('crash_reporter') + +const errorUtils = require('@electron/internal/common/error-utils') + class CrashReporter { - contructor() { - this.productName = null; - this.crashesDirectory = null; + contructor () { + this.productName = null + this.crashesDirectory = null + } + + sendSync (channel, ...args) { + throw new Error('Not implemented') + } + + invoke (command, ...args) { + const [ error, result ] = this.sendSync(command, ...args) + + if (error) { + throw errorUtils.deserialize(error) } - init(options) { - throw new Error('Not implemented'); + + return result + } + + start (options) { + if (options == null) options = {} + + let { + productName, + companyName, + extra, + ignoreSystemCrashHandler, + submitURL, + uploadToServer + } = options + + if (uploadToServer == null) { + uploadToServer = true } - start(options) { - if (options == null) - options = {}; - const { productName, companyName, extra = {}, ignoreSystemCrashHandler = false, submitURL, uploadToServer = true } = options; - if (companyName == null) - throw new Error('companyName is a required option to crashReporter.start'); - if (submitURL == null) - throw new Error('submitURL is a required option to crashReporter.start'); - const ret = this.init({ - submitURL, - productName - }); - this.productName = ret.productName; - this.crashesDirectory = ret.crashesDirectory; - if (extra._productName == null) - extra._productName = ret.productName; - if (extra._companyName == null) - extra._companyName = companyName; - if (extra._version == null) - extra._version = ret.appVersion; - binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra); + + if (ignoreSystemCrashHandler == null) { + ignoreSystemCrashHandler = false } - getLastCrashReport() { - const reports = this.getUploadedReports() - .sort((a, b) => { - const ats = (a && a.date) ? new Date(a.date).getTime() : 0; - const bts = (b && b.date) ? new Date(b.date).getTime() : 0; - return bts - ats; - }); - return (reports.length > 0) ? reports[0] : null; + + if (companyName == null) { + throw new Error('companyName is a required option to crashReporter.start') } - getUploadedReports() { - return binding.getUploadedReports(this.getCrashesDirectory()); + if (submitURL == null) { + throw new Error('submitURL is a required option to crashReporter.start') } - getCrashesDirectory() { - return this.crashesDirectory; + + const ret = this.invoke('ELECTRON_CRASH_REPORTER_INIT', { + submitURL, + productName + }) + + this.productName = ret.productName + this.crashesDirectory = ret.crashesDirectory + this.crashServicePid = ret.crashServicePid + + if (extra == null) extra = {} + if (extra._productName == null) extra._productName = ret.productName + if (extra._companyName == null) extra._companyName = companyName + if (extra._version == null) extra._version = ret.appVersion + + binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra) + } + + getLastCrashReport () { + const reports = this.getUploadedReports() + .sort((a, b) => { + const ats = (a && a.date) ? new Date(a.date).getTime() : 0 + const bts = (b && b.date) ? new Date(b.date).getTime() : 0 + return bts - ats + }) + + return (reports.length > 0) ? reports[0] : null + } + + getUploadedReports () { + return binding.getUploadedReports(this.getCrashesDirectory()) + } + + getCrashesDirectory () { + return this.crashesDirectory + } + + getProductName () { + return this.productName + } + + getUploadToServer () { + if (process.type === 'browser') { + return binding.getUploadToServer() + } else { + throw new Error('getUploadToServer can only be called from the main process') } - 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(); + } + + setUploadToServer (uploadToServer) { + if (process.type === 'browser') { + return binding.setUploadToServer(uploadToServer) + } else { + throw new Error('setUploadToServer can only be called from the main process') } + } + + addExtraParameter (key, value) { + binding.addExtraParameter(key, value) + } + + removeExtraParameter (key) { + binding.removeExtraParameter(key) + } + + getParameters (key, value) { + return binding.getParameters() + } } -module.exports = CrashReporter; -//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file + +module.exports = CrashReporter diff --git a/electronasar/canary/common/error-utils.js b/electronasar/canary/common/error-utils.js index ecfd055..26dd018 100644 --- a/electronasar/canary/common/error-utils.js +++ b/electronasar/canary/common/error-utils.js @@ -1,37 +1,39 @@ -'use strict'; +'use strict' + const constructors = new Map([ - [Error.name, Error], - [EvalError.name, EvalError], - [RangeError.name, RangeError], - [ReferenceError.name, ReferenceError], - [SyntaxError.name, SyntaxError], - [TypeError.name, TypeError], - [URIError.name, URIError] -]); + [Error.name, Error], + [EvalError.name, EvalError], + [RangeError.name, RangeError], + [ReferenceError.name, ReferenceError], + [SyntaxError.name, SyntaxError], + [TypeError.name, TypeError], + [URIError.name, URIError] +]) + exports.deserialize = function (error) { - if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) { - const constructor = constructors.get(error.name); - const deserializedError = new constructor(error.message); - deserializedError.stack = error.stack; - deserializedError.from = error.from; - deserializedError.cause = exports.deserialize(error.cause); - return deserializedError; - } - return error; -}; + if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) { + const constructor = constructors.get(error.name) + const deserializedError = new constructor(error.message) + deserializedError.stack = error.stack + deserializedError.from = error.from + deserializedError.cause = exports.deserialize(error.cause) + return deserializedError + } + return error +} + exports.serialize = function (error) { - if (error instanceof Error) { - // Errors get lost, because: JSON.stringify(new Error('Message')) === {} - // Take the serializable properties and construct a generic object - return { - message: error.message, - stack: error.stack, - name: error.name, - from: process.type, - cause: exports.serialize(error.cause), - __ELECTRON_SERIALIZED_ERROR__: true - }; + if (error 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 + } + return error +} diff --git a/electronasar/canary/common/init.js b/electronasar/canary/common/init.js index c90468d..a69d962 100644 --- a/electronasar/canary/common/init.js +++ b/electronasar/canary/common/init.js @@ -1,9 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const timers = require("timers"); -const util = require("util"); -const atom_binding_setup_1 = require("@electron/internal/common/atom-binding-setup"); -process.electronBinding = atom_binding_setup_1.electronBindingSetup(process._linkedBinding, process.type); +'use strict' + +const timers = require('timers') +const util = require('util') + +process.atomBinding = require('@electron/internal/common/atom-binding-setup')(process.binding, process.type) + // setImmediate and process.nextTick makes use of uv_check and uv_prepare to // run the callbacks, however since we only run uv loop on requests, the // callbacks wouldn't be called until something else activated the uv loop, @@ -11,48 +12,42 @@ process.electronBinding = atom_binding_setup_1.electronBindingSetup(process._lin // 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 wrap(func, function (func) { + return function () { + process.activateUvLoop() + return func.apply(this, arguments) } - return wrapped; + }) } -process.nextTick = wrapWithActivateUvLoop(process.nextTick); -global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate); -global.clearImmediate = timers.clearImmediate; + +function wrap (func, wrapper) { + const wrapped = wrapper(func) + if (func[util.promisify.custom]) { + wrapped[util.promisify.custom] = wrapper(func[util.promisify.custom]) + } + return wrapped +} + +process.nextTick = wrapWithActivateUvLoop(process.nextTick) + +global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) +global.clearImmediate = timers.clearImmediate + if (process.type === 'browser') { - // setTimeout needs to update the polling timeout of the event loop, when - // called under Chromium's event loop the node's event loop won't get a chance - // to update the timeout, so we have to force the node's event loop to - // recalculate the timeout in browser process. - global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout); - global.setInterval = wrapWithActivateUvLoop(timers.setInterval); + // setTimeout needs to update the polling timeout of the event loop, when + // called under Chromium's event loop the node's event loop won't get a chance + // to update the timeout, so we have to force the node's event loop to + // recalculate the timeout in browser process. + global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) + global.setInterval = wrapWithActivateUvLoop(timers.setInterval) } + if (process.platform === 'win32') { - // Always returns EOF for stdin stream. - const { Readable } = require('stream'); - const stdin = new Readable(); - stdin.push(null); - Object.defineProperty(process, 'stdin', { - configurable: false, - enumerable: true, - get() { - return stdin; - } - }); + // Always returns EOF for stdin stream. + const { Readable } = require('stream') + const stdin = new Readable() + stdin.push(null) + process.__defineGetter__('stdin', function () { + return stdin + }) } -//# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/electronasar/canary/common/parse-features-string.js b/electronasar/canary/common/parse-features-string.js index 26ca6ff..955ab8f 100644 --- a/electronasar/canary/common/parse-features-string.js +++ b/electronasar/canary/common/parse-features-string.js @@ -1,20 +1,21 @@ -'use strict'; +'use strict' + // parses a feature string that has the format used in window.open() // - `features` input string // - `emit` function(key, value) - called for each parsed KV -module.exports = function parseFeaturesString(features, emit) { - features = `${features}`.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 +module.exports = function parseFeaturesString (features, emit) { + features = `${features}` + // split the string by ',' + features.split(/,\s*/).forEach((feature) => { + // expected form is either a key by itself or a key/value pair in the form of + // 'key=value' + let [key, value] = feature.split(/\s*=/) + if (!key) return + + // interpret the value as a boolean, if possible + value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value + + // emit the parsed pair + emit(key, value) + }) +} diff --git a/electronasar/canary/common/path-utils.js b/electronasar/canary/common/path-utils.js deleted file mode 100644 index 42ec67e..0000000 --- a/electronasar/canary/common/path-utils.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const path = require("path"); -exports.isParentDir = function (parent, dir) { - const relative = path.relative(parent, dir); - return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); -}; -//# sourceMappingURL=path-utils.js.map \ No newline at end of file diff --git a/electronasar/canary/common/reset-search-paths.js b/electronasar/canary/common/reset-search-paths.js index acb19bb..5890592 100644 --- a/electronasar/canary/common/reset-search-paths.js +++ b/electronasar/canary/common/reset-search-paths.js @@ -1,44 +1,45 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const path = require("path"); -const Module = require('module'); +'use strict' + +const path = require('path') +const Module = require('module') + // Clear Node's global search paths. -Module.globalPaths.length = 0; +Module.globalPaths.length = 0 + // Clear current and parent(init.js)'s search paths. -module.paths = []; -module.parent.paths = []; +module.paths = [] +module.parent.paths = [] + // Prevent Node from adding paths outside this app to search paths. -const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep; -const originalNodeModulePaths = Module._nodeModulePaths; +const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep +const originalNodeModulePaths = Module._nodeModulePaths Module._nodeModulePaths = function (from) { - const paths = originalNodeModulePaths(from); - const fromPath = path.resolve(from) + path.sep; - // If "from" is outside the app then we do nothing. - if (fromPath.startsWith(resourcesPathWithTrailingSlash)) { - return paths.filter(function (candidate) { - return candidate.startsWith(resourcesPathWithTrailingSlash); - }); - } - else { - return paths; - } -}; -const BASE_INTERNAL_PATH = path.resolve(__dirname, '..'); -const INTERNAL_MODULE_PREFIX = '@electron/internal/'; + const paths = originalNodeModulePaths(from) + const fromPath = path.resolve(from) + path.sep + // If "from" is outside the app then we do nothing. + if (fromPath.startsWith(resourcesPathWithTrailingSlash)) { + return paths.filter(function (candidate) { + return candidate.startsWith(resourcesPathWithTrailingSlash) + }) + } else { + return paths + } +} + +const BASE_INTERNAL_PATH = path.resolve(__dirname, '..') +const INTERNAL_MODULE_PREFIX = '@electron/internal/' + // Patch Module._resolveFilename to always require the Electron API when // require('electron') is done. -const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js'); -const originalResolveFilename = Module._resolveFilename; +const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js') +const originalResolveFilename = Module._resolveFilename Module._resolveFilename = function (request, parent, isMain) { - if (request === 'electron') { - return electronPath; - } - else if (request.startsWith(INTERNAL_MODULE_PREFIX) && request.length > INTERNAL_MODULE_PREFIX.length) { - const slicedRequest = request.slice(INTERNAL_MODULE_PREFIX.length); - return path.resolve(BASE_INTERNAL_PATH, `${slicedRequest}${slicedRequest.endsWith('.js') ? '' : '.js'}`); - } - else { - return originalResolveFilename(request, parent, isMain); - } -}; -//# sourceMappingURL=reset-search-paths.js.map \ No newline at end of file + 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) + } +} diff --git a/electronasar/canary/common/web-view-methods.js b/electronasar/canary/common/web-view-methods.js index 28811c9..0fdaebb 100644 --- a/electronasar/canary/common/web-view-methods.js +++ b/electronasar/canary/common/web-view-methods.js @@ -1,69 +1,70 @@ -'use strict'; +'use strict' + // Public-facing API methods. exports.syncMethods = new Set([ - 'getURL', - 'loadURL', - 'getTitle', - 'isLoading', - 'isLoadingMainFrame', - 'isWaitingForResponse', - 'stop', - 'reload', - 'reloadIgnoringCache', - 'canGoBack', - 'canGoForward', - 'canGoToOffset', - 'clearHistory', - 'goBack', - 'goForward', - 'goToIndex', - 'goToOffset', - 'isCrashed', - 'setUserAgent', - 'getUserAgent', - 'openDevTools', - 'closeDevTools', - 'isDevToolsOpened', - 'isDevToolsFocused', - 'inspectElement', - 'setAudioMuted', - 'isAudioMuted', - 'isCurrentlyAudible', - 'undo', - 'redo', - 'cut', - 'copy', - 'paste', - 'pasteAndMatchStyle', - 'delete', - 'selectAll', - 'unselect', - 'replace', - 'replaceMisspelling', - 'findInPage', - 'stopFindInPage', - 'downloadURL', - 'inspectSharedWorker', - 'inspectServiceWorker', - 'showDefinitionForSelection', - 'getZoomFactor', - 'getZoomLevel', - 'setZoomFactor', - 'setZoomLevel', - 'sendImeEvent' -]); + 'getURL', + 'loadURL', + 'getTitle', + 'isLoading', + 'isLoadingMainFrame', + 'isWaitingForResponse', + 'stop', + 'reload', + 'reloadIgnoringCache', + 'canGoBack', + 'canGoForward', + 'canGoToOffset', + 'clearHistory', + 'goBack', + 'goForward', + 'goToIndex', + 'goToOffset', + 'isCrashed', + 'setUserAgent', + 'getUserAgent', + 'openDevTools', + 'closeDevTools', + 'isDevToolsOpened', + 'isDevToolsFocused', + 'inspectElement', + 'setAudioMuted', + 'isAudioMuted', + 'isCurrentlyAudible', + 'undo', + 'redo', + 'cut', + 'copy', + 'paste', + 'pasteAndMatchStyle', + 'delete', + 'selectAll', + 'unselect', + 'replace', + 'replaceMisspelling', + 'findInPage', + 'stopFindInPage', + 'downloadURL', + 'inspectServiceWorker', + 'showDefinitionForSelection', + 'setZoomFactor', + 'setZoomLevel', + 'sendImeEvent' +]) + exports.asyncCallbackMethods = new Set([ - 'insertCSS', - 'insertText', - 'send', - 'sendInputEvent', - 'setLayoutZoomLevelLimits', - 'setVisualZoomLevelLimits', - 'print' -]); + 'insertCSS', + 'insertText', + 'send', + 'sendInputEvent', + 'setLayoutZoomLevelLimits', + 'setVisualZoomLevelLimits', + 'getZoomFactor', + 'getZoomLevel', + 'print', + 'printToPDF' +]) + exports.asyncPromiseMethods = new Set([ - 'capturePage', - 'executeJavaScript', - 'printToPDF' -]); -//# sourceMappingURL=web-view-methods.js.map \ No newline at end of file + 'capturePage', + 'executeJavaScript' +]) diff --git a/electronasar/canary/renderer/api/crash-reporter.js b/electronasar/canary/renderer/api/crash-reporter.js index 59ea3b5..5bbd90e 100644 --- a/electronasar/canary/renderer/api/crash-reporter.js +++ b/electronasar/canary/renderer/api/crash-reporter.js @@ -1,10 +1,12 @@ -'use strict'; -const CrashReporter = require('@electron/internal/common/crash-reporter'); -const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +'use strict' + +const CrashReporter = require('@electron/internal/common/crash-reporter') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + class CrashReporterRenderer extends CrashReporter { - init(options) { - return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options); - } + sendSync (channel, ...args) { + return ipcRenderer.sendSync(channel, ...args) + } } -module.exports = new CrashReporterRenderer(); -//# sourceMappingURL=crash-reporter.js.map \ No newline at end of file + +module.exports = new CrashReporterRenderer() diff --git a/electronasar/canary/renderer/api/desktop-capturer.js b/electronasar/canary/renderer/api/desktop-capturer.js index f77c21a..e4ece82 100644 --- a/electronasar/canary/renderer/api/desktop-capturer.js +++ b/electronasar/canary/renderer/api/desktop-capturer.js @@ -1,38 +1,48 @@ -'use strict'; -const { nativeImage, deprecate } = require('electron'); -const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils'); +'use strict' + +const { nativeImage } = require('electron') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + +const includes = [].includes +let currentId = 0 + +const incrementId = () => { + currentId += 1 + return currentId +} + // |options.types| can't be empty and must be an array -function isValid(options) { - const types = options ? options.types : undefined; - return Array.isArray(types); +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 - })); + +exports.getSources = function (options, callback) { + if (!isValid(options)) return callback(new Error('Invalid options')) + const captureWindow = includes.call(options.types, 'window') + const captureScreen = includes.call(options.types, 'screen') + + if (options.thumbnailSize == null) { + options.thumbnailSize = { + width: 150, + height: 150 + } + } + + const id = incrementId() + ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id) + return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => { + callback(null, (() => { + const results = [] + sources.forEach(source => { + results.push({ + id: source.id, + name: source.name, + thumbnail: nativeImage.createFromDataURL(source.thumbnail), + display_id: source.display_id + }) + }) + return results + })()) + }) } -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/canary/renderer/api/exports/electron.js b/electronasar/canary/renderer/api/exports/electron.js index 3702ebe..63aa8d4 100644 --- a/electronasar/canary/renderer/api/exports/electron.js +++ b/electronasar/canary/renderer/api/exports/electron.js @@ -1,21 +1,23 @@ -'use strict'; -const common = require('@electron/internal/common/api/exports/electron'); -const moduleList = require('@electron/internal/renderer/api/module-list'); +'use strict' + +const common = require('@electron/internal/common/api/exports/electron') +const moduleList = require('@electron/internal/renderer/api/module-list') + // Import common modules. -common.defineProperties(exports); -for (const { name, file, enabled = true, private: isPrivate = false } of moduleList) { - if (!enabled) { - continue; - } - Object.defineProperty(exports, name, { - enumerable: !isPrivate, - get: common.memoizedGetter(() => { - 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; - }) - }); +common.defineProperties(exports) + +for (const { + name, + file, + enabled = true, + private: isPrivate = false +} of moduleList) { + if (!enabled) { + continue + } + + Object.defineProperty(exports, name, { + enumerable: !isPrivate, + get: common.memoizedGetter(() => require(`@electron/internal/renderer/api/${file}`)) + }) } -//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/electronasar/canary/renderer/api/ipc-renderer.js b/electronasar/canary/renderer/api/ipc-renderer.js index fa5e2b9..7705f9f 100644 --- a/electronasar/canary/renderer/api/ipc-renderer.js +++ b/electronasar/canary/renderer/api/ipc-renderer.js @@ -1,23 +1,30 @@ -'use strict'; -const { ipc } = process.electronBinding('ipc'); -const v8Util = process.electronBinding('v8_util'); +'use strict' + +const binding = process.atomBinding('ipc') +const v8Util = process.atomBinding('v8_util') + // Created by init.js. -const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); -const internal = false; -ipcRenderer.send = function (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); -}; +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') +const internal = false + +ipcRenderer.send = function (...args) { + return binding.send('ipc-message', args) +} + +ipcRenderer.sendSync = function (...args) { + return binding.sendSync('ipc-message-sync', args)[0] +} + +ipcRenderer.sendToHost = function (...args) { + return binding.send('ipc-message-host', args) +} + ipcRenderer.sendTo = function (webContentsId, channel, ...args) { - return ipc.sendTo(internal, false, webContentsId, channel, args); -}; + return binding.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 + return binding.sendTo(internal, true, webContentsId, channel, args) +} + +module.exports = ipcRenderer diff --git a/electronasar/canary/renderer/api/module-list.js b/electronasar/canary/renderer/api/module-list.js index c27561b..b9bba8a 100644 --- a/electronasar/canary/renderer/api/module-list.js +++ b/electronasar/canary/renderer/api/module-list.js @@ -1,18 +1,21 @@ -'use strict'; -const features = process.electronBinding('features'); -const v8Util = process.electronBinding('v8_util'); -const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule'); +'use strict' + +const features = process.atomBinding('features') +const v8Util = process.atomBinding('v8_util') + +const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule') + // Renderer side modules, please sort alphabetically. // A module is `enabled` if there is no explicit condition defined. module.exports = [ - { name: 'crashReporter', file: 'crash-reporter', enabled: true }, - { - name: 'desktopCapturer', - file: 'desktop-capturer', - enabled: features.isDesktopCapturerEnabled() - }, - { name: 'ipcRenderer', file: 'ipc-renderer' }, - { name: 'remote', file: 'remote', enabled: enableRemoteModule }, - { name: 'webFrame', file: 'web-frame' } -]; -//# sourceMappingURL=module-list.js.map \ No newline at end of file + { name: 'crashReporter', file: 'crash-reporter', enabled: true }, + { + name: 'desktopCapturer', + file: 'desktop-capturer', + enabled: features.isDesktopCapturerEnabled() + }, + { name: 'ipcRenderer', file: 'ipc-renderer' }, + { name: 'remote', file: 'remote', enabled: enableRemoteModule }, + { name: 'screen', file: 'screen' }, + { name: 'webFrame', file: 'web-frame' } +] diff --git a/electronasar/canary/renderer/api/remote.js b/electronasar/canary/renderer/api/remote.js index 57a75d3..036ebd8 100644 --- a/electronasar/canary/renderer/api/remote.js +++ b/electronasar/canary/renderer/api/remote.js @@ -1,340 +1,355 @@ -'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(); +'use strict' + +const v8Util = process.atomBinding('v8_util') +const { isPromise } = require('electron') +const resolvePromise = Promise.resolve.bind(Promise) + +const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry') +const bufferUtils = require('@electron/internal/common/buffer-utils') +const errorUtils = require('@electron/internal/common/error-utils') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + +const callbacksRegistry = new CallbacksRegistry() +const remoteObjectCache = v8Util.createIDWeakMap() + // An unique ID that can represent current context. -const contextId = v8Util.getHiddenValue(global, 'contextId'); +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); -}); + const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' + ipcRenderer.sendSync(command, contextId) +}) + // Convert the arguments object into an array of meta data. -function wrapArgs(args, visited = new Set()) { - const valueToMeta = (value) => { - // Check for circular reference. - if (visited.has(value)) { - return { - type: 'value', - value: null - }; +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) + }) } - if (Array.isArray(value)) { - visited.add(value); - const meta = { - type: 'array', - value: wrapArgs(value, visited) - }; - visited.delete(value); - return meta; + } else if (v8Util.getHiddenValue(value, 'atomId')) { + return { + type: 'remote-object', + id: v8Util.getHiddenValue(value, 'atomId') } - 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); + } + + 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; +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' } - 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; - }; - } + const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args)) + return metaToValue(ret) + } + + let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name) + + descriptor.get = () => { + descriptorFunction.ref = ref // The member should reference its object. + return descriptorFunction + } + // Enable monkey-patch the method + descriptor.set = (value) => { + descriptorFunction = value + return value + } + descriptor.configurable = true + } else if (member.type === 'get') { + descriptor.get = () => { + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name) + return metaToValue(meta) + } + + if (member.writable) { + descriptor.set = (value) => { + const args = wrapArgs([value]) + const command = 'ELECTRON_BROWSER_MEMBER_SET' + const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args) + if (meta != null) metaToValue(meta) + return value } - Object.defineProperty(object, member.name, descriptor); + } } + + Object.defineProperty(object, member.name, descriptor) + } } + // Populate object's prototype from descriptor. // This matches |getObjectPrototype| in rpc-server. -function setObjectPrototype(ref, object, metaId, descriptor) { - if (descriptor === null) - return; - const proto = {}; - setObjectMembers(ref, proto, metaId, descriptor.members); - setObjectPrototype(ref, proto, metaId, descriptor.proto); - Object.setPrototypeOf(object, proto); +function setObjectPrototype (ref, object, metaId, descriptor) { + if (descriptor === null) return + const proto = {} + setObjectMembers(ref, proto, metaId, descriptor.members) + setObjectPrototype(ref, proto, metaId, descriptor.proto) + Object.setPrototypeOf(object, proto) } + // Wrap function in Proxy for accessing remote properties -function proxyFunctionProperties(remoteMemberFunction, metaId, name) { - let loaded = false; - // Lazily load function properties - const loadRemoteProperties = () => { - if (loaded) - return; - loaded = true; - const command = 'ELECTRON_BROWSER_MEMBER_GET'; - const meta = 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); - } - }); +function proxyFunctionProperties (remoteMemberFunction, metaId, name) { + let loaded = false + + // Lazily load function properties + const loadRemoteProperties = () => { + if (loaded) return + loaded = true + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, contextId, metaId, name) + setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) + } + + return new Proxy(remoteMemberFunction, { + set: (target, property, value, receiver) => { + if (property !== 'ref') loadRemoteProperties() + target[property] = value + return true + }, + get: (target, property, receiver) => { + if (!target.hasOwnProperty(property)) loadRemoteProperties() + const value = target[property] + if (property === 'toString' && typeof value === 'function') { + return value.bind(target) + } + return value + }, + ownKeys: (target) => { + loadRemoteProperties() + return Object.getOwnPropertyNames(target) + }, + getOwnPropertyDescriptor: (target, property) => { + const descriptor = Object.getOwnPropertyDescriptor(target, property) + if (descriptor) return descriptor + loadRemoteProperties() + return Object.getOwnPropertyDescriptor(target, property) + } + }) } + // Convert meta data from browser into real value. -function metaToValue(meta) { - const types = { - value: () => meta.value, - array: () => meta.members.map((member) => metaToValue(member)), - buffer: () => bufferUtils.metaToBuffer(meta.value), - promise: () => resolvePromise({ then: metaToValue(meta.then) }), - error: () => metaToPlainObject(meta), - date: () => new Date(meta.value), - exception: () => { throw errorUtils.deserialize(meta.value); } - }; - if (meta.type in types) { - return types[meta.type](); +function metaToValue (meta) { + const types = { + value: () => meta.value, + array: () => meta.members.map((member) => metaToValue(member)), + buffer: () => bufferUtils.metaToBuffer(meta.value), + promise: () => resolvePromise({ then: metaToValue(meta.then) }), + error: () => metaToPlainObject(meta), + date: () => new Date(meta.value), + exception: () => { throw errorUtils.deserialize(meta.value) } + } + + if (meta.type in types) { + return types[meta.type]() + } else { + let ret + if (remoteObjectCache.has(meta.id)) { + return remoteObjectCache.get(meta.id) } - 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' } - // 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; + const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args)) + return metaToValue(obj) + } + ret = remoteFunction + } else { + ret = {} } + + setObjectMembers(ret, ret, meta.id, meta.members) + setObjectPrototype(ret, ret, meta.id, meta.proto) + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + // Track delegate obj's lifetime & tell browser to clean up when object is GCed. + v8Util.setRemoteObjectFreer(ret, contextId, meta.id) + v8Util.setHiddenValue(ret, 'atomId', meta.id) + remoteObjectCache.set(meta.id, ret) + return ret + } } + // Construct a plain object from the meta. -function metaToPlainObject(meta) { - const obj = (() => meta.type === 'error' ? new Error() : {})(); - for (let i = 0; i < meta.members.length; i++) { - const { name, value } = meta.members[i]; - obj[name] = value; +function metaToPlainObject (meta) { + const obj = (() => meta.type === 'error' ? new Error() : {})() + for (let i = 0; i < meta.members.length; i++) { + const { name, value } = meta.members[i] + obj[name] = value + } + return obj +} + +function handleMessage (channel, handler) { + ipcRenderer.on(channel, (event, passedContextId, id, ...args) => { + if (passedContextId === contextId) { + handler(id, ...args) + } else { + // Message sent to an un-exist context, notify the error to main process. + ipcRenderer.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id) } - return obj; -} -function handleMessage(channel, handler) { - ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => { - if (passedContextId === contextId) { - handler(id, ...args); - } - else { - // Message sent to an un-exist context, notify the error to main process. - ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id); - } - }); + }) } + // Browser calls a callback in renderer. handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => { - callbacksRegistry.apply(id, metaToValue(args)); -}); + callbacksRegistry.apply(id, metaToValue(args)) +}) + // A callback in browser is released. handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => { - callbacksRegistry.remove(id); -}); + callbacksRegistry.remove(id) +}) + exports.require = (module) => { - const command = 'ELECTRON_BROWSER_REQUIRE'; - const meta = ipcRendererInternal.sendSync(command, contextId, module); - return metaToValue(meta); -}; + const command = 'ELECTRON_BROWSER_REQUIRE' + const meta = ipcRenderer.sendSync(command, contextId, module) + return metaToValue(meta) +} + // Alias to remote.require('electron').xxx. exports.getBuiltin = (module) => { - const command = 'ELECTRON_BROWSER_GET_BUILTIN'; - const meta = ipcRendererInternal.sendSync(command, contextId, module); - return metaToValue(meta); -}; + const command = 'ELECTRON_BROWSER_GET_BUILTIN' + const meta = ipcRenderer.sendSync(command, contextId, module) + return metaToValue(meta) +} + exports.getCurrentWindow = () => { - const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'; - const meta = ipcRendererInternal.sendSync(command, contextId); - return metaToValue(meta); -}; + const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' + const meta = ipcRenderer.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); -}; + return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId)) +} + // Get a global object in browser. exports.getGlobal = (name) => { - const command = 'ELECTRON_BROWSER_GLOBAL'; - const meta = ipcRendererInternal.sendSync(command, contextId, name); - return metaToValue(meta); -}; + const command = 'ELECTRON_BROWSER_GLOBAL' + const meta = ipcRenderer.sendSync(command, contextId, name) + return metaToValue(meta) +} + // Get the process object in browser. -exports.__defineGetter__('process', () => exports.getGlobal('process')); +exports.__defineGetter__('process', () => exports.getGlobal('process')) + // Create a function that will return the specified value when called in browser. exports.createFunctionWithReturnValue = (returnValue) => { - const func = () => returnValue; - v8Util.setHiddenValue(func, 'returnValue', true); - return func; -}; + const func = () => returnValue + v8Util.setHiddenValue(func, 'returnValue', true) + return func +} + // Get the guest WebContents from guestInstanceId. exports.getGuestWebContents = (guestInstanceId) => { - const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'; - const meta = ipcRendererInternal.sendSync(command, contextId, guestInstanceId); - return metaToValue(meta); -}; + const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS' + const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId) + return metaToValue(meta) +} + const addBuiltinProperty = (name) => { - Object.defineProperty(exports, name, { - get: () => exports.getBuiltin(name) - }); -}; -const browserModules = require('@electron/internal/common/api/module-list').concat(require('@electron/internal/browser/api/module-list')); + 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 + .filter((m) => !m.private) + .map((m) => m.name) + .forEach(addBuiltinProperty) diff --git a/electronasar/canary/renderer/api/screen.js b/electronasar/canary/renderer/api/screen.js new file mode 100644 index 0000000..6cae10d --- /dev/null +++ b/electronasar/canary/renderer/api/screen.js @@ -0,0 +1,4 @@ +'use strict' + +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('screen').screen diff --git a/electronasar/canary/renderer/api/web-frame.js b/electronasar/canary/renderer/api/web-frame.js index 7f902cb..534a98a 100644 --- a/electronasar/canary/renderer/api/web-frame.js +++ b/electronasar/canary/renderer/api/web-frame.js @@ -1,82 +1,67 @@ -"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 }); - } +'use strict' + +const { EventEmitter } = require('events') +const binding = process.atomBinding('web_frame') + +class WebFrame extends EventEmitter { + constructor (context) { + super() + + this.context = context + // Lots of webview would subscribe to webFrame's events. + this.setMaxListeners(0) + } + + findFrameByRoutingId (...args) { + return getWebFrame(binding._findFrameByRoutingId(this.context, ...args)) + } + + getFrameForSelector (...args) { + return getWebFrame(binding._getFrameForSelector(this.context, ...args)) + } + + findFrameByName (...args) { + return getWebFrame(binding._findFrameByName(this.context, ...args)) + } + + get opener () { + return getWebFrame(binding._getOpener(this.context)) + } + + get parent () { + return getWebFrame(binding._getParent(this.context)) + } + + get top () { + return getWebFrame(binding._getTop(this.context)) + } + + get firstChild () { + return getWebFrame(binding._getFirstChild(this.context)) + } + + get nextSibling () { + return getWebFrame(binding._getNextSibling(this.context)) + } + + get routingId () { + return binding._getRoutingId(this.context) + } } + // Populate the methods. for (const name in binding) { - if (!name.startsWith('_')) { // some methods are 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); - }; + if (!name.startsWith('_')) { // some methods are manully populated above + WebFrame.prototype[name] = function (...args) { + return binding[name](this.context, ...args) } + } } + // Helper to return WebFrame or null depending on context. -// TODO(zcbenz): Consider returning same WebFrame for the same frame. -function getWebFrame(context) { - return context ? new WebFrame(context) : null; +// TODO(zcbenz): Consider returning same WebFrame for the same context. +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 + +module.exports = new WebFrame(window) diff --git a/electronasar/canary/renderer/callbacks-registry.js b/electronasar/canary/renderer/callbacks-registry.js index 20060c1..6a1c63b 100644 --- a/electronasar/canary/renderer/callbacks-registry.js +++ b/electronasar/canary/renderer/callbacks-registry.js @@ -1,52 +1,59 @@ -'use strict'; -const v8Util = process.electronBinding('v8_util'); +'use strict' + +const v8Util = process.atomBinding('v8_util') + class CallbacksRegistry { - constructor() { - this.nextId = 0; - this.callbacks = {}; + 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 } - 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]; - } + 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 + +module.exports = CallbacksRegistry diff --git a/electronasar/canary/renderer/chrome-api.js b/electronasar/canary/renderer/chrome-api.js index e5aa6bb..361c735 100644 --- a/electronasar/canary/renderer/chrome-api.js +++ b/electronasar/canary/renderer/chrome-api.js @@ -1,174 +1,190 @@ -"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'); +'use strict' + +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const Event = require('@electron/internal/renderer/extensions/event') +const url = require('url') + class Tab { - constructor(tabId) { - this.id = tabId; - } + constructor (tabId) { + this.id = tabId + } } + class MessageSender { - constructor(tabId, extensionId) { - this.tab = tabId ? new Tab(tabId) : null; - this.id = extensionId; - this.url = `chrome-extension://${extensionId}`; - } + constructor (tabId, extensionId) { + this.tab = tabId ? new Tab(tabId) : null + this.id = extensionId + this.url = `chrome-extension://${extensionId}` + } } + class Port { - constructor(tabId, portId, extensionId, name) { - this.tabId = tabId; - this.portId = portId; - this.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(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}`, message); - } - _onDisconnect() { - this.disconnected = true; - ipc_renderer_internal_1.ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`); - this.onDisconnect.emit(); - } + constructor (tabId, portId, extensionId, name) { + this.tabId = tabId + this.portId = portId + this.disconnected = false + + this.name = name + this.onDisconnect = new Event() + this.onMessage = new Event() + this.sender = new MessageSender(tabId, extensionId) + + ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => { + this._onDisconnect() + }) + ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => { + const sendResponse = function () { console.error('sendResponse is not implemented') } + this.onMessage.emit(message, this.sender, sendResponse) + }) + } + + disconnect () { + if (this.disconnected) return + + ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`) + this._onDisconnect() + } + + postMessage (message) { + ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message) + } + + _onDisconnect () { + this.disconnected = true + ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`) + this.onDisconnect.emit() + } } + // Inject chrome API to the |context| -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 = function (extensionId, isBackgroundPage, context) { + const chrome = context.chrome = context.chrome || {} + let originResultID = 1 + + ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => { + chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name)) + }) + + ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message, resultID) => { + chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), (messageResult) => { + ipcRenderer.send(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, messageResult) + }) + }) + + ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => { + chrome.tabs.onCreated.emit(new Tab(tabId)) + }) + + ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => { + chrome.tabs.onRemoved.emit(tabId) + }) + + chrome.runtime = { + id: extensionId, + + getURL: function (path) { + return url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: extensionId, + pathname: path + }) + }, + + connect (...args) { + if (isBackgroundPage) { + console.error('chrome.runtime.connect is not supported in background page') + return + } + + // Parse the optional args. + let targetExtensionId = extensionId + let connectInfo = { name: '' } + if (args.length === 1) { + connectInfo = args[0] + } else if (args.length === 2) { + [targetExtensionId, connectInfo] = args + } + + const { tabId, portId } = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo) + return new Port(tabId, portId, extensionId, connectInfo.name) + }, + + sendMessage (...args) { + if (isBackgroundPage) { + console.error('chrome.runtime.sendMessage is not supported in background page') + return + } + + // Parse the optional args. + let targetExtensionId = extensionId + let message + if (args.length === 1) { + message = args[0] + } else if (args.length === 2) { + // A case of not provide extension-id: (message, responseCallback) + if (typeof args[1] === 'function') { + ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[1](result)) + message = args[0] + } else { + [targetExtensionId, message] = args + } + } else { + console.error('options is not supported') + ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[2](result)) + } + + ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message, originResultID) + originResultID++ + }, + + onConnect: new Event(), + onMessage: new Event(), + onInstalled: new Event() + } + + chrome.tabs = { + executeScript (tabId, details, resultCallback) { + if (resultCallback) { + ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${originResultID}`, (event, result) => resultCallback([result])) + } + ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', originResultID, tabId, extensionId, details) + originResultID++ + }, + + sendMessage (tabId, message, options, responseCallback) { + if (responseCallback) { + ipcRenderer.on(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, (event, result) => responseCallback(result)) + } + ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message, originResultID) + originResultID++ + }, + + onUpdated: new Event(), + onCreated: new Event(), + onRemoved: new Event() + } + + chrome.extension = { + getURL: chrome.runtime.getURL, + connect: chrome.runtime.connect, + onConnect: chrome.runtime.onConnect, + sendMessage: chrome.runtime.sendMessage, + onMessage: chrome.runtime.onMessage + } + + chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId) + + chrome.pageAction = { + show () {}, + hide () {}, + setTitle () {}, + getTitle () {}, + setIcon () {}, + setPopup () {}, + getPopup () {} + } + + chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId) + chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup() } -exports.injectTo = injectTo; -//# sourceMappingURL=chrome-api.js.map \ No newline at end of file diff --git a/electronasar/canary/renderer/content-scripts-injector.js b/electronasar/canary/renderer/content-scripts-injector.js index a1c5b1e..5091d84 100644 --- a/electronasar/canary/renderer/content-scripts-injector.js +++ b/electronasar/canary/renderer/content-scripts-injector.js @@ -1,109 +1,101 @@ -"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, '\\$&'); -}; +'use strict' + +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const { runInThisContext } = require('vm') + // Check whether pattern matches. // https://developer.chrome.com/extensions/match_patterns const matchesPattern = function (pattern) { - if (pattern === '') - return true; - const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`); - const url = `${location.protocol}//${location.host}${location.pathname}`; - return url.match(regexp); -}; + if (pattern === '') return true + const regexp = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`) + const url = `${location.protocol}//${location.host}${location.pathname}` + return url.match(regexp) +} + // Run the code with chrome API integrated. const runContentScript = function (extensionId, url, code) { - // 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 context = {} + require('@electron/internal/renderer/chrome-api').injectTo(extensionId, false, context) + const wrapper = `((chrome) => {\n ${code}\n })` + const compiledWrapper = runInThisContext(wrapper, { + filename: url, + lineOffset: 1, + displayErrors: true + }) + return compiledWrapper.call(this, context.chrome) +} + const runAllContentScript = function (scripts, extensionId) { - for (const { url, code } of scripts) { - runContentScript.call(window, extensionId, url, code); - } -}; + 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); + const wrapper = `((code) => { + function init() { + const styleElement = document.createElement('style'); + styleElement.textContent = code; + document.head.append(styleElement); } -}; + document.addEventListener('DOMContentLoaded', init); + })` + const compiledWrapper = runInThisContext(wrapper, { + filename: url, + lineOffset: 1, + displayErrors: true + }) + return compiledWrapper.call(this, code) +} + +const runAllStylesheet = function (css) { + for (const { url, code } of css) { + runStylesheet.call(window, url, code) + } +} + // Run injected scripts. // https://developer.chrome.com/extensions/content_scripts const injectContentScript = function (extensionId, script) { - if (!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.matches.some(matchesPattern)) return + + if (script.js) { + const fire = runAllContentScript.bind(window, script.js, extensionId) + if (script.runAt === 'document_start') { + process.once('document-start', fire) + } else if (script.runAt === 'document_end') { + process.once('document-end', fire) + } else { + document.addEventListener('DOMContentLoaded', fire) } - if (script.css) { - const fire = runAllStylesheet.bind(window, script.css); - if (script.runAt === 'document_start') { - process.once('document-start', fire); - } - else if (script.runAt === 'document_end') { - process.once('document-end', fire); - } - else { - document.addEventListener('DOMContentLoaded', fire); - } + } + + if (script.css) { + const fire = runAllStylesheet.bind(window, script.css) + if (script.runAt === 'document_start') { + process.once('document-start', fire) + } else if (script.runAt === 'document_end') { + process.once('document-end', fire) + } else { + document.addEventListener('DOMContentLoaded', fire) } -}; + } +} + // Handle the request of chrome.tabs.executeJavaScript. -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); - } - } - } +ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) { + const result = runContentScript.call(window, extensionId, url, code) + ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result) +}) + +// Read the renderer process preferences. +const preferences = process.getRenderProcessPreferences() +if (preferences) { + for (const pref of preferences) { + if (pref.contentScripts) { + for (const script of pref.contentScripts) { + injectContentScript(pref.extensionId, script) + } } -}; -//# sourceMappingURL=content-scripts-injector.js.map \ No newline at end of file + } +} diff --git a/electronasar/canary/renderer/extensions/event.js b/electronasar/canary/renderer/extensions/event.js index 9d6c1cd..93f5ad2 100644 --- a/electronasar/canary/renderer/extensions/event.js +++ b/electronasar/canary/renderer/extensions/event.js @@ -1,22 +1,26 @@ -'use strict'; +'use strict' + class Event { - constructor() { - this.listeners = []; + 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) } - 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); - } + } + + emit (...args) { + for (const listener of this.listeners) { + listener(...args) } + } } -module.exports = Event; -//# sourceMappingURL=event.js.map \ No newline at end of file + +module.exports = Event diff --git a/electronasar/canary/renderer/extensions/i18n.js b/electronasar/canary/renderer/extensions/i18n.js index 83c4101..661b264 100644 --- a/electronasar/canary/renderer/extensions/i18n.js +++ b/electronasar/canary/renderer/extensions/i18n.js @@ -1,53 +1,88 @@ -'use strict'; +'use strict' + // Implementation of chrome.i18n.getMessage // https://developer.chrome.com/extensions/i18n#method-getMessage // // Does not implement predefined messages: // https://developer.chrome.com/extensions/i18n#overview-predefined -const 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 ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const fs = require('fs') +const path = require('path') + +let metadata + +const getExtensionMetadata = (extensionId) => { + if (!metadata) { + metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId) + } + return metadata +} + +const getMessagesPath = (extensionId, language) => { + const metadata = getExtensionMetadata(extensionId) + const localesDirectory = path.join(metadata.srcDirectory, '_locales') + try { + const filename = path.join(localesDirectory, language, 'messages.json') + fs.accessSync(filename, fs.constants.R_OK) + return filename + } catch (err) { + const defaultLocale = metadata.default_locale || 'en' + return path.join(localesDirectory, defaultLocale, 'messages.json') + } +} + +const getMessages = (extensionId, language) => { + try { + const messagesPath = getMessagesPath(extensionId, language) + return JSON.parse(fs.readFileSync(messagesPath)) || {} + } catch (error) { + return {} + } +} + +const getLanguage = () => { + return navigator.language.replace(/-.*$/, '').toLowerCase() +} + const replaceNumberedSubstitutions = (message, substitutions) => { - return message.replace(/\$(\d+)/, (_, number) => { - const index = parseInt(number, 10) - 1; - return substitutions[index] || ''; - }); -}; + 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); -}; + 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); - } -}; + const messages = getMessages(extensionId, getLanguage()) + if (messages.hasOwnProperty(messageName)) { + const { message, placeholders } = messages[messageName] + return replacePlaceholders(message, placeholders, substitutions) + } +} + exports.setup = (extensionId) => { - return { - getMessage(messageName, substitutions) { - return getMessage(extensionId, messageName, substitutions); - } - }; -}; -//# sourceMappingURL=i18n.js.map \ No newline at end of file + return { + getMessage (messageName, substitutions) { + return getMessage(extensionId, messageName, substitutions) + } + } +} diff --git a/electronasar/canary/renderer/extensions/storage.js b/electronasar/canary/renderer/extensions/storage.js index f649571..8afaac7 100644 --- a/electronasar/canary/renderer/extensions/storage.js +++ b/electronasar/canary/renderer/extensions/storage.js @@ -1,88 +1,138 @@ -'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)); +'use strict' + +const fs = require('fs') +const path = require('path') +const { remote } = require('electron') +const { app } = remote + +const getChromeStoragePath = (storageType, extensionId) => { + return path.join( + app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`) +} + +const mkdirp = (dir, callback) => { + fs.mkdir(dir, (error) => { + if (error && error.code === 'ENOENT') { + mkdirp(path.dirname(dir), (error) => { + if (!error) { + mkdirp(dir, callback) } - else { - // 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(); - }); -}; + }) + } else if (error && error.code === 'EEXIST') { + callback(null) + } else { + callback(error) + } + }) +} + +const readChromeStorageFile = (storageType, extensionId, cb) => { + const filePath = getChromeStoragePath(storageType, extensionId) + fs.readFile(filePath, 'utf8', (err, data) => { + if (err && err.code === 'ENOENT') { + return cb(null, null) + } + cb(err, data) + }) +} + +const writeChromeStorageFile = (storageType, extensionId, data, cb) => { + const filePath = getChromeStoragePath(storageType, extensionId) + + mkdirp(path.dirname(filePath), err => { + if (err) { /* we just ignore the errors of mkdir or mkdirp */ } + fs.writeFile(filePath, data, cb) + }) +} + +const getStorage = (storageType, extensionId, cb) => { + readChromeStorageFile(storageType, extensionId, (err, data) => { + if (err) throw err + if (!cb) throw new TypeError('No callback provided') + + if (data !== null) { + cb(JSON.parse(data)) + } else { + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + cb({}) + } + }) +} + +const setStorage = (storageType, extensionId, storage, cb) => { + const json = JSON.stringify(storage) + writeChromeStorageFile(storageType, extensionId, json, err => { + if (err) throw err + if (cb) cb() + }) +} + const getStorageManager = (storageType, extensionId) => { - return { - get(keys, callback) { - getStorage(storageType, extensionId, storage => { - if (keys == null) - return callback(storage); - let defaults = {}; - switch (typeof keys) { - case 'string': - keys = [keys]; - break; - case 'object': - if (!Array.isArray(keys)) { - defaults = keys; - keys = Object.keys(keys); - } - break; - } - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - if (keys.length === 0) - return callback({}); - const items = {}; - keys.forEach(function (key) { - let value = storage[key]; - if (value == null) - value = defaults[key]; - items[key] = value; - }); - callback(items); - }); - }, - set(items, callback) { - getStorage(storageType, extensionId, storage => { - Object.keys(items).forEach(function (name) { - storage[name] = items[name]; - }); - setStorage(storageType, extensionId, storage, callback); - }); - }, - remove(keys, callback) { - getStorage(storageType, extensionId, storage => { - if (!Array.isArray(keys)) { - keys = [keys]; - } - keys.forEach(function (key) { - delete storage[key]; - }); - setStorage(storageType, extensionId, storage, callback); - }); - }, - clear(callback) { - setStorage(storageType, extensionId, {}, callback); + 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 + setup: extensionId => ({ + sync: getStorageManager('sync', extensionId), + local: getStorageManager('local', extensionId) + }) +} diff --git a/electronasar/canary/renderer/extensions/web-navigation.js b/electronasar/canary/renderer/extensions/web-navigation.js index 87cae98..9c3f3f0 100644 --- a/electronasar/canary/renderer/extensions/web-navigation.js +++ b/electronasar/canary/renderer/extensions/web-navigation.js @@ -1,19 +1,23 @@ -'use strict'; -const Event = require('@electron/internal/renderer/extensions/event'); -const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); +'use strict' + +const Event = require('@electron/internal/renderer/extensions/event') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + class WebNavigation { - constructor() { - this.onBeforeNavigate = new Event(); - this.onCompleted = new Event(); - ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => { - this.onBeforeNavigate.emit(details); - }); - ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => { - this.onCompleted.emit(details); - }); - } + constructor () { + this.onBeforeNavigate = new Event() + this.onCompleted = new Event() + + ipcRenderer.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => { + this.onBeforeNavigate.emit(details) + }) + + ipcRenderer.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => { + this.onCompleted.emit(details) + }) + } } + exports.setup = () => { - return new WebNavigation(); -}; -//# sourceMappingURL=web-navigation.js.map \ No newline at end of file + return new WebNavigation() +} diff --git a/electronasar/canary/renderer/init.js b/electronasar/canary/renderer/init.js index fb22749..cf1af5b 100644 --- a/electronasar/canary/renderer/init.js +++ b/electronasar/canary/renderer/init.js @@ -1,176 +1,176 @@ -"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); });' -]; +'use strict' + +const { EventEmitter } = require('events') +const path = require('path') +const Module = require('module') + // We modified the original process.argv to let node.js load the // init.js, we need to restore it here. -process.argv.splice(1, 1); +process.argv.splice(1, 1) + // Clear search paths. -require('../common/reset-search-paths'); +require('../common/reset-search-paths') + // Import common settings. -require('@electron/internal/common/init'); -const globalPaths = Module.globalPaths; +require('@electron/internal/common/init') + +const globalPaths = Module.globalPaths + // Expose public APIs. -globalPaths.push(path.join(__dirname, 'api', 'exports')); +globalPaths.push(path.join(__dirname, 'api', 'exports')) + // The global variable will be used by ipc for event dispatching -const v8Util = process.electronBinding('v8_util'); -const ipcEmitter = new events_1.EventEmitter(); -const ipcInternalEmitter = new events_1.EventEmitter(); -v8Util.setHiddenValue(global, 'ipc', ipcEmitter); -v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter); -v8Util.setHiddenValue(global, 'ipcNative', { - onMessage(internal, channel, args, senderId) { - const sender = internal ? ipcInternalEmitter : ipcEmitter; - sender.emit(channel, { sender, senderId }, ...args); - } -}); +const v8Util = process.atomBinding('v8_util') + +v8Util.setHiddenValue(global, 'ipc', new EventEmitter()) +v8Util.setHiddenValue(global, 'ipc-internal', new EventEmitter()) + // Use electron module after everything is ready. -const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal'); -const { webFrameInit } = require('@electron/internal/renderer/web-frame-init'); -webFrameInit(); +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + +require('@electron/internal/renderer/web-frame-init')() + // 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 }; +let nodeIntegration = false +let webviewTag = false +let preloadScript = null +let preloadScripts = [] +let isBackgroundPage = false +let appPath = null +for (const arg of process.argv) { + if (arg.indexOf('--guest-instance-id=') === 0) { + // This is a guest web view. + process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--opener-id=') === 0) { + // This is a guest BrowserWindow. + process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--node-integration=') === 0) { + nodeIntegration = arg.substr(arg.indexOf('=') + 1) === 'true' + } else if (arg.indexOf('--preload=') === 0) { + preloadScript = arg.substr(arg.indexOf('=') + 1) + } else if (arg === '--background-page') { + isBackgroundPage = true + } else if (arg.indexOf('--app-path=') === 0) { + appPath = arg.substr(arg.indexOf('=') + 1) + } else if (arg.indexOf('--webview-tag=') === 0) { + webviewTag = arg.substr(arg.indexOf('=') + 1) === 'true' + } else if (arg.indexOf('--preload-scripts') === 0) { + preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter) + } +} + // The webContents preload script is loaded after the session preload scripts. if (preloadScript) { - preloadScripts.push(preloadScript); + 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); + +if (window.location.protocol === 'chrome-devtools:') { + // Override some inspector APIs. + require('@electron/internal/renderer/inspector') + nodeIntegration = false +} else if (window.location.protocol === 'chrome-extension:') { + // Add implementations of chrome API. + require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window) + nodeIntegration = false +} else if (window.location.protocol === 'chrome:') { + // Disable node integration for chrome UI scheme. + nodeIntegration = false +} else { + // Override default web functions. + require('@electron/internal/renderer/override') + + // Inject content scripts. + require('@electron/internal/renderer/content-scripts-injector') + + // Load webview tag implementation. + if (webviewTag && process.guestInstanceId == null) { + const { setupWebView } = require('@electron/internal/renderer/web-view/web-view') + if (process.argv.includes('--context-isolation')) { + v8Util.setHiddenValue(window, 'setup-webview', setupWebView) + } else { + setupWebView(window) } + } } -// 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)); + // 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}` + } } - else { - global.__filename = __filename; - global.__dirname = __dirname; - if (appPath) { - // Search for module under the app directory - module.paths = module.paths.concat(Module._nodeModulePaths(appPath)); - } + + 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; - } - }; + } + + // Redirect window.onerror to uncaughtException. + window.onerror = function (message, filename, lineno, colno, error) { + if (global.process.listeners('uncaughtException').length > 0) { + global.process.emit('uncaughtException', error) + return true + } else { + return false + } + } +} else { + // Delete Node's symbols after the Environment has been loaded. + process.once('loaded', function () { + delete global.process + delete global.Buffer + delete global.setImmediate + delete global.clearImmediate + delete global.global + }) } -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)); - } + try { + require(preloadScript) + } catch (error) { + console.error('Unable to load preload script: ' + preloadScript) + console.error(error.stack || error.message) + } } + // Warn about security issues -if (process.isMainFrame) { - const { securityWarnings } = require('@electron/internal/renderer/security-warnings'); - securityWarnings(nodeIntegration); +require('@electron/internal/renderer/security-warnings')(nodeIntegration) + +// Report focus/blur events of webview to browser. +// Note that while Chromium content APIs have observer for focus/blur, they +// unfortunately do not work for webview. +if (process.guestInstanceId) { + window.addEventListener('focus', () => { + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, process.guestInstanceId) + }) + window.addEventListener('blur', () => { + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, process.guestInstanceId) + }) } -//# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/electronasar/canary/renderer/inspector.js b/electronasar/canary/renderer/inspector.js index 3c1f84e..b358fa6 100644 --- a/electronasar/canary/renderer/inspector.js +++ b/electronasar/canary/renderer/inspector.js @@ -1,53 +1,138 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const ipc_renderer_internal_utils_1 = require("@electron/internal/renderer/ipc-renderer-internal-utils"); +'use strict' + window.onload = function () { - // Use menu API to show context menu. - window.InspectorFrontendHost.showContextMenuAtPoint = createMenu; - // correct for Chromium returning undefined for filesystem - window.Persistence.FileSystemWorkspaceBinding.completeURL = completeURL; - // Use dialog API to override file chooser dialog. - window.UI.createFileSelectorElement = createFileSelectorElement; -}; -// Extra / is needed as a result of MacOS requiring absolute paths -function completeURL(project, path) { - project = 'file:///'; - return `${project}${path}`; + // 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 } -// The DOM implementation expects (message?: string) => boolean + +// Extra / is needed as a result of MacOS requiring absolute paths +function completeURL (project, path) { + project = 'file:///' + return `${project}${path}` +} + window.confirm = function (message, title) { - 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 { dialog } = require('electron').remote + if (title == null) { + title = '' + } + return !dialog.showMessageBox({ + message: message, + title: title, + buttons: ['OK', 'Cancel'], + cancelId: 1 + }) +} + +const convertToMenuTemplate = function (items) { + return items.map(function (item) { + const transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(item.subItems) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + } + + if (item.id != null) { + transformed.click = function () { + window.DevToolsAPI.contextMenuItemSelected(item.id) + return window.DevToolsAPI.contextMenuCleared() + } + } + + return transformed + }) +} + const createMenu = function (x, y, items) { - const 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 { remote } = require('electron') + const { Menu } = remote + + let template = convertToMenuTemplate(items) + if (useEditMenuItems(x, y, template)) { + template = getEditMenuItems() + } + const menu = Menu.buildFromTemplate(template) + + // The menu is expected to show asynchronously. + setTimeout(function () { + menu.popup({ window: remote.getCurrentWindow() }) + }) +} + +const useEditMenuItems = function (x, y, items) { + return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) { + return element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || element.isContentEditable + }) +} + +const getEditMenuItems = function () { + return [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + { + role: 'pasteAndMatchStyle' + }, + { + role: 'delete' + }, + { + role: 'selectAll' + } + ] +} + const showFileChooserDialog = function (callback) { - 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 { dialog } = require('electron').remote + const files = dialog.showOpenDialog({}) + if (files != null) { + callback(pathToHtml5FileObject(files[0])) + } +} + +const pathToHtml5FileObject = function (path) { + const fs = require('fs') + const blob = new Blob([fs.readFileSync(path)]) + blob.name = path + return blob +} + const createFileSelectorElement = function (callback) { - const fileSelectorElement = document.createElement('span'); - fileSelectorElement.style.display = 'none'; - fileSelectorElement.click = showFileChooserDialog.bind(this, callback); - return fileSelectorElement; -}; -//# sourceMappingURL=inspector.js.map \ No newline at end of file + const fileSelectorElement = document.createElement('span') + fileSelectorElement.style.display = 'none' + fileSelectorElement.click = showFileChooserDialog.bind(this, callback) + return fileSelectorElement +} diff --git a/electronasar/canary/renderer/ipc-renderer-internal-utils.js b/electronasar/canary/renderer/ipc-renderer-internal-utils.js deleted file mode 100644 index 2dc6e8d..0000000 --- a/electronasar/canary/renderer/ipc-renderer-internal-utils.js +++ /dev/null @@ -1,42 +0,0 @@ -"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/canary/renderer/ipc-renderer-internal.js b/electronasar/canary/renderer/ipc-renderer-internal.js index d318025..a8dda32 100644 --- a/electronasar/canary/renderer/ipc-renderer-internal.js +++ b/electronasar/canary/renderer/ipc-renderer-internal.js @@ -1,20 +1,26 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const binding = process.electronBinding('ipc'); -const v8Util = process.electronBinding('v8_util'); +'use strict' + +const binding = process.atomBinding('ipc') +const v8Util = process.atomBinding('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.sendTo(internal, false, webContentsId, channel, args); -}; -exports.ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) { - return binding.sendTo(internal, true, webContentsId, channel, args); -}; -//# sourceMappingURL=ipc-renderer-internal.js.map \ No newline at end of file +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc-internal') +const internal = true + +ipcRenderer.send = function (...args) { + return binding.send('ipc-internal-message', args) +} + +ipcRenderer.sendSync = function (...args) { + return binding.sendSync('ipc-internal-message-sync', args)[0] +} + +ipcRenderer.sendTo = function (webContentsId, channel, ...args) { + return binding.sendTo(internal, false, webContentsId, channel, args) +} + +ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { + return binding.sendTo(internal, true, webContentsId, channel, args) +} + +module.exports = ipcRenderer diff --git a/electronasar/canary/renderer/override.js b/electronasar/canary/renderer/override.js new file mode 100644 index 0000000..72a4a5a --- /dev/null +++ b/electronasar/canary/renderer/override.js @@ -0,0 +1,18 @@ +'use strict' + +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + +const v8Util = process.atomBinding('v8_util') + +const { guestInstanceId, openerId } = process +const hiddenPage = process.argv.includes('--hidden-page') +const usesNativeWindowOpen = process.argv.includes('--native-window-open') +const contextIsolation = process.argv.includes('--context-isolation') + +// Pass the arguments to isolatedWorld. +if (contextIsolation) { + const isolatedWorldArgs = { ipcRenderer, guestInstanceId, hiddenPage, openerId, usesNativeWindowOpen } + v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs) +} + +require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen) diff --git a/electronasar/canary/renderer/remote.js b/electronasar/canary/renderer/remote.js new file mode 100644 index 0000000..b807723 --- /dev/null +++ b/electronasar/canary/renderer/remote.js @@ -0,0 +1,10 @@ +'use strict' + +const { remote } = require('electron') + +exports.getRemoteForUsage = function (usage) { + if (!remote) { + throw new Error(`${usage} requires remote, which is not enabled`) + } + return remote +} diff --git a/electronasar/canary/renderer/security-warnings.js b/electronasar/canary/renderer/security-warnings.js index 5dfc97e..619f8bb 100644 --- a/electronasar/canary/renderer/security-warnings.js +++ b/electronasar/canary/renderer/security-warnings.js @@ -1,8 +1,7 @@ -"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; +'use strict' + +let shouldLog = null + /** * This method checks if a security message should be logged. * It does so by determining whether we're running as Electron, @@ -12,64 +11,76 @@ let shouldLog = null; * @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; -}; + 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); - } -}; + 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 { webFrame } = require('electron') + + return new Promise((resolve) => { + webFrame.executeJavaScript(`(${(() => { + try { + new Function('') // eslint-disable-line no-new,no-new-func + } catch (err) { + return false + } + return true + }).toString()})()`, resolve) + }) +} + const moreInformation = `\nFor more information and help, consult https://electronjs.org/docs/tutorial/security.\n This warning will not show up -once the app is packaged.`; +once the app is packaged.` + /** * #1 Only load secure content * @@ -77,23 +88,29 @@ once the app is packaged.`; * 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 + 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); -}; + \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 @@ -101,32 +118,39 @@ const warnAboutInsecureResources = function () { * 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 + 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); - } -}; + 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); -}; + 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') @@ -136,104 +160,180 @@ const warnAboutDisabledWebSecurity = function (webPreferences) { * 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 + 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); - }); -}; + 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" + 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); -}; + ${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. + 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); -}; + 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" + if (webPreferences === null || + !webPreferences.hasOwnProperty('enableBlinkFeatures') || + webPreferences.enableBlinkFeatures.length === 0) { + return + } + + const warning = `This renderer process has additional "enableBlinkFeatures" enabled. This exposes users of this app to some security risk. If you do not - need this feature, you should disable it.\n ${moreInformation}`; - console.warn('%cElectron Security Warning (enableBlinkFeatures)', 'font-weight: bold;', warning); -}; + 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 + 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); - } -}; + ${moreInformation}` + + console.warn('%cElectron Security Warning (allowpopups)', + 'font-weight: bold;', warning) + } +} + +const warnAboutNodeIntegrationDefault = function (webPreferences) { + if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) { + const warning = `This window has node integration enabled by default. In ` + + `Electron 5.0.0, node integration will be disabled by default. To prepare ` + + `for this change, set {nodeIntegration: true} in the webPreferences for ` + + `this window, or ensure that this window does not rely on node integration ` + + `and set {nodeIntegration: false}.` + console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning) + } +} + +const warnAboutContextIsolationDefault = function (webPreferences) { + if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) { + const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content' + const warning = `This window has context isolation disabled by default. In ` + + `Electron 5.0.0, context isolation will be enabled by default. To prepare ` + + `for this change, set {contextIsolation: false} in the webPreferences for ` + + `this window, or ensure that this window does not rely on context ` + + `isolation being disabled, and set {contextIsolation: true}.\n\n` + + `For more information, see ${url}` + console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning) + } +} + +const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) { + if (!webPreferences) { + return + } + if (webPreferences.webviewTagWasExplicitlyEnabled) { + return + } + if (!document || !document.getElementsByTagName) { + return + } + const webviews = document.getElementsByTagName('webview') + if (webviews && webviews.length > 0) { + const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-' + const warning = `This window has the tag enabled by default. In ` + + `Electron 5.0.0, tags will be disabled by default. To prepare ` + + `for this change, set {webviewTag: true} in the webPreferences for ` + + `this window.\n\n` + + `For more information, see ${url}` + + console.warn('%cElectron Deprecation Warning (webviewTag default change)', + 'font-weight: bold;', warning) + } +} + // Currently missing since we can't easily programmatically check for it: // #12WebViews: Verify the options and params of all `` 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 }); + warnAboutNodeWithRemoteContent(nodeIntegration) + warnAboutDisabledWebSecurity(webPreferences) + warnAboutInsecureResources() + warnAboutInsecureContentAllowed(webPreferences) + warnAboutExperimentalFeatures(webPreferences) + warnAboutEnableBlinkFeatures(webPreferences) + warnAboutInsecureCSP() + warnAboutAllowedPopups() + warnAboutNodeIntegrationDefault(webPreferences) + warnAboutContextIsolationDefault(webPreferences) + warnAboutDeprecatedWebviewTagDefault(webPreferences) +} + +const getWebPreferences = function () { + const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + const errorUtils = require('@electron/internal/common/error-utils') + + const [ error, result ] = ipcRenderer.sendSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES') + + if (error) { + console.warn(`getLastWebPreferences() failed: ${errorUtils.deserialize(error)}`) + return null + } else { + return result + } +} + +module.exports = function (nodeIntegration) { + const loadHandler = function () { + if (shouldLogSecurityWarnings()) { + const webPreferences = getWebPreferences() + logSecurityWarnings(webPreferences, nodeIntegration) + } + } + window.addEventListener('load', loadHandler, { once: true }) } -exports.securityWarnings = securityWarnings; -//# sourceMappingURL=security-warnings.js.map \ No newline at end of file diff --git a/electronasar/canary/renderer/web-frame-init.js b/electronasar/canary/renderer/web-frame-init.js index 29b03f1..f7861db 100644 --- a/electronasar/canary/renderer/web-frame-init.js +++ b/electronasar/canary/renderer/web-frame-init.js @@ -1,14 +1,24 @@ -"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 +'use strict' + +const { webFrame } = require('electron') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const errorUtils = require('@electron/internal/common/error-utils') + +module.exports = () => { + // Call webFrame method + ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { + webFrame[method](...args) + }) + + ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { + new Promise(resolve => + webFrame[method](...args, resolve) + ).then(result => { + return [null, result] + }, error => { + return [errorUtils.serialize(error)] + }).then(responseArgs => { + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, ...responseArgs) + }) + }) +} diff --git a/electronasar/canary/renderer/web-view/guest-view-internal.js b/electronasar/canary/renderer/web-view/guest-view-internal.js index c16720c..3d544aa 100644 --- a/electronasar/canary/renderer/web-view/guest-view-internal.js +++ b/electronasar/canary/renderer/web-view/guest-view-internal.js @@ -1,111 +1,108 @@ -"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"); +'use strict' + +const { webFrame } = require('electron') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + +let requestId = 0 + 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'] -}; + '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': [], + 'gpu-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' -}; + '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); - }); + 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(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); + +module.exports = { + registerEvents: function (webView, viewInstanceId) { + ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () { + webView.guestInstanceId = undefined + webView.reset() + const domEvent = new Event('destroyed') + webView.dispatchEvent(domEvent) + }) + + ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) { + dispatchEvent(webView, eventName, eventName, ...args) + }) + + ipcRenderer.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) + }) + }, + deregisterEvents: function (viewInstanceId) { + ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`) + ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`) + ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`) + }, + createGuest: function (params, callback) { + requestId++ + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId) + ipcRenderer.once(`ELECTRON_RESPONSE_${requestId}`, callback) + }, + createGuestSync: function (params) { + return ipcRenderer.sendSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', params) + }, + destroyGuest: function (guestInstanceId) { + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId) + }, + attachGuest: function (elementInstanceId, guestInstanceId, params, contentWindow) { + const embedderFrameId = webFrame.getWebFrameId(contentWindow) if (embedderFrameId < 0) { // this error should not happen. - throw new Error('Invalid embedder frame'); + throw new Error('Invalid embedder frame') } - ipc_renderer_internal_utils_1.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params); + ipcRenderer.send('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/canary/renderer/web-view/web-view-attributes.js b/electronasar/canary/renderer/web-view/web-view-attributes.js index 02c4f09..39d986e 100644 --- a/electronasar/canary/renderer/web-view/web-view-attributes.js +++ b/electronasar/canary/renderer/web-view/web-view-attributes.js @@ -1,251 +1,281 @@ -"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"); +'use strict' + +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const { WebViewImpl } = require('@electron/internal/renderer/web-view/web-view') +const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants') +const errorUtils = require('@electron/internal/common/error-utils') + // Helper function to resolve url set in attribute. -const a = document.createElement('a'); +const a = document.createElement('a') + const resolveURL = function (url) { - if (!url) - return ''; - a.href = url; - return a.href; -}; + 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 - }); - } + constructor (name, webViewImpl) { + this.name = name + this.value = webViewImpl.webviewNode[name] || '' + this.webViewImpl = webViewImpl + this.ignoreMutation = false + 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 + }) + } + + // Called when the attribute's value changes. + handleMutation () {} } + // 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); - } + 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 */); - } - }; + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl) + this.validPartitionId = true + } + + handleMutation (oldValue, newValue) { + newValue = newValue || '' + + // The partition cannot change if the webview has already navigated. + if (!this.webViewImpl.beforeFirstNavigation) { + console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED) + this.setValueIgnoreMutation(oldValue) + return } + if (newValue === 'persist:') { + this.validPartitionId = false + console.error(webViewConstants.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(); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_SRC, webViewImpl) + this.setupMutationObserver() + } + + getValue () { + if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) + } else { + return this.value } - 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() + } + + 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 } - 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(); + this.parse() + } + + // 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] } - // 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); + this.observer.observe(this.webViewImpl.webviewNode, params) + } + + parse () { + if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { + return } - 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.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args); + 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[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() + if (httpreferrer) { + opts.httpReferrer = httpreferrer + } + const useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue() + if (useragent) { + opts.userAgent = useragent + } + + const guestInstanceId = this.webViewImpl.guestInstanceId + const method = 'loadURL' + const args = [this.getValue(), opts] + + const [error] = ipcRenderer.sendSync('ELECTRON_GUEST_VIEW_MANAGER_SYNC_CALL', guestInstanceId, method, args) + if (error) { + throw errorUtils.deserialize(error) + } + } } + // Attribute specifies HTTP referrer. class HttpReferrerAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super("httpreferrer" /* ATTRIBUTE_HTTPREFERRER */, webViewImpl); - } + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl) + } } + // Attribute specifies user agent class UserAgentAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super("useragent" /* ATTRIBUTE_USERAGENT */, webViewImpl); - } + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl) + } } + // Attribute that set preload script. class PreloadAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super("preload" /* ATTRIBUTE_PRELOAD */, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl) + } + + getValue () { + if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return this.value } - 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; + let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) + const protocol = preload.substr(0, 5) + if (protocol !== 'file:') { + console.error(webViewConstants.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); - } + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl) + } } + // Attribute that specifies the blink features to be disabled. class DisableBlinkFeaturesAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super("disableblinkfeatures" /* ATTRIBUTE_DISABLEBLINKFEATURES */, webViewImpl); - } + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl) + } } + // Attribute that specifies the web preferences to be enabled. class WebPreferencesAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super("webpreferences" /* ATTRIBUTE_WEBPREFERENCES */, webViewImpl); - } + constructor (webViewImpl) { + super(webViewConstants.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'); - } + constructor (webViewImpl) { + super(webViewConstants.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 +WebViewImpl.prototype.setupWebViewAttributes = function () { + this.attributes = {} + this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) + this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) + this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) + this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) + this.attributes[webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this) +} diff --git a/electronasar/canary/renderer/web-view/web-view-constants.js b/electronasar/canary/renderer/web-view/web-view-constants.js index 97cfb5f..459aafa 100644 --- a/electronasar/canary/renderer/web-view/web-view-constants.js +++ b/electronasar/canary/renderer/web-view/web-view-constants.js @@ -1,3 +1,28 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=web-view-constants.js.map \ No newline at end of file +'use strict' + +module.exports = { + // Attributes. + ATTRIBUTE_NAME: 'name', + ATTRIBUTE_PARTITION: 'partition', + ATTRIBUTE_SRC: 'src', + ATTRIBUTE_HTTPREFERRER: 'httpreferrer', + ATTRIBUTE_NODEINTEGRATION: 'nodeintegration', + ATTRIBUTE_ENABLEREMOTEMODULE: 'enableremotemodule', + ATTRIBUTE_PLUGINS: 'plugins', + ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity', + ATTRIBUTE_ALLOWPOPUPS: 'allowpopups', + ATTRIBUTE_PRELOAD: 'preload', + ATTRIBUTE_USERAGENT: 'useragent', + ATTRIBUTE_BLINKFEATURES: 'blinkfeatures', + ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures', + ATTRIBUTE_WEBPREFERENCES: 'webpreferences', + + // Internal attribute. + ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', + + // Error messages. + ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.', + ERROR_MSG_CANNOT_INJECT_SCRIPT: ': ' + 'Script cannot be injected into content until the page has loaded.', + ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.', + ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.' +} diff --git a/electronasar/canary/renderer/web-view/web-view-element.js b/electronasar/canary/renderer/web-view/web-view-element.js deleted file mode 100644 index 6c7092e..0000000 --- a/electronasar/canary/renderer/web-view/web-view-element.js +++ /dev/null @@ -1,102 +0,0 @@ -"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/canary/renderer/web-view/web-view-impl.js b/electronasar/canary/renderer/web-view/web-view-impl.js deleted file mode 100644 index 3bb03e5..0000000 --- a/electronasar/canary/renderer/web-view/web-view-impl.js +++ /dev/null @@ -1,233 +0,0 @@ -"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/canary/renderer/web-view/web-view-init.js b/electronasar/canary/renderer/web-view/web-view-init.js deleted file mode 100644 index e1951a7..0000000 --- a/electronasar/canary/renderer/web-view/web-view-init.js +++ /dev/null @@ -1,33 +0,0 @@ -"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/canary/renderer/web-view/web-view.js b/electronasar/canary/renderer/web-view/web-view.js new file mode 100644 index 0000000..560c728 --- /dev/null +++ b/electronasar/canary/renderer/web-view/web-view.js @@ -0,0 +1,346 @@ +'use strict' + +const { webFrame } = require('electron') + +const v8Util = process.atomBinding('v8_util') +const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') +const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') +const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants') +const errorUtils = require('@electron/internal/common/error-utils') +const { + syncMethods, + asyncCallbackMethods, + asyncPromiseMethods +} = require('@electron/internal/common/web-view-methods') + +// 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 + v8Util.setHiddenValue(this.webviewNode, 'internal', this) + this.elementAttached = false + this.beforeFirstNavigation = true + this.hasFocus = false + + // on* Event handlers. + this.on = {} + + // 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 + }) + } + + 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[webViewConstants.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 + oldFrame.parentNode.replaceChild(newFrame, oldFrame) + } + + // Sets the .request property. + setRequestPropertyOnWebViewNode (request) { + Object.defineProperty(this.webviewNode, 'request', { + value: request, + enumerable: true + }) + } + + // 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 () { + return guestViewInternal.createGuest(this.buildParams(), (event, 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(webViewConstants.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[webViewConstants.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[webViewConstants.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)).observe(this.internalElement) + } +} + +// Registers custom element. +const registerWebViewElement = (window) => { + const proto = Object.create(window.HTMLObjectElement.prototype) + proto.createdCallback = function () { + return new WebViewImpl(this) + } + proto.attributeChangedCallback = function (name, oldValue, newValue) { + const internal = v8Util.getHiddenValue(this, 'internal') + if (internal) { + internal.handleWebviewAttributeMutation(name, oldValue, newValue) + } + } + proto.detachedCallback = function () { + const internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + guestViewInternal.deregisterEvents(internal.viewInstanceId) + internal.elementAttached = false + this.internalInstanceId = 0 + internal.reset() + } + proto.attachedCallback = function () { + const internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + if (!internal.elementAttached) { + guestViewInternal.registerEvents(internal, internal.viewInstanceId) + internal.elementAttached = true + internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() + } + } + + const getGuestInstanceId = function (self) { + const internal = v8Util.getHiddenValue(self, '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 + } + + // Forward proto.foo* method calls to WebViewImpl.foo*. + const createBlockHandler = function (method) { + return function (...args) { + const [error, result] = ipcRenderer.sendSync('ELECTRON_GUEST_VIEW_MANAGER_SYNC_CALL', getGuestInstanceId(this), method, args) + if (error) { + throw errorUtils.deserialize(error) + } else { + return result + } + } + } + for (const method of syncMethods) { + proto[method] = createBlockHandler(method) + } + + const createNonBlockHandler = function (method) { + return function (...args) { + const callback = (typeof args[args.length - 1] === 'function') ? args.pop() : null + const requestId = getNextId() + ipcRenderer.once(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, function (event, error, result) { + if (error == null) { + if (callback) callback(result) + } else { + throw errorUtils.deserialize(error) + } + }) + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', requestId, getGuestInstanceId(this), method, args, callback != null) + } + } + + for (const method of asyncCallbackMethods) { + proto[method] = createNonBlockHandler(method) + } + + const createPromiseHandler = function (method) { + return function (...args) { + return new Promise((resolve, reject) => { + const callback = (typeof args[args.length - 1] === 'function') ? args.pop() : null + const requestId = getNextId() + + ipcRenderer.once(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, function (event, error, result) { + if (error == null) { + if (callback) { + callback(result) + } + resolve(result) + } else { + reject(errorUtils.deserialize(error)) + } + }) + ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', requestId, getGuestInstanceId(this), method, args, callback != null) + }) + } + } + + for (const method of asyncPromiseMethods) { + proto[method] = createPromiseHandler(method) + } + + // WebContents associated with this webview. + proto.getWebContents = function () { + const { getRemoteForUsage } = require('@electron/internal/renderer/remote') + const remote = getRemoteForUsage('getWebContents()') + const internal = v8Util.getHiddenValue(this, 'internal') + if (!internal.guestInstanceId) { + internal.createGuestSync() + } + return remote.getGuestWebContents(internal.guestInstanceId) + } + + // Focusing the webview should move page focus to the underlying iframe. + proto.focus = function () { + this.contentWindow.focus() + } + + window.WebView = webFrame.registerEmbedderCustomElement(window, 'webview', { + prototype: proto + }) + + // Delete the callbacks so developers cannot call them and produce unexpected + // behavior. + delete proto.createdCallback + delete proto.attachedCallback + delete proto.detachedCallback + delete proto.attributeChangedCallback +} + +const setupWebView = (window) => { + require('@electron/internal/renderer/web-view/web-view-attributes') + + const useCapture = true + window.addEventListener('readystatechange', function listener (event) { + if (document.readyState === 'loading') { + return + } + registerWebViewElement(window) + window.removeEventListener(event.type, listener, useCapture) + }, useCapture) +} + +module.exports = { setupWebView, WebViewImpl } diff --git a/electronasar/canary/renderer/window-setup.js b/electronasar/canary/renderer/window-setup.js index 4a3c8a8..703b82f 100644 --- a/electronasar/canary/renderer/window-setup.js +++ b/electronasar/canary/renderer/window-setup.js @@ -1,13 +1,12 @@ -"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"); +'use strict' + +// This file should have no requires since it is used by the isolated context +// preload bundle. Instead arguments should be passed in for everything it +// needs. + // This file implements the following APIs: +// - window.alert() +// - window.confirm() // - window.history.back() // - window.history.forward() // - window.history.go() @@ -23,225 +22,171 @@ const ipc_renderer_internal_1 = require("@electron/internal/renderer/ipc-rendere // - window.prompt() // - document.hidden // - document.visibilityState -const { defineProperty } = Object; + +const { defineProperty } = Object + // Helper function to resolve relative url. -const a = window.document.createElement('a'); +const a = window.top.document.createElement('a') const resolveURL = function (url) { - a.href = url; - return a.href; -}; + 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; -}; + return value != null ? `${value}` : value +} + +const windowProxies = {} + +const getOrCreateProxy = (ipcRenderer, guestId) => { + let proxy = windowProxies[guestId] + if (proxy == null) { + proxy = new BrowserWindowProxy(ipcRenderer, 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 this.ipcRenderer.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; - } + delete windowProxies[guestId] } -__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); + +function BrowserWindowProxy (ipcRenderer, guestId) { + this.closed = false + + defineProperty(this, 'location', { + get: function () { + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'getURL') + }, + set: function (url) { + url = resolveURL(url) + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'loadURL', url) } + }) + + ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { + removeProxy(guestId) + this.closed = true + }) + + this.close = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId) + } + + this.focus = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus') + } + + this.blur = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur') + } + + this.print = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print') + } + + this.postMessage = (message, targetOrigin) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, toString(targetOrigin), window.location.origin) + } + + this.eval = (...args) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', 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'); - }; + +module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen) => { + if (guestInstanceId == null) { + // Override default window.close. + window.close = function () { + ipcRenderer.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); - } + } + + 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 = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features)) + if (guestId != null) { + return getOrCreateProxy(ipcRenderer, guestId) + } else { + return null + } } - // 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; - } - }); + + if (openerId != null) { + window.opener = getOrCreateProxy(ipcRenderer, openerId) } -}; -//# sourceMappingURL=window-setup.js.map \ No newline at end of file + } + + // But we do not support prompt(). + window.prompt = function () { + throw new Error('prompt() is and will not be supported.') + } + + ipcRenderer.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. + event = document.createEvent('Event') + event.initEvent('message', false, false) + event.data = message + event.origin = sourceOrigin + event.source = getOrCreateProxy(ipcRenderer, sourceId) + window.dispatchEvent(event) + }) + + window.history.back = function () { + ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK') + } + + window.history.forward = function () { + ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD') + } + + window.history.go = function (offset) { + ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset) + } + + defineProperty(window.history, 'length', { + get: function () { + return ipcRenderer.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 = hiddenPage ? 'hidden' : 'visible' + + // Subscribe to visibilityState changes. + ipcRenderer.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 + } + }) + } +} diff --git a/electronasar/canary/worker/init.js b/electronasar/canary/worker/init.js index 492e6a1..e4f8250 100644 --- a/electronasar/canary/worker/init.js +++ b/electronasar/canary/worker/init.js @@ -1,30 +1,37 @@ -'use strict'; -const path = require('path'); -const Module = require('module'); +'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); +process.argv.splice(1, 1) + // Clear search paths. -require('../common/reset-search-paths'); +require('../common/reset-search-paths') + // Import common settings. -require('@electron/internal/common/init'); +require('@electron/internal/common/init') + // Expose public APIs. -Module.globalPaths.push(path.join(__dirname, 'api', 'exports')); +Module.globalPaths.push(path.join(__dirname, 'api', 'exports')) + // Export node bindings to global. -global.require = require; -global.module = module; +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)); + 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 } -else { - global.__filename = __filename; - global.__dirname = __dirname; -} -//# sourceMappingURL=init.js.map \ No newline at end of file