Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Ì È Ä {"files":{"browser":{"files":{"api":{"files":{"app.js":{"size":3279,"offset":"0"},"auto-updater":{"files":{"auto-updater-native.js":{"size":315,"offset":"3532"},"auto-updater-win.js":{"size":2247,"offset":"3847"},"squirrel-update-win.js":{"size":3855,"offset":"6094"}}},"auto-updater.js":{"size":253,"offset":"3279"},"browser-view.js":{"size":411,"offset":"9949"},"browser-window.js":{"size":5670,"offset":"10360"},"content-tracing.js":{"size":73,"offset":"16030"},"crash-reporter.js":{"size":395,"offset":"16103"},"dialog.js":{"size":9080,"offset":"16498"},"exports":{"files":{"electron.js":{"size":530,"offset":"25578"}}},"global-shortcut.js":{"size":88,"offset":"26108"},"in-app-purchase.js":{"size":619,"offset":"26196"},"ipc-main.js":{"size":216,"offset":"26815"},"menu-item-roles.js":{"size":6795,"offset":"27031"},"menu-item.js":{"size":2803,"offset":"33826"},"menu-utils.js":{"size":4586,"offset":"36629"},"menu.js":{"size":8529,"offset":"41215"},"module-list.js":{"size":1908,"offset":"49744"},"navigation-controller.js":{"size":5661,"offset":"51652"},"net-log.js":{"size":830,"offset":"57313"},"net.js":{"size":10281,"offset":"58143"},"notification.js":{"size":283,"offset":"68424"},"power-monitor.js":{"size":793,"offset":"68707"},"power-save-blocker.js":{"size":93,"offset":"69500"},"protocol.js":{"size":820,"offset":"69593"},"screen.js":{"size":270,"offset":"70413"},"session.js":{"size":613,"offset":"70683"},"system-preferences.js":{"size":348,"offset":"71296"},"top-level-window.js":{"size":699,"offset":"71644"},"touch-bar.js":{"size":10768,"offset":"72343"},"tray.js":{"size":196,"offset":"83111"},"view.js":{"size":239,"offset":"83307"},"web-contents-view.js":{"size":379,"offset":"83546"},"web-contents.js":{"size":15583,"offset":"83925"}}},"chrome-extension.js":{"size":14751,"offset":"99508"},"desktop-capturer.js":{"size":2455,"offset":"114259"},"guest-view-manager.js":{"size":15187,"offset":"116714"},"guest-window-manager.js":{"size":13148,"offset":"131901"},"init.js":{"size":5945,"offset":"145049"},"ipc-main-internal.js":{"size":216,"offset":"150994"},"objects-registry.js":{"size":3411,"offset":"151210"},"rpc-server.js":{"size":18747,"offset":"154621"}}},"common":{"files":{"api":{"files":{"clipboard.js":{"size":1182,"offset":"173368"},"deprecate.js":{"size":2606,"offset":"174550"},"deprecations.js":{"size":242,"offset":"177156"},"exports":{"files":{"electron.js":{"size":920,"offset":"177398"}}},"is-promise.js":{"size":334,"offset":"178318"},"module-list.js":{"size":471,"offset":"178652"},"native-image.js":{"size":70,"offset":"179123"},"shell.js":{"size":63,"offset":"179193"}}},"atom-binding-setup.js":{"size":371,"offset":"179256"},"buffer-utils.js":{"size":1509,"offset":"179627"},"crash-reporter.js":{"size":3092,"offset":"181136"},"error-utils.js":{"size":1199,"offset":"184228"},"init.js":{"size":1889,"offset":"185427"},"parse-features-string.js":{"size":747,"offset":"187316"},"reset-search-paths.js":{"size":1744,"offset":"188063"},"web-view-methods.js":{"size":1271,"offset":"189807"}}},"renderer":{"files":{"api":{"files":{"crash-reporter.js":{"size":368,"offset":"191078"},"desktop-capturer.js":{"size":1421,"offset":"191446"},"exports":{"files":{"electron.js":{"size":541,"offset":"192867"}}},"ipc-renderer.js":{"size":834,"offset":"193408"},"module-list.js":{"size":735,"offset":"194242"},"remote.js":{"size":11723,"offset":"194977"},"screen.js":{"size":146,"offset":"206700"},"web-frame.js":{"size":1696,"offset":"206846"}}},"callbacks-registry.js":{"size":1510,"offset":"208542"},"chrome-api.js":{"size":6056,"offset":"210052"},"content-scripts-injector.js":{"size":3360,"offset":"216108"},"extensions":{"files":{"event.js":{"size":446,"offset":"219468"},"i18n.js":{"size":2683,"offset":"219914"},"storage.js":{"size":3731,"offset":"222597"},"web-navigation.js":{"size":621,"offset":"226328"}}},"init.js":{"size":6149,"offset":"226949"},"inspector.js":{"size":3457,"offset":"233098"},"ipc-renderer-internal.js":{"size":759,"offset":"236555"},"override.js":{"size":783,"offset":"237314"},"remote.js":{"size":220,"offset":"238097"},"security-warnings.js":{"size":11985,"offset":"238317"},"web-frame-init.js":{"size":847,"offset":"250302"},"web-view":{"files":{"guest-view-internal.js":{"size":4383,"offset":"251149"},"web-view-attributes.js":{"size":9820,"offset":"255532"},"web-view-constants.js":{"size":1150,"offset":"265352"},"web-view.js":{"size":12358,"offset":"266502"}}},"window-setup.js":{"size":6179,"offset":"278860"}}},"worker":{"files":{"init.js":{"size":1244,"offset":"285039"}}}}}'use strict'
- const bindings = process.atomBinding('app')
- const path = require('path')
- const { app, App } = bindings
- // Only one app object permitted.
- module.exports = app
- const electron = require('electron')
- const { deprecate, Menu } = electron
- const { EventEmitter } = require('events')
- let dockMenu = null
- // App is an EventEmitter.
- Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
- EventEmitter.call(app)
- Object.assign(app, {
- setApplicationMenu (menu) {
- return Menu.setApplicationMenu(menu)
- },
- getApplicationMenu () {
- return Menu.getApplicationMenu()
- },
- commandLine: {
- appendSwitch (...args) {
- const castedArgs = args.map((arg) => {
- return typeof arg !== 'string' ? `${arg}` : arg
- })
- return bindings.appendSwitch(...castedArgs)
- },
- appendArgument (...args) {
- const castedArgs = args.map((arg) => {
- return typeof arg !== 'string' ? `${arg}` : arg
- })
- return bindings.appendArgument(...castedArgs)
- }
- }
- })
- const nativeFn = app.getAppMetrics
- app.getAppMetrics = () => {
- const metrics = nativeFn.call(app)
- for (const metric of metrics) {
- if ('memory' in metric) {
- deprecate.removeProperty(metric, 'memory')
- }
- }
- return metrics
- }
- app.isPackaged = (() => {
- const execFile = path.basename(process.execPath).toLowerCase()
- if (process.platform === 'win32') {
- return execFile !== 'electron.exe'
- }
- return execFile !== 'electron'
- })()
- if (process.platform === 'darwin') {
- app.dock = {
- bounce (type = 'informational') {
- return bindings.dockBounce(type)
- },
- cancelBounce: bindings.dockCancelBounce,
- downloadFinished: bindings.dockDownloadFinished,
- setBadge: bindings.dockSetBadgeText,
- getBadge: bindings.dockGetBadgeText,
- hide: bindings.dockHide,
- show: bindings.dockShow,
- isVisible: bindings.dockIsVisible,
- setMenu (menu) {
- dockMenu = menu
- bindings.dockSetMenu(menu)
- },
- getMenu () {
- return dockMenu
- },
- setIcon: bindings.dockSetIcon
- }
- }
- if (process.platform === 'linux') {
- app.launcher = {
- setBadgeCount: bindings.unityLauncherSetBadgeCount,
- getBadgeCount: bindings.unityLauncherGetBadgeCount,
- isCounterBadgeAvailable: bindings.unityLauncherAvailable,
- isUnityRunning: bindings.unityLauncherAvailable
- }
- }
- app.allowNTLMCredentialsForAllDomains = function (allow) {
- if (!process.noDeprecations) {
- deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains')
- }
- const domains = allow ? '*' : ''
- if (!this.isReady()) {
- this.commandLine.appendSwitch('auth-server-whitelist', domains)
- } else {
- electron.session.defaultSession.allowNTLMCredentialsForDomains(domains)
- }
- }
- // Routes the events to webContents.
- const events = ['login', 'certificate-error', 'select-client-certificate']
- for (const name of events) {
- app.on(name, (event, webContents, ...args) => {
- webContents.emit(name, event, ...args)
- })
- }
- // Wrappers for native classes.
- const { DownloadItem } = process.atomBinding('download_item')
- Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype)
- 'use strict'
- if (process.platform === 'win32') {
- module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win')
- } else {
- module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native')
- }
- '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
- 'use strict'
- const { app } = require('electron')
- const { EventEmitter } = require('events')
- const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win')
- class AutoUpdater extends EventEmitter {
- quitAndInstall () {
- if (!this.updateAvailable) {
- return this.emitError('No update available, can\'t quit and install')
- }
- squirrelUpdate.processStart()
- app.quit()
- }
- getFeedURL () {
- return this.updateURL
- }
- setFeedURL (options) {
- let updateURL
- if (typeof options === 'object') {
- if (typeof options.url === 'string') {
- updateURL = options.url
- } else {
- throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call')
- }
- } else if (typeof options === 'string') {
- updateURL = options
- } else {
- throw new Error('Expected an options object with a \'url\' property to be provided')
- }
- this.updateURL = updateURL
- }
- checkForUpdates () {
- if (!this.updateURL) {
- return this.emitError('Update URL is not set')
- }
- if (!squirrelUpdate.supported()) {
- return this.emitError('Can not find Squirrel')
- }
- this.emit('checking-for-update')
- squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => {
- if (error != null) {
- return this.emitError(error)
- }
- if (update == null) {
- return this.emit('update-not-available')
- }
- this.updateAvailable = true
- this.emit('update-available')
- squirrelUpdate.update(this.updateURL, (error) => {
- if (error != null) {
- return this.emitError(error)
- }
- const { releaseNotes, version } = update
- // Date is not available on Windows, so fake it.
- const date = new Date()
- this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
- this.quitAndInstall()
- })
- })
- })
- }
- // Private: Emit both error object and message, this is to keep compatibility
- // with Old APIs.
- emitError (message) {
- this.emit('error', new Error(message), message)
- }
- }
- module.exports = new AutoUpdater()
- 'use strict'
- const fs = require('fs')
- const path = require('path')
- const spawn = require('child_process').spawn
- // i.e. my-app/app-0.1.13/
- const appFolder = path.dirname(process.execPath)
- // i.e. my-app/Update.exe
- const updateExe = path.resolve(appFolder, '..', 'Update.exe')
- const exeName = path.basename(process.execPath)
- let spawnedArgs = []
- let spawnedProcess
- const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i])
- // Spawn a command and invoke the callback when it completes with an error
- // and the output from standard out.
- const spawnUpdate = function (args, detached, callback) {
- let error, errorEmitted, stderr, stdout
- try {
- // Ensure we don't spawn multiple squirrel processes
- // Process spawned, same args: Attach events to alread running process
- // Process spawned, different args: Return with error
- // No process spawned: Spawn new process
- if (spawnedProcess && !isSameArgs(args)) {
- // Disabled for backwards compatibility:
- // eslint-disable-next-line standard/no-callback-literal
- return callback(`AutoUpdater process with arguments ${args} is already running`)
- } else if (!spawnedProcess) {
- spawnedProcess = spawn(updateExe, args, {
- detached: detached,
- windowsHide: true
- })
- spawnedArgs = args || []
- }
- } catch (error1) {
- error = error1
- // Shouldn't happen, but still guard it.
- process.nextTick(function () {
- return callback(error)
- })
- return
- }
- stdout = ''
- stderr = ''
- spawnedProcess.stdout.on('data', (data) => { stdout += data })
- spawnedProcess.stderr.on('data', (data) => { stderr += data })
- errorEmitted = false
- spawnedProcess.on('error', (error) => {
- errorEmitted = true
- callback(error)
- })
- return spawnedProcess.on('exit', function (code, signal) {
- spawnedProcess = undefined
- spawnedArgs = []
- // We may have already emitted an error.
- if (errorEmitted) {
- return
- }
- // Process terminated with error.
- if (code !== 0) {
- // Disabled for backwards compatibility:
- // eslint-disable-next-line standard/no-callback-literal
- return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`)
- }
- // Success.
- callback(null, stdout)
- })
- }
- // Start an instance of the installed app.
- exports.processStart = function () {
- return spawnUpdate(['--processStartAndWait', exeName], true, function () {})
- }
- // Download the releases specified by the URL and write new results to stdout.
- exports.checkForUpdate = function (updateURL, callback) {
- return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) {
- let ref, ref1, update
- if (error != null) {
- return callback(error)
- }
- try {
- // Last line of output is the JSON details about the releases
- const json = stdout.trim().split('\n').pop()
- update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0
- } catch (jsonError) {
- // Disabled for backwards compatibility:
- // eslint-disable-next-line standard/no-callback-literal
- return callback(`Invalid result:\n${stdout}`)
- }
- return callback(null, update)
- })
- }
- // Update the application to the latest remote version specified by URL.
- exports.update = function (updateURL, callback) {
- return spawnUpdate(['--update', updateURL], false, callback)
- }
- // Is the Update.exe installed with the current application?
- exports.supported = function () {
- try {
- fs.accessSync(updateExe, fs.R_OK)
- return true
- } catch (error) {
- return false
- }
- }
- '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
- 'use strict'
- const electron = require('electron')
- const { WebContentsView, TopLevelWindow } = electron
- const { BrowserWindow } = process.atomBinding('window')
- Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype)
- BrowserWindow.prototype._init = function () {
- // Call parent class's _init.
- TopLevelWindow.prototype._init.call(this)
- // Avoid recursive require.
- const { app } = electron
- // Create WebContentsView.
- this.setContentView(new WebContentsView(this.webContents))
- const nativeSetBounds = this.setBounds
- this.setBounds = (bounds, ...opts) => {
- bounds = {
- ...this.getBounds(),
- ...bounds
- }
- nativeSetBounds.call(this, bounds, ...opts)
- }
- // window.resizeTo(...)
- // window.moveTo(...)
- this.webContents.on('move', (event, size) => {
- this.setBounds(size)
- })
- // Hide the auto-hide menu when webContents is focused.
- this.webContents.on('activate', () => {
- if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) {
- this.setMenuBarVisibility(false)
- }
- })
- // Change window title to page title.
- this.webContents.on('page-title-updated', (event, title) => {
- // Route the event to BrowserWindow.
- this.emit('page-title-updated', event, title)
- if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
- })
- // Sometimes the webContents doesn't get focus when window is shown, so we
- // have to force focusing on webContents in this case. The safest way is to
- // focus it when we first start to load URL, if we do it earlier it won't
- // have effect, if we do it later we might move focus in the page.
- //
- // Though this hack is only needed on macOS when the app is launched from
- // Finder, we still do it on all platforms in case of other bugs we don't
- // know.
- this.webContents.once('load-url', function () {
- this.focus()
- })
- // Redirect focus/blur event to app instance too.
- this.on('blur', (event) => {
- app.emit('browser-window-blur', event, this)
- })
- this.on('focus', (event) => {
- app.emit('browser-window-focus', event, this)
- })
- // Subscribe to visibilityState changes and pass to renderer process.
- let isVisible = this.isVisible() && !this.isMinimized()
- const visibilityChanged = () => {
- const newState = this.isVisible() && !this.isMinimized()
- if (isVisible !== newState) {
- isVisible = newState
- const visibilityState = isVisible ? 'visible' : 'hidden'
- this.webContents.emit('-window-visibility-change', visibilityState)
- }
- }
- const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']
- for (const event of visibilityEvents) {
- this.on(event, visibilityChanged)
- }
- // Notify the creation of the window.
- app.emit('browser-window-created', {}, this)
- Object.defineProperty(this, 'devToolsWebContents', {
- enumerable: true,
- configurable: false,
- get () {
- return this.webContents.devToolsWebContents
- }
- })
- }
- const isBrowserWindow = (win) => {
- return win && win.constructor.name === 'BrowserWindow'
- }
- BrowserWindow.fromId = (id) => {
- const win = TopLevelWindow.fromId(id)
- return isBrowserWindow(win) ? win : null
- }
- BrowserWindow.getAllWindows = () => {
- return TopLevelWindow.getAllWindows().filter(isBrowserWindow)
- }
- BrowserWindow.getFocusedWindow = () => {
- for (const window of BrowserWindow.getAllWindows()) {
- if (window.isFocused() || window.isDevToolsFocused()) return window
- }
- return null
- }
- BrowserWindow.fromWebContents = (webContents) => {
- for (const window of BrowserWindow.getAllWindows()) {
- if (window.webContents.equal(webContents)) return window
- }
- }
- BrowserWindow.fromBrowserView = (browserView) => {
- for (const window of BrowserWindow.getAllWindows()) {
- if (window.getBrowserView() === browserView) return window
- }
- return null
- }
- BrowserWindow.fromDevToolsWebContents = (webContents) => {
- for (const window of BrowserWindow.getAllWindows()) {
- const { devToolsWebContents } = window
- if (devToolsWebContents != null && devToolsWebContents.equal(webContents)) {
- return window
- }
- }
- }
- // Helpers.
- Object.assign(BrowserWindow.prototype, {
- loadURL (...args) {
- return this.webContents.loadURL(...args)
- },
- getURL (...args) {
- return this.webContents.getURL()
- },
- loadFile (...args) {
- return this.webContents.loadFile(...args)
- },
- reload (...args) {
- return this.webContents.reload(...args)
- },
- send (...args) {
- return this.webContents.send(...args)
- },
- openDevTools (...args) {
- return this.webContents.openDevTools(...args)
- },
- closeDevTools () {
- return this.webContents.closeDevTools()
- },
- isDevToolsOpened () {
- return this.webContents.isDevToolsOpened()
- },
- isDevToolsFocused () {
- return this.webContents.isDevToolsFocused()
- },
- toggleDevTools () {
- return this.webContents.toggleDevTools()
- },
- inspectElement (...args) {
- return this.webContents.inspectElement(...args)
- },
- inspectServiceWorker () {
- return this.webContents.inspectServiceWorker()
- },
- showDefinitionForSelection () {
- return this.webContents.showDefinitionForSelection()
- },
- capturePage (...args) {
- return this.webContents.capturePage(...args)
- },
- setTouchBar (touchBar) {
- electron.TouchBar._setOnWindow(touchBar, this)
- },
- setBackgroundThrottling (allowed) {
- this.webContents.setBackgroundThrottling(allowed)
- }
- })
- module.exports = BrowserWindow
- 'use strict'
- module.exports = process.atomBinding('content_tracing')
- 'use strict'
- const CrashReporter = require('@electron/internal/common/crash-reporter')
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- class CrashReporterMain extends CrashReporter {
- sendSync (channel, ...args) {
- const event = {}
- ipcMain.emit(channel, event, ...args)
- return event.returnValue
- }
- }
- module.exports = new CrashReporterMain()
- 'use strict'
- const { app, BrowserWindow } = require('electron')
- const binding = process.atomBinding('dialog')
- const v8Util = process.atomBinding('v8_util')
- const fileDialogProperties = {
- openFile: 1 << 0,
- openDirectory: 1 << 1,
- multiSelections: 1 << 2,
- createDirectory: 1 << 3,
- showHiddenFiles: 1 << 4,
- promptToCreate: 1 << 5,
- noResolveAliases: 1 << 6,
- treatPackageAsDirectory: 1 << 7
- }
- const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']
- const messageBoxOptions = {
- noLink: 1 << 0
- }
- const parseArgs = function (window, options, callback, ...args) {
- if (window != null && window.constructor !== BrowserWindow) {
- // Shift.
- [callback, options, window] = [options, window, null]
- }
- if ((callback == null) && typeof options === 'function') {
- // Shift.
- [callback, options] = [options, null]
- }
- // Fallback to using very last argument as the callback function
- const lastArgument = args[args.length - 1]
- if ((callback == null) && typeof lastArgument === 'function') {
- callback = lastArgument
- }
- return [window, options, callback]
- }
- const normalizeAccessKey = (text) => {
- if (typeof text !== 'string') return text
- // macOS does not have access keys so remove single ampersands
- // and replace double ampersands with a single ampersand
- if (process.platform === 'darwin') {
- return text.replace(/&(&?)/g, '$1')
- }
- // Linux uses a single underscore as an access key prefix so escape
- // existing single underscores with a second underscore, replace double
- // ampersands with a single ampersand, and replace a single ampersand with
- // a single underscore
- if (process.platform === 'linux') {
- return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
- if (after === '&') return after
- return `_${after}`
- })
- }
- return text
- }
- const checkAppInitialized = function () {
- if (!app.isReady()) {
- throw new Error('dialog module can only be used after app is ready')
- }
- }
- module.exports = {
- showOpenDialog: function (...args) {
- checkAppInitialized()
- let [window, options, callback] = parseArgs(...args)
- if (options == null) {
- options = {
- title: 'Open',
- properties: ['openFile']
- }
- }
- let { buttonLabel, defaultPath, filters, properties, title, message, securityScopedBookmarks = false } = options
- if (properties == null) {
- properties = ['openFile']
- } else if (!Array.isArray(properties)) {
- throw new TypeError('Properties must be an array')
- }
- let dialogProperties = 0
- for (const prop in fileDialogProperties) {
- if (properties.includes(prop)) {
- dialogProperties |= fileDialogProperties[prop]
- }
- }
- if (title == null) {
- title = ''
- } else if (typeof title !== 'string') {
- throw new TypeError('Title must be a string')
- }
- if (buttonLabel == null) {
- buttonLabel = ''
- } else if (typeof buttonLabel !== 'string') {
- throw new TypeError('Button label must be a string')
- }
- if (defaultPath == null) {
- defaultPath = ''
- } else if (typeof defaultPath !== 'string') {
- throw new TypeError('Default path must be a string')
- }
- if (filters == null) {
- filters = []
- }
- if (message == null) {
- message = ''
- } else if (typeof message !== 'string') {
- throw new TypeError('Message must be a string')
- }
- const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) {
- return success ? callback(result, bookmarkData) : callback()
- } : null
- const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window }
- settings.properties = dialogProperties
- return binding.showOpenDialog(settings, wrappedCallback)
- },
- showSaveDialog: function (...args) {
- checkAppInitialized()
- let [window, options, callback] = parseArgs(...args)
- if (options == null) {
- options = {
- title: 'Save'
- }
- }
- let { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks = false, nameFieldLabel, showsTagField } = options
- if (title == null) {
- title = ''
- } else if (typeof title !== 'string') {
- throw new TypeError('Title must be a string')
- }
- if (buttonLabel == null) {
- buttonLabel = ''
- } else if (typeof buttonLabel !== 'string') {
- throw new TypeError('Button label must be a string')
- }
- if (defaultPath == null) {
- defaultPath = ''
- } else if (typeof defaultPath !== 'string') {
- throw new TypeError('Default path must be a string')
- }
- if (filters == null) {
- filters = []
- }
- if (message == null) {
- message = ''
- } else if (typeof message !== 'string') {
- throw new TypeError('Message must be a string')
- }
- if (nameFieldLabel == null) {
- nameFieldLabel = ''
- } else if (typeof nameFieldLabel !== 'string') {
- throw new TypeError('Name field label must be a string')
- }
- if (showsTagField == null) {
- showsTagField = true
- }
- const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) {
- return success ? callback(result, bookmarkData) : callback()
- } : null
- const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window }
- return binding.showSaveDialog(settings, wrappedCallback)
- },
- showMessageBox: function (...args) {
- checkAppInitialized()
- let [window, options, callback] = parseArgs(...args)
- if (options == null) {
- options = {
- type: 'none'
- }
- }
- let {
- buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail,
- icon, message, title, type
- } = options
- if (type == null) {
- type = 'none'
- }
- const messageBoxType = messageBoxTypes.indexOf(type)
- if (messageBoxType === -1) {
- throw new TypeError('Invalid message box type')
- }
- if (buttons == null) {
- buttons = []
- } else if (!Array.isArray(buttons)) {
- throw new TypeError('Buttons must be an array')
- }
- if (options.normalizeAccessKeys) {
- buttons = buttons.map(normalizeAccessKey)
- }
- if (title == null) {
- title = ''
- } else if (typeof title !== 'string') {
- throw new TypeError('Title must be a string')
- }
- if (message == null) {
- message = ''
- } else if (typeof message !== 'string') {
- throw new TypeError('Message must be a string')
- }
- if (detail == null) {
- detail = ''
- } else if (typeof detail !== 'string') {
- throw new TypeError('Detail must be a string')
- }
- checkboxChecked = !!checkboxChecked
- if (checkboxLabel == null) {
- checkboxLabel = ''
- } else if (typeof checkboxLabel !== 'string') {
- throw new TypeError('checkboxLabel must be a string')
- }
- if (icon == null) {
- icon = null
- }
- if (defaultId == null) {
- defaultId = -1
- }
- // Choose a default button to get selected when dialog is cancelled.
- if (cancelId == null) {
- // If the defaultId is set to 0, ensure the cancel button is a different index (1)
- cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0
- for (let i = 0; i < buttons.length; i++) {
- const text = buttons[i].toLowerCase()
- if (text === 'cancel' || text === 'no') {
- cancelId = i
- break
- }
- }
- }
- const flags = options.noLink ? messageBoxOptions.noLink : 0
- return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
- flags, title, message, detail, checkboxLabel,
- checkboxChecked, icon, window, callback)
- },
- showErrorBox: function (...args) {
- return binding.showErrorBox(...args)
- },
- showCertificateTrustDialog: function (...args) {
- const [window, options, callback] = parseArgs(...args)
- if (options == null || typeof options !== 'object') {
- throw new TypeError('options must be an object')
- }
- let { certificate, message } = options
- if (certificate == null || typeof certificate !== 'object') {
- throw new TypeError('certificate must be an object')
- }
- if (message == null) {
- message = ''
- } else if (typeof message !== 'string') {
- throw new TypeError('message must be a string')
- }
- return binding.showCertificateTrustDialog(window, certificate, message, callback)
- }
- }
- // Mark standard asynchronous functions.
- v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)
- v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true)
- v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true)
- 'use strict'
- const common = require('@electron/internal/common/api/exports/electron')
- // since browser module list is also used in renderer, keep it separate.
- const moduleList = require('@electron/internal/browser/api/module-list')
- // Import common modules.
- common.defineProperties(exports)
- for (const module of moduleList) {
- Object.defineProperty(exports, module.name, {
- enumerable: !module.private,
- get: common.memoizedGetter(() => require(`@electron/internal/browser/api/${module.file}.js`))
- })
- }
- 'use strict'
- module.exports = process.atomBinding('global_shortcut').globalShortcut
- 'use strict'
- if (process.platform === 'darwin') {
- const { EventEmitter } = require('events')
- const { inAppPurchase, InAppPurchase } = process.atomBinding('in_app_purchase')
- // inAppPurchase is an EventEmitter.
- Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype)
- EventEmitter.call(inAppPurchase)
- module.exports = inAppPurchase
- } else {
- module.exports = {
- purchaseProduct: (productID, quantity, callback) => {
- throw new Error('The inAppPurchase module can only be used on macOS')
- },
- canMakePayments: () => false,
- getReceiptURL: () => ''
- }
- }
- 'use strict'
- const { EventEmitter } = require('events')
- const emitter = new EventEmitter()
- // Do not throw exception when channel name is "error".
- emitter.on('error', () => {})
- module.exports = emitter
- 'use strict'
- const { app } = require('electron')
- const roles = {
- about: {
- get label () {
- return process.platform === 'linux' ? 'About' : `About ${app.getName()}`
- }
- },
- close: {
- label: process.platform === 'darwin' ? 'Close Window' : 'Close',
- accelerator: 'CommandOrControl+W',
- windowMethod: 'close'
- },
- copy: {
- label: 'Copy',
- accelerator: 'CommandOrControl+C',
- webContentsMethod: 'copy',
- registerAccelerator: false
- },
- cut: {
- label: 'Cut',
- accelerator: 'CommandOrControl+X',
- webContentsMethod: 'cut',
- registerAccelerator: false
- },
- delete: {
- label: 'Delete',
- webContentsMethod: 'delete'
- },
- forcereload: {
- label: 'Force Reload',
- accelerator: 'Shift+CmdOrCtrl+R',
- nonNativeMacOSRole: true,
- windowMethod: (window) => {
- window.webContents.reloadIgnoringCache()
- }
- },
- front: {
- label: 'Bring All to Front'
- },
- help: {
- label: 'Help'
- },
- hide: {
- get label () {
- return `Hide ${app.getName()}`
- },
- accelerator: 'Command+H'
- },
- hideothers: {
- label: 'Hide Others',
- accelerator: 'Command+Alt+H'
- },
- minimize: {
- label: 'Minimize',
- accelerator: 'CommandOrControl+M',
- windowMethod: 'minimize'
- },
- paste: {
- label: 'Paste',
- accelerator: 'CommandOrControl+V',
- webContentsMethod: 'paste',
- registerAccelerator: false
- },
- pasteandmatchstyle: {
- label: 'Paste and Match Style',
- accelerator: 'Shift+CommandOrControl+V',
- webContentsMethod: 'pasteAndMatchStyle',
- registerAccelerator: false
- },
- quit: {
- get label () {
- switch (process.platform) {
- case 'darwin': return `Quit ${app.getName()}`
- case 'win32': return 'Exit'
- default: return 'Quit'
- }
- },
- accelerator: process.platform === 'win32' ? null : 'CommandOrControl+Q',
- appMethod: 'quit'
- },
- redo: {
- label: 'Redo',
- accelerator: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z',
- webContentsMethod: 'redo'
- },
- reload: {
- label: 'Reload',
- accelerator: 'CmdOrCtrl+R',
- nonNativeMacOSRole: true,
- windowMethod: 'reload'
- },
- resetzoom: {
- label: 'Actual Size',
- accelerator: 'CommandOrControl+0',
- nonNativeMacOSRole: true,
- webContentsMethod: (webContents) => {
- webContents.setZoomLevel(0)
- }
- },
- selectall: {
- label: 'Select All',
- accelerator: 'CommandOrControl+A',
- webContentsMethod: 'selectAll'
- },
- services: {
- label: 'Services'
- },
- recentdocuments: {
- label: 'Open Recent'
- },
- clearrecentdocuments: {
- label: 'Clear Menu'
- },
- startspeaking: {
- label: 'Start Speaking'
- },
- stopspeaking: {
- label: 'Stop Speaking'
- },
- toggledevtools: {
- label: 'Toggle Developer Tools',
- accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
- nonNativeMacOSRole: true,
- windowMethod: 'toggleDevTools'
- },
- togglefullscreen: {
- label: 'Toggle Full Screen',
- accelerator: process.platform === 'darwin' ? 'Control+Command+F' : 'F11',
- windowMethod: (window) => {
- window.setFullScreen(!window.isFullScreen())
- }
- },
- undo: {
- label: 'Undo',
- accelerator: 'CommandOrControl+Z',
- webContentsMethod: 'undo'
- },
- unhide: {
- label: 'Show All'
- },
- window: {
- label: 'Window'
- },
- zoom: {
- label: 'Zoom'
- },
- zoomin: {
- label: 'Zoom In',
- accelerator: 'CommandOrControl+Plus',
- nonNativeMacOSRole: true,
- webContentsMethod: (webContents) => {
- webContents.getZoomLevel((zoomLevel) => {
- webContents.setZoomLevel(zoomLevel + 0.5)
- })
- }
- },
- zoomout: {
- label: 'Zoom Out',
- accelerator: 'CommandOrControl+-',
- nonNativeMacOSRole: true,
- webContentsMethod: (webContents) => {
- webContents.getZoomLevel((zoomLevel) => {
- webContents.setZoomLevel(zoomLevel - 0.5)
- })
- }
- },
- // Edit submenu (should fit both Mac & Windows)
- editmenu: {
- label: 'Edit',
- submenu: [
- {
- role: 'undo'
- },
- {
- role: 'redo'
- },
- {
- type: 'separator'
- },
- {
- role: 'cut'
- },
- {
- role: 'copy'
- },
- {
- role: 'paste'
- },
- process.platform === 'darwin' ? {
- role: 'pasteAndMatchStyle'
- } : null,
- {
- role: 'delete'
- },
- process.platform === 'win32' ? {
- type: 'separator'
- } : null,
- {
- role: 'selectAll'
- }
- ]
- },
- // Window submenu should be used for Mac only
- windowmenu: {
- label: 'Window',
- submenu: [
- {
- role: 'minimize'
- },
- {
- role: 'close'
- },
- process.platform === 'darwin' ? {
- type: 'separator'
- } : null,
- process.platform === 'darwin' ? {
- role: 'front'
- } : null
- ]
- }
- }
- const canExecuteRole = (role) => {
- if (!roles.hasOwnProperty(role)) return false
- if (process.platform !== 'darwin') return true
- // macOS handles all roles natively except for a few
- return roles[role].nonNativeMacOSRole
- }
- exports.getDefaultLabel = (role) => {
- return roles.hasOwnProperty(role) ? roles[role].label : ''
- }
- exports.getDefaultAccelerator = (role) => {
- if (roles.hasOwnProperty(role)) return roles[role].accelerator
- }
- exports.shouldRegisterAccelerator = (role) => {
- const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined
- return hasRoleRegister ? roles[role].registerAccelerator : true
- }
- exports.getDefaultSubmenu = (role) => {
- if (!roles.hasOwnProperty(role)) return
- let { submenu } = roles[role]
- // remove null items from within the submenu
- if (Array.isArray(submenu)) {
- submenu = submenu.filter((item) => item != null)
- }
- return submenu
- }
- exports.execute = (role, focusedWindow, focusedWebContents) => {
- if (!canExecuteRole(role)) return false
- const { appMethod, webContentsMethod, windowMethod } = roles[role]
- if (appMethod) {
- app[appMethod]()
- return true
- }
- if (windowMethod && focusedWindow != null) {
- if (typeof windowMethod === 'function') {
- windowMethod(focusedWindow)
- } else {
- focusedWindow[windowMethod]()
- }
- return true
- }
- if (webContentsMethod && focusedWebContents != null) {
- if (typeof webContentsMethod === 'function') {
- webContentsMethod(focusedWebContents)
- } else {
- focusedWebContents[webContentsMethod]()
- }
- return true
- }
- return false
- }
- 'use strict'
- const roles = require('@electron/internal/browser/api/menu-item-roles')
- let nextCommandId = 0
- const MenuItem = function (options) {
- const { Menu } = require('electron')
- // Preserve extra fields specified by user
- for (const key in options) {
- if (!(key in this)) this[key] = options[key]
- }
- if (typeof this.role === 'string' || this.role instanceof String) {
- this.role = this.role.toLowerCase()
- }
- this.submenu = this.submenu || roles.getDefaultSubmenu(this.role)
- if (this.submenu != null && this.submenu.constructor !== Menu) {
- this.submenu = Menu.buildFromTemplate(this.submenu)
- }
- if (this.type == null && this.submenu != null) {
- this.type = 'submenu'
- }
- if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) {
- throw new Error('Invalid submenu')
- }
- this.overrideReadOnlyProperty('type', 'normal')
- this.overrideReadOnlyProperty('role')
- this.overrideReadOnlyProperty('accelerator')
- this.overrideReadOnlyProperty('icon')
- this.overrideReadOnlyProperty('submenu')
- this.overrideProperty('label', roles.getDefaultLabel(this.role))
- this.overrideProperty('sublabel', '')
- this.overrideProperty('enabled', true)
- this.overrideProperty('visible', true)
- this.overrideProperty('checked', false)
- this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role))
- if (!MenuItem.types.includes(this.type)) {
- throw new Error(`Unknown menu item type: ${this.type}`)
- }
- this.overrideReadOnlyProperty('commandId', ++nextCommandId)
- const click = options.click
- this.click = (event, focusedWindow, focusedWebContents) => {
- // Manually flip the checked flags when clicked.
- if (this.type === 'checkbox' || this.type === 'radio') {
- this.checked = !this.checked
- }
- if (!roles.execute(this.role, focusedWindow, focusedWebContents)) {
- if (typeof click === 'function') {
- click(this, focusedWindow, event)
- } else if (typeof this.selector === 'string' && process.platform === 'darwin') {
- Menu.sendActionToFirstResponder(this.selector)
- }
- }
- }
- }
- MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']
- MenuItem.prototype.getDefaultRoleAccelerator = function () {
- return roles.getDefaultAccelerator(this.role)
- }
- MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
- if (this[name] == null) {
- this[name] = defaultValue
- }
- }
- MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
- this.overrideProperty(name, defaultValue)
- Object.defineProperty(this, name, {
- enumerable: true,
- writable: false,
- value: this[name]
- })
- }
- module.exports = MenuItem
- 'use strict'
- function splitArray (arr, predicate) {
- const result = arr.reduce((multi, item) => {
- const current = multi[multi.length - 1]
- if (predicate(item)) {
- if (current.length > 0) multi.push([])
- } else {
- current.push(item)
- }
- return multi
- }, [[]])
- if (result[result.length - 1].length === 0) {
- return result.slice(0, result.length - 1)
- }
- return result
- }
- function joinArrays (arrays, joinIDs) {
- return arrays.reduce((joined, arr, i) => {
- if (i > 0 && arr.length) {
- if (joinIDs.length > 0) {
- joined.push(joinIDs[0])
- joinIDs.splice(0, 1)
- } else {
- joined.push({ type: 'separator' })
- }
- }
- return joined.concat(arr)
- }, [])
- }
- function pushOntoMultiMap (map, key, value) {
- if (!map.has(key)) {
- map.set(key, [])
- }
- map.get(key).push(value)
- }
- function indexOfGroupContainingID (groups, id, ignoreGroup) {
- return groups.findIndex(
- candidateGroup =>
- candidateGroup !== ignoreGroup &&
- candidateGroup.some(
- candidateItem => candidateItem.id === id
- )
- )
- }
- // Sort nodes topologically using a depth-first approach. Encountered cycles
- // are broken.
- function sortTopologically (originalOrder, edgesById) {
- const sorted = []
- const marked = new Set()
- const visit = (mark) => {
- if (marked.has(mark)) return
- marked.add(mark)
- const edges = edgesById.get(mark)
- if (edges != null) {
- edges.forEach(visit)
- }
- sorted.push(mark)
- }
- originalOrder.forEach(visit)
- return sorted
- }
- function attemptToMergeAGroup (groups) {
- for (let i = 0; i < groups.length; i++) {
- const group = groups[i]
- for (const item of group) {
- const toIDs = [...(item.before || []), ...(item.after || [])]
- for (const id of toIDs) {
- const index = indexOfGroupContainingID(groups, id, group)
- if (index === -1) continue
- const mergeTarget = groups[index]
- mergeTarget.push(...group)
- groups.splice(i, 1)
- return true
- }
- }
- }
- return false
- }
- function mergeGroups (groups) {
- let merged = true
- while (merged) {
- merged = attemptToMergeAGroup(groups)
- }
- return groups
- }
- function sortItemsInGroup (group) {
- const originalOrder = group.map((node, i) => i)
- const edges = new Map()
- const idToIndex = new Map(group.map((item, i) => [item.id, i]))
- group.forEach((item, i) => {
- if (item.before) {
- item.before.forEach(toID => {
- const to = idToIndex.get(toID)
- if (to != null) {
- pushOntoMultiMap(edges, to, i)
- }
- })
- }
- if (item.after) {
- item.after.forEach(toID => {
- const to = idToIndex.get(toID)
- if (to != null) {
- pushOntoMultiMap(edges, i, to)
- }
- })
- }
- })
- const sortedNodes = sortTopologically(originalOrder, edges)
- return sortedNodes.map(i => group[i])
- }
- function findEdgesInGroup (groups, i, edges) {
- const group = groups[i]
- for (const item of group) {
- if (item.beforeGroupContaining) {
- for (const id of item.beforeGroupContaining) {
- const to = indexOfGroupContainingID(groups, id, group)
- if (to !== -1) {
- pushOntoMultiMap(edges, to, i)
- return
- }
- }
- }
- if (item.afterGroupContaining) {
- for (const id of item.afterGroupContaining) {
- const to = indexOfGroupContainingID(groups, id, group)
- if (to !== -1) {
- pushOntoMultiMap(edges, i, to)
- return
- }
- }
- }
- }
- }
- function sortGroups (groups) {
- const originalOrder = groups.map((item, i) => i)
- const edges = new Map()
- for (let i = 0; i < groups.length; i++) {
- findEdgesInGroup(groups, i, edges)
- }
- const sortedGroupIndexes = sortTopologically(originalOrder, edges)
- return sortedGroupIndexes.map(i => groups[i])
- }
- function sortMenuItems (menuItems) {
- const isSeparator = (item) => item.type === 'separator'
- const separators = menuItems.filter(i => i.type === 'separator')
- // Split the items into their implicit groups based upon separators.
- const groups = splitArray(menuItems, isSeparator)
- const mergedGroups = mergeGroups(groups)
- const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup)
- const sortedGroups = sortGroups(mergedGroupsWithSortedItems)
- const joined = joinArrays(sortedGroups, separators)
- return joined
- }
- module.exports = { sortMenuItems }
- 'use strict'
- const { TopLevelWindow, MenuItem, webContents } = require('electron')
- const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils')
- const EventEmitter = require('events').EventEmitter
- const v8Util = process.atomBinding('v8_util')
- const bindings = process.atomBinding('menu')
- const { Menu } = bindings
- let applicationMenu = null
- let groupIdIndex = 0
- Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype)
- // Menu Delegate.
- // This object should hold no reference to |Menu| to avoid cyclic reference.
- const delegate = {
- isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined,
- isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined,
- isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined,
- getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => {
- const command = menu.commandsMap[id]
- if (!command) return
- if (command.accelerator != null) return command.accelerator
- if (useDefaultAccelerator) return command.getDefaultRoleAccelerator()
- },
- shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined,
- executeCommand: (menu, event, id) => {
- const command = menu.commandsMap[id]
- if (!command) return
- command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents())
- },
- menuWillShow: (menu) => {
- // Ensure radio groups have at least one menu item seleted
- for (const id in menu.groupsMap) {
- const found = menu.groupsMap[id].find(item => item.checked) || null
- if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true)
- }
- }
- }
- /* Instance Methods */
- Menu.prototype._init = function () {
- this.commandsMap = {}
- this.groupsMap = {}
- this.items = []
- this.delegate = delegate
- }
- Menu.prototype.popup = function (options = {}) {
- if (options == null || typeof options !== 'object') {
- throw new TypeError('Options must be an object')
- }
- let { window, x, y, positioningItem, callback } = options
- // no callback passed
- if (!callback || typeof callback !== 'function') callback = () => {}
- // set defaults
- if (typeof x !== 'number') x = -1
- if (typeof y !== 'number') y = -1
- if (typeof positioningItem !== 'number') positioningItem = -1
- // find which window to use
- const wins = TopLevelWindow.getAllWindows()
- if (!wins || wins.indexOf(window) === -1) {
- window = TopLevelWindow.getFocusedWindow()
- if (!window && wins && wins.length > 0) {
- window = wins[0]
- }
- if (!window) {
- throw new Error(`Cannot open Menu without a TopLevelWindow present`)
- }
- }
- this.popupAt(window, x, y, positioningItem, callback)
- return { browserWindow: window, x, y, position: positioningItem }
- }
- Menu.prototype.closePopup = function (window) {
- if (window instanceof TopLevelWindow) {
- this.closePopupAt(window.id)
- } else {
- // Passing -1 (invalid) would make closePopupAt close the all menu runners
- // belong to this menu.
- this.closePopupAt(-1)
- }
- }
- Menu.prototype.getMenuItemById = function (id) {
- const items = this.items
- let found = items.find(item => item.id === id) || null
- for (let i = 0; !found && i < items.length; i++) {
- if (items[i].submenu) {
- found = items[i].submenu.getMenuItemById(id)
- }
- }
- return found
- }
- Menu.prototype.append = function (item) {
- return this.insert(this.getItemCount(), item)
- }
- Menu.prototype.insert = function (pos, item) {
- if ((item ? item.constructor : void 0) !== MenuItem) {
- throw new TypeError('Invalid item')
- }
- // insert item depending on its type
- insertItemByType.call(this, item, pos)
- // set item properties
- if (item.sublabel) this.setSublabel(pos, item.sublabel)
- if (item.icon) this.setIcon(pos, item.icon)
- if (item.role) this.setRole(pos, item.role)
- // Make menu accessable to items.
- item.overrideReadOnlyProperty('menu', this)
- // Remember the items.
- this.items.splice(pos, 0, item)
- this.commandsMap[item.commandId] = item
- }
- Menu.prototype._callMenuWillShow = function () {
- if (this.delegate) this.delegate.menuWillShow(this)
- this.items.forEach(item => {
- if (item.submenu) item.submenu._callMenuWillShow()
- })
- }
- /* Static Methods */
- Menu.getApplicationMenu = () => applicationMenu
- Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder
- // set application menu with a preexisting menu
- Menu.setApplicationMenu = function (menu) {
- if (menu && menu.constructor !== Menu) {
- throw new TypeError('Invalid menu')
- }
- applicationMenu = menu
- if (process.platform === 'darwin') {
- if (!menu) return
- menu._callMenuWillShow()
- bindings.setApplicationMenu(menu)
- } else {
- const windows = TopLevelWindow.getAllWindows()
- return windows.map(w => w.setMenu(menu))
- }
- }
- Menu.buildFromTemplate = function (template) {
- if (!Array.isArray(template)) {
- throw new TypeError('Invalid template for Menu: Menu template must be an array')
- }
- const menu = new Menu()
- if (!areValidTemplateItems(template)) {
- throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type')
- }
- const filtered = removeExtraSeparators(template)
- const sorted = sortTemplate(filtered)
- sorted.forEach((item) => menu.append(new MenuItem(item)))
- return menu
- }
- /* Helper Functions */
- // validate the template against having the wrong attribute
- function areValidTemplateItems (template) {
- return template.every(item =>
- item != null && typeof item === 'object' && (item.hasOwnProperty('label') || item.hasOwnProperty('role') || item.type === 'separator'))
- }
- function sortTemplate (template) {
- const sorted = sortMenuItems(template)
- for (const id in sorted) {
- const item = sorted[id]
- if (Array.isArray(item.submenu)) {
- item.submenu = sortTemplate(item.submenu)
- }
- }
- return sorted
- }
- // Search between separators to find a radio menu item and return its group id
- function generateGroupId (items, pos) {
- if (pos > 0) {
- for (let idx = pos - 1; idx >= 0; idx--) {
- if (items[idx].type === 'radio') return items[idx].groupId
- if (items[idx].type === 'separator') break
- }
- } else if (pos < items.length) {
- for (let idx = pos; idx <= items.length - 1; idx++) {
- if (items[idx].type === 'radio') return items[idx].groupId
- if (items[idx].type === 'separator') break
- }
- }
- groupIdIndex += 1
- return groupIdIndex
- }
- function removeExtraSeparators (items) {
- // fold adjacent separators together
- let ret = items.filter((e, idx, arr) => {
- if (e.visible === false) return true
- return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator'
- })
- // remove edge separators
- ret = ret.filter((e, idx, arr) => {
- if (e.visible === false) return true
- return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1)
- })
- return ret
- }
- function insertItemByType (item, pos) {
- const types = {
- normal: () => this.insertItem(pos, item.commandId, item.label),
- checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
- separator: () => this.insertSeparator(pos),
- submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
- radio: () => {
- // Grouping radio menu items
- item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos))
- if (this.groupsMap[item.groupId] == null) {
- this.groupsMap[item.groupId] = []
- }
- this.groupsMap[item.groupId].push(item)
- // Setting a radio menu item should flip other items in the group.
- v8Util.setHiddenValue(item, 'checked', item.checked)
- Object.defineProperty(item, 'checked', {
- enumerable: true,
- get: () => v8Util.getHiddenValue(item, 'checked'),
- set: () => {
- this.groupsMap[item.groupId].forEach(other => {
- if (other !== item) v8Util.setHiddenValue(other, 'checked', false)
- })
- v8Util.setHiddenValue(item, 'checked', true)
- }
- })
- this.insertRadioItem(pos, item.commandId, item.label, item.groupId)
- }
- }
- types[item.type]()
- }
- module.exports = Menu
- 'use strict'
- const features = process.atomBinding('features')
- // Browser side modules, please sort alphabetically.
- module.exports = [
- { name: 'app', file: 'app' },
- { name: 'autoUpdater', file: 'auto-updater' },
- { name: 'BrowserView', file: 'browser-view' },
- { name: 'BrowserWindow', file: 'browser-window' },
- { name: 'contentTracing', file: 'content-tracing' },
- { name: 'crashReporter', file: 'crash-reporter' },
- { name: 'dialog', file: 'dialog' },
- { name: 'globalShortcut', file: 'global-shortcut' },
- { name: 'ipcMain', file: 'ipc-main' },
- { name: 'inAppPurchase', file: 'in-app-purchase' },
- { name: 'Menu', file: 'menu' },
- { name: 'MenuItem', file: 'menu-item' },
- { name: 'net', file: 'net' },
- { name: 'netLog', file: 'net-log' },
- { name: 'Notification', file: 'notification' },
- { name: 'powerMonitor', file: 'power-monitor' },
- { name: 'powerSaveBlocker', file: 'power-save-blocker' },
- { name: 'protocol', file: 'protocol' },
- { name: 'screen', file: 'screen' },
- { name: 'session', file: 'session' },
- { name: 'systemPreferences', file: 'system-preferences' },
- { name: 'TopLevelWindow', file: 'top-level-window' },
- { name: 'TouchBar', file: 'touch-bar' },
- { name: 'Tray', file: 'tray' },
- { name: 'View', file: 'view' },
- { name: 'webContents', file: 'web-contents' },
- { name: 'WebContentsView', file: 'web-contents-view' },
- // The internal modules, invisible unless you know their names.
- { name: 'NavigationController', file: 'navigation-controller', private: true }
- ]
- if (features.isViewApiEnabled()) {
- module.exports.push(
- { name: 'BoxLayout', file: 'views/box-layout' },
- { name: 'Button', file: 'views/button' },
- { name: 'LabelButton', file: 'views/label-button' },
- { name: 'LayoutManager', file: 'views/layout-manager' },
- { name: 'TextField', file: 'views/text-field' }
- )
- }
- '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
- 'use strict'
- // TODO(deepak1556): Deprecate and remove standalone netLog module,
- // it is now a property of sessio module.
- const { app, session } = require('electron')
- // Fallback to default session.
- Object.setPrototypeOf(module.exports, new Proxy({}, {
- get (target, property) {
- if (!app.isReady()) return
- const netLog = session.defaultSession.netLog
- if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return
- // Returning a native function directly would throw error.
- return (...args) => netLog[property](...args)
- },
- ownKeys () {
- if (!app.isReady()) return []
- return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog))
- },
- getOwnPropertyDescriptor (target) {
- return { configurable: true, enumerable: true }
- }
- }))
- 'use strict'
- const url = require('url')
- const { EventEmitter } = require('events')
- const { Readable } = require('stream')
- const { app } = require('electron')
- const { Session } = process.atomBinding('session')
- const { net, Net } = process.atomBinding('net')
- const { URLRequest } = net
- // Net is an EventEmitter.
- Object.setPrototypeOf(Net.prototype, EventEmitter.prototype)
- EventEmitter.call(net)
- Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
- const kSupportedProtocols = new Set(['http:', 'https:'])
- class IncomingMessage extends Readable {
- constructor (urlRequest) {
- super()
- this.urlRequest = urlRequest
- this.shouldPush = false
- this.data = []
- this.urlRequest.on('data', (event, chunk) => {
- this._storeInternalData(chunk)
- this._pushInternalData()
- })
- this.urlRequest.on('end', () => {
- this._storeInternalData(null)
- this._pushInternalData()
- })
- }
- get statusCode () {
- return this.urlRequest.statusCode
- }
- get statusMessage () {
- return this.urlRequest.statusMessage
- }
- get headers () {
- return this.urlRequest.rawResponseHeaders
- }
- get httpVersion () {
- return `${this.httpVersionMajor}.${this.httpVersionMinor}`
- }
- get httpVersionMajor () {
- return this.urlRequest.httpVersionMajor
- }
- get httpVersionMinor () {
- return this.urlRequest.httpVersionMinor
- }
- get rawTrailers () {
- throw new Error('HTTP trailers are not supported.')
- }
- get trailers () {
- throw new Error('HTTP trailers are not supported.')
- }
- _storeInternalData (chunk) {
- this.data.push(chunk)
- }
- _pushInternalData () {
- while (this.shouldPush && this.data.length > 0) {
- const chunk = this.data.shift()
- this.shouldPush = this.push(chunk)
- }
- }
- _read () {
- this.shouldPush = true
- this._pushInternalData()
- }
- }
- URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) {
- if (isAsync) {
- process.nextTick(() => {
- this.clientRequest.emit(...rest)
- })
- } else {
- this.clientRequest.emit(...rest)
- }
- }
- URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) {
- if (isAsync) {
- process.nextTick(() => {
- this._response.emit(...rest)
- })
- } else {
- this._response.emit(...rest)
- }
- }
- class ClientRequest extends EventEmitter {
- constructor (options, callback) {
- super()
- if (!app.isReady()) {
- throw new Error('net module can only be used after app is ready')
- }
- if (typeof options === 'string') {
- options = url.parse(options)
- } else {
- options = Object.assign({}, options)
- }
- const method = (options.method || 'GET').toUpperCase()
- let urlStr = options.url
- if (!urlStr) {
- const urlObj = {}
- const protocol = options.protocol || 'http:'
- if (!kSupportedProtocols.has(protocol)) {
- throw new Error('Protocol "' + protocol + '" not supported. ')
- }
- urlObj.protocol = protocol
- if (options.host) {
- urlObj.host = options.host
- } else {
- if (options.hostname) {
- urlObj.hostname = options.hostname
- } else {
- urlObj.hostname = 'localhost'
- }
- if (options.port) {
- urlObj.port = options.port
- }
- }
- if (options.path && / /.test(options.path)) {
- // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
- // with an additional rule for ignoring percentage-escaped characters
- // but that's a) hard to capture in a regular expression that performs
- // well, and b) possibly too restrictive for real-world usage. That's
- // why it only scans for spaces because those are guaranteed to create
- // an invalid request.
- throw new TypeError('Request path contains unescaped characters.')
- }
- const pathObj = url.parse(options.path || '/')
- urlObj.pathname = pathObj.pathname
- urlObj.search = pathObj.search
- urlObj.hash = pathObj.hash
- urlStr = url.format(urlObj)
- }
- const redirectPolicy = options.redirect || 'follow'
- if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
- throw new Error('redirect mode should be one of follow, error or manual')
- }
- const urlRequestOptions = {
- method: method,
- url: urlStr,
- redirect: redirectPolicy
- }
- if (options.session) {
- if (options.session instanceof Session) {
- urlRequestOptions.session = options.session
- } else {
- throw new TypeError('`session` should be an instance of the Session class.')
- }
- } else if (options.partition) {
- if (typeof options.partition === 'string') {
- urlRequestOptions.partition = options.partition
- } else {
- throw new TypeError('`partition` should be an a string.')
- }
- }
- const urlRequest = new URLRequest(urlRequestOptions)
- // Set back and forward links.
- this.urlRequest = urlRequest
- urlRequest.clientRequest = this
- // This is a copy of the extra headers structure held by the native
- // net::URLRequest. The main reason is to keep the getHeader API synchronous
- // after the request starts.
- this.extraHeaders = {}
- if (options.headers) {
- for (const key in options.headers) {
- this.setHeader(key, options.headers[key])
- }
- }
- // Set when the request uses chunked encoding. Can be switched
- // to true only once and never set back to false.
- this.chunkedEncodingEnabled = false
- urlRequest.on('response', () => {
- const response = new IncomingMessage(urlRequest)
- urlRequest._response = response
- this.emit('response', response)
- })
- urlRequest.on('login', (event, authInfo, callback) => {
- this.emit('login', authInfo, (username, password) => {
- // If null or undefined username/password, force to empty string.
- if (username === null || username === undefined) {
- username = ''
- }
- if (typeof username !== 'string') {
- throw new Error('username must be a string')
- }
- if (password === null || password === undefined) {
- password = ''
- }
- if (typeof password !== 'string') {
- throw new Error('password must be a string')
- }
- callback(username, password)
- })
- })
- if (callback) {
- this.once('response', callback)
- }
- }
- get chunkedEncoding () {
- return this.chunkedEncodingEnabled
- }
- set chunkedEncoding (value) {
- if (!this.urlRequest.notStarted) {
- throw new Error('Can\'t set the transfer encoding, headers have been sent.')
- }
- this.chunkedEncodingEnabled = value
- }
- setHeader (name, value) {
- if (typeof name !== 'string') {
- throw new TypeError('`name` should be a string in setHeader(name, value).')
- }
- if (value == null) {
- throw new Error('`value` required in setHeader("' + name + '", value).')
- }
- if (!this.urlRequest.notStarted) {
- throw new Error('Can\'t set headers after they are sent.')
- }
- const key = name.toLowerCase()
- this.extraHeaders[key] = value
- this.urlRequest.setExtraHeader(name, value.toString())
- }
- getHeader (name) {
- if (name == null) {
- throw new Error('`name` is required for getHeader(name).')
- }
- if (!this.extraHeaders) {
- return
- }
- const key = name.toLowerCase()
- return this.extraHeaders[key]
- }
- removeHeader (name) {
- if (name == null) {
- throw new Error('`name` is required for removeHeader(name).')
- }
- if (!this.urlRequest.notStarted) {
- throw new Error('Can\'t remove headers after they are sent.')
- }
- const key = name.toLowerCase()
- delete this.extraHeaders[key]
- this.urlRequest.removeExtraHeader(name)
- }
- _write (chunk, encoding, callback, isLast) {
- const chunkIsString = typeof chunk === 'string'
- const chunkIsBuffer = chunk instanceof Buffer
- if (!chunkIsString && !chunkIsBuffer) {
- throw new TypeError('First argument must be a string or Buffer.')
- }
- if (chunkIsString) {
- // We convert all strings into binary buffers.
- chunk = Buffer.from(chunk, encoding)
- }
- // Since writing to the network is asynchronous, we conservatively
- // assume that request headers are written after delivering the first
- // buffer to the network IO thread.
- if (this.urlRequest.notStarted) {
- this.urlRequest.setChunkedUpload(this.chunkedEncoding)
- }
- // Headers are assumed to be sent on first call to _writeBuffer,
- // i.e. after the first call to write or end.
- const result = this.urlRequest.write(chunk, isLast)
- // The write callback is fired asynchronously to mimic Node.js.
- if (callback) {
- process.nextTick(callback)
- }
- return result
- }
- write (data, encoding, callback) {
- if (this.urlRequest.finished) {
- const error = new Error('Write after end.')
- process.nextTick(writeAfterEndNT, this, error, callback)
- return true
- }
- return this._write(data, encoding, callback, false)
- }
- end (data, encoding, callback) {
- if (this.urlRequest.finished) {
- return false
- }
- if (typeof data === 'function') {
- callback = data
- encoding = null
- data = null
- } else if (typeof encoding === 'function') {
- callback = encoding
- encoding = null
- }
- data = data || ''
- return this._write(data, encoding, callback, true)
- }
- followRedirect () {
- this.urlRequest.followRedirect()
- }
- abort () {
- this.urlRequest.cancel()
- }
- getUploadProgress () {
- return this.urlRequest.getUploadProgress()
- }
- }
- function writeAfterEndNT (self, error, callback) {
- self.emit('error', error)
- if (callback) callback(error)
- }
- Net.prototype.request = function (options, callback) {
- return new ClientRequest(options, callback)
- }
- net.ClientRequest = ClientRequest
- module.exports = net
- 'use strict'
- const { EventEmitter } = require('events')
- const { Notification, isSupported } = process.atomBinding('notification')
- Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype)
- Notification.isSupported = isSupported
- module.exports = Notification
- 'use strict'
- const { EventEmitter } = require('events')
- const { powerMonitor, PowerMonitor } = process.atomBinding('power_monitor')
- // PowerMonitor is an EventEmitter.
- Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
- EventEmitter.call(powerMonitor)
- // On Linux we need to call blockShutdown() to subscribe to shutdown event.
- if (process.platform === 'linux') {
- powerMonitor.on('newListener', (event) => {
- if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
- powerMonitor.blockShutdown()
- }
- })
- powerMonitor.on('removeListener', (event) => {
- if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
- powerMonitor.unblockShutdown()
- }
- })
- }
- module.exports = powerMonitor
- 'use strict'
- module.exports = process.atomBinding('power_save_blocker').powerSaveBlocker
- 'use strict'
- const { app, session } = require('electron')
- // Global protocol APIs.
- module.exports = process.atomBinding('protocol')
- // Fallback protocol APIs of default session.
- Object.setPrototypeOf(module.exports, new Proxy({}, {
- get (target, property) {
- if (!app.isReady()) return
- const protocol = session.defaultSession.protocol
- if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return
- // Returning a native function directly would throw error.
- return (...args) => protocol[property](...args)
- },
- ownKeys () {
- if (!app.isReady()) return []
- return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.protocol))
- },
- getOwnPropertyDescriptor (target) {
- return { configurable: true, enumerable: true }
- }
- }))
- '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
- 'use strict'
- const { EventEmitter } = require('events')
- const { app } = require('electron')
- const { fromPartition, Session, Cookies } = process.atomBinding('session')
- // Public API.
- Object.defineProperties(exports, {
- defaultSession: {
- enumerable: true,
- get () { return fromPartition('') }
- },
- fromPartition: {
- enumerable: true,
- value: fromPartition
- }
- })
- Object.setPrototypeOf(Session.prototype, EventEmitter.prototype)
- Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype)
- Session.prototype._init = function () {
- app.emit('session-created', this)
- }
- '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
- 'use strict'
- const electron = require('electron')
- const { EventEmitter } = require('events')
- const { TopLevelWindow } = process.atomBinding('top_level_window')
- Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype)
- TopLevelWindow.prototype._init = function () {
- // Avoid recursive require.
- const { app } = electron
- // Simulate the application menu on platforms other than macOS.
- if (process.platform !== 'darwin') {
- const menu = app.getApplicationMenu()
- if (menu) this.setMenu(menu)
- }
- }
- TopLevelWindow.getFocusedWindow = () => {
- return TopLevelWindow.getAllWindows().find((win) => win.isFocused())
- }
- module.exports = TopLevelWindow
- 'use strict'
- const { EventEmitter } = require('events')
- let nextItemID = 1
- class TouchBar extends EventEmitter {
- // Bind a touch bar to a window
- static _setOnWindow (touchBar, window) {
- if (window._touchBar != null) {
- window._touchBar._removeFromWindow(window)
- }
- if (touchBar == null) {
- window._setTouchBarItems([])
- return
- }
- if (Array.isArray(touchBar)) {
- touchBar = new TouchBar(touchBar)
- }
- touchBar._addToWindow(window)
- }
- constructor (options) {
- super()
- if (options == null) {
- throw new Error('Must specify options object as first argument')
- }
- let { items, escapeItem } = options
- // FIXME Support array as first argument, remove in 2.0
- if (Array.isArray(options)) {
- items = options
- escapeItem = null
- }
- if (!Array.isArray(items)) {
- items = []
- }
- this.changeListener = (item) => {
- this.emit('change', item.id, item.type)
- }
- this.windowListeners = {}
- this.items = {}
- this.ordereredItems = []
- this.escapeItem = escapeItem
- const registerItem = (item) => {
- this.items[item.id] = item
- item.on('change', this.changeListener)
- if (item.child instanceof TouchBar) {
- item.child.ordereredItems.forEach(registerItem)
- }
- }
- items.forEach((item) => {
- if (!(item instanceof TouchBarItem)) {
- throw new Error('Each item must be an instance of TouchBarItem')
- }
- this.ordereredItems.push(item)
- registerItem(item)
- })
- }
- set escapeItem (item) {
- if (item != null && !(item instanceof TouchBarItem)) {
- throw new Error('Escape item must be an instance of TouchBarItem')
- }
- if (this.escapeItem != null) {
- this.escapeItem.removeListener('change', this.changeListener)
- }
- this._escapeItem = item
- if (this.escapeItem != null) {
- this.escapeItem.on('change', this.changeListener)
- }
- this.emit('escape-item-change', item)
- }
- get escapeItem () {
- return this._escapeItem
- }
- _addToWindow (window) {
- const { id } = window
- // Already added to window
- if (this.windowListeners.hasOwnProperty(id)) return
- window._touchBar = this
- const changeListener = (itemID) => {
- window._refreshTouchBarItem(itemID)
- }
- this.on('change', changeListener)
- const escapeItemListener = (item) => {
- window._setEscapeTouchBarItem(item != null ? item : {})
- }
- this.on('escape-item-change', escapeItemListener)
- const interactionListener = (event, itemID, details) => {
- let item = this.items[itemID]
- if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
- item = this.escapeItem
- }
- if (item != null && item.onInteraction != null) {
- item.onInteraction(details)
- }
- }
- window.on('-touch-bar-interaction', interactionListener)
- const removeListeners = () => {
- this.removeListener('change', changeListener)
- this.removeListener('escape-item-change', escapeItemListener)
- window.removeListener('-touch-bar-interaction', interactionListener)
- window.removeListener('closed', removeListeners)
- window._touchBar = null
- delete this.windowListeners[id]
- const unregisterItems = (items) => {
- for (const item of items) {
- item.removeListener('change', this.changeListener)
- if (item.child instanceof TouchBar) {
- unregisterItems(item.child.ordereredItems)
- }
- }
- }
- unregisterItems(this.ordereredItems)
- if (this.escapeItem) {
- this.escapeItem.removeListener('change', this.changeListener)
- }
- }
- window.once('closed', removeListeners)
- this.windowListeners[id] = removeListeners
- window._setTouchBarItems(this.ordereredItems)
- escapeItemListener(this.escapeItem)
- }
- _removeFromWindow (window) {
- const removeListeners = this.windowListeners[window.id]
- if (removeListeners != null) removeListeners()
- }
- }
- class TouchBarItem extends EventEmitter {
- constructor () {
- super()
- this._addImmutableProperty('id', `${nextItemID++}`)
- this._parents = []
- }
- _addImmutableProperty (name, value) {
- Object.defineProperty(this, name, {
- get: function () {
- return value
- },
- set: function () {
- throw new Error(`Cannot override property ${name}`)
- },
- enumerable: true,
- configurable: false
- })
- }
- _addLiveProperty (name, initialValue) {
- const privateName = `_${name}`
- this[privateName] = initialValue
- Object.defineProperty(this, name, {
- get: function () {
- return this[privateName]
- },
- set: function (value) {
- this[privateName] = value
- this.emit('change', this)
- },
- enumerable: true
- })
- }
- _addParent (item) {
- const existing = this._parents.some(test => test.id === item.id)
- if (!existing) {
- this._parents.push({
- id: item.id,
- type: item.type
- })
- }
- }
- }
- TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'button')
- const { click, icon, iconPosition, label, backgroundColor } = config
- this._addLiveProperty('label', label)
- this._addLiveProperty('backgroundColor', backgroundColor)
- this._addLiveProperty('icon', icon)
- this._addLiveProperty('iconPosition', iconPosition)
- if (typeof click === 'function') {
- this._addImmutableProperty('onInteraction', () => {
- config.click()
- })
- }
- }
- }
- TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'colorpicker')
- const { availableColors, change, selectedColor } = config
- this._addLiveProperty('availableColors', availableColors)
- this._addLiveProperty('selectedColor', selectedColor)
- if (typeof change === 'function') {
- this._addImmutableProperty('onInteraction', (details) => {
- this._selectedColor = details.color
- change(details.color)
- })
- }
- }
- }
- TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'group')
- const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
- this._addLiveProperty('child', defaultChild)
- this.child.ordereredItems.forEach((item) => item._addParent(this))
- }
- }
- TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'label')
- this._addLiveProperty('label', config.label)
- this._addLiveProperty('textColor', config.textColor)
- }
- }
- TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'popover')
- this._addLiveProperty('label', config.label)
- this._addLiveProperty('icon', config.icon)
- this._addLiveProperty('showCloseButton', config.showCloseButton)
- const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
- this._addLiveProperty('child', defaultChild)
- this.child.ordereredItems.forEach((item) => item._addParent(this))
- }
- }
- TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'slider')
- const { change, label, minValue, maxValue, value } = config
- this._addLiveProperty('label', label)
- this._addLiveProperty('minValue', minValue)
- this._addLiveProperty('maxValue', maxValue)
- this._addLiveProperty('value', value)
- if (typeof change === 'function') {
- this._addImmutableProperty('onInteraction', (details) => {
- this._value = details.value
- change(details.value)
- })
- }
- }
- }
- TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- this._addImmutableProperty('type', 'spacer')
- this._addImmutableProperty('size', config.size)
- }
- }
- TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- const { segmentStyle, segments, selectedIndex, change, mode } = config
- this._addImmutableProperty('type', 'segmented_control')
- this._addLiveProperty('segmentStyle', segmentStyle)
- this._addLiveProperty('segments', segments || [])
- this._addLiveProperty('selectedIndex', selectedIndex)
- this._addLiveProperty('mode', mode)
- if (typeof change === 'function') {
- this._addImmutableProperty('onInteraction', (details) => {
- this._selectedIndex = details.selectedIndex
- change(details.selectedIndex, details.isSelected)
- })
- }
- }
- }
- TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
- constructor (config) {
- super()
- if (config == null) config = {}
- const { items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode } = config
- let { select, highlight } = config
- this._addImmutableProperty('type', 'scrubber')
- this._addLiveProperty('items', items)
- this._addLiveProperty('selectedStyle', selectedStyle || null)
- this._addLiveProperty('overlayStyle', overlayStyle || null)
- this._addLiveProperty('showArrowButtons', showArrowButtons || false)
- this._addLiveProperty('mode', mode || 'free')
- this._addLiveProperty('continuous', typeof continuous === 'undefined' ? true : continuous)
- if (typeof select === 'function' || typeof highlight === 'function') {
- if (select == null) select = () => {}
- if (highlight == null) highlight = () => {}
- this._addImmutableProperty('onInteraction', (details) => {
- if (details.type === 'select' && typeof select === 'function') {
- select(details.selectedIndex)
- } else if (details.type === 'highlight' && typeof highlight === 'function') {
- highlight(details.highlightedIndex)
- }
- })
- }
- }
- }
- module.exports = TouchBar
- 'use strict'
- const { EventEmitter } = require('events')
- const { Tray } = process.atomBinding('tray')
- Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype)
- module.exports = Tray
- 'use strict'
- const { EventEmitter } = require('events')
- const { View } = process.atomBinding('view')
- Object.setPrototypeOf(View.prototype, EventEmitter.prototype)
- View.prototype._init = function () {
- }
- module.exports = View
- '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
- 'use strict'
- const features = process.atomBinding('features')
- const { EventEmitter } = require('events')
- const electron = require('electron')
- const path = require('path')
- const url = require('url')
- const v8Util = process.atomBinding('v8_util')
- const { app, ipcMain, session, NavigationController, deprecate } = electron
- const ipcMainInternal = require('@electron/internal/browser/ipc-main-internal')
- const errorUtils = require('@electron/internal/common/error-utils')
- // session is not used here, the purpose is to make sure session is initalized
- // before the webContents module.
- // eslint-disable-next-line
- session
- let nextId = 0
- const getNextId = function () {
- return ++nextId
- }
- // Stock page sizes
- const PDFPageSizes = {
- A5: {
- custom_display_name: 'A5',
- height_microns: 210000,
- name: 'ISO_A5',
- width_microns: 148000
- },
- A4: {
- custom_display_name: 'A4',
- height_microns: 297000,
- name: 'ISO_A4',
- is_default: 'true',
- width_microns: 210000
- },
- A3: {
- custom_display_name: 'A3',
- height_microns: 420000,
- name: 'ISO_A3',
- width_microns: 297000
- },
- Legal: {
- custom_display_name: 'Legal',
- height_microns: 355600,
- name: 'NA_LEGAL',
- width_microns: 215900
- },
- Letter: {
- custom_display_name: 'Letter',
- height_microns: 279400,
- name: 'NA_LETTER',
- width_microns: 215900
- },
- Tabloid: {
- height_microns: 431800,
- name: 'NA_LEDGER',
- width_microns: 279400,
- custom_display_name: 'Tabloid'
- }
- }
- // Default printing setting
- const defaultPrintingSetting = {
- pageRage: [],
- mediaSize: {},
- landscape: false,
- color: 2,
- headerFooterEnabled: false,
- marginsType: 0,
- isFirstRequest: false,
- requestID: getNextId(),
- previewUIID: 0,
- previewModifiable: true,
- printToPDF: true,
- printWithCloudPrint: false,
- printWithPrivet: false,
- printWithExtension: false,
- deviceName: 'Save as PDF',
- generateDraftData: true,
- fitToPageEnabled: false,
- scaleFactor: 1,
- dpiHorizontal: 72,
- dpiVertical: 72,
- rasterizePDF: false,
- duplex: 0,
- copies: 1,
- collate: true,
- shouldPrintBackgrounds: false,
- shouldPrintSelectionOnly: false
- }
- // JavaScript implementations of WebContents.
- const binding = process.atomBinding('web_contents')
- const { WebContents } = binding
- Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype)
- Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype)
- // WebContents::send(channel, args..)
- // WebContents::sendToAll(channel, args..)
- WebContents.prototype.send = function (channel, ...args) {
- if (typeof channel !== 'string') {
- throw new Error('Missing required channel argument')
- }
- const internal = false
- const sendToAll = false
- return this._send(internal, sendToAll, channel, args)
- }
- WebContents.prototype.sendToAll = function (channel, ...args) {
- if (typeof channel !== 'string') {
- throw new Error('Missing required channel argument')
- }
- const internal = false
- const sendToAll = true
- return this._send(internal, sendToAll, channel, args)
- }
- WebContents.prototype._sendInternal = function (channel, ...args) {
- if (typeof channel !== 'string') {
- throw new Error('Missing required channel argument')
- }
- const internal = true
- const sendToAll = false
- return this._send(internal, sendToAll, channel, args)
- }
- WebContents.prototype._sendInternalToAll = function (channel, ...args) {
- if (typeof channel !== 'string') {
- throw new Error('Missing required channel argument')
- }
- const internal = true
- const sendToAll = true
- return this._send(internal, sendToAll, channel, args)
- }
- // Following methods are mapped to webFrame.
- const webFrameMethods = [
- 'insertCSS',
- 'insertText',
- 'setLayoutZoomLevelLimits',
- 'setVisualZoomLevelLimits'
- ]
- const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
- return new Promise((resolve, reject) => {
- ipcMainInternal.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) {
- if (error == null) {
- if (typeof callback === 'function') callback(result)
- resolve(result)
- } else {
- reject(errorUtils.deserialize(error))
- }
- })
- this._sendInternal('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args)
- })
- }
- for (const method of webFrameMethods) {
- WebContents.prototype[method] = function (...args) {
- this._sendInternal('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args)
- }
- }
- // Make sure WebContents::executeJavaScript would run the code only when the
- // WebContents has been loaded.
- WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) {
- const requestId = getNextId()
- if (typeof hasUserGesture === 'function') {
- // Shift.
- callback = hasUserGesture
- hasUserGesture = null
- }
- if (hasUserGesture == null) {
- hasUserGesture = false
- }
- if (this.getURL() && !this.isLoadingMainFrame()) {
- return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture)
- } else {
- return new Promise((resolve, reject) => {
- this.once('did-stop-loading', () => {
- asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject)
- })
- })
- }
- }
- WebContents.prototype.takeHeapSnapshot = function (filePath) {
- return new Promise((resolve, reject) => {
- const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
- ipcMainInternal.once(channel, (event, success) => {
- if (success) {
- resolve()
- } else {
- reject(new Error('takeHeapSnapshot failed'))
- }
- })
- if (!this._takeHeapSnapshot(filePath, channel)) {
- ipcMainInternal.emit(channel, false)
- }
- })
- }
- // Translate the options of printToPDF.
- WebContents.prototype.printToPDF = function (options, callback) {
- const printingSetting = Object.assign({}, defaultPrintingSetting)
- if (options.landscape) {
- printingSetting.landscape = options.landscape
- }
- if (options.marginsType) {
- printingSetting.marginsType = options.marginsType
- }
- if (options.printSelectionOnly) {
- printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
- }
- if (options.printBackground) {
- printingSetting.shouldPrintBackgrounds = options.printBackground
- }
- if (options.pageSize) {
- const pageSize = options.pageSize
- if (typeof pageSize === 'object') {
- if (!pageSize.height || !pageSize.width) {
- return callback(new Error('Must define height and width for pageSize'))
- }
- // Dimensions in Microns
- // 1 meter = 10^6 microns
- printingSetting.mediaSize = {
- name: 'CUSTOM',
- custom_display_name: 'Custom',
- height_microns: Math.ceil(pageSize.height),
- width_microns: Math.ceil(pageSize.width)
- }
- } else if (PDFPageSizes[pageSize]) {
- printingSetting.mediaSize = PDFPageSizes[pageSize]
- } else {
- return callback(new Error(`Does not support pageSize with ${pageSize}`))
- }
- } else {
- printingSetting.mediaSize = PDFPageSizes['A4']
- }
- // Chromium expects this in a 0-100 range number, not as float
- printingSetting.scaleFactor *= 100
- if (features.isPrintingEnabled()) {
- this._printToPDF(printingSetting, callback)
- } else {
- console.error('Error: Printing feature is disabled.')
- }
- }
- WebContents.prototype.print = function (...args) {
- if (features.isPrintingEnabled()) {
- this._print(...args)
- } else {
- console.error('Error: Printing feature is disabled.')
- }
- }
- WebContents.prototype.getPrinters = function () {
- if (features.isPrintingEnabled()) {
- return this._getPrinters()
- } else {
- console.error('Error: Printing feature is disabled.')
- }
- }
- WebContents.prototype.getZoomLevel = function (callback) {
- if (typeof callback !== 'function') {
- throw new Error('Must pass function as an argument')
- }
- process.nextTick(() => {
- const zoomLevel = this._getZoomLevel()
- callback(zoomLevel)
- })
- }
- WebContents.prototype.loadFile = function (filePath, options = {}) {
- if (typeof filePath !== 'string') {
- throw new Error('Must pass filePath as a string')
- }
- const { query, search, hash } = options
- return this.loadURL(url.format({
- protocol: 'file',
- slashes: true,
- pathname: path.resolve(app.getAppPath(), filePath),
- query,
- search,
- hash
- }))
- }
- WebContents.prototype.getZoomFactor = function (callback) {
- if (typeof callback !== 'function') {
- throw new Error('Must pass function as an argument')
- }
- process.nextTick(() => {
- const zoomFactor = this._getZoomFactor()
- callback(zoomFactor)
- })
- }
- WebContents.prototype.findInPage = function (text, options = {}) {
- // TODO (nitsakh): Remove in 5.0
- if (options.wordStart != null || options.medialCapitalAtWordStart != null) {
- deprecate.log('wordStart and medialCapitalAtWordStart options are deprecated')
- }
- return this._findInPage(text, options)
- }
- const safeProtocols = new Set([
- 'chrome-devtools:',
- 'chrome-extension:'
- ])
- const isWebContentsTrusted = function (contents) {
- const pageURL = contents._getURL()
- const { protocol } = url.parse(pageURL)
- return safeProtocols.has(protocol)
- }
- // Add JavaScript wrappers for WebContents class.
- WebContents.prototype._init = function () {
- // The navigation controller.
- NavigationController.call(this, this)
- // Every remote callback from renderer process would add a listenter to the
- // render-view-deleted event, so ignore the listenters warning.
- this.setMaxListeners(0)
- // Dispatch IPC messages to the ipc module.
- this.on('ipc-message', function (event, [channel, ...args]) {
- ipcMain.emit(channel, event, ...args)
- })
- this.on('ipc-message-sync', function (event, [channel, ...args]) {
- Object.defineProperty(event, 'returnValue', {
- set: function (value) {
- return event.sendReply([value])
- },
- get: function () {}
- })
- ipcMain.emit(channel, event, ...args)
- })
- this.on('ipc-internal-message', function (event, [channel, ...args]) {
- ipcMainInternal.emit(channel, event, ...args)
- })
- this.on('ipc-internal-message-sync', function (event, [channel, ...args]) {
- Object.defineProperty(event, 'returnValue', {
- set: function (value) {
- return event.sendReply([value])
- },
- get: function () {}
- })
- ipcMainInternal.emit(channel, event, ...args)
- })
- // Handle context menu action request from pepper plugin.
- this.on('pepper-context-menu', function (event, params, callback) {
- // Access Menu via electron.Menu to prevent circular require.
- const menu = electron.Menu.buildFromTemplate(params.menu)
- menu.popup({
- window: event.sender.getOwnerBrowserWindow(),
- x: params.x,
- y: params.y,
- callback
- })
- })
- const forwardedEvents = [
- 'remote-require',
- 'remote-get-global',
- 'remote-get-builtin',
- 'remote-get-current-window',
- 'remote-get-current-web-contents',
- 'remote-get-guest-web-contents'
- ]
- for (const eventName of forwardedEvents) {
- this.on(eventName, (event, ...args) => {
- if (!isWebContentsTrusted(event.sender)) {
- app.emit(eventName, event, this, ...args)
- }
- })
- }
- deprecate.event(this, 'did-get-response-details', '-did-get-response-details')
- deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request')
- // The devtools requests the webContents to reload.
- this.on('devtools-reload-page', function () {
- this.reload()
- })
- // Handle window.open for BrowserWindow and BrowserView.
- if (['browserView', 'window'].includes(this.getType())) {
- // Make new windows requested by links behave like "window.open"
- this.webContents.on('-new-window', (event, url, frameName, disposition,
- additionalFeatures, postData,
- referrer) => {
- const options = {
- show: true,
- width: 800,
- height: 600
- }
- ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
- event, url, referrer, frameName, disposition,
- options, additionalFeatures, postData)
- })
- this.webContents.on('-web-contents-created', (event, webContents, url,
- frameName) => {
- v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName })
- })
- // Create a new browser window for the native implementation of
- // "window.open", used in sandbox and nativeWindowOpen mode
- this.webContents.on('-add-new-contents', (event, webContents, disposition,
- userGesture, left, top, width,
- height) => {
- const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
- if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
- disposition !== 'background-tab') || !urlFrameName) {
- event.preventDefault()
- return
- }
- if (webContents.getLastWebPreferences().nodeIntegration === true) {
- const message =
- 'Enabling Node.js integration in child windows opened with the ' +
- '"nativeWindowOpen" option will cause memory leaks, please turn off ' +
- 'the "nodeIntegration" option.\\n' +
- 'From 5.x child windows opened with the "nativeWindowOpen" option ' +
- 'will always have Node.js integration disabled.\\n' +
- 'See https://github.com/electron/electron/pull/15076 for more.'
- // console is only available after DOM is created.
- const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
- if (this.webContents.isDomReady()) {
- printWarning()
- } else {
- this.webContents.once('dom-ready', printWarning)
- }
- }
- const { url, frameName } = urlFrameName
- v8Util.deleteHiddenValue(webContents, 'url-framename')
- const options = {
- show: true,
- x: left,
- y: top,
- width: width || 800,
- height: height || 600,
- webContents
- }
- const referrer = { url: '', policy: 'default' }
- ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
- event, url, referrer, frameName, disposition, options)
- })
- }
- app.emit('web-contents-created', {}, this)
- }
- // JavaScript wrapper of Debugger.
- const { Debugger } = process.atomBinding('debugger')
- Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
- // Public APIs.
- module.exports = {
- create (options = {}) {
- return binding.create(options)
- },
- fromId (id) {
- return binding.fromId(id)
- },
- getFocusedWebContents () {
- let focused = null
- for (const contents of binding.getAllWebContents()) {
- if (!contents.isFocused()) continue
- if (focused == null) focused = contents
- // Return webview web contents which may be embedded inside another
- // web contents that is also reporting as focused
- if (contents.getType() === 'webview') return contents
- }
- return focused
- },
- getAllWebContents () {
- return binding.getAllWebContents()
- }
- }
- 'use strict'
- const { app, webContents, BrowserWindow } = require('electron')
- const { getAllWebContents } = process.atomBinding('web_contents')
- const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents()
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- const { Buffer } = require('buffer')
- const fs = require('fs')
- const path = require('path')
- const url = require('url')
- // Mapping between extensionId(hostname) and manifest.
- const manifestMap = {} // extensionId => manifest
- const manifestNameMap = {} // name => manifest
- const devToolsExtensionNames = new Set()
- const generateExtensionIdFromName = function (name) {
- return name.replace(/[\W_]+/g, '-').toLowerCase()
- }
- const isWindowOrWebView = function (webContents) {
- const type = webContents.getType()
- return type === 'window' || type === 'webview'
- }
- // Create or get manifest object from |srcDirectory|.
- const getManifestFromPath = function (srcDirectory) {
- let manifest
- let manifestContent
- try {
- manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
- } catch (readError) {
- console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
- console.warn(readError.stack || readError)
- throw readError
- }
- try {
- manifest = JSON.parse(manifestContent)
- } catch (parseError) {
- console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
- console.warn(parseError.stack || parseError)
- throw parseError
- }
- if (!manifestNameMap[manifest.name]) {
- const extensionId = generateExtensionIdFromName(manifest.name)
- manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
- Object.assign(manifest, {
- srcDirectory: srcDirectory,
- extensionId: extensionId,
- // We can not use 'file://' directly because all resources in the extension
- // will be treated as relative to the root in Chrome.
- startPage: url.format({
- protocol: 'chrome-extension',
- slashes: true,
- hostname: extensionId,
- pathname: manifest.devtools_page
- })
- })
- return manifest
- } else if (manifest && manifest.name) {
- console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
- return manifest
- }
- }
- // Manage the background pages.
- const backgroundPages = {}
- const startBackgroundPages = function (manifest) {
- if (backgroundPages[manifest.extensionId] || !manifest.background) return
- let html
- let name
- if (manifest.background.page) {
- name = manifest.background.page
- html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page))
- } else {
- name = '_generated_background_page.html'
- const scripts = manifest.background.scripts.map((name) => {
- return `<script src="${name}"></script>`
- }).join('')
- html = Buffer.from(`<html><body>${scripts}</body></html>`)
- }
- const contents = webContents.create({
- partition: 'persist:__chrome_extension',
- isBackgroundPage: true,
- commandLineSwitches: ['--background-page']
- })
- backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }
- contents.loadURL(url.format({
- protocol: 'chrome-extension',
- slashes: true,
- hostname: manifest.extensionId,
- pathname: name
- }))
- }
- const removeBackgroundPages = function (manifest) {
- if (!backgroundPages[manifest.extensionId]) return
- backgroundPages[manifest.extensionId].webContents.destroy()
- delete backgroundPages[manifest.extensionId]
- }
- const sendToBackgroundPages = function (...args) {
- for (const page of Object.values(backgroundPages)) {
- page.webContents._sendInternalToAll(...args)
- }
- }
- // Dispatch web contents events to Chrome APIs
- const hookWebContentsEvents = function (webContents) {
- const tabId = webContents.id
- sendToBackgroundPages('CHROME_TABS_ONCREATED')
- webContents.on('will-navigate', (event, url) => {
- sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
- frameId: 0,
- parentFrameId: -1,
- processId: webContents.getProcessId(),
- tabId: tabId,
- timeStamp: Date.now(),
- url: url
- })
- })
- webContents.on('did-navigate', (event, url) => {
- sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
- frameId: 0,
- parentFrameId: -1,
- processId: webContents.getProcessId(),
- tabId: tabId,
- timeStamp: Date.now(),
- url: url
- })
- })
- webContents.once('destroyed', () => {
- sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
- })
- }
- // Handle the chrome.* API messages.
- let nextId = 0
- ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
- const page = backgroundPages[extensionId]
- if (!page) {
- console.error(`Connect to unknown extension ${extensionId}`)
- return
- }
- const portId = ++nextId
- event.returnValue = { tabId: page.webContents.id, portId: portId }
- event.sender.once('render-view-deleted', () => {
- if (page.webContents.isDestroyed()) return
- page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`)
- })
- page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
- })
- ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) {
- event.returnValue = manifestMap[extensionId]
- })
- let resultID = 1
- ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message, originResultID) {
- const page = backgroundPages[extensionId]
- if (!page) {
- console.error(`Connect to unknown extension ${extensionId}`)
- return
- }
- page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message, resultID)
- ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
- event.sender._sendInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
- })
- resultID++
- })
- ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message, originResultID) {
- const contents = webContents.fromId(tabId)
- if (!contents) {
- console.error(`Sending message to unknown tab ${tabId}`)
- return
- }
- const senderTabId = isBackgroundPage ? null : event.sender.id
- contents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message, resultID)
- ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
- event.sender._sendInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
- })
- resultID++
- })
- const isChromeExtension = function (pageURL) {
- const { protocol } = url.parse(pageURL)
- return protocol === 'chrome-extension:'
- }
- ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) {
- const pageURL = event.sender._getURL()
- if (!isChromeExtension(pageURL)) {
- console.error(`Blocked ${pageURL} from calling chrome.tabs.executeScript()`)
- return
- }
- const contents = webContents.fromId(tabId)
- if (!contents) {
- console.error(`Sending message to unknown tab ${tabId}`)
- return
- }
- let code, url
- if (details.file) {
- const manifest = manifestMap[extensionId]
- code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
- url = `chrome-extension://${extensionId}${details.file}`
- } else {
- code = details.code
- url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
- }
- contents._sendInternal('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code)
- })
- // Transfer the content scripts to renderer.
- const contentScripts = {}
- const injectContentScripts = function (manifest) {
- if (contentScripts[manifest.name] || !manifest.content_scripts) return
- const readArrayOfFiles = function (relativePath) {
- return {
- url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
- code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
- }
- }
- const contentScriptToEntry = function (script) {
- return {
- matches: script.matches,
- js: script.js ? script.js.map(readArrayOfFiles) : [],
- css: script.css ? script.css.map(readArrayOfFiles) : [],
- runAt: script.run_at || 'document_idle'
- }
- }
- try {
- const entry = {
- extensionId: manifest.extensionId,
- contentScripts: manifest.content_scripts.map(contentScriptToEntry)
- }
- contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
- } catch (e) {
- console.error('Failed to read content scripts', e)
- }
- }
- const removeContentScripts = function (manifest) {
- if (!contentScripts[manifest.name]) return
- renderProcessPreferences.removeEntry(contentScripts[manifest.name])
- delete contentScripts[manifest.name]
- }
- // Transfer the |manifest| to a format that can be recognized by the
- // |DevToolsAPI.addExtensions|.
- const manifestToExtensionInfo = function (manifest) {
- return {
- startPage: manifest.startPage,
- srcDirectory: manifest.srcDirectory,
- name: manifest.name,
- exposeExperimentalAPIs: true
- }
- }
- // Load the extensions for the window.
- const loadExtension = function (manifest) {
- startBackgroundPages(manifest)
- injectContentScripts(manifest)
- }
- const loadDevToolsExtensions = function (win, manifests) {
- if (!win.devToolsWebContents) return
- manifests.forEach(loadExtension)
- const extensionInfoArray = manifests.map(manifestToExtensionInfo)
- extensionInfoArray.forEach((extension) => {
- win.devToolsWebContents._grantOriginAccess(extension.startPage)
- })
- win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
- }
- app.on('web-contents-created', function (event, webContents) {
- if (!isWindowOrWebView(webContents)) return
- hookWebContentsEvents(webContents)
- webContents.on('devtools-opened', function () {
- loadDevToolsExtensions(webContents, Object.values(manifestMap))
- })
- })
- // The chrome-extension: can map a extension URL request to real file path.
- const chromeExtensionHandler = function (request, callback) {
- const parsed = url.parse(request.url)
- if (!parsed.hostname || !parsed.path) return callback()
- const manifest = manifestMap[parsed.hostname]
- if (!manifest) return callback()
- const page = backgroundPages[parsed.hostname]
- if (page && parsed.path === `/${page.name}`) {
- // Disabled due to false positive in StandardJS
- // eslint-disable-next-line standard/no-callback-literal
- return callback({
- mimeType: 'text/html',
- data: page.html
- })
- }
- fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
- if (err) {
- // Disabled due to false positive in StandardJS
- // eslint-disable-next-line standard/no-callback-literal
- return callback(-6) // FILE_NOT_FOUND
- } else {
- return callback(content)
- }
- })
- }
- app.on('session-created', function (ses) {
- ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
- if (error) {
- console.error(`Unable to register chrome-extension protocol: ${error}`)
- }
- })
- })
- // The persistent path of "DevTools Extensions" preference file.
- let loadedDevToolsExtensionsPath = null
- app.on('will-quit', function () {
- try {
- const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
- .map(name => manifestNameMap[name].srcDirectory)
- if (loadedDevToolsExtensions.length > 0) {
- try {
- fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath))
- } catch (error) {
- // Ignore error
- }
- fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions))
- } else {
- fs.unlinkSync(loadedDevToolsExtensionsPath)
- }
- } catch (error) {
- // Ignore error
- }
- })
- // We can not use protocol or BrowserWindow until app is ready.
- app.once('ready', function () {
- // The public API to add/remove extensions.
- BrowserWindow.addExtension = function (srcDirectory) {
- const manifest = getManifestFromPath(srcDirectory)
- if (manifest) {
- loadExtension(manifest)
- for (const webContents of getAllWebContents()) {
- if (isWindowOrWebView(webContents)) {
- loadDevToolsExtensions(webContents, [manifest])
- }
- }
- return manifest.name
- }
- }
- BrowserWindow.removeExtension = function (name) {
- const manifest = manifestNameMap[name]
- if (!manifest) return
- removeBackgroundPages(manifest)
- removeContentScripts(manifest)
- delete manifestMap[manifest.extensionId]
- delete manifestNameMap[name]
- }
- BrowserWindow.getExtensions = function () {
- const extensions = {}
- Object.keys(manifestNameMap).forEach(function (name) {
- const manifest = manifestNameMap[name]
- extensions[name] = { name: manifest.name, version: manifest.version }
- })
- return extensions
- }
- BrowserWindow.addDevToolsExtension = function (srcDirectory) {
- const manifestName = BrowserWindow.addExtension(srcDirectory)
- if (manifestName) {
- devToolsExtensionNames.add(manifestName)
- }
- return manifestName
- }
- BrowserWindow.removeDevToolsExtension = function (name) {
- BrowserWindow.removeExtension(name)
- devToolsExtensionNames.delete(name)
- }
- BrowserWindow.getDevToolsExtensions = function () {
- const extensions = BrowserWindow.getExtensions()
- const devExtensions = {}
- Array.from(devToolsExtensionNames).forEach(function (name) {
- if (!extensions[name]) return
- devExtensions[name] = extensions[name]
- })
- return devExtensions
- }
- // Load persisted extensions.
- loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
- try {
- const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath))
- if (Array.isArray(loadedDevToolsExtensions)) {
- for (const srcDirectory of loadedDevToolsExtensions) {
- // Start background pages and set content scripts.
- BrowserWindow.addDevToolsExtension(srcDirectory)
- }
- }
- } catch (error) {
- if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') {
- console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath)
- console.error(error)
- }
- }
- })
- 'use strict'
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- const { desktopCapturer } = process.atomBinding('desktop_capturer')
- const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b)
- // A queue for holding all requests from renderer process.
- let requestsQueue = []
- const electronSources = 'ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES'
- const capturerResult = (id) => `ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`
- ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, id) => {
- const request = {
- id,
- options: {
- captureWindow,
- captureScreen,
- thumbnailSize
- },
- webContents: event.sender
- }
- requestsQueue.push(request)
- if (requestsQueue.length === 1) {
- desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
- }
- // If the WebContents is destroyed before receiving result, just remove the
- // reference from requestsQueue to make the module not send the result to it.
- event.sender.once('destroyed', () => {
- request.webContents = null
- })
- })
- desktopCapturer.emit = (event, name, sources) => {
- // Receiving sources result from main process, now send them back to renderer.
- const handledRequest = requestsQueue.shift()
- const handledWebContents = handledRequest.webContents
- const unhandledRequestsQueue = []
- const result = sources.map(source => {
- return {
- id: source.id,
- name: source.name,
- thumbnail: source.thumbnail.toDataURL(),
- display_id: source.display_id
- }
- })
- if (handledWebContents) {
- handledWebContents._sendInternal(capturerResult(handledRequest.id), result)
- }
- // Check the queue to see whether there is another identical request & handle
- requestsQueue.forEach(request => {
- const webContents = request.webContents
- if (deepEqual(handledRequest.options, request.options)) {
- if (webContents) {
- webContents._sendInternal(capturerResult(request.id), result)
- }
- } else {
- unhandledRequestsQueue.push(request)
- }
- })
- requestsQueue = unhandledRequestsQueue
- // If the requestsQueue is not empty, start a new request handling.
- if (requestsQueue.length > 0) {
- const { captureWindow, captureScreen, thumbnailSize } = requestsQueue[0].options
- return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
- }
- }
- 'use strict'
- const { webContents } = require('electron')
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- const parseFeaturesString = require('@electron/internal/common/parse-features-string')
- const errorUtils = require('@electron/internal/common/error-utils')
- const {
- syncMethods,
- asyncCallbackMethods,
- asyncPromiseMethods
- } = require('@electron/internal/common/web-view-methods')
- // Doesn't exist in early initialization.
- let webViewManager = null
- const supportedWebViewEvents = [
- 'load-commit',
- 'did-attach',
- 'did-finish-load',
- 'did-fail-load',
- 'did-frame-finish-load',
- 'did-start-loading',
- 'did-stop-loading',
- 'dom-ready',
- 'console-message',
- 'context-menu',
- 'devtools-opened',
- 'devtools-closed',
- 'devtools-focused',
- 'new-window',
- 'will-navigate',
- 'did-start-navigation',
- 'did-navigate',
- 'did-frame-navigate',
- 'did-navigate-in-page',
- 'focus-change',
- 'close',
- 'crashed',
- 'gpu-crashed',
- 'plugin-crashed',
- 'destroyed',
- 'page-title-updated',
- 'page-favicon-updated',
- 'enter-html-full-screen',
- 'leave-html-full-screen',
- 'media-started-playing',
- 'media-paused',
- 'found-in-page',
- 'did-change-theme-color',
- 'update-target-url'
- ]
- let nextGuestInstanceId = 0
- const guestInstances = {}
- const embedderElementsMap = {}
- // Generate guestInstanceId.
- const getNextGuestInstanceId = function () {
- return ++nextGuestInstanceId
- }
- // Create a new guest instance.
- const createGuest = function (embedder, params) {
- if (webViewManager == null) {
- webViewManager = process.atomBinding('web_view_manager')
- }
- const guestInstanceId = getNextGuestInstanceId(embedder)
- const guest = webContents.create({
- isGuest: true,
- partition: params.partition,
- embedder: embedder
- })
- guestInstances[guestInstanceId] = {
- guest: guest,
- embedder: embedder
- }
- // Clear the guest from map when it is destroyed.
- //
- // The guest WebContents is usually destroyed in 2 cases:
- // 1. The embedder frame is closed (reloaded or destroyed), and it
- // automatically closes the guest frame.
- // 2. The guest frame is detached dynamically via JS, and it is manually
- // destroyed when the renderer sends the GUEST_VIEW_MANAGER_DESTROY_GUEST
- // message.
- // The second case relies on the libcc patch:
- // https://github.com/electron/libchromiumcontent/pull/676
- // The patch was introduced to work around a bug in Chromium:
- // https://github.com/electron/electron/issues/14211
- // We should revisit the bug to see if we can remove our libcc patch, the
- // patch was introduced in Chrome 66.
- guest.once('destroyed', () => {
- if (guestInstanceId in guestInstances) {
- detachGuest(embedder, guestInstanceId)
- }
- })
- // Init guest web view after attached.
- guest.once('did-attach', function (event) {
- params = this.attachParams
- delete this.attachParams
- const previouslyAttached = this.viewInstanceId != null
- this.viewInstanceId = params.instanceId
- // Only load URL and set size on first attach
- if (previouslyAttached) {
- return
- }
- if (params.src) {
- const opts = {}
- if (params.httpreferrer) {
- opts.httpReferrer = params.httpreferrer
- }
- if (params.useragent) {
- opts.userAgent = params.useragent
- }
- this.loadURL(params.src, opts)
- }
- guest.allowPopups = params.allowpopups
- embedder.emit('did-attach-webview', event, guest)
- })
- const sendToEmbedder = (channel, ...args) => {
- if (!embedder.isDestroyed()) {
- embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args)
- }
- }
- // Dispatch events to embedder.
- const fn = function (event) {
- guest.on(event, function (_, ...args) {
- sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args)
- })
- }
- for (const event of supportedWebViewEvents) {
- fn(event)
- }
- // Dispatch guest's IPC messages to embedder.
- guest.on('ipc-message-host', function (_, [channel, ...args]) {
- sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)
- })
- // Notify guest of embedder window visibility when it is ready
- // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
- guest.on('dom-ready', function () {
- const guestInstance = guestInstances[guestInstanceId]
- if (guestInstance != null && guestInstance.visibilityState != null) {
- guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState)
- }
- })
- // Forward internal web contents event to embedder to handle
- // native window.open setup
- guest.on('-add-new-contents', (...args) => {
- if (guest.getLastWebPreferences().nativeWindowOpen === true) {
- const embedder = getEmbedder(guestInstanceId)
- if (embedder != null) {
- embedder.emit('-add-new-contents', ...args)
- }
- }
- })
- guest.on('-web-contents-created', (...args) => {
- if (guest.getLastWebPreferences().nativeWindowOpen === true) {
- const embedder = getEmbedder(guestInstanceId)
- if (embedder != null) {
- embedder.emit('-web-contents-created', ...args)
- }
- }
- })
- return guestInstanceId
- }
- // Attach the guest to an element of embedder.
- const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
- const embedder = event.sender
- // Destroy the old guest when attaching.
- const key = `${embedder.id}-${elementInstanceId}`
- const oldGuestInstanceId = embedderElementsMap[key]
- if (oldGuestInstanceId != null) {
- // Reattachment to the same guest is just a no-op.
- if (oldGuestInstanceId === guestInstanceId) {
- return
- }
- const oldGuestInstance = guestInstances[oldGuestInstanceId]
- if (oldGuestInstance) {
- oldGuestInstance.guest.destroy()
- }
- }
- const guestInstance = guestInstances[guestInstanceId]
- // If this isn't a valid guest instance then do nothing.
- if (!guestInstance) {
- throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
- }
- const { guest } = guestInstance
- if (guest.hostWebContents !== event.sender) {
- throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
- }
- // If this guest is already attached to an element then remove it
- if (guestInstance.elementInstanceId) {
- const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`
- delete embedderElementsMap[oldKey]
- // Remove guest from embedder if moving across web views
- if (guest.viewInstanceId !== params.instanceId) {
- webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
- guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`)
- }
- }
- const webPreferences = {
- guestInstanceId: guestInstanceId,
- nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
- enableRemoteModule: params.enableremotemodule,
- plugins: params.plugins,
- zoomFactor: embedder._getZoomFactor(),
- webSecurity: !params.disablewebsecurity,
- enableBlinkFeatures: params.blinkfeatures,
- disableBlinkFeatures: params.disableblinkfeatures
- }
- // parse the 'webpreferences' attribute string, if set
- // this uses the same parsing rules as window.open uses for its features
- if (typeof params.webpreferences === 'string') {
- parseFeaturesString(params.webpreferences, function (key, value) {
- if (value === undefined) {
- // no value was specified, default it to true
- value = true
- }
- webPreferences[key] = value
- })
- }
- if (params.preload) {
- webPreferences.preloadURL = params.preload
- }
- // Return null from native window.open if allowpopups is unset
- if (webPreferences.nativeWindowOpen === true && !params.allowpopups) {
- webPreferences.disablePopups = true
- }
- // Security options that guest will always inherit from embedder
- const inheritedWebPreferences = new Map([
- ['contextIsolation', true],
- ['javascript', false],
- ['nativeWindowOpen', true],
- ['nodeIntegration', false],
- ['enableRemoteModule', false],
- ['sandbox', true]
- ])
- // Inherit certain option values from embedder
- const lastWebPreferences = embedder.getLastWebPreferences()
- for (const [name, value] of inheritedWebPreferences) {
- if (lastWebPreferences[name] === value) {
- webPreferences[name] = value
- }
- }
- embedder.emit('will-attach-webview', event, webPreferences, params)
- if (event.defaultPrevented) {
- if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId
- guest.destroy()
- return
- }
- guest.attachParams = params
- embedderElementsMap[key] = guestInstanceId
- guest.setEmbedder(embedder)
- guestInstance.embedder = embedder
- guestInstance.elementInstanceId = elementInstanceId
- watchEmbedder(embedder)
- webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
- guest.attachToIframe(embedder, embedderFrameId)
- }
- // Remove an guest-embedder relationship.
- const detachGuest = function (embedder, guestInstanceId) {
- const guestInstance = guestInstances[guestInstanceId]
- if (embedder !== guestInstance.embedder) {
- return
- }
- webViewManager.removeGuest(embedder, guestInstanceId)
- delete guestInstances[guestInstanceId]
- const key = `${embedder.id}-${guestInstance.elementInstanceId}`
- delete embedderElementsMap[key]
- }
- // Once an embedder has had a guest attached we watch it for destruction to
- // destroy any remaining guests.
- const watchedEmbedders = new Set()
- const watchEmbedder = function (embedder) {
- if (watchedEmbedders.has(embedder)) {
- return
- }
- watchedEmbedders.add(embedder)
- // Forward embedder window visiblity change events to guest
- const onVisibilityChange = function (visibilityState) {
- for (const guestInstanceId in guestInstances) {
- const guestInstance = guestInstances[guestInstanceId]
- guestInstance.visibilityState = visibilityState
- if (guestInstance.embedder === embedder) {
- guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState)
- }
- }
- }
- embedder.on('-window-visibility-change', onVisibilityChange)
- embedder.once('will-destroy', () => {
- // Usually the guestInstances is cleared when guest is destroyed, but it
- // may happen that the embedder gets manually destroyed earlier than guest,
- // and the embedder will be invalid in the usual code path.
- for (const guestInstanceId in guestInstances) {
- const guestInstance = guestInstances[guestInstanceId]
- if (guestInstance.embedder === embedder) {
- detachGuest(embedder, parseInt(guestInstanceId))
- }
- }
- // Clear the listeners.
- embedder.removeListener('-window-visibility-change', onVisibilityChange)
- watchedEmbedders.delete(embedder)
- })
- }
- const isWebViewTagEnabledCache = new WeakMap()
- const isWebViewTagEnabled = function (contents) {
- if (!isWebViewTagEnabledCache.has(contents)) {
- const value = contents.getLastWebPreferences().webviewTag
- isWebViewTagEnabledCache.set(contents, value)
- }
- return isWebViewTagEnabledCache.get(contents)
- }
- const handleMessage = function (channel, handler) {
- ipcMain.on(channel, (event, ...args) => {
- if (isWebViewTagEnabled(event.sender)) {
- handler(event, ...args)
- } else {
- event.returnValue = null
- }
- })
- }
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
- event.sender._sendInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
- })
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event, params) {
- event.returnValue = createGuest(event.sender, params)
- })
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
- try {
- const guest = getGuestForWebContents(guestInstanceId, event.sender)
- guest.destroy()
- } catch (error) {
- console.error(`Guest destroy failed: ${error}`)
- }
- })
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
- try {
- attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params)
- } catch (error) {
- console.error(`Guest attach failed: ${error}`)
- }
- })
- // this message is sent by the actual <webview>
- ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
- const guest = getGuest(guestInstanceId)
- if (guest === event.sender) {
- event.sender.emit('focus-change', {}, focus, guestInstanceId)
- } else {
- console.error(`focus-change for guestInstanceId: ${guestInstanceId}`)
- }
- })
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, requestId, guestInstanceId, method, args, hasCallback) {
- new Promise(resolve => {
- const guest = getGuestForWebContents(guestInstanceId, event.sender)
- if (!asyncCallbackMethods.has(method) && !asyncPromiseMethods.has(method)) {
- throw new Error(`Invalid method: ${method}`)
- }
- if (hasCallback) {
- guest[method](...args, resolve)
- } else {
- resolve(guest[method](...args))
- }
- }).then(result => {
- return [null, result]
- }, error => {
- return [errorUtils.serialize(error)]
- }).then(responseArgs => {
- event.sender._sendInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
- })
- })
- handleMessage('ELECTRON_GUEST_VIEW_MANAGER_SYNC_CALL', function (event, guestInstanceId, method, args) {
- try {
- const guest = getGuestForWebContents(guestInstanceId, event.sender)
- if (!syncMethods.has(method)) {
- throw new Error(`Invalid method: ${method}`)
- }
- event.returnValue = [null, guest[method](...args)]
- } catch (error) {
- event.returnValue = [errorUtils.serialize(error)]
- }
- })
- // Returns WebContents from its guest id hosted in given webContents.
- const getGuestForWebContents = function (guestInstanceId, contents) {
- const guest = getGuest(guestInstanceId)
- if (!guest) {
- throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
- }
- if (guest.hostWebContents !== contents) {
- throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
- }
- return guest
- }
- // Returns WebContents from its guest id.
- const getGuest = function (guestInstanceId) {
- const guestInstance = guestInstances[guestInstanceId]
- if (guestInstance != null) return guestInstance.guest
- }
- // Returns the embedder of the guest.
- const getEmbedder = function (guestInstanceId) {
- const guestInstance = guestInstances[guestInstanceId]
- if (guestInstance != null) return guestInstance.embedder
- }
- exports.getGuestForWebContents = getGuestForWebContents
- 'use strict'
- const { BrowserWindow, webContents } = require('electron')
- const { isSameOrigin } = process.atomBinding('v8_util')
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- const parseFeaturesString = require('@electron/internal/common/parse-features-string')
- const hasProp = {}.hasOwnProperty
- const frameToGuest = new Map()
- // Security options that child windows will always inherit from parent windows
- const inheritedWebPreferences = new Map([
- ['contextIsolation', true],
- ['javascript', false],
- ['nativeWindowOpen', true],
- ['nodeIntegration', false],
- ['enableRemoteModule', false],
- ['sandbox', true],
- ['webviewTag', false]
- ])
- // Copy attribute of |parent| to |child| if it is not defined in |child|.
- const mergeOptions = function (child, parent, visited) {
- // Check for circular reference.
- if (visited == null) visited = new Set()
- if (visited.has(parent)) return
- visited.add(parent)
- for (const key in parent) {
- if (key === 'isBrowserView') continue
- if (!hasProp.call(parent, key)) continue
- if (key in child && key !== 'webPreferences') continue
- const value = parent[key]
- if (typeof value === 'object') {
- child[key] = mergeOptions(child[key] || {}, value, visited)
- } else {
- child[key] = value
- }
- }
- visited.delete(parent)
- return child
- }
- // Merge |options| with the |embedder|'s window's options.
- const mergeBrowserWindowOptions = function (embedder, options) {
- if (options.webPreferences == null) {
- options.webPreferences = {}
- }
- if (embedder.browserWindowOptions != null) {
- let parentOptions = embedder.browserWindowOptions
- // if parent's visibility is available, that overrides 'show' flag (#12125)
- const win = BrowserWindow.fromWebContents(embedder.webContents)
- if (win != null) {
- parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() }
- }
- // Inherit the original options if it is a BrowserWindow.
- mergeOptions(options, parentOptions)
- } else {
- // Or only inherit webPreferences if it is a webview.
- mergeOptions(options.webPreferences, embedder.getLastWebPreferences())
- }
- // Inherit certain option values from parent window
- const webPreferences = embedder.getLastWebPreferences()
- for (const [name, value] of inheritedWebPreferences) {
- if (webPreferences[name] === value) {
- options.webPreferences[name] = value
- }
- }
- // Sets correct openerId here to give correct options to 'new-window' event handler
- options.webPreferences.openerId = embedder.id
- return options
- }
- // Setup a new guest with |embedder|
- const setupGuest = function (embedder, frameName, guest, options) {
- // When |embedder| is destroyed we should also destroy attached guest, and if
- // guest is closed by user then we should prevent |embedder| from double
- // closing guest.
- const guestId = guest.webContents.id
- const closedByEmbedder = function () {
- guest.removeListener('closed', closedByUser)
- guest.destroy()
- }
- const closedByUser = function () {
- embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
- embedder.removeListener('render-view-deleted', closedByEmbedder)
- }
- embedder.once('render-view-deleted', closedByEmbedder)
- guest.once('closed', closedByUser)
- if (frameName) {
- frameToGuest.set(frameName, guest)
- guest.frameName = frameName
- guest.once('closed', function () {
- frameToGuest.delete(frameName)
- })
- }
- return guestId
- }
- // Create a new guest created by |embedder| with |options|.
- const createGuest = function (embedder, url, referrer, frameName, options, postData) {
- let guest = frameToGuest.get(frameName)
- if (frameName && (guest != null)) {
- guest.loadURL(url)
- return guest.webContents.id
- }
- // Remember the embedder window's id.
- if (options.webPreferences == null) {
- options.webPreferences = {}
- }
- guest = new BrowserWindow(options)
- if (!options.webContents || url !== 'about:blank') {
- // We should not call `loadURL` if the window was constructed from an
- // existing webContents(window.open in a sandboxed renderer) and if the url
- // is not 'about:blank'.
- //
- // Navigating to the url when creating the window from an existing
- // webContents would not be necessary(it will navigate there anyway), but
- // apparently there's a bug that allows the child window to be scripted by
- // the opener, even when the child window is from another origin.
- //
- // That's why the second condition(url !== "about:blank") is required: to
- // force `OverrideSiteInstanceForNavigation` to be called and consequently
- // spawn a new renderer if the new window is targeting a different origin.
- //
- // If the URL is "about:blank", then it is very likely that the opener just
- // wants to synchronously script the popup, for example:
- //
- // let popup = window.open()
- // popup.document.body.write('<h1>hello</h1>')
- //
- // The above code would not work if a navigation to "about:blank" is done
- // here, since the window would be cleared of all changes in the next tick.
- const loadOptions = {
- httpReferrer: referrer
- }
- if (postData != null) {
- loadOptions.postData = postData
- loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
- if (postData.length > 0) {
- const postDataFront = postData[0].bytes.toString()
- const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
- if (boundary != null) {
- loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
- }
- }
- }
- guest.loadURL(url, loadOptions)
- }
- return setupGuest(embedder, frameName, guest, options)
- }
- const getGuestWindow = function (guestContents) {
- let guestWindow = BrowserWindow.fromWebContents(guestContents)
- if (guestWindow == null) {
- const hostContents = guestContents.hostWebContents
- if (hostContents != null) {
- guestWindow = BrowserWindow.fromWebContents(hostContents)
- }
- }
- return guestWindow
- }
- // Checks whether |sender| can access the |target|:
- // 1. Check whether |sender| is the parent of |target|.
- // 2. Check whether |sender| has node integration, if so it is allowed to
- // do anything it wants.
- // 3. Check whether the origins match.
- //
- // However it allows a child window without node integration but with same
- // origin to do anything it wants, when its opener window has node integration.
- // The W3C does not have anything on this, but from my understanding of the
- // security model of |window.opener|, this should be fine.
- const canAccessWindow = function (sender, target) {
- return (target.getLastWebPreferences().openerId === sender.id) ||
- (sender.getLastWebPreferences().nodeIntegration === true) ||
- isSameOrigin(sender.getURL(), target.getURL())
- }
- // Routed window.open messages with raw options
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
- if (url == null || url === '') url = 'about:blank'
- if (frameName == null) frameName = ''
- if (features == null) features = ''
- const options = {}
- const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
- const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag']
- const disposition = 'new-window'
- // Used to store additional features
- const additionalFeatures = []
- // Parse the features
- parseFeaturesString(features, function (key, value) {
- if (value === undefined) {
- additionalFeatures.push(key)
- } else {
- // Don't allow webPreferences to be set since it must be an object
- // that cannot be directly overridden
- if (key === 'webPreferences') return
- if (webPreferences.includes(key)) {
- if (options.webPreferences == null) {
- options.webPreferences = {}
- }
- options.webPreferences[key] = value
- } else {
- options[key] = value
- }
- }
- })
- if (options.left) {
- if (options.x == null) {
- options.x = options.left
- }
- }
- if (options.top) {
- if (options.y == null) {
- options.y = options.top
- }
- }
- if (options.title == null) {
- options.title = frameName
- }
- if (options.width == null) {
- options.width = 800
- }
- if (options.height == null) {
- options.height = 600
- }
- for (const name of ints) {
- if (options[name] != null) {
- options[name] = parseInt(options[name], 10)
- }
- }
- const referrer = { url: '', policy: 'default' }
- ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
- url, referrer, frameName, disposition, options, additionalFeatures)
- })
- // Routed window.open messages with fully parsed options
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer,
- frameName, disposition, options,
- additionalFeatures, postData) {
- options = mergeBrowserWindowOptions(event.sender, options)
- event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer)
- const { newGuest } = event
- if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
- if (newGuest != null) {
- if (options.webContents === newGuest.webContents) {
- // the webContents is not changed, so set defaultPrevented to false to
- // stop the callers of this event from destroying the webContents.
- event.defaultPrevented = false
- }
- event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
- } else {
- event.returnValue = null
- }
- } else {
- event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData)
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- if (!canAccessWindow(event.sender, guestContents)) {
- console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
- return
- }
- const guestWindow = getGuestWindow(guestContents)
- if (guestWindow != null) guestWindow.destroy()
- })
- const windowMethods = new Set([
- 'focus',
- 'blur'
- ])
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) {
- event.returnValue = null
- return
- }
- if (!canAccessWindow(event.sender, guestContents) || !windowMethods.has(method)) {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- event.returnValue = null
- return
- }
- const guestWindow = getGuestWindow(guestContents)
- if (guestWindow != null) {
- event.returnValue = guestWindow[method](...args)
- } else {
- event.returnValue = null
- }
- })
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
- if (targetOrigin == null) {
- targetOrigin = '*'
- }
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- // The W3C does not seem to have word on how postMessage should work when the
- // origins do not match, so we do not do |canAccessWindow| check here since
- // postMessage across origins is useful and not harmful.
- if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
- const sourceId = event.sender.id
- guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
- }
- })
- const webContentsMethods = new Set([
- 'print',
- 'executeJavaScript'
- ])
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) return
- if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) {
- guestContents[method](...args)
- } else {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- }
- })
- const webContentsSyncMethods = new Set([
- 'getURL',
- 'loadURL'
- ])
- ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
- const guestContents = webContents.fromId(guestId)
- if (guestContents == null) {
- event.returnValue = null
- return
- }
- if (canAccessWindow(event.sender, guestContents) && webContentsSyncMethods.has(method)) {
- event.returnValue = guestContents[method](...args)
- } else {
- console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
- event.returnValue = null
- }
- })
- 'use strict'
- const { Buffer } = require('buffer')
- const fs = require('fs')
- const path = require('path')
- const util = require('util')
- const Module = require('module')
- const v8 = require('v8')
- // We modified the original process.argv to let node.js load the init.js,
- // we need to restore it here.
- process.argv.splice(1, 1)
- // Clear search paths.
- require('../common/reset-search-paths')
- // Import common settings.
- require('@electron/internal/common/init')
- const globalPaths = Module.globalPaths
- // Expose public APIs.
- globalPaths.push(path.join(__dirname, 'api', 'exports'))
- if (process.platform === 'win32') {
- // Redirect node's console to use our own implementations, since node can not
- // handle console output when running as GUI program.
- const consoleLog = function (...args) {
- return process.log(util.format(...args) + '\n')
- }
- const streamWrite = function (chunk, encoding, callback) {
- if (Buffer.isBuffer(chunk)) {
- chunk = chunk.toString(encoding)
- }
- process.log(chunk)
- if (callback) {
- callback()
- }
- return true
- }
- console.log = console.error = console.warn = consoleLog
- process.stdout.write = process.stderr.write = streamWrite
- }
- // Don't quit on fatal error.
- process.on('uncaughtException', function (error) {
- // Do nothing if the user has a custom uncaught exception handler.
- if (process.listeners('uncaughtException').length > 1) {
- return
- }
- // Show error in GUI.
- const dialog = require('electron').dialog
- const stack = error.stack ? error.stack : `${error.name}: ${error.message}`
- const message = 'Uncaught Exception:\n' + stack
- dialog.showErrorBox('A JavaScript error occurred in the main process', message)
- })
- // Emit 'exit' event on quit.
- const { app } = require('electron')
- app.on('quit', function (event, exitCode) {
- process.emit('exit', exitCode)
- })
- if (process.platform === 'win32') {
- // If we are a Squirrel.Windows-installed app, set app user model ID
- // so that users don't have to do this.
- //
- // Squirrel packages are always of the form:
- //
- // PACKAGE-NAME
- // - Update.exe
- // - app-VERSION
- // - OUREXE.exe
- //
- // Squirrel itself will always set the shortcut's App User Model ID to the
- // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
- // app.setAppUserModelId with a matching identifier so that renderer processes
- // will inherit this value.
- const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe')
- if (fs.existsSync(updateDotExe)) {
- const packageDir = path.dirname(path.resolve(updateDotExe))
- const packageName = path.basename(packageDir).replace(/\s/g, '')
- const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '')
- app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`)
- }
- }
- // Map process.exit to app.exit, which quits gracefully.
- process.exit = app.exit
- // Load the RPC server.
- require('@electron/internal/browser/rpc-server')
- // Load the guest view manager.
- require('@electron/internal/browser/guest-view-manager')
- require('@electron/internal/browser/guest-window-manager')
- // Now we try to load app's package.json.
- let packagePath = null
- let packageJson = null
- const searchPaths = ['app', 'app.asar', 'default_app.asar']
- for (packagePath of searchPaths) {
- try {
- packagePath = path.join(process.resourcesPath, packagePath)
- packageJson = require(path.join(packagePath, 'package.json'))
- break
- } catch (error) {
- continue
- }
- }
- if (packageJson == null) {
- process.nextTick(function () {
- return process.exit(1)
- })
- throw new Error('Unable to find a valid app')
- }
- // Set application's version.
- if (packageJson.version != null) {
- app.setVersion(packageJson.version)
- }
- // Set application's name.
- if (packageJson.productName != null) {
- app.setName(`${packageJson.productName}`.trim())
- } else if (packageJson.name != null) {
- app.setName(`${packageJson.name}`.trim())
- }
- // Set application's desktop name.
- if (packageJson.desktopName != null) {
- app.setDesktopName(packageJson.desktopName)
- } else {
- app.setDesktopName((app.getName()) + '.desktop')
- }
- // Set v8 flags
- if (packageJson.v8Flags != null) {
- v8.setFlagsFromString(packageJson.v8Flags)
- }
- // Set the user path according to application's name.
- app.setPath('userData', path.join(app.getPath('appData'), app.getName()))
- app.setPath('userCache', path.join(app.getPath('cache'), app.getName()))
- app.setAppPath(packagePath)
- // Load the chrome extension support.
- require('@electron/internal/browser/chrome-extension')
- const features = process.atomBinding('features')
- if (features.isDesktopCapturerEnabled()) {
- // Load internal desktop-capturer module.
- require('@electron/internal/browser/desktop-capturer')
- }
- // Load protocol module to ensure it is populated on app ready
- require('@electron/internal/browser/api/protocol')
- // Set main startup script of the app.
- const mainStartupScript = packageJson.main || 'index.js'
- const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME']
- function currentPlatformSupportsAppIndicator () {
- if (process.platform !== 'linux') return false
- const currentDesktop = process.env.XDG_CURRENT_DESKTOP
- if (!currentDesktop) return false
- if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true
- // ubuntu based or derived session (default ubuntu one, communitheme…) supports
- // indicator too.
- if (/ubuntu/ig.test(currentDesktop)) return true
- return false
- }
- // Workaround for electron/electron#5050 and electron/electron#9046
- if (currentPlatformSupportsAppIndicator()) {
- process.env.XDG_CURRENT_DESKTOP = 'Unity'
- }
- // Finally load app's main.js and transfer control to C++.
- Module._load(path.join(packagePath, mainStartupScript), Module, true)
- 'use strict'
- const { EventEmitter } = require('events')
- const emitter = new EventEmitter()
- // Do not throw exception when channel name is "error".
- emitter.on('error', () => {})
- module.exports = emitter
- 'use strict'
- const v8Util = process.atomBinding('v8_util')
- const getOwnerKey = (webContents, contextId) => {
- return `${webContents.id}-${contextId}`
- }
- class ObjectsRegistry {
- constructor () {
- this.nextId = 0
- // Stores all objects by ref-counting.
- // (id) => {object, count}
- this.storage = {}
- // Stores the IDs of objects referenced by WebContents.
- // (ownerKey) => [id]
- this.owners = {}
- }
- // Register a new object and return its assigned ID. If the object is already
- // registered then the already assigned ID would be returned.
- add (webContents, contextId, obj) {
- // Get or assign an ID to the object.
- const id = this.saveToStorage(obj)
- // Add object to the set of referenced objects.
- const ownerKey = getOwnerKey(webContents, contextId)
- let owner = this.owners[ownerKey]
- if (!owner) {
- owner = this.owners[ownerKey] = new Set()
- this.registerDeleteListener(webContents, contextId)
- }
- if (!owner.has(id)) {
- owner.add(id)
- // Increase reference count if not referenced before.
- this.storage[id].count++
- }
- return id
- }
- // Get an object according to its ID.
- get (id) {
- const pointer = this.storage[id]
- if (pointer != null) return pointer.object
- }
- // Dereference an object according to its ID.
- // Note that an object may be double-freed (cleared when page is reloaded, and
- // then garbage collected in old page).
- remove (webContents, contextId, id) {
- const ownerKey = getOwnerKey(webContents, contextId)
- const owner = this.owners[ownerKey]
- if (owner) {
- // Remove the reference in owner.
- owner.delete(id)
- // Dereference from the storage.
- this.dereference(id)
- }
- }
- // Clear all references to objects refrenced by the WebContents.
- clear (webContents, contextId) {
- const ownerKey = getOwnerKey(webContents, contextId)
- const owner = this.owners[ownerKey]
- if (!owner) return
- for (const id of owner) this.dereference(id)
- delete this.owners[ownerKey]
- }
- // Private: Saves the object into storage and assigns an ID for it.
- saveToStorage (object) {
- let id = v8Util.getHiddenValue(object, 'atomId')
- if (!id) {
- id = ++this.nextId
- this.storage[id] = {
- count: 0,
- object: object
- }
- v8Util.setHiddenValue(object, 'atomId', id)
- }
- return id
- }
- // Private: Dereference the object from store.
- dereference (id) {
- const pointer = this.storage[id]
- if (pointer == null) {
- return
- }
- pointer.count -= 1
- if (pointer.count === 0) {
- v8Util.deleteHiddenValue(pointer.object, 'atomId')
- delete this.storage[id]
- }
- }
- // Private: Clear the storage when renderer process is destroyed.
- registerDeleteListener (webContents, contextId) {
- // contextId => ${processHostId}-${contextCount}
- const processHostId = contextId.split('-')[0]
- const listener = (event, deletedProcessHostId) => {
- if (deletedProcessHostId &&
- deletedProcessHostId.toString() === processHostId) {
- webContents.removeListener('render-view-deleted', listener)
- this.clear(webContents, contextId)
- }
- }
- webContents.on('render-view-deleted', listener)
- }
- }
- module.exports = new ObjectsRegistry()
- 'use strict'
- const { spawn } = require('child_process')
- const electron = require('electron')
- const { EventEmitter } = require('events')
- const fs = require('fs')
- const os = require('os')
- const path = require('path')
- const v8Util = process.atomBinding('v8_util')
- const eventBinding = process.atomBinding('event')
- const { isPromise } = electron
- const ipcMain = require('@electron/internal/browser/ipc-main-internal')
- const objectsRegistry = require('@electron/internal/browser/objects-registry')
- const guestViewManager = require('@electron/internal/browser/guest-view-manager')
- const bufferUtils = require('@electron/internal/common/buffer-utils')
- const errorUtils = require('@electron/internal/common/error-utils')
- const hasProp = {}.hasOwnProperty
- // The internal properties of Function.
- const FUNCTION_PROPERTIES = [
- 'length', 'name', 'arguments', 'caller', 'prototype'
- ]
- // The remote functions in renderer processes.
- // id => Function
- const rendererFunctions = v8Util.createDoubleIDWeakMap()
- // Return the description of object's members:
- const getObjectMembers = function (object) {
- let names = Object.getOwnPropertyNames(object)
- // For Function, we should not override following properties even though they
- // are "own" properties.
- if (typeof object === 'function') {
- names = names.filter((name) => {
- return !FUNCTION_PROPERTIES.includes(name)
- })
- }
- // Map properties to descriptors.
- return names.map((name) => {
- const descriptor = Object.getOwnPropertyDescriptor(object, name)
- const member = { name, enumerable: descriptor.enumerable, writable: false }
- if (descriptor.get === undefined && typeof object[name] === 'function') {
- member.type = 'method'
- } else {
- if (descriptor.set || descriptor.writable) member.writable = true
- member.type = 'get'
- }
- return member
- })
- }
- // Return the description of object's prototype.
- const getObjectPrototype = function (object) {
- const proto = Object.getPrototypeOf(object)
- if (proto === null || proto === Object.prototype) return null
- return {
- members: getObjectMembers(proto),
- proto: getObjectPrototype(proto)
- }
- }
- // Convert a real value into meta data.
- const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
- // Determine the type of value.
- const meta = { type: typeof value }
- if (meta.type === 'object') {
- // Recognize certain types of objects.
- if (value === null) {
- meta.type = 'value'
- } else if (bufferUtils.isBuffer(value)) {
- meta.type = 'buffer'
- } else if (Array.isArray(value)) {
- meta.type = 'array'
- } else if (value instanceof Error) {
- meta.type = 'error'
- } else if (value instanceof Date) {
- meta.type = 'date'
- } else if (isPromise(value)) {
- meta.type = 'promise'
- } else if (hasProp.call(value, 'callee') && value.length != null) {
- // Treat the arguments object as array.
- meta.type = 'array'
- } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
- // Treat simple objects as value.
- meta.type = 'value'
- }
- }
- // Fill the meta object according to value's type.
- if (meta.type === 'array') {
- meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
- } else if (meta.type === 'object' || meta.type === 'function') {
- meta.name = value.constructor ? value.constructor.name : ''
- // Reference the original value if it's an object, because when it's
- // passed to renderer we would assume the renderer keeps a reference of
- // it.
- meta.id = objectsRegistry.add(sender, contextId, value)
- meta.members = getObjectMembers(value)
- meta.proto = getObjectPrototype(value)
- } else if (meta.type === 'buffer') {
- meta.value = bufferUtils.bufferToMeta(value)
- } else if (meta.type === 'promise') {
- // Add default handler to prevent unhandled rejections in main process
- // Instead they should appear in the renderer process
- value.then(function () {}, function () {})
- meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
- value.then(onFulfilled, onRejected)
- })
- } else if (meta.type === 'error') {
- meta.members = plainObjectToMeta(value)
- // Error.name is not part of own properties.
- meta.members.push({
- name: 'name',
- value: value.name
- })
- } else if (meta.type === 'date') {
- meta.value = value.getTime()
- } else {
- meta.type = 'value'
- meta.value = value
- }
- return meta
- }
- // Convert object to meta by value.
- const plainObjectToMeta = function (obj) {
- return Object.getOwnPropertyNames(obj).map(function (name) {
- return {
- name: name,
- value: obj[name]
- }
- })
- }
- // Convert Error into meta data.
- const exceptionToMeta = function (sender, contextId, error) {
- return {
- type: 'exception',
- value: errorUtils.serialize(error)
- }
- }
- const throwRPCError = function (message) {
- const error = new Error(message)
- error.code = 'EBADRPC'
- error.errno = -72
- throw error
- }
- const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
- const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
- let message = `Attempting to call a function in a renderer window that has been closed or released.` +
- `\nFunction provided here: ${location}`
- if (sender instanceof EventEmitter) {
- const remoteEvents = sender.eventNames().filter((eventName) => {
- return sender.listeners(eventName).includes(callIntoRenderer)
- })
- if (remoteEvents.length > 0) {
- message += `\nRemote event names: ${remoteEvents.join(', ')}`
- remoteEvents.forEach((eventName) => {
- sender.removeListener(eventName, callIntoRenderer)
- })
- }
- }
- console.warn(message)
- }
- // Convert array of meta data from renderer into array of real values.
- const unwrapArgs = function (sender, contextId, args) {
- const metaToValue = function (meta) {
- switch (meta.type) {
- case 'value':
- return meta.value
- case 'remote-object':
- return objectsRegistry.get(meta.id)
- case 'array':
- return unwrapArgs(sender, contextId, meta.value)
- case 'buffer':
- return bufferUtils.metaToBuffer(meta.value)
- case 'date':
- return new Date(meta.value)
- case 'promise':
- return Promise.resolve({
- then: metaToValue(meta.then)
- })
- case 'object': {
- const ret = {}
- Object.defineProperty(ret.constructor, 'name', { value: meta.name })
- for (const { name, value } of meta.members) {
- ret[name] = metaToValue(value)
- }
- return ret
- }
- case 'function-with-return-value':
- const returnValue = metaToValue(meta.value)
- return function () {
- return returnValue
- }
- case 'function': {
- // Merge contextId and meta.id, since meta.id can be the same in
- // different webContents.
- const objectId = [contextId, meta.id]
- // Cache the callbacks in renderer.
- if (rendererFunctions.has(objectId)) {
- return rendererFunctions.get(objectId)
- }
- const callIntoRenderer = function (...args) {
- if (!sender.isDestroyed()) {
- sender._sendInternal('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
- } else {
- removeRemoteListenersAndLogWarning(this, callIntoRenderer)
- }
- }
- v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
- Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
- v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
- rendererFunctions.set(objectId, callIntoRenderer)
- return callIntoRenderer
- }
- default:
- throw new TypeError(`Unknown type: ${meta.type}`)
- }
- }
- return args.map(metaToValue)
- }
- // Call a function and send reply asynchronously if it's a an asynchronous
- // style function and the caller didn't pass a callback.
- const callFunction = function (event, contextId, func, caller, args) {
- const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
- const funcPassedCallback = typeof args[args.length - 1] === 'function'
- try {
- if (funcMarkedAsync && !funcPassedCallback) {
- args.push(function (ret) {
- event.returnValue = valueToMeta(event.sender, contextId, ret, true)
- })
- func.apply(caller, args)
- } else {
- const ret = func.apply(caller, args)
- return valueToMeta(event.sender, contextId, ret, true)
- }
- } catch (error) {
- // Catch functions thrown further down in function invocation and wrap
- // them with the function name so it's easier to trace things like
- // `Error processing argument -1.`
- const funcName = func.name || 'anonymous'
- const err = new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`)
- err.cause = error
- throw err
- }
- }
- const handleRemoteCommand = function (channel, handler) {
- ipcMain.on(channel, (event, contextId, ...args) => {
- let returnValue
- if (!event.sender._isRemoteModuleEnabled()) {
- event.returnValue = null
- return
- }
- try {
- returnValue = handler(event, contextId, ...args)
- } catch (error) {
- returnValue = exceptionToMeta(event.sender, contextId, error)
- }
- if (returnValue !== undefined) {
- event.returnValue = returnValue
- }
- })
- }
- handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
- const objectId = [passedContextId, id]
- if (!rendererFunctions.has(objectId)) {
- // Do nothing if the error has already been reported before.
- return
- }
- removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
- })
- handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-require', customEvent, moduleName)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error(`Blocked remote.require('${moduleName}')`)
- } else {
- customEvent.returnValue = process.mainModule.require(moduleName)
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-get-builtin', customEvent, moduleName)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
- } else {
- customEvent.returnValue = electron[moduleName]
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-get-global', customEvent, globalName)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error(`Blocked remote.getGlobal('${globalName}')`)
- } else {
- customEvent.returnValue = global[globalName]
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-get-current-window', customEvent)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error('Blocked remote.getCurrentWindow()')
- } else {
- customEvent.returnValue = event.sender.getOwnerBrowserWindow()
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-get-current-web-contents', customEvent)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error('Blocked remote.getCurrentWebContents()')
- } else {
- customEvent.returnValue = event.sender
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
- args = unwrapArgs(event.sender, contextId, args)
- const constructor = objectsRegistry.get(id)
- if (constructor == null) {
- throwRPCError(`Cannot call constructor on missing remote object ${id}`)
- }
- return valueToMeta(event.sender, contextId, new constructor(...args))
- })
- handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
- args = unwrapArgs(event.sender, contextId, args)
- const func = objectsRegistry.get(id)
- if (func == null) {
- throwRPCError(`Cannot call function on missing remote object ${id}`)
- }
- return callFunction(event, contextId, func, global, args)
- })
- handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
- args = unwrapArgs(event.sender, contextId, args)
- const object = objectsRegistry.get(id)
- if (object == null) {
- throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
- }
- return valueToMeta(event.sender, contextId, new object[method](...args))
- })
- handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
- args = unwrapArgs(event.sender, contextId, args)
- const obj = objectsRegistry.get(id)
- if (obj == null) {
- throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
- }
- return callFunction(event, contextId, obj[method], obj, args)
- })
- handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
- args = unwrapArgs(event.sender, contextId, args)
- const obj = objectsRegistry.get(id)
- if (obj == null) {
- throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
- }
- obj[name] = args[0]
- return null
- })
- handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
- const obj = objectsRegistry.get(id)
- if (obj == null) {
- throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
- }
- return valueToMeta(event.sender, contextId, obj[name])
- })
- handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
- objectsRegistry.remove(event.sender, contextId, id)
- })
- handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
- objectsRegistry.clear(event.sender, contextId)
- return null
- })
- handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
- const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
- const customEvent = eventBinding.createWithSender(event.sender)
- event.sender.emit('remote-get-guest-web-contents', customEvent, guest)
- if (customEvent.returnValue === undefined) {
- if (customEvent.defaultPrevented) {
- throw new Error(`Blocked remote.getGuestForWebContents()`)
- } else {
- customEvent.returnValue = guest
- }
- }
- return valueToMeta(event.sender, contextId, customEvent.returnValue)
- })
- // Implements window.close()
- ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
- const window = event.sender.getOwnerBrowserWindow()
- if (window) {
- window.close()
- }
- event.returnValue = null
- })
- const getTempDirectory = function () {
- try {
- return electron.app.getPath('temp')
- } catch (error) {
- return os.tmpdir()
- }
- }
- const crashReporterInit = function (options) {
- const productName = options.productName || electron.app.getName()
- const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
- let crashServicePid
- if (process.platform === 'win32') {
- const env = {
- ELECTRON_INTERNAL_CRASH_SERVICE: 1
- }
- const args = [
- '--reporter-url=' + options.submitURL,
- '--application-name=' + productName,
- '--crashes-directory=' + crashesDirectory,
- '--v=1'
- ]
- const crashServiceProcess = spawn(process.helperExecPath, args, {
- env,
- detached: true
- })
- crashServicePid = crashServiceProcess.pid
- }
- return {
- productName,
- crashesDirectory,
- crashServicePid,
- appVersion: electron.app.getVersion()
- }
- }
- const setReturnValue = function (event, getValue) {
- try {
- event.returnValue = [null, getValue()]
- } catch (error) {
- event.returnValue = [errorUtils.serialize(error)]
- }
- }
- ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
- setReturnValue(event, () => crashReporterInit(options))
- })
- ipcMain.on('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
- setReturnValue(event, () => event.sender.getLastWebPreferences())
- })
- ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', function (event) {
- setReturnValue(event, () => electron.clipboard.readFindText())
- })
- ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text) {
- setReturnValue(event, () => electron.clipboard.writeFindText(text))
- })
- ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
- const preloadPath = event.sender._getPreloadPath()
- let preloadSrc = null
- let preloadError = null
- if (preloadPath) {
- try {
- preloadSrc = fs.readFileSync(preloadPath).toString()
- } catch (err) {
- preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack }
- }
- }
- event.returnValue = {
- preloadSrc,
- preloadError,
- isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(),
- process: {
- arch: process.arch,
- platform: process.platform,
- env: process.env,
- version: process.version,
- versions: process.versions
- }
- }
- })
- '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
- }
- 'use strict'
- let deprecationHandler = null
- function warnOnce (oldName, newName) {
- let warned = false
- const msg = newName
- ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
- : `'${oldName}' is deprecated and will be removed.`
- return () => {
- if (!warned && !process.noDeprecation) {
- warned = true
- deprecate.log(msg)
- }
- }
- }
- const deprecate = {
- setHandler: (handler) => { deprecationHandler = handler },
- getHandler: () => deprecationHandler,
- warn: (oldName, newName) => {
- return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
- },
- log: (message) => {
- if (typeof deprecationHandler === 'function') {
- deprecationHandler(message)
- } else if (process.throwDeprecation) {
- throw new Error(message)
- } else if (process.traceDeprecation) {
- return console.trace(message)
- } else {
- return console.warn(`(electron) ${message}`)
- }
- },
- event: (emitter, oldName, newName) => {
- const warn = newName.startsWith('-') /* internal event */
- ? warnOnce(`${oldName} event`)
- : warnOnce(`${oldName} event`, `${newName} event`)
- return emitter.on(newName, function (...args) {
- if (this.listenerCount(oldName) !== 0) {
- warn()
- this.emit(oldName, ...args)
- }
- })
- },
- removeProperty: (o, removedName) => {
- // if the property's already been removed, warn about it
- if (!(removedName in o)) {
- deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`)
- }
- // wrap the deprecated property in an accessor to warn
- const warn = warnOnce(removedName)
- let val = o[removedName]
- return Object.defineProperty(o, removedName, {
- configurable: true,
- get: () => {
- warn()
- return val
- },
- set: newVal => {
- warn()
- val = newVal
- }
- })
- },
- renameProperty: (o, oldName, newName) => {
- const warn = warnOnce(oldName, newName)
- // if the new property isn't there yet,
- // inject it and warn about it
- if ((oldName in o) && !(newName in o)) {
- warn()
- o[newName] = o[oldName]
- }
- // wrap the deprecated property in an accessor to warn
- // and redirect to the new property
- return Object.defineProperty(o, oldName, {
- get: () => {
- warn()
- return o[newName]
- },
- set: value => {
- warn()
- o[newName] = value
- }
- })
- }
- }
- module.exports = deprecate
- 'use strict'
- const deprecate = require('electron').deprecate
- exports.setHandler = function (deprecationHandler) {
- deprecate.setHandler(deprecationHandler)
- }
- exports.getHandler = function () {
- return deprecate.getHandler()
- }
- 'use strict'
- const moduleList = require('@electron/internal/common/api/module-list')
- exports.memoizedGetter = (getter) => {
- /*
- * It's ok to leak this value as it would be leaked by the global
- * node module cache anyway at `Module._cache`. This memoization
- * is dramatically faster than relying on nodes module cache however
- */
- let memoizedValue = null
- return () => {
- if (memoizedValue === null) {
- memoizedValue = getter()
- }
- return memoizedValue
- }
- }
- // Attaches properties to |targetExports|.
- exports.defineProperties = function (targetExports) {
- const descriptors = {}
- for (const module of moduleList) {
- descriptors[module.name] = {
- enumerable: !module.private,
- get: exports.memoizedGetter(() => require(`@electron/internal/common/api/${module.file}`))
- }
- }
- return Object.defineProperties(targetExports, descriptors)
- }
- 'use strict'
- module.exports = function isPromise (val) {
- return (
- val &&
- val.then &&
- val.then instanceof Function &&
- val.constructor &&
- val.constructor.reject &&
- val.constructor.reject instanceof Function &&
- val.constructor.resolve &&
- val.constructor.resolve instanceof Function
- )
- }
- 'use strict'
- // 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 }
- ]
- 'use strict'
- module.exports = process.atomBinding('native_image')
- 'use strict'
- module.exports = process.atomBinding('shell')
- 'use strict'
- module.exports = function atomBindingSetup (binding, processType) {
- return function atomBinding (name) {
- try {
- return binding(`atom_${processType}_${name}`)
- } catch (error) {
- if (/No such module/.test(error.message)) {
- return binding(`atom_common_${name}`)
- } else {
- throw error
- }
- }
- }
- }
- 'use strict'
- // Note: Don't use destructuring assignment for `Buffer`, or we'll hit a
- // browserify bug that makes the statement invalid, throwing an error in
- // sandboxed renderer.
- const Buffer = require('buffer').Buffer
- const typedArrays = {
- Buffer,
- ArrayBuffer,
- Int8Array,
- Uint8Array,
- Uint8ClampedArray,
- Int16Array,
- Uint16Array,
- Int32Array,
- Uint32Array,
- Float32Array,
- Float64Array
- }
- function getType (value) {
- for (const type of Object.keys(typedArrays)) {
- if (value instanceof typedArrays[type]) {
- return type
- }
- }
- return null
- }
- function getBuffer (value) {
- if (value instanceof Buffer) {
- return value
- } else if (value instanceof ArrayBuffer) {
- return Buffer.from(value)
- } else {
- return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
- }
- }
- exports.isBuffer = function (value) {
- return ArrayBuffer.isView(value) || value instanceof ArrayBuffer
- }
- exports.bufferToMeta = function (value) {
- return {
- type: getType(value),
- data: getBuffer(value),
- length: value.length
- }
- }
- exports.metaToBuffer = function (value) {
- const constructor = typedArrays[value.type]
- const data = getBuffer(value.data)
- if (constructor === Buffer) {
- return data
- } else if (constructor === ArrayBuffer) {
- return data.buffer
- } else if (constructor) {
- return new constructor(data.buffer, data.byteOffset, value.length)
- } else {
- return data
- }
- }
- 'use strict'
- const binding = process.atomBinding('crash_reporter')
- const errorUtils = require('@electron/internal/common/error-utils')
- class CrashReporter {
- contructor () {
- this.productName = null
- this.crashesDirectory = null
- }
- sendSync (channel, ...args) {
- throw new Error('Not implemented')
- }
- invoke (command, ...args) {
- const [ error, result ] = this.sendSync(command, ...args)
- if (error) {
- throw errorUtils.deserialize(error)
- }
- return result
- }
- start (options) {
- if (options == null) options = {}
- let {
- productName,
- companyName,
- extra,
- ignoreSystemCrashHandler,
- submitURL,
- uploadToServer
- } = options
- if (uploadToServer == null) {
- uploadToServer = true
- }
- if (ignoreSystemCrashHandler == null) {
- ignoreSystemCrashHandler = false
- }
- if (companyName == null) {
- throw new Error('companyName is a required option to crashReporter.start')
- }
- if (submitURL == null) {
- throw new Error('submitURL is a required option to crashReporter.start')
- }
- const ret = this.invoke('ELECTRON_CRASH_REPORTER_INIT', {
- submitURL,
- productName
- })
- this.productName = ret.productName
- this.crashesDirectory = ret.crashesDirectory
- this.crashServicePid = ret.crashServicePid
- if (extra == null) extra = {}
- if (extra._productName == null) extra._productName = ret.productName
- if (extra._companyName == null) extra._companyName = companyName
- if (extra._version == null) extra._version = ret.appVersion
- binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra)
- }
- getLastCrashReport () {
- const reports = this.getUploadedReports()
- .sort((a, b) => {
- const ats = (a && a.date) ? new Date(a.date).getTime() : 0
- const bts = (b && b.date) ? new Date(b.date).getTime() : 0
- return bts - ats
- })
- return (reports.length > 0) ? reports[0] : null
- }
- getUploadedReports () {
- return binding.getUploadedReports(this.getCrashesDirectory())
- }
- getCrashesDirectory () {
- return this.crashesDirectory
- }
- getProductName () {
- return this.productName
- }
- getUploadToServer () {
- if (process.type === 'browser') {
- return binding.getUploadToServer()
- } else {
- throw new Error('getUploadToServer can only be called from the main process')
- }
- }
- setUploadToServer (uploadToServer) {
- if (process.type === 'browser') {
- return binding.setUploadToServer(uploadToServer)
- } else {
- throw new Error('setUploadToServer can only be called from the main process')
- }
- }
- addExtraParameter (key, value) {
- binding.addExtraParameter(key, value)
- }
- removeExtraParameter (key) {
- binding.removeExtraParameter(key)
- }
- getParameters (key, value) {
- return binding.getParameters()
- }
- }
- module.exports = CrashReporter
- 'use strict'
- const constructors = new Map([
- [Error.name, Error],
- [EvalError.name, EvalError],
- [RangeError.name, RangeError],
- [ReferenceError.name, ReferenceError],
- [SyntaxError.name, SyntaxError],
- [TypeError.name, TypeError],
- [URIError.name, URIError]
- ])
- exports.deserialize = function (error) {
- if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) {
- const constructor = constructors.get(error.name)
- const deserializedError = new constructor(error.message)
- deserializedError.stack = error.stack
- deserializedError.from = error.from
- deserializedError.cause = exports.deserialize(error.cause)
- return deserializedError
- }
- return error
- }
- exports.serialize = function (error) {
- if (error instanceof Error) {
- // Errors get lost, because: JSON.stringify(new Error('Message')) === {}
- // Take the serializable properties and construct a generic object
- return {
- message: error.message,
- stack: error.stack,
- name: error.name,
- from: process.type,
- cause: exports.serialize(error.cause),
- __ELECTRON_SERIALIZED_ERROR__: true
- }
- }
- return error
- }
- 'use strict'
- const timers = require('timers')
- const util = require('util')
- process.atomBinding = require('@electron/internal/common/atom-binding-setup')(process.binding, process.type)
- // setImmediate and process.nextTick makes use of uv_check and uv_prepare to
- // run the callbacks, however since we only run uv loop on requests, the
- // callbacks wouldn't be called until something else activated the uv loop,
- // which would delay the callbacks for arbitrary long time. So we should
- // initiatively activate the uv loop once setImmediate and process.nextTick is
- // called.
- const wrapWithActivateUvLoop = function (func) {
- return wrap(func, function (func) {
- return function () {
- process.activateUvLoop()
- return func.apply(this, arguments)
- }
- })
- }
- function wrap (func, wrapper) {
- const wrapped = wrapper(func)
- if (func[util.promisify.custom]) {
- wrapped[util.promisify.custom] = wrapper(func[util.promisify.custom])
- }
- return wrapped
- }
- process.nextTick = wrapWithActivateUvLoop(process.nextTick)
- global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate)
- global.clearImmediate = timers.clearImmediate
- if (process.type === 'browser') {
- // setTimeout needs to update the polling timeout of the event loop, when
- // called under Chromium's event loop the node's event loop won't get a chance
- // to update the timeout, so we have to force the node's event loop to
- // recalculate the timeout in browser process.
- global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout)
- global.setInterval = wrapWithActivateUvLoop(timers.setInterval)
- }
- if (process.platform === 'win32') {
- // Always returns EOF for stdin stream.
- const { Readable } = require('stream')
- const stdin = new Readable()
- stdin.push(null)
- process.__defineGetter__('stdin', function () {
- return stdin
- })
- }
- 'use strict'
- // parses a feature string that has the format used in window.open()
- // - `features` input string
- // - `emit` function(key, value) - called for each parsed KV
- module.exports = function parseFeaturesString (features, emit) {
- features = `${features}`
- // split the string by ','
- features.split(/,\s*/).forEach((feature) => {
- // expected form is either a key by itself or a key/value pair in the form of
- // 'key=value'
- let [key, value] = feature.split(/\s*=/)
- if (!key) return
- // interpret the value as a boolean, if possible
- value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value
- // emit the parsed pair
- emit(key, value)
- })
- }
- 'use strict'
- const path = require('path')
- const Module = require('module')
- // Clear Node's global search paths.
- Module.globalPaths.length = 0
- // Clear current and parent(init.js)'s search paths.
- module.paths = []
- module.parent.paths = []
- // Prevent Node from adding paths outside this app to search paths.
- const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
- const originalNodeModulePaths = Module._nodeModulePaths
- Module._nodeModulePaths = function (from) {
- const paths = originalNodeModulePaths(from)
- const fromPath = path.resolve(from) + path.sep
- // If "from" is outside the app then we do nothing.
- if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
- return paths.filter(function (candidate) {
- return candidate.startsWith(resourcesPathWithTrailingSlash)
- })
- } else {
- return paths
- }
- }
- const BASE_INTERNAL_PATH = path.resolve(__dirname, '..')
- const INTERNAL_MODULE_PREFIX = '@electron/internal/'
- // Patch Module._resolveFilename to always require the Electron API when
- // require('electron') is done.
- const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js')
- const originalResolveFilename = Module._resolveFilename
- Module._resolveFilename = function (request, parent, isMain) {
- if (request === 'electron') {
- return electronPath
- } else if (request.startsWith(INTERNAL_MODULE_PREFIX) && request.length > INTERNAL_MODULE_PREFIX.length) {
- const slicedRequest = request.slice(INTERNAL_MODULE_PREFIX.length)
- return path.resolve(BASE_INTERNAL_PATH, `${slicedRequest}${slicedRequest.endsWith('.js') ? '' : '.js'}`)
- } else {
- return originalResolveFilename(request, parent, isMain)
- }
- }
- 'use strict'
- // Public-facing API methods.
- exports.syncMethods = new Set([
- 'getURL',
- 'loadURL',
- 'getTitle',
- 'isLoading',
- 'isLoadingMainFrame',
- 'isWaitingForResponse',
- 'stop',
- 'reload',
- 'reloadIgnoringCache',
- 'canGoBack',
- 'canGoForward',
- 'canGoToOffset',
- 'clearHistory',
- 'goBack',
- 'goForward',
- 'goToIndex',
- 'goToOffset',
- 'isCrashed',
- 'setUserAgent',
- 'getUserAgent',
- 'openDevTools',
- 'closeDevTools',
- 'isDevToolsOpened',
- 'isDevToolsFocused',
- 'inspectElement',
- 'setAudioMuted',
- 'isAudioMuted',
- 'isCurrentlyAudible',
- 'undo',
- 'redo',
- 'cut',
- 'copy',
- 'paste',
- 'pasteAndMatchStyle',
- 'delete',
- 'selectAll',
- 'unselect',
- 'replace',
- 'replaceMisspelling',
- 'findInPage',
- 'stopFindInPage',
- 'downloadURL',
- 'inspectServiceWorker',
- 'showDefinitionForSelection',
- 'setZoomFactor',
- 'setZoomLevel',
- 'sendImeEvent'
- ])
- exports.asyncCallbackMethods = new Set([
- 'insertCSS',
- 'insertText',
- 'send',
- 'sendInputEvent',
- 'setLayoutZoomLevelLimits',
- 'setVisualZoomLevelLimits',
- 'getZoomFactor',
- 'getZoomLevel',
- 'print',
- 'printToPDF'
- ])
- exports.asyncPromiseMethods = new Set([
- 'capturePage',
- 'executeJavaScript'
- ])
- 'use strict'
- const CrashReporter = require('@electron/internal/common/crash-reporter')
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- class CrashReporterRenderer extends CrashReporter {
- sendSync (channel, ...args) {
- return ipcRenderer.sendSync(channel, ...args)
- }
- }
- module.exports = new CrashReporterRenderer()
- 'use strict'
- const { nativeImage } = require('electron')
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const includes = [].includes
- let currentId = 0
- const incrementId = () => {
- currentId += 1
- return currentId
- }
- // |options.types| can't be empty and must be an array
- function isValid (options) {
- const types = options ? options.types : undefined
- return Array.isArray(types)
- }
- exports.getSources = function (options, callback) {
- if (!isValid(options)) return callback(new Error('Invalid options'))
- const captureWindow = includes.call(options.types, 'window')
- const captureScreen = includes.call(options.types, 'screen')
- if (options.thumbnailSize == null) {
- options.thumbnailSize = {
- width: 150,
- height: 150
- }
- }
- const id = incrementId()
- ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id)
- return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => {
- callback(null, (() => {
- const results = []
- sources.forEach(source => {
- results.push({
- id: source.id,
- name: source.name,
- thumbnail: nativeImage.createFromDataURL(source.thumbnail),
- display_id: source.display_id
- })
- })
- return results
- })())
- })
- }
- 'use strict'
- const common = require('@electron/internal/common/api/exports/electron')
- const moduleList = require('@electron/internal/renderer/api/module-list')
- // Import common modules.
- common.defineProperties(exports)
- for (const {
- name,
- file,
- enabled = true,
- private: isPrivate = false
- } of moduleList) {
- if (!enabled) {
- continue
- }
- Object.defineProperty(exports, name, {
- enumerable: !isPrivate,
- get: common.memoizedGetter(() => require(`@electron/internal/renderer/api/${file}`))
- })
- }
- 'use strict'
- const binding = process.atomBinding('ipc')
- const v8Util = process.atomBinding('v8_util')
- // Created by init.js.
- const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
- const internal = false
- ipcRenderer.send = function (...args) {
- return binding.send('ipc-message', args)
- }
- ipcRenderer.sendSync = function (...args) {
- return binding.sendSync('ipc-message-sync', args)[0]
- }
- ipcRenderer.sendToHost = function (...args) {
- return binding.send('ipc-message-host', args)
- }
- ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
- return binding.sendTo(internal, false, webContentsId, channel, args)
- }
- ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
- return binding.sendTo(internal, true, webContentsId, channel, args)
- }
- module.exports = ipcRenderer
- 'use strict'
- const features = process.atomBinding('features')
- const v8Util = process.atomBinding('v8_util')
- const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule')
- // Renderer side modules, please sort alphabetically.
- // A module is `enabled` if there is no explicit condition defined.
- module.exports = [
- { name: 'crashReporter', file: 'crash-reporter', enabled: true },
- {
- name: 'desktopCapturer',
- file: 'desktop-capturer',
- enabled: features.isDesktopCapturerEnabled()
- },
- { name: 'ipcRenderer', file: 'ipc-renderer' },
- { name: 'remote', file: 'remote', enabled: enableRemoteModule },
- { name: 'screen', file: 'screen' },
- { name: 'webFrame', file: 'web-frame' }
- ]
- 'use strict'
- const v8Util = process.atomBinding('v8_util')
- const { isPromise } = require('electron')
- const resolvePromise = Promise.resolve.bind(Promise)
- const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry')
- const bufferUtils = require('@electron/internal/common/buffer-utils')
- const errorUtils = require('@electron/internal/common/error-utils')
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const callbacksRegistry = new CallbacksRegistry()
- const remoteObjectCache = v8Util.createIDWeakMap()
- // An unique ID that can represent current context.
- const contextId = v8Util.getHiddenValue(global, 'contextId')
- // Notify the main process when current context is going to be released.
- // Note that when the renderer process is destroyed, the message may not be
- // sent, we also listen to the "render-view-deleted" event in the main process
- // to guard that situation.
- process.on('exit', () => {
- const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
- ipcRenderer.sendSync(command, contextId)
- })
- // Convert the arguments object into an array of meta data.
- function wrapArgs (args, visited = new Set()) {
- const valueToMeta = (value) => {
- // Check for circular reference.
- if (visited.has(value)) {
- return {
- type: 'value',
- value: null
- }
- }
- if (Array.isArray(value)) {
- visited.add(value)
- const meta = {
- type: 'array',
- value: wrapArgs(value, visited)
- }
- visited.delete(value)
- return meta
- } else if (bufferUtils.isBuffer(value)) {
- return {
- type: 'buffer',
- value: bufferUtils.bufferToMeta(value)
- }
- } else if (value instanceof Date) {
- return {
- type: 'date',
- value: value.getTime()
- }
- } else if ((value != null) && typeof value === 'object') {
- if (isPromise(value)) {
- return {
- type: 'promise',
- then: valueToMeta(function (onFulfilled, onRejected) {
- value.then(onFulfilled, onRejected)
- })
- }
- } else if (v8Util.getHiddenValue(value, 'atomId')) {
- return {
- type: 'remote-object',
- id: v8Util.getHiddenValue(value, 'atomId')
- }
- }
- const meta = {
- type: 'object',
- name: value.constructor ? value.constructor.name : '',
- members: []
- }
- visited.add(value)
- for (const prop in value) {
- meta.members.push({
- name: prop,
- value: valueToMeta(value[prop])
- })
- }
- visited.delete(value)
- return meta
- } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
- return {
- type: 'function-with-return-value',
- value: valueToMeta(value())
- }
- } else if (typeof value === 'function') {
- return {
- type: 'function',
- id: callbacksRegistry.add(value),
- location: v8Util.getHiddenValue(value, 'location'),
- length: value.length
- }
- } else {
- return {
- type: 'value',
- value: value
- }
- }
- }
- return args.map(valueToMeta)
- }
- // Populate object's members from descriptors.
- // The |ref| will be kept referenced by |members|.
- // This matches |getObjectMemebers| in rpc-server.
- function setObjectMembers (ref, object, metaId, members) {
- if (!Array.isArray(members)) return
- for (const member of members) {
- if (object.hasOwnProperty(member.name)) continue
- const descriptor = { enumerable: member.enumerable }
- if (member.type === 'method') {
- const remoteMemberFunction = function (...args) {
- let command
- if (this && this.constructor === remoteMemberFunction) {
- command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR'
- } else {
- command = 'ELECTRON_BROWSER_MEMBER_CALL'
- }
- const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args))
- return metaToValue(ret)
- }
- let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name)
- descriptor.get = () => {
- descriptorFunction.ref = ref // The member should reference its object.
- return descriptorFunction
- }
- // Enable monkey-patch the method
- descriptor.set = (value) => {
- descriptorFunction = value
- return value
- }
- descriptor.configurable = true
- } else if (member.type === 'get') {
- descriptor.get = () => {
- const command = 'ELECTRON_BROWSER_MEMBER_GET'
- const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name)
- return metaToValue(meta)
- }
- if (member.writable) {
- descriptor.set = (value) => {
- const args = wrapArgs([value])
- const command = 'ELECTRON_BROWSER_MEMBER_SET'
- const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args)
- if (meta != null) metaToValue(meta)
- return value
- }
- }
- }
- Object.defineProperty(object, member.name, descriptor)
- }
- }
- // Populate object's prototype from descriptor.
- // This matches |getObjectPrototype| in rpc-server.
- function setObjectPrototype (ref, object, metaId, descriptor) {
- if (descriptor === null) return
- const proto = {}
- setObjectMembers(ref, proto, metaId, descriptor.members)
- setObjectPrototype(ref, proto, metaId, descriptor.proto)
- Object.setPrototypeOf(object, proto)
- }
- // Wrap function in Proxy for accessing remote properties
- function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
- let loaded = false
- // Lazily load function properties
- const loadRemoteProperties = () => {
- if (loaded) return
- loaded = true
- const command = 'ELECTRON_BROWSER_MEMBER_GET'
- const meta = ipcRenderer.sendSync(command, contextId, metaId, name)
- setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
- }
- return new Proxy(remoteMemberFunction, {
- set: (target, property, value, receiver) => {
- if (property !== 'ref') loadRemoteProperties()
- target[property] = value
- return true
- },
- get: (target, property, receiver) => {
- if (!target.hasOwnProperty(property)) loadRemoteProperties()
- const value = target[property]
- if (property === 'toString' && typeof value === 'function') {
- return value.bind(target)
- }
- return value
- },
- ownKeys: (target) => {
- loadRemoteProperties()
- return Object.getOwnPropertyNames(target)
- },
- getOwnPropertyDescriptor: (target, property) => {
- const descriptor = Object.getOwnPropertyDescriptor(target, property)
- if (descriptor) return descriptor
- loadRemoteProperties()
- return Object.getOwnPropertyDescriptor(target, property)
- }
- })
- }
- // Convert meta data from browser into real value.
- function metaToValue (meta) {
- const types = {
- value: () => meta.value,
- array: () => meta.members.map((member) => metaToValue(member)),
- buffer: () => bufferUtils.metaToBuffer(meta.value),
- promise: () => resolvePromise({ then: metaToValue(meta.then) }),
- error: () => metaToPlainObject(meta),
- date: () => new Date(meta.value),
- exception: () => { throw errorUtils.deserialize(meta.value) }
- }
- if (meta.type in types) {
- return types[meta.type]()
- } else {
- let ret
- if (remoteObjectCache.has(meta.id)) {
- return remoteObjectCache.get(meta.id)
- }
- // A shadow class to represent the remote function object.
- if (meta.type === 'function') {
- const remoteFunction = function (...args) {
- let command
- if (this && this.constructor === remoteFunction) {
- command = 'ELECTRON_BROWSER_CONSTRUCTOR'
- } else {
- command = 'ELECTRON_BROWSER_FUNCTION_CALL'
- }
- const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args))
- return metaToValue(obj)
- }
- ret = remoteFunction
- } else {
- ret = {}
- }
- setObjectMembers(ret, ret, meta.id, meta.members)
- setObjectPrototype(ret, ret, meta.id, meta.proto)
- Object.defineProperty(ret.constructor, 'name', { value: meta.name })
- // Track delegate obj's lifetime & tell browser to clean up when object is GCed.
- v8Util.setRemoteObjectFreer(ret, contextId, meta.id)
- v8Util.setHiddenValue(ret, 'atomId', meta.id)
- remoteObjectCache.set(meta.id, ret)
- return ret
- }
- }
- // Construct a plain object from the meta.
- function metaToPlainObject (meta) {
- const obj = (() => meta.type === 'error' ? new Error() : {})()
- for (let i = 0; i < meta.members.length; i++) {
- const { name, value } = meta.members[i]
- obj[name] = value
- }
- return obj
- }
- function handleMessage (channel, handler) {
- ipcRenderer.on(channel, (event, passedContextId, id, ...args) => {
- if (passedContextId === contextId) {
- handler(id, ...args)
- } else {
- // Message sent to an un-exist context, notify the error to main process.
- ipcRenderer.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id)
- }
- })
- }
- // Browser calls a callback in renderer.
- handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
- callbacksRegistry.apply(id, metaToValue(args))
- })
- // A callback in browser is released.
- handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
- callbacksRegistry.remove(id)
- })
- exports.require = (module) => {
- const command = 'ELECTRON_BROWSER_REQUIRE'
- const meta = ipcRenderer.sendSync(command, contextId, module)
- return metaToValue(meta)
- }
- // Alias to remote.require('electron').xxx.
- exports.getBuiltin = (module) => {
- const command = 'ELECTRON_BROWSER_GET_BUILTIN'
- const meta = ipcRenderer.sendSync(command, contextId, module)
- return metaToValue(meta)
- }
- exports.getCurrentWindow = () => {
- const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
- const meta = ipcRenderer.sendSync(command, contextId)
- return metaToValue(meta)
- }
- // Get current WebContents object.
- exports.getCurrentWebContents = () => {
- return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
- }
- // Get a global object in browser.
- exports.getGlobal = (name) => {
- const command = 'ELECTRON_BROWSER_GLOBAL'
- const meta = ipcRenderer.sendSync(command, contextId, name)
- return metaToValue(meta)
- }
- // Get the process object in browser.
- exports.__defineGetter__('process', () => exports.getGlobal('process'))
- // Create a function that will return the specified value when called in browser.
- exports.createFunctionWithReturnValue = (returnValue) => {
- const func = () => returnValue
- v8Util.setHiddenValue(func, 'returnValue', true)
- return func
- }
- // Get the guest WebContents from guestInstanceId.
- exports.getGuestWebContents = (guestInstanceId) => {
- const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'
- const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId)
- return metaToValue(meta)
- }
- const addBuiltinProperty = (name) => {
- Object.defineProperty(exports, name, {
- get: () => exports.getBuiltin(name)
- })
- }
- const browserModules =
- require('@electron/internal/common/api/module-list').concat(
- require('@electron/internal/browser/api/module-list'))
- // And add a helper receiver for each one.
- browserModules
- .filter((m) => !m.private)
- .map((m) => m.name)
- .forEach(addBuiltinProperty)
- 'use strict'
- const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
- module.exports = getRemoteForUsage('screen').screen
- 'use strict'
- const { EventEmitter } = require('events')
- const binding = process.atomBinding('web_frame')
- class WebFrame extends EventEmitter {
- constructor (context) {
- super()
- this.context = context
- // Lots of webview would subscribe to webFrame's events.
- this.setMaxListeners(0)
- }
- findFrameByRoutingId (...args) {
- return getWebFrame(binding._findFrameByRoutingId(this.context, ...args))
- }
- getFrameForSelector (...args) {
- return getWebFrame(binding._getFrameForSelector(this.context, ...args))
- }
- findFrameByName (...args) {
- return getWebFrame(binding._findFrameByName(this.context, ...args))
- }
- get opener () {
- return getWebFrame(binding._getOpener(this.context))
- }
- get parent () {
- return getWebFrame(binding._getParent(this.context))
- }
- get top () {
- return getWebFrame(binding._getTop(this.context))
- }
- get firstChild () {
- return getWebFrame(binding._getFirstChild(this.context))
- }
- get nextSibling () {
- return getWebFrame(binding._getNextSibling(this.context))
- }
- get routingId () {
- return binding._getRoutingId(this.context)
- }
- }
- // Populate the methods.
- for (const name in binding) {
- if (!name.startsWith('_')) { // some methods are manully populated above
- WebFrame.prototype[name] = function (...args) {
- return binding[name](this.context, ...args)
- }
- }
- }
- // Helper to return WebFrame or null depending on context.
- // TODO(zcbenz): Consider returning same WebFrame for the same context.
- function getWebFrame (context) {
- return context ? new WebFrame(context) : null
- }
- module.exports = new WebFrame(window)
- 'use strict'
- const v8Util = process.atomBinding('v8_util')
- class CallbacksRegistry {
- constructor () {
- this.nextId = 0
- this.callbacks = {}
- }
- add (callback) {
- // The callback is already added.
- let id = v8Util.getHiddenValue(callback, 'callbackId')
- if (id != null) return id
- id = this.nextId += 1
- // Capture the location of the function and put it in the ID string,
- // so that release errors can be tracked down easily.
- const regexp = /at (.*)/gi
- const stackString = (new Error()).stack
- let filenameAndLine
- let match
- while ((match = regexp.exec(stackString)) !== null) {
- const location = match[1]
- if (location.includes('(native)')) continue
- if (location.includes('(<anonymous>)')) continue
- if (location.includes('electron.asar')) continue
- const ref = /([^/^)]*)\)?$/gi.exec(location)
- filenameAndLine = ref[1]
- break
- }
- this.callbacks[id] = callback
- v8Util.setHiddenValue(callback, 'callbackId', id)
- v8Util.setHiddenValue(callback, 'location', filenameAndLine)
- return id
- }
- get (id) {
- return this.callbacks[id] || function () {}
- }
- apply (id, ...args) {
- return this.get(id).apply(global, ...args)
- }
- remove (id) {
- const callback = this.callbacks[id]
- if (callback) {
- v8Util.deleteHiddenValue(callback, 'callbackId')
- delete this.callbacks[id]
- }
- }
- }
- module.exports = CallbacksRegistry
- 'use strict'
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const Event = require('@electron/internal/renderer/extensions/event')
- const url = require('url')
- class Tab {
- constructor (tabId) {
- this.id = tabId
- }
- }
- class MessageSender {
- constructor (tabId, extensionId) {
- this.tab = tabId ? new Tab(tabId) : null
- this.id = extensionId
- this.url = `chrome-extension://${extensionId}`
- }
- }
- class Port {
- constructor (tabId, portId, extensionId, name) {
- this.tabId = tabId
- this.portId = portId
- this.disconnected = false
- this.name = name
- this.onDisconnect = new Event()
- this.onMessage = new Event()
- this.sender = new MessageSender(tabId, extensionId)
- ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
- this._onDisconnect()
- })
- ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => {
- const sendResponse = function () { console.error('sendResponse is not implemented') }
- this.onMessage.emit(message, this.sender, sendResponse)
- })
- }
- disconnect () {
- if (this.disconnected) return
- ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
- this._onDisconnect()
- }
- postMessage (message) {
- ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message)
- }
- _onDisconnect () {
- this.disconnected = true
- ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
- this.onDisconnect.emit()
- }
- }
- // Inject chrome API to the |context|
- exports.injectTo = function (extensionId, isBackgroundPage, context) {
- const chrome = context.chrome = context.chrome || {}
- let originResultID = 1
- ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => {
- chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
- })
- ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message, resultID) => {
- chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), (messageResult) => {
- ipcRenderer.send(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, messageResult)
- })
- })
- ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => {
- chrome.tabs.onCreated.emit(new Tab(tabId))
- })
- ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => {
- chrome.tabs.onRemoved.emit(tabId)
- })
- chrome.runtime = {
- id: extensionId,
- getURL: function (path) {
- return url.format({
- protocol: 'chrome-extension',
- slashes: true,
- hostname: extensionId,
- pathname: path
- })
- },
- connect (...args) {
- if (isBackgroundPage) {
- console.error('chrome.runtime.connect is not supported in background page')
- return
- }
- // Parse the optional args.
- let targetExtensionId = extensionId
- let connectInfo = { name: '' }
- if (args.length === 1) {
- connectInfo = args[0]
- } else if (args.length === 2) {
- [targetExtensionId, connectInfo] = args
- }
- const { tabId, portId } = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
- return new Port(tabId, portId, extensionId, connectInfo.name)
- },
- sendMessage (...args) {
- if (isBackgroundPage) {
- console.error('chrome.runtime.sendMessage is not supported in background page')
- return
- }
- // Parse the optional args.
- let targetExtensionId = extensionId
- let message
- if (args.length === 1) {
- message = args[0]
- } else if (args.length === 2) {
- // A case of not provide extension-id: (message, responseCallback)
- if (typeof args[1] === 'function') {
- ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[1](result))
- message = args[0]
- } else {
- [targetExtensionId, message] = args
- }
- } else {
- console.error('options is not supported')
- ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[2](result))
- }
- ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message, originResultID)
- originResultID++
- },
- onConnect: new Event(),
- onMessage: new Event(),
- onInstalled: new Event()
- }
- chrome.tabs = {
- executeScript (tabId, details, resultCallback) {
- if (resultCallback) {
- ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${originResultID}`, (event, result) => resultCallback([result]))
- }
- ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', originResultID, tabId, extensionId, details)
- originResultID++
- },
- sendMessage (tabId, message, options, responseCallback) {
- if (responseCallback) {
- ipcRenderer.on(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, (event, result) => responseCallback(result))
- }
- ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message, originResultID)
- originResultID++
- },
- onUpdated: new Event(),
- onCreated: new Event(),
- onRemoved: new Event()
- }
- chrome.extension = {
- getURL: chrome.runtime.getURL,
- connect: chrome.runtime.connect,
- onConnect: chrome.runtime.onConnect,
- sendMessage: chrome.runtime.sendMessage,
- onMessage: chrome.runtime.onMessage
- }
- chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId)
- chrome.pageAction = {
- show () {},
- hide () {},
- setTitle () {},
- getTitle () {},
- setIcon () {},
- setPopup () {},
- getPopup () {}
- }
- chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId)
- chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup()
- }
- 'use strict'
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const { runInThisContext } = require('vm')
- // Check whether pattern matches.
- // https://developer.chrome.com/extensions/match_patterns
- const matchesPattern = function (pattern) {
- if (pattern === '<all_urls>') return true
- const regexp = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)
- const url = `${location.protocol}//${location.host}${location.pathname}`
- return url.match(regexp)
- }
- // Run the code with chrome API integrated.
- const runContentScript = function (extensionId, url, code) {
- const context = {}
- require('@electron/internal/renderer/chrome-api').injectTo(extensionId, false, context)
- const wrapper = `((chrome) => {\n ${code}\n })`
- const compiledWrapper = runInThisContext(wrapper, {
- filename: url,
- lineOffset: 1,
- displayErrors: true
- })
- return compiledWrapper.call(this, context.chrome)
- }
- const runAllContentScript = function (scripts, extensionId) {
- for (const { url, code } of scripts) {
- runContentScript.call(window, extensionId, url, code)
- }
- }
- const runStylesheet = function (url, code) {
- const wrapper = `((code) => {
- function init() {
- const styleElement = document.createElement('style');
- styleElement.textContent = code;
- document.head.append(styleElement);
- }
- document.addEventListener('DOMContentLoaded', init);
- })`
- const compiledWrapper = runInThisContext(wrapper, {
- filename: url,
- lineOffset: 1,
- displayErrors: true
- })
- return compiledWrapper.call(this, code)
- }
- const runAllStylesheet = function (css) {
- for (const { url, code } of css) {
- runStylesheet.call(window, url, code)
- }
- }
- // Run injected scripts.
- // https://developer.chrome.com/extensions/content_scripts
- const injectContentScript = function (extensionId, script) {
- if (!script.matches.some(matchesPattern)) return
- if (script.js) {
- const fire = runAllContentScript.bind(window, script.js, extensionId)
- if (script.runAt === 'document_start') {
- process.once('document-start', fire)
- } else if (script.runAt === 'document_end') {
- process.once('document-end', fire)
- } else {
- document.addEventListener('DOMContentLoaded', fire)
- }
- }
- if (script.css) {
- const fire = runAllStylesheet.bind(window, script.css)
- if (script.runAt === 'document_start') {
- process.once('document-start', fire)
- } else if (script.runAt === 'document_end') {
- process.once('document-end', fire)
- } else {
- document.addEventListener('DOMContentLoaded', fire)
- }
- }
- }
- // Handle the request of chrome.tabs.executeJavaScript.
- ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) {
- const result = runContentScript.call(window, extensionId, url, code)
- ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result)
- })
- // Read the renderer process preferences.
- const preferences = process.getRenderProcessPreferences()
- if (preferences) {
- for (const pref of preferences) {
- if (pref.contentScripts) {
- for (const script of pref.contentScripts) {
- injectContentScript(pref.extensionId, script)
- }
- }
- }
- }
- 'use strict'
- class Event {
- constructor () {
- this.listeners = []
- }
- addListener (callback) {
- this.listeners.push(callback)
- }
- removeListener (callback) {
- const index = this.listeners.indexOf(callback)
- if (index !== -1) {
- this.listeners.splice(index, 1)
- }
- }
- emit (...args) {
- for (const listener of this.listeners) {
- listener(...args)
- }
- }
- }
- module.exports = Event
- 'use strict'
- // Implementation of chrome.i18n.getMessage
- // https://developer.chrome.com/extensions/i18n#method-getMessage
- //
- // Does not implement predefined messages:
- // https://developer.chrome.com/extensions/i18n#overview-predefined
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const fs = require('fs')
- const path = require('path')
- let metadata
- const getExtensionMetadata = (extensionId) => {
- if (!metadata) {
- metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId)
- }
- return metadata
- }
- const getMessagesPath = (extensionId, language) => {
- const metadata = getExtensionMetadata(extensionId)
- const localesDirectory = path.join(metadata.srcDirectory, '_locales')
- try {
- const filename = path.join(localesDirectory, language, 'messages.json')
- fs.accessSync(filename, fs.constants.R_OK)
- return filename
- } catch (err) {
- const defaultLocale = metadata.default_locale || 'en'
- return path.join(localesDirectory, defaultLocale, 'messages.json')
- }
- }
- const getMessages = (extensionId, language) => {
- try {
- const messagesPath = getMessagesPath(extensionId, language)
- return JSON.parse(fs.readFileSync(messagesPath)) || {}
- } catch (error) {
- return {}
- }
- }
- const getLanguage = () => {
- return navigator.language.replace(/-.*$/, '').toLowerCase()
- }
- const replaceNumberedSubstitutions = (message, substitutions) => {
- return message.replace(/\$(\d+)/, (_, number) => {
- const index = parseInt(number, 10) - 1
- return substitutions[index] || ''
- })
- }
- const replacePlaceholders = (message, placeholders, substitutions) => {
- if (typeof substitutions === 'string') {
- substitutions = [substitutions]
- }
- if (!Array.isArray(substitutions)) {
- substitutions = []
- }
- if (placeholders) {
- Object.keys(placeholders).forEach((name) => {
- let { content } = placeholders[name]
- content = replaceNumberedSubstitutions(content, substitutions)
- message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content)
- })
- }
- return replaceNumberedSubstitutions(message, substitutions)
- }
- const getMessage = (extensionId, messageName, substitutions) => {
- const messages = getMessages(extensionId, getLanguage())
- if (messages.hasOwnProperty(messageName)) {
- const { message, placeholders } = messages[messageName]
- return replacePlaceholders(message, placeholders, substitutions)
- }
- }
- exports.setup = (extensionId) => {
- return {
- getMessage (messageName, substitutions) {
- return getMessage(extensionId, messageName, substitutions)
- }
- }
- }
- 'use strict'
- const fs = require('fs')
- const path = require('path')
- const { remote } = require('electron')
- const { app } = remote
- const getChromeStoragePath = (storageType, extensionId) => {
- return path.join(
- app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`)
- }
- const mkdirp = (dir, callback) => {
- fs.mkdir(dir, (error) => {
- if (error && error.code === 'ENOENT') {
- mkdirp(path.dirname(dir), (error) => {
- if (!error) {
- mkdirp(dir, callback)
- }
- })
- } else if (error && error.code === 'EEXIST') {
- callback(null)
- } else {
- callback(error)
- }
- })
- }
- const readChromeStorageFile = (storageType, extensionId, cb) => {
- const filePath = getChromeStoragePath(storageType, extensionId)
- fs.readFile(filePath, 'utf8', (err, data) => {
- if (err && err.code === 'ENOENT') {
- return cb(null, null)
- }
- cb(err, data)
- })
- }
- const writeChromeStorageFile = (storageType, extensionId, data, cb) => {
- const filePath = getChromeStoragePath(storageType, extensionId)
- mkdirp(path.dirname(filePath), err => {
- if (err) { /* we just ignore the errors of mkdir or mkdirp */ }
- fs.writeFile(filePath, data, cb)
- })
- }
- const getStorage = (storageType, extensionId, cb) => {
- readChromeStorageFile(storageType, extensionId, (err, data) => {
- if (err) throw err
- if (!cb) throw new TypeError('No callback provided')
- if (data !== null) {
- cb(JSON.parse(data))
- } else {
- // Disabled due to false positive in StandardJS
- // eslint-disable-next-line standard/no-callback-literal
- cb({})
- }
- })
- }
- const setStorage = (storageType, extensionId, storage, cb) => {
- const json = JSON.stringify(storage)
- writeChromeStorageFile(storageType, extensionId, json, err => {
- if (err) throw err
- if (cb) cb()
- })
- }
- const getStorageManager = (storageType, extensionId) => {
- return {
- get (keys, callback) {
- getStorage(storageType, extensionId, storage => {
- if (keys == null) return callback(storage)
- let defaults = {}
- switch (typeof keys) {
- case 'string':
- keys = [keys]
- break
- case 'object':
- if (!Array.isArray(keys)) {
- defaults = keys
- keys = Object.keys(keys)
- }
- break
- }
- // Disabled due to false positive in StandardJS
- // eslint-disable-next-line standard/no-callback-literal
- if (keys.length === 0) return callback({})
- const items = {}
- keys.forEach(function (key) {
- let value = storage[key]
- if (value == null) value = defaults[key]
- items[key] = value
- })
- callback(items)
- })
- },
- set (items, callback) {
- getStorage(storageType, extensionId, storage => {
- Object.keys(items).forEach(function (name) {
- storage[name] = items[name]
- })
- setStorage(storageType, extensionId, storage, callback)
- })
- },
- remove (keys, callback) {
- getStorage(storageType, extensionId, storage => {
- if (!Array.isArray(keys)) {
- keys = [keys]
- }
- keys.forEach(function (key) {
- delete storage[key]
- })
- setStorage(storageType, extensionId, storage, callback)
- })
- },
- clear (callback) {
- setStorage(storageType, extensionId, {}, callback)
- }
- }
- }
- module.exports = {
- setup: extensionId => ({
- sync: getStorageManager('sync', extensionId),
- local: getStorageManager('local', extensionId)
- })
- }
- 'use strict'
- const Event = require('@electron/internal/renderer/extensions/event')
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- class WebNavigation {
- constructor () {
- this.onBeforeNavigate = new Event()
- this.onCompleted = new Event()
- ipcRenderer.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => {
- this.onBeforeNavigate.emit(details)
- })
- ipcRenderer.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => {
- this.onCompleted.emit(details)
- })
- }
- }
- exports.setup = () => {
- return new WebNavigation()
- }
- 'use strict'
- const { EventEmitter } = require('events')
- const path = require('path')
- const Module = require('module')
- // We modified the original process.argv to let node.js load the
- // init.js, we need to restore it here.
- process.argv.splice(1, 1)
- // Clear search paths.
- require('../common/reset-search-paths')
- // Import common settings.
- require('@electron/internal/common/init')
- const globalPaths = Module.globalPaths
- // Expose public APIs.
- globalPaths.push(path.join(__dirname, 'api', 'exports'))
- // The global variable will be used by ipc for event dispatching
- const v8Util = process.atomBinding('v8_util')
- v8Util.setHiddenValue(global, 'ipc', new EventEmitter())
- v8Util.setHiddenValue(global, 'ipc-internal', new EventEmitter())
- // Use electron module after everything is ready.
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- require('@electron/internal/renderer/web-frame-init')()
- // Process command line arguments.
- let nodeIntegration = false
- let webviewTag = false
- let preloadScript = null
- let preloadScripts = []
- let isBackgroundPage = false
- let appPath = null
- for (const arg of process.argv) {
- if (arg.indexOf('--guest-instance-id=') === 0) {
- // This is a guest web view.
- process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
- } else if (arg.indexOf('--opener-id=') === 0) {
- // This is a guest BrowserWindow.
- process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
- } else if (arg.indexOf('--node-integration=') === 0) {
- nodeIntegration = arg.substr(arg.indexOf('=') + 1) === 'true'
- } else if (arg.indexOf('--preload=') === 0) {
- preloadScript = arg.substr(arg.indexOf('=') + 1)
- } else if (arg === '--background-page') {
- isBackgroundPage = true
- } else if (arg.indexOf('--app-path=') === 0) {
- appPath = arg.substr(arg.indexOf('=') + 1)
- } else if (arg.indexOf('--webview-tag=') === 0) {
- webviewTag = arg.substr(arg.indexOf('=') + 1) === 'true'
- } else if (arg.indexOf('--preload-scripts') === 0) {
- preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter)
- }
- }
- // The webContents preload script is loaded after the session preload scripts.
- if (preloadScript) {
- preloadScripts.push(preloadScript)
- }
- if (window.location.protocol === 'chrome-devtools:') {
- // Override some inspector APIs.
- require('@electron/internal/renderer/inspector')
- nodeIntegration = false
- } else if (window.location.protocol === 'chrome-extension:') {
- // Add implementations of chrome API.
- require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
- nodeIntegration = false
- } else if (window.location.protocol === 'chrome:') {
- // Disable node integration for chrome UI scheme.
- nodeIntegration = false
- } else {
- // Override default web functions.
- require('@electron/internal/renderer/override')
- // Inject content scripts.
- require('@electron/internal/renderer/content-scripts-injector')
- // Load webview tag implementation.
- if (webviewTag && process.guestInstanceId == null) {
- const { setupWebView } = require('@electron/internal/renderer/web-view/web-view')
- if (process.argv.includes('--context-isolation')) {
- v8Util.setHiddenValue(window, 'setup-webview', setupWebView)
- } else {
- setupWebView(window)
- }
- }
- }
- if (nodeIntegration) {
- // Export node bindings to global.
- global.require = require
- global.module = module
- // Set the __filename to the path of html file if it is file: protocol.
- if (window.location.protocol === 'file:') {
- const location = window.location
- let pathname = location.pathname
- if (process.platform === 'win32') {
- if (pathname[0] === '/') pathname = pathname.substr(1)
- const isWindowsNetworkSharePath = location.hostname.length > 0 && globalPaths[0].startsWith('\\')
- if (isWindowsNetworkSharePath) {
- pathname = `//${location.host}/${pathname}`
- }
- }
- global.__filename = path.normalize(decodeURIComponent(pathname))
- global.__dirname = path.dirname(global.__filename)
- // Set module's filename so relative require can work as expected.
- module.filename = global.__filename
- // Also search for module under the html file.
- module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
- } else {
- global.__filename = __filename
- global.__dirname = __dirname
- if (appPath) {
- // Search for module under the app directory
- module.paths = module.paths.concat(Module._nodeModulePaths(appPath))
- }
- }
- // Redirect window.onerror to uncaughtException.
- window.onerror = function (message, filename, lineno, colno, error) {
- if (global.process.listeners('uncaughtException').length > 0) {
- global.process.emit('uncaughtException', error)
- return true
- } else {
- return false
- }
- }
- } else {
- // Delete Node's symbols after the Environment has been loaded.
- process.once('loaded', function () {
- delete global.process
- delete global.Buffer
- delete global.setImmediate
- delete global.clearImmediate
- delete global.global
- })
- }
- // Load the preload scripts.
- for (const preloadScript of preloadScripts) {
- try {
- require(preloadScript)
- } catch (error) {
- console.error('Unable to load preload script: ' + preloadScript)
- console.error(error.stack || error.message)
- }
- }
- // Warn about security issues
- require('@electron/internal/renderer/security-warnings')(nodeIntegration)
- // Report focus/blur events of webview to browser.
- // Note that while Chromium content APIs have observer for focus/blur, they
- // unfortunately do not work for webview.
- if (process.guestInstanceId) {
- window.addEventListener('focus', () => {
- ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, process.guestInstanceId)
- })
- window.addEventListener('blur', () => {
- ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, process.guestInstanceId)
- })
- }
- 'use strict'
- window.onload = function () {
- // Use menu API to show context menu.
- window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
- // correct for Chromium returning undefined for filesystem
- window.Persistence.FileSystemWorkspaceBinding.completeURL = completeURL
- // Use dialog API to override file chooser dialog.
- window.UI.createFileSelectorElement = createFileSelectorElement
- }
- // Extra / is needed as a result of MacOS requiring absolute paths
- function completeURL (project, path) {
- project = 'file:///'
- return `${project}${path}`
- }
- window.confirm = function (message, title) {
- const { dialog } = require('electron').remote
- if (title == null) {
- title = ''
- }
- return !dialog.showMessageBox({
- message: message,
- title: title,
- buttons: ['OK', 'Cancel'],
- cancelId: 1
- })
- }
- const convertToMenuTemplate = function (items) {
- return items.map(function (item) {
- const transformed = item.type === 'subMenu' ? {
- type: 'submenu',
- label: item.label,
- enabled: item.enabled,
- submenu: convertToMenuTemplate(item.subItems)
- } : item.type === 'separator' ? {
- type: 'separator'
- } : item.type === 'checkbox' ? {
- type: 'checkbox',
- label: item.label,
- enabled: item.enabled,
- checked: item.checked
- } : {
- type: 'normal',
- label: item.label,
- enabled: item.enabled
- }
- if (item.id != null) {
- transformed.click = function () {
- window.DevToolsAPI.contextMenuItemSelected(item.id)
- return window.DevToolsAPI.contextMenuCleared()
- }
- }
- return transformed
- })
- }
- const createMenu = function (x, y, items) {
- const { remote } = require('electron')
- const { Menu } = remote
- let template = convertToMenuTemplate(items)
- if (useEditMenuItems(x, y, template)) {
- template = getEditMenuItems()
- }
- const menu = Menu.buildFromTemplate(template)
- // The menu is expected to show asynchronously.
- setTimeout(function () {
- menu.popup({ window: remote.getCurrentWindow() })
- })
- }
- const useEditMenuItems = function (x, y, items) {
- return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
- return element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || element.isContentEditable
- })
- }
- const getEditMenuItems = function () {
- return [
- {
- role: 'undo'
- },
- {
- role: 'redo'
- },
- {
- type: 'separator'
- },
- {
- role: 'cut'
- },
- {
- role: 'copy'
- },
- {
- role: 'paste'
- },
- {
- role: 'pasteAndMatchStyle'
- },
- {
- role: 'delete'
- },
- {
- role: 'selectAll'
- }
- ]
- }
- const showFileChooserDialog = function (callback) {
- const { dialog } = require('electron').remote
- const files = dialog.showOpenDialog({})
- if (files != null) {
- callback(pathToHtml5FileObject(files[0]))
- }
- }
- const pathToHtml5FileObject = function (path) {
- const fs = require('fs')
- const blob = new Blob([fs.readFileSync(path)])
- blob.name = path
- return blob
- }
- const createFileSelectorElement = function (callback) {
- const fileSelectorElement = document.createElement('span')
- fileSelectorElement.style.display = 'none'
- fileSelectorElement.click = showFileChooserDialog.bind(this, callback)
- return fileSelectorElement
- }
- 'use strict'
- const binding = process.atomBinding('ipc')
- const v8Util = process.atomBinding('v8_util')
- // Created by init.js.
- const ipcRenderer = v8Util.getHiddenValue(global, 'ipc-internal')
- const internal = true
- ipcRenderer.send = function (...args) {
- return binding.send('ipc-internal-message', args)
- }
- ipcRenderer.sendSync = function (...args) {
- return binding.sendSync('ipc-internal-message-sync', args)[0]
- }
- ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
- return binding.sendTo(internal, false, webContentsId, channel, args)
- }
- ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
- return binding.sendTo(internal, true, webContentsId, channel, args)
- }
- module.exports = ipcRenderer
- '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)
- 'use strict'
- const { remote } = require('electron')
- exports.getRemoteForUsage = function (usage) {
- if (!remote) {
- throw new Error(`${usage} requires remote, which is not enabled`)
- }
- return remote
- }
- 'use strict'
- let shouldLog = null
- /**
- * This method checks if a security message should be logged.
- * It does so by determining whether we're running as Electron,
- * which indicates that a developer is currently looking at the
- * app.
- *
- * @returns {boolean} - Should we log?
- */
- const shouldLogSecurityWarnings = function () {
- if (shouldLog !== null) {
- return shouldLog
- }
- const { platform, execPath, env } = process
- switch (platform) {
- case 'darwin':
- shouldLog = execPath.endsWith('MacOS/Electron') ||
- execPath.includes('Electron.app/Contents/Frameworks/')
- break
- case 'freebsd':
- case 'linux':
- shouldLog = execPath.endsWith('/electron')
- break
- case 'win32':
- shouldLog = execPath.endsWith('\\electron.exe')
- break
- default:
- shouldLog = false
- }
- if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
- (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
- shouldLog = false
- }
- if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
- (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
- shouldLog = true
- }
- return shouldLog
- }
- /**
- * Checks if the current window is remote.
- *
- * @returns {boolean} - Is this a remote protocol?
- */
- const getIsRemoteProtocol = function () {
- if (window && window.location && window.location.protocol) {
- return /^(http|ftp)s?/gi.test(window.location.protocol)
- }
- }
- /**
- * Tries to determine whether a CSP without `unsafe-eval` is set.
- *
- * @returns {boolean} Is a CSP with `unsafe-eval` set?
- */
- const isUnsafeEvalEnabled = function () {
- const { webFrame } = require('electron')
- return new Promise((resolve) => {
- webFrame.executeJavaScript(`(${(() => {
- try {
- new Function('') // eslint-disable-line no-new,no-new-func
- } catch (err) {
- return false
- }
- return true
- }).toString()})()`, resolve)
- })
- }
- const moreInformation = `\nFor more information and help, consult
- https://electronjs.org/docs/tutorial/security.\n This warning will not show up
- once the app is packaged.`
- /**
- * #1 Only load secure content
- *
- * Checks the loaded resources on the current page and logs a
- * message about all resources loaded over HTTP or FTP.
- */
- const warnAboutInsecureResources = function () {
- if (!window || !window.performance || !window.performance.getEntriesByType) {
- return
- }
- const resources = window.performance
- .getEntriesByType('resource')
- .filter(({ name }) => /^(http|ftp):/gi.test(name || ''))
- .map(({ name }) => `- ${name}`)
- .join('\n')
- if (!resources || resources.length === 0) {
- return
- }
- const warning = `This renderer process loads resources using insecure
- protocols.This exposes users of this app to unnecessary security risks.
- Consider loading the following resources over HTTPS or FTPS. \n ${resources}
- \n ${moreInformation}`
- console.warn('%cElectron Security Warning (Insecure Resources)',
- 'font-weight: bold;', warning)
- }
- /**
- * #2 on the checklist: Disable the Node.js integration in all renderers that
- * display remote content
- *
- * Logs a warning message about Node integration.
- */
- const warnAboutNodeWithRemoteContent = function (nodeIntegration) {
- if (!nodeIntegration) return
- if (getIsRemoteProtocol()) {
- const warning = `This renderer process has Node.js integration enabled
- and attempted to load remote content from '${window.location}'. This
- exposes users of this app to severe security risks.\n ${moreInformation}`
- console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
- 'font-weight: bold;', warning)
- }
- }
- // Currently missing since it has ramifications and is still experimental:
- // #3 Enable context isolation in all renderers that display remote content
- //
- // Currently missing since we can't easily programmatically check for those cases:
- // #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content
- /**
- * #5 on the checklist: Do not disable websecurity
- *
- * Logs a warning message about disabled webSecurity.
- */
- const warnAboutDisabledWebSecurity = function (webPreferences) {
- if (!webPreferences || webPreferences.webSecurity !== false) return
- const warning = `This renderer process has "webSecurity" disabled. This
- exposes users of this app to severe security risks.\n ${moreInformation}`
- console.warn('%cElectron Security Warning (Disabled webSecurity)',
- 'font-weight: bold;', warning)
- }
- /**
- * #6 on the checklist: Define a Content-Security-Policy and use restrictive
- * rules (i.e. script-src 'self')
- *
- * #7 on the checklist: Disable eval
- *
- * Logs a warning message about unset or insecure CSP
- */
- const warnAboutInsecureCSP = function () {
- isUnsafeEvalEnabled().then((enabled) => {
- if (!enabled) return
- const warning = `This renderer process has either no Content Security
- Policy set or a policy with "unsafe-eval" enabled. This exposes users of
- this app to unnecessary security risks.\n ${moreInformation}`
- console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
- 'font-weight: bold;', warning)
- })
- }
- /**
- * #8 on the checklist: Do not set allowRunningInsecureContent to true
- *
- * Logs a warning message about disabled webSecurity.
- */
- const warnAboutInsecureContentAllowed = function (webPreferences) {
- if (!webPreferences || !webPreferences.allowRunningInsecureContent) return
- const warning = `This renderer process has "allowRunningInsecureContent"
- enabled. This exposes users of this app to severe security risks.\n
- ${moreInformation}`
- console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
- 'font-weight: bold;', warning)
- }
- /**
- * #9 on the checklist: Do not enable experimental features
- *
- * Logs a warning message about experimental features.
- */
- const warnAboutExperimentalFeatures = function (webPreferences) {
- if (!webPreferences || (!webPreferences.experimentalFeatures)) {
- return
- }
- const warning = `This renderer process has "experimentalFeatures" enabled.
- This exposes users of this app to some security risk. If you do not need
- this feature, you should disable it.\n ${moreInformation}`
- console.warn('%cElectron Security Warning (experimentalFeatures)',
- 'font-weight: bold;', warning)
- }
- /**
- * #10 on the checklist: Do not use enableBlinkFeatures
- *
- * Logs a warning message about enableBlinkFeatures
- */
- const warnAboutEnableBlinkFeatures = function (webPreferences) {
- if (webPreferences === null ||
- !webPreferences.hasOwnProperty('enableBlinkFeatures') ||
- webPreferences.enableBlinkFeatures.length === 0) {
- return
- }
- const warning = `This renderer process has additional "enableBlinkFeatures"
- enabled. This exposes users of this app to some security risk. If you do not
- need this feature, you should disable it.\n ${moreInformation}`
- console.warn('%cElectron Security Warning (enableBlinkFeatures)',
- 'font-weight: bold;', warning)
- }
- /**
- * #11 on the checklist: Do Not Use allowpopups
- *
- * Logs a warning message about allowed popups
- */
- const warnAboutAllowedPopups = function () {
- if (document && document.querySelectorAll) {
- const domElements = document.querySelectorAll('[allowpopups]')
- if (!domElements || domElements.length === 0) {
- return
- }
- const warning = `A <webview> has "allowpopups" set to true. This exposes
- users of this app to some security risk, since popups are just
- BrowserWindows. If you do not need this feature, you should disable it.\n
- ${moreInformation}`
- console.warn('%cElectron Security Warning (allowpopups)',
- 'font-weight: bold;', warning)
- }
- }
- const warnAboutNodeIntegrationDefault = function (webPreferences) {
- if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) {
- const warning = `This window has node integration enabled by default. In ` +
- `Electron 5.0.0, node integration will be disabled by default. To prepare ` +
- `for this change, set {nodeIntegration: true} in the webPreferences for ` +
- `this window, or ensure that this window does not rely on node integration ` +
- `and set {nodeIntegration: false}.`
- console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning)
- }
- }
- const warnAboutContextIsolationDefault = function (webPreferences) {
- if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) {
- const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content'
- const warning = `This window has context isolation disabled by default. In ` +
- `Electron 5.0.0, context isolation will be enabled by default. To prepare ` +
- `for this change, set {contextIsolation: false} in the webPreferences for ` +
- `this window, or ensure that this window does not rely on context ` +
- `isolation being disabled, and set {contextIsolation: true}.\n\n` +
- `For more information, see ${url}`
- console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning)
- }
- }
- const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) {
- if (!webPreferences) {
- return
- }
- if (webPreferences.webviewTagWasExplicitlyEnabled) {
- return
- }
- if (!document || !document.getElementsByTagName) {
- return
- }
- const webviews = document.getElementsByTagName('webview')
- if (webviews && webviews.length > 0) {
- const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-'
- const warning = `This window has the <webview> tag enabled by default. In ` +
- `Electron 5.0.0, <webview> tags will be disabled by default. To prepare ` +
- `for this change, set {webviewTag: true} in the webPreferences for ` +
- `this window.\n\n` +
- `For more information, see ${url}`
- console.warn('%cElectron Deprecation Warning (webviewTag default change)',
- 'font-weight: bold;', warning)
- }
- }
- // Currently missing since we can't easily programmatically check for it:
- // #12WebViews: Verify the options and params of all `<webview>` tags
- const logSecurityWarnings = function (webPreferences, nodeIntegration) {
- warnAboutNodeWithRemoteContent(nodeIntegration)
- warnAboutDisabledWebSecurity(webPreferences)
- warnAboutInsecureResources()
- warnAboutInsecureContentAllowed(webPreferences)
- warnAboutExperimentalFeatures(webPreferences)
- warnAboutEnableBlinkFeatures(webPreferences)
- warnAboutInsecureCSP()
- warnAboutAllowedPopups()
- warnAboutNodeIntegrationDefault(webPreferences)
- warnAboutContextIsolationDefault(webPreferences)
- warnAboutDeprecatedWebviewTagDefault(webPreferences)
- }
- const getWebPreferences = function () {
- const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
- const errorUtils = require('@electron/internal/common/error-utils')
- const [ error, result ] = ipcRenderer.sendSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
- if (error) {
- console.warn(`getLastWebPreferences() failed: ${errorUtils.deserialize(error)}`)
- return null
- } else {
- return result
- }
- }
- module.exports = function (nodeIntegration) {
- const loadHandler = function () {
- if (shouldLogSecurityWarnings()) {
- const webPreferences = getWebPreferences()
- logSecurityWarnings(webPreferences, nodeIntegration)
- }
- }
- window.addEventListener('load', loadHandler, { once: true })
- }
- '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)
- })
- })
- }
- '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': [],
- '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'
- }
- 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(domEvent)
- }
- }
- 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')
- }
- ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params)
- }
- }
- '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 resolveURL = function (url) {
- if (url === '') return ''
- a.href = url
- return a.href
- }
- // Attribute objects.
- // Default implementation of a WebView attribute.
- class WebViewAttribute {
- constructor (name, webViewImpl) {
- this.name = name
- this.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)
- }
- }
- }
- // Attribute representing the state of the storage partition.
- class PartitionAttribute extends WebViewAttribute {
- 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(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
- }
- }
- 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 <webview> 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()
- }
- // The purpose of this mutation observer is to catch assignment to the src
- // attribute without any changes to its value. This is useful in the case
- // where the webview guest has crashed and navigating to the same address
- // spawns off a new process.
- setupMutationObserver () {
- this.observer = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- const { oldValue } = mutation
- const newValue = this.getValue()
- if (oldValue !== newValue) {
- return
- }
- this.handleMutation(oldValue, newValue)
- }
- })
- const params = {
- attributes: true,
- attributeOldValue: true,
- attributeFilter: [this.name]
- }
- this.observer.observe(this.webViewImpl.webviewNode, params)
- }
- parse () {
- if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.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[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(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl)
- }
- }
- // Attribute specifies user agent
- class UserAgentAttribute extends WebViewAttribute {
- constructor (webViewImpl) {
- super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl)
- }
- }
- // Attribute that set preload script.
- class PreloadAttribute extends WebViewAttribute {
- constructor (webViewImpl) {
- super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl)
- }
- getValue () {
- if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
- return this.value
- }
- let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
- const protocol = preload.substr(0, 5)
- if (protocol !== 'file:') {
- console.error(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(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl)
- }
- }
- // Attribute that specifies the blink features to be disabled.
- class DisableBlinkFeaturesAttribute extends WebViewAttribute {
- constructor (webViewImpl) {
- super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
- }
- }
- // Attribute that specifies the web preferences to be enabled.
- class WebPreferencesAttribute extends WebViewAttribute {
- constructor (webViewImpl) {
- super(webViewConstants.ATTRIBUTE_WEBPREFERENCES, webViewImpl)
- }
- }
- class EnableRemoteModuleAttribute extends WebViewAttribute {
- 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.
- 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)
- }
- '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: '<webview>: ' + '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.'
- }
- '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 = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>'
- 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 <webview> element to the DOM.
- reset () {
- // If guestInstanceId is defined then the <webview> 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 <webview>.request property.
- setRequestPropertyOnWebViewNode (request) {
- Object.defineProperty(this.webviewNode, 'request', {
- value: request,
- enumerable: true
- })
- }
- // This observer monitors mutations to attributes of the <webview> 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<event>' 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 <webview> 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 }
- '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()
- // - window.history.length
- // - window.open()
- // - window.opener.blur()
- // - window.opener.close()
- // - window.opener.eval()
- // - window.opener.focus()
- // - window.opener.location
- // - window.opener.print()
- // - window.opener.postMessage()
- // - window.prompt()
- // - document.hidden
- // - document.visibilityState
- const { defineProperty } = Object
- // Helper function to resolve relative url.
- const a = window.top.document.createElement('a')
- const resolveURL = function (url) {
- a.href = url
- return a.href
- }
- // Use this method to ensure values expected as strings in the main process
- // are convertible to strings in the renderer process. This ensures exceptions
- // converting values to strings are thrown in this process.
- const toString = (value) => {
- return value != null ? `${value}` : value
- }
- const windowProxies = {}
- const getOrCreateProxy = (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]
- }
- 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)
- }
- }
- 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 = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features))
- if (guestId != null) {
- return getOrCreateProxy(ipcRenderer, guestId)
- } else {
- return null
- }
- }
- if (openerId != null) {
- window.opener = getOrCreateProxy(ipcRenderer, openerId)
- }
- }
- // 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 <webview> 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
- }
- })
- }
- }
- 'use strict'
- const path = require('path')
- const Module = require('module')
- // We modified the original process.argv to let node.js load the
- // init.js, we need to restore it here.
- process.argv.splice(1, 1)
- // Clear search paths.
- require('../common/reset-search-paths')
- // Import common settings.
- require('@electron/internal/common/init')
- // Expose public APIs.
- Module.globalPaths.push(path.join(__dirname, 'api', 'exports'))
- // Export node bindings to global.
- global.require = require
- global.module = module
- // Set the __filename to the path of html file if it is file: protocol.
- if (self.location.protocol === 'file:') {
- const pathname = process.platform === 'win32' && self.location.pathname[0] === '/' ? self.location.pathname.substr(1) : self.location.pathname
- global.__filename = path.normalize(decodeURIComponent(pathname))
- global.__dirname = path.dirname(global.__filename)
- // Set module's filename so relative require can work as expected.
- module.filename = global.__filename
- // Also search for module under the html file.
- module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
- } else {
- global.__filename = __filename
- global.__dirname = __dirname
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement