main.js 11 KB

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