main.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // Deprecated APIs are still supported and should be tested.
  2. process.throwDeprecation = false
  3. const electron = require('electron')
  4. const { app, BrowserWindow, crashReporter, dialog, ipcMain, protocol, webContents } = electron
  5. const fs = require('fs')
  6. const path = require('path')
  7. const util = require('util')
  8. const v8 = require('v8')
  9. const argv = require('yargs')
  10. .boolean('ci')
  11. .string('g').alias('g', 'grep')
  12. .boolean('i').alias('i', 'invert')
  13. .argv
  14. let window = null
  15. // will be used by crash-reporter spec.
  16. process.port = 0
  17. v8.setFlagsFromString('--expose_gc')
  18. app.commandLine.appendSwitch('js-flags', '--expose_gc')
  19. app.commandLine.appendSwitch('ignore-certificate-errors')
  20. app.commandLine.appendSwitch('disable-renderer-backgrounding')
  21. // Disable security warnings (the security warnings test will enable them)
  22. process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true
  23. // Accessing stdout in the main process will result in the process.stdout
  24. // throwing UnknownSystemError in renderer process sometimes. This line makes
  25. // sure we can reproduce it in renderer process.
  26. // eslint-disable-next-line
  27. process.stdout
  28. // Adding a variable for sandbox process.env test validation
  29. process.env.sandboxmain = ''
  30. // Access console to reproduce #3482.
  31. // eslint-disable-next-line
  32. console
  33. ipcMain.on('message', function (event, ...args) {
  34. event.sender.send('message', ...args)
  35. })
  36. // Set productName so getUploadedReports() uses the right directory in specs
  37. if (process.platform !== 'darwin') {
  38. crashReporter.productName = 'Zombies'
  39. }
  40. // Write output to file if OUTPUT_TO_FILE is defined.
  41. const outputToFile = process.env.OUTPUT_TO_FILE
  42. const print = function (_, args) {
  43. const output = util.format.apply(null, args)
  44. if (outputToFile) {
  45. fs.appendFileSync(outputToFile, output + '\n')
  46. } else {
  47. console.error(output)
  48. }
  49. }
  50. ipcMain.on('console.log', print)
  51. ipcMain.on('console.error', print)
  52. ipcMain.on('process.exit', function (event, code) {
  53. process.exit(code)
  54. })
  55. ipcMain.on('eval', function (event, script) {
  56. event.returnValue = eval(script) // eslint-disable-line
  57. })
  58. ipcMain.on('echo', function (event, msg) {
  59. event.returnValue = msg
  60. })
  61. global.setTimeoutPromisified = util.promisify(setTimeout)
  62. global.permissionChecks = {
  63. allow: () => electron.session.defaultSession.setPermissionCheckHandler(null),
  64. reject: () => electron.session.defaultSession.setPermissionCheckHandler(() => false)
  65. }
  66. global.isCi = !!argv.ci
  67. if (global.isCi) {
  68. process.removeAllListeners('uncaughtException')
  69. process.on('uncaughtException', function (error) {
  70. console.error(error, error.stack)
  71. process.exit(1)
  72. })
  73. }
  74. global.nativeModulesEnabled = !process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS
  75. // Register app as standard scheme.
  76. global.standardScheme = 'app'
  77. global.zoomScheme = 'zoom'
  78. protocol.registerSchemesAsPrivileged([
  79. { scheme: global.standardScheme, privileges: { standard: true, secure: true } },
  80. { scheme: global.zoomScheme, privileges: { standard: true, secure: true } },
  81. { scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } },
  82. { scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } },
  83. { scheme: 'no-cors', privileges: { supportFetchAPI: true } },
  84. { scheme: 'no-fetch', privileges: { corsEnabled: true } }
  85. ])
  86. app.on('window-all-closed', function () {
  87. app.quit()
  88. })
  89. app.on('gpu-process-crashed', (event, killed) => {
  90. console.log(`GPU process crashed (killed=${killed})`)
  91. })
  92. app.on('renderer-process-crashed', (event, contents, killed) => {
  93. console.log(`webContents ${contents.id} crashed: ${contents.getURL()} (killed=${killed})`)
  94. })
  95. app.on('ready', function () {
  96. // Test if using protocol module would crash.
  97. electron.protocol.registerStringProtocol('test-if-crashes', function () {})
  98. // Send auto updater errors to window to be verified in specs
  99. electron.autoUpdater.on('error', function (error) {
  100. window.send('auto-updater-error', error.message)
  101. })
  102. window = new BrowserWindow({
  103. title: 'Electron Tests',
  104. show: !global.isCi,
  105. width: 800,
  106. height: 600,
  107. webPreferences: {
  108. backgroundThrottling: false,
  109. nodeIntegration: true,
  110. webviewTag: true
  111. }
  112. })
  113. window.loadFile('static/index.html', {
  114. query: {
  115. grep: argv.grep,
  116. invert: argv.invert ? 'true' : ''
  117. }
  118. })
  119. window.on('unresponsive', function () {
  120. const chosen = dialog.showMessageBox(window, {
  121. type: 'warning',
  122. buttons: ['Close', 'Keep Waiting'],
  123. message: 'Window is not responsing',
  124. detail: 'The window is not responding. Would you like to force close it or just keep waiting?'
  125. })
  126. if (chosen === 0) window.destroy()
  127. })
  128. window.webContents.on('crashed', function () {
  129. console.error('Renderer process crashed')
  130. process.exit(1)
  131. })
  132. // For session's download test, listen 'will-download' event in browser, and
  133. // reply the result to renderer for verifying
  134. const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
  135. ipcMain.on('set-download-option', function (event, needCancel, preventDefault, filePath = downloadFilePath, dialogOptions = {}) {
  136. window.webContents.session.once('will-download', function (e, item) {
  137. window.webContents.send('download-created',
  138. item.getState(),
  139. item.getURLChain(),
  140. item.getMimeType(),
  141. item.getReceivedBytes(),
  142. item.getTotalBytes(),
  143. item.getFilename(),
  144. item.getSavePath())
  145. if (preventDefault) {
  146. e.preventDefault()
  147. const url = item.getURL()
  148. const filename = item.getFilename()
  149. setImmediate(function () {
  150. try {
  151. item.getURL()
  152. } catch (err) {
  153. window.webContents.send('download-error', url, filename, err.message)
  154. }
  155. })
  156. } else {
  157. if (item.getState() === 'interrupted' && !needCancel) {
  158. item.resume()
  159. } else {
  160. item.setSavePath(filePath)
  161. item.setSaveDialogOptions(dialogOptions)
  162. }
  163. item.on('done', function (e, state) {
  164. window.webContents.send('download-done',
  165. state,
  166. item.getURL(),
  167. item.getMimeType(),
  168. item.getReceivedBytes(),
  169. item.getTotalBytes(),
  170. item.getContentDisposition(),
  171. item.getFilename(),
  172. item.getSavePath(),
  173. item.getSaveDialogOptions(),
  174. item.getURLChain(),
  175. item.getLastModifiedTime(),
  176. item.getETag())
  177. })
  178. if (needCancel) item.cancel()
  179. }
  180. })
  181. event.returnValue = 'done'
  182. })
  183. ipcMain.on('prevent-next-input-event', (event, key, id) => {
  184. webContents.fromId(id).once('before-input-event', (event, input) => {
  185. if (key === input.key) event.preventDefault()
  186. })
  187. event.returnValue = null
  188. })
  189. ipcMain.on('executeJavaScript', function (event, code, hasCallback) {
  190. let promise
  191. if (hasCallback) {
  192. promise = window.webContents.executeJavaScript(code, (result) => {
  193. window.webContents.send('executeJavaScript-response', result)
  194. })
  195. } else {
  196. promise = window.webContents.executeJavaScript(code)
  197. }
  198. promise.then((result) => {
  199. window.webContents.send('executeJavaScript-promise-response', result)
  200. }).catch((error) => {
  201. window.webContents.send('executeJavaScript-promise-error', error)
  202. if (error && error.name) {
  203. window.webContents.send('executeJavaScript-promise-error-name', error.name)
  204. }
  205. })
  206. if (!hasCallback) {
  207. event.returnValue = 'success'
  208. }
  209. })
  210. })
  211. ipcMain.on('handle-next-ipc-message-sync', function (event, returnValue) {
  212. event.sender.once('ipc-message-sync', (event, channel, args) => {
  213. event.returnValue = returnValue
  214. })
  215. })
  216. for (const eventName of [
  217. 'remote-require',
  218. 'remote-get-global',
  219. 'remote-get-builtin'
  220. ]) {
  221. ipcMain.on(`handle-next-${eventName}`, function (event, valuesMap = {}) {
  222. event.sender.once(eventName, (event, name) => {
  223. if (valuesMap.hasOwnProperty(name)) {
  224. event.returnValue = valuesMap[name]
  225. } else {
  226. event.preventDefault()
  227. }
  228. })
  229. })
  230. }
  231. for (const eventName of [
  232. 'desktop-capturer-get-sources',
  233. 'remote-get-current-window',
  234. 'remote-get-current-web-contents',
  235. 'remote-get-guest-web-contents'
  236. ]) {
  237. ipcMain.on(`handle-next-${eventName}`, function (event, returnValue) {
  238. event.sender.once(eventName, (event) => {
  239. if (returnValue) {
  240. event.returnValue = returnValue
  241. } else {
  242. event.preventDefault()
  243. }
  244. })
  245. })
  246. }
  247. ipcMain.on('set-client-certificate-option', function (event, skip) {
  248. app.once('select-client-certificate', function (event, webContents, url, list, callback) {
  249. event.preventDefault()
  250. if (skip) {
  251. callback()
  252. } else {
  253. ipcMain.on('client-certificate-response', function (event, certificate) {
  254. callback(certificate)
  255. })
  256. window.webContents.send('select-client-certificate', webContents.id, list)
  257. }
  258. })
  259. event.returnValue = 'done'
  260. })
  261. ipcMain.on('close-on-will-navigate', (event, id) => {
  262. const contents = event.sender
  263. const window = BrowserWindow.fromId(id)
  264. window.webContents.once('will-navigate', (event, input) => {
  265. window.close()
  266. contents.send('closed-on-will-navigate')
  267. })
  268. })
  269. ipcMain.on('close-on-will-redirect', (event, id) => {
  270. const contents = event.sender
  271. const window = BrowserWindow.fromId(id)
  272. window.webContents.once('will-redirect', (event, input) => {
  273. window.close()
  274. contents.send('closed-on-will-redirect')
  275. })
  276. })
  277. ipcMain.on('prevent-will-redirect', (event, id) => {
  278. const window = BrowserWindow.fromId(id)
  279. window.webContents.once('will-redirect', (event) => {
  280. event.preventDefault()
  281. })
  282. })
  283. ipcMain.on('create-window-with-options-cycle', (event) => {
  284. // This can't be done over remote since cycles are already
  285. // nulled out at the IPC layer
  286. const foo = {}
  287. foo.bar = foo
  288. foo.baz = {
  289. hello: {
  290. world: true
  291. }
  292. }
  293. foo.baz2 = foo.baz
  294. const window = new BrowserWindow({ show: false, foo: foo })
  295. event.returnValue = window.id
  296. })
  297. ipcMain.on('prevent-next-new-window', (event, id) => {
  298. webContents.fromId(id).once('new-window', event => event.preventDefault())
  299. })
  300. ipcMain.on('set-options-on-next-new-window', (event, id, key, value) => {
  301. webContents.fromId(id).once('new-window', (event, url, frameName, disposition, options) => {
  302. options[key] = value
  303. })
  304. })
  305. ipcMain.on('set-web-preferences-on-next-new-window', (event, id, key, value) => {
  306. webContents.fromId(id).once('new-window', (event, url, frameName, disposition, options) => {
  307. options.webPreferences[key] = value
  308. })
  309. })
  310. ipcMain.on('prevent-next-will-attach-webview', (event) => {
  311. event.sender.once('will-attach-webview', event => event.preventDefault())
  312. })
  313. ipcMain.on('prevent-next-will-prevent-unload', (event, id) => {
  314. webContents.fromId(id).once('will-prevent-unload', event => event.preventDefault())
  315. })
  316. ipcMain.on('disable-node-on-next-will-attach-webview', (event, id) => {
  317. event.sender.once('will-attach-webview', (event, webPreferences, params) => {
  318. params.src = `file://${path.join(__dirname, '..', 'fixtures', 'pages', 'c.html')}`
  319. webPreferences.nodeIntegration = false
  320. })
  321. })
  322. ipcMain.on('disable-preload-on-next-will-attach-webview', (event, id) => {
  323. event.sender.once('will-attach-webview', (event, webPreferences, params) => {
  324. params.src = `file://${path.join(__dirname, '..', 'fixtures', 'pages', 'webview-stripped-preload.html')}`
  325. delete webPreferences.preload
  326. delete webPreferences.preloadURL
  327. })
  328. })
  329. ipcMain.on('try-emit-web-contents-event', (event, id, eventName) => {
  330. const consoleWarn = console.warn
  331. const contents = webContents.fromId(id)
  332. const listenerCountBefore = contents.listenerCount(eventName)
  333. console.warn = (warningMessage) => {
  334. console.warn = consoleWarn
  335. const listenerCountAfter = contents.listenerCount(eventName)
  336. event.returnValue = {
  337. warningMessage,
  338. listenerCountBefore,
  339. listenerCountAfter
  340. }
  341. }
  342. contents.emit(eventName, { sender: contents })
  343. })
  344. ipcMain.on('handle-uncaught-exception', (event, message) => {
  345. suspendListeners(process, 'uncaughtException', (error) => {
  346. event.returnValue = error.message
  347. })
  348. fs.readFile(__filename, () => {
  349. throw new Error(message)
  350. })
  351. })
  352. ipcMain.on('handle-unhandled-rejection', (event, message) => {
  353. suspendListeners(process, 'unhandledRejection', (error) => {
  354. event.returnValue = error.message
  355. })
  356. fs.readFile(__filename, () => {
  357. Promise.reject(new Error(message))
  358. })
  359. })
  360. ipcMain.on('test-webcontents-navigation-observer', (event, options) => {
  361. let contents = null
  362. let destroy = () => {}
  363. if (options.id) {
  364. const w = BrowserWindow.fromId(options.id)
  365. contents = w.webContents
  366. destroy = () => w.close()
  367. } else {
  368. contents = webContents.create()
  369. destroy = () => contents.destroy()
  370. }
  371. contents.once(options.name, () => destroy())
  372. contents.once('destroyed', () => {
  373. event.sender.send(options.responseEvent)
  374. })
  375. contents.loadURL(options.url)
  376. })
  377. ipcMain.on('test-browserwindow-destroy', (event, testOptions) => {
  378. const focusListener = (event, win) => win.id
  379. app.on('browser-window-focus', focusListener)
  380. const windowCount = 3
  381. const windowOptions = {
  382. show: false,
  383. width: 400,
  384. height: 400,
  385. webPreferences: {
  386. backgroundThrottling: false
  387. }
  388. }
  389. const windows = Array.from(Array(windowCount)).map(x => new BrowserWindow(windowOptions))
  390. windows.forEach(win => win.show())
  391. windows.forEach(win => win.focus())
  392. windows.forEach(win => win.destroy())
  393. app.removeListener('browser-window-focus', focusListener)
  394. event.sender.send(testOptions.responseEvent)
  395. })
  396. // Suspend listeners until the next event and then restore them
  397. const suspendListeners = (emitter, eventName, callback) => {
  398. const listeners = emitter.listeners(eventName)
  399. emitter.removeAllListeners(eventName)
  400. emitter.once(eventName, (...args) => {
  401. emitter.removeAllListeners(eventName)
  402. listeners.forEach((listener) => {
  403. emitter.on(eventName, listener)
  404. })
  405. // eslint-disable-next-line standard/no-callback-literal
  406. callback(...args)
  407. })
  408. }