Advertisement
Guest User

Untitled

a guest
Dec 30th, 2017
279
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 235.46 KB | None | 0 0
  1.  t p l {"files":{"browser":{"files":{"api":{"files":{"app.js":{"size":2587,"offset":"0"},"auto-updater":{"files":{"auto-updater-native.js":{"size":229,"offset":"2766"},"auto-updater-win.js":{"size":1746,"offset":"2995"},"squirrel-update-win.js":{"size":3547,"offset":"4741"}}},"auto-updater.js":{"size":179,"offset":"2587"},"browser-view.js":{"size":221,"offset":"8288"},"browser-window.js":{"size":6583,"offset":"8509"},"content-tracing.js":{"size":57,"offset":"15092"},"dialog.js":{"size":8770,"offset":"15149"},"exports":{"files":{"electron.js":{"size":422,"offset":"23919"}}},"global-shortcut.js":{"size":72,"offset":"24341"},"ipc-main.js":{"size":530,"offset":"24413"},"menu-item-roles.js":{"size":6340,"offset":"24943"},"menu-item.js":{"size":2561,"offset":"31283"},"menu.js":{"size":9750,"offset":"33844"},"module-list.js":{"size":1103,"offset":"43594"},"navigation-controller.js":{"size":5578,"offset":"44697"},"net.js":{"size":10151,"offset":"50275"},"power-monitor.js":{"size":223,"offset":"60426"},"power-save-blocker.js":{"size":77,"offset":"60649"},"protocol.js":{"size":802,"offset":"60726"},"screen.js":{"size":192,"offset":"61528"},"session.js":{"size":1057,"offset":"61720"},"system-preferences.js":{"size":248,"offset":"62777"},"touch-bar.js":{"size":9231,"offset":"63025"},"tray.js":{"size":176,"offset":"72256"},"web-contents.js":{"size":9177,"offset":"72432"}}},"chrome-extension.js":{"size":13000,"offset":"81609"},"desktop-capturer.js":{"size":2707,"offset":"94609"},"guest-view-manager.js":{"size":9929,"offset":"97316"},"guest-window-manager.js":{"size":12628,"offset":"107245"},"init.js":{"size":5156,"offset":"119873"},"objects-registry.js":{"size":2944,"offset":"125029"},"rpc-server.js":{"size":15366,"offset":"127973"}}},"common":{"files":{"api":{"files":{"callbacks-registry.js":{"size":1549,"offset":"143339"},"clipboard.js":{"size":628,"offset":"144888"},"crash-reporter.js":{"size":3517,"offset":"145516"},"deprecate.js":{"size":2840,"offset":"149033"},"deprecations.js":{"size":242,"offset":"151873"},"exports":{"files":{"electron.js":{"size":387,"offset":"152115"}}},"is-promise.js":{"size":334,"offset":"152502"},"module-list.js":{"size":570,"offset":"152836"},"native-image.js":{"size":54,"offset":"153406"},"shell.js":{"size":47,"offset":"153460"}}},"atom-binding-setup.js":{"size":355,"offset":"153507"},"init.js":{"size":1886,"offset":"153862"},"parse-features-string.js":{"size":731,"offset":"155748"},"reset-search-paths.js":{"size":1322,"offset":"156479"}}},"renderer":{"files":{"api":{"files":{"desktop-capturer.js":{"size":1485,"offset":"157801"},"exports":{"files":{"electron.js":{"size":348,"offset":"159286"}}},"ipc-renderer.js":{"size":1424,"offset":"159634"},"module-list.js":{"size":297,"offset":"161058"},"remote.js":{"size":11949,"offset":"161355"},"screen.js":{"size":52,"offset":"173304"},"web-frame.js":{"size":341,"offset":"173356"}}},"chrome-api.js":{"size":5866,"offset":"173697"},"content-scripts-injector.js":{"size":2164,"offset":"179563"},"extensions":{"files":{"event.js":{"size":430,"offset":"181727"},"i18n.js":{"size":2604,"offset":"182157"},"storage.js":{"size":2046,"offset":"184761"},"web-navigation.js":{"size":529,"offset":"186807"}}},"init.js":{"size":5477,"offset":"187336"},"inspector.js":{"size":3173,"offset":"192813"},"override.js":{"size":246,"offset":"195986"},"web-view":{"files":{"guest-view-internal.js":{"size":4413,"offset":"196232"},"web-view-attributes.js":{"size":12049,"offset":"200645"},"web-view-constants.js":{"size":1358,"offset":"212694"},"web-view.js":{"size":15933,"offset":"214052"}}},"window-setup.js":{"size":6196,"offset":"229985"}}},"worker":{"files":{"init.js":{"size":1226,"offset":"236181"}}}}}'use strict'
  2.  
  3. const bindings = process.atomBinding('app')
  4. const {app, App} = bindings
  5.  
  6. // Only one app object permitted.
  7. module.exports = app
  8.  
  9. const electron = require('electron')
  10. const {deprecate, Menu} = electron
  11. const {EventEmitter} = require('events')
  12.  
  13. Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
  14.  
  15. Object.assign(app, {
  16. setApplicationMenu (menu) {
  17. return Menu.setApplicationMenu(menu)
  18. },
  19. getApplicationMenu () {
  20. return Menu.getApplicationMenu()
  21. },
  22. commandLine: {
  23. appendSwitch (...args) {
  24. const castedArgs = args.map((arg) => {
  25. return typeof arg !== 'string' ? `${arg}` : arg
  26. })
  27. return bindings.appendSwitch(...castedArgs)
  28. },
  29. appendArgument (...args) {
  30. const castedArgs = args.map((arg) => {
  31. return typeof arg !== 'string' ? `${arg}` : arg
  32. })
  33. return bindings.appendArgument(...castedArgs)
  34. }
  35. }
  36. })
  37.  
  38. if (process.platform === 'darwin') {
  39. app.dock = {
  40. bounce (type = 'informational') {
  41. return bindings.dockBounce(type)
  42. },
  43. cancelBounce: bindings.dockCancelBounce,
  44. downloadFinished: bindings.dockDownloadFinished,
  45. setBadge: bindings.dockSetBadgeText,
  46. getBadge: bindings.dockGetBadgeText,
  47. hide: bindings.dockHide,
  48. show: bindings.dockShow,
  49. isVisible: bindings.dockIsVisible,
  50. setMenu: bindings.dockSetMenu,
  51. setIcon: bindings.dockSetIcon
  52. }
  53. }
  54.  
  55. if (process.platform === 'linux') {
  56. app.launcher = {
  57. setBadgeCount: bindings.unityLauncherSetBadgeCount,
  58. getBadgeCount: bindings.unityLauncherGetBadgeCount,
  59. isCounterBadgeAvailable: bindings.unityLauncherAvailable,
  60. isUnityRunning: bindings.unityLauncherAvailable
  61. }
  62. }
  63.  
  64. app.allowNTLMCredentialsForAllDomains = function (allow) {
  65. if (!process.noDeprecations) {
  66. deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains')
  67. }
  68. let domains = allow ? '*' : ''
  69. if (!this.isReady()) {
  70. this.commandLine.appendSwitch('auth-server-whitelist', domains)
  71. } else {
  72. electron.session.defaultSession.allowNTLMCredentialsForDomains(domains)
  73. }
  74. }
  75.  
  76. // Routes the events to webContents.
  77. const events = ['login', 'certificate-error', 'select-client-certificate']
  78. for (let name of events) {
  79. app.on(name, (event, webContents, ...args) => {
  80. webContents.emit(name, event, ...args)
  81. })
  82. }
  83.  
  84. // Wrappers for native classes.
  85. const {DownloadItem} = process.atomBinding('download_item')
  86. Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype)
  87. if (process.platform === 'win32') {
  88. module.exports = require('./auto-updater/auto-updater-win')
  89. } else {
  90. module.exports = require('./auto-updater/auto-updater-native')
  91. }
  92. const EventEmitter = require('events').EventEmitter
  93. const {autoUpdater, AutoUpdater} = process.atomBinding('auto_updater')
  94.  
  95. Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype)
  96.  
  97. module.exports = autoUpdater
  98. 'use strict'
  99.  
  100. const {app} = require('electron')
  101. const {EventEmitter} = require('events')
  102. const squirrelUpdate = require('./squirrel-update-win')
  103.  
  104. class AutoUpdater extends EventEmitter {
  105. quitAndInstall () {
  106. if (!this.updateAvailable) {
  107. return this.emitError('No update available, can\'t quit and install')
  108. }
  109. squirrelUpdate.processStart()
  110. app.quit()
  111. }
  112.  
  113. getFeedURL () {
  114. return this.updateURL
  115. }
  116.  
  117. setFeedURL (updateURL, headers) {
  118. this.updateURL = updateURL
  119. }
  120.  
  121. checkForUpdates () {
  122. if (!this.updateURL) {
  123. return this.emitError('Update URL is not set')
  124. }
  125. if (!squirrelUpdate.supported()) {
  126. return this.emitError('Can not find Squirrel')
  127. }
  128. this.emit('checking-for-update')
  129. squirrelUpdate.download(this.updateURL, (error, update) => {
  130. if (error != null) {
  131. return this.emitError(error)
  132. }
  133. if (update == null) {
  134. return this.emit('update-not-available')
  135. }
  136. this.updateAvailable = true
  137. this.emit('update-available')
  138. squirrelUpdate.update(this.updateURL, (error) => {
  139. if (error != null) {
  140. return this.emitError(error)
  141. }
  142. const {releaseNotes, version} = update
  143. // Date is not available on Windows, so fake it.
  144. const date = new Date()
  145. this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
  146. this.quitAndInstall()
  147. })
  148. })
  149. })
  150. }
  151.  
  152. // Private: Emit both error object and message, this is to keep compatibility
  153. // with Old APIs.
  154. emitError (message) {
  155. this.emit('error', new Error(message), message)
  156. }
  157. }
  158.  
  159. module.exports = new AutoUpdater()
  160. const fs = require('fs')
  161. const path = require('path')
  162. const spawn = require('child_process').spawn
  163.  
  164. // i.e. my-app/app-0.1.13/
  165. const appFolder = path.dirname(process.execPath)
  166.  
  167. // i.e. my-app/Update.exe
  168. const updateExe = path.resolve(appFolder, '..', 'Update.exe')
  169. const exeName = path.basename(process.execPath)
  170. var spawnedArgs = []
  171. var spawnedProcess
  172.  
  173. var isSameArgs = function (args) {
  174. return (args.length === spawnedArgs.length) && args.every(function (e, i) {
  175. return e === spawnedArgs[i]
  176. })
  177. }
  178.  
  179. // Spawn a command and invoke the callback when it completes with an error
  180. // and the output from standard out.
  181. var spawnUpdate = function (args, detached, callback) {
  182. var error, errorEmitted, stderr, stdout
  183.  
  184. try {
  185. // Ensure we don't spawn multiple squirrel processes
  186. // Process spawned, same args: Attach events to alread running process
  187. // Process spawned, different args: Return with error
  188. // No process spawned: Spawn new process
  189. if (spawnedProcess && !isSameArgs(args)) {
  190. return callback('AutoUpdater process with arguments ' + args + ' is already running')
  191. } else if (!spawnedProcess) {
  192. spawnedProcess = spawn(updateExe, args, {
  193. detached: detached
  194. })
  195. spawnedArgs = args || []
  196. }
  197. } catch (error1) {
  198. error = error1
  199.  
  200. // Shouldn't happen, but still guard it.
  201. process.nextTick(function () {
  202. return callback(error)
  203. })
  204. return
  205. }
  206. stdout = ''
  207. stderr = ''
  208. spawnedProcess.stdout.on('data', function (data) {
  209. stdout += data
  210. })
  211. spawnedProcess.stderr.on('data', function (data) {
  212. stderr += data
  213. })
  214. errorEmitted = false
  215. spawnedProcess.on('error', function (error) {
  216. errorEmitted = true
  217. callback(error)
  218. })
  219. return spawnedProcess.on('exit', function (code, signal) {
  220. spawnedProcess = undefined
  221. spawnedArgs = []
  222.  
  223. // We may have already emitted an error.
  224. if (errorEmitted) {
  225. return
  226. }
  227.  
  228. // Process terminated with error.
  229. if (code !== 0) {
  230. return callback('Command failed: ' + (signal != null ? signal : code) + '\n' + stderr)
  231. }
  232.  
  233. // Success.
  234. callback(null, stdout)
  235. })
  236. }
  237.  
  238. // Start an instance of the installed app.
  239. exports.processStart = function () {
  240. return spawnUpdate(['--processStartAndWait', exeName], true, function () {})
  241. }
  242.  
  243. // Download the releases specified by the URL and write new results to stdout.
  244. exports.download = function (updateURL, callback) {
  245. return spawnUpdate(['--download', updateURL], false, function (error, stdout) {
  246. var json, ref, ref1, update
  247. if (error != null) {
  248. return callback(error)
  249. }
  250. try {
  251. // Last line of output is the JSON details about the releases
  252. json = stdout.trim().split('\n').pop()
  253. update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0
  254. } catch (jsonError) {
  255. return callback('Invalid result:\n' + stdout)
  256. }
  257. return callback(null, update)
  258. })
  259. }
  260.  
  261. // Update the application to the latest remote version specified by URL.
  262. exports.update = function (updateURL, callback) {
  263. return spawnUpdate(['--update', updateURL], false, callback)
  264. }
  265.  
  266. // Is the Update.exe installed with the current application?
  267. exports.supported = function () {
  268. try {
  269. fs.accessSync(updateExe, fs.R_OK)
  270. return true
  271. } catch (error) {
  272. return false
  273. }
  274. }
  275. 'use strict'
  276.  
  277. const {EventEmitter} = require('events')
  278. const {BrowserView} = process.atomBinding('browser_view')
  279.  
  280. Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype)
  281.  
  282. module.exports = BrowserView
  283. 'use strict'
  284.  
  285. const electron = require('electron')
  286. const {ipcMain} = electron
  287. const {EventEmitter} = require('events')
  288. const {BrowserWindow} = process.atomBinding('window')
  289. const v8Util = process.atomBinding('v8_util')
  290.  
  291. Object.setPrototypeOf(BrowserWindow.prototype, EventEmitter.prototype)
  292.  
  293. BrowserWindow.prototype._init = function () {
  294. // Avoid recursive require.
  295. const {app} = require('electron')
  296.  
  297. // Simulate the application menu on platforms other than macOS.
  298. if (process.platform !== 'darwin') {
  299. const menu = app.getApplicationMenu()
  300. if (menu) this.setMenu(menu)
  301. }
  302.  
  303. // Make new windows requested by links behave like "window.open"
  304. this.webContents.on('-new-window', (event, url, frameName,
  305. disposition, additionalFeatures,
  306. postData) => {
  307. const options = {
  308. show: true,
  309. width: 800,
  310. height: 600
  311. }
  312. ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
  313. event, url, frameName, disposition,
  314. options, additionalFeatures, postData)
  315. })
  316.  
  317. this.webContents.on('-web-contents-created', (event, webContents, url,
  318. frameName) => {
  319. v8Util.setHiddenValue(webContents, 'url-framename', {url, frameName})
  320. })
  321. // Create a new browser window for the native implementation of
  322. // "window.open"(sandbox mode only)
  323. this.webContents.on('-add-new-contents', (event, webContents, disposition,
  324. userGesture, left, top, width,
  325. height) => {
  326. let urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
  327. if ((disposition !== 'foreground-tab' && disposition !== 'new-window') ||
  328. !urlFrameName) {
  329. return
  330. }
  331.  
  332. let {url, frameName} = urlFrameName
  333. v8Util.deleteHiddenValue(webContents, 'url-framename')
  334. const options = {
  335. show: true,
  336. x: left,
  337. y: top,
  338. width: width || 800,
  339. height: height || 600,
  340. webContents: webContents
  341. }
  342. ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
  343. event, url, frameName, disposition, options)
  344. })
  345.  
  346. // window.resizeTo(...)
  347. // window.moveTo(...)
  348. this.webContents.on('move', (event, size) => {
  349. this.setBounds(size)
  350. })
  351.  
  352. // Hide the auto-hide menu when webContents is focused.
  353. this.webContents.on('activate', () => {
  354. if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) {
  355. this.setMenuBarVisibility(false)
  356. }
  357. })
  358.  
  359. // Change window title to page title.
  360. this.webContents.on('page-title-updated', (event, title) => {
  361. // Route the event to BrowserWindow.
  362. this.emit('page-title-updated', event, title)
  363. if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
  364. })
  365.  
  366. // Sometimes the webContents doesn't get focus when window is shown, so we
  367. // have to force focusing on webContents in this case. The safest way is to
  368. // focus it when we first start to load URL, if we do it earlier it won't
  369. // have effect, if we do it later we might move focus in the page.
  370. //
  371. // Though this hack is only needed on macOS when the app is launched from
  372. // Finder, we still do it on all platforms in case of other bugs we don't
  373. // know.
  374. this.webContents.once('load-url', function () {
  375. this.focus()
  376. })
  377.  
  378. // Redirect focus/blur event to app instance too.
  379. this.on('blur', (event) => {
  380. app.emit('browser-window-blur', event, this)
  381. })
  382. this.on('focus', (event) => {
  383. app.emit('browser-window-focus', event, this)
  384. })
  385.  
  386. // Subscribe to visibilityState changes and pass to renderer process.
  387. let isVisible = this.isVisible() && !this.isMinimized()
  388. const visibilityChanged = () => {
  389. const newState = this.isVisible() && !this.isMinimized()
  390. if (isVisible !== newState) {
  391. isVisible = newState
  392. const visibilityState = isVisible ? 'visible' : 'hidden'
  393. this.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState)
  394. this.webContents.emit('-window-visibility-change', visibilityState)
  395. }
  396. }
  397.  
  398. const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']
  399. for (let event of visibilityEvents) {
  400. this.on(event, visibilityChanged)
  401. }
  402.  
  403. // Notify the creation of the window.
  404. app.emit('browser-window-created', {}, this)
  405.  
  406. Object.defineProperty(this, 'devToolsWebContents', {
  407. enumerable: true,
  408. configurable: false,
  409. get () {
  410. return this.webContents.devToolsWebContents
  411. }
  412. })
  413. }
  414.  
  415. BrowserWindow.getFocusedWindow = () => {
  416. for (let window of BrowserWindow.getAllWindows()) {
  417. if (window.isFocused()) return window
  418. }
  419. return null
  420. }
  421.  
  422. BrowserWindow.fromWebContents = (webContents) => {
  423. for (const window of BrowserWindow.getAllWindows()) {
  424. if (window.webContents.equal(webContents)) return window
  425. }
  426. }
  427.  
  428. BrowserWindow.fromDevToolsWebContents = (webContents) => {
  429. for (const window of BrowserWindow.getAllWindows()) {
  430. const {devToolsWebContents} = window
  431. if (devToolsWebContents != null && devToolsWebContents.equal(webContents)) {
  432. return window
  433. }
  434. }
  435. }
  436.  
  437. // Helpers.
  438. Object.assign(BrowserWindow.prototype, {
  439. loadURL (...args) {
  440. return this.webContents.loadURL(...args)
  441. },
  442. getURL (...args) {
  443. return this.webContents.getURL()
  444. },
  445. reload (...args) {
  446. return this.webContents.reload(...args)
  447. },
  448. send (...args) {
  449. return this.webContents.send(...args)
  450. },
  451. openDevTools (...args) {
  452. return this.webContents.openDevTools(...args)
  453. },
  454. closeDevTools () {
  455. return this.webContents.closeDevTools()
  456. },
  457. isDevToolsOpened () {
  458. return this.webContents.isDevToolsOpened()
  459. },
  460. isDevToolsFocused () {
  461. return this.webContents.isDevToolsFocused()
  462. },
  463. toggleDevTools () {
  464. return this.webContents.toggleDevTools()
  465. },
  466. inspectElement (...args) {
  467. return this.webContents.inspectElement(...args)
  468. },
  469. inspectServiceWorker () {
  470. return this.webContents.inspectServiceWorker()
  471. },
  472. showDefinitionForSelection () {
  473. return this.webContents.showDefinitionForSelection()
  474. },
  475. capturePage (...args) {
  476. return this.webContents.capturePage(...args)
  477. },
  478. setTouchBar (touchBar) {
  479. electron.TouchBar._setOnWindow(touchBar, this)
  480. }
  481. })
  482.  
  483. module.exports = BrowserWindow
  484. module.exports = process.atomBinding('content_tracing')
  485. 'use strict'
  486.  
  487. const {app, BrowserWindow} = require('electron')
  488. const binding = process.atomBinding('dialog')
  489. const v8Util = process.atomBinding('v8_util')
  490.  
  491. const fileDialogProperties = {
  492. openFile: 1 << 0,
  493. openDirectory: 1 << 1,
  494. multiSelections: 1 << 2,
  495. createDirectory: 1 << 3,
  496. showHiddenFiles: 1 << 4,
  497. promptToCreate: 1 << 5,
  498. noResolveAliases: 1 << 6
  499. }
  500.  
  501. const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']
  502.  
  503. const messageBoxOptions = {
  504. noLink: 1 << 0
  505. }
  506.  
  507. const parseArgs = function (window, options, callback, ...args) {
  508. if (window != null && window.constructor !== BrowserWindow) {
  509. // Shift.
  510. [callback, options, window] = [options, window, null]
  511. }
  512.  
  513. if ((callback == null) && typeof options === 'function') {
  514. // Shift.
  515. [callback, options] = [options, null]
  516. }
  517.  
  518. // Fallback to using very last argument as the callback function
  519. const lastArgument = args[args.length - 1]
  520. if ((callback == null) && typeof lastArgument === 'function') {
  521. callback = lastArgument
  522. }
  523.  
  524. return [window, options, callback]
  525. }
  526.  
  527. const normalizeAccessKey = (text) => {
  528. if (typeof text !== 'string') return text
  529.  
  530. // macOS does not have access keys so remove single ampersands
  531. // and replace double ampersands with a single ampersand
  532. if (process.platform === 'darwin') {
  533. return text.replace(/&(&?)/g, '$1')
  534. }
  535.  
  536. // Linux uses a single underscore as an access key prefix so escape
  537. // existing single underscores with a second underscore, replace double
  538. // ampersands with a single ampersand, and replace a single ampersand with
  539. // a single underscore
  540. if (process.platform === 'linux') {
  541. return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
  542. if (after === '&') return after
  543. return `_${after}`
  544. })
  545. }
  546.  
  547. return text
  548. }
  549.  
  550. const checkAppInitialized = function () {
  551. if (!app.isReady()) {
  552. throw new Error('dialog module can only be used after app is ready')
  553. }
  554. }
  555.  
  556. module.exports = {
  557. showOpenDialog: function (...args) {
  558. checkAppInitialized()
  559.  
  560. let [window, options, callback] = parseArgs(...args)
  561.  
  562. if (options == null) {
  563. options = {
  564. title: 'Open',
  565. properties: ['openFile']
  566. }
  567. }
  568.  
  569. let {buttonLabel, defaultPath, filters, properties, title, message} = options
  570.  
  571. if (properties == null) {
  572. properties = ['openFile']
  573. } else if (!Array.isArray(properties)) {
  574. throw new TypeError('Properties must be an array')
  575. }
  576.  
  577. let dialogProperties = 0
  578. for (const prop in fileDialogProperties) {
  579. if (properties.includes(prop)) {
  580. dialogProperties |= fileDialogProperties[prop]
  581. }
  582. }
  583.  
  584. if (title == null) {
  585. title = ''
  586. } else if (typeof title !== 'string') {
  587. throw new TypeError('Title must be a string')
  588. }
  589.  
  590. if (buttonLabel == null) {
  591. buttonLabel = ''
  592. } else if (typeof buttonLabel !== 'string') {
  593. throw new TypeError('Button label must be a string')
  594. }
  595.  
  596. if (defaultPath == null) {
  597. defaultPath = ''
  598. } else if (typeof defaultPath !== 'string') {
  599. throw new TypeError('Default path must be a string')
  600. }
  601.  
  602. if (filters == null) {
  603. filters = []
  604. }
  605.  
  606. if (message == null) {
  607. message = ''
  608. } else if (typeof message !== 'string') {
  609. throw new TypeError('Message must be a string')
  610. }
  611.  
  612. const wrappedCallback = typeof callback === 'function' ? function (success, result) {
  613. return callback(success ? result : void 0)
  614. } : null
  615. const settings = {title, buttonLabel, defaultPath, filters, message, window}
  616. settings.properties = dialogProperties
  617. return binding.showOpenDialog(settings, wrappedCallback)
  618. },
  619.  
  620. showSaveDialog: function (...args) {
  621. checkAppInitialized()
  622.  
  623. let [window, options, callback] = parseArgs(...args)
  624.  
  625. if (options == null) {
  626. options = {
  627. title: 'Save'
  628. }
  629. }
  630.  
  631. let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, showsTagField} = options
  632.  
  633. if (title == null) {
  634. title = ''
  635. } else if (typeof title !== 'string') {
  636. throw new TypeError('Title must be a string')
  637. }
  638.  
  639. if (buttonLabel == null) {
  640. buttonLabel = ''
  641. } else if (typeof buttonLabel !== 'string') {
  642. throw new TypeError('Button label must be a string')
  643. }
  644.  
  645. if (defaultPath == null) {
  646. defaultPath = ''
  647. } else if (typeof defaultPath !== 'string') {
  648. throw new TypeError('Default path must be a string')
  649. }
  650.  
  651. if (filters == null) {
  652. filters = []
  653. }
  654.  
  655. if (message == null) {
  656. message = ''
  657. } else if (typeof message !== 'string') {
  658. throw new TypeError('Message must be a string')
  659. }
  660.  
  661. if (nameFieldLabel == null) {
  662. nameFieldLabel = ''
  663. } else if (typeof nameFieldLabel !== 'string') {
  664. throw new TypeError('Name field label must be a string')
  665. }
  666.  
  667. if (showsTagField == null) {
  668. showsTagField = true
  669. }
  670.  
  671. const wrappedCallback = typeof callback === 'function' ? function (success, result) {
  672. return callback(success ? result : void 0)
  673. } : null
  674. const settings = {title, buttonLabel, defaultPath, filters, message, nameFieldLabel, showsTagField, window}
  675. return binding.showSaveDialog(settings, wrappedCallback)
  676. },
  677.  
  678. showMessageBox: function (...args) {
  679. checkAppInitialized()
  680.  
  681. let [window, options, callback] = parseArgs(...args)
  682.  
  683. if (options == null) {
  684. options = {
  685. type: 'none'
  686. }
  687. }
  688.  
  689. let {
  690. buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail,
  691. icon, message, title, type
  692. } = options
  693.  
  694. if (type == null) {
  695. type = 'none'
  696. }
  697.  
  698. const messageBoxType = messageBoxTypes.indexOf(type)
  699. if (messageBoxType === -1) {
  700. throw new TypeError('Invalid message box type')
  701. }
  702.  
  703. if (buttons == null) {
  704. buttons = []
  705. } else if (!Array.isArray(buttons)) {
  706. throw new TypeError('Buttons must be an array')
  707. }
  708.  
  709. if (options.normalizeAccessKeys) {
  710. buttons = buttons.map(normalizeAccessKey)
  711. }
  712.  
  713. if (title == null) {
  714. title = ''
  715. } else if (typeof title !== 'string') {
  716. throw new TypeError('Title must be a string')
  717. }
  718.  
  719. if (message == null) {
  720. message = ''
  721. } else if (typeof message !== 'string') {
  722. throw new TypeError('Message must be a string')
  723. }
  724.  
  725. if (detail == null) {
  726. detail = ''
  727. } else if (typeof detail !== 'string') {
  728. throw new TypeError('Detail must be a string')
  729. }
  730.  
  731. checkboxChecked = !!checkboxChecked
  732.  
  733. if (checkboxLabel == null) {
  734. checkboxLabel = ''
  735. } else if (typeof checkboxLabel !== 'string') {
  736. throw new TypeError('checkboxLabel must be a string')
  737. }
  738.  
  739. if (icon == null) {
  740. icon = null
  741. }
  742.  
  743. if (defaultId == null) {
  744. defaultId = -1
  745. }
  746.  
  747. // Choose a default button to get selected when dialog is cancelled.
  748. if (cancelId == null) {
  749. cancelId = 0
  750. for (let i = 0; i < buttons.length; i++) {
  751. const text = buttons[i].toLowerCase()
  752. if (text === 'cancel' || text === 'no') {
  753. cancelId = i
  754. break
  755. }
  756. }
  757. }
  758.  
  759. const flags = options.noLink ? messageBoxOptions.noLink : 0
  760. return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
  761. flags, title, message, detail, checkboxLabel,
  762. checkboxChecked, icon, window, callback)
  763. },
  764.  
  765. showErrorBox: function (...args) {
  766. return binding.showErrorBox(...args)
  767. },
  768.  
  769. showCertificateTrustDialog: function (...args) {
  770. let [window, options, callback] = parseArgs(...args)
  771.  
  772. if (options == null || typeof options !== 'object') {
  773. throw new TypeError('options must be an object')
  774. }
  775.  
  776. let {certificate, message} = options
  777. if (certificate == null || typeof certificate !== 'object') {
  778. throw new TypeError('certificate must be an object')
  779. }
  780.  
  781. if (message == null) {
  782. message = ''
  783. } else if (typeof message !== 'string') {
  784. throw new TypeError('message must be a string')
  785. }
  786.  
  787. return binding.showCertificateTrustDialog(window, certificate, message, callback)
  788. }
  789. }
  790.  
  791. // Mark standard asynchronous functions.
  792. v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)
  793. v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true)
  794. v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true)
  795. const common = require('../../../common/api/exports/electron')
  796. // since browser module list is also used in renderer, keep it separate.
  797. const moduleList = require('../module-list')
  798.  
  799. // Import common modules.
  800. common.defineProperties(exports)
  801.  
  802. for (const module of moduleList) {
  803. Object.defineProperty(exports, module.name, {
  804. enumerable: !module.private,
  805. get: () => require(`../${module.file}`)
  806. })
  807. }
  808. module.exports = process.atomBinding('global_shortcut').globalShortcut
  809. const EventEmitter = require('events').EventEmitter
  810.  
  811. const emitter = new EventEmitter()
  812.  
  813. const removeAllListeners = emitter.removeAllListeners.bind(emitter)
  814. emitter.removeAllListeners = function (...args) {
  815. if (args.length === 0) {
  816. throw new Error('Removing all listeners from ipcMain will make Electron internals stop working. Please specify a event name')
  817. }
  818. removeAllListeners(...args)
  819. }
  820.  
  821. // Do not throw exception when channel name is "error".
  822. emitter.on('error', () => {})
  823.  
  824. module.exports = emitter
  825. const {app} = require('electron')
  826.  
  827. const roles = {
  828. about: {
  829. get label () {
  830. return process.platform === 'linux' ? 'About' : `About ${app.getName()}`
  831. }
  832. },
  833. close: {
  834. label: process.platform === 'darwin' ? 'Close Window' : 'Close',
  835. accelerator: 'CommandOrControl+W',
  836. windowMethod: 'close'
  837. },
  838. copy: {
  839. label: 'Copy',
  840. accelerator: 'CommandOrControl+C',
  841. webContentsMethod: 'copy'
  842. },
  843. cut: {
  844. label: 'Cut',
  845. accelerator: 'CommandOrControl+X',
  846. webContentsMethod: 'cut'
  847. },
  848. delete: {
  849. label: 'Delete',
  850. webContentsMethod: 'delete'
  851. },
  852. forcereload: {
  853. label: 'Force Reload',
  854. accelerator: 'Shift+CmdOrCtrl+R',
  855. nonNativeMacOSRole: true,
  856. windowMethod: (window) => {
  857. window.webContents.reloadIgnoringCache()
  858. }
  859. },
  860. front: {
  861. label: 'Bring All to Front'
  862. },
  863. help: {
  864. label: 'Help'
  865. },
  866. hide: {
  867. get label () {
  868. return `Hide ${app.getName()}`
  869. },
  870. accelerator: 'Command+H'
  871. },
  872. hideothers: {
  873. label: 'Hide Others',
  874. accelerator: 'Command+Alt+H'
  875. },
  876. minimize: {
  877. label: 'Minimize',
  878. accelerator: 'CommandOrControl+M',
  879. windowMethod: 'minimize'
  880. },
  881. paste: {
  882. label: 'Paste',
  883. accelerator: 'CommandOrControl+V',
  884. webContentsMethod: 'paste'
  885. },
  886. pasteandmatchstyle: {
  887. label: 'Paste and Match Style',
  888. accelerator: 'Shift+CommandOrControl+V',
  889. webContentsMethod: 'pasteAndMatchStyle'
  890. },
  891. quit: {
  892. get label () {
  893. switch (process.platform) {
  894. case 'darwin': return `Quit ${app.getName()}`
  895. case 'win32': return 'Exit'
  896. default: return 'Quit'
  897. }
  898. },
  899. accelerator: process.platform === 'win32' ? null : 'CommandOrControl+Q',
  900. appMethod: 'quit'
  901. },
  902. redo: {
  903. label: 'Redo',
  904. accelerator: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z',
  905. webContentsMethod: 'redo'
  906. },
  907. reload: {
  908. label: 'Reload',
  909. accelerator: 'CmdOrCtrl+R',
  910. nonNativeMacOSRole: true,
  911. windowMethod: 'reload'
  912. },
  913. resetzoom: {
  914. label: 'Actual Size',
  915. accelerator: 'CommandOrControl+0',
  916. nonNativeMacOSRole: true,
  917. webContentsMethod: (webContents) => {
  918. webContents.setZoomLevel(0)
  919. }
  920. },
  921. selectall: {
  922. label: 'Select All',
  923. accelerator: 'CommandOrControl+A',
  924. webContentsMethod: 'selectAll'
  925. },
  926. services: {
  927. label: 'Services'
  928. },
  929. startspeaking: {
  930. label: 'Start Speaking'
  931. },
  932. stopspeaking: {
  933. label: 'Stop Speaking'
  934. },
  935. toggledevtools: {
  936. label: 'Toggle Developer Tools',
  937. accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
  938. nonNativeMacOSRole: true,
  939. windowMethod: 'toggleDevTools'
  940. },
  941. togglefullscreen: {
  942. label: 'Toggle Full Screen',
  943. accelerator: process.platform === 'darwin' ? 'Control+Command+F' : 'F11',
  944. windowMethod: (window) => {
  945. window.setFullScreen(!window.isFullScreen())
  946. }
  947. },
  948. undo: {
  949. label: 'Undo',
  950. accelerator: 'CommandOrControl+Z',
  951. webContentsMethod: 'undo'
  952. },
  953. unhide: {
  954. label: 'Show All'
  955. },
  956. window: {
  957. label: 'Window'
  958. },
  959. zoom: {
  960. label: 'Zoom'
  961. },
  962. zoomin: {
  963. label: 'Zoom In',
  964. accelerator: 'CommandOrControl+Plus',
  965. nonNativeMacOSRole: true,
  966. webContentsMethod: (webContents) => {
  967. webContents.getZoomLevel((zoomLevel) => {
  968. webContents.setZoomLevel(zoomLevel + 0.5)
  969. })
  970. }
  971. },
  972. zoomout: {
  973. label: 'Zoom Out',
  974. accelerator: 'CommandOrControl+-',
  975. nonNativeMacOSRole: true,
  976. webContentsMethod: (webContents) => {
  977. webContents.getZoomLevel((zoomLevel) => {
  978. webContents.setZoomLevel(zoomLevel - 0.5)
  979. })
  980. }
  981. },
  982. // Edit submenu (should fit both Mac & Windows)
  983. editMenu: {
  984. label: 'Edit',
  985. submenu: [
  986. {
  987. role: 'undo'
  988. },
  989. {
  990. role: 'redo'
  991. },
  992. {
  993. type: 'separator'
  994. },
  995. {
  996. role: 'cut'
  997. },
  998. {
  999. role: 'copy'
  1000. },
  1001. {
  1002. role: 'paste'
  1003. },
  1004.  
  1005. process.platform === 'darwin' ? {
  1006. role: 'pasteandmatchstyle'
  1007. } : null,
  1008.  
  1009. {
  1010. role: 'delete'
  1011. },
  1012.  
  1013. process.platform === 'win32' ? {
  1014. type: 'separator'
  1015. } : null,
  1016.  
  1017. {
  1018. role: 'selectall'
  1019. }
  1020. ]
  1021. },
  1022.  
  1023. // Window submenu should be used for Mac only
  1024. windowMenu: {
  1025. label: 'Window',
  1026. submenu: [
  1027. {
  1028. role: 'minimize'
  1029. },
  1030. {
  1031. role: 'close'
  1032. },
  1033.  
  1034. process.platform === 'darwin' ? {
  1035. type: 'separator'
  1036. } : null,
  1037.  
  1038. process.platform === 'darwin' ? {
  1039. role: 'front'
  1040. } : null
  1041.  
  1042. ]
  1043. }
  1044. }
  1045.  
  1046. const canExecuteRole = (role) => {
  1047. if (!roles.hasOwnProperty(role)) return false
  1048. if (process.platform !== 'darwin') return true
  1049. // macOS handles all roles natively except for a few
  1050. return roles[role].nonNativeMacOSRole
  1051. }
  1052.  
  1053. exports.getDefaultLabel = (role) => {
  1054. if (roles.hasOwnProperty(role)) {
  1055. return roles[role].label
  1056. } else {
  1057. return ''
  1058. }
  1059. }
  1060.  
  1061. exports.getDefaultAccelerator = (role) => {
  1062. if (roles.hasOwnProperty(role)) return roles[role].accelerator
  1063. }
  1064.  
  1065. exports.getDefaultSubmenu = (role) => {
  1066. if (!roles.hasOwnProperty(role)) return
  1067.  
  1068. let {submenu} = roles[role]
  1069.  
  1070. // remove null items from within the submenu
  1071. if (Array.isArray(submenu)) {
  1072. submenu = submenu.filter((item) => item != null)
  1073. }
  1074.  
  1075. return submenu
  1076. }
  1077.  
  1078. exports.execute = (role, focusedWindow, focusedWebContents) => {
  1079. if (!canExecuteRole(role)) return false
  1080.  
  1081. const {appMethod, webContentsMethod, windowMethod} = roles[role]
  1082.  
  1083. if (appMethod) {
  1084. app[appMethod]()
  1085. return true
  1086. }
  1087.  
  1088. if (windowMethod && focusedWindow != null) {
  1089. if (typeof windowMethod === 'function') {
  1090. windowMethod(focusedWindow)
  1091. } else {
  1092. focusedWindow[windowMethod]()
  1093. }
  1094. return true
  1095. }
  1096.  
  1097. if (webContentsMethod && focusedWebContents != null) {
  1098. if (typeof webContentsMethod === 'function') {
  1099. webContentsMethod(focusedWebContents)
  1100. } else {
  1101. focusedWebContents[webContentsMethod]()
  1102. }
  1103. return true
  1104. }
  1105.  
  1106. return false
  1107. }
  1108. 'use strict'
  1109.  
  1110. const roles = require('./menu-item-roles')
  1111.  
  1112. let nextCommandId = 0
  1113.  
  1114. const MenuItem = function (options) {
  1115. const {Menu} = require('electron')
  1116.  
  1117. // Preserve extra fields specified by user
  1118. for (let key in options) {
  1119. if (!(key in this)) this[key] = options[key]
  1120. }
  1121. this.submenu = this.submenu || roles.getDefaultSubmenu(this.role)
  1122. if (this.submenu != null && this.submenu.constructor !== Menu) {
  1123. this.submenu = Menu.buildFromTemplate(this.submenu)
  1124. }
  1125. if (this.type == null && this.submenu != null) {
  1126. this.type = 'submenu'
  1127. }
  1128. if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) {
  1129. throw new Error('Invalid submenu')
  1130. }
  1131.  
  1132. this.overrideReadOnlyProperty('type', 'normal')
  1133. this.overrideReadOnlyProperty('role')
  1134. this.overrideReadOnlyProperty('accelerator')
  1135. this.overrideReadOnlyProperty('icon')
  1136. this.overrideReadOnlyProperty('submenu')
  1137.  
  1138. this.overrideProperty('label', roles.getDefaultLabel(this.role))
  1139. this.overrideProperty('sublabel', '')
  1140. this.overrideProperty('enabled', true)
  1141. this.overrideProperty('visible', true)
  1142. this.overrideProperty('checked', false)
  1143.  
  1144. if (!MenuItem.types.includes(this.type)) {
  1145. throw new Error(`Unknown menu item type: ${this.type}`)
  1146. }
  1147.  
  1148. this.overrideReadOnlyProperty('commandId', ++nextCommandId)
  1149.  
  1150. const click = options.click
  1151. this.click = (event, focusedWindow, focusedWebContents) => {
  1152. // Manually flip the checked flags when clicked.
  1153. if (this.type === 'checkbox' || this.type === 'radio') {
  1154. this.checked = !this.checked
  1155. }
  1156.  
  1157. if (!roles.execute(this.role, focusedWindow, focusedWebContents)) {
  1158. if (typeof click === 'function') {
  1159. click(this, focusedWindow, event)
  1160. } else if (typeof this.selector === 'string' && process.platform === 'darwin') {
  1161. Menu.sendActionToFirstResponder(this.selector)
  1162. }
  1163. }
  1164. }
  1165. }
  1166.  
  1167. MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']
  1168.  
  1169. MenuItem.prototype.getDefaultRoleAccelerator = function () {
  1170. return roles.getDefaultAccelerator(this.role)
  1171. }
  1172.  
  1173. MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
  1174. if (this[name] == null) {
  1175. this[name] = defaultValue
  1176. }
  1177. }
  1178.  
  1179. MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
  1180. this.overrideProperty(name, defaultValue)
  1181. Object.defineProperty(this, name, {
  1182. enumerable: true,
  1183. writable: false,
  1184. value: this[name]
  1185. })
  1186. }
  1187.  
  1188. module.exports = MenuItem
  1189. 'use strict'
  1190.  
  1191. const {BrowserWindow, MenuItem, webContents} = require('electron')
  1192. const EventEmitter = require('events').EventEmitter
  1193. const v8Util = process.atomBinding('v8_util')
  1194. const bindings = process.atomBinding('menu')
  1195.  
  1196. // Automatically generated radio menu item's group id.
  1197. var nextGroupId = 0
  1198.  
  1199. // Search between separators to find a radio menu item and return its group id,
  1200. // otherwise generate a group id.
  1201. var generateGroupId = function (items, pos) {
  1202. var i, item, j, k, ref1, ref2, ref3
  1203. if (pos > 0) {
  1204. for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) {
  1205. item = items[i]
  1206. if (item.type === 'radio') {
  1207. return item.groupId
  1208. }
  1209. if (item.type === 'separator') {
  1210. break
  1211. }
  1212. }
  1213. } else if (pos < items.length) {
  1214. for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) {
  1215. item = items[i]
  1216. if (item.type === 'radio') {
  1217. return item.groupId
  1218. }
  1219. if (item.type === 'separator') {
  1220. break
  1221. }
  1222. }
  1223. }
  1224. return ++nextGroupId
  1225. }
  1226.  
  1227. // Returns the index of item according to |id|.
  1228. var indexOfItemById = function (items, id) {
  1229. var i, item, j, len
  1230. for (i = j = 0, len = items.length; j < len; i = ++j) {
  1231. item = items[i]
  1232. if (item.id === id) {
  1233. return i
  1234. }
  1235. }
  1236. return -1
  1237. }
  1238.  
  1239. // Returns the index of where to insert the item according to |position|.
  1240. var indexToInsertByPosition = function (items, position) {
  1241. var insertIndex
  1242. if (!position) {
  1243. return items.length
  1244. }
  1245. const [query, id] = position.split('=')
  1246. insertIndex = indexOfItemById(items, id)
  1247. if (insertIndex === -1 && query !== 'endof') {
  1248. console.warn("Item with id '" + id + "' is not found")
  1249. return items.length
  1250. }
  1251. switch (query) {
  1252. case 'after':
  1253. insertIndex++
  1254. break
  1255. case 'endof':
  1256.  
  1257. // If the |id| doesn't exist, then create a new group with the |id|.
  1258. if (insertIndex === -1) {
  1259. items.push({
  1260. id: id,
  1261. type: 'separator'
  1262. })
  1263. insertIndex = items.length - 1
  1264. }
  1265.  
  1266. // Find the end of the group.
  1267. insertIndex++
  1268. while (insertIndex < items.length && items[insertIndex].type !== 'separator') {
  1269. insertIndex++
  1270. }
  1271. }
  1272. return insertIndex
  1273. }
  1274.  
  1275. const Menu = bindings.Menu
  1276.  
  1277. Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype)
  1278.  
  1279. Menu.prototype._init = function () {
  1280. this.commandsMap = {}
  1281. this.groupsMap = {}
  1282. this.items = []
  1283. this.delegate = {
  1284. isCommandIdChecked: (commandId) => {
  1285. var command = this.commandsMap[commandId]
  1286. return command != null ? command.checked : undefined
  1287. },
  1288. isCommandIdEnabled: (commandId) => {
  1289. var command = this.commandsMap[commandId]
  1290. return command != null ? command.enabled : undefined
  1291. },
  1292. isCommandIdVisible: (commandId) => {
  1293. var command = this.commandsMap[commandId]
  1294. return command != null ? command.visible : undefined
  1295. },
  1296. getAcceleratorForCommandId: (commandId, useDefaultAccelerator) => {
  1297. const command = this.commandsMap[commandId]
  1298. if (command == null) return
  1299. if (command.accelerator != null) return command.accelerator
  1300. if (useDefaultAccelerator) return command.getDefaultRoleAccelerator()
  1301. },
  1302. getIconForCommandId: (commandId) => {
  1303. var command = this.commandsMap[commandId]
  1304. return command != null ? command.icon : undefined
  1305. },
  1306. executeCommand: (event, commandId) => {
  1307. const command = this.commandsMap[commandId]
  1308. if (command == null) return
  1309. command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents())
  1310. },
  1311. menuWillShow: () => {
  1312. // Make sure radio groups have at least one menu item seleted.
  1313. var checked, group, id, j, len, radioItem, ref1
  1314. ref1 = this.groupsMap
  1315. for (id in ref1) {
  1316. group = ref1[id]
  1317. checked = false
  1318. for (j = 0, len = group.length; j < len; j++) {
  1319. radioItem = group[j]
  1320. if (!radioItem.checked) {
  1321. continue
  1322. }
  1323. checked = true
  1324. break
  1325. }
  1326. if (!checked) {
  1327. v8Util.setHiddenValue(group[0], 'checked', true)
  1328. }
  1329. }
  1330. }
  1331. }
  1332. }
  1333.  
  1334. Menu.prototype.popup = function (window, x, y, positioningItem) {
  1335. let asyncPopup
  1336.  
  1337. // menu.popup(x, y, positioningItem)
  1338. if (window != null && (typeof window !== 'object' || window.constructor !== BrowserWindow)) {
  1339. // Shift.
  1340. positioningItem = y
  1341. y = x
  1342. x = window
  1343. window = null
  1344. }
  1345.  
  1346. // menu.popup(window, {})
  1347. if (x != null && typeof x === 'object') {
  1348. const options = x
  1349. x = options.x
  1350. y = options.y
  1351. positioningItem = options.positioningItem
  1352. asyncPopup = options.async
  1353. }
  1354.  
  1355. // Default to showing in focused window.
  1356. if (window == null) window = BrowserWindow.getFocusedWindow()
  1357.  
  1358. // Default to showing under mouse location.
  1359. if (typeof x !== 'number') x = -1
  1360. if (typeof y !== 'number') y = -1
  1361.  
  1362. // Default to not highlighting any item.
  1363. if (typeof positioningItem !== 'number') positioningItem = -1
  1364.  
  1365. // Default to synchronous for backwards compatibility.
  1366. if (typeof asyncPopup !== 'boolean') asyncPopup = false
  1367.  
  1368. this.popupAt(window, x, y, positioningItem, asyncPopup)
  1369. }
  1370.  
  1371. Menu.prototype.closePopup = function (window) {
  1372. if (window == null || window.constructor !== BrowserWindow) {
  1373. window = BrowserWindow.getFocusedWindow()
  1374. }
  1375. if (window != null) {
  1376. this.closePopupAt(window.id)
  1377. }
  1378. }
  1379.  
  1380. Menu.prototype.append = function (item) {
  1381. return this.insert(this.getItemCount(), item)
  1382. }
  1383.  
  1384. Menu.prototype.insert = function (pos, item) {
  1385. var base, name
  1386. if ((item != null ? item.constructor : void 0) !== MenuItem) {
  1387. throw new TypeError('Invalid item')
  1388. }
  1389. switch (item.type) {
  1390. case 'normal':
  1391. this.insertItem(pos, item.commandId, item.label)
  1392. break
  1393. case 'checkbox':
  1394. this.insertCheckItem(pos, item.commandId, item.label)
  1395. break
  1396. case 'separator':
  1397. this.insertSeparator(pos)
  1398. break
  1399. case 'submenu':
  1400. this.insertSubMenu(pos, item.commandId, item.label, item.submenu)
  1401. break
  1402. case 'radio':
  1403. // Grouping radio menu items.
  1404. item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos))
  1405. if ((base = this.groupsMap)[name = item.groupId] == null) {
  1406. base[name] = []
  1407. }
  1408. this.groupsMap[item.groupId].push(item)
  1409.  
  1410. // Setting a radio menu item should flip other items in the group.
  1411. v8Util.setHiddenValue(item, 'checked', item.checked)
  1412. Object.defineProperty(item, 'checked', {
  1413. enumerable: true,
  1414. get: function () {
  1415. return v8Util.getHiddenValue(item, 'checked')
  1416. },
  1417. set: () => {
  1418. var j, len, otherItem, ref1
  1419. ref1 = this.groupsMap[item.groupId]
  1420. for (j = 0, len = ref1.length; j < len; j++) {
  1421. otherItem = ref1[j]
  1422. if (otherItem !== item) {
  1423. v8Util.setHiddenValue(otherItem, 'checked', false)
  1424. }
  1425. }
  1426. return v8Util.setHiddenValue(item, 'checked', true)
  1427. }
  1428. })
  1429. this.insertRadioItem(pos, item.commandId, item.label, item.groupId)
  1430. }
  1431. if (item.sublabel != null) {
  1432. this.setSublabel(pos, item.sublabel)
  1433. }
  1434. if (item.icon != null) {
  1435. this.setIcon(pos, item.icon)
  1436. }
  1437. if (item.role != null) {
  1438. this.setRole(pos, item.role)
  1439. }
  1440.  
  1441. // Make menu accessable to items.
  1442. item.overrideReadOnlyProperty('menu', this)
  1443.  
  1444. // Remember the items.
  1445. this.items.splice(pos, 0, item)
  1446. this.commandsMap[item.commandId] = item
  1447. }
  1448.  
  1449. // Force menuWillShow to be called
  1450. Menu.prototype._callMenuWillShow = function () {
  1451. if (this.delegate != null) {
  1452. this.delegate.menuWillShow()
  1453. }
  1454. this.items.forEach(function (item) {
  1455. if (item.submenu != null) {
  1456. item.submenu._callMenuWillShow()
  1457. }
  1458. })
  1459. }
  1460.  
  1461. var applicationMenu = null
  1462.  
  1463. Menu.setApplicationMenu = function (menu) {
  1464. if (!(menu === null || menu.constructor === Menu)) {
  1465. throw new TypeError('Invalid menu')
  1466. }
  1467.  
  1468. // Keep a reference.
  1469. applicationMenu = menu
  1470. if (process.platform === 'darwin') {
  1471. if (menu === null) {
  1472. return
  1473. }
  1474. menu._callMenuWillShow()
  1475. bindings.setApplicationMenu(menu)
  1476. } else {
  1477. BrowserWindow.getAllWindows().forEach(function (window) {
  1478. window.setMenu(menu)
  1479. })
  1480. }
  1481. }
  1482.  
  1483. Menu.getApplicationMenu = function () {
  1484. return applicationMenu
  1485. }
  1486.  
  1487. Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder
  1488.  
  1489. Menu.buildFromTemplate = function (template) {
  1490. var insertIndex, item, j, k, len, len1, menu, menuItem, positionedTemplate
  1491. if (!Array.isArray(template)) {
  1492. throw new TypeError('Invalid template for Menu')
  1493. }
  1494. positionedTemplate = []
  1495. insertIndex = 0
  1496. for (j = 0, len = template.length; j < len; j++) {
  1497. item = template[j]
  1498. if (item.position) {
  1499. insertIndex = indexToInsertByPosition(positionedTemplate, item.position)
  1500. } else {
  1501. // If no |position| is specified, insert after last item.
  1502. insertIndex++
  1503. }
  1504. positionedTemplate.splice(insertIndex, 0, item)
  1505. }
  1506. menu = new Menu()
  1507. for (k = 0, len1 = positionedTemplate.length; k < len1; k++) {
  1508. item = positionedTemplate[k]
  1509. if (typeof item !== 'object') {
  1510. throw new TypeError('Invalid template for MenuItem')
  1511. }
  1512. menuItem = new MenuItem(item)
  1513. menu.append(menuItem)
  1514. }
  1515. return menu
  1516. }
  1517.  
  1518. module.exports = Menu
  1519. // Browser side modules, please sort alphabetically.
  1520. module.exports = [
  1521. {name: 'app', file: 'app'},
  1522. {name: 'autoUpdater', file: 'auto-updater'},
  1523. {name: 'BrowserView', file: 'browser-view'},
  1524. {name: 'BrowserWindow', file: 'browser-window'},
  1525. {name: 'contentTracing', file: 'content-tracing'},
  1526. {name: 'dialog', file: 'dialog'},
  1527. {name: 'globalShortcut', file: 'global-shortcut'},
  1528. {name: 'ipcMain', file: 'ipc-main'},
  1529. {name: 'Menu', file: 'menu'},
  1530. {name: 'MenuItem', file: 'menu-item'},
  1531. {name: 'net', file: 'net'},
  1532. {name: 'powerMonitor', file: 'power-monitor'},
  1533. {name: 'powerSaveBlocker', file: 'power-save-blocker'},
  1534. {name: 'protocol', file: 'protocol'},
  1535. {name: 'screen', file: 'screen'},
  1536. {name: 'session', file: 'session'},
  1537. {name: 'systemPreferences', file: 'system-preferences'},
  1538. {name: 'TouchBar', file: 'touch-bar'},
  1539. {name: 'Tray', file: 'tray'},
  1540. {name: 'webContents', file: 'web-contents'},
  1541. // The internal modules, invisible unless you know their names.
  1542. {name: 'NavigationController', file: 'navigation-controller', private: true}
  1543. ]
  1544. 'use strict'
  1545.  
  1546. const {ipcMain} = require('electron')
  1547.  
  1548. // The history operation in renderer is redirected to browser.
  1549. ipcMain.on('ELECTRON_NAVIGATION_CONTROLLER', function (event, method, ...args) {
  1550. event.sender[method](...args)
  1551. })
  1552.  
  1553. ipcMain.on('ELECTRON_SYNC_NAVIGATION_CONTROLLER', function (event, method, ...args) {
  1554. event.returnValue = event.sender[method](...args)
  1555. })
  1556.  
  1557. // JavaScript implementation of Chromium's NavigationController.
  1558. // Instead of relying on Chromium for history control, we compeletely do history
  1559. // control on user land, and only rely on WebContents.loadURL for navigation.
  1560. // This helps us avoid Chromium's various optimizations so we can ensure renderer
  1561. // process is restarted everytime.
  1562. var NavigationController = (function () {
  1563. function NavigationController (webContents) {
  1564. this.webContents = webContents
  1565. this.clearHistory()
  1566.  
  1567. // webContents may have already navigated to a page.
  1568. if (this.webContents._getURL()) {
  1569. this.currentIndex++
  1570. this.history.push(this.webContents._getURL())
  1571. }
  1572. this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => {
  1573. var currentEntry
  1574. if (this.inPageIndex > -1 && !inPage) {
  1575. // Navigated to a new page, clear in-page mark.
  1576. this.inPageIndex = -1
  1577. } else if (this.inPageIndex === -1 && inPage && !replaceEntry) {
  1578. // Started in-page navigations.
  1579. this.inPageIndex = this.currentIndex
  1580. }
  1581. if (this.pendingIndex >= 0) {
  1582. // Go to index.
  1583. this.currentIndex = this.pendingIndex
  1584. this.pendingIndex = -1
  1585. this.history[this.currentIndex] = url
  1586. } else if (replaceEntry) {
  1587. // Non-user initialized navigation.
  1588. this.history[this.currentIndex] = url
  1589. } else {
  1590. // Normal navigation. Clear history.
  1591. this.history = this.history.slice(0, this.currentIndex + 1)
  1592. currentEntry = this.history[this.currentIndex]
  1593.  
  1594. if (currentEntry !== url) {
  1595. this.currentIndex++
  1596. this.history.push(url)
  1597. }
  1598. }
  1599. })
  1600. }
  1601.  
  1602. NavigationController.prototype.loadURL = function (url, options) {
  1603. if (options == null) {
  1604. options = {}
  1605. }
  1606. this.pendingIndex = -1
  1607. this.webContents._loadURL(url, options)
  1608. return this.webContents.emit('load-url', url, options)
  1609. }
  1610.  
  1611. NavigationController.prototype.getURL = function () {
  1612. if (this.currentIndex === -1) {
  1613. return ''
  1614. } else {
  1615. return this.history[this.currentIndex]
  1616. }
  1617. }
  1618.  
  1619. NavigationController.prototype.stop = function () {
  1620. this.pendingIndex = -1
  1621. return this.webContents._stop()
  1622. }
  1623.  
  1624. NavigationController.prototype.reload = function () {
  1625. this.pendingIndex = this.currentIndex
  1626. return this.webContents._loadURL(this.getURL(), {})
  1627. }
  1628.  
  1629. NavigationController.prototype.reloadIgnoringCache = function () {
  1630. this.pendingIndex = this.currentIndex
  1631. return this.webContents._loadURL(this.getURL(), {
  1632. extraHeaders: 'pragma: no-cache\n'
  1633. })
  1634. }
  1635.  
  1636. NavigationController.prototype.canGoBack = function () {
  1637. return this.getActiveIndex() > 0
  1638. }
  1639.  
  1640. NavigationController.prototype.canGoForward = function () {
  1641. return this.getActiveIndex() < this.history.length - 1
  1642. }
  1643.  
  1644. NavigationController.prototype.canGoToIndex = function (index) {
  1645. return index >= 0 && index < this.history.length
  1646. }
  1647.  
  1648. NavigationController.prototype.canGoToOffset = function (offset) {
  1649. return this.canGoToIndex(this.currentIndex + offset)
  1650. }
  1651.  
  1652. NavigationController.prototype.clearHistory = function () {
  1653. this.history = []
  1654. this.currentIndex = -1
  1655. this.pendingIndex = -1
  1656. this.inPageIndex = -1
  1657. }
  1658.  
  1659. NavigationController.prototype.goBack = function () {
  1660. if (!this.canGoBack()) {
  1661. return
  1662. }
  1663. this.pendingIndex = this.getActiveIndex() - 1
  1664. if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
  1665. return this.webContents._goBack()
  1666. } else {
  1667. return this.webContents._loadURL(this.history[this.pendingIndex], {})
  1668. }
  1669. }
  1670.  
  1671. NavigationController.prototype.goForward = function () {
  1672. if (!this.canGoForward()) {
  1673. return
  1674. }
  1675. this.pendingIndex = this.getActiveIndex() + 1
  1676. if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
  1677. return this.webContents._goForward()
  1678. } else {
  1679. return this.webContents._loadURL(this.history[this.pendingIndex], {})
  1680. }
  1681. }
  1682.  
  1683. NavigationController.prototype.goToIndex = function (index) {
  1684. if (!this.canGoToIndex(index)) {
  1685. return
  1686. }
  1687. this.pendingIndex = index
  1688. return this.webContents._loadURL(this.history[this.pendingIndex], {})
  1689. }
  1690.  
  1691. NavigationController.prototype.goToOffset = function (offset) {
  1692. var pendingIndex
  1693. if (!this.canGoToOffset(offset)) {
  1694. return
  1695. }
  1696. pendingIndex = this.currentIndex + offset
  1697. if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) {
  1698. this.pendingIndex = pendingIndex
  1699. return this.webContents._goToOffset(offset)
  1700. } else {
  1701. return this.goToIndex(pendingIndex)
  1702. }
  1703. }
  1704.  
  1705. NavigationController.prototype.getActiveIndex = function () {
  1706. if (this.pendingIndex === -1) {
  1707. return this.currentIndex
  1708. } else {
  1709. return this.pendingIndex
  1710. }
  1711. }
  1712.  
  1713. NavigationController.prototype.length = function () {
  1714. return this.history.length
  1715. }
  1716.  
  1717. return NavigationController
  1718. })()
  1719.  
  1720. module.exports = NavigationController
  1721. 'use strict'
  1722.  
  1723. const url = require('url')
  1724. const {EventEmitter} = require('events')
  1725. const util = require('util')
  1726. const {Readable} = require('stream')
  1727. const {app} = require('electron')
  1728. const {Session} = process.atomBinding('session')
  1729. const {net, Net} = process.atomBinding('net')
  1730. const {URLRequest} = net
  1731.  
  1732. Object.setPrototypeOf(Net.prototype, EventEmitter.prototype)
  1733. Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
  1734.  
  1735. const kSupportedProtocols = new Set(['http:', 'https:'])
  1736.  
  1737. class IncomingMessage extends Readable {
  1738. constructor (urlRequest) {
  1739. super()
  1740. this.urlRequest = urlRequest
  1741. this.shouldPush = false
  1742. this.data = []
  1743. this.urlRequest.on('data', (event, chunk) => {
  1744. this._storeInternalData(chunk)
  1745. this._pushInternalData()
  1746. })
  1747. this.urlRequest.on('end', () => {
  1748. this._storeInternalData(null)
  1749. this._pushInternalData()
  1750. })
  1751. }
  1752.  
  1753. get statusCode () {
  1754. return this.urlRequest.statusCode
  1755. }
  1756.  
  1757. get statusMessage () {
  1758. return this.urlRequest.statusMessage
  1759. }
  1760.  
  1761. get headers () {
  1762. return this.urlRequest.rawResponseHeaders
  1763. }
  1764.  
  1765. get httpVersion () {
  1766. return `${this.httpVersionMajor}.${this.httpVersionMinor}`
  1767. }
  1768.  
  1769. get httpVersionMajor () {
  1770. return this.urlRequest.httpVersionMajor
  1771. }
  1772.  
  1773. get httpVersionMinor () {
  1774. return this.urlRequest.httpVersionMinor
  1775. }
  1776.  
  1777. get rawTrailers () {
  1778. throw new Error('HTTP trailers are not supported.')
  1779. }
  1780.  
  1781. get trailers () {
  1782. throw new Error('HTTP trailers are not supported.')
  1783. }
  1784.  
  1785. _storeInternalData (chunk) {
  1786. this.data.push(chunk)
  1787. }
  1788.  
  1789. _pushInternalData () {
  1790. while (this.shouldPush && this.data.length > 0) {
  1791. const chunk = this.data.shift()
  1792. this.shouldPush = this.push(chunk)
  1793. }
  1794. }
  1795.  
  1796. _read () {
  1797. this.shouldPush = true
  1798. this._pushInternalData()
  1799. }
  1800.  
  1801. }
  1802.  
  1803. URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) {
  1804. if (isAsync) {
  1805. process.nextTick(() => {
  1806. this.clientRequest.emit(...rest)
  1807. })
  1808. } else {
  1809. this.clientRequest.emit(...rest)
  1810. }
  1811. }
  1812.  
  1813. URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) {
  1814. if (isAsync) {
  1815. process.nextTick(() => {
  1816. this._response.emit(...rest)
  1817. })
  1818. } else {
  1819. this._response.emit(...rest)
  1820. }
  1821. }
  1822.  
  1823. class ClientRequest extends EventEmitter {
  1824.  
  1825. constructor (options, callback) {
  1826. super()
  1827.  
  1828. if (!app.isReady()) {
  1829. throw new Error('net module can only be used after app is ready')
  1830. }
  1831.  
  1832. if (typeof options === 'string') {
  1833. options = url.parse(options)
  1834. } else {
  1835. options = util._extend({}, options)
  1836. }
  1837.  
  1838. const method = (options.method || 'GET').toUpperCase()
  1839. let urlStr = options.url
  1840.  
  1841. if (!urlStr) {
  1842. let urlObj = {}
  1843. const protocol = options.protocol || 'http:'
  1844. if (!kSupportedProtocols.has(protocol)) {
  1845. throw new Error('Protocol "' + protocol + '" not supported. ')
  1846. }
  1847. urlObj.protocol = protocol
  1848.  
  1849. if (options.host) {
  1850. urlObj.host = options.host
  1851. } else {
  1852. if (options.hostname) {
  1853. urlObj.hostname = options.hostname
  1854. } else {
  1855. urlObj.hostname = 'localhost'
  1856. }
  1857.  
  1858. if (options.port) {
  1859. urlObj.port = options.port
  1860. }
  1861. }
  1862.  
  1863. if (options.path && / /.test(options.path)) {
  1864. // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
  1865. // with an additional rule for ignoring percentage-escaped characters
  1866. // but that's a) hard to capture in a regular expression that performs
  1867. // well, and b) possibly too restrictive for real-world usage. That's
  1868. // why it only scans for spaces because those are guaranteed to create
  1869. // an invalid request.
  1870. throw new TypeError('Request path contains unescaped characters.')
  1871. }
  1872. let pathObj = url.parse(options.path || '/')
  1873. urlObj.pathname = pathObj.pathname
  1874. urlObj.search = pathObj.search
  1875. urlObj.hash = pathObj.hash
  1876. urlStr = url.format(urlObj)
  1877. }
  1878.  
  1879. const redirectPolicy = options.redirect || 'follow'
  1880. if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
  1881. throw new Error('redirect mode should be one of follow, error or manual')
  1882. }
  1883.  
  1884. let urlRequestOptions = {
  1885. method: method,
  1886. url: urlStr,
  1887. redirect: redirectPolicy
  1888. }
  1889. if (options.session) {
  1890. if (options.session instanceof Session) {
  1891. urlRequestOptions.session = options.session
  1892. } else {
  1893. throw new TypeError('`session` should be an instance of the Session class.')
  1894. }
  1895. } else if (options.partition) {
  1896. if (typeof options.partition === 'string') {
  1897. urlRequestOptions.partition = options.partition
  1898. } else {
  1899. throw new TypeError('`partition` should be an a string.')
  1900. }
  1901. }
  1902.  
  1903. let urlRequest = new URLRequest(urlRequestOptions)
  1904.  
  1905. // Set back and forward links.
  1906. this.urlRequest = urlRequest
  1907. urlRequest.clientRequest = this
  1908.  
  1909. // This is a copy of the extra headers structure held by the native
  1910. // net::URLRequest. The main reason is to keep the getHeader API synchronous
  1911. // after the request starts.
  1912. this.extraHeaders = {}
  1913.  
  1914. if (options.headers) {
  1915. for (let key in options.headers) {
  1916. this.setHeader(key, options.headers[key])
  1917. }
  1918. }
  1919.  
  1920. // Set when the request uses chunked encoding. Can be switched
  1921. // to true only once and never set back to false.
  1922. this.chunkedEncodingEnabled = false
  1923.  
  1924. urlRequest.on('response', () => {
  1925. const response = new IncomingMessage(urlRequest)
  1926. urlRequest._response = response
  1927. this.emit('response', response)
  1928. })
  1929.  
  1930. urlRequest.on('login', (event, authInfo, callback) => {
  1931. this.emit('login', authInfo, (username, password) => {
  1932. // If null or undefined username/password, force to empty string.
  1933. if (username === null || username === undefined) {
  1934. username = ''
  1935. }
  1936. if (typeof username !== 'string') {
  1937. throw new Error('username must be a string')
  1938. }
  1939. if (password === null || password === undefined) {
  1940. password = ''
  1941. }
  1942. if (typeof password !== 'string') {
  1943. throw new Error('password must be a string')
  1944. }
  1945. callback(username, password)
  1946. })
  1947. })
  1948.  
  1949. if (callback) {
  1950. this.once('response', callback)
  1951. }
  1952. }
  1953.  
  1954. get chunkedEncoding () {
  1955. return this.chunkedEncodingEnabled
  1956. }
  1957.  
  1958. set chunkedEncoding (value) {
  1959. if (!this.urlRequest.notStarted) {
  1960. throw new Error('Can\'t set the transfer encoding, headers have been sent.')
  1961. }
  1962. this.chunkedEncodingEnabled = value
  1963. }
  1964.  
  1965. setHeader (name, value) {
  1966. if (typeof name !== 'string') {
  1967. throw new TypeError('`name` should be a string in setHeader(name, value).')
  1968. }
  1969. if (value == null) {
  1970. throw new Error('`value` required in setHeader("' + name + '", value).')
  1971. }
  1972. if (!this.urlRequest.notStarted) {
  1973. throw new Error('Can\'t set headers after they are sent.')
  1974. }
  1975.  
  1976. const key = name.toLowerCase()
  1977. this.extraHeaders[key] = value
  1978. this.urlRequest.setExtraHeader(name, value.toString())
  1979. }
  1980.  
  1981. getHeader (name) {
  1982. if (name == null) {
  1983. throw new Error('`name` is required for getHeader(name).')
  1984. }
  1985.  
  1986. if (!this.extraHeaders) {
  1987. return
  1988. }
  1989.  
  1990. const key = name.toLowerCase()
  1991. return this.extraHeaders[key]
  1992. }
  1993.  
  1994. removeHeader (name) {
  1995. if (name == null) {
  1996. throw new Error('`name` is required for removeHeader(name).')
  1997. }
  1998.  
  1999. if (!this.urlRequest.notStarted) {
  2000. throw new Error('Can\'t remove headers after they are sent.')
  2001. }
  2002.  
  2003. const key = name.toLowerCase()
  2004. delete this.extraHeaders[key]
  2005. this.urlRequest.removeExtraHeader(name)
  2006. }
  2007.  
  2008. _write (chunk, encoding, callback, isLast) {
  2009. let chunkIsString = typeof chunk === 'string'
  2010. let chunkIsBuffer = chunk instanceof Buffer
  2011. if (!chunkIsString && !chunkIsBuffer) {
  2012. throw new TypeError('First argument must be a string or Buffer.')
  2013. }
  2014.  
  2015. if (chunkIsString) {
  2016. // We convert all strings into binary buffers.
  2017. chunk = Buffer.from(chunk, encoding)
  2018. }
  2019.  
  2020. // Since writing to the network is asynchronous, we conservatively
  2021. // assume that request headers are written after delivering the first
  2022. // buffer to the network IO thread.
  2023. if (this.urlRequest.notStarted) {
  2024. this.urlRequest.setChunkedUpload(this.chunkedEncoding)
  2025. }
  2026.  
  2027. // Headers are assumed to be sent on first call to _writeBuffer,
  2028. // i.e. after the first call to write or end.
  2029. let result = this.urlRequest.write(chunk, isLast)
  2030.  
  2031. // The write callback is fired asynchronously to mimic Node.js.
  2032. if (callback) {
  2033. process.nextTick(callback)
  2034. }
  2035.  
  2036. return result
  2037. }
  2038.  
  2039. write (data, encoding, callback) {
  2040. if (this.urlRequest.finished) {
  2041. let error = new Error('Write after end.')
  2042. process.nextTick(writeAfterEndNT, this, error, callback)
  2043. return true
  2044. }
  2045.  
  2046. return this._write(data, encoding, callback, false)
  2047. }
  2048.  
  2049. end (data, encoding, callback) {
  2050. if (this.urlRequest.finished) {
  2051. return false
  2052. }
  2053.  
  2054. if (typeof data === 'function') {
  2055. callback = data
  2056. encoding = null
  2057. data = null
  2058. } else if (typeof encoding === 'function') {
  2059. callback = encoding
  2060. encoding = null
  2061. }
  2062.  
  2063. data = data || ''
  2064.  
  2065. return this._write(data, encoding, callback, true)
  2066. }
  2067.  
  2068. followRedirect () {
  2069. this.urlRequest.followRedirect()
  2070. }
  2071.  
  2072. abort () {
  2073. this.urlRequest.cancel()
  2074. }
  2075.  
  2076. }
  2077.  
  2078. function writeAfterEndNT (self, error, callback) {
  2079. self.emit('error', error)
  2080. if (callback) callback(error)
  2081. }
  2082.  
  2083. Net.prototype.request = function (options, callback) {
  2084. return new ClientRequest(options, callback)
  2085. }
  2086.  
  2087. net.ClientRequest = ClientRequest
  2088.  
  2089. module.exports = net
  2090. const {EventEmitter} = require('events')
  2091. const {powerMonitor, PowerMonitor} = process.atomBinding('power_monitor')
  2092.  
  2093. Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
  2094.  
  2095. module.exports = powerMonitor
  2096. module.exports = process.atomBinding('power_save_blocker').powerSaveBlocker
  2097. const {app, session} = require('electron')
  2098.  
  2099. // Global protocol APIs.
  2100. module.exports = process.atomBinding('protocol')
  2101.  
  2102. // Fallback protocol APIs of default session.
  2103. Object.setPrototypeOf(module.exports, new Proxy({}, {
  2104. get (target, property) {
  2105. if (!app.isReady()) return
  2106.  
  2107. const protocol = session.defaultSession.protocol
  2108. if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return
  2109.  
  2110. // Returning a native function directly would throw error.
  2111. return (...args) => protocol[property](...args)
  2112. },
  2113.  
  2114. ownKeys () {
  2115. if (!app.isReady()) return []
  2116.  
  2117. return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.protocol))
  2118. },
  2119.  
  2120. getOwnPropertyDescriptor (target) {
  2121. return { configurable: true, enumerable: true }
  2122. }
  2123. }))
  2124. const {EventEmitter} = require('events')
  2125. const {screen, Screen} = process.atomBinding('screen')
  2126.  
  2127. Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype)
  2128.  
  2129. module.exports = screen
  2130. const {EventEmitter} = require('events')
  2131. const {app} = require('electron')
  2132. const {fromPartition, Session, Cookies} = process.atomBinding('session')
  2133.  
  2134. // Public API.
  2135. Object.defineProperties(exports, {
  2136. defaultSession: {
  2137. enumerable: true,
  2138. get () { return fromPartition('') }
  2139. },
  2140. fromPartition: {
  2141. enumerable: true,
  2142. value: fromPartition
  2143. }
  2144. })
  2145.  
  2146. Object.setPrototypeOf(Session.prototype, EventEmitter.prototype)
  2147. Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype)
  2148.  
  2149. Session.prototype._init = function () {
  2150. app.emit('session-created', this)
  2151. }
  2152.  
  2153. Session.prototype.setCertificateVerifyProc = function (verifyProc) {
  2154. if (verifyProc != null && verifyProc.length > 2) {
  2155. // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings
  2156. this._setCertificateVerifyProc(({hostname, certificate, verificationResult}, cb) => {
  2157. verifyProc(hostname, certificate, (result) => {
  2158. cb(result ? 0 : -2)
  2159. })
  2160. })
  2161. } else {
  2162. this._setCertificateVerifyProc(verifyProc)
  2163. }
  2164. }
  2165. const {EventEmitter} = require('events')
  2166. const {systemPreferences, SystemPreferences} = process.atomBinding('system_preferences')
  2167.  
  2168. Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
  2169.  
  2170. module.exports = systemPreferences
  2171. const {EventEmitter} = require('events')
  2172.  
  2173. let nextItemID = 1
  2174.  
  2175. class TouchBar extends EventEmitter {
  2176. // Bind a touch bar to a window
  2177. static _setOnWindow (touchBar, window) {
  2178. if (window._touchBar != null) {
  2179. window._touchBar._removeFromWindow(window)
  2180. }
  2181.  
  2182. if (touchBar == null) {
  2183. window._setTouchBarItems([])
  2184. return
  2185. }
  2186.  
  2187. if (Array.isArray(touchBar)) {
  2188. touchBar = new TouchBar(touchBar)
  2189. }
  2190. touchBar._addToWindow(window)
  2191. }
  2192.  
  2193. constructor (options) {
  2194. super()
  2195.  
  2196. if (options == null) {
  2197. throw new Error('Must specify options object as first argument')
  2198. }
  2199.  
  2200. let {items, escapeItem} = options
  2201.  
  2202. // FIXME Support array as first argument, remove in 2.0
  2203. if (Array.isArray(options)) {
  2204. items = options
  2205. escapeItem = null
  2206. }
  2207.  
  2208. if (!Array.isArray(items)) {
  2209. items = []
  2210. }
  2211.  
  2212. this.changeListener = (item) => {
  2213. this.emit('change', item.id, item.type)
  2214. }
  2215.  
  2216. this.windowListeners = {}
  2217. this.items = {}
  2218. this.ordereredItems = []
  2219. this.escapeItem = escapeItem
  2220.  
  2221. const registerItem = (item) => {
  2222. this.items[item.id] = item
  2223. item.on('change', this.changeListener)
  2224. if (item.child instanceof TouchBar) {
  2225. item.child.ordereredItems.forEach(registerItem)
  2226. }
  2227. }
  2228. items.forEach((item) => {
  2229. if (!(item instanceof TouchBarItem)) {
  2230. throw new Error('Each item must be an instance of TouchBarItem')
  2231. }
  2232. this.ordereredItems.push(item)
  2233. registerItem(item)
  2234. })
  2235. }
  2236.  
  2237. set escapeItem (item) {
  2238. if (item != null && !(item instanceof TouchBarItem)) {
  2239. throw new Error('Escape item must be an instance of TouchBarItem')
  2240. }
  2241. if (this.escapeItem != null) {
  2242. this.escapeItem.removeListener('change', this.changeListener)
  2243. }
  2244. this._escapeItem = item
  2245. if (this.escapeItem != null) {
  2246. this.escapeItem.on('change', this.changeListener)
  2247. }
  2248. this.emit('escape-item-change', item)
  2249. }
  2250.  
  2251. get escapeItem () {
  2252. return this._escapeItem
  2253. }
  2254.  
  2255. _addToWindow (window) {
  2256. const {id} = window
  2257.  
  2258. // Already added to window
  2259. if (this.windowListeners.hasOwnProperty(id)) return
  2260.  
  2261. window._touchBar = this
  2262.  
  2263. const changeListener = (itemID) => {
  2264. window._refreshTouchBarItem(itemID)
  2265. }
  2266. this.on('change', changeListener)
  2267.  
  2268. const escapeItemListener = (item) => {
  2269. window._setEscapeTouchBarItem(item != null ? item : {})
  2270. }
  2271. this.on('escape-item-change', escapeItemListener)
  2272.  
  2273. const interactionListener = (event, itemID, details) => {
  2274. let item = this.items[itemID]
  2275. if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
  2276. item = this.escapeItem
  2277. }
  2278. if (item != null && item.onInteraction != null) {
  2279. item.onInteraction(details)
  2280. }
  2281. }
  2282. window.on('-touch-bar-interaction', interactionListener)
  2283.  
  2284. const removeListeners = () => {
  2285. this.removeListener('change', changeListener)
  2286. this.removeListener('escape-item-change', escapeItemListener)
  2287. window.removeListener('-touch-bar-interaction', interactionListener)
  2288. window.removeListener('closed', removeListeners)
  2289. window._touchBar = null
  2290. delete this.windowListeners[id]
  2291. }
  2292. window.once('closed', removeListeners)
  2293. this.windowListeners[id] = removeListeners
  2294.  
  2295. window._setTouchBarItems(this.ordereredItems)
  2296. escapeItemListener(this.escapeItem)
  2297. }
  2298.  
  2299. _removeFromWindow (window) {
  2300. const removeListeners = this.windowListeners[window.id]
  2301. if (removeListeners != null) removeListeners()
  2302. }
  2303. }
  2304.  
  2305. class TouchBarItem extends EventEmitter {
  2306. constructor () {
  2307. super()
  2308. this.id = `${nextItemID++}`
  2309. }
  2310.  
  2311. _addLiveProperty (name, initialValue) {
  2312. const privateName = `_${name}`
  2313. this[privateName] = initialValue
  2314. Object.defineProperty(this, name, {
  2315. get: function () {
  2316. return this[privateName]
  2317. },
  2318. set: function (value) {
  2319. this[privateName] = value
  2320. this.emit('change', this)
  2321. },
  2322. enumerable: true
  2323. })
  2324. }
  2325. }
  2326.  
  2327. TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
  2328. constructor (config) {
  2329. super()
  2330. if (config == null) config = {}
  2331. this.type = 'button'
  2332. const {click, icon, iconPosition, label, backgroundColor} = config
  2333. this._addLiveProperty('label', label)
  2334. this._addLiveProperty('backgroundColor', backgroundColor)
  2335. this._addLiveProperty('icon', icon)
  2336. this._addLiveProperty('iconPosition', iconPosition)
  2337. if (typeof click === 'function') {
  2338. this.onInteraction = () => {
  2339. config.click()
  2340. }
  2341. }
  2342. }
  2343. }
  2344.  
  2345. TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
  2346. constructor (config) {
  2347. super()
  2348. if (config == null) config = {}
  2349. this.type = 'colorpicker'
  2350. const {availableColors, change, selectedColor} = config
  2351. this._addLiveProperty('availableColors', availableColors)
  2352. this._addLiveProperty('selectedColor', selectedColor)
  2353.  
  2354. if (typeof change === 'function') {
  2355. this.onInteraction = (details) => {
  2356. this._selectedColor = details.color
  2357. change(details.color)
  2358. }
  2359. }
  2360. }
  2361. }
  2362.  
  2363. TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
  2364. constructor (config) {
  2365. super()
  2366. if (config == null) config = {}
  2367. this.type = 'group'
  2368. this.child = config.items
  2369. if (!(this.child instanceof TouchBar)) {
  2370. this.child = new TouchBar(this.child)
  2371. }
  2372. }
  2373. }
  2374.  
  2375. TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
  2376. constructor (config) {
  2377. super()
  2378. if (config == null) config = {}
  2379. this.type = 'label'
  2380. this._addLiveProperty('label', config.label)
  2381. this._addLiveProperty('textColor', config.textColor)
  2382. }
  2383. }
  2384.  
  2385. TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
  2386. constructor (config) {
  2387. super()
  2388. if (config == null) config = {}
  2389. this.type = 'popover'
  2390. this._addLiveProperty('label', config.label)
  2391. this._addLiveProperty('icon', config.icon)
  2392. this.showCloseButton = config.showCloseButton
  2393. this.child = config.items
  2394. if (!(this.child instanceof TouchBar)) {
  2395. this.child = new TouchBar(this.child)
  2396. }
  2397. this.child.ordereredItems.forEach((item) => {
  2398. item._popover = item._popover || []
  2399. if (!item._popover.includes(this.id)) item._popover.push(this.id)
  2400. })
  2401. }
  2402. }
  2403.  
  2404. TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
  2405. constructor (config) {
  2406. super()
  2407. if (config == null) config = {}
  2408. this.type = 'slider'
  2409. const {change, label, minValue, maxValue, value} = config
  2410. this._addLiveProperty('label', label)
  2411. this._addLiveProperty('minValue', minValue)
  2412. this._addLiveProperty('maxValue', maxValue)
  2413. this._addLiveProperty('value', value)
  2414.  
  2415. if (typeof change === 'function') {
  2416. this.onInteraction = (details) => {
  2417. this._value = details.value
  2418. change(details.value)
  2419. }
  2420. }
  2421. }
  2422. }
  2423.  
  2424. TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
  2425. constructor (config) {
  2426. super()
  2427. if (config == null) config = {}
  2428. this.type = 'spacer'
  2429. this.size = config.size
  2430. }
  2431. }
  2432.  
  2433. TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
  2434. constructor (config) {
  2435. super()
  2436. if (config == null) config = {}
  2437. const {segmentStyle, segments, selectedIndex, change, mode} = config
  2438. this.type = 'segmented_control'
  2439. this._addLiveProperty('segmentStyle', segmentStyle)
  2440. this._addLiveProperty('segments', segments || [])
  2441. this._addLiveProperty('selectedIndex', selectedIndex)
  2442. this._addLiveProperty('mode', mode)
  2443.  
  2444. if (typeof change === 'function') {
  2445. this.onInteraction = (details) => {
  2446. this._selectedIndex = details.selectedIndex
  2447. change(details.selectedIndex, details.isSelected)
  2448. }
  2449. }
  2450. }
  2451. }
  2452.  
  2453. TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
  2454. constructor (config) {
  2455. super()
  2456. if (config == null) config = {}
  2457. const {items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode} = config
  2458. let {select, highlight} = config
  2459. this.type = 'scrubber'
  2460. this._addLiveProperty('items', items)
  2461. this._addLiveProperty('selectedStyle', selectedStyle || null)
  2462. this._addLiveProperty('overlayStyle', overlayStyle || null)
  2463. this._addLiveProperty('showArrowButtons', showArrowButtons || false)
  2464. this._addLiveProperty('mode', mode || 'free')
  2465. this._addLiveProperty('continuous', continuous || true)
  2466.  
  2467. if (typeof select === 'function' || typeof highlight === 'function') {
  2468. if (select == null) select = () => {}
  2469. if (highlight == null) highlight = () => {}
  2470. this.onInteraction = (details) => {
  2471. if (details.type === 'select') {
  2472. select(details.selectedIndex)
  2473. } else if (details.type === 'highlight') {
  2474. highlight(details.highlightedIndex)
  2475. }
  2476. }
  2477. }
  2478. }
  2479. }
  2480.  
  2481. module.exports = TouchBar
  2482. const {EventEmitter} = require('events')
  2483. const {Tray} = process.atomBinding('tray')
  2484.  
  2485. Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype)
  2486.  
  2487. module.exports = Tray
  2488. 'use strict'
  2489.  
  2490. const {EventEmitter} = require('events')
  2491. const electron = require('electron')
  2492. const {app, ipcMain, session, NavigationController} = electron
  2493.  
  2494. // session is not used here, the purpose is to make sure session is initalized
  2495. // before the webContents module.
  2496. session
  2497.  
  2498. let nextId = 0
  2499. const getNextId = function () {
  2500. return ++nextId
  2501. }
  2502.  
  2503. // Stock page sizes
  2504. const PDFPageSizes = {
  2505. A5: {
  2506. custom_display_name: 'A5',
  2507. height_microns: 210000,
  2508. name: 'ISO_A5',
  2509. width_microns: 148000
  2510. },
  2511. A4: {
  2512. custom_display_name: 'A4',
  2513. height_microns: 297000,
  2514. name: 'ISO_A4',
  2515. is_default: 'true',
  2516. width_microns: 210000
  2517. },
  2518. A3: {
  2519. custom_display_name: 'A3',
  2520. height_microns: 420000,
  2521. name: 'ISO_A3',
  2522. width_microns: 297000
  2523. },
  2524. Legal: {
  2525. custom_display_name: 'Legal',
  2526. height_microns: 355600,
  2527. name: 'NA_LEGAL',
  2528. width_microns: 215900
  2529. },
  2530. Letter: {
  2531. custom_display_name: 'Letter',
  2532. height_microns: 279400,
  2533. name: 'NA_LETTER',
  2534. width_microns: 215900
  2535. },
  2536. Tabloid: {
  2537. height_microns: 431800,
  2538. name: 'NA_LEDGER',
  2539. width_microns: 279400,
  2540. custom_display_name: 'Tabloid'
  2541. }
  2542. }
  2543.  
  2544. // Default printing setting
  2545. const defaultPrintingSetting = {
  2546. pageRage: [],
  2547. mediaSize: {},
  2548. landscape: false,
  2549. color: 2,
  2550. headerFooterEnabled: false,
  2551. marginsType: 0,
  2552. isFirstRequest: false,
  2553. requestID: getNextId(),
  2554. previewModifiable: true,
  2555. printToPDF: true,
  2556. printWithCloudPrint: false,
  2557. printWithPrivet: false,
  2558. printWithExtension: false,
  2559. deviceName: 'Save as PDF',
  2560. generateDraftData: true,
  2561. fitToPageEnabled: false,
  2562. scaleFactor: 1,
  2563. rasterizePDF: false,
  2564. duplex: 0,
  2565. copies: 1,
  2566. collate: true,
  2567. shouldPrintBackgrounds: false,
  2568. shouldPrintSelectionOnly: false
  2569. }
  2570.  
  2571. // JavaScript implementations of WebContents.
  2572. const binding = process.atomBinding('web_contents')
  2573. const {WebContents} = binding
  2574.  
  2575. Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype)
  2576. Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype)
  2577.  
  2578. // WebContents::send(channel, args..)
  2579. // WebContents::sendToAll(channel, args..)
  2580. WebContents.prototype.send = function (channel, ...args) {
  2581. if (channel == null) throw new Error('Missing required channel argument')
  2582. return this._send(false, channel, args)
  2583. }
  2584. WebContents.prototype.sendToAll = function (channel, ...args) {
  2585. if (channel == null) throw new Error('Missing required channel argument')
  2586. return this._send(true, channel, args)
  2587. }
  2588.  
  2589. // Following methods are mapped to webFrame.
  2590. const webFrameMethods = [
  2591. 'insertCSS',
  2592. 'insertText',
  2593. 'setLayoutZoomLevelLimits',
  2594. 'setVisualZoomLevelLimits',
  2595. // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings
  2596. 'setZoomLevelLimits'
  2597. ]
  2598. const webFrameMethodsWithResult = []
  2599.  
  2600. const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
  2601. return new Promise((resolve, reject) => {
  2602. this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args)
  2603. ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) {
  2604. if (error == null) {
  2605. if (typeof callback === 'function') callback(result)
  2606. resolve(result)
  2607. } else {
  2608. reject(error)
  2609. }
  2610. })
  2611. })
  2612. }
  2613.  
  2614. const syncWebFrameMethods = function (requestId, method, callback, ...args) {
  2615. this.send('ELECTRON_INTERNAL_RENDERER_SYNC_WEB_FRAME_METHOD', requestId, method, args)
  2616. ipcMain.once(`ELECTRON_INTERNAL_BROWSER_SYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, result) {
  2617. if (callback) callback(result)
  2618. })
  2619. }
  2620.  
  2621. for (const method of webFrameMethods) {
  2622. WebContents.prototype[method] = function (...args) {
  2623. this.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args)
  2624. }
  2625. }
  2626.  
  2627. for (const method of webFrameMethodsWithResult) {
  2628. WebContents.prototype[method] = function (...args) {
  2629. const callback = args[args.length - 1]
  2630. const actualArgs = args.slice(0, args.length - 2)
  2631. syncWebFrameMethods.call(this, getNextId(), method, callback, ...actualArgs)
  2632. }
  2633. }
  2634.  
  2635. // Make sure WebContents::executeJavaScript would run the code only when the
  2636. // WebContents has been loaded.
  2637. WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) {
  2638. const requestId = getNextId()
  2639.  
  2640. if (typeof hasUserGesture === 'function') {
  2641. // Shift.
  2642. callback = hasUserGesture
  2643. hasUserGesture = null
  2644. }
  2645.  
  2646. if (hasUserGesture == null) {
  2647. hasUserGesture = false
  2648. }
  2649.  
  2650. if (this.getURL() && !this.isLoadingMainFrame()) {
  2651. return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture)
  2652. } else {
  2653. return new Promise((resolve, reject) => {
  2654. this.once('did-finish-load', () => {
  2655. asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject)
  2656. })
  2657. })
  2658. }
  2659. }
  2660.  
  2661. // Translate the options of printToPDF.
  2662. WebContents.prototype.printToPDF = function (options, callback) {
  2663. const printingSetting = Object.assign({}, defaultPrintingSetting)
  2664. if (options.landscape) {
  2665. printingSetting.landscape = options.landscape
  2666. }
  2667. if (options.marginsType) {
  2668. printingSetting.marginsType = options.marginsType
  2669. }
  2670. if (options.printSelectionOnly) {
  2671. printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
  2672. }
  2673. if (options.printBackground) {
  2674. printingSetting.shouldPrintBackgrounds = options.printBackground
  2675. }
  2676.  
  2677. if (options.pageSize) {
  2678. const pageSize = options.pageSize
  2679. if (typeof pageSize === 'object') {
  2680. if (!pageSize.height || !pageSize.width) {
  2681. return callback(new Error('Must define height and width for pageSize'))
  2682. }
  2683. // Dimensions in Microns
  2684. // 1 meter = 10^6 microns
  2685. printingSetting.mediaSize = {
  2686. name: 'CUSTOM',
  2687. custom_display_name: 'Custom',
  2688. height_microns: pageSize.height,
  2689. width_microns: pageSize.width
  2690. }
  2691. } else if (PDFPageSizes[pageSize]) {
  2692. printingSetting.mediaSize = PDFPageSizes[pageSize]
  2693. } else {
  2694. return callback(new Error(`Does not support pageSize with ${pageSize}`))
  2695. }
  2696. } else {
  2697. printingSetting.mediaSize = PDFPageSizes['A4']
  2698. }
  2699.  
  2700. this._printToPDF(printingSetting, callback)
  2701. }
  2702.  
  2703. WebContents.prototype.getZoomLevel = function (callback) {
  2704. if (typeof callback !== 'function') {
  2705. throw new Error('Must pass function as an argument')
  2706. }
  2707. process.nextTick(() => {
  2708. const zoomLevel = this._getZoomLevel()
  2709. callback(zoomLevel)
  2710. })
  2711. }
  2712.  
  2713. WebContents.prototype.getZoomFactor = function (callback) {
  2714. if (typeof callback !== 'function') {
  2715. throw new Error('Must pass function as an argument')
  2716. }
  2717. process.nextTick(() => {
  2718. const zoomFactor = this._getZoomFactor()
  2719. callback(zoomFactor)
  2720. })
  2721. }
  2722.  
  2723. // Add JavaScript wrappers for WebContents class.
  2724. WebContents.prototype._init = function () {
  2725. // The navigation controller.
  2726. NavigationController.call(this, this)
  2727.  
  2728. // Every remote callback from renderer process would add a listenter to the
  2729. // render-view-deleted event, so ignore the listenters warning.
  2730. this.setMaxListeners(0)
  2731.  
  2732. // Dispatch IPC messages to the ipc module.
  2733. this.on('ipc-message', function (event, [channel, ...args]) {
  2734. ipcMain.emit(channel, event, ...args)
  2735. })
  2736. this.on('ipc-message-sync', function (event, [channel, ...args]) {
  2737. Object.defineProperty(event, 'returnValue', {
  2738. set: function (value) {
  2739. return event.sendReply(JSON.stringify(value))
  2740. },
  2741. get: function () {}
  2742. })
  2743. ipcMain.emit(channel, event, ...args)
  2744. })
  2745.  
  2746. // Handle context menu action request from pepper plugin.
  2747. this.on('pepper-context-menu', function (event, params) {
  2748. // Access Menu via electron.Menu to prevent circular require
  2749. const menu = electron.Menu.buildFromTemplate(params.menu)
  2750. menu.popup(event.sender.getOwnerBrowserWindow(), params.x, params.y)
  2751. })
  2752.  
  2753. // The devtools requests the webContents to reload.
  2754. this.on('devtools-reload-page', function () {
  2755. this.reload()
  2756. })
  2757.  
  2758. app.emit('web-contents-created', {}, this)
  2759. }
  2760.  
  2761. // JavaScript wrapper of Debugger.
  2762. const {Debugger} = process.atomBinding('debugger')
  2763.  
  2764. Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
  2765.  
  2766. // Public APIs.
  2767. module.exports = {
  2768. create (options = {}) {
  2769. return binding.create(options)
  2770. },
  2771.  
  2772. fromId (id) {
  2773. return binding.fromId(id)
  2774. },
  2775.  
  2776. getFocusedWebContents () {
  2777. let focused = null
  2778. for (let contents of binding.getAllWebContents()) {
  2779. if (!contents.isFocused()) continue
  2780. if (focused == null) focused = contents
  2781. // Return webview web contents which may be embedded inside another
  2782. // web contents that is also reporting as focused
  2783. if (contents.getType() === 'webview') return contents
  2784. }
  2785. return focused
  2786. },
  2787.  
  2788. getAllWebContents () {
  2789. return binding.getAllWebContents()
  2790. }
  2791. }
  2792. const {app, ipcMain, webContents, BrowserWindow} = require('electron')
  2793. const {getAllWebContents} = process.atomBinding('web_contents')
  2794. const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents()
  2795.  
  2796. const {Buffer} = require('buffer')
  2797. const fs = require('fs')
  2798. const path = require('path')
  2799. const url = require('url')
  2800.  
  2801. // TODO(zcbenz): Remove this when we have Object.values().
  2802. const objectValues = function (object) {
  2803. return Object.keys(object).map(function (key) { return object[key] })
  2804. }
  2805.  
  2806. // Mapping between extensionId(hostname) and manifest.
  2807. const manifestMap = {} // extensionId => manifest
  2808. const manifestNameMap = {} // name => manifest
  2809.  
  2810. const generateExtensionIdFromName = function (name) {
  2811. return name.replace(/[\W_]+/g, '-').toLowerCase()
  2812. }
  2813.  
  2814. const isWindowOrWebView = function (webContents) {
  2815. const type = webContents.getType()
  2816. return type === 'window' || type === 'webview'
  2817. }
  2818.  
  2819. // Create or get manifest object from |srcDirectory|.
  2820. const getManifestFromPath = function (srcDirectory) {
  2821. let manifest
  2822. let manifestContent
  2823.  
  2824. try {
  2825. manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
  2826. } catch (readError) {
  2827. console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
  2828. console.warn(readError.stack || readError)
  2829. throw readError
  2830. }
  2831.  
  2832. try {
  2833. manifest = JSON.parse(manifestContent)
  2834. } catch (parseError) {
  2835. console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
  2836. console.warn(parseError.stack || parseError)
  2837. throw parseError
  2838. }
  2839.  
  2840. if (!manifestNameMap[manifest.name]) {
  2841. const extensionId = generateExtensionIdFromName(manifest.name)
  2842. manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
  2843. Object.assign(manifest, {
  2844. srcDirectory: srcDirectory,
  2845. extensionId: extensionId,
  2846. // We can not use 'file://' directly because all resources in the extension
  2847. // will be treated as relative to the root in Chrome.
  2848. startPage: url.format({
  2849. protocol: 'chrome-extension',
  2850. slashes: true,
  2851. hostname: extensionId,
  2852. pathname: manifest.devtools_page
  2853. })
  2854. })
  2855. return manifest
  2856. } else if (manifest && manifest.name) {
  2857. console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
  2858. }
  2859. }
  2860.  
  2861. // Manage the background pages.
  2862. const backgroundPages = {}
  2863.  
  2864. const startBackgroundPages = function (manifest) {
  2865. if (backgroundPages[manifest.extensionId] || !manifest.background) return
  2866.  
  2867. let html
  2868. let name
  2869. if (manifest.background.page) {
  2870. name = manifest.background.page
  2871. html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page))
  2872. } else {
  2873. name = '_generated_background_page.html'
  2874. const scripts = manifest.background.scripts.map((name) => {
  2875. return `<script src="${name}"></script>`
  2876. }).join('')
  2877. html = new Buffer(`<html><body>${scripts}</body></html>`)
  2878. }
  2879.  
  2880. const contents = webContents.create({
  2881. partition: 'persist:__chrome_extension',
  2882. isBackgroundPage: true,
  2883. commandLineSwitches: ['--background-page']
  2884. })
  2885. backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }
  2886. contents.loadURL(url.format({
  2887. protocol: 'chrome-extension',
  2888. slashes: true,
  2889. hostname: manifest.extensionId,
  2890. pathname: name
  2891. }))
  2892. }
  2893.  
  2894. const removeBackgroundPages = function (manifest) {
  2895. if (!backgroundPages[manifest.extensionId]) return
  2896.  
  2897. backgroundPages[manifest.extensionId].webContents.destroy()
  2898. delete backgroundPages[manifest.extensionId]
  2899. }
  2900.  
  2901. const sendToBackgroundPages = function (...args) {
  2902. for (const page of objectValues(backgroundPages)) {
  2903. page.webContents.sendToAll(...args)
  2904. }
  2905. }
  2906.  
  2907. // Dispatch web contents events to Chrome APIs
  2908. const hookWebContentsEvents = function (webContents) {
  2909. const tabId = webContents.id
  2910.  
  2911. sendToBackgroundPages('CHROME_TABS_ONCREATED')
  2912.  
  2913. webContents.on('will-navigate', (event, url) => {
  2914. sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
  2915. frameId: 0,
  2916. parentFrameId: -1,
  2917. processId: webContents.getProcessId(),
  2918. tabId: tabId,
  2919. timeStamp: Date.now(),
  2920. url: url
  2921. })
  2922. })
  2923.  
  2924. webContents.on('did-navigate', (event, url) => {
  2925. sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
  2926. frameId: 0,
  2927. parentFrameId: -1,
  2928. processId: webContents.getProcessId(),
  2929. tabId: tabId,
  2930. timeStamp: Date.now(),
  2931. url: url
  2932. })
  2933. })
  2934.  
  2935. webContents.once('destroyed', () => {
  2936. sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
  2937. })
  2938. }
  2939.  
  2940. // Handle the chrome.* API messages.
  2941. let nextId = 0
  2942.  
  2943. ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
  2944. const page = backgroundPages[extensionId]
  2945. if (!page) {
  2946. console.error(`Connect to unknown extension ${extensionId}`)
  2947. return
  2948. }
  2949.  
  2950. const portId = ++nextId
  2951. event.returnValue = {tabId: page.webContents.id, portId: portId}
  2952.  
  2953. event.sender.once('render-view-deleted', () => {
  2954. if (page.webContents.isDestroyed()) return
  2955. page.webContents.sendToAll(`CHROME_PORT_DISCONNECT_${portId}`)
  2956. })
  2957. page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
  2958. })
  2959.  
  2960. ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) {
  2961. event.returnValue = manifestMap[extensionId]
  2962. })
  2963.  
  2964. let resultID = 1
  2965. ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message, originResultID) {
  2966. const page = backgroundPages[extensionId]
  2967. if (!page) {
  2968. console.error(`Connect to unknown extension ${extensionId}`)
  2969. return
  2970. }
  2971.  
  2972. page.webContents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message, resultID)
  2973. ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
  2974. event.sender.send(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
  2975. })
  2976. resultID++
  2977. })
  2978.  
  2979. ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message, originResultID) {
  2980. const contents = webContents.fromId(tabId)
  2981. if (!contents) {
  2982. console.error(`Sending message to unknown tab ${tabId}`)
  2983. return
  2984. }
  2985.  
  2986. const senderTabId = isBackgroundPage ? null : event.sender.id
  2987.  
  2988. contents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message, resultID)
  2989. ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
  2990. event.sender.send(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
  2991. })
  2992. resultID++
  2993. })
  2994.  
  2995. ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) {
  2996. const contents = webContents.fromId(tabId)
  2997. if (!contents) {
  2998. console.error(`Sending message to unknown tab ${tabId}`)
  2999. return
  3000. }
  3001.  
  3002. let code, url
  3003. if (details.file) {
  3004. const manifest = manifestMap[extensionId]
  3005. code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
  3006. url = `chrome-extension://${extensionId}${details.file}`
  3007. } else {
  3008. code = details.code
  3009. url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
  3010. }
  3011.  
  3012. contents.send('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code)
  3013. })
  3014.  
  3015. // Transfer the content scripts to renderer.
  3016. const contentScripts = {}
  3017.  
  3018. const injectContentScripts = function (manifest) {
  3019. if (contentScripts[manifest.name] || !manifest.content_scripts) return
  3020.  
  3021. const readArrayOfFiles = function (relativePath) {
  3022. return {
  3023. url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
  3024. code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
  3025. }
  3026. }
  3027.  
  3028. const contentScriptToEntry = function (script) {
  3029. return {
  3030. matches: script.matches,
  3031. js: script.js.map(readArrayOfFiles),
  3032. runAt: script.run_at || 'document_idle'
  3033. }
  3034. }
  3035.  
  3036. try {
  3037. const entry = {
  3038. extensionId: manifest.extensionId,
  3039. contentScripts: manifest.content_scripts.map(contentScriptToEntry)
  3040. }
  3041. contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
  3042. } catch (e) {
  3043. console.error('Failed to read content scripts', e)
  3044. }
  3045. }
  3046.  
  3047. const removeContentScripts = function (manifest) {
  3048. if (!contentScripts[manifest.name]) return
  3049.  
  3050. renderProcessPreferences.removeEntry(contentScripts[manifest.name])
  3051. delete contentScripts[manifest.name]
  3052. }
  3053.  
  3054. // Transfer the |manifest| to a format that can be recognized by the
  3055. // |DevToolsAPI.addExtensions|.
  3056. const manifestToExtensionInfo = function (manifest) {
  3057. return {
  3058. startPage: manifest.startPage,
  3059. srcDirectory: manifest.srcDirectory,
  3060. name: manifest.name,
  3061. exposeExperimentalAPIs: true
  3062. }
  3063. }
  3064.  
  3065. // Load the extensions for the window.
  3066. const loadExtension = function (manifest) {
  3067. startBackgroundPages(manifest)
  3068. injectContentScripts(manifest)
  3069. }
  3070.  
  3071. const loadDevToolsExtensions = function (win, manifests) {
  3072. if (!win.devToolsWebContents) return
  3073.  
  3074. manifests.forEach(loadExtension)
  3075.  
  3076. const extensionInfoArray = manifests.map(manifestToExtensionInfo)
  3077. win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
  3078. }
  3079.  
  3080. app.on('web-contents-created', function (event, webContents) {
  3081. if (!isWindowOrWebView(webContents)) return
  3082.  
  3083. hookWebContentsEvents(webContents)
  3084. webContents.on('devtools-opened', function () {
  3085. loadDevToolsExtensions(webContents, objectValues(manifestMap))
  3086. })
  3087. })
  3088.  
  3089. // The chrome-extension: can map a extension URL request to real file path.
  3090. const chromeExtensionHandler = function (request, callback) {
  3091. const parsed = url.parse(request.url)
  3092. if (!parsed.hostname || !parsed.path) return callback()
  3093.  
  3094. const manifest = manifestMap[parsed.hostname]
  3095. if (!manifest) return callback()
  3096.  
  3097. const page = backgroundPages[parsed.hostname]
  3098. if (page && parsed.path === `/${page.name}`) {
  3099. return callback({
  3100. mimeType: 'text/html',
  3101. data: page.html
  3102. })
  3103. }
  3104.  
  3105. fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
  3106. if (err) {
  3107. return callback(-6) // FILE_NOT_FOUND
  3108. } else {
  3109. return callback(content)
  3110. }
  3111. })
  3112. }
  3113.  
  3114. app.on('session-created', function (ses) {
  3115. ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
  3116. if (error) {
  3117. console.error(`Unable to register chrome-extension protocol: ${error}`)
  3118. }
  3119. })
  3120. })
  3121.  
  3122. // The persistent path of "DevTools Extensions" preference file.
  3123. let loadedExtensionsPath = null
  3124.  
  3125. app.on('will-quit', function () {
  3126. try {
  3127. const loadedExtensions = objectValues(manifestMap).map(function (manifest) {
  3128. return manifest.srcDirectory
  3129. })
  3130. if (loadedExtensions.length > 0) {
  3131. try {
  3132. fs.mkdirSync(path.dirname(loadedExtensionsPath))
  3133. } catch (error) {
  3134. // Ignore error
  3135. }
  3136. fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions))
  3137. } else {
  3138. fs.unlinkSync(loadedExtensionsPath)
  3139. }
  3140. } catch (error) {
  3141. // Ignore error
  3142. }
  3143. })
  3144.  
  3145. // We can not use protocol or BrowserWindow until app is ready.
  3146. app.once('ready', function () {
  3147. // Load persisted extensions.
  3148. loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
  3149. try {
  3150. const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
  3151. if (Array.isArray(loadedExtensions)) {
  3152. for (const srcDirectory of loadedExtensions) {
  3153. // Start background pages and set content scripts.
  3154. const manifest = getManifestFromPath(srcDirectory)
  3155. loadExtension(manifest)
  3156. }
  3157. }
  3158. } catch (error) {
  3159. // Ignore error
  3160. }
  3161.  
  3162. // The public API to add/remove extensions.
  3163. BrowserWindow.addDevToolsExtension = function (srcDirectory) {
  3164. const manifest = getManifestFromPath(srcDirectory)
  3165. if (manifest) {
  3166. loadExtension(manifest)
  3167. for (const webContents of getAllWebContents()) {
  3168. if (isWindowOrWebView(webContents)) {
  3169. loadDevToolsExtensions(webContents, [manifest])
  3170. }
  3171. }
  3172. return manifest.name
  3173. }
  3174. }
  3175.  
  3176. BrowserWindow.removeDevToolsExtension = function (name) {
  3177. const manifest = manifestNameMap[name]
  3178. if (!manifest) return
  3179.  
  3180. removeBackgroundPages(manifest)
  3181. removeContentScripts(manifest)
  3182. delete manifestMap[manifest.extensionId]
  3183. delete manifestNameMap[name]
  3184. }
  3185.  
  3186. BrowserWindow.getDevToolsExtensions = function () {
  3187. const extensions = {}
  3188. Object.keys(manifestNameMap).forEach(function (name) {
  3189. const manifest = manifestNameMap[name]
  3190. extensions[name] = {name: manifest.name, version: manifest.version}
  3191. })
  3192. return extensions
  3193. }
  3194. })
  3195. 'use strict'
  3196.  
  3197. const ipcMain = require('electron').ipcMain
  3198. const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer
  3199.  
  3200. var deepEqual = function (opt1, opt2) {
  3201. return JSON.stringify(opt1) === JSON.stringify(opt2)
  3202. }
  3203.  
  3204. // A queue for holding all requests from renderer process.
  3205. var requestsQueue = []
  3206.  
  3207. ipcMain.on('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, captureWindow, captureScreen, thumbnailSize, id) {
  3208. var request
  3209. request = {
  3210. id: id,
  3211. options: {
  3212. captureWindow: captureWindow,
  3213. captureScreen: captureScreen,
  3214. thumbnailSize: thumbnailSize
  3215. },
  3216. webContents: event.sender
  3217. }
  3218. requestsQueue.push(request)
  3219. if (requestsQueue.length === 1) {
  3220. desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
  3221. }
  3222.  
  3223. // If the WebContents is destroyed before receiving result, just remove the
  3224. // reference from requestsQueue to make the module not send the result to it.
  3225. event.sender.once('destroyed', function () {
  3226. request.webContents = null
  3227. })
  3228. })
  3229.  
  3230. desktopCapturer.emit = function (event, name, sources) {
  3231. // Receiving sources result from main process, now send them back to renderer.
  3232. var handledRequest, i, len, ref, ref1, request, result, source, unhandledRequestsQueue
  3233. handledRequest = requestsQueue.shift(0)
  3234. result = (function () {
  3235. var i, len, results
  3236. results = []
  3237. for (i = 0, len = sources.length; i < len; i++) {
  3238. source = sources[i]
  3239. results.push({
  3240. id: source.id,
  3241. name: source.name,
  3242. thumbnail: source.thumbnail.toDataURL()
  3243. })
  3244. }
  3245. return results
  3246. })()
  3247. if ((ref = handledRequest.webContents) != null) {
  3248. ref.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + handledRequest.id, result)
  3249. }
  3250.  
  3251. // Check the queue to see whether there is other same request. If has, handle
  3252. // it for reducing redunplicated `desktopCaptuer.startHandling` calls.
  3253. unhandledRequestsQueue = []
  3254. for (i = 0, len = requestsQueue.length; i < len; i++) {
  3255. request = requestsQueue[i]
  3256. if (deepEqual(handledRequest.options, request.options)) {
  3257. if ((ref1 = request.webContents) != null) {
  3258. ref1.send('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + request.id, result)
  3259. }
  3260. } else {
  3261. unhandledRequestsQueue.push(request)
  3262. }
  3263. }
  3264. requestsQueue = unhandledRequestsQueue
  3265.  
  3266. // If the requestsQueue is not empty, start a new request handling.
  3267. if (requestsQueue.length > 0) {
  3268. const {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options
  3269. return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
  3270. }
  3271. }
  3272. 'use strict'
  3273.  
  3274. const {ipcMain, webContents} = require('electron')
  3275. const parseFeaturesString = require('../common/parse-features-string')
  3276.  
  3277. // Doesn't exist in early initialization.
  3278. let webViewManager = null
  3279.  
  3280. const supportedWebViewEvents = [
  3281. 'load-commit',
  3282. 'did-attach',
  3283. 'did-finish-load',
  3284. 'did-fail-load',
  3285. 'did-frame-finish-load',
  3286. 'did-start-loading',
  3287. 'did-stop-loading',
  3288. 'did-get-response-details',
  3289. 'did-get-redirect-request',
  3290. 'dom-ready',
  3291. 'console-message',
  3292. 'context-menu',
  3293. 'devtools-opened',
  3294. 'devtools-closed',
  3295. 'devtools-focused',
  3296. 'new-window',
  3297. 'will-navigate',
  3298. 'did-navigate',
  3299. 'did-navigate-in-page',
  3300. 'close',
  3301. 'crashed',
  3302. 'gpu-crashed',
  3303. 'plugin-crashed',
  3304. 'destroyed',
  3305. 'page-title-updated',
  3306. 'page-favicon-updated',
  3307. 'enter-html-full-screen',
  3308. 'leave-html-full-screen',
  3309. 'media-started-playing',
  3310. 'media-paused',
  3311. 'found-in-page',
  3312. 'did-change-theme-color',
  3313. 'update-target-url'
  3314. ]
  3315.  
  3316. let nextGuestInstanceId = 0
  3317. const guestInstances = {}
  3318. const embedderElementsMap = {}
  3319.  
  3320. // Moves the last element of array to the first one.
  3321. const moveLastToFirst = function (list) {
  3322. list.unshift(list.pop())
  3323. }
  3324.  
  3325. // Generate guestInstanceId.
  3326. const getNextGuestInstanceId = function () {
  3327. return ++nextGuestInstanceId
  3328. }
  3329.  
  3330. // Create a new guest instance.
  3331. const createGuest = function (embedder, params) {
  3332. if (webViewManager == null) {
  3333. webViewManager = process.atomBinding('web_view_manager')
  3334. }
  3335.  
  3336. const guestInstanceId = getNextGuestInstanceId(embedder)
  3337. const guest = webContents.create({
  3338. isGuest: true,
  3339. partition: params.partition,
  3340. embedder: embedder
  3341. })
  3342. guestInstances[guestInstanceId] = {
  3343. guest: guest,
  3344. embedder: embedder
  3345. }
  3346.  
  3347. watchEmbedder(embedder)
  3348.  
  3349. // Init guest web view after attached.
  3350. guest.on('did-attach', function () {
  3351. params = this.attachParams
  3352. delete this.attachParams
  3353.  
  3354. const previouslyAttached = this.viewInstanceId != null
  3355. this.viewInstanceId = params.instanceId
  3356.  
  3357. // Only load URL and set size on first attach
  3358. if (previouslyAttached) {
  3359. return
  3360. }
  3361.  
  3362. this.setSize({
  3363. normal: {
  3364. width: params.elementWidth,
  3365. height: params.elementHeight
  3366. },
  3367. enableAutoSize: params.autosize,
  3368. min: {
  3369. width: params.minwidth,
  3370. height: params.minheight
  3371. },
  3372. max: {
  3373. width: params.maxwidth,
  3374. height: params.maxheight
  3375. }
  3376. })
  3377. if (params.src) {
  3378. const opts = {}
  3379. if (params.httpreferrer) {
  3380. opts.httpReferrer = params.httpreferrer
  3381. }
  3382. if (params.useragent) {
  3383. opts.userAgent = params.useragent
  3384. }
  3385. this.loadURL(params.src, opts)
  3386. }
  3387. guest.allowPopups = params.allowpopups
  3388. })
  3389.  
  3390. const sendToEmbedder = (channel, ...args) => {
  3391. const embedder = getEmbedder(guestInstanceId)
  3392. if (embedder != null) {
  3393. embedder.send(`${channel}-${guest.viewInstanceId}`, ...args)
  3394. }
  3395. }
  3396.  
  3397. // Dispatch events to embedder.
  3398. const fn = function (event) {
  3399. guest.on(event, function (_, ...args) {
  3400. sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args)
  3401. })
  3402. }
  3403. for (const event of supportedWebViewEvents) {
  3404. fn(event)
  3405. }
  3406.  
  3407. // Dispatch guest's IPC messages to embedder.
  3408. guest.on('ipc-message-host', function (_, [channel, ...args]) {
  3409. sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)
  3410. })
  3411.  
  3412. // Autosize.
  3413. guest.on('size-changed', function (_, ...args) {
  3414. sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED', ...args)
  3415. })
  3416.  
  3417. return guestInstanceId
  3418. }
  3419.  
  3420. // Attach the guest to an element of embedder.
  3421. const attachGuest = function (event, elementInstanceId, guestInstanceId, params) {
  3422. const embedder = event.sender
  3423. // Destroy the old guest when attaching.
  3424. const key = `${embedder.getId()}-${elementInstanceId}`
  3425. const oldGuestInstanceId = embedderElementsMap[key]
  3426. if (oldGuestInstanceId != null) {
  3427. // Reattachment to the same guest is just a no-op.
  3428. if (oldGuestInstanceId === guestInstanceId) {
  3429. return
  3430. }
  3431.  
  3432. destroyGuest(embedder, oldGuestInstanceId)
  3433. }
  3434.  
  3435. const guestInstance = guestInstances[guestInstanceId]
  3436. // If this isn't a valid guest instance then do nothing.
  3437. if (!guestInstance) {
  3438. return
  3439. }
  3440. const {guest} = guestInstance
  3441.  
  3442. // If this guest is already attached to an element then remove it
  3443. if (guestInstance.elementInstanceId) {
  3444. const oldKey = `${guestInstance.embedder.getId()}-${guestInstance.elementInstanceId}`
  3445. delete embedderElementsMap[oldKey]
  3446.  
  3447. // Remove guest from embedder if moving across web views
  3448. if (guest.viewInstanceId !== params.instanceId) {
  3449. webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
  3450. guestInstance.embedder.send(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`)
  3451. }
  3452. }
  3453.  
  3454. const webPreferences = {
  3455. guestInstanceId: guestInstanceId,
  3456. nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
  3457. plugins: params.plugins,
  3458. zoomFactor: embedder._getZoomFactor(),
  3459. webSecurity: !params.disablewebsecurity,
  3460. blinkFeatures: params.blinkfeatures,
  3461. disableBlinkFeatures: params.disableblinkfeatures
  3462. }
  3463.  
  3464. // parse the 'webpreferences' attribute string, if set
  3465. // this uses the same parsing rules as window.open uses for its features
  3466. if (typeof params.webpreferences === 'string') {
  3467. parseFeaturesString(params.webpreferences, function (key, value) {
  3468. if (value === undefined) {
  3469. // no value was specified, default it to true
  3470. value = true
  3471. }
  3472. webPreferences[key] = value
  3473. })
  3474. }
  3475.  
  3476. if (params.preload) {
  3477. webPreferences.preloadURL = params.preload
  3478. }
  3479.  
  3480. embedder.emit('will-attach-webview', event, webPreferences, params)
  3481. if (event.defaultPrevented) {
  3482. if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId
  3483. destroyGuest(embedder, guestInstanceId)
  3484. return
  3485. }
  3486.  
  3487. webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
  3488. guest.attachParams = params
  3489. embedderElementsMap[key] = guestInstanceId
  3490.  
  3491. guest.setEmbedder(embedder)
  3492. guestInstance.embedder = embedder
  3493. guestInstance.elementInstanceId = elementInstanceId
  3494.  
  3495. watchEmbedder(embedder)
  3496. }
  3497.  
  3498. // Destroy an existing guest instance.
  3499. const destroyGuest = function (embedder, guestInstanceId) {
  3500. if (!(guestInstanceId in guestInstances)) {
  3501. return
  3502. }
  3503.  
  3504. const guestInstance = guestInstances[guestInstanceId]
  3505. if (embedder !== guestInstance.embedder) {
  3506. return
  3507. }
  3508.  
  3509. webViewManager.removeGuest(embedder, guestInstanceId)
  3510. guestInstance.guest.destroy()
  3511. delete guestInstances[guestInstanceId]
  3512.  
  3513. const key = `${embedder.getId()}-${guestInstance.elementInstanceId}`
  3514. delete embedderElementsMap[key]
  3515. }
  3516.  
  3517. // Once an embedder has had a guest attached we watch it for destruction to
  3518. // destroy any remaining guests.
  3519. const watchedEmbedders = new Set()
  3520. const watchEmbedder = function (embedder) {
  3521. if (watchedEmbedders.has(embedder)) {
  3522. return
  3523. }
  3524. watchedEmbedders.add(embedder)
  3525.  
  3526. // Forward embedder window visiblity change events to guest
  3527. const onVisibilityChange = function (visibilityState) {
  3528. for (const guestInstanceId of Object.keys(guestInstances)) {
  3529. const guestInstance = guestInstances[guestInstanceId]
  3530. if (guestInstance.embedder === embedder) {
  3531. guestInstance.guest.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState)
  3532. }
  3533. }
  3534. }
  3535. embedder.on('-window-visibility-change', onVisibilityChange)
  3536.  
  3537. const destroyEvents = ['will-destroy', 'crashed', 'did-navigate']
  3538. const destroy = function () {
  3539. for (const guestInstanceId of Object.keys(guestInstances)) {
  3540. if (guestInstances[guestInstanceId].embedder === embedder) {
  3541. destroyGuest(embedder, parseInt(guestInstanceId))
  3542. }
  3543. }
  3544.  
  3545. for (const event of destroyEvents) {
  3546. embedder.removeListener(event, destroy)
  3547. }
  3548. embedder.removeListener('-window-visibility-change', onVisibilityChange)
  3549.  
  3550. watchedEmbedders.delete(embedder)
  3551. }
  3552.  
  3553. for (const event of destroyEvents) {
  3554. embedder.once(event, destroy)
  3555.  
  3556. // Users might also listen to the crashed event, so we must ensure the guest
  3557. // is destroyed before users' listener gets called. It is done by moving our
  3558. // listener to the first one in queue.
  3559. const listeners = embedder._events[event]
  3560. if (Array.isArray(listeners)) {
  3561. moveLastToFirst(listeners)
  3562. }
  3563. }
  3564. }
  3565.  
  3566. ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
  3567. event.sender.send(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
  3568. })
  3569.  
  3570. ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) {
  3571. attachGuest(event, elementInstanceId, guestInstanceId, params)
  3572. })
  3573.  
  3574. ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
  3575. destroyGuest(event.sender, guestInstanceId)
  3576. })
  3577.  
  3578. ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, guestInstanceId, params) {
  3579. const guest = getGuest(guestInstanceId)
  3580. if (guest != null) guest.setSize(params)
  3581. })
  3582.  
  3583. // Returns WebContents from its guest id.
  3584. const getGuest = function (guestInstanceId) {
  3585. const guestInstance = guestInstances[guestInstanceId]
  3586. if (guestInstance != null) return guestInstance.guest
  3587. }
  3588.  
  3589. // Returns the embedder of the guest.
  3590. const getEmbedder = function (guestInstanceId) {
  3591. const guestInstance = guestInstances[guestInstanceId]
  3592. if (guestInstance != null) return guestInstance.embedder
  3593. }
  3594.  
  3595. exports.getGuest = getGuest
  3596. exports.getEmbedder = getEmbedder
  3597. 'use strict'
  3598.  
  3599. const {BrowserWindow, ipcMain, webContents} = require('electron')
  3600. const {isSameOrigin} = process.atomBinding('v8_util')
  3601. const parseFeaturesString = require('../common/parse-features-string')
  3602.  
  3603. const hasProp = {}.hasOwnProperty
  3604. const frameToGuest = new Map()
  3605.  
  3606. // Copy attribute of |parent| to |child| if it is not defined in |child|.
  3607. const mergeOptions = function (child, parent, visited) {
  3608. // Check for circular reference.
  3609. if (visited == null) visited = new Set()
  3610. if (visited.has(parent)) return
  3611.  
  3612. visited.add(parent)
  3613. for (const key in parent) {
  3614. if (!hasProp.call(parent, key)) continue
  3615. if (key in child) continue
  3616.  
  3617. const value = parent[key]
  3618. if (typeof value === 'object') {
  3619. child[key] = mergeOptions({}, value, visited)
  3620. } else {
  3621. child[key] = value
  3622. }
  3623. }
  3624. visited.delete(parent)
  3625.  
  3626. return child
  3627. }
  3628.  
  3629. // Merge |options| with the |embedder|'s window's options.
  3630. const mergeBrowserWindowOptions = function (embedder, options) {
  3631. if (options.webPreferences == null) {
  3632. options.webPreferences = {}
  3633. }
  3634. if (embedder.browserWindowOptions != null) {
  3635. // Inherit the original options if it is a BrowserWindow.
  3636. mergeOptions(options, embedder.browserWindowOptions)
  3637. } else {
  3638. // Or only inherit web-preferences if it is a webview.
  3639. mergeOptions(options.webPreferences, embedder.getWebPreferences())
  3640. }
  3641.  
  3642. // Disable node integration on child window if disabled on parent window
  3643. if (embedder.getWebPreferences().nodeIntegration === false) {
  3644. options.webPreferences.nodeIntegration = false
  3645. }
  3646.  
  3647. // Enable context isolation on child window if enabled on parent window
  3648. if (embedder.getWebPreferences().contextIsolation === true) {
  3649. options.webPreferences.contextIsolation = true
  3650. }
  3651.  
  3652. // Disable JavaScript on child window if disabled on parent window
  3653. if (embedder.getWebPreferences().javascript === false) {
  3654. options.webPreferences.javascript = false
  3655. }
  3656.  
  3657. // Sets correct openerId here to give correct options to 'new-window' event handler
  3658. options.webPreferences.openerId = embedder.id
  3659.  
  3660. return options
  3661. }
  3662.  
  3663. // Setup a new guest with |embedder|
  3664. const setupGuest = function (embedder, frameName, guest, options) {
  3665. // When |embedder| is destroyed we should also destroy attached guest, and if
  3666. // guest is closed by user then we should prevent |embedder| from double
  3667. // closing guest.
  3668. const guestId = guest.webContents.id
  3669. const closedByEmbedder = function () {
  3670. guest.removeListener('closed', closedByUser)
  3671. guest.destroy()
  3672. }
  3673. const closedByUser = function () {
  3674. embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
  3675. embedder.removeListener('render-view-deleted', closedByEmbedder)
  3676. }
  3677. if (!options.webPreferences.sandbox) {
  3678. // These events should only be handled when the guest window is opened by a
  3679. // non-sandboxed renderer for two reasons:
  3680. //
  3681. // - `render-view-deleted` is emitted when the popup is closed by the user,
  3682. // and that will eventually result in NativeWindow::NotifyWindowClosed
  3683. // using a dangling pointer since `destroy()` would have been called by
  3684. // `closeByEmbedded`
  3685. // - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
  3686. // there's no renderer code listening to it.,
  3687. embedder.once('render-view-deleted', closedByEmbedder)
  3688. guest.once('closed', closedByUser)
  3689. }
  3690. if (frameName) {
  3691. frameToGuest.set(frameName, guest)
  3692. guest.frameName = frameName
  3693. guest.once('closed', function () {
  3694. frameToGuest.delete(frameName)
  3695. })
  3696. }
  3697. return guestId
  3698. }
  3699.  
  3700. // Create a new guest created by |embedder| with |options|.
  3701. const createGuest = function (embedder, url, frameName, options, postData) {
  3702. let guest = frameToGuest.get(frameName)
  3703. if (frameName && (guest != null)) {
  3704. guest.loadURL(url)
  3705. return guest.webContents.id
  3706. }
  3707.  
  3708. // Remember the embedder window's id.
  3709. if (options.webPreferences == null) {
  3710. options.webPreferences = {}
  3711. }
  3712.  
  3713. guest = new BrowserWindow(options)
  3714. if (!options.webContents || url !== 'about:blank') {
  3715. // We should not call `loadURL` if the window was constructed from an
  3716. // existing webContents(window.open in a sandboxed renderer) and if the url
  3717. // is not 'about:blank'.
  3718. //
  3719. // Navigating to the url when creating the window from an existing
  3720. // webContents would not be necessary(it will navigate there anyway), but
  3721. // apparently there's a bug that allows the child window to be scripted by
  3722. // the opener, even when the child window is from another origin.
  3723. //
  3724. // That's why the second condition(url !== "about:blank") is required: to
  3725. // force `OverrideSiteInstanceForNavigation` to be called and consequently
  3726. // spawn a new renderer if the new window is targeting a different origin.
  3727. //
  3728. // If the URL is "about:blank", then it is very likely that the opener just
  3729. // wants to synchronously script the popup, for example:
  3730. //
  3731. // let popup = window.open()
  3732. // popup.document.body.write('<h1>hello</h1>')
  3733. //
  3734. // The above code would not work if a navigation to "about:blank" is done
  3735. // here, since the window would be cleared of all changes in the next tick.
  3736. const loadOptions = {}
  3737. if (postData != null) {
  3738. loadOptions.postData = postData
  3739. loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
  3740. if (postData.length > 0) {
  3741. const postDataFront = postData[0].bytes.toString()
  3742. const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
  3743. if (boundary != null) {
  3744. loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
  3745. }
  3746. }
  3747. }
  3748. guest.loadURL(url, loadOptions)
  3749. }
  3750.  
  3751. return setupGuest(embedder, frameName, guest, options)
  3752. }
  3753.  
  3754. const getGuestWindow = function (guestContents) {
  3755. let guestWindow = BrowserWindow.fromWebContents(guestContents)
  3756. if (guestWindow == null) {
  3757. const hostContents = guestContents.hostWebContents
  3758. if (hostContents != null) {
  3759. guestWindow = BrowserWindow.fromWebContents(hostContents)
  3760. }
  3761. }
  3762. return guestWindow
  3763. }
  3764.  
  3765. // Checks whether |sender| can access the |target|:
  3766. // 1. Check whether |sender| is the parent of |target|.
  3767. // 2. Check whether |sender| has node integration, if so it is allowed to
  3768. // do anything it wants.
  3769. // 3. Check whether the origins match.
  3770. //
  3771. // However it allows a child window without node integration but with same
  3772. // origin to do anything it wants, when its opener window has node integration.
  3773. // The W3C does not have anything on this, but from my understanding of the
  3774. // security model of |window.opener|, this should be fine.
  3775. const canAccessWindow = function (sender, target) {
  3776. return (target.getWebPreferences().openerId === sender.id) ||
  3777. (sender.getWebPreferences().nodeIntegration === true) ||
  3778. isSameOrigin(sender.getURL(), target.getURL())
  3779. }
  3780.  
  3781. // Routed window.open messages with raw options
  3782. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
  3783. if (url == null || url === '') url = 'about:blank'
  3784. if (frameName == null) frameName = ''
  3785. if (features == null) features = ''
  3786.  
  3787. const options = {}
  3788.  
  3789. const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
  3790. const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload', 'javascript', 'contextIsolation']
  3791. const disposition = 'new-window'
  3792.  
  3793. // Used to store additional features
  3794. const additionalFeatures = []
  3795.  
  3796. // Parse the features
  3797. parseFeaturesString(features, function (key, value) {
  3798. if (value === undefined) {
  3799. additionalFeatures.push(key)
  3800. } else {
  3801. // Don't allow webPreferences to be set since it must be an object
  3802. // that cannot be directly overridden
  3803. if (key === 'webPreferences') return
  3804.  
  3805. if (webPreferences.includes(key)) {
  3806. if (options.webPreferences == null) {
  3807. options.webPreferences = {}
  3808. }
  3809. options.webPreferences[key] = value
  3810. } else {
  3811. options[key] = value
  3812. }
  3813. }
  3814. })
  3815. if (options.left) {
  3816. if (options.x == null) {
  3817. options.x = options.left
  3818. }
  3819. }
  3820. if (options.top) {
  3821. if (options.y == null) {
  3822. options.y = options.top
  3823. }
  3824. }
  3825. if (options.title == null) {
  3826. options.title = frameName
  3827. }
  3828. if (options.width == null) {
  3829. options.width = 800
  3830. }
  3831. if (options.height == null) {
  3832. options.height = 600
  3833. }
  3834.  
  3835. for (const name of ints) {
  3836. if (options[name] != null) {
  3837. options[name] = parseInt(options[name], 10)
  3838. }
  3839. }
  3840.  
  3841. ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
  3842. url, frameName, disposition, options, additionalFeatures)
  3843. })
  3844.  
  3845. // Routed window.open messages with fully parsed options
  3846. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName,
  3847. disposition, options,
  3848. additionalFeatures, postData) {
  3849. options = mergeBrowserWindowOptions(event.sender, options)
  3850. event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures)
  3851. const newGuest = event.newGuest
  3852. if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
  3853. if (newGuest !== undefined && newGuest !== null) {
  3854. event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
  3855. } else {
  3856. event.returnValue = null
  3857. }
  3858. } else {
  3859. event.returnValue = createGuest(event.sender, url, frameName, options, postData)
  3860. }
  3861. })
  3862.  
  3863. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
  3864. const guestContents = webContents.fromId(guestId)
  3865. if (guestContents == null) return
  3866.  
  3867. if (!canAccessWindow(event.sender, guestContents)) {
  3868. console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
  3869. return
  3870. }
  3871.  
  3872. const guestWindow = getGuestWindow(guestContents)
  3873. if (guestWindow != null) guestWindow.destroy()
  3874. })
  3875.  
  3876. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
  3877. const guestContents = webContents.fromId(guestId)
  3878. if (guestContents == null) {
  3879. event.returnValue = null
  3880. return
  3881. }
  3882.  
  3883. if (!canAccessWindow(event.sender, guestContents)) {
  3884. console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  3885. event.returnValue = null
  3886. return
  3887. }
  3888.  
  3889. const guestWindow = getGuestWindow(guestContents)
  3890. if (guestWindow != null) {
  3891. event.returnValue = guestWindow[method](...args)
  3892. } else {
  3893. event.returnValue = null
  3894. }
  3895. })
  3896.  
  3897. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
  3898. if (targetOrigin == null) {
  3899. targetOrigin = '*'
  3900. }
  3901.  
  3902. const guestContents = webContents.fromId(guestId)
  3903. if (guestContents == null) return
  3904.  
  3905. // The W3C does not seem to have word on how postMessage should work when the
  3906. // origins do not match, so we do not do |canAccessWindow| check here since
  3907. // postMessage across origins is useful and not harmful.
  3908. if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
  3909. const sourceId = event.sender.id
  3910. guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
  3911. }
  3912. })
  3913.  
  3914. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
  3915. const guestContents = webContents.fromId(guestId)
  3916. if (guestContents == null) return
  3917.  
  3918. if (canAccessWindow(event.sender, guestContents)) {
  3919. guestContents[method](...args)
  3920. } else {
  3921. console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  3922. }
  3923. })
  3924.  
  3925. ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
  3926. const guestContents = webContents.fromId(guestId)
  3927. if (guestContents == null) {
  3928. event.returnValue = null
  3929. return
  3930. }
  3931.  
  3932. if (canAccessWindow(event.sender, guestContents)) {
  3933. event.returnValue = guestContents[method](...args)
  3934. } else {
  3935. console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  3936. event.returnValue = null
  3937. }
  3938. })
  3939. 'use strict'
  3940.  
  3941. const {Buffer} = require('buffer')
  3942. const fs = require('fs')
  3943. const path = require('path')
  3944. const util = require('util')
  3945. const Module = require('module')
  3946. const v8 = require('v8')
  3947.  
  3948. // We modified the original process.argv to let node.js load the atom.js,
  3949. // we need to restore it here.
  3950. process.argv.splice(1, 1)
  3951.  
  3952. // Clear search paths.
  3953. require('../common/reset-search-paths')
  3954.  
  3955. // Import common settings.
  3956. require('../common/init')
  3957.  
  3958. var globalPaths = Module.globalPaths
  3959.  
  3960. // Expose public APIs.
  3961. globalPaths.push(path.join(__dirname, 'api', 'exports'))
  3962.  
  3963. if (process.platform === 'win32') {
  3964. // Redirect node's console to use our own implementations, since node can not
  3965. // handle console output when running as GUI program.
  3966. var consoleLog = function (...args) {
  3967. return process.log(util.format(...args) + '\n')
  3968. }
  3969. var streamWrite = function (chunk, encoding, callback) {
  3970. if (Buffer.isBuffer(chunk)) {
  3971. chunk = chunk.toString(encoding)
  3972. }
  3973. process.log(chunk)
  3974. if (callback) {
  3975. callback()
  3976. }
  3977. return true
  3978. }
  3979. console.log = console.error = console.warn = consoleLog
  3980. process.stdout.write = process.stderr.write = streamWrite
  3981. }
  3982.  
  3983. // Don't quit on fatal error.
  3984. process.on('uncaughtException', function (error) {
  3985. // Do nothing if the user has a custom uncaught exception handler.
  3986. var dialog, message, ref, stack
  3987. if (process.listeners('uncaughtException').length > 1) {
  3988. return
  3989. }
  3990.  
  3991. // Show error in GUI.
  3992. dialog = require('electron').dialog
  3993. stack = (ref = error.stack) != null ? ref : error.name + ': ' + error.message
  3994. message = 'Uncaught Exception:\n' + stack
  3995. dialog.showErrorBox('A JavaScript error occurred in the main process', message)
  3996. })
  3997.  
  3998. // Emit 'exit' event on quit.
  3999. const {app} = require('electron')
  4000.  
  4001. app.on('quit', function (event, exitCode) {
  4002. process.emit('exit', exitCode)
  4003. })
  4004.  
  4005. if (process.platform === 'win32') {
  4006. // If we are a Squirrel.Windows-installed app, set app user model ID
  4007. // so that users don't have to do this.
  4008. //
  4009. // Squirrel packages are always of the form:
  4010. //
  4011. // PACKAGE-NAME
  4012. // - Update.exe
  4013. // - app-VERSION
  4014. // - OUREXE.exe
  4015. //
  4016. // Squirrel itself will always set the shortcut's App User Model ID to the
  4017. // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
  4018. // app.setAppUserModelId with a matching identifier so that renderer processes
  4019. // will inherit this value.
  4020. const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe')
  4021.  
  4022. if (fs.existsSync(updateDotExe)) {
  4023. const packageDir = path.dirname(path.resolve(updateDotExe))
  4024. const packageName = path.basename(packageDir).replace(/\s/g, '')
  4025. const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '')
  4026.  
  4027. app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`)
  4028. }
  4029. }
  4030.  
  4031. // Map process.exit to app.exit, which quits gracefully.
  4032. process.exit = app.exit
  4033.  
  4034. // Load the RPC server.
  4035. require('./rpc-server')
  4036.  
  4037. // Load the guest view manager.
  4038. require('./guest-view-manager')
  4039. require('./guest-window-manager')
  4040.  
  4041. // Now we try to load app's package.json.
  4042. let packagePath = null
  4043. let packageJson = null
  4044. const searchPaths = ['app', 'app.asar', 'default_app.asar']
  4045. for (packagePath of searchPaths) {
  4046. try {
  4047. packagePath = path.join(process.resourcesPath, packagePath)
  4048. packageJson = require(path.join(packagePath, 'package.json'))
  4049. break
  4050. } catch (error) {
  4051. continue
  4052. }
  4053. }
  4054.  
  4055. if (packageJson == null) {
  4056. process.nextTick(function () {
  4057. return process.exit(1)
  4058. })
  4059. throw new Error('Unable to find a valid app')
  4060. }
  4061.  
  4062. // Set application's version.
  4063. if (packageJson.version != null) {
  4064. app.setVersion(packageJson.version)
  4065. }
  4066.  
  4067. // Set application's name.
  4068. if (packageJson.productName != null) {
  4069. app.setName(packageJson.productName)
  4070. } else if (packageJson.name != null) {
  4071. app.setName(packageJson.name)
  4072. }
  4073.  
  4074. // Set application's desktop name.
  4075. if (packageJson.desktopName != null) {
  4076. app.setDesktopName(packageJson.desktopName)
  4077. } else {
  4078. app.setDesktopName((app.getName()) + '.desktop')
  4079. }
  4080.  
  4081. // Set v8 flags
  4082. if (packageJson.v8Flags != null) {
  4083. v8.setFlagsFromString(packageJson.v8Flags)
  4084. }
  4085.  
  4086. // Set the user path according to application's name.
  4087. app.setPath('userData', path.join(app.getPath('appData'), app.getName()))
  4088. app.setPath('userCache', path.join(app.getPath('cache'), app.getName()))
  4089. app.setAppPath(packagePath)
  4090.  
  4091. // Load the chrome extension support.
  4092. require('./chrome-extension')
  4093.  
  4094. // Load internal desktop-capturer module.
  4095. require('./desktop-capturer')
  4096.  
  4097. // Load protocol module to ensure it is populated on app ready
  4098. require('./api/protocol')
  4099.  
  4100. // Set main startup script of the app.
  4101. const mainStartupScript = packageJson.main || 'index.js'
  4102.  
  4103. // Workaround for electron/electron#5050
  4104. if (process.platform === 'linux' && process.env.XDG_CURRENT_DESKTOP === 'Pantheon') {
  4105. process.env.XDG_CURRENT_DESKTOP = 'Unity'
  4106. }
  4107.  
  4108. // Finally load app's main.js and transfer control to C++.
  4109. Module._load(path.join(packagePath, mainStartupScript), Module, true)
  4110. 'use strict'
  4111.  
  4112. const v8Util = process.atomBinding('v8_util')
  4113.  
  4114. class ObjectsRegistry {
  4115. constructor () {
  4116. this.nextId = 0
  4117.  
  4118. // Stores all objects by ref-counting.
  4119. // (id) => {object, count}
  4120. this.storage = {}
  4121.  
  4122. // Stores the IDs of objects referenced by WebContents.
  4123. // (webContentsId) => [id]
  4124. this.owners = {}
  4125. }
  4126.  
  4127. // Register a new object and return its assigned ID. If the object is already
  4128. // registered then the already assigned ID would be returned.
  4129. add (webContents, obj) {
  4130. // Get or assign an ID to the object.
  4131. const id = this.saveToStorage(obj)
  4132.  
  4133. // Add object to the set of referenced objects.
  4134. const webContentsId = webContents.getId()
  4135. let owner = this.owners[webContentsId]
  4136. if (!owner) {
  4137. owner = this.owners[webContentsId] = new Set()
  4138. this.registerDeleteListener(webContents, webContentsId)
  4139. }
  4140. if (!owner.has(id)) {
  4141. owner.add(id)
  4142. // Increase reference count if not referenced before.
  4143. this.storage[id].count++
  4144. }
  4145. return id
  4146. }
  4147.  
  4148. // Get an object according to its ID.
  4149. get (id) {
  4150. const pointer = this.storage[id]
  4151. if (pointer != null) return pointer.object
  4152. }
  4153.  
  4154. // Dereference an object according to its ID.
  4155. remove (webContentsId, id) {
  4156. // Dereference from the storage.
  4157. this.dereference(id)
  4158.  
  4159. // Also remove the reference in owner.
  4160. let owner = this.owners[webContentsId]
  4161. if (owner) {
  4162. owner.delete(id)
  4163. }
  4164. }
  4165.  
  4166. // Clear all references to objects refrenced by the WebContents.
  4167. clear (webContentsId) {
  4168. let owner = this.owners[webContentsId]
  4169. if (!owner) return
  4170.  
  4171. for (let id of owner) this.dereference(id)
  4172.  
  4173. delete this.owners[webContentsId]
  4174. }
  4175.  
  4176. // Private: Saves the object into storage and assigns an ID for it.
  4177. saveToStorage (object) {
  4178. let id = v8Util.getHiddenValue(object, 'atomId')
  4179. if (!id) {
  4180. id = ++this.nextId
  4181. this.storage[id] = {
  4182. count: 0,
  4183. object: object
  4184. }
  4185. v8Util.setHiddenValue(object, 'atomId', id)
  4186. }
  4187. return id
  4188. }
  4189.  
  4190. // Private: Dereference the object from store.
  4191. dereference (id) {
  4192. let pointer = this.storage[id]
  4193. if (pointer == null) {
  4194. return
  4195. }
  4196. pointer.count -= 1
  4197. if (pointer.count === 0) {
  4198. v8Util.deleteHiddenValue(pointer.object, 'atomId')
  4199. delete this.storage[id]
  4200. }
  4201. }
  4202.  
  4203. // Private: Clear the storage when webContents is reloaded/navigated.
  4204. registerDeleteListener (webContents, webContentsId) {
  4205. const processId = webContents.getProcessId()
  4206. const listener = (event, deletedProcessId) => {
  4207. if (deletedProcessId === processId) {
  4208. webContents.removeListener('render-view-deleted', listener)
  4209. this.clear(webContentsId)
  4210. }
  4211. }
  4212. webContents.on('render-view-deleted', listener)
  4213. }
  4214. }
  4215.  
  4216. module.exports = new ObjectsRegistry()
  4217. 'use strict'
  4218.  
  4219. const {Buffer} = require('buffer')
  4220. const electron = require('electron')
  4221. const v8Util = process.atomBinding('v8_util')
  4222. const {WebContents} = process.atomBinding('web_contents')
  4223.  
  4224. const {ipcMain, isPromise, webContents} = electron
  4225.  
  4226. const objectsRegistry = require('./objects-registry')
  4227.  
  4228. const hasProp = {}.hasOwnProperty
  4229.  
  4230. // The internal properties of Function.
  4231. const FUNCTION_PROPERTIES = [
  4232. 'length', 'name', 'arguments', 'caller', 'prototype'
  4233. ]
  4234.  
  4235. // The remote functions in renderer processes.
  4236. // id => Function
  4237. let rendererFunctions = v8Util.createDoubleIDWeakMap()
  4238.  
  4239. // Return the description of object's members:
  4240. let getObjectMembers = function (object) {
  4241. let names = Object.getOwnPropertyNames(object)
  4242. // For Function, we should not override following properties even though they
  4243. // are "own" properties.
  4244. if (typeof object === 'function') {
  4245. names = names.filter((name) => {
  4246. return !FUNCTION_PROPERTIES.includes(name)
  4247. })
  4248. }
  4249. // Map properties to descriptors.
  4250. return names.map((name) => {
  4251. let descriptor = Object.getOwnPropertyDescriptor(object, name)
  4252. let member = {name, enumerable: descriptor.enumerable, writable: false}
  4253. if (descriptor.get === undefined && typeof object[name] === 'function') {
  4254. member.type = 'method'
  4255. } else {
  4256. if (descriptor.set || descriptor.writable) member.writable = true
  4257. member.type = 'get'
  4258. }
  4259. return member
  4260. })
  4261. }
  4262.  
  4263. // Return the description of object's prototype.
  4264. let getObjectPrototype = function (object) {
  4265. let proto = Object.getPrototypeOf(object)
  4266. if (proto === null || proto === Object.prototype) return null
  4267. return {
  4268. members: getObjectMembers(proto),
  4269. proto: getObjectPrototype(proto)
  4270. }
  4271. }
  4272.  
  4273. // Convert a real value into meta data.
  4274. let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
  4275. // Determine the type of value.
  4276. const meta = { type: typeof value }
  4277. if (meta.type === 'object') {
  4278. // Recognize certain types of objects.
  4279. if (value === null) {
  4280. meta.type = 'value'
  4281. } else if (ArrayBuffer.isView(value)) {
  4282. meta.type = 'buffer'
  4283. } else if (Array.isArray(value)) {
  4284. meta.type = 'array'
  4285. } else if (value instanceof Error) {
  4286. meta.type = 'error'
  4287. } else if (value instanceof Date) {
  4288. meta.type = 'date'
  4289. } else if (isPromise(value)) {
  4290. meta.type = 'promise'
  4291. } else if (hasProp.call(value, 'callee') && value.length != null) {
  4292. // Treat the arguments object as array.
  4293. meta.type = 'array'
  4294. } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
  4295. // Treat simple objects as value.
  4296. meta.type = 'value'
  4297. }
  4298. }
  4299.  
  4300. // Fill the meta object according to value's type.
  4301. if (meta.type === 'array') {
  4302. meta.members = value.map((el) => valueToMeta(sender, el))
  4303. } else if (meta.type === 'object' || meta.type === 'function') {
  4304. meta.name = value.constructor ? value.constructor.name : ''
  4305.  
  4306. // Reference the original value if it's an object, because when it's
  4307. // passed to renderer we would assume the renderer keeps a reference of
  4308. // it.
  4309. meta.id = objectsRegistry.add(sender, value)
  4310. meta.members = getObjectMembers(value)
  4311. meta.proto = getObjectPrototype(value)
  4312. } else if (meta.type === 'buffer') {
  4313. meta.value = Buffer.from(value)
  4314. } else if (meta.type === 'promise') {
  4315. // Add default handler to prevent unhandled rejections in main process
  4316. // Instead they should appear in the renderer process
  4317. value.then(function () {}, function () {})
  4318.  
  4319. meta.then = valueToMeta(sender, function (onFulfilled, onRejected) {
  4320. value.then(onFulfilled, onRejected)
  4321. })
  4322. } else if (meta.type === 'error') {
  4323. meta.members = plainObjectToMeta(value)
  4324.  
  4325. // Error.name is not part of own properties.
  4326. meta.members.push({
  4327. name: 'name',
  4328. value: value.name
  4329. })
  4330. } else if (meta.type === 'date') {
  4331. meta.value = value.getTime()
  4332. } else {
  4333. meta.type = 'value'
  4334. meta.value = value
  4335. }
  4336. return meta
  4337. }
  4338.  
  4339. // Convert object to meta by value.
  4340. const plainObjectToMeta = function (obj) {
  4341. return Object.getOwnPropertyNames(obj).map(function (name) {
  4342. return {
  4343. name: name,
  4344. value: obj[name]
  4345. }
  4346. })
  4347. }
  4348.  
  4349. // Convert Error into meta data.
  4350. const exceptionToMeta = function (error) {
  4351. return {
  4352. type: 'exception',
  4353. message: error.message,
  4354. stack: error.stack || error
  4355. }
  4356. }
  4357.  
  4358. const throwRPCError = function (message) {
  4359. const error = new Error(message)
  4360. error.code = 'EBADRPC'
  4361. error.errno = -72
  4362. throw error
  4363. }
  4364.  
  4365. const removeRemoteListenersAndLogWarning = (meta, args, callIntoRenderer) => {
  4366. let message = `Attempting to call a function in a renderer window that has been closed or released.` +
  4367. `\nFunction provided here: ${meta.location}`
  4368.  
  4369. if (args.length > 0 && (args[0].sender instanceof WebContents)) {
  4370. const {sender} = args[0]
  4371. const remoteEvents = sender.eventNames().filter((eventName) => {
  4372. return sender.listeners(eventName).includes(callIntoRenderer)
  4373. })
  4374.  
  4375. if (remoteEvents.length > 0) {
  4376. message += `\nRemote event names: ${remoteEvents.join(', ')}`
  4377. remoteEvents.forEach((eventName) => {
  4378. sender.removeListener(eventName, callIntoRenderer)
  4379. })
  4380. }
  4381. }
  4382.  
  4383. console.warn(message)
  4384. }
  4385.  
  4386. // Convert array of meta data from renderer into array of real values.
  4387. const unwrapArgs = function (sender, args) {
  4388. const metaToValue = function (meta) {
  4389. let i, len, member, ref, returnValue
  4390. switch (meta.type) {
  4391. case 'value':
  4392. return meta.value
  4393. case 'remote-object':
  4394. return objectsRegistry.get(meta.id)
  4395. case 'array':
  4396. return unwrapArgs(sender, meta.value)
  4397. case 'buffer':
  4398. return Buffer.from(meta.value)
  4399. case 'date':
  4400. return new Date(meta.value)
  4401. case 'promise':
  4402. return Promise.resolve({
  4403. then: metaToValue(meta.then)
  4404. })
  4405. case 'object': {
  4406. let ret = {}
  4407. Object.defineProperty(ret.constructor, 'name', { value: meta.name })
  4408.  
  4409. ref = meta.members
  4410. for (i = 0, len = ref.length; i < len; i++) {
  4411. member = ref[i]
  4412. ret[member.name] = metaToValue(member.value)
  4413. }
  4414. return ret
  4415. }
  4416. case 'function-with-return-value':
  4417. returnValue = metaToValue(meta.value)
  4418. return function () {
  4419. return returnValue
  4420. }
  4421. case 'function': {
  4422. // Merge webContentsId and meta.id, since meta.id can be the same in
  4423. // different webContents.
  4424. const webContentsId = sender.getId()
  4425. const objectId = [webContentsId, meta.id]
  4426.  
  4427. // Cache the callbacks in renderer.
  4428. if (rendererFunctions.has(objectId)) {
  4429. return rendererFunctions.get(objectId)
  4430. }
  4431.  
  4432. let callIntoRenderer = function (...args) {
  4433. if (!sender.isDestroyed() && webContentsId === sender.getId()) {
  4434. sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
  4435. } else {
  4436. removeRemoteListenersAndLogWarning(meta, args, callIntoRenderer)
  4437. }
  4438. }
  4439. Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
  4440.  
  4441. v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender)
  4442. rendererFunctions.set(objectId, callIntoRenderer)
  4443. return callIntoRenderer
  4444. }
  4445. default:
  4446. throw new TypeError(`Unknown type: ${meta.type}`)
  4447. }
  4448. }
  4449. return args.map(metaToValue)
  4450. }
  4451.  
  4452. // Call a function and send reply asynchronously if it's a an asynchronous
  4453. // style function and the caller didn't pass a callback.
  4454. const callFunction = function (event, func, caller, args) {
  4455. let funcMarkedAsync, funcName, funcPassedCallback, ref, ret
  4456. funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
  4457. funcPassedCallback = typeof args[args.length - 1] === 'function'
  4458. try {
  4459. if (funcMarkedAsync && !funcPassedCallback) {
  4460. args.push(function (ret) {
  4461. event.returnValue = valueToMeta(event.sender, ret, true)
  4462. })
  4463. func.apply(caller, args)
  4464. } else {
  4465. ret = func.apply(caller, args)
  4466. event.returnValue = valueToMeta(event.sender, ret, true)
  4467. }
  4468. } catch (error) {
  4469. // Catch functions thrown further down in function invocation and wrap
  4470. // them with the function name so it's easier to trace things like
  4471. // `Error processing argument -1.`
  4472. funcName = ((ref = func.name) != null) ? ref : 'anonymous'
  4473. throw new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`)
  4474. }
  4475. }
  4476.  
  4477. ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) {
  4478. try {
  4479. event.returnValue = valueToMeta(event.sender, process.mainModule.require(module))
  4480. } catch (error) {
  4481. event.returnValue = exceptionToMeta(error)
  4482. }
  4483. })
  4484.  
  4485. ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) {
  4486. try {
  4487. event.returnValue = valueToMeta(event.sender, electron[module])
  4488. } catch (error) {
  4489. event.returnValue = exceptionToMeta(error)
  4490. }
  4491. })
  4492.  
  4493. ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) {
  4494. try {
  4495. event.returnValue = valueToMeta(event.sender, global[name])
  4496. } catch (error) {
  4497. event.returnValue = exceptionToMeta(error)
  4498. }
  4499. })
  4500.  
  4501. ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) {
  4502. try {
  4503. event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow())
  4504. } catch (error) {
  4505. event.returnValue = exceptionToMeta(error)
  4506. }
  4507. })
  4508.  
  4509. ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) {
  4510. event.returnValue = valueToMeta(event.sender, event.sender)
  4511. })
  4512.  
  4513. ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
  4514. try {
  4515. args = unwrapArgs(event.sender, args)
  4516. let constructor = objectsRegistry.get(id)
  4517.  
  4518. if (constructor == null) {
  4519. throwRPCError(`Cannot call constructor on missing remote object ${id}`)
  4520. }
  4521.  
  4522. // Call new with array of arguments.
  4523. // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
  4524. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  4525. event.returnValue = valueToMeta(event.sender, obj)
  4526. } catch (error) {
  4527. event.returnValue = exceptionToMeta(error)
  4528. }
  4529. })
  4530.  
  4531. ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) {
  4532. try {
  4533. args = unwrapArgs(event.sender, args)
  4534. let func = objectsRegistry.get(id)
  4535.  
  4536. if (func == null) {
  4537. throwRPCError(`Cannot call function on missing remote object ${id}`)
  4538. }
  4539.  
  4540. callFunction(event, func, global, args)
  4541. } catch (error) {
  4542. event.returnValue = exceptionToMeta(error)
  4543. }
  4544. })
  4545.  
  4546. ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) {
  4547. try {
  4548. args = unwrapArgs(event.sender, args)
  4549. let object = objectsRegistry.get(id)
  4550.  
  4551. if (object == null) {
  4552. throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
  4553. }
  4554.  
  4555. // Call new with array of arguments.
  4556. let constructor = object[method]
  4557. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  4558. event.returnValue = valueToMeta(event.sender, obj)
  4559. } catch (error) {
  4560. event.returnValue = exceptionToMeta(error)
  4561. }
  4562. })
  4563.  
  4564. ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) {
  4565. try {
  4566. args = unwrapArgs(event.sender, args)
  4567. let obj = objectsRegistry.get(id)
  4568.  
  4569. if (obj == null) {
  4570. throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
  4571. }
  4572.  
  4573. callFunction(event, obj[method], obj, args)
  4574. } catch (error) {
  4575. event.returnValue = exceptionToMeta(error)
  4576. }
  4577. })
  4578.  
  4579. ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
  4580. try {
  4581. args = unwrapArgs(event.sender, args)
  4582. let obj = objectsRegistry.get(id)
  4583.  
  4584. if (obj == null) {
  4585. throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
  4586. }
  4587.  
  4588. obj[name] = args[0]
  4589. event.returnValue = null
  4590. } catch (error) {
  4591. event.returnValue = exceptionToMeta(error)
  4592. }
  4593. })
  4594.  
  4595. ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
  4596. try {
  4597. let obj = objectsRegistry.get(id)
  4598.  
  4599. if (obj == null) {
  4600. throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
  4601. }
  4602.  
  4603. event.returnValue = valueToMeta(event.sender, obj[name])
  4604. } catch (error) {
  4605. event.returnValue = exceptionToMeta(error)
  4606. }
  4607. })
  4608.  
  4609. ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) {
  4610. objectsRegistry.remove(event.sender.getId(), id)
  4611. })
  4612.  
  4613. ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e) => {
  4614. objectsRegistry.clear(e.sender.getId())
  4615. e.returnValue = null
  4616. })
  4617.  
  4618. ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) {
  4619. try {
  4620. let guestViewManager = require('./guest-view-manager')
  4621. event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId))
  4622. } catch (error) {
  4623. event.returnValue = exceptionToMeta(error)
  4624. }
  4625. })
  4626.  
  4627. ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) {
  4628. try {
  4629. let guestViewManager = require('./guest-view-manager')
  4630. let guest = guestViewManager.getGuest(guestInstanceId)
  4631. if (requestId) {
  4632. const responseCallback = function (result) {
  4633. event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result)
  4634. }
  4635. args.push(responseCallback)
  4636. }
  4637. guest[method].apply(guest, args)
  4638. } catch (error) {
  4639. event.returnValue = exceptionToMeta(error)
  4640. }
  4641. })
  4642.  
  4643. ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId, channel, ...args) {
  4644. let contents = webContents.fromId(webContentsId)
  4645. if (!contents) {
  4646. console.error(`Sending message to WebContents with unknown ID ${webContentsId}`)
  4647. return
  4648. }
  4649.  
  4650. if (sendToAll) {
  4651. contents.sendToAll(channel, ...args)
  4652. } else {
  4653. contents.send(channel, ...args)
  4654. }
  4655. })
  4656.  
  4657. // Implements window.alert(message, title)
  4658. ipcMain.on('ELECTRON_BROWSER_WINDOW_ALERT', function (event, message, title) {
  4659. if (message == null) message = ''
  4660. if (title == null) title = ''
  4661.  
  4662. event.returnValue = electron.dialog.showMessageBox(event.sender.getOwnerBrowserWindow(), {
  4663. message: `${message}`,
  4664. title: `${title}`,
  4665. buttons: ['OK']
  4666. })
  4667. })
  4668.  
  4669. // Implements window.confirm(message, title)
  4670. ipcMain.on('ELECTRON_BROWSER_WINDOW_CONFIRM', function (event, message, title) {
  4671. if (message == null) message = ''
  4672. if (title == null) title = ''
  4673.  
  4674. event.returnValue = !electron.dialog.showMessageBox(event.sender.getOwnerBrowserWindow(), {
  4675. message: `${message}`,
  4676. title: `${title}`,
  4677. buttons: ['OK', 'Cancel'],
  4678. cancelId: 1
  4679. })
  4680. })
  4681.  
  4682. // Implements window.close()
  4683. ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
  4684. event.sender.getOwnerBrowserWindow().close()
  4685. event.returnValue = null
  4686. })
  4687. 'use strict'
  4688.  
  4689. const v8Util = process.atomBinding('v8_util')
  4690.  
  4691. class CallbacksRegistry {
  4692. constructor () {
  4693. this.nextId = 0
  4694. this.callbacks = {}
  4695. }
  4696.  
  4697. add (callback) {
  4698. // The callback is already added.
  4699. var filenameAndLine, id, location, match, ref, regexp, stackString
  4700. id = v8Util.getHiddenValue(callback, 'callbackId')
  4701. if (id != null) {
  4702. return id
  4703. }
  4704. id = ++this.nextId
  4705.  
  4706. // Capture the location of the function and put it in the ID string,
  4707. // so that release errors can be tracked down easily.
  4708. regexp = /at (.*)/gi
  4709. stackString = (new Error()).stack
  4710. while ((match = regexp.exec(stackString)) !== null) {
  4711. location = match[1]
  4712. if (location.indexOf('(native)') !== -1) {
  4713. continue
  4714. }
  4715. if (location.indexOf('electron.asar') !== -1) {
  4716. continue
  4717. }
  4718. ref = /([^/^)]*)\)?$/gi.exec(location)
  4719. filenameAndLine = ref[1]
  4720. break
  4721. }
  4722. this.callbacks[id] = callback
  4723. v8Util.setHiddenValue(callback, 'callbackId', id)
  4724. v8Util.setHiddenValue(callback, 'location', filenameAndLine)
  4725. return id
  4726. }
  4727.  
  4728. get (id) {
  4729. var ref
  4730. return (ref = this.callbacks[id]) != null ? ref : function () {}
  4731. }
  4732.  
  4733. apply (id, ...args) {
  4734. return this.get(id).apply(global, ...args)
  4735. }
  4736.  
  4737. remove (id) {
  4738. const callback = this.callbacks[id]
  4739. if (callback) {
  4740. v8Util.deleteHiddenValue(callback, 'callbackId')
  4741. delete this.callbacks[id]
  4742. }
  4743. }
  4744. }
  4745.  
  4746. module.exports = CallbacksRegistry
  4747. if (process.platform === 'linux' && process.type === 'renderer') {
  4748. // On Linux we could not access clipboard in renderer process.
  4749. module.exports = require('electron').remote.clipboard
  4750. } else {
  4751. const clipboard = process.atomBinding('clipboard')
  4752.  
  4753. // Read/write to find pasteboard over IPC since only main process is notified
  4754. // of changes
  4755. if (process.platform === 'darwin' && process.type === 'renderer') {
  4756. clipboard.readFindText = require('electron').remote.clipboard.readFindText
  4757. clipboard.writeFindText = require('electron').remote.clipboard.writeFindText
  4758. }
  4759.  
  4760. module.exports = clipboard
  4761. }
  4762. 'use strict'
  4763.  
  4764. const {spawn} = require('child_process')
  4765. const os = require('os')
  4766. const path = require('path')
  4767. const electron = require('electron')
  4768. const {app} = process.type === 'browser' ? electron : electron.remote
  4769. const binding = process.atomBinding('crash_reporter')
  4770.  
  4771. class CrashReporter {
  4772. start (options) {
  4773. if (options == null) {
  4774. options = {}
  4775. }
  4776. this.productName = options.productName != null ? options.productName : app.getName()
  4777. let {companyName, extra, ignoreSystemCrashHandler, submitURL, uploadToServer} = options
  4778.  
  4779. if (uploadToServer == null) {
  4780. // TODO: Remove deprecated autoSubmit property in 2.0
  4781. uploadToServer = options.autoSubmit
  4782. }
  4783.  
  4784. if (uploadToServer == null) {
  4785. uploadToServer = true
  4786. }
  4787.  
  4788. if (ignoreSystemCrashHandler == null) {
  4789. ignoreSystemCrashHandler = false
  4790. }
  4791. if (extra == null) {
  4792. extra = {}
  4793. }
  4794. if (extra._productName == null) {
  4795. extra._productName = this.getProductName()
  4796. }
  4797. if (extra._companyName == null) {
  4798. extra._companyName = companyName
  4799. }
  4800. if (extra._version == null) {
  4801. extra._version = app.getVersion()
  4802. }
  4803. if (companyName == null) {
  4804. throw new Error('companyName is a required option to crashReporter.start')
  4805. }
  4806. if (submitURL == null) {
  4807. throw new Error('submitURL is a required option to crashReporter.start')
  4808. }
  4809.  
  4810. if (process.platform === 'win32') {
  4811. const args = [
  4812. '--reporter-url=' + submitURL,
  4813. '--application-name=' + this.getProductName(),
  4814. '--crashes-directory=' + this.getCrashesDirectory(),
  4815. '--v=1'
  4816. ]
  4817. const env = {
  4818. ELECTRON_INTERNAL_CRASH_SERVICE: 1
  4819. }
  4820. this._crashServiceProcess = spawn(process.execPath, args, {
  4821. env: env,
  4822. detached: true
  4823. })
  4824. }
  4825.  
  4826. binding.start(this.getProductName(), companyName, submitURL, this.getCrashesDirectory(), uploadToServer, ignoreSystemCrashHandler, extra)
  4827. }
  4828.  
  4829. getLastCrashReport () {
  4830. const reports = this.getUploadedReports()
  4831. if (reports.length > 0) {
  4832. return reports[0]
  4833. } else {
  4834. return null
  4835. }
  4836. }
  4837.  
  4838. getUploadedReports () {
  4839. return binding.getUploadedReports(this.getCrashesDirectory())
  4840. }
  4841.  
  4842. getCrashesDirectory () {
  4843. const crashesDir = this.getProductName() + ' Crashes'
  4844. return path.join(this.getTempDirectory(), crashesDir)
  4845. }
  4846.  
  4847. getProductName () {
  4848. if (this.productName == null) {
  4849. this.productName = app.getName()
  4850. }
  4851. return this.productName
  4852. }
  4853.  
  4854. getTempDirectory () {
  4855. if (this.tempDirectory == null) {
  4856. try {
  4857. this.tempDirectory = app.getPath('temp')
  4858. } catch (error) {
  4859. // app.getPath may throw so fallback to OS temp directory
  4860. this.tempDirectory = os.tmpdir()
  4861. }
  4862. }
  4863. return this.tempDirectory
  4864. }
  4865.  
  4866. getUploadToServer () {
  4867. if (process.type === 'browser') {
  4868. return binding.getUploadToServer()
  4869. } else {
  4870. throw new Error('getUploadToServer can only be called from the main process')
  4871. }
  4872. }
  4873.  
  4874. setUploadToServer (uploadToServer) {
  4875. if (process.type === 'browser') {
  4876. return binding.setUploadToServer(uploadToServer)
  4877. } else {
  4878. throw new Error('setUploadToServer can only be called from the main process')
  4879. }
  4880. }
  4881.  
  4882. setExtraParameter (key, value) {
  4883. binding.setExtraParameter(key, value)
  4884. }
  4885. }
  4886.  
  4887. module.exports = new CrashReporter()
  4888. // Deprecate a method.
  4889. const deprecate = function (oldName, newName, fn) {
  4890. var warned
  4891. warned = false
  4892. return function () {
  4893. if (!(warned || process.noDeprecation)) {
  4894. warned = true
  4895. deprecate.warn(oldName, newName)
  4896. }
  4897. return fn.apply(this, arguments)
  4898. }
  4899. }
  4900.  
  4901. // The method is renamed.
  4902. deprecate.rename = function (object, oldName, newName) {
  4903. var newMethod, warned
  4904. warned = false
  4905. newMethod = function () {
  4906. if (!(warned || process.noDeprecation)) {
  4907. warned = true
  4908. deprecate.warn(oldName, newName)
  4909. }
  4910. return this[newName].apply(this, arguments)
  4911. }
  4912. if (typeof object === 'function') {
  4913. object.prototype[oldName] = newMethod
  4914. } else {
  4915. object[oldName] = newMethod
  4916. }
  4917. }
  4918.  
  4919. // Forward the method to member.
  4920. deprecate.member = function (object, method, member) {
  4921. var warned
  4922. warned = false
  4923. object.prototype[method] = function () {
  4924. if (!(warned || process.noDeprecation)) {
  4925. warned = true
  4926. deprecate.warn(method, member + '.' + method)
  4927. }
  4928. return this[member][method].apply(this[member], arguments)
  4929. }
  4930. }
  4931.  
  4932. // Deprecate a property.
  4933. deprecate.property = function (object, property, method) {
  4934. return Object.defineProperty(object, property, {
  4935. get: function () {
  4936. var warned
  4937. warned = false
  4938. if (!(warned || process.noDeprecation)) {
  4939. warned = true
  4940. deprecate.warn(property + ' property', method + ' method')
  4941. }
  4942. return this[method]()
  4943. }
  4944. })
  4945. }
  4946.  
  4947. // Deprecate an event.
  4948. deprecate.event = function (emitter, oldName, newName, fn) {
  4949. var warned = false
  4950. return emitter.on(newName, function (...args) {
  4951. // there is listeners for old API.
  4952. if (this.listenerCount(oldName) > 0) {
  4953. if (!(warned || process.noDeprecation)) {
  4954. warned = true
  4955. deprecate.warn("'" + oldName + "' event", "'" + newName + "' event")
  4956. }
  4957. if (fn != null) {
  4958. fn.apply(this, arguments)
  4959. } else {
  4960. this.emit.apply(this, [oldName].concat(args))
  4961. }
  4962. }
  4963. })
  4964. }
  4965.  
  4966. // Print deprecation warning.
  4967. deprecate.warn = function (oldName, newName) {
  4968. return deprecate.log(oldName + ' is deprecated. Use ' + newName + ' instead.')
  4969. }
  4970.  
  4971. var deprecationHandler = null
  4972.  
  4973. // Print deprecation message.
  4974. deprecate.log = function (message) {
  4975. if (typeof deprecationHandler === 'function') {
  4976. deprecationHandler(message)
  4977. } else if (process.throwDeprecation) {
  4978. throw new Error(message)
  4979. } else if (process.traceDeprecation) {
  4980. return console.trace(message)
  4981. } else {
  4982. return console.warn('(electron) ' + message)
  4983. }
  4984. }
  4985.  
  4986. deprecate.setHandler = function (handler) {
  4987. deprecationHandler = handler
  4988. }
  4989.  
  4990. deprecate.getHandler = function () {
  4991. return deprecationHandler
  4992. }
  4993.  
  4994. module.exports = deprecate
  4995. 'use strict'
  4996.  
  4997. const deprecate = require('electron').deprecate
  4998.  
  4999. exports.setHandler = function (deprecationHandler) {
  5000. deprecate.setHandler(deprecationHandler)
  5001. }
  5002.  
  5003. exports.getHandler = function () {
  5004. return deprecate.getHandler()
  5005. }
  5006. const moduleList = require('../module-list')
  5007.  
  5008. // Attaches properties to |exports|.
  5009. exports.defineProperties = function (exports) {
  5010. const descriptors = {}
  5011. for (const module of moduleList) {
  5012. descriptors[module.name] = {
  5013. enumerable: !module.private,
  5014. get: () => require(`../${module.file}`)
  5015. }
  5016. }
  5017. return Object.defineProperties(exports, descriptors)
  5018. }
  5019. 'use strict'
  5020.  
  5021. module.exports = function isPromise (val) {
  5022. return (
  5023. val &&
  5024. val.then &&
  5025. val.then instanceof Function &&
  5026. val.constructor &&
  5027. val.constructor.reject &&
  5028. val.constructor.reject instanceof Function &&
  5029. val.constructor.resolve &&
  5030. val.constructor.resolve instanceof Function
  5031. )
  5032. }
  5033. // Common modules, please sort alphabetically
  5034. module.exports = [
  5035. {name: 'clipboard', file: 'clipboard'},
  5036. {name: 'crashReporter', file: 'crash-reporter'},
  5037. {name: 'nativeImage', file: 'native-image'},
  5038. {name: 'shell', file: 'shell'},
  5039. // The internal modules, invisible unless you know their names.
  5040. {name: 'CallbacksRegistry', file: 'callbacks-registry', private: true},
  5041. {name: 'deprecate', file: 'deprecate', private: true},
  5042. {name: 'deprecations', file: 'deprecations', private: true},
  5043. {name: 'isPromise', file: 'is-promise', private: true}
  5044. ]
  5045. module.exports = process.atomBinding('native_image')
  5046. module.exports = process.atomBinding('shell')
  5047. module.exports = function atomBindingSetup (binding, processType) {
  5048. return function atomBinding (name) {
  5049. try {
  5050. return binding(`atom_${processType}_${name}`)
  5051. } catch (error) {
  5052. if (/No such module/.test(error.message)) {
  5053. return binding(`atom_common_${name}`)
  5054. } else {
  5055. throw error
  5056. }
  5057. }
  5058. }
  5059. }
  5060. const timers = require('timers')
  5061.  
  5062. process.atomBinding = require('./atom-binding-setup')(process.binding, process.type)
  5063.  
  5064. // setImmediate and process.nextTick makes use of uv_check and uv_prepare to
  5065. // run the callbacks, however since we only run uv loop on requests, the
  5066. // callbacks wouldn't be called until something else activated the uv loop,
  5067. // which would delay the callbacks for arbitrary long time. So we should
  5068. // initiatively activate the uv loop once setImmediate and process.nextTick is
  5069. // called.
  5070. var wrapWithActivateUvLoop = function (func) {
  5071. return function () {
  5072. process.activateUvLoop()
  5073. return func.apply(this, arguments)
  5074. }
  5075. }
  5076.  
  5077. process.nextTick = wrapWithActivateUvLoop(process.nextTick)
  5078.  
  5079. global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate)
  5080.  
  5081. global.clearImmediate = timers.clearImmediate
  5082.  
  5083. if (process.type === 'browser') {
  5084. // setTimeout needs to update the polling timeout of the event loop, when
  5085. // called under Chromium's event loop the node's event loop won't get a chance
  5086. // to update the timeout, so we have to force the node's event loop to
  5087. // recalculate the timeout in browser process.
  5088. global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout)
  5089. global.setInterval = wrapWithActivateUvLoop(timers.setInterval)
  5090. }
  5091.  
  5092. if (process.platform === 'win32') {
  5093. // Always returns EOF for stdin stream.
  5094. const {Readable} = require('stream')
  5095. const stdin = new Readable()
  5096. stdin.push(null)
  5097. process.__defineGetter__('stdin', function () {
  5098. return stdin
  5099. })
  5100.  
  5101. // If we're running as a Windows Store app, __dirname will be set
  5102. // to C:/Program Files/WindowsApps.
  5103. //
  5104. // Nobody else get's to install there, changing the path is forbidden
  5105. // We can therefore say that we're running as appx
  5106. if (__dirname.includes('\\WindowsApps\\')) {
  5107. process.windowsStore = true
  5108. }
  5109. }
  5110. // parses a feature string that has the format used in window.open()
  5111. // - `features` input string
  5112. // - `emit` function(key, value) - called for each parsed KV
  5113. module.exports = function parseFeaturesString (features, emit) {
  5114. features = `${features}`
  5115. // split the string by ','
  5116. features.split(/,\s*/).forEach((feature) => {
  5117. // expected form is either a key by itself or a key/value pair in the form of
  5118. // 'key=value'
  5119. let [key, value] = feature.split(/\s*=/)
  5120. if (!key) return
  5121.  
  5122. // interpret the value as a boolean, if possible
  5123. value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value
  5124.  
  5125. // emit the parsed pair
  5126. emit(key, value)
  5127. })
  5128. }
  5129. const path = require('path')
  5130. const Module = require('module')
  5131.  
  5132. // Clear Node's global search paths.
  5133. Module.globalPaths.length = 0
  5134.  
  5135. // Clear current and parent(init.js)'s search paths.
  5136. module.paths = []
  5137. module.parent.paths = []
  5138.  
  5139. // Prevent Node from adding paths outside this app to search paths.
  5140. const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
  5141. const originalNodeModulePaths = Module._nodeModulePaths
  5142. Module._nodeModulePaths = function (from) {
  5143. const paths = originalNodeModulePaths(from)
  5144. const fromPath = path.resolve(from) + path.sep
  5145. // If "from" is outside the app then we do nothing.
  5146. if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
  5147. return paths.filter(function (candidate) {
  5148. return candidate.startsWith(resourcesPathWithTrailingSlash)
  5149. })
  5150. } else {
  5151. return paths
  5152. }
  5153. }
  5154.  
  5155. // Patch Module._resolveFilename to always require the Electron API when
  5156. // require('electron') is done.
  5157. const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js')
  5158. const originalResolveFilename = Module._resolveFilename
  5159. Module._resolveFilename = function (request, parent, isMain) {
  5160. if (request === 'electron') {
  5161. return electronPath
  5162. } else {
  5163. return originalResolveFilename(request, parent, isMain)
  5164. }
  5165. }
  5166. const ipcRenderer = require('electron').ipcRenderer
  5167. const nativeImage = require('electron').nativeImage
  5168.  
  5169. var nextId = 0
  5170. var includes = [].includes
  5171.  
  5172. var getNextId = function () {
  5173. return ++nextId
  5174. }
  5175.  
  5176. // |options.type| can not be empty and has to include 'window' or 'screen'.
  5177. var isValid = function (options) {
  5178. return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types)
  5179. }
  5180.  
  5181. exports.getSources = function (options, callback) {
  5182. var captureScreen, captureWindow, id
  5183. if (!isValid(options)) {
  5184. return callback(new Error('Invalid options'))
  5185. }
  5186. captureWindow = includes.call(options.types, 'window')
  5187. captureScreen = includes.call(options.types, 'screen')
  5188. if (options.thumbnailSize == null) {
  5189. options.thumbnailSize = {
  5190. width: 150,
  5191. height: 150
  5192. }
  5193. }
  5194. id = getNextId()
  5195. ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id)
  5196. return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) {
  5197. var source
  5198. callback(null, (function () {
  5199. var i, len, results
  5200. results = []
  5201. for (i = 0, len = sources.length; i < len; i++) {
  5202. source = sources[i]
  5203. results.push({
  5204. id: source.id,
  5205. name: source.name,
  5206. thumbnail: nativeImage.createFromDataURL(source.thumbnail)
  5207. })
  5208. }
  5209. return results
  5210. })())
  5211. })
  5212. }
  5213. const common = require('../../../common/api/exports/electron')
  5214. const moduleList = require('../module-list')
  5215.  
  5216. // Import common modules.
  5217. common.defineProperties(exports)
  5218.  
  5219. for (const module of moduleList) {
  5220. Object.defineProperty(exports, module.name, {
  5221. enumerable: !module.private,
  5222. get: () => require(`../${module.file}`)
  5223. })
  5224. }
  5225. 'use strict'
  5226.  
  5227. const binding = process.atomBinding('ipc')
  5228. const v8Util = process.atomBinding('v8_util')
  5229.  
  5230. // Created by init.js.
  5231. const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
  5232.  
  5233. ipcRenderer.send = function (...args) {
  5234. return binding.send('ipc-message', args)
  5235. }
  5236.  
  5237. ipcRenderer.sendSync = function (...args) {
  5238. return JSON.parse(binding.sendSync('ipc-message-sync', args))
  5239. }
  5240.  
  5241. ipcRenderer.sendToHost = function (...args) {
  5242. return binding.send('ipc-message-host', args)
  5243. }
  5244.  
  5245. ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
  5246. if (typeof webContentsId !== 'number') {
  5247. throw new TypeError('First argument has to be webContentsId')
  5248. }
  5249.  
  5250. ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args)
  5251. }
  5252.  
  5253. ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
  5254. if (typeof webContentsId !== 'number') {
  5255. throw new TypeError('First argument has to be webContentsId')
  5256. }
  5257.  
  5258. ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args)
  5259. }
  5260.  
  5261. const removeAllListeners = ipcRenderer.removeAllListeners.bind(ipcRenderer)
  5262. ipcRenderer.removeAllListeners = function (...args) {
  5263. if (args.length === 0) {
  5264. throw new Error('Removing all listeners from ipcRenderer will make Electron internals stop working. Please specify a event name')
  5265. }
  5266. removeAllListeners(...args)
  5267. }
  5268.  
  5269. module.exports = ipcRenderer
  5270. // Renderer side modules, please sort alphabetically.
  5271. module.exports = [
  5272. {name: 'desktopCapturer', file: 'desktop-capturer'},
  5273. {name: 'ipcRenderer', file: 'ipc-renderer'},
  5274. {name: 'remote', file: 'remote'},
  5275. {name: 'screen', file: 'screen'},
  5276. {name: 'webFrame', file: 'web-frame'}
  5277. ]
  5278. 'use strict'
  5279.  
  5280. // Note: Don't use destructuring assignment for `Buffer`, or we'll hit a
  5281. // browserify bug that makes the statement invalid, throwing an error in
  5282. // sandboxed renderer.
  5283. const Buffer = require('buffer').Buffer
  5284. const v8Util = process.atomBinding('v8_util')
  5285. const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron')
  5286. const resolvePromise = Promise.resolve.bind(Promise)
  5287.  
  5288. const callbacksRegistry = new CallbacksRegistry()
  5289.  
  5290. const remoteObjectCache = v8Util.createIDWeakMap()
  5291.  
  5292. // Convert the arguments object into an array of meta data.
  5293. const wrapArgs = function (args, visited) {
  5294. if (visited == null) {
  5295. visited = new Set()
  5296. }
  5297.  
  5298. const valueToMeta = function (value) {
  5299. // Check for circular reference.
  5300. if (visited.has(value)) {
  5301. return {
  5302. type: 'value',
  5303. value: null
  5304. }
  5305. }
  5306.  
  5307. if (Array.isArray(value)) {
  5308. visited.add(value)
  5309. let meta = {
  5310. type: 'array',
  5311. value: wrapArgs(value, visited)
  5312. }
  5313. visited.delete(value)
  5314. return meta
  5315. } else if (ArrayBuffer.isView(value)) {
  5316. return {
  5317. type: 'buffer',
  5318. value: Buffer.from(value)
  5319. }
  5320. } else if (value instanceof Date) {
  5321. return {
  5322. type: 'date',
  5323. value: value.getTime()
  5324. }
  5325. } else if ((value != null) && typeof value === 'object') {
  5326. if (isPromise(value)) {
  5327. return {
  5328. type: 'promise',
  5329. then: valueToMeta(function (onFulfilled, onRejected) {
  5330. value.then(onFulfilled, onRejected)
  5331. })
  5332. }
  5333. } else if (v8Util.getHiddenValue(value, 'atomId')) {
  5334. return {
  5335. type: 'remote-object',
  5336. id: v8Util.getHiddenValue(value, 'atomId')
  5337. }
  5338. }
  5339.  
  5340. let meta = {
  5341. type: 'object',
  5342. name: value.constructor != null ? value.constructor.name : '',
  5343. members: []
  5344. }
  5345. visited.add(value)
  5346. for (let prop in value) {
  5347. meta.members.push({
  5348. name: prop,
  5349. value: valueToMeta(value[prop])
  5350. })
  5351. }
  5352. visited.delete(value)
  5353. return meta
  5354. } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
  5355. return {
  5356. type: 'function-with-return-value',
  5357. value: valueToMeta(value())
  5358. }
  5359. } else if (typeof value === 'function') {
  5360. return {
  5361. type: 'function',
  5362. id: callbacksRegistry.add(value),
  5363. location: v8Util.getHiddenValue(value, 'location'),
  5364. length: value.length
  5365. }
  5366. } else {
  5367. return {
  5368. type: 'value',
  5369. value: value
  5370. }
  5371. }
  5372. }
  5373. return args.map(valueToMeta)
  5374. }
  5375.  
  5376. // Populate object's members from descriptors.
  5377. // The |ref| will be kept referenced by |members|.
  5378. // This matches |getObjectMemebers| in rpc-server.
  5379. const setObjectMembers = function (ref, object, metaId, members) {
  5380. if (!Array.isArray(members)) return
  5381.  
  5382. for (let member of members) {
  5383. if (object.hasOwnProperty(member.name)) continue
  5384.  
  5385. let descriptor = { enumerable: member.enumerable }
  5386. if (member.type === 'method') {
  5387. const remoteMemberFunction = function (...args) {
  5388. if (this && this.constructor === remoteMemberFunction) {
  5389. // Constructor call.
  5390. let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(args))
  5391. return metaToValue(ret)
  5392. } else {
  5393. // Call member function.
  5394. let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(args))
  5395. return metaToValue(ret)
  5396. }
  5397. }
  5398.  
  5399. let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name)
  5400.  
  5401. descriptor.get = function () {
  5402. descriptorFunction.ref = ref // The member should reference its object.
  5403. return descriptorFunction
  5404. }
  5405. // Enable monkey-patch the method
  5406. descriptor.set = function (value) {
  5407. descriptorFunction = value
  5408. return value
  5409. }
  5410. descriptor.configurable = true
  5411. } else if (member.type === 'get') {
  5412. descriptor.get = function () {
  5413. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, member.name))
  5414. }
  5415.  
  5416. // Only set setter when it is writable.
  5417. if (member.writable) {
  5418. descriptor.set = function (value) {
  5419. const args = wrapArgs([value])
  5420. const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, args)
  5421. // Meta will be non-null when a setter error occurred so parse it
  5422. // to a value so it gets re-thrown.
  5423. if (meta != null) {
  5424. metaToValue(meta)
  5425. }
  5426. return value
  5427. }
  5428. }
  5429. }
  5430.  
  5431. Object.defineProperty(object, member.name, descriptor)
  5432. }
  5433. }
  5434.  
  5435. // Populate object's prototype from descriptor.
  5436. // This matches |getObjectPrototype| in rpc-server.
  5437. const setObjectPrototype = function (ref, object, metaId, descriptor) {
  5438. if (descriptor === null) return
  5439. let proto = {}
  5440. setObjectMembers(ref, proto, metaId, descriptor.members)
  5441. setObjectPrototype(ref, proto, metaId, descriptor.proto)
  5442. Object.setPrototypeOf(object, proto)
  5443. }
  5444.  
  5445. // Wrap function in Proxy for accessing remote properties
  5446. const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) {
  5447. let loaded = false
  5448.  
  5449. // Lazily load function properties
  5450. const loadRemoteProperties = () => {
  5451. if (loaded) return
  5452. loaded = true
  5453. const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, name)
  5454. setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
  5455. }
  5456.  
  5457. return new Proxy(remoteMemberFunction, {
  5458. set: (target, property, value, receiver) => {
  5459. if (property !== 'ref') loadRemoteProperties()
  5460. target[property] = value
  5461. return true
  5462. },
  5463. get: (target, property, receiver) => {
  5464. if (!target.hasOwnProperty(property)) loadRemoteProperties()
  5465. const value = target[property]
  5466.  
  5467. // Bind toString to target if it is a function to avoid
  5468. // Function.prototype.toString is not generic errors
  5469. if (property === 'toString' && typeof value === 'function') {
  5470. return value.bind(target)
  5471. }
  5472.  
  5473. return value
  5474. },
  5475. ownKeys: (target) => {
  5476. loadRemoteProperties()
  5477. return Object.getOwnPropertyNames(target)
  5478. },
  5479. getOwnPropertyDescriptor: (target, property) => {
  5480. let descriptor = Object.getOwnPropertyDescriptor(target, property)
  5481. if (descriptor != null) return descriptor
  5482. loadRemoteProperties()
  5483. return Object.getOwnPropertyDescriptor(target, property)
  5484. }
  5485. })
  5486. }
  5487.  
  5488. // Convert meta data from browser into real value.
  5489. const metaToValue = function (meta) {
  5490. var el, i, len, ref1, results, ret
  5491. switch (meta.type) {
  5492. case 'value':
  5493. return meta.value
  5494. case 'array':
  5495. ref1 = meta.members
  5496. results = []
  5497. for (i = 0, len = ref1.length; i < len; i++) {
  5498. el = ref1[i]
  5499. results.push(metaToValue(el))
  5500. }
  5501. return results
  5502. case 'buffer':
  5503. return Buffer.from(meta.value)
  5504. case 'promise':
  5505. return resolvePromise({then: metaToValue(meta.then)})
  5506. case 'error':
  5507. return metaToPlainObject(meta)
  5508. case 'date':
  5509. return new Date(meta.value)
  5510. case 'exception':
  5511. throw new Error(meta.message + '\n' + meta.stack)
  5512. default:
  5513. if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id)
  5514.  
  5515. if (meta.type === 'function') {
  5516. // A shadow class to represent the remote function object.
  5517. let remoteFunction = function (...args) {
  5518. if (this && this.constructor === remoteFunction) {
  5519. // Constructor call.
  5520. let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(args))
  5521. // Returning object in constructor will replace constructed object
  5522. // with the returned object.
  5523. // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this
  5524. return metaToValue(obj)
  5525. } else {
  5526. // Function call.
  5527. let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(args))
  5528. return metaToValue(obj)
  5529. }
  5530. }
  5531. ret = remoteFunction
  5532. } else {
  5533. ret = {}
  5534. }
  5535.  
  5536. // Populate delegate members.
  5537. setObjectMembers(ret, ret, meta.id, meta.members)
  5538. // Populate delegate prototype.
  5539. setObjectPrototype(ret, ret, meta.id, meta.proto)
  5540.  
  5541. // Set constructor.name to object's name.
  5542. Object.defineProperty(ret.constructor, 'name', { value: meta.name })
  5543.  
  5544. // Track delegate object's life time, and tell the browser to clean up
  5545. // when the object is GCed.
  5546. v8Util.setRemoteObjectFreer(ret, meta.id)
  5547.  
  5548. // Remember object's id.
  5549. v8Util.setHiddenValue(ret, 'atomId', meta.id)
  5550. remoteObjectCache.set(meta.id, ret)
  5551. return ret
  5552. }
  5553. }
  5554.  
  5555. // Construct a plain object from the meta.
  5556. const metaToPlainObject = function (meta) {
  5557. var i, len, obj, ref1
  5558. obj = (function () {
  5559. switch (meta.type) {
  5560. case 'error':
  5561. return new Error()
  5562. default:
  5563. return {}
  5564. }
  5565. })()
  5566. ref1 = meta.members
  5567. for (i = 0, len = ref1.length; i < len; i++) {
  5568. let {name, value} = ref1[i]
  5569. obj[name] = value
  5570. }
  5571. return obj
  5572. }
  5573.  
  5574. // Browser calls a callback in renderer.
  5575. ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', function (event, id, args) {
  5576. callbacksRegistry.apply(id, metaToValue(args))
  5577. })
  5578.  
  5579. // A callback in browser is released.
  5580. ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) {
  5581. callbacksRegistry.remove(id)
  5582. })
  5583.  
  5584. process.on('exit', () => {
  5585. ipcRenderer.sendSync('ELECTRON_BROWSER_CONTEXT_RELEASE')
  5586. })
  5587.  
  5588. // Get remote module.
  5589. exports.require = function (module) {
  5590. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module))
  5591. }
  5592.  
  5593. // Alias to remote.require('electron').xxx.
  5594. exports.getBuiltin = function (module) {
  5595. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GET_BUILTIN', module))
  5596. }
  5597.  
  5598. // Get current BrowserWindow.
  5599. exports.getCurrentWindow = function () {
  5600. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WINDOW'))
  5601. }
  5602.  
  5603. // Get current WebContents object.
  5604. exports.getCurrentWebContents = function () {
  5605. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'))
  5606. }
  5607.  
  5608. // Get a global object in browser.
  5609. exports.getGlobal = function (name) {
  5610. return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GLOBAL', name))
  5611. }
  5612.  
  5613. // Get the process object in browser.
  5614. exports.__defineGetter__('process', function () {
  5615. return exports.getGlobal('process')
  5616. })
  5617.  
  5618. // Create a funtion that will return the specifed value when called in browser.
  5619. exports.createFunctionWithReturnValue = function (returnValue) {
  5620. const func = function () {
  5621. return returnValue
  5622. }
  5623. v8Util.setHiddenValue(func, 'returnValue', true)
  5624. return func
  5625. }
  5626.  
  5627. // Get the guest WebContents from guestInstanceId.
  5628. exports.getGuestWebContents = function (guestInstanceId) {
  5629. const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId)
  5630. return metaToValue(meta)
  5631. }
  5632.  
  5633. const addBuiltinProperty = (name) => {
  5634. Object.defineProperty(exports, name, {
  5635. get: function () {
  5636. return exports.getBuiltin(name)
  5637. }
  5638. })
  5639. }
  5640.  
  5641. const browserModules =
  5642. require('../../common/api/module-list').concat(
  5643. require('../../browser/api/module-list'))
  5644.  
  5645. // And add a helper receiver for each one.
  5646. browserModules
  5647. .filter((m) => !m.private)
  5648. .map((m) => m.name)
  5649. .forEach(addBuiltinProperty)
  5650. module.exports = require('electron').remote.screen
  5651. 'use strict'
  5652.  
  5653. const {EventEmitter} = require('events')
  5654. const {webFrame, WebFrame} = process.atomBinding('web_frame')
  5655.  
  5656. // WebFrame is an EventEmitter.
  5657. Object.setPrototypeOf(WebFrame.prototype, EventEmitter.prototype)
  5658.  
  5659. // Lots of webview would subscribe to webFrame's events.
  5660. webFrame.setMaxListeners(0)
  5661.  
  5662. module.exports = webFrame
  5663. const {ipcRenderer} = require('electron')
  5664. const Event = require('./extensions/event')
  5665. const url = require('url')
  5666.  
  5667. let nextId = 0
  5668.  
  5669. class Tab {
  5670. constructor (tabId) {
  5671. this.id = tabId
  5672. }
  5673. }
  5674.  
  5675. class MessageSender {
  5676. constructor (tabId, extensionId) {
  5677. this.tab = tabId ? new Tab(tabId) : null
  5678. this.id = extensionId
  5679. this.url = `chrome-extension://${extensionId}`
  5680. }
  5681. }
  5682.  
  5683. class Port {
  5684. constructor (tabId, portId, extensionId, name) {
  5685. this.tabId = tabId
  5686. this.portId = portId
  5687. this.disconnected = false
  5688.  
  5689. this.name = name
  5690. this.onDisconnect = new Event()
  5691. this.onMessage = new Event()
  5692. this.sender = new MessageSender(tabId, extensionId)
  5693.  
  5694. ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
  5695. this._onDisconnect()
  5696. })
  5697. ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => {
  5698. const sendResponse = function () { console.error('sendResponse is not implemented') }
  5699. this.onMessage.emit(message, this.sender, sendResponse)
  5700. })
  5701. }
  5702.  
  5703. disconnect () {
  5704. if (this.disconnected) return
  5705.  
  5706. ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
  5707. this._onDisconnect()
  5708. }
  5709.  
  5710. postMessage (message) {
  5711. ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message)
  5712. }
  5713.  
  5714. _onDisconnect () {
  5715. this.disconnected = true
  5716. ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
  5717. this.onDisconnect.emit()
  5718. }
  5719. }
  5720.  
  5721. // Inject chrome API to the |context|
  5722. exports.injectTo = function (extensionId, isBackgroundPage, context) {
  5723. const chrome = context.chrome = context.chrome || {}
  5724. let originResultID = 1
  5725.  
  5726. ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => {
  5727. chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
  5728. })
  5729.  
  5730. ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message, resultID) => {
  5731. chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), (messageResult) => {
  5732. ipcRenderer.send(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, messageResult)
  5733. })
  5734. })
  5735.  
  5736. ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => {
  5737. chrome.tabs.onCreated.emit(new Tab(tabId))
  5738. })
  5739.  
  5740. ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => {
  5741. chrome.tabs.onRemoved.emit(tabId)
  5742. })
  5743.  
  5744. chrome.runtime = {
  5745. id: extensionId,
  5746.  
  5747. getURL: function (path) {
  5748. return url.format({
  5749. protocol: 'chrome-extension',
  5750. slashes: true,
  5751. hostname: extensionId,
  5752. pathname: path
  5753. })
  5754. },
  5755.  
  5756. connect (...args) {
  5757. if (isBackgroundPage) {
  5758. console.error('chrome.runtime.connect is not supported in background page')
  5759. return
  5760. }
  5761.  
  5762. // Parse the optional args.
  5763. let targetExtensionId = extensionId
  5764. let connectInfo = {name: ''}
  5765. if (args.length === 1) {
  5766. connectInfo = args[0]
  5767. } else if (args.length === 2) {
  5768. [targetExtensionId, connectInfo] = args
  5769. }
  5770.  
  5771. const {tabId, portId} = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
  5772. return new Port(tabId, portId, extensionId, connectInfo.name)
  5773. },
  5774.  
  5775. sendMessage (...args) {
  5776. if (isBackgroundPage) {
  5777. console.error('chrome.runtime.sendMessage is not supported in background page')
  5778. return
  5779. }
  5780.  
  5781. // Parse the optional args.
  5782. let targetExtensionId = extensionId
  5783. let message
  5784. if (args.length === 1) {
  5785. message = args[0]
  5786. } else if (args.length === 2) {
  5787. // A case of not provide extension-id: (message, responseCallback)
  5788. if (typeof args[1] === 'function') {
  5789. ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[1](result))
  5790. message = args[0]
  5791. } else {
  5792. [targetExtensionId, message] = args
  5793. }
  5794. } else {
  5795. console.error('options is not supported')
  5796. ipcRenderer.on(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, (event, result) => args[2](result))
  5797. }
  5798.  
  5799. ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message, originResultID)
  5800. originResultID++
  5801. },
  5802.  
  5803. onConnect: new Event(),
  5804. onMessage: new Event(),
  5805. onInstalled: new Event()
  5806. }
  5807.  
  5808. chrome.tabs = {
  5809. executeScript (tabId, details, callback) {
  5810. const requestId = ++nextId
  5811. ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, (event, result) => {
  5812. callback([event.result])
  5813. })
  5814. ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', requestId, tabId, extensionId, details)
  5815. },
  5816.  
  5817. sendMessage (tabId, message, options, responseCallback) {
  5818. if (responseCallback) {
  5819. ipcRenderer.on(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, (event, result) => responseCallback(result))
  5820. }
  5821. ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message, originResultID)
  5822. originResultID++
  5823. },
  5824.  
  5825. onUpdated: new Event(),
  5826. onCreated: new Event(),
  5827. onRemoved: new Event()
  5828. }
  5829.  
  5830. chrome.extension = {
  5831. getURL: chrome.runtime.getURL,
  5832. connect: chrome.runtime.connect,
  5833. onConnect: chrome.runtime.onConnect,
  5834. sendMessage: chrome.runtime.sendMessage,
  5835. onMessage: chrome.runtime.onMessage
  5836. }
  5837.  
  5838. chrome.storage = require('./extensions/storage')
  5839.  
  5840. chrome.pageAction = {
  5841. show () {},
  5842. hide () {},
  5843. setTitle () {},
  5844. getTitle () {},
  5845. setIcon () {},
  5846. setPopup () {},
  5847. getPopup () {}
  5848. }
  5849.  
  5850. chrome.i18n = require('./extensions/i18n').setup(extensionId)
  5851. chrome.webNavigation = require('./extensions/web-navigation').setup()
  5852. }
  5853. const {ipcRenderer} = require('electron')
  5854. const {runInThisContext} = require('vm')
  5855.  
  5856. // Check whether pattern matches.
  5857. // https://developer.chrome.com/extensions/match_patterns
  5858. const matchesPattern = function (pattern) {
  5859. if (pattern === '<all_urls>') return true
  5860.  
  5861. const regexp = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
  5862. return location.href.match(regexp)
  5863. }
  5864.  
  5865. // Run the code with chrome API integrated.
  5866. const runContentScript = function (extensionId, url, code) {
  5867. const context = {}
  5868. require('./chrome-api').injectTo(extensionId, false, context)
  5869. const wrapper = `(function (chrome) {\n ${code}\n })`
  5870. const compiledWrapper = runInThisContext(wrapper, {
  5871. filename: url,
  5872. lineOffset: 1,
  5873. displayErrors: true
  5874. })
  5875. return compiledWrapper.call(this, context.chrome)
  5876. }
  5877.  
  5878. // Run injected scripts.
  5879. // https://developer.chrome.com/extensions/content_scripts
  5880. const injectContentScript = function (extensionId, script) {
  5881. for (const match of script.matches) {
  5882. if (!matchesPattern(match)) return
  5883. }
  5884.  
  5885. for (const {url, code} of script.js) {
  5886. const fire = runContentScript.bind(window, extensionId, url, code)
  5887. if (script.runAt === 'document_start') {
  5888. process.once('document-start', fire)
  5889. } else if (script.runAt === 'document_end') {
  5890. process.once('document-end', fire)
  5891. } else if (script.runAt === 'document_idle') {
  5892. document.addEventListener('DOMContentLoaded', fire)
  5893. }
  5894. }
  5895. }
  5896.  
  5897. // Handle the request of chrome.tabs.executeJavaScript.
  5898. ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) {
  5899. const result = runContentScript.call(window, extensionId, url, code)
  5900. ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result)
  5901. })
  5902.  
  5903. // Read the renderer process preferences.
  5904. const preferences = process.getRenderProcessPreferences()
  5905. if (preferences) {
  5906. for (const pref of preferences) {
  5907. if (pref.contentScripts) {
  5908. for (const script of pref.contentScripts) {
  5909. injectContentScript(pref.extensionId, script)
  5910. }
  5911. }
  5912. }
  5913. }
  5914. class Event {
  5915. constructor () {
  5916. this.listeners = []
  5917. }
  5918.  
  5919. addListener (callback) {
  5920. this.listeners.push(callback)
  5921. }
  5922.  
  5923. removeListener (callback) {
  5924. const index = this.listeners.indexOf(callback)
  5925. if (index !== -1) {
  5926. this.listeners.splice(index, 1)
  5927. }
  5928. }
  5929.  
  5930. emit (...args) {
  5931. for (const listener of this.listeners) {
  5932. listener(...args)
  5933. }
  5934. }
  5935. }
  5936.  
  5937. module.exports = Event
  5938. // Implementation of chrome.i18n.getMessage
  5939. // https://developer.chrome.com/extensions/i18n#method-getMessage
  5940. //
  5941. // Does not implement predefined messages:
  5942. // https://developer.chrome.com/extensions/i18n#overview-predefined
  5943.  
  5944. const {ipcRenderer} = require('electron')
  5945. const fs = require('fs')
  5946. const path = require('path')
  5947.  
  5948. let metadata
  5949.  
  5950. const getExtensionMetadata = (extensionId) => {
  5951. if (!metadata) {
  5952. metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId)
  5953. }
  5954. return metadata
  5955. }
  5956.  
  5957. const getMessagesPath = (extensionId, language) => {
  5958. const metadata = getExtensionMetadata(extensionId)
  5959. const defaultLocale = metadata.default_locale || 'en'
  5960. const localesDirectory = path.join(metadata.srcDirectory, '_locales')
  5961. let messagesPath = path.join(localesDirectory, language, 'messages.json')
  5962. if (!fs.statSyncNoException(messagesPath)) {
  5963. messagesPath = path.join(localesDirectory, defaultLocale, 'messages.json')
  5964. }
  5965. return messagesPath
  5966. }
  5967.  
  5968. const getMessages = (extensionId, language) => {
  5969. try {
  5970. const messagesPath = getMessagesPath(extensionId, language)
  5971. return JSON.parse(fs.readFileSync(messagesPath)) || {}
  5972. } catch (error) {
  5973. return {}
  5974. }
  5975. }
  5976.  
  5977. const getLanguage = () => {
  5978. return navigator.language.replace(/-.*$/, '').toLowerCase()
  5979. }
  5980.  
  5981. const replaceNumberedSubstitutions = (message, substitutions) => {
  5982. return message.replace(/\$(\d+)/, (_, number) => {
  5983. const index = parseInt(number, 10) - 1
  5984. return substitutions[index] || ''
  5985. })
  5986. }
  5987.  
  5988. const replacePlaceholders = (message, placeholders, substitutions) => {
  5989. if (typeof substitutions === 'string') {
  5990. substitutions = [substitutions]
  5991. }
  5992. if (!Array.isArray(substitutions)) {
  5993. substitutions = []
  5994. }
  5995.  
  5996. if (placeholders) {
  5997. Object.keys(placeholders).forEach((name) => {
  5998. let {content} = placeholders[name]
  5999. content = replaceNumberedSubstitutions(content, substitutions)
  6000. message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content)
  6001. })
  6002. }
  6003.  
  6004. return replaceNumberedSubstitutions(message, substitutions)
  6005. }
  6006.  
  6007. const getMessage = (extensionId, messageName, substitutions) => {
  6008. const messages = getMessages(extensionId, getLanguage())
  6009. if (messages.hasOwnProperty(messageName)) {
  6010. const {message, placeholders} = messages[messageName]
  6011. return replacePlaceholders(message, placeholders, substitutions)
  6012. }
  6013. }
  6014.  
  6015. exports.setup = (extensionId) => {
  6016. return {
  6017. getMessage (messageName, substitutions) {
  6018. return getMessage(extensionId, messageName, substitutions)
  6019. }
  6020. }
  6021. }
  6022. const getStorage = (storageType) => {
  6023. const data = window.localStorage.getItem(`__chrome.storage.${storageType}__`)
  6024. if (data != null) {
  6025. return JSON.parse(data)
  6026. } else {
  6027. return {}
  6028. }
  6029. }
  6030.  
  6031. const setStorage = (storageType, storage) => {
  6032. const json = JSON.stringify(storage)
  6033. window.localStorage.setItem(`__chrome.storage.${storageType}__`, json)
  6034. }
  6035.  
  6036. const scheduleCallback = (items, callback) => {
  6037. setTimeout(function () {
  6038. callback(items)
  6039. })
  6040. }
  6041.  
  6042. const getStorageManager = (storageType) => {
  6043. return {
  6044. get (keys, callback) {
  6045. const storage = getStorage(storageType)
  6046. if (keys == null) return scheduleCallback(storage, callback)
  6047.  
  6048. let defaults = {}
  6049. switch (typeof keys) {
  6050. case 'string':
  6051. keys = [keys]
  6052. break
  6053. case 'object':
  6054. if (!Array.isArray(keys)) {
  6055. defaults = keys
  6056. keys = Object.keys(keys)
  6057. }
  6058. break
  6059. }
  6060. if (keys.length === 0) return scheduleCallback({}, callback)
  6061.  
  6062. let items = {}
  6063. keys.forEach(function (key) {
  6064. var value = storage[key]
  6065. if (value == null) value = defaults[key]
  6066. items[key] = value
  6067. })
  6068. scheduleCallback(items, callback)
  6069. },
  6070.  
  6071. set (items, callback) {
  6072. const storage = getStorage(storageType)
  6073.  
  6074. Object.keys(items).forEach(function (name) {
  6075. storage[name] = items[name]
  6076. })
  6077.  
  6078. setStorage(storageType, storage)
  6079.  
  6080. setTimeout(callback)
  6081. },
  6082.  
  6083. remove (keys, callback) {
  6084. const storage = getStorage(storageType)
  6085.  
  6086. if (!Array.isArray(keys)) {
  6087. keys = [keys]
  6088. }
  6089. keys.forEach(function (key) {
  6090. delete storage[key]
  6091. })
  6092.  
  6093. setStorage(storageType, storage)
  6094.  
  6095. setTimeout(callback)
  6096. },
  6097.  
  6098. clear (callback) {
  6099. setStorage(storageType, {})
  6100.  
  6101. setTimeout(callback)
  6102. }
  6103. }
  6104. }
  6105.  
  6106. module.exports = {
  6107. sync: getStorageManager('sync'),
  6108. local: getStorageManager('local')
  6109. }
  6110. const Event = require('./event')
  6111. const {ipcRenderer} = require('electron')
  6112.  
  6113. class WebNavigation {
  6114. constructor () {
  6115. this.onBeforeNavigate = new Event()
  6116. this.onCompleted = new Event()
  6117.  
  6118. ipcRenderer.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event, details) => {
  6119. this.onBeforeNavigate.emit(details)
  6120. })
  6121.  
  6122. ipcRenderer.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event, details) => {
  6123. this.onCompleted.emit(details)
  6124. })
  6125. }
  6126. }
  6127.  
  6128. exports.setup = () => {
  6129. return new WebNavigation()
  6130. }
  6131. 'use strict'
  6132.  
  6133. const events = require('events')
  6134. const path = require('path')
  6135. const Module = require('module')
  6136. const resolvePromise = Promise.resolve.bind(Promise)
  6137.  
  6138. // We modified the original process.argv to let node.js load the
  6139. // init.js, we need to restore it here.
  6140. process.argv.splice(1, 1)
  6141.  
  6142. // Clear search paths.
  6143. require('../common/reset-search-paths')
  6144.  
  6145. // Import common settings.
  6146. require('../common/init')
  6147.  
  6148. var globalPaths = Module.globalPaths
  6149.  
  6150. // Expose public APIs.
  6151. globalPaths.push(path.join(__dirname, 'api', 'exports'))
  6152.  
  6153. // The global variable will be used by ipc for event dispatching
  6154. var v8Util = process.atomBinding('v8_util')
  6155.  
  6156. v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter())
  6157.  
  6158. // Use electron module after everything is ready.
  6159. const electron = require('electron')
  6160.  
  6161. // Call webFrame method.
  6162. electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => {
  6163. electron.webFrame[method](...args)
  6164. })
  6165.  
  6166. electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_SYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => {
  6167. const result = electron.webFrame[method](...args)
  6168. event.sender.send(`ELECTRON_INTERNAL_BROWSER_SYNC_WEB_FRAME_RESPONSE_${requestId}`, result)
  6169. })
  6170.  
  6171. electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => {
  6172. const responseCallback = function (result) {
  6173. resolvePromise(result)
  6174. .then((resolvedResult) => {
  6175. event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult)
  6176. })
  6177. .catch((resolvedError) => {
  6178. event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedError)
  6179. })
  6180. }
  6181. args.push(responseCallback)
  6182. electron.webFrame[method](...args)
  6183. })
  6184.  
  6185. // Process command line arguments.
  6186. let nodeIntegration = 'false'
  6187. let preloadScript = null
  6188. let isBackgroundPage = false
  6189. let appPath = null
  6190. for (let arg of process.argv) {
  6191. if (arg.indexOf('--guest-instance-id=') === 0) {
  6192. // This is a guest web view.
  6193. process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
  6194. } else if (arg.indexOf('--opener-id=') === 0) {
  6195. // This is a guest BrowserWindow.
  6196. process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
  6197. } else if (arg.indexOf('--node-integration=') === 0) {
  6198. nodeIntegration = arg.substr(arg.indexOf('=') + 1)
  6199. } else if (arg.indexOf('--preload=') === 0) {
  6200. preloadScript = arg.substr(arg.indexOf('=') + 1)
  6201. } else if (arg === '--background-page') {
  6202. isBackgroundPage = true
  6203. } else if (arg.indexOf('--app-path=') === 0) {
  6204. appPath = arg.substr(arg.indexOf('=') + 1)
  6205. }
  6206. }
  6207.  
  6208. if (window.location.protocol === 'chrome-devtools:') {
  6209. // Override some inspector APIs.
  6210. require('./inspector')
  6211. nodeIntegration = 'false'
  6212. } else if (window.location.protocol === 'chrome-extension:') {
  6213. // Add implementations of chrome API.
  6214. require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
  6215. nodeIntegration = 'false'
  6216. } else if (window.location.protocol === 'chrome:') {
  6217. // Disable node integration for chrome UI scheme.
  6218. nodeIntegration = 'false'
  6219. } else {
  6220. // Override default web functions.
  6221. require('./override')
  6222.  
  6223. // Inject content scripts.
  6224. require('./content-scripts-injector')
  6225.  
  6226. // Load webview tag implementation.
  6227. if (nodeIntegration === 'true' && process.guestInstanceId == null) {
  6228. require('./web-view/web-view')
  6229. require('./web-view/web-view-attributes')
  6230. }
  6231. }
  6232.  
  6233. if (nodeIntegration === 'true') {
  6234. // Export node bindings to global.
  6235. global.require = require
  6236. global.module = module
  6237.  
  6238. // Set the __filename to the path of html file if it is file: protocol.
  6239. if (window.location.protocol === 'file:') {
  6240. var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname
  6241. global.__filename = path.normalize(decodeURIComponent(pathname))
  6242. global.__dirname = path.dirname(global.__filename)
  6243.  
  6244. // Set module's filename so relative require can work as expected.
  6245. module.filename = global.__filename
  6246.  
  6247. // Also search for module under the html file.
  6248. module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
  6249. } else {
  6250. global.__filename = __filename
  6251. global.__dirname = __dirname
  6252.  
  6253. if (appPath) {
  6254. // Search for module under the app directory
  6255. module.paths = module.paths.concat(Module._nodeModulePaths(appPath))
  6256. }
  6257. }
  6258.  
  6259. // Redirect window.onerror to uncaughtException.
  6260. window.onerror = function (message, filename, lineno, colno, error) {
  6261. if (global.process.listeners('uncaughtException').length > 0) {
  6262. global.process.emit('uncaughtException', error)
  6263. return true
  6264. } else {
  6265. return false
  6266. }
  6267. }
  6268. } else {
  6269. // Delete Node's symbols after the Environment has been loaded.
  6270. process.once('loaded', function () {
  6271. delete global.process
  6272. delete global.Buffer
  6273. delete global.setImmediate
  6274. delete global.clearImmediate
  6275. delete global.global
  6276. })
  6277. }
  6278.  
  6279. // Load the script specfied by the "preload" attribute.
  6280. if (preloadScript) {
  6281. try {
  6282. require(preloadScript)
  6283. } catch (error) {
  6284. console.error('Unable to load preload script: ' + preloadScript)
  6285. console.error(error.stack || error.message)
  6286. }
  6287. }
  6288. window.onload = function () {
  6289. // Use menu API to show context menu.
  6290. window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
  6291.  
  6292. // Use dialog API to override file chooser dialog.
  6293. // Note: It will be moved to UI after Chrome 57.
  6294. window.Bindings.createFileSelectorElement = createFileSelectorElement
  6295. }
  6296.  
  6297. window.confirm = function (message, title) {
  6298. const {dialog} = require('electron').remote
  6299. if (title == null) {
  6300. title = ''
  6301. }
  6302. return !dialog.showMessageBox({
  6303. message: message,
  6304. title: title,
  6305. buttons: ['OK', 'Cancel'],
  6306. cancelId: 1
  6307. })
  6308. }
  6309.  
  6310. const convertToMenuTemplate = function (items) {
  6311. return items.map(function (item) {
  6312. const transformed = item.type === 'subMenu' ? {
  6313. type: 'submenu',
  6314. label: item.label,
  6315. enabled: item.enabled,
  6316. submenu: convertToMenuTemplate(item.subItems)
  6317. } : item.type === 'separator' ? {
  6318. type: 'separator'
  6319. } : item.type === 'checkbox' ? {
  6320. type: 'checkbox',
  6321. label: item.label,
  6322. enabled: item.enabled,
  6323. checked: item.checked
  6324. } : {
  6325. type: 'normal',
  6326. label: item.label,
  6327. enabled: item.enabled
  6328. }
  6329.  
  6330. if (item.id != null) {
  6331. transformed.click = function () {
  6332. window.DevToolsAPI.contextMenuItemSelected(item.id)
  6333. return window.DevToolsAPI.contextMenuCleared()
  6334. }
  6335. }
  6336.  
  6337. return transformed
  6338. })
  6339. }
  6340.  
  6341. const createMenu = function (x, y, items) {
  6342. const {remote} = require('electron')
  6343. const {Menu} = remote
  6344.  
  6345. let template = convertToMenuTemplate(items)
  6346. if (useEditMenuItems(x, y, template)) {
  6347. template = getEditMenuItems()
  6348. }
  6349. const menu = Menu.buildFromTemplate(template)
  6350.  
  6351. // The menu is expected to show asynchronously.
  6352. setTimeout(function () {
  6353. menu.popup(remote.getCurrentWindow())
  6354. })
  6355. }
  6356.  
  6357. const useEditMenuItems = function (x, y, items) {
  6358. return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
  6359. return element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || element.isContentEditable
  6360. })
  6361. }
  6362.  
  6363. const getEditMenuItems = function () {
  6364. return [
  6365. {
  6366. role: 'undo'
  6367. },
  6368. {
  6369. role: 'redo'
  6370. },
  6371. {
  6372. type: 'separator'
  6373. },
  6374. {
  6375. role: 'cut'
  6376. },
  6377. {
  6378. role: 'copy'
  6379. },
  6380. {
  6381. role: 'paste'
  6382. },
  6383. {
  6384. role: 'pasteandmatchstyle'
  6385. },
  6386. {
  6387. role: 'delete'
  6388. },
  6389. {
  6390. role: 'selectall'
  6391. }
  6392. ]
  6393. }
  6394.  
  6395. const showFileChooserDialog = function (callback) {
  6396. const {dialog} = require('electron').remote
  6397. const files = dialog.showOpenDialog({})
  6398. if (files != null) {
  6399. callback(pathToHtml5FileObject(files[0]))
  6400. }
  6401. }
  6402.  
  6403. const pathToHtml5FileObject = function (path) {
  6404. const fs = require('fs')
  6405. const blob = new Blob([fs.readFileSync(path)])
  6406. blob.name = path
  6407. return blob
  6408. }
  6409.  
  6410. const createFileSelectorElement = function (callback) {
  6411. const fileSelectorElement = document.createElement('span')
  6412. fileSelectorElement.style.display = 'none'
  6413. fileSelectorElement.click = showFileChooserDialog.bind(this, callback)
  6414. return fileSelectorElement
  6415. }
  6416. 'use strict'
  6417.  
  6418. const {ipcRenderer} = require('electron')
  6419.  
  6420. const {guestInstanceId, openerId} = process
  6421. const hiddenPage = process.argv.includes('--hidden-page')
  6422.  
  6423. require('./window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage)
  6424. 'use strict'
  6425.  
  6426. const {ipcRenderer, webFrame} = require('electron')
  6427.  
  6428. let requestId = 0
  6429.  
  6430. const WEB_VIEW_EVENTS = {
  6431. 'load-commit': ['url', 'isMainFrame'],
  6432. 'did-attach': [],
  6433. 'did-finish-load': [],
  6434. 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame'],
  6435. 'did-frame-finish-load': ['isMainFrame'],
  6436. 'did-start-loading': [],
  6437. 'did-stop-loading': [],
  6438. 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers', 'resourceType'],
  6439. 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'],
  6440. 'dom-ready': [],
  6441. 'console-message': ['level', 'message', 'line', 'sourceId'],
  6442. 'context-menu': ['params'],
  6443. 'devtools-opened': [],
  6444. 'devtools-closed': [],
  6445. 'devtools-focused': [],
  6446. 'new-window': ['url', 'frameName', 'disposition', 'options'],
  6447. 'will-navigate': ['url'],
  6448. 'did-navigate': ['url'],
  6449. 'did-navigate-in-page': ['url', 'isMainFrame'],
  6450. 'close': [],
  6451. 'crashed': [],
  6452. 'gpu-crashed': [],
  6453. 'plugin-crashed': ['name', 'version'],
  6454. 'destroyed': [],
  6455. 'page-title-updated': ['title', 'explicitSet'],
  6456. 'page-favicon-updated': ['favicons'],
  6457. 'enter-html-full-screen': [],
  6458. 'leave-html-full-screen': [],
  6459. 'media-started-playing': [],
  6460. 'media-paused': [],
  6461. 'found-in-page': ['result'],
  6462. 'did-change-theme-color': ['themeColor'],
  6463. 'update-target-url': ['url']
  6464. }
  6465.  
  6466. const DEPRECATED_EVENTS = {
  6467. 'page-title-updated': 'page-title-set'
  6468. }
  6469.  
  6470. const dispatchEvent = function (webView, eventName, eventKey, ...args) {
  6471. if (DEPRECATED_EVENTS[eventName] != null) {
  6472. dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args)
  6473. }
  6474. const domEvent = new Event(eventName)
  6475. WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => {
  6476. domEvent[prop] = args[index]
  6477. })
  6478. webView.dispatchEvent(domEvent)
  6479. if (eventName === 'load-commit') {
  6480. webView.onLoadCommit(domEvent)
  6481. }
  6482. }
  6483.  
  6484. module.exports = {
  6485. registerEvents: function (webView, viewInstanceId) {
  6486. ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
  6487. webFrame.detachGuest(webView.internalInstanceId)
  6488. webView.guestInstanceId = undefined
  6489. webView.reset()
  6490. const domEvent = new Event('destroyed')
  6491. webView.dispatchEvent(domEvent)
  6492. })
  6493.  
  6494. ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
  6495. dispatchEvent(webView, eventName, eventName, ...args)
  6496. })
  6497.  
  6498. ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
  6499. const domEvent = new Event('ipc-message')
  6500. domEvent.channel = channel
  6501. domEvent.args = args
  6502. webView.dispatchEvent(domEvent)
  6503. })
  6504.  
  6505. ipcRenderer.on(`ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-${viewInstanceId}`, function (event, ...args) {
  6506. const domEvent = new Event('size-changed')
  6507. const props = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']
  6508. for (let i = 0; i < props.length; i++) {
  6509. const prop = props[i]
  6510. domEvent[prop] = args[i]
  6511. }
  6512. webView.onSizeChanged(domEvent)
  6513. })
  6514. },
  6515. deregisterEvents: function (viewInstanceId) {
  6516. ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`)
  6517. ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`)
  6518. ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`)
  6519. ipcRenderer.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-${viewInstanceId}`)
  6520. },
  6521. createGuest: function (params, callback) {
  6522. requestId++
  6523. ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId)
  6524. ipcRenderer.once(`ELECTRON_RESPONSE_${requestId}`, callback)
  6525. },
  6526. attachGuest: function (elementInstanceId, guestInstanceId, params) {
  6527. ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params)
  6528. webFrame.attachGuest(elementInstanceId)
  6529. },
  6530. destroyGuest: function (guestInstanceId) {
  6531. ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId)
  6532. },
  6533. setSize: function (guestInstanceId, params) {
  6534. ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params)
  6535. }
  6536. }
  6537. 'use strict'
  6538.  
  6539. const WebViewImpl = require('./web-view')
  6540. const guestViewInternal = require('./guest-view-internal')
  6541. const webViewConstants = require('./web-view-constants')
  6542. const {remote} = require('electron')
  6543.  
  6544. // Helper function to resolve url set in attribute.
  6545. const a = document.createElement('a')
  6546.  
  6547. const resolveURL = function (url) {
  6548. if (url === '') return ''
  6549. a.href = url
  6550. return a.href
  6551. }
  6552.  
  6553. // Attribute objects.
  6554. // Default implementation of a WebView attribute.
  6555. class WebViewAttribute {
  6556. constructor (name, webViewImpl) {
  6557. this.name = name
  6558. this.value = webViewImpl.webviewNode[name] || ''
  6559. this.webViewImpl = webViewImpl
  6560. this.ignoreMutation = false
  6561. this.defineProperty()
  6562. }
  6563.  
  6564. // Retrieves and returns the attribute's value.
  6565. getValue () {
  6566. return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value
  6567. }
  6568.  
  6569. // Sets the attribute's value.
  6570. setValue (value) {
  6571. this.webViewImpl.webviewNode.setAttribute(this.name, value || '')
  6572. }
  6573.  
  6574. // Changes the attribute's value without triggering its mutation handler.
  6575. setValueIgnoreMutation (value) {
  6576. this.ignoreMutation = true
  6577. this.setValue(value)
  6578. this.ignoreMutation = false
  6579. }
  6580.  
  6581. // Defines this attribute as a property on the webview node.
  6582. defineProperty () {
  6583. return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
  6584. get: () => {
  6585. return this.getValue()
  6586. },
  6587. set: (value) => {
  6588. return this.setValue(value)
  6589. },
  6590. enumerable: true
  6591. })
  6592. }
  6593.  
  6594. // Called when the attribute's value changes.
  6595. handleMutation () {}
  6596. }
  6597.  
  6598. // An attribute that is treated as a Boolean.
  6599. class BooleanAttribute extends WebViewAttribute {
  6600. getValue () {
  6601. return this.webViewImpl.webviewNode.hasAttribute(this.name)
  6602. }
  6603.  
  6604. setValue (value) {
  6605. if (value) {
  6606. this.webViewImpl.webviewNode.setAttribute(this.name, '')
  6607. } else {
  6608. this.webViewImpl.webviewNode.removeAttribute(this.name)
  6609. }
  6610. }
  6611. }
  6612.  
  6613. // Attribute used to define the demension limits of autosizing.
  6614. class AutosizeDimensionAttribute extends WebViewAttribute {
  6615. getValue () {
  6616. return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0
  6617. }
  6618.  
  6619. handleMutation () {
  6620. if (!this.webViewImpl.guestInstanceId) {
  6621. return
  6622. }
  6623. guestViewInternal.setSize(this.webViewImpl.guestInstanceId, {
  6624. enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
  6625. min: {
  6626. width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0),
  6627. height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0)
  6628. },
  6629. max: {
  6630. width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0),
  6631. height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0)
  6632. }
  6633. })
  6634. }
  6635. }
  6636.  
  6637. // Attribute that specifies whether the webview should be autosized.
  6638. class AutosizeAttribute extends BooleanAttribute {
  6639. constructor (webViewImpl) {
  6640. super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl)
  6641. }
  6642. }
  6643.  
  6644. AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation
  6645.  
  6646. // Attribute representing the state of the storage partition.
  6647. class PartitionAttribute extends WebViewAttribute {
  6648. constructor (webViewImpl) {
  6649. super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl)
  6650. this.validPartitionId = true
  6651. }
  6652.  
  6653. handleMutation (oldValue, newValue) {
  6654. newValue = newValue || ''
  6655.  
  6656. // The partition cannot change if the webview has already navigated.
  6657. if (!this.webViewImpl.beforeFirstNavigation) {
  6658. window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED)
  6659. this.setValueIgnoreMutation(oldValue)
  6660. return
  6661. }
  6662. if (newValue === 'persist:') {
  6663. this.validPartitionId = false
  6664. window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE)
  6665. }
  6666. }
  6667. }
  6668.  
  6669. // An attribute that controls the guest instance this webview is connected to
  6670. class GuestInstanceAttribute extends WebViewAttribute {
  6671. constructor (webViewImpl) {
  6672. super(webViewConstants.ATTRIBUTE_GUESTINSTANCE, webViewImpl)
  6673. }
  6674.  
  6675. // Retrieves and returns the attribute's value.
  6676. getValue () {
  6677. if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  6678. return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name))
  6679. }
  6680. }
  6681.  
  6682. // Sets the attribute's value.
  6683. setValue (value) {
  6684. if (!value) {
  6685. this.webViewImpl.webviewNode.removeAttribute(this.name)
  6686. } else if (!isNaN(value)) {
  6687. this.webViewImpl.webviewNode.setAttribute(this.name, value)
  6688. }
  6689. }
  6690.  
  6691. handleMutation (oldValue, newValue) {
  6692. if (!newValue) {
  6693. this.webViewImpl.reset()
  6694. return
  6695. }
  6696.  
  6697. const intVal = parseInt(newValue)
  6698. if (!isNaN(newValue) && remote.getGuestWebContents(intVal)) {
  6699. this.webViewImpl.attachGuestInstance(intVal)
  6700. } else {
  6701. this.setValueIgnoreMutation(oldValue)
  6702. }
  6703. }
  6704. }
  6705.  
  6706. // Attribute that handles the location and navigation of the webview.
  6707. class SrcAttribute extends WebViewAttribute {
  6708. constructor (webViewImpl) {
  6709. super(webViewConstants.ATTRIBUTE_SRC, webViewImpl)
  6710. this.setupMutationObserver()
  6711. }
  6712.  
  6713. getValue () {
  6714. if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  6715. return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
  6716. } else {
  6717. return this.value
  6718. }
  6719. }
  6720.  
  6721. setValueIgnoreMutation (value) {
  6722. super.setValueIgnoreMutation(value)
  6723.  
  6724. // takeRecords() is needed to clear queued up src mutations. Without it, it
  6725. // is possible for this change to get picked up asyncronously by src's
  6726. // mutation observer |observer|, and then get handled even though we do not
  6727. // want to handle this mutation.
  6728. this.observer.takeRecords()
  6729. }
  6730.  
  6731. handleMutation (oldValue, newValue) {
  6732. // Once we have navigated, we don't allow clearing the src attribute.
  6733. // Once <webview> enters a navigated state, it cannot return to a
  6734. // placeholder state.
  6735. if (!newValue && oldValue) {
  6736. // src attribute changes normally initiate a navigation. We suppress
  6737. // the next src attribute handler call to avoid reloading the page
  6738. // on every guest-initiated navigation.
  6739. this.setValueIgnoreMutation(oldValue)
  6740. return
  6741. }
  6742. this.parse()
  6743. }
  6744.  
  6745. // The purpose of this mutation observer is to catch assignment to the src
  6746. // attribute without any changes to its value. This is useful in the case
  6747. // where the webview guest has crashed and navigating to the same address
  6748. // spawns off a new process.
  6749. setupMutationObserver () {
  6750. this.observer = new MutationObserver((mutations) => {
  6751. for (const mutation of mutations) {
  6752. const {oldValue} = mutation
  6753. const newValue = this.getValue()
  6754. if (oldValue !== newValue) {
  6755. return
  6756. }
  6757. this.handleMutation(oldValue, newValue)
  6758. }
  6759. })
  6760. const params = {
  6761. attributes: true,
  6762. attributeOldValue: true,
  6763. attributeFilter: [this.name]
  6764. }
  6765. this.observer.observe(this.webViewImpl.webviewNode, params)
  6766. }
  6767.  
  6768. parse () {
  6769. if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) {
  6770. return
  6771. }
  6772. if (this.webViewImpl.guestInstanceId == null) {
  6773. if (this.webViewImpl.beforeFirstNavigation) {
  6774. this.webViewImpl.beforeFirstNavigation = false
  6775. this.webViewImpl.createGuest()
  6776. }
  6777. return
  6778. }
  6779.  
  6780. // Navigate to |this.src|.
  6781. const opts = {}
  6782. const httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()
  6783. if (httpreferrer) {
  6784. opts.httpReferrer = httpreferrer
  6785. }
  6786. const useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue()
  6787. if (useragent) {
  6788. opts.userAgent = useragent
  6789. }
  6790. const guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId)
  6791. guestContents.loadURL(this.getValue(), opts)
  6792. }
  6793. }
  6794.  
  6795. // Attribute specifies HTTP referrer.
  6796. class HttpReferrerAttribute extends WebViewAttribute {
  6797. constructor (webViewImpl) {
  6798. super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl)
  6799. }
  6800. }
  6801.  
  6802. // Attribute specifies user agent
  6803. class UserAgentAttribute extends WebViewAttribute {
  6804. constructor (webViewImpl) {
  6805. super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl)
  6806. }
  6807. }
  6808.  
  6809. // Attribute that set preload script.
  6810. class PreloadAttribute extends WebViewAttribute {
  6811. constructor (webViewImpl) {
  6812. super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl)
  6813. }
  6814.  
  6815. getValue () {
  6816. if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
  6817. return this.value
  6818. }
  6819. let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
  6820. const protocol = preload.substr(0, 5)
  6821. if (protocol !== 'file:') {
  6822. console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE)
  6823. preload = ''
  6824. }
  6825. return preload
  6826. }
  6827. }
  6828.  
  6829. // Attribute that specifies the blink features to be enabled.
  6830. class BlinkFeaturesAttribute extends WebViewAttribute {
  6831. constructor (webViewImpl) {
  6832. super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl)
  6833. }
  6834. }
  6835.  
  6836. // Attribute that specifies the blink features to be disabled.
  6837. class DisableBlinkFeaturesAttribute extends WebViewAttribute {
  6838. constructor (webViewImpl) {
  6839. super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
  6840. }
  6841. }
  6842.  
  6843. // Attribute that specifies the web preferences to be enabled.
  6844. class WebPreferencesAttribute extends WebViewAttribute {
  6845. constructor (webViewImpl) {
  6846. super(webViewConstants.ATTRIBUTE_WEBPREFERENCES, webViewImpl)
  6847. }
  6848. }
  6849.  
  6850. // Sets up all of the webview attributes.
  6851. WebViewImpl.prototype.setupWebViewAttributes = function () {
  6852. this.attributes = {}
  6853. this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this)
  6854. this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this)
  6855. this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this)
  6856. this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
  6857. this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
  6858. this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this)
  6859. this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this)
  6860. this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this)
  6861. this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this)
  6862. this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
  6863. this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
  6864. this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
  6865. this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this)
  6866. this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE, this)
  6867. this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this)
  6868.  
  6869. const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]
  6870. autosizeAttributes.forEach((attribute) => {
  6871. this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)
  6872. })
  6873. }
  6874. module.exports = {
  6875. // Attributes.
  6876. ATTRIBUTE_AUTOSIZE: 'autosize',
  6877. ATTRIBUTE_MAXHEIGHT: 'maxheight',
  6878. ATTRIBUTE_MAXWIDTH: 'maxwidth',
  6879. ATTRIBUTE_MINHEIGHT: 'minheight',
  6880. ATTRIBUTE_MINWIDTH: 'minwidth',
  6881. ATTRIBUTE_NAME: 'name',
  6882. ATTRIBUTE_PARTITION: 'partition',
  6883. ATTRIBUTE_SRC: 'src',
  6884. ATTRIBUTE_HTTPREFERRER: 'httpreferrer',
  6885. ATTRIBUTE_NODEINTEGRATION: 'nodeintegration',
  6886. ATTRIBUTE_PLUGINS: 'plugins',
  6887. ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity',
  6888. ATTRIBUTE_ALLOWPOPUPS: 'allowpopups',
  6889. ATTRIBUTE_PRELOAD: 'preload',
  6890. ATTRIBUTE_USERAGENT: 'useragent',
  6891. ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
  6892. ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
  6893. ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
  6894. ATTRIBUTE_DISABLEGUESTRESIZE: 'disableguestresize',
  6895. ATTRIBUTE_WEBPREFERENCES: 'webpreferences',
  6896.  
  6897. // Internal attribute.
  6898. ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',
  6899.  
  6900. // Error messages.
  6901. ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.',
  6902. ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' + 'Script cannot be injected into content until the page has loaded.',
  6903. ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.',
  6904. ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.'
  6905. }
  6906. 'use strict'
  6907.  
  6908. const {ipcRenderer, remote, webFrame} = require('electron')
  6909.  
  6910. const v8Util = process.atomBinding('v8_util')
  6911. const guestViewInternal = require('./guest-view-internal')
  6912. const webViewConstants = require('./web-view-constants')
  6913.  
  6914. const hasProp = {}.hasOwnProperty
  6915.  
  6916. // ID generator.
  6917. let nextId = 0
  6918.  
  6919. const getNextId = function () {
  6920. return ++nextId
  6921. }
  6922.  
  6923. // Represents the internal state of the WebView node.
  6924. class WebViewImpl {
  6925. constructor (webviewNode) {
  6926. this.webviewNode = webviewNode
  6927. v8Util.setHiddenValue(this.webviewNode, 'internal', this)
  6928. this.attached = false
  6929. this.elementAttached = false
  6930. this.beforeFirstNavigation = true
  6931.  
  6932. // on* Event handlers.
  6933. this.on = {}
  6934. this.browserPluginNode = this.createBrowserPluginNode()
  6935. const shadowRoot = this.webviewNode.createShadowRoot()
  6936. shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>'
  6937. this.setupWebViewAttributes()
  6938. this.setupFocusPropagation()
  6939. this.viewInstanceId = getNextId()
  6940. shadowRoot.appendChild(this.browserPluginNode)
  6941. }
  6942.  
  6943. createBrowserPluginNode () {
  6944. // We create BrowserPlugin as a custom element in order to observe changes
  6945. // to attributes synchronously.
  6946. const browserPluginNode = new WebViewImpl.BrowserPlugin()
  6947. v8Util.setHiddenValue(browserPluginNode, 'internal', this)
  6948. return browserPluginNode
  6949. }
  6950.  
  6951. // Resets some state upon reattaching <webview> element to the DOM.
  6952. reset () {
  6953. // If guestInstanceId is defined then the <webview> has navigated and has
  6954. // already picked up a partition ID. Thus, we need to reset the initialization
  6955. // state. However, it may be the case that beforeFirstNavigation is false BUT
  6956. // guestInstanceId has yet to be initialized. This means that we have not
  6957. // heard back from createGuest yet. We will not reset the flag in this case so
  6958. // that we don't end up allocating a second guest.
  6959. if (this.guestInstanceId) {
  6960. guestViewInternal.destroyGuest(this.guestInstanceId)
  6961. this.guestInstanceId = void 0
  6962. }
  6963.  
  6964. this.webContents = null
  6965. this.beforeFirstNavigation = true
  6966. this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
  6967.  
  6968. // Set guestinstance last since this can trigger the attachedCallback to fire
  6969. // when moving the webview using element.replaceChild
  6970. this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].setValueIgnoreMutation(undefined)
  6971. }
  6972.  
  6973. // Sets the <webview>.request property.
  6974. setRequestPropertyOnWebViewNode (request) {
  6975. Object.defineProperty(this.webviewNode, 'request', {
  6976. value: request,
  6977. enumerable: true
  6978. })
  6979. }
  6980.  
  6981. setupFocusPropagation () {
  6982. if (!this.webviewNode.hasAttribute('tabIndex')) {
  6983. // <webview> needs a tabIndex in order to be focusable.
  6984. // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
  6985. // to allow <webview> to be focusable.
  6986. // See http://crbug.com/231664.
  6987. this.webviewNode.setAttribute('tabIndex', -1)
  6988. }
  6989.  
  6990. // Focus the BrowserPlugin when the <webview> takes focus.
  6991. this.webviewNode.addEventListener('focus', () => {
  6992. this.browserPluginNode.focus()
  6993. })
  6994.  
  6995. // Blur the BrowserPlugin when the <webview> loses focus.
  6996. this.webviewNode.addEventListener('blur', () => {
  6997. this.browserPluginNode.blur()
  6998. })
  6999. }
  7000.  
  7001. // This observer monitors mutations to attributes of the <webview> and
  7002. // updates the BrowserPlugin properties accordingly. In turn, updating
  7003. // a BrowserPlugin property will update the corresponding BrowserPlugin
  7004. // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
  7005. // details.
  7006. handleWebviewAttributeMutation (attributeName, oldValue, newValue) {
  7007. if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) {
  7008. return
  7009. }
  7010.  
  7011. // Let the changed attribute handle its own mutation
  7012. this.attributes[attributeName].handleMutation(oldValue, newValue)
  7013. }
  7014.  
  7015. handleBrowserPluginAttributeMutation (attributeName, oldValue, newValue) {
  7016. if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) {
  7017. this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID)
  7018. this.internalInstanceId = parseInt(newValue)
  7019.  
  7020. // Track when the element resizes using the element resize callback.
  7021. webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this))
  7022. if (this.guestInstanceId) {
  7023. guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams())
  7024. }
  7025. }
  7026. }
  7027.  
  7028. onSizeChanged (webViewEvent) {
  7029. const {newHeight, newWidth} = webViewEvent
  7030. const node = this.webviewNode
  7031. const width = node.offsetWidth
  7032.  
  7033. // Check the current bounds to make sure we do not resize <webview>
  7034. // outside of current constraints.
  7035. const maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width
  7036. const maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width
  7037. let minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width
  7038. let minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width
  7039. minWidth = Math.min(minWidth, maxWidth)
  7040. minHeight = Math.min(minHeight, maxHeight)
  7041. if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) {
  7042. node.style.width = `${newWidth}px`
  7043. node.style.height = `${newHeight}px`
  7044.  
  7045. // Only fire the DOM event if the size of the <webview> has actually
  7046. // changed.
  7047. this.dispatchEvent(webViewEvent)
  7048. }
  7049. }
  7050.  
  7051. onElementResize (newSize) {
  7052. // Dispatch the 'resize' event.
  7053. const resizeEvent = new Event('resize', {
  7054. bubbles: true
  7055. })
  7056.  
  7057. // Using client size values, because when a webview is transformed `newSize`
  7058. // is incorrect
  7059. newSize.width = this.webviewNode.clientWidth
  7060. newSize.height = this.webviewNode.clientHeight
  7061.  
  7062. resizeEvent.newWidth = newSize.width
  7063. resizeEvent.newHeight = newSize.height
  7064. this.dispatchEvent(resizeEvent)
  7065. if (this.guestInstanceId &&
  7066. !this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE].getValue()) {
  7067. guestViewInternal.setSize(this.guestInstanceId, {
  7068. normal: newSize
  7069. })
  7070. }
  7071. }
  7072.  
  7073. createGuest () {
  7074. return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => {
  7075. this.attachGuestInstance(guestInstanceId)
  7076. })
  7077. }
  7078.  
  7079. dispatchEvent (webViewEvent) {
  7080. this.webviewNode.dispatchEvent(webViewEvent)
  7081. }
  7082.  
  7083. // Adds an 'on<event>' property on the webview, which can be used to set/unset
  7084. // an event handler.
  7085. setupEventProperty (eventName) {
  7086. const propertyName = `on${eventName.toLowerCase()}`
  7087. return Object.defineProperty(this.webviewNode, propertyName, {
  7088. get: () => {
  7089. return this.on[propertyName]
  7090. },
  7091. set: (value) => {
  7092. if (this.on[propertyName]) {
  7093. this.webviewNode.removeEventListener(eventName, this.on[propertyName])
  7094. }
  7095. this.on[propertyName] = value
  7096. if (value) {
  7097. return this.webviewNode.addEventListener(eventName, value)
  7098. }
  7099. },
  7100. enumerable: true
  7101. })
  7102. }
  7103.  
  7104. // Updates state upon loadcommit.
  7105. onLoadCommit (webViewEvent) {
  7106. const oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC)
  7107. const newValue = webViewEvent.url
  7108. if (webViewEvent.isMainFrame && (oldValue !== newValue)) {
  7109. // Touching the src attribute triggers a navigation. To avoid
  7110. // triggering a page reload on every guest-initiated navigation,
  7111. // we do not handle this mutation.
  7112. this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue)
  7113. }
  7114. }
  7115.  
  7116. onAttach (storagePartitionId) {
  7117. return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId)
  7118. }
  7119.  
  7120. buildParams () {
  7121. const params = {
  7122. instanceId: this.viewInstanceId,
  7123. userAgentOverride: this.userAgentOverride
  7124. }
  7125. for (const attributeName in this.attributes) {
  7126. if (hasProp.call(this.attributes, attributeName)) {
  7127. params[attributeName] = this.attributes[attributeName].getValue()
  7128. }
  7129. }
  7130.  
  7131. // When the WebView is not participating in layout (display:none)
  7132. // then getBoundingClientRect() would report a width and height of 0.
  7133. // However, in the case where the WebView has a fixed size we can
  7134. // use that value to initially size the guest so as to avoid a relayout of
  7135. // the on display:block.
  7136. const css = window.getComputedStyle(this.webviewNode, null)
  7137. const elementRect = this.webviewNode.getBoundingClientRect()
  7138. params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width'))
  7139. params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height'))
  7140. return params
  7141. }
  7142.  
  7143. attachGuestInstance (guestInstanceId) {
  7144. this.guestInstanceId = guestInstanceId
  7145. this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].setValueIgnoreMutation(guestInstanceId)
  7146. this.webContents = remote.getGuestWebContents(this.guestInstanceId)
  7147. if (!this.internalInstanceId) {
  7148. return true
  7149. }
  7150. return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams())
  7151. }
  7152. }
  7153.  
  7154. // Registers browser plugin <object> custom element.
  7155. const registerBrowserPluginElement = function () {
  7156. const proto = Object.create(HTMLObjectElement.prototype)
  7157. proto.createdCallback = function () {
  7158. this.setAttribute('type', 'application/browser-plugin')
  7159. this.setAttribute('id', `browser-plugin-${getNextId()}`)
  7160.  
  7161. // The <object> node fills in the <webview> container.
  7162. this.style.flex = '1 1 auto'
  7163. }
  7164. proto.attributeChangedCallback = function (name, oldValue, newValue) {
  7165. const internal = v8Util.getHiddenValue(this, 'internal')
  7166. if (internal) {
  7167. internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue)
  7168. }
  7169. }
  7170. proto.attachedCallback = function () {
  7171. // Load the plugin immediately.
  7172. return this.nonExistentAttribute
  7173. }
  7174. WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', {
  7175. 'extends': 'object',
  7176. prototype: proto
  7177. })
  7178. delete proto.createdCallback
  7179. delete proto.attachedCallback
  7180. delete proto.detachedCallback
  7181. delete proto.attributeChangedCallback
  7182. }
  7183.  
  7184. // Registers <webview> custom element.
  7185. const registerWebViewElement = function () {
  7186. const proto = Object.create(HTMLObjectElement.prototype)
  7187. proto.createdCallback = function () {
  7188. return new WebViewImpl(this)
  7189. }
  7190. proto.attributeChangedCallback = function (name, oldValue, newValue) {
  7191. const internal = v8Util.getHiddenValue(this, 'internal')
  7192. if (internal) {
  7193. internal.handleWebviewAttributeMutation(name, oldValue, newValue)
  7194. }
  7195. }
  7196. proto.detachedCallback = function () {
  7197. const internal = v8Util.getHiddenValue(this, 'internal')
  7198. if (!internal) {
  7199. return
  7200. }
  7201. guestViewInternal.deregisterEvents(internal.viewInstanceId)
  7202. internal.elementAttached = false
  7203. this.internalInstanceId = 0
  7204. internal.reset()
  7205. }
  7206. proto.attachedCallback = function () {
  7207. const internal = v8Util.getHiddenValue(this, 'internal')
  7208. if (!internal) {
  7209. return
  7210. }
  7211. if (!internal.elementAttached) {
  7212. guestViewInternal.registerEvents(internal, internal.viewInstanceId)
  7213. internal.elementAttached = true
  7214. const instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue()
  7215. if (instance) {
  7216. internal.attachGuestInstance(instance)
  7217. } else {
  7218. internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
  7219. }
  7220. }
  7221. }
  7222.  
  7223. // Public-facing API methods.
  7224. const methods = [
  7225. 'getURL',
  7226. 'loadURL',
  7227. 'getTitle',
  7228. 'isLoading',
  7229. 'isLoadingMainFrame',
  7230. 'isWaitingForResponse',
  7231. 'stop',
  7232. 'reload',
  7233. 'reloadIgnoringCache',
  7234. 'canGoBack',
  7235. 'canGoForward',
  7236. 'canGoToOffset',
  7237. 'clearHistory',
  7238. 'goBack',
  7239. 'goForward',
  7240. 'goToIndex',
  7241. 'goToOffset',
  7242. 'isCrashed',
  7243. 'setUserAgent',
  7244. 'getUserAgent',
  7245. 'openDevTools',
  7246. 'closeDevTools',
  7247. 'isDevToolsOpened',
  7248. 'isDevToolsFocused',
  7249. 'inspectElement',
  7250. 'setAudioMuted',
  7251. 'isAudioMuted',
  7252. 'undo',
  7253. 'redo',
  7254. 'cut',
  7255. 'copy',
  7256. 'paste',
  7257. 'pasteAndMatchStyle',
  7258. 'delete',
  7259. 'selectAll',
  7260. 'unselect',
  7261. 'replace',
  7262. 'replaceMisspelling',
  7263. 'findInPage',
  7264. 'stopFindInPage',
  7265. 'getId',
  7266. 'downloadURL',
  7267. 'inspectServiceWorker',
  7268. 'print',
  7269. 'printToPDF',
  7270. 'showDefinitionForSelection',
  7271. 'capturePage',
  7272. 'setZoomFactor',
  7273. 'setZoomLevel',
  7274. 'getZoomLevel',
  7275. 'getZoomFactor'
  7276. ]
  7277. const nonblockMethods = [
  7278. 'insertCSS',
  7279. 'insertText',
  7280. 'send',
  7281. 'sendInputEvent',
  7282. 'setLayoutZoomLevelLimits',
  7283. 'setVisualZoomLevelLimits',
  7284. // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings
  7285. 'setZoomLevelLimits'
  7286. ]
  7287.  
  7288. // Forward proto.foo* method calls to WebViewImpl.foo*.
  7289. const createBlockHandler = function (m) {
  7290. return function (...args) {
  7291. const internal = v8Util.getHiddenValue(this, 'internal')
  7292. if (internal.webContents) {
  7293. return internal.webContents[m](...args)
  7294. } else {
  7295. throw new Error(`Cannot call ${m} because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.`)
  7296. }
  7297. }
  7298. }
  7299. for (const method of methods) {
  7300. proto[method] = createBlockHandler(method)
  7301. }
  7302.  
  7303. const createNonBlockHandler = function (m) {
  7304. return function (...args) {
  7305. const internal = v8Util.getHiddenValue(this, 'internal')
  7306. ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m, ...args)
  7307. }
  7308. }
  7309. for (const method of nonblockMethods) {
  7310. proto[method] = createNonBlockHandler(method)
  7311. }
  7312.  
  7313. proto.executeJavaScript = function (code, hasUserGesture, callback) {
  7314. const internal = v8Util.getHiddenValue(this, 'internal')
  7315. if (typeof hasUserGesture === 'function') {
  7316. callback = hasUserGesture
  7317. hasUserGesture = false
  7318. }
  7319. const requestId = getNextId()
  7320. ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
  7321. ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) {
  7322. if (callback) callback(result)
  7323. })
  7324. }
  7325.  
  7326. // WebContents associated with this webview.
  7327. proto.getWebContents = function () {
  7328. return v8Util.getHiddenValue(this, 'internal').webContents
  7329. }
  7330.  
  7331. window.WebView = webFrame.registerEmbedderCustomElement('webview', {
  7332. prototype: proto
  7333. })
  7334.  
  7335. // Delete the callbacks so developers cannot call them and produce unexpected
  7336. // behavior.
  7337. delete proto.createdCallback
  7338. delete proto.attachedCallback
  7339. delete proto.detachedCallback
  7340. delete proto.attributeChangedCallback
  7341. }
  7342.  
  7343. const useCapture = true
  7344.  
  7345. const listener = function (event) {
  7346. if (document.readyState === 'loading') {
  7347. return
  7348. }
  7349. registerBrowserPluginElement()
  7350. registerWebViewElement()
  7351. window.removeEventListener(event.type, listener, useCapture)
  7352. }
  7353.  
  7354. window.addEventListener('readystatechange', listener, true)
  7355.  
  7356. module.exports = WebViewImpl
  7357. // This file should have no requires since it is used by the isolated context
  7358. // preload bundle. Instead arguments should be passed in for everything it
  7359. // needs.
  7360.  
  7361. // This file implements the following APIs:
  7362. // - window.alert()
  7363. // - window.confirm()
  7364. // - window.history.back()
  7365. // - window.history.forward()
  7366. // - window.history.go()
  7367. // - window.history.length
  7368. // - window.open()
  7369. // - window.opener.blur()
  7370. // - window.opener.close()
  7371. // - window.opener.eval()
  7372. // - window.opener.focus()
  7373. // - window.opener.location
  7374. // - window.opener.print()
  7375. // - window.opener.postMessage()
  7376. // - window.prompt()
  7377. // - document.hidden
  7378. // - document.visibilityState
  7379.  
  7380. 'use strict'
  7381.  
  7382. const {defineProperty} = Object
  7383.  
  7384. // Helper function to resolve relative url.
  7385. const a = window.top.document.createElement('a')
  7386. const resolveURL = function (url) {
  7387. a.href = url
  7388. return a.href
  7389. }
  7390.  
  7391. // Use this method to ensure values expected as strings in the main process
  7392. // are convertible to strings in the renderer process. This ensures exceptions
  7393. // converting values to strings are thrown in this process.
  7394. const toString = (value) => {
  7395. return value != null ? `${value}` : value
  7396. }
  7397.  
  7398. const windowProxies = {}
  7399.  
  7400. const getOrCreateProxy = (ipcRenderer, guestId) => {
  7401. let proxy = windowProxies[guestId]
  7402. if (proxy == null) {
  7403. proxy = new BrowserWindowProxy(ipcRenderer, guestId)
  7404. windowProxies[guestId] = proxy
  7405. }
  7406. return proxy
  7407. }
  7408.  
  7409. const removeProxy = (guestId) => {
  7410. delete windowProxies[guestId]
  7411. }
  7412.  
  7413. function BrowserWindowProxy (ipcRenderer, guestId) {
  7414. this.closed = false
  7415.  
  7416. defineProperty(this, 'location', {
  7417. get: function () {
  7418. return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'getURL')
  7419. },
  7420. set: function (url) {
  7421. url = resolveURL(url)
  7422. return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'loadURL', url)
  7423. }
  7424. })
  7425.  
  7426. ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => {
  7427. removeProxy(guestId)
  7428. this.closed = true
  7429. })
  7430.  
  7431. this.close = () => {
  7432. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId)
  7433. }
  7434.  
  7435. this.focus = () => {
  7436. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus')
  7437. }
  7438.  
  7439. this.blur = () => {
  7440. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur')
  7441. }
  7442.  
  7443. this.print = () => {
  7444. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print')
  7445. }
  7446.  
  7447. this.postMessage = (message, targetOrigin) => {
  7448. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, toString(targetOrigin), window.location.origin)
  7449. }
  7450.  
  7451. this.eval = (...args) => {
  7452. ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'executeJavaScript', ...args)
  7453. }
  7454. }
  7455.  
  7456. // Forward history operations to browser.
  7457. const sendHistoryOperation = function (ipcRenderer, ...args) {
  7458. ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args)
  7459. }
  7460.  
  7461. const getHistoryOperation = function (ipcRenderer, ...args) {
  7462. return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args)
  7463. }
  7464.  
  7465. module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => {
  7466. if (guestInstanceId == null) {
  7467. // Override default window.close.
  7468. window.close = function () {
  7469. ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE')
  7470. }
  7471. }
  7472.  
  7473. // Make the browser window or guest view emit "new-window" event.
  7474. window.open = function (url, frameName, features) {
  7475. if (url != null && url !== '') {
  7476. url = resolveURL(url)
  7477. }
  7478. const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features))
  7479. if (guestId != null) {
  7480. return getOrCreateProxy(ipcRenderer, guestId)
  7481. } else {
  7482. return null
  7483. }
  7484. }
  7485.  
  7486. window.alert = function (message, title) {
  7487. ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', toString(message), toString(title))
  7488. }
  7489.  
  7490. window.confirm = function (message, title) {
  7491. return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', toString(message), toString(title))
  7492. }
  7493.  
  7494. // But we do not support prompt().
  7495. window.prompt = function () {
  7496. throw new Error('prompt() is and will not be supported.')
  7497. }
  7498.  
  7499. if (openerId != null) {
  7500. window.opener = getOrCreateProxy(ipcRenderer, openerId)
  7501. }
  7502.  
  7503. ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) {
  7504. // Manually dispatch event instead of using postMessage because we also need to
  7505. // set event.source.
  7506. event = document.createEvent('Event')
  7507. event.initEvent('message', false, false)
  7508. event.data = message
  7509. event.origin = sourceOrigin
  7510. event.source = getOrCreateProxy(ipcRenderer, sourceId)
  7511. window.dispatchEvent(event)
  7512. })
  7513.  
  7514. window.history.back = function () {
  7515. sendHistoryOperation(ipcRenderer, 'goBack')
  7516. }
  7517.  
  7518. window.history.forward = function () {
  7519. sendHistoryOperation(ipcRenderer, 'goForward')
  7520. }
  7521.  
  7522. window.history.go = function (offset) {
  7523. sendHistoryOperation(ipcRenderer, 'goToOffset', +offset)
  7524. }
  7525.  
  7526. defineProperty(window.history, 'length', {
  7527. get: function () {
  7528. return getHistoryOperation(ipcRenderer, 'length')
  7529. }
  7530. })
  7531.  
  7532. // The initial visibilityState.
  7533. let cachedVisibilityState = hiddenPage ? 'hidden' : 'visible'
  7534.  
  7535. // Subscribe to visibilityState changes.
  7536. ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) {
  7537. if (cachedVisibilityState !== visibilityState) {
  7538. cachedVisibilityState = visibilityState
  7539. document.dispatchEvent(new Event('visibilitychange'))
  7540. }
  7541. })
  7542.  
  7543. // Make document.hidden and document.visibilityState return the correct value.
  7544. defineProperty(document, 'hidden', {
  7545. get: function () {
  7546. return cachedVisibilityState !== 'visible'
  7547. }
  7548. })
  7549.  
  7550. defineProperty(document, 'visibilityState', {
  7551. get: function () {
  7552. return cachedVisibilityState
  7553. }
  7554. })
  7555. }
  7556. 'use strict'
  7557.  
  7558. const path = require('path')
  7559. const Module = require('module')
  7560.  
  7561. // We modified the original process.argv to let node.js load the
  7562. // init.js, we need to restore it here.
  7563. process.argv.splice(1, 1)
  7564.  
  7565. // Clear search paths.
  7566. require('../common/reset-search-paths')
  7567.  
  7568. // Import common settings.
  7569. require('../common/init')
  7570.  
  7571. // Expose public APIs.
  7572. Module.globalPaths.push(path.join(__dirname, 'api', 'exports'))
  7573.  
  7574. // Export node bindings to global.
  7575. global.require = require
  7576. global.module = module
  7577.  
  7578. // Set the __filename to the path of html file if it is file: protocol.
  7579. if (self.location.protocol === 'file:') {
  7580. let pathname = process.platform === 'win32' && self.location.pathname[0] === '/' ? self.location.pathname.substr(1) : self.location.pathname
  7581. global.__filename = path.normalize(decodeURIComponent(pathname))
  7582. global.__dirname = path.dirname(global.__filename)
  7583.  
  7584. // Set module's filename so relative require can work as expected.
  7585. module.filename = global.__filename
  7586.  
  7587. // Also search for module under the html file.
  7588. module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
  7589. } else {
  7590. global.__filename = __filename
  7591. global.__dirname = __dirname
  7592. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement