api-browser-window-spec.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092
  1. 'use strict'
  2. const assert = require('assert')
  3. const fs = require('fs')
  4. const path = require('path')
  5. const os = require('os')
  6. const qs = require('querystring')
  7. const http = require('http')
  8. const {closeWindow} = require('./window-helpers')
  9. const {ipcRenderer, remote, screen} = require('electron')
  10. const {app, ipcMain, BrowserWindow, protocol, webContents} = remote
  11. const isCI = remote.getGlobal('isCi')
  12. describe('BrowserWindow module', function () {
  13. var fixtures = path.resolve(__dirname, 'fixtures')
  14. var w = null
  15. var server, postData
  16. before(function (done) {
  17. const filePath = path.join(fixtures, 'pages', 'a.html')
  18. const fileStats = fs.statSync(filePath)
  19. postData = [
  20. {
  21. type: 'rawData',
  22. bytes: new Buffer('username=test&file=')
  23. },
  24. {
  25. type: 'file',
  26. filePath: filePath,
  27. offset: 0,
  28. length: fileStats.size,
  29. modificationTime: fileStats.mtime.getTime() / 1000
  30. }
  31. ]
  32. server = http.createServer(function (req, res) {
  33. function respond () {
  34. if (req.method === 'POST') {
  35. let body = ''
  36. req.on('data', (data) => {
  37. if (data) {
  38. body += data
  39. }
  40. })
  41. req.on('end', () => {
  42. let parsedData = qs.parse(body)
  43. fs.readFile(filePath, (err, data) => {
  44. if (err) return
  45. if (parsedData.username === 'test' &&
  46. parsedData.file === data.toString()) {
  47. res.end()
  48. }
  49. })
  50. })
  51. } else {
  52. res.end()
  53. }
  54. }
  55. setTimeout(respond, req.url.includes('slow') ? 200 : 0)
  56. })
  57. server.listen(0, '127.0.0.1', function () {
  58. server.url = 'http://127.0.0.1:' + server.address().port
  59. done()
  60. })
  61. })
  62. after(function () {
  63. server.close()
  64. server = null
  65. })
  66. beforeEach(function () {
  67. w = new BrowserWindow({
  68. show: false,
  69. width: 400,
  70. height: 400,
  71. webPreferences: {
  72. backgroundThrottling: false
  73. }
  74. })
  75. })
  76. afterEach(function () {
  77. return closeWindow(w).then(function () { w = null })
  78. })
  79. describe('BrowserWindow.close()', function () {
  80. it('should emit unload handler', function (done) {
  81. w.webContents.on('did-finish-load', function () {
  82. w.close()
  83. })
  84. w.once('closed', function () {
  85. var test = path.join(fixtures, 'api', 'unload')
  86. var content = fs.readFileSync(test)
  87. fs.unlinkSync(test)
  88. assert.equal(String(content), 'unload')
  89. done()
  90. })
  91. w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html'))
  92. })
  93. it('should emit beforeunload handler', function (done) {
  94. w.once('onbeforeunload', function () {
  95. done()
  96. })
  97. w.webContents.on('did-finish-load', function () {
  98. w.close()
  99. })
  100. w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html'))
  101. })
  102. })
  103. describe('window.close()', function () {
  104. it('should emit unload handler', function (done) {
  105. w.once('closed', function () {
  106. var test = path.join(fixtures, 'api', 'close')
  107. var content = fs.readFileSync(test)
  108. fs.unlinkSync(test)
  109. assert.equal(String(content), 'close')
  110. done()
  111. })
  112. w.loadURL('file://' + path.join(fixtures, 'api', 'close.html'))
  113. })
  114. it('should emit beforeunload handler', function (done) {
  115. w.once('onbeforeunload', function () {
  116. done()
  117. })
  118. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html'))
  119. })
  120. })
  121. describe('BrowserWindow.destroy()', function () {
  122. it('prevents users to access methods of webContents', function () {
  123. const contents = w.webContents
  124. w.destroy()
  125. assert.throws(function () {
  126. contents.getId()
  127. }, /Object has been destroyed/)
  128. })
  129. })
  130. describe('BrowserWindow.loadURL(url)', function () {
  131. it('should emit did-start-loading event', function (done) {
  132. w.webContents.on('did-start-loading', function () {
  133. done()
  134. })
  135. w.loadURL('about:blank')
  136. })
  137. it('should emit ready-to-show event', function (done) {
  138. w.on('ready-to-show', function () {
  139. done()
  140. })
  141. w.loadURL('about:blank')
  142. })
  143. it('should emit did-get-response-details event', function (done) {
  144. // expected {fileName: resourceType} pairs
  145. var expectedResources = {
  146. 'did-get-response-details.html': 'mainFrame',
  147. 'logo.png': 'image'
  148. }
  149. var responses = 0
  150. w.webContents.on('did-get-response-details', function (event, status, newUrl, oldUrl, responseCode, method, referrer, headers, resourceType) {
  151. responses++
  152. var fileName = newUrl.slice(newUrl.lastIndexOf('/') + 1)
  153. var expectedType = expectedResources[fileName]
  154. assert(!!expectedType, `Unexpected response details for ${newUrl}`)
  155. assert(typeof status === 'boolean', 'status should be boolean')
  156. assert.equal(responseCode, 200)
  157. assert.equal(method, 'GET')
  158. assert(typeof referrer === 'string', 'referrer should be string')
  159. assert(!!headers, 'headers should be present')
  160. assert(typeof headers === 'object', 'headers should be object')
  161. assert.equal(resourceType, expectedType, 'Incorrect resourceType')
  162. if (responses === Object.keys(expectedResources).length) {
  163. done()
  164. }
  165. })
  166. w.loadURL('file://' + path.join(fixtures, 'pages', 'did-get-response-details.html'))
  167. })
  168. it('should emit did-fail-load event for files that do not exist', function (done) {
  169. w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) {
  170. assert.equal(code, -6)
  171. assert.equal(desc, 'ERR_FILE_NOT_FOUND')
  172. assert.equal(isMainFrame, true)
  173. done()
  174. })
  175. w.loadURL('file://a.txt')
  176. })
  177. it('should emit did-fail-load event for invalid URL', function (done) {
  178. w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) {
  179. assert.equal(desc, 'ERR_INVALID_URL')
  180. assert.equal(code, -300)
  181. assert.equal(isMainFrame, true)
  182. done()
  183. })
  184. w.loadURL('http://example:port')
  185. })
  186. it('should set `mainFrame = false` on did-fail-load events in iframes', function (done) {
  187. w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) {
  188. assert.equal(isMainFrame, false)
  189. done()
  190. })
  191. w.loadURL('file://' + path.join(fixtures, 'api', 'did-fail-load-iframe.html'))
  192. })
  193. it('does not crash in did-fail-provisional-load handler', function (done) {
  194. w.webContents.once('did-fail-provisional-load', function () {
  195. w.loadURL('http://127.0.0.1:11111')
  196. done()
  197. })
  198. w.loadURL('http://127.0.0.1:11111')
  199. })
  200. it('should emit did-fail-load event for URL exceeding character limit', function (done) {
  201. w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) {
  202. assert.equal(desc, 'ERR_INVALID_URL')
  203. assert.equal(code, -300)
  204. assert.equal(isMainFrame, true)
  205. done()
  206. })
  207. const data = new Buffer(2 * 1024 * 1024).toString('base64')
  208. w.loadURL(`data:image/png;base64,${data}`)
  209. })
  210. describe('POST navigations', function () {
  211. afterEach(() => {
  212. w.webContents.session.webRequest.onBeforeSendHeaders(null)
  213. })
  214. it('supports specifying POST data', function (done) {
  215. w.webContents.on('did-finish-load', () => done())
  216. w.loadURL(server.url, {postData: postData})
  217. })
  218. it('sets the content type header on URL encoded forms', function (done) {
  219. w.webContents.on('did-finish-load', () => {
  220. w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
  221. assert.equal(details.requestHeaders['content-type'], 'application/x-www-form-urlencoded')
  222. done()
  223. })
  224. w.webContents.executeJavaScript(`
  225. form = document.createElement('form')
  226. document.body.appendChild(form)
  227. form.method = 'POST'
  228. form.target = '_blank'
  229. form.submit()
  230. `)
  231. })
  232. w.loadURL(server.url)
  233. })
  234. it('sets the content type header on multi part forms', function (done) {
  235. w.webContents.on('did-finish-load', () => {
  236. w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
  237. assert(details.requestHeaders['content-type'].startsWith('multipart/form-data; boundary=----WebKitFormBoundary'))
  238. done()
  239. })
  240. w.webContents.executeJavaScript(`
  241. form = document.createElement('form')
  242. document.body.appendChild(form)
  243. form.method = 'POST'
  244. form.target = '_blank'
  245. form.enctype = 'multipart/form-data'
  246. file = document.createElement('input')
  247. file.type = 'file'
  248. file.name = 'file'
  249. form.appendChild(file)
  250. form.submit()
  251. `)
  252. })
  253. w.loadURL(server.url)
  254. })
  255. })
  256. })
  257. describe('will-navigate event', function () {
  258. it('allows the window to be closed from the event listener', (done) => {
  259. ipcRenderer.send('close-on-will-navigate', w.id)
  260. ipcRenderer.once('closed-on-will-navigate', () => {
  261. done()
  262. })
  263. w.loadURL('file://' + fixtures + '/pages/will-navigate.html')
  264. })
  265. })
  266. describe('BrowserWindow.show()', function () {
  267. if (isCI) {
  268. return
  269. }
  270. it('should focus on window', function () {
  271. w.show()
  272. assert(w.isFocused())
  273. })
  274. it('should make the window visible', function () {
  275. w.show()
  276. assert(w.isVisible())
  277. })
  278. it('emits when window is shown', function (done) {
  279. w.once('show', function () {
  280. assert.equal(w.isVisible(), true)
  281. done()
  282. })
  283. w.show()
  284. })
  285. })
  286. describe('BrowserWindow.hide()', function () {
  287. if (isCI) {
  288. return
  289. }
  290. it('should defocus on window', function () {
  291. w.hide()
  292. assert(!w.isFocused())
  293. })
  294. it('should make the window not visible', function () {
  295. w.show()
  296. w.hide()
  297. assert(!w.isVisible())
  298. })
  299. it('emits when window is hidden', function (done) {
  300. w.show()
  301. w.once('hide', function () {
  302. assert.equal(w.isVisible(), false)
  303. done()
  304. })
  305. w.hide()
  306. })
  307. })
  308. describe('BrowserWindow.showInactive()', function () {
  309. it('should not focus on window', function () {
  310. w.showInactive()
  311. assert(!w.isFocused())
  312. })
  313. })
  314. describe('BrowserWindow.focus()', function () {
  315. it('does not make the window become visible', function () {
  316. assert.equal(w.isVisible(), false)
  317. w.focus()
  318. assert.equal(w.isVisible(), false)
  319. })
  320. })
  321. describe('BrowserWindow.blur()', function () {
  322. it('removes focus from window', function () {
  323. w.blur()
  324. assert(!w.isFocused())
  325. })
  326. })
  327. describe('BrowserWindow.capturePage(rect, callback)', function () {
  328. it('calls the callback with a Buffer', function (done) {
  329. w.capturePage({
  330. x: 0,
  331. y: 0,
  332. width: 100,
  333. height: 100
  334. }, function (image) {
  335. assert.equal(image.isEmpty(), true)
  336. done()
  337. })
  338. })
  339. })
  340. describe('BrowserWindow.setSize(width, height)', function () {
  341. it('sets the window size', function (done) {
  342. var size = [300, 400]
  343. w.once('resize', function () {
  344. assertBoundsEqual(w.getSize(), size)
  345. done()
  346. })
  347. w.setSize(size[0], size[1])
  348. })
  349. })
  350. describe('BrowserWindow.setMinimum/MaximumSize(width, height)', function () {
  351. it('sets the maximum and minimum size of the window', function () {
  352. assert.deepEqual(w.getMinimumSize(), [0, 0])
  353. assert.deepEqual(w.getMaximumSize(), [0, 0])
  354. w.setMinimumSize(100, 100)
  355. assertBoundsEqual(w.getMinimumSize(), [100, 100])
  356. assertBoundsEqual(w.getMaximumSize(), [0, 0])
  357. w.setMaximumSize(900, 600)
  358. assertBoundsEqual(w.getMinimumSize(), [100, 100])
  359. assertBoundsEqual(w.getMaximumSize(), [900, 600])
  360. })
  361. })
  362. describe('BrowserWindow.setAspectRatio(ratio)', function () {
  363. it('resets the behaviour when passing in 0', function (done) {
  364. var size = [300, 400]
  365. w.setAspectRatio(1 / 2)
  366. w.setAspectRatio(0)
  367. w.once('resize', function () {
  368. assertBoundsEqual(w.getSize(), size)
  369. done()
  370. })
  371. w.setSize(size[0], size[1])
  372. })
  373. })
  374. describe('BrowserWindow.setPosition(x, y)', function () {
  375. it('sets the window position', function (done) {
  376. var pos = [10, 10]
  377. w.once('move', function () {
  378. var newPos = w.getPosition()
  379. assert.equal(newPos[0], pos[0])
  380. assert.equal(newPos[1], pos[1])
  381. done()
  382. })
  383. w.setPosition(pos[0], pos[1])
  384. })
  385. })
  386. describe('BrowserWindow.setContentSize(width, height)', function () {
  387. it('sets the content size', function () {
  388. var size = [400, 400]
  389. w.setContentSize(size[0], size[1])
  390. var after = w.getContentSize()
  391. assert.equal(after[0], size[0])
  392. assert.equal(after[1], size[1])
  393. })
  394. it('works for a frameless window', function () {
  395. w.destroy()
  396. w = new BrowserWindow({
  397. show: false,
  398. frame: false,
  399. width: 400,
  400. height: 400
  401. })
  402. var size = [400, 400]
  403. w.setContentSize(size[0], size[1])
  404. var after = w.getContentSize()
  405. assert.equal(after[0], size[0])
  406. assert.equal(after[1], size[1])
  407. })
  408. })
  409. describe('BrowserWindow.setContentBounds(bounds)', function () {
  410. it('sets the content size and position', function (done) {
  411. var bounds = {x: 10, y: 10, width: 250, height: 250}
  412. w.once('resize', function () {
  413. assertBoundsEqual(w.getContentBounds(), bounds)
  414. done()
  415. })
  416. w.setContentBounds(bounds)
  417. })
  418. it('works for a frameless window', function (done) {
  419. w.destroy()
  420. w = new BrowserWindow({
  421. show: false,
  422. frame: false,
  423. width: 300,
  424. height: 300
  425. })
  426. var bounds = {x: 10, y: 10, width: 250, height: 250}
  427. w.once('resize', function () {
  428. assert.deepEqual(w.getContentBounds(), bounds)
  429. done()
  430. })
  431. w.setContentBounds(bounds)
  432. })
  433. })
  434. describe('BrowserWindow.setProgressBar(progress)', function () {
  435. it('sets the progress', function () {
  436. assert.doesNotThrow(function () {
  437. if (process.platform === 'darwin') {
  438. app.dock.setIcon(path.join(fixtures, 'assets', 'logo.png'))
  439. }
  440. w.setProgressBar(0.5)
  441. if (process.platform === 'darwin') {
  442. app.dock.setIcon(null)
  443. }
  444. w.setProgressBar(-1)
  445. })
  446. })
  447. it('sets the progress using "paused" mode', function () {
  448. assert.doesNotThrow(function () {
  449. w.setProgressBar(0.5, {mode: 'paused'})
  450. })
  451. })
  452. it('sets the progress using "error" mode', function () {
  453. assert.doesNotThrow(function () {
  454. w.setProgressBar(0.5, {mode: 'error'})
  455. })
  456. })
  457. it('sets the progress using "normal" mode', function () {
  458. assert.doesNotThrow(function () {
  459. w.setProgressBar(0.5, {mode: 'normal'})
  460. })
  461. })
  462. })
  463. describe('BrowserWindow.setAlwaysOnTop(flag, level)', function () {
  464. it('sets the window as always on top', function () {
  465. assert.equal(w.isAlwaysOnTop(), false)
  466. w.setAlwaysOnTop(true, 'screen-saver')
  467. assert.equal(w.isAlwaysOnTop(), true)
  468. w.setAlwaysOnTop(false)
  469. assert.equal(w.isAlwaysOnTop(), false)
  470. w.setAlwaysOnTop(true)
  471. assert.equal(w.isAlwaysOnTop(), true)
  472. })
  473. it('raises an error when relativeLevel is out of bounds', function () {
  474. if (process.platform !== 'darwin') return
  475. assert.throws(function () {
  476. w.setAlwaysOnTop(true, '', -2147483644)
  477. })
  478. assert.throws(function () {
  479. w.setAlwaysOnTop(true, '', 2147483632)
  480. })
  481. })
  482. })
  483. describe('BrowserWindow.setAutoHideCursor(autoHide)', () => {
  484. if (process.platform !== 'darwin') {
  485. it('is not available on non-macOS platforms', () => {
  486. assert.ok(!w.setAutoHideCursor)
  487. })
  488. return
  489. }
  490. it('allows changing cursor auto-hiding', () => {
  491. assert.doesNotThrow(() => {
  492. w.setAutoHideCursor(false)
  493. w.setAutoHideCursor(true)
  494. })
  495. })
  496. })
  497. describe('BrowserWindow.setVibrancy(type)', function () {
  498. it('allows setting, changing, and removing the vibrancy', function () {
  499. assert.doesNotThrow(function () {
  500. w.setVibrancy('light')
  501. w.setVibrancy('dark')
  502. w.setVibrancy(null)
  503. w.setVibrancy('ultra-dark')
  504. w.setVibrancy('')
  505. })
  506. })
  507. })
  508. describe('BrowserWindow.setAppDetails(options)', function () {
  509. it('supports setting the app details', function () {
  510. if (process.platform !== 'win32') return
  511. const iconPath = path.join(fixtures, 'assets', 'icon.ico')
  512. assert.doesNotThrow(function () {
  513. w.setAppDetails({appId: 'my.app.id'})
  514. w.setAppDetails({appIconPath: iconPath, appIconIndex: 0})
  515. w.setAppDetails({appIconPath: iconPath})
  516. w.setAppDetails({relaunchCommand: 'my-app.exe arg1 arg2', relaunchDisplayName: 'My app name'})
  517. w.setAppDetails({relaunchCommand: 'my-app.exe arg1 arg2'})
  518. w.setAppDetails({relaunchDisplayName: 'My app name'})
  519. w.setAppDetails({
  520. appId: 'my.app.id',
  521. appIconPath: iconPath,
  522. appIconIndex: 0,
  523. relaunchCommand: 'my-app.exe arg1 arg2',
  524. relaunchDisplayName: 'My app name'
  525. })
  526. w.setAppDetails({})
  527. })
  528. assert.throws(function () {
  529. w.setAppDetails()
  530. }, /Insufficient number of arguments\./)
  531. })
  532. })
  533. describe('BrowserWindow.fromId(id)', function () {
  534. it('returns the window with id', function () {
  535. assert.equal(w.id, BrowserWindow.fromId(w.id).id)
  536. })
  537. })
  538. describe('BrowserWindow.fromWebContents(webContents)', function () {
  539. let contents = null
  540. beforeEach(function () {
  541. contents = webContents.create({})
  542. })
  543. afterEach(function () {
  544. contents.destroy()
  545. })
  546. it('returns the window with the webContents', function () {
  547. assert.equal(BrowserWindow.fromWebContents(w.webContents).id, w.id)
  548. assert.equal(BrowserWindow.fromWebContents(contents), undefined)
  549. })
  550. })
  551. describe('BrowserWindow.fromDevToolsWebContents(webContents)', function () {
  552. let contents = null
  553. beforeEach(function () {
  554. contents = webContents.create({})
  555. })
  556. afterEach(function () {
  557. contents.destroy()
  558. })
  559. it('returns the window with the webContents', function (done) {
  560. w.webContents.once('devtools-opened', () => {
  561. assert.equal(BrowserWindow.fromDevToolsWebContents(w.devToolsWebContents).id, w.id)
  562. assert.equal(BrowserWindow.fromDevToolsWebContents(w.webContents), undefined)
  563. assert.equal(BrowserWindow.fromDevToolsWebContents(contents), undefined)
  564. done()
  565. })
  566. w.webContents.openDevTools()
  567. })
  568. })
  569. describe('"useContentSize" option', function () {
  570. it('make window created with content size when used', function () {
  571. w.destroy()
  572. w = new BrowserWindow({
  573. show: false,
  574. width: 400,
  575. height: 400,
  576. useContentSize: true
  577. })
  578. var contentSize = w.getContentSize()
  579. assert.equal(contentSize[0], 400)
  580. assert.equal(contentSize[1], 400)
  581. })
  582. it('make window created with window size when not used', function () {
  583. var size = w.getSize()
  584. assert.equal(size[0], 400)
  585. assert.equal(size[1], 400)
  586. })
  587. it('works for a frameless window', function () {
  588. w.destroy()
  589. w = new BrowserWindow({
  590. show: false,
  591. frame: false,
  592. width: 400,
  593. height: 400,
  594. useContentSize: true
  595. })
  596. var contentSize = w.getContentSize()
  597. assert.equal(contentSize[0], 400)
  598. assert.equal(contentSize[1], 400)
  599. var size = w.getSize()
  600. assert.equal(size[0], 400)
  601. assert.equal(size[1], 400)
  602. })
  603. })
  604. describe('"title-bar-style" option', function () {
  605. if (process.platform !== 'darwin') {
  606. return
  607. }
  608. if (parseInt(os.release().split('.')[0]) < 14) {
  609. return
  610. }
  611. it('creates browser window with hidden title bar', function () {
  612. w.destroy()
  613. w = new BrowserWindow({
  614. show: false,
  615. width: 400,
  616. height: 400,
  617. titleBarStyle: 'hidden'
  618. })
  619. var contentSize = w.getContentSize()
  620. assert.equal(contentSize[1], 400)
  621. })
  622. it('creates browser window with hidden inset title bar', function () {
  623. w.destroy()
  624. w = new BrowserWindow({
  625. show: false,
  626. width: 400,
  627. height: 400,
  628. titleBarStyle: 'hidden-inset'
  629. })
  630. var contentSize = w.getContentSize()
  631. assert.equal(contentSize[1], 400)
  632. })
  633. })
  634. describe('enableLargerThanScreen" option', function () {
  635. if (process.platform === 'linux') {
  636. return
  637. }
  638. beforeEach(function () {
  639. w.destroy()
  640. w = new BrowserWindow({
  641. show: true,
  642. width: 400,
  643. height: 400,
  644. enableLargerThanScreen: true
  645. })
  646. })
  647. it('can move the window out of screen', function () {
  648. w.setPosition(-10, -10)
  649. var after = w.getPosition()
  650. assert.equal(after[0], -10)
  651. assert.equal(after[1], -10)
  652. })
  653. it('can set the window larger than screen', function () {
  654. var size = screen.getPrimaryDisplay().size
  655. size.width += 100
  656. size.height += 100
  657. w.setSize(size.width, size.height)
  658. assertBoundsEqual(w.getSize(), [size.width, size.height])
  659. })
  660. })
  661. describe('"zoomToPageWidth" option', function () {
  662. it('sets the window width to the page width when used', function () {
  663. if (process.platform !== 'darwin') return
  664. w.destroy()
  665. w = new BrowserWindow({
  666. show: false,
  667. width: 500,
  668. height: 400,
  669. zoomToPageWidth: true
  670. })
  671. w.maximize()
  672. assert.equal(w.getSize()[0], 500)
  673. })
  674. })
  675. describe('"web-preferences" option', function () {
  676. afterEach(function () {
  677. ipcMain.removeAllListeners('answer')
  678. })
  679. describe('"preload" option', function () {
  680. it('loads the script before other scripts in window', function (done) {
  681. var preload = path.join(fixtures, 'module', 'set-global.js')
  682. ipcMain.once('answer', function (event, test) {
  683. assert.equal(test, 'preload')
  684. done()
  685. })
  686. w.destroy()
  687. w = new BrowserWindow({
  688. show: false,
  689. webPreferences: {
  690. preload: preload
  691. }
  692. })
  693. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  694. })
  695. it('can successfully delete the Buffer global', function (done) {
  696. var preload = path.join(fixtures, 'module', 'delete-buffer.js')
  697. ipcMain.once('answer', function (event, test) {
  698. assert.equal(test.toString(), 'buffer')
  699. done()
  700. })
  701. w.destroy()
  702. w = new BrowserWindow({
  703. show: false,
  704. webPreferences: {
  705. preload: preload
  706. }
  707. })
  708. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  709. })
  710. })
  711. describe('"node-integration" option', function () {
  712. it('disables node integration when specified to false', function (done) {
  713. var preload = path.join(fixtures, 'module', 'send-later.js')
  714. ipcMain.once('answer', function (event, test) {
  715. assert.equal(test, 'undefined')
  716. done()
  717. })
  718. w.destroy()
  719. w = new BrowserWindow({
  720. show: false,
  721. webPreferences: {
  722. preload: preload,
  723. nodeIntegration: false
  724. }
  725. })
  726. w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html'))
  727. })
  728. })
  729. describe('"sandbox" option', function () {
  730. function waitForEvents (emitter, events, callback) {
  731. let count = events.length
  732. for (let event of events) {
  733. emitter.once(event, () => {
  734. if (!--count) callback()
  735. })
  736. }
  737. }
  738. const preload = path.join(fixtures, 'module', 'preload-sandbox.js')
  739. // http protocol to simulate accessing another domain. This is required
  740. // because the code paths for cross domain popups is different.
  741. function crossDomainHandler (request, callback) {
  742. callback({
  743. mimeType: 'text/html',
  744. data: `<html><body><h1>${request.url}</h1></body></html>`
  745. })
  746. }
  747. before(function (done) {
  748. protocol.interceptStringProtocol('http', crossDomainHandler, function () {
  749. done()
  750. })
  751. })
  752. after(function (done) {
  753. protocol.uninterceptProtocol('http', function () {
  754. done()
  755. })
  756. })
  757. it('exposes ipcRenderer to preload script', function (done) {
  758. ipcMain.once('answer', function (event, test) {
  759. assert.equal(test, 'preload')
  760. done()
  761. })
  762. w.destroy()
  763. w = new BrowserWindow({
  764. show: false,
  765. webPreferences: {
  766. sandbox: true,
  767. preload: preload
  768. }
  769. })
  770. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  771. })
  772. it('exposes "exit" event to preload script', function (done) {
  773. w.destroy()
  774. w = new BrowserWindow({
  775. show: false,
  776. webPreferences: {
  777. sandbox: true,
  778. preload: preload
  779. }
  780. })
  781. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?exit-event')
  782. const pageUrl = 'file://' + htmlPath
  783. w.loadURL(pageUrl)
  784. ipcMain.once('answer', function (event, url) {
  785. let expectedUrl = pageUrl
  786. if (process.platform === 'win32') {
  787. expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
  788. }
  789. assert.equal(url, expectedUrl)
  790. done()
  791. })
  792. })
  793. it('should open windows in same domain with cross-scripting enabled', function (done) {
  794. w.destroy()
  795. w = new BrowserWindow({
  796. show: false,
  797. webPreferences: {
  798. sandbox: true,
  799. preload: preload
  800. }
  801. })
  802. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open')
  803. const pageUrl = 'file://' + htmlPath
  804. w.loadURL(pageUrl)
  805. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  806. let expectedUrl = pageUrl
  807. if (process.platform === 'win32') {
  808. expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
  809. }
  810. assert.equal(url, expectedUrl)
  811. assert.equal(frameName, 'popup!')
  812. assert.equal(options.x, 50)
  813. assert.equal(options.y, 60)
  814. assert.equal(options.width, 500)
  815. assert.equal(options.height, 600)
  816. ipcMain.once('answer', function (event, html) {
  817. assert.equal(html, '<h1>scripting from opener</h1>')
  818. done()
  819. })
  820. })
  821. })
  822. it('should open windows in another domain with cross-scripting disabled', function (done) {
  823. w.destroy()
  824. w = new BrowserWindow({
  825. show: false,
  826. webPreferences: {
  827. sandbox: true,
  828. preload: preload
  829. }
  830. })
  831. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open-external')
  832. const pageUrl = 'file://' + htmlPath
  833. let popupWindow
  834. w.loadURL(pageUrl)
  835. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  836. assert.equal(url, 'http://www.google.com/#q=electron')
  837. assert.equal(options.x, 55)
  838. assert.equal(options.y, 65)
  839. assert.equal(options.width, 505)
  840. assert.equal(options.height, 605)
  841. ipcMain.once('child-loaded', function (event, openerIsNull, html) {
  842. assert(openerIsNull)
  843. assert.equal(html, '<h1>http://www.google.com/#q=electron</h1>')
  844. ipcMain.once('answer', function (event, exceptionMessage) {
  845. assert(/Blocked a frame with origin/.test(exceptionMessage))
  846. // FIXME this popup window should be closed in sandbox.html
  847. closeWindow(popupWindow, {assertSingleWindow: false}).then(() => {
  848. popupWindow = null
  849. done()
  850. })
  851. })
  852. w.webContents.send('child-loaded')
  853. })
  854. })
  855. app.once('browser-window-created', function (event, window) {
  856. popupWindow = window
  857. })
  858. })
  859. it('should set ipc event sender correctly', function (done) {
  860. w.destroy()
  861. w = new BrowserWindow({
  862. show: false,
  863. webPreferences: {
  864. sandbox: true,
  865. preload: preload
  866. }
  867. })
  868. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?verify-ipc-sender')
  869. const pageUrl = 'file://' + htmlPath
  870. w.loadURL(pageUrl)
  871. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  872. let parentWc = w.webContents
  873. let childWc = options.webContents
  874. assert.notEqual(parentWc, childWc)
  875. ipcMain.once('parent-ready', function (event) {
  876. assert.equal(parentWc, event.sender)
  877. parentWc.send('verified')
  878. })
  879. ipcMain.once('child-ready', function (event) {
  880. assert.equal(childWc, event.sender)
  881. childWc.send('verified')
  882. })
  883. waitForEvents(ipcMain, [
  884. 'parent-answer',
  885. 'child-answer'
  886. ], done)
  887. })
  888. })
  889. describe('event handling', function () {
  890. it('works for window events', function (done) {
  891. waitForEvents(w, [
  892. 'page-title-updated'
  893. ], done)
  894. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?window-events'))
  895. })
  896. it('works for web contents events', function (done) {
  897. waitForEvents(w.webContents, [
  898. 'did-navigate',
  899. 'did-fail-load',
  900. 'did-stop-loading'
  901. ], done)
  902. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-stop'))
  903. waitForEvents(w.webContents, [
  904. 'did-finish-load',
  905. 'did-frame-finish-load',
  906. 'did-navigate-in-page',
  907. 'will-navigate',
  908. 'did-start-loading',
  909. 'did-stop-loading',
  910. 'did-frame-finish-load',
  911. 'dom-ready'
  912. ], done)
  913. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-events'))
  914. })
  915. })
  916. it('can print to PDF', function (done) {
  917. w.destroy()
  918. w = new BrowserWindow({
  919. show: false,
  920. webPreferences: {
  921. sandbox: true,
  922. preload: preload
  923. }
  924. })
  925. w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E')
  926. w.webContents.once('did-finish-load', function () {
  927. w.webContents.printToPDF({}, function (error, data) {
  928. assert.equal(error, null)
  929. assert.equal(data instanceof Buffer, true)
  930. assert.notEqual(data.length, 0)
  931. done()
  932. })
  933. })
  934. })
  935. it('supports calling preventDefault on new-window events', (done) => {
  936. w.destroy()
  937. w = new BrowserWindow({
  938. show: false,
  939. webPreferences: {
  940. sandbox: true
  941. }
  942. })
  943. const initialWebContents = webContents.getAllWebContents()
  944. ipcRenderer.send('prevent-next-new-window', w.webContents.id)
  945. w.webContents.once('new-window', () => {
  946. assert.deepEqual(webContents.getAllWebContents(), initialWebContents)
  947. done()
  948. })
  949. w.loadURL('file://' + path.join(fixtures, 'pages', 'window-open.html'))
  950. })
  951. })
  952. })
  953. describe('beforeunload handler', function () {
  954. it('returning undefined would not prevent close', function (done) {
  955. w.once('closed', function () {
  956. done()
  957. })
  958. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-undefined.html'))
  959. })
  960. it('returning false would prevent close', function (done) {
  961. w.once('onbeforeunload', function () {
  962. done()
  963. })
  964. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html'))
  965. })
  966. it('returning empty string would prevent close', function (done) {
  967. w.once('onbeforeunload', function () {
  968. done()
  969. })
  970. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html'))
  971. })
  972. })
  973. describe('new-window event', function () {
  974. if (isCI && process.platform === 'darwin') {
  975. return
  976. }
  977. it('emits when window.open is called', function (done) {
  978. w.webContents.once('new-window', function (e, url, frameName, disposition, options, additionalFeatures) {
  979. e.preventDefault()
  980. assert.equal(url, 'http://host/')
  981. assert.equal(frameName, 'host')
  982. assert.equal(additionalFeatures[0], 'this-is-not-a-standard-feature')
  983. done()
  984. })
  985. w.loadURL('file://' + fixtures + '/pages/window-open.html')
  986. })
  987. it('emits when window.open is called with no webPreferences', function (done) {
  988. w.destroy()
  989. w = new BrowserWindow({ show: false })
  990. w.webContents.once('new-window', function (e, url, frameName, disposition, options, additionalFeatures) {
  991. e.preventDefault()
  992. assert.equal(url, 'http://host/')
  993. assert.equal(frameName, 'host')
  994. assert.equal(additionalFeatures[0], 'this-is-not-a-standard-feature')
  995. done()
  996. })
  997. w.loadURL('file://' + fixtures + '/pages/window-open.html')
  998. })
  999. it('emits when link with target is called', function (done) {
  1000. w.webContents.once('new-window', function (e, url, frameName) {
  1001. e.preventDefault()
  1002. assert.equal(url, 'http://host/')
  1003. assert.equal(frameName, 'target')
  1004. done()
  1005. })
  1006. w.loadURL('file://' + fixtures + '/pages/target-name.html')
  1007. })
  1008. })
  1009. describe('maximize event', function () {
  1010. if (isCI) {
  1011. return
  1012. }
  1013. it('emits when window is maximized', function (done) {
  1014. w.once('maximize', function () {
  1015. done()
  1016. })
  1017. w.show()
  1018. w.maximize()
  1019. })
  1020. })
  1021. describe('unmaximize event', function () {
  1022. if (isCI) {
  1023. return
  1024. }
  1025. it('emits when window is unmaximized', function (done) {
  1026. w.once('unmaximize', function () {
  1027. done()
  1028. })
  1029. w.show()
  1030. w.maximize()
  1031. w.unmaximize()
  1032. })
  1033. })
  1034. describe('minimize event', function () {
  1035. if (isCI) {
  1036. return
  1037. }
  1038. it('emits when window is minimized', function (done) {
  1039. w.once('minimize', function () {
  1040. done()
  1041. })
  1042. w.show()
  1043. w.minimize()
  1044. })
  1045. })
  1046. describe('beginFrameSubscription method', function () {
  1047. // This test is too slow, only test it on CI.
  1048. if (!isCI) return
  1049. it('subscribes to frame updates', function (done) {
  1050. let called = false
  1051. w.loadURL('file://' + fixtures + '/api/frame-subscriber.html')
  1052. w.webContents.on('dom-ready', function () {
  1053. w.webContents.beginFrameSubscription(function (data) {
  1054. // This callback might be called twice.
  1055. if (called) return
  1056. called = true
  1057. assert.notEqual(data.length, 0)
  1058. w.webContents.endFrameSubscription()
  1059. done()
  1060. })
  1061. })
  1062. })
  1063. it('subscribes to frame updates (only dirty rectangle)', function (done) {
  1064. let called = false
  1065. w.loadURL('file://' + fixtures + '/api/frame-subscriber.html')
  1066. w.webContents.on('dom-ready', function () {
  1067. w.webContents.beginFrameSubscription(true, function (data) {
  1068. // This callback might be called twice.
  1069. if (called) return
  1070. called = true
  1071. assert.notEqual(data.length, 0)
  1072. w.webContents.endFrameSubscription()
  1073. done()
  1074. })
  1075. })
  1076. })
  1077. it('throws error when subscriber is not well defined', function (done) {
  1078. w.loadURL('file://' + fixtures + '/api/frame-subscriber.html')
  1079. try {
  1080. w.webContents.beginFrameSubscription(true, true)
  1081. } catch (e) {
  1082. done()
  1083. }
  1084. })
  1085. })
  1086. describe('savePage method', function () {
  1087. const savePageDir = path.join(fixtures, 'save_page')
  1088. const savePageHtmlPath = path.join(savePageDir, 'save_page.html')
  1089. const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js')
  1090. const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css')
  1091. after(function () {
  1092. try {
  1093. fs.unlinkSync(savePageCssPath)
  1094. fs.unlinkSync(savePageJsPath)
  1095. fs.unlinkSync(savePageHtmlPath)
  1096. fs.rmdirSync(path.join(savePageDir, 'save_page_files'))
  1097. fs.rmdirSync(savePageDir)
  1098. } catch (e) {
  1099. // Ignore error
  1100. }
  1101. })
  1102. it('should save page to disk', function (done) {
  1103. w.webContents.on('did-finish-load', function () {
  1104. w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function (error) {
  1105. assert.equal(error, null)
  1106. assert(fs.existsSync(savePageHtmlPath))
  1107. assert(fs.existsSync(savePageJsPath))
  1108. assert(fs.existsSync(savePageCssPath))
  1109. done()
  1110. })
  1111. })
  1112. w.loadURL('file://' + fixtures + '/pages/save_page/index.html')
  1113. })
  1114. })
  1115. describe('BrowserWindow options argument is optional', function () {
  1116. it('should create a window with default size (800x600)', function () {
  1117. w.destroy()
  1118. w = new BrowserWindow()
  1119. var size = w.getSize()
  1120. assert.equal(size[0], 800)
  1121. assert.equal(size[1], 600)
  1122. })
  1123. })
  1124. describe('window states', function () {
  1125. it('does not resize frameless windows when states change', function () {
  1126. w.destroy()
  1127. w = new BrowserWindow({
  1128. frame: false,
  1129. width: 300,
  1130. height: 200,
  1131. show: false
  1132. })
  1133. w.setMinimizable(false)
  1134. w.setMinimizable(true)
  1135. assert.deepEqual(w.getSize(), [300, 200])
  1136. w.setResizable(false)
  1137. w.setResizable(true)
  1138. assert.deepEqual(w.getSize(), [300, 200])
  1139. w.setMaximizable(false)
  1140. w.setMaximizable(true)
  1141. assert.deepEqual(w.getSize(), [300, 200])
  1142. w.setFullScreenable(false)
  1143. w.setFullScreenable(true)
  1144. assert.deepEqual(w.getSize(), [300, 200])
  1145. w.setClosable(false)
  1146. w.setClosable(true)
  1147. assert.deepEqual(w.getSize(), [300, 200])
  1148. })
  1149. describe('resizable state', function () {
  1150. it('can be changed with resizable option', function () {
  1151. w.destroy()
  1152. w = new BrowserWindow({show: false, resizable: false})
  1153. assert.equal(w.isResizable(), false)
  1154. if (process.platform === 'darwin') {
  1155. assert.equal(w.isMaximizable(), true)
  1156. }
  1157. })
  1158. it('can be changed with setResizable method', function () {
  1159. assert.equal(w.isResizable(), true)
  1160. w.setResizable(false)
  1161. assert.equal(w.isResizable(), false)
  1162. w.setResizable(true)
  1163. assert.equal(w.isResizable(), true)
  1164. })
  1165. it('works for a frameless window', () => {
  1166. w.destroy()
  1167. w = new BrowserWindow({show: false, frame: false})
  1168. assert.equal(w.isResizable(), true)
  1169. if (process.platform === 'win32') {
  1170. w.destroy()
  1171. w = new BrowserWindow({show: false, thickFrame: false})
  1172. assert.equal(w.isResizable(), false)
  1173. }
  1174. })
  1175. })
  1176. describe('loading main frame state', function () {
  1177. it('is true when the main frame is loading', function (done) {
  1178. w.webContents.on('did-start-loading', function () {
  1179. assert.equal(w.webContents.isLoadingMainFrame(), true)
  1180. done()
  1181. })
  1182. w.webContents.loadURL(server.url)
  1183. })
  1184. it('is false when only a subframe is loading', function (done) {
  1185. w.webContents.once('did-finish-load', function () {
  1186. assert.equal(w.webContents.isLoadingMainFrame(), false)
  1187. w.webContents.on('did-start-loading', function () {
  1188. assert.equal(w.webContents.isLoadingMainFrame(), false)
  1189. done()
  1190. })
  1191. w.webContents.executeJavaScript(`
  1192. var iframe = document.createElement('iframe')
  1193. iframe.src = '${server.url}/page2'
  1194. document.body.appendChild(iframe)
  1195. `)
  1196. })
  1197. w.webContents.loadURL(server.url)
  1198. })
  1199. it('is true when navigating to pages from the same origin', function (done) {
  1200. w.webContents.once('did-finish-load', function () {
  1201. assert.equal(w.webContents.isLoadingMainFrame(), false)
  1202. w.webContents.on('did-start-loading', function () {
  1203. assert.equal(w.webContents.isLoadingMainFrame(), true)
  1204. done()
  1205. })
  1206. w.webContents.loadURL(`${server.url}/page2`)
  1207. })
  1208. w.webContents.loadURL(server.url)
  1209. })
  1210. })
  1211. })
  1212. describe('window states (excluding Linux)', function () {
  1213. // Not implemented on Linux.
  1214. if (process.platform === 'linux') return
  1215. describe('movable state', function () {
  1216. it('can be changed with movable option', function () {
  1217. w.destroy()
  1218. w = new BrowserWindow({show: false, movable: false})
  1219. assert.equal(w.isMovable(), false)
  1220. })
  1221. it('can be changed with setMovable method', function () {
  1222. assert.equal(w.isMovable(), true)
  1223. w.setMovable(false)
  1224. assert.equal(w.isMovable(), false)
  1225. w.setMovable(true)
  1226. assert.equal(w.isMovable(), true)
  1227. })
  1228. })
  1229. describe('minimizable state', function () {
  1230. it('can be changed with minimizable option', function () {
  1231. w.destroy()
  1232. w = new BrowserWindow({show: false, minimizable: false})
  1233. assert.equal(w.isMinimizable(), false)
  1234. })
  1235. it('can be changed with setMinimizable method', function () {
  1236. assert.equal(w.isMinimizable(), true)
  1237. w.setMinimizable(false)
  1238. assert.equal(w.isMinimizable(), false)
  1239. w.setMinimizable(true)
  1240. assert.equal(w.isMinimizable(), true)
  1241. })
  1242. })
  1243. describe('maximizable state', function () {
  1244. it('can be changed with maximizable option', function () {
  1245. w.destroy()
  1246. w = new BrowserWindow({show: false, maximizable: false})
  1247. assert.equal(w.isMaximizable(), false)
  1248. })
  1249. it('can be changed with setMaximizable method', function () {
  1250. assert.equal(w.isMaximizable(), true)
  1251. w.setMaximizable(false)
  1252. assert.equal(w.isMaximizable(), false)
  1253. w.setMaximizable(true)
  1254. assert.equal(w.isMaximizable(), true)
  1255. })
  1256. it('is not affected when changing other states', function () {
  1257. w.setMaximizable(false)
  1258. assert.equal(w.isMaximizable(), false)
  1259. w.setMinimizable(false)
  1260. assert.equal(w.isMaximizable(), false)
  1261. w.setClosable(false)
  1262. assert.equal(w.isMaximizable(), false)
  1263. w.setMaximizable(true)
  1264. assert.equal(w.isMaximizable(), true)
  1265. w.setClosable(true)
  1266. assert.equal(w.isMaximizable(), true)
  1267. w.setFullScreenable(false)
  1268. assert.equal(w.isMaximizable(), true)
  1269. w.setResizable(false)
  1270. assert.equal(w.isMaximizable(), true)
  1271. })
  1272. })
  1273. describe('fullscreenable state', function () {
  1274. // Only implemented on macOS.
  1275. if (process.platform !== 'darwin') return
  1276. it('can be changed with fullscreenable option', function () {
  1277. w.destroy()
  1278. w = new BrowserWindow({show: false, fullscreenable: false})
  1279. assert.equal(w.isFullScreenable(), false)
  1280. })
  1281. it('can be changed with setFullScreenable method', function () {
  1282. assert.equal(w.isFullScreenable(), true)
  1283. w.setFullScreenable(false)
  1284. assert.equal(w.isFullScreenable(), false)
  1285. w.setFullScreenable(true)
  1286. assert.equal(w.isFullScreenable(), true)
  1287. })
  1288. })
  1289. describe('kiosk state', function () {
  1290. // Only implemented on macOS.
  1291. if (process.platform !== 'darwin') return
  1292. it('can be changed with setKiosk method', function () {
  1293. w.destroy()
  1294. w = new BrowserWindow()
  1295. w.setKiosk(true)
  1296. assert.equal(w.isKiosk(), true)
  1297. w.setKiosk(false)
  1298. assert.equal(w.isKiosk(), false)
  1299. })
  1300. })
  1301. describe('fullscreen state', function () {
  1302. // Only implemented on macOS.
  1303. if (process.platform !== 'darwin') return
  1304. it('can be changed with setFullScreen method', function (done) {
  1305. w.destroy()
  1306. w = new BrowserWindow()
  1307. w.once('enter-full-screen', () => {
  1308. assert.equal(w.isFullScreen(), true)
  1309. w.setFullScreen(false)
  1310. })
  1311. w.once('leave-full-screen', () => {
  1312. assert.equal(w.isFullScreen(), false)
  1313. done()
  1314. })
  1315. w.setFullScreen(true)
  1316. })
  1317. it('should not be changed by setKiosk method', function (done) {
  1318. w.destroy()
  1319. w = new BrowserWindow()
  1320. w.once('enter-full-screen', () => {
  1321. assert.equal(w.isFullScreen(), true)
  1322. w.setKiosk(true)
  1323. w.setKiosk(false)
  1324. assert.equal(w.isFullScreen(), true)
  1325. w.setFullScreen(false)
  1326. })
  1327. w.once('leave-full-screen', () => {
  1328. assert.equal(w.isFullScreen(), false)
  1329. done()
  1330. })
  1331. w.setFullScreen(true)
  1332. })
  1333. })
  1334. describe('closable state', function () {
  1335. it('can be changed with closable option', function () {
  1336. w.destroy()
  1337. w = new BrowserWindow({show: false, closable: false})
  1338. assert.equal(w.isClosable(), false)
  1339. })
  1340. it('can be changed with setClosable method', function () {
  1341. assert.equal(w.isClosable(), true)
  1342. w.setClosable(false)
  1343. assert.equal(w.isClosable(), false)
  1344. w.setClosable(true)
  1345. assert.equal(w.isClosable(), true)
  1346. })
  1347. })
  1348. describe('hasShadow state', function () {
  1349. // On Window there is no shadow by default and it can not be changed
  1350. // dynamically.
  1351. it('can be changed with hasShadow option', function () {
  1352. w.destroy()
  1353. let hasShadow = process.platform !== 'darwin'
  1354. w = new BrowserWindow({show: false, hasShadow: hasShadow})
  1355. assert.equal(w.hasShadow(), hasShadow)
  1356. })
  1357. it('can be changed with setHasShadow method', function () {
  1358. if (process.platform !== 'darwin') return
  1359. assert.equal(w.hasShadow(), true)
  1360. w.setHasShadow(false)
  1361. assert.equal(w.hasShadow(), false)
  1362. w.setHasShadow(true)
  1363. assert.equal(w.hasShadow(), true)
  1364. })
  1365. })
  1366. })
  1367. describe('BrowserWindow.restore()', function () {
  1368. it('should restore the previous window size', function () {
  1369. if (w != null) w.destroy()
  1370. w = new BrowserWindow({
  1371. minWidth: 800,
  1372. width: 800
  1373. })
  1374. const initialSize = w.getSize()
  1375. w.minimize()
  1376. w.restore()
  1377. assertBoundsEqual(w.getSize(), initialSize)
  1378. })
  1379. })
  1380. describe('BrowserWindow.unmaximize()', function () {
  1381. it('should restore the previous window position', function () {
  1382. if (w != null) w.destroy()
  1383. w = new BrowserWindow()
  1384. const initialPosition = w.getPosition()
  1385. w.maximize()
  1386. w.unmaximize()
  1387. assertBoundsEqual(w.getPosition(), initialPosition)
  1388. })
  1389. })
  1390. describe('parent window', function () {
  1391. let c = null
  1392. beforeEach(function () {
  1393. if (c != null) c.destroy()
  1394. c = new BrowserWindow({show: false, parent: w})
  1395. })
  1396. afterEach(function () {
  1397. if (c != null) c.destroy()
  1398. c = null
  1399. })
  1400. describe('parent option', function () {
  1401. it('sets parent window', function () {
  1402. assert.equal(c.getParentWindow(), w)
  1403. })
  1404. it('adds window to child windows of parent', function () {
  1405. assert.deepEqual(w.getChildWindows(), [c])
  1406. })
  1407. it('removes from child windows of parent when window is closed', function (done) {
  1408. c.once('closed', () => {
  1409. assert.deepEqual(w.getChildWindows(), [])
  1410. done()
  1411. })
  1412. c.close()
  1413. })
  1414. })
  1415. describe('win.setParentWindow(parent)', function () {
  1416. if (process.platform === 'win32') return
  1417. beforeEach(function () {
  1418. if (c != null) c.destroy()
  1419. c = new BrowserWindow({show: false})
  1420. })
  1421. it('sets parent window', function () {
  1422. assert.equal(w.getParentWindow(), null)
  1423. assert.equal(c.getParentWindow(), null)
  1424. c.setParentWindow(w)
  1425. assert.equal(c.getParentWindow(), w)
  1426. c.setParentWindow(null)
  1427. assert.equal(c.getParentWindow(), null)
  1428. })
  1429. it('adds window to child windows of parent', function () {
  1430. assert.deepEqual(w.getChildWindows(), [])
  1431. c.setParentWindow(w)
  1432. assert.deepEqual(w.getChildWindows(), [c])
  1433. c.setParentWindow(null)
  1434. assert.deepEqual(w.getChildWindows(), [])
  1435. })
  1436. it('removes from child windows of parent when window is closed', function (done) {
  1437. c.once('closed', () => {
  1438. assert.deepEqual(w.getChildWindows(), [])
  1439. done()
  1440. })
  1441. c.setParentWindow(w)
  1442. c.close()
  1443. })
  1444. })
  1445. describe('modal option', function () {
  1446. // The isEnabled API is not reliable on macOS.
  1447. if (process.platform === 'darwin') return
  1448. beforeEach(function () {
  1449. if (c != null) c.destroy()
  1450. c = new BrowserWindow({show: false, parent: w, modal: true})
  1451. })
  1452. it('disables parent window', function () {
  1453. assert.equal(w.isEnabled(), true)
  1454. c.show()
  1455. assert.equal(w.isEnabled(), false)
  1456. })
  1457. it('enables parent window when closed', function (done) {
  1458. c.once('closed', () => {
  1459. assert.equal(w.isEnabled(), true)
  1460. done()
  1461. })
  1462. c.show()
  1463. c.close()
  1464. })
  1465. it('disables parent window recursively', function () {
  1466. let c2 = new BrowserWindow({show: false, parent: w, modal: true})
  1467. c.show()
  1468. assert.equal(w.isEnabled(), false)
  1469. c2.show()
  1470. assert.equal(w.isEnabled(), false)
  1471. c.destroy()
  1472. assert.equal(w.isEnabled(), false)
  1473. c2.destroy()
  1474. assert.equal(w.isEnabled(), true)
  1475. })
  1476. })
  1477. })
  1478. describe('window.webContents.send(channel, args...)', function () {
  1479. it('throws an error when the channel is missing', function () {
  1480. assert.throws(function () {
  1481. w.webContents.send()
  1482. }, 'Missing required channel argument')
  1483. assert.throws(function () {
  1484. w.webContents.send(null)
  1485. }, 'Missing required channel argument')
  1486. })
  1487. })
  1488. describe('dev tool extensions', function () {
  1489. let showPanelTimeoutId
  1490. const showLastDevToolsPanel = () => {
  1491. w.webContents.once('devtools-opened', function () {
  1492. const show = function () {
  1493. if (w == null || w.isDestroyed()) {
  1494. return
  1495. }
  1496. const {devToolsWebContents} = w
  1497. if (devToolsWebContents == null || devToolsWebContents.isDestroyed()) {
  1498. return
  1499. }
  1500. const showLastPanel = function () {
  1501. const lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
  1502. WebInspector.inspectorView.showPanel(lastPanelId)
  1503. }
  1504. devToolsWebContents.executeJavaScript(`(${showLastPanel})()`, false, () => {
  1505. showPanelTimeoutId = setTimeout(show, 100)
  1506. })
  1507. }
  1508. showPanelTimeoutId = setTimeout(show, 100)
  1509. })
  1510. }
  1511. afterEach(function () {
  1512. clearTimeout(showPanelTimeoutId)
  1513. })
  1514. describe('BrowserWindow.addDevToolsExtension', function () {
  1515. beforeEach(function () {
  1516. BrowserWindow.removeDevToolsExtension('foo')
  1517. assert.equal(BrowserWindow.getDevToolsExtensions().hasOwnProperty('foo'), false)
  1518. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  1519. BrowserWindow.addDevToolsExtension(extensionPath)
  1520. assert.equal(BrowserWindow.getDevToolsExtensions().hasOwnProperty('foo'), true)
  1521. showLastDevToolsPanel()
  1522. w.loadURL('about:blank')
  1523. })
  1524. it('throws errors for missing manifest.json files', function () {
  1525. assert.throws(function () {
  1526. BrowserWindow.addDevToolsExtension(path.join(__dirname, 'does-not-exist'))
  1527. }, /ENOENT: no such file or directory/)
  1528. })
  1529. it('throws errors for invalid manifest.json files', function () {
  1530. assert.throws(function () {
  1531. BrowserWindow.addDevToolsExtension(path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest'))
  1532. }, /Unexpected token }/)
  1533. })
  1534. describe('when the devtools is docked', function () {
  1535. it('creates the extension', function (done) {
  1536. w.webContents.openDevTools({mode: 'bottom'})
  1537. ipcMain.once('answer', function (event, message) {
  1538. assert.equal(message.runtimeId, 'foo')
  1539. assert.equal(message.tabId, w.webContents.id)
  1540. assert.equal(message.i18nString, 'foo - bar (baz)')
  1541. assert.deepEqual(message.storageItems, {
  1542. local: {
  1543. set: {hello: 'world', world: 'hello'},
  1544. remove: {world: 'hello'},
  1545. clear: {}
  1546. },
  1547. sync: {
  1548. set: {foo: 'bar', bar: 'foo'},
  1549. remove: {foo: 'bar'},
  1550. clear: {}
  1551. }
  1552. })
  1553. done()
  1554. })
  1555. })
  1556. })
  1557. describe('when the devtools is undocked', function () {
  1558. it('creates the extension', function (done) {
  1559. w.webContents.openDevTools({mode: 'undocked'})
  1560. ipcMain.once('answer', function (event, message, extensionId) {
  1561. assert.equal(message.runtimeId, 'foo')
  1562. assert.equal(message.tabId, w.webContents.id)
  1563. done()
  1564. })
  1565. })
  1566. })
  1567. })
  1568. it('works when used with partitions', function (done) {
  1569. if (w != null) {
  1570. w.destroy()
  1571. }
  1572. w = new BrowserWindow({
  1573. show: false,
  1574. webPreferences: {
  1575. partition: 'temp'
  1576. }
  1577. })
  1578. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  1579. BrowserWindow.removeDevToolsExtension('foo')
  1580. BrowserWindow.addDevToolsExtension(extensionPath)
  1581. showLastDevToolsPanel()
  1582. w.loadURL('about:blank')
  1583. w.webContents.openDevTools({mode: 'bottom'})
  1584. ipcMain.once('answer', function (event, message) {
  1585. assert.equal(message.runtimeId, 'foo')
  1586. done()
  1587. })
  1588. })
  1589. it('serializes the registered extensions on quit', function () {
  1590. var extensionName = 'foo'
  1591. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName)
  1592. var serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions')
  1593. BrowserWindow.addDevToolsExtension(extensionPath)
  1594. app.emit('will-quit')
  1595. assert.deepEqual(JSON.parse(fs.readFileSync(serializedPath)), [extensionPath])
  1596. BrowserWindow.removeDevToolsExtension(extensionName)
  1597. app.emit('will-quit')
  1598. assert.equal(fs.existsSync(serializedPath), false)
  1599. })
  1600. })
  1601. describe('window.webContents.executeJavaScript', function () {
  1602. var expected = 'hello, world!'
  1603. var expectedErrorMsg = 'woops!'
  1604. var code = `(() => "${expected}")()`
  1605. var asyncCode = `(() => new Promise(r => setTimeout(() => r("${expected}"), 500)))()`
  1606. var badAsyncCode = `(() => new Promise((r, e) => setTimeout(() => e("${expectedErrorMsg}"), 500)))()`
  1607. it('doesnt throw when no calback is provided', function () {
  1608. const result = ipcRenderer.sendSync('executeJavaScript', code, false)
  1609. assert.equal(result, 'success')
  1610. })
  1611. it('returns result when calback is provided', function (done) {
  1612. ipcRenderer.send('executeJavaScript', code, true)
  1613. ipcRenderer.once('executeJavaScript-response', function (event, result) {
  1614. assert.equal(result, expected)
  1615. done()
  1616. })
  1617. })
  1618. it('returns result if the code returns an asyncronous promise', function (done) {
  1619. ipcRenderer.send('executeJavaScript', asyncCode, true)
  1620. ipcRenderer.once('executeJavaScript-response', function (event, result) {
  1621. assert.equal(result, expected)
  1622. done()
  1623. })
  1624. })
  1625. it('resolves the returned promise with the result', function (done) {
  1626. ipcRenderer.send('executeJavaScript', code, true)
  1627. ipcRenderer.once('executeJavaScript-promise-response', function (event, result) {
  1628. assert.equal(result, expected)
  1629. done()
  1630. })
  1631. })
  1632. it('resolves the returned promise with the result if the code returns an asyncronous promise', function (done) {
  1633. ipcRenderer.send('executeJavaScript', asyncCode, true)
  1634. ipcRenderer.once('executeJavaScript-promise-response', function (event, result) {
  1635. assert.equal(result, expected)
  1636. done()
  1637. })
  1638. })
  1639. it('rejects the returned promise if an async error is thrown', function (done) {
  1640. ipcRenderer.send('executeJavaScript', badAsyncCode, true)
  1641. ipcRenderer.once('executeJavaScript-promise-error', function (event, error) {
  1642. assert.equal(error, expectedErrorMsg)
  1643. done()
  1644. })
  1645. })
  1646. it('works after page load and during subframe load', function (done) {
  1647. w.webContents.once('did-finish-load', function () {
  1648. // initiate a sub-frame load, then try and execute script during it
  1649. w.webContents.executeJavaScript(`
  1650. var iframe = document.createElement('iframe')
  1651. iframe.src = '${server.url}/slow'
  1652. document.body.appendChild(iframe)
  1653. `, function () {
  1654. w.webContents.executeJavaScript('console.log(\'hello\')', function () {
  1655. done()
  1656. })
  1657. })
  1658. })
  1659. w.loadURL(server.url)
  1660. })
  1661. it('executes after page load', function (done) {
  1662. w.webContents.executeJavaScript(code, function (result) {
  1663. assert.equal(result, expected)
  1664. done()
  1665. })
  1666. w.loadURL(server.url)
  1667. })
  1668. it('works with result objects that have DOM class prototypes', function (done) {
  1669. w.webContents.executeJavaScript('document.location', function (result) {
  1670. assert.equal(result.origin, server.url)
  1671. assert.equal(result.protocol, 'http:')
  1672. done()
  1673. })
  1674. w.loadURL(server.url)
  1675. })
  1676. })
  1677. describe('previewFile', function () {
  1678. it('opens the path in Quick Look on macOS', function () {
  1679. if (process.platform !== 'darwin') return
  1680. assert.doesNotThrow(function () {
  1681. w.previewFile(__filename)
  1682. w.closeFilePreview()
  1683. })
  1684. })
  1685. })
  1686. describe('contextIsolation option', () => {
  1687. const expectedContextData = {
  1688. preloadContext: {
  1689. preloadProperty: 'number',
  1690. pageProperty: 'undefined',
  1691. typeofRequire: 'function',
  1692. typeofProcess: 'object',
  1693. typeofArrayPush: 'function',
  1694. typeofFunctionApply: 'function'
  1695. },
  1696. pageContext: {
  1697. preloadProperty: 'undefined',
  1698. pageProperty: 'string',
  1699. typeofRequire: 'undefined',
  1700. typeofProcess: 'undefined',
  1701. typeofArrayPush: 'number',
  1702. typeofFunctionApply: 'boolean',
  1703. typeofPreloadExecuteJavaScriptProperty: 'number',
  1704. typeofOpenedWindow: 'object',
  1705. documentHidden: true,
  1706. documentVisibilityState: 'hidden'
  1707. }
  1708. }
  1709. beforeEach(() => {
  1710. if (w != null) w.destroy()
  1711. w = new BrowserWindow({
  1712. show: false,
  1713. webPreferences: {
  1714. contextIsolation: true,
  1715. preload: path.join(fixtures, 'api', 'isolated-preload.js')
  1716. }
  1717. })
  1718. })
  1719. it('separates the page context from the Electron/preload context', (done) => {
  1720. ipcMain.once('isolated-world', (event, data) => {
  1721. assert.deepEqual(data, expectedContextData)
  1722. done()
  1723. })
  1724. w.loadURL('file://' + fixtures + '/api/isolated.html')
  1725. })
  1726. it('recreates the contexts on reload', (done) => {
  1727. w.webContents.once('did-finish-load', () => {
  1728. ipcMain.once('isolated-world', (event, data) => {
  1729. assert.deepEqual(data, expectedContextData)
  1730. done()
  1731. })
  1732. w.webContents.reload()
  1733. })
  1734. w.loadURL('file://' + fixtures + '/api/isolated.html')
  1735. })
  1736. it('enables context isolation on child windows', function (done) {
  1737. app.once('browser-window-created', function (event, window) {
  1738. assert.equal(window.webContents.getWebPreferences().contextIsolation, true)
  1739. done()
  1740. })
  1741. w.loadURL('file://' + fixtures + '/pages/window-open.html')
  1742. })
  1743. })
  1744. describe('offscreen rendering', function () {
  1745. beforeEach(function () {
  1746. if (w != null) w.destroy()
  1747. w = new BrowserWindow({
  1748. show: false,
  1749. webPreferences: {
  1750. backgroundThrottling: false,
  1751. offscreen: true
  1752. }
  1753. })
  1754. })
  1755. it('creates offscreen window', function (done) {
  1756. w.webContents.once('paint', function (event, rect, data, size) {
  1757. assert.notEqual(data.length, 0)
  1758. done()
  1759. })
  1760. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1761. })
  1762. describe('window.webContents.isOffscreen()', function () {
  1763. it('is true for offscreen type', function () {
  1764. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1765. assert.equal(w.webContents.isOffscreen(), true)
  1766. })
  1767. it('is false for regular window', function () {
  1768. let c = new BrowserWindow({show: false})
  1769. assert.equal(c.webContents.isOffscreen(), false)
  1770. c.destroy()
  1771. })
  1772. })
  1773. describe('window.webContents.isPainting()', function () {
  1774. it('returns whether is currently painting', function (done) {
  1775. w.webContents.once('paint', function (event, rect, data, size) {
  1776. assert.equal(w.webContents.isPainting(), true)
  1777. done()
  1778. })
  1779. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1780. })
  1781. })
  1782. describe('window.webContents.stopPainting()', function () {
  1783. it('stops painting', function (done) {
  1784. w.webContents.on('dom-ready', function () {
  1785. w.webContents.stopPainting()
  1786. assert.equal(w.webContents.isPainting(), false)
  1787. done()
  1788. })
  1789. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1790. })
  1791. })
  1792. describe('window.webContents.startPainting()', function () {
  1793. it('starts painting', function (done) {
  1794. w.webContents.on('dom-ready', function () {
  1795. w.webContents.stopPainting()
  1796. w.webContents.startPainting()
  1797. w.webContents.once('paint', function (event, rect, data, size) {
  1798. assert.equal(w.webContents.isPainting(), true)
  1799. done()
  1800. })
  1801. })
  1802. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1803. })
  1804. })
  1805. describe('window.webContents.getFrameRate()', function () {
  1806. it('has default frame rate', function (done) {
  1807. w.webContents.once('paint', function (event, rect, data, size) {
  1808. assert.equal(w.webContents.getFrameRate(), 60)
  1809. done()
  1810. })
  1811. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1812. })
  1813. })
  1814. describe('window.webContents.setFrameRate(frameRate)', function () {
  1815. it('sets custom frame rate', function (done) {
  1816. w.webContents.on('dom-ready', function () {
  1817. w.webContents.setFrameRate(30)
  1818. w.webContents.once('paint', function (event, rect, data, size) {
  1819. assert.equal(w.webContents.getFrameRate(), 30)
  1820. done()
  1821. })
  1822. })
  1823. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  1824. })
  1825. })
  1826. })
  1827. })
  1828. const assertBoundsEqual = (actual, expect) => {
  1829. if (!isScaleFactorRounding()) {
  1830. assert.deepEqual(expect, actual)
  1831. } else if (Array.isArray(actual)) {
  1832. assertWithinDelta(actual[0], expect[0], 1, 'x')
  1833. assertWithinDelta(actual[1], expect[1], 1, 'y')
  1834. } else {
  1835. assertWithinDelta(actual.x, expect.x, 1, 'x')
  1836. assertWithinDelta(actual.y, expect.y, 1, 'y')
  1837. assertWithinDelta(actual.width, expect.width, 1, 'width')
  1838. assertWithinDelta(actual.height, expect.height, 1, 'height')
  1839. }
  1840. }
  1841. const assertWithinDelta = (actual, expect, delta, label) => {
  1842. const result = Math.abs(actual - expect)
  1843. assert.ok(result <= delta, `${label} value of ${expect} was not within ${delta} of ${actual}`)
  1844. }
  1845. // Is the display's scale factor possibly causing rounding of pixel coordinate
  1846. // values?
  1847. const isScaleFactorRounding = () => {
  1848. const {scaleFactor} = screen.getPrimaryDisplay()
  1849. // Return true if scale factor is non-integer value
  1850. if (Math.round(scaleFactor) !== scaleFactor) return true
  1851. // Return true if scale factor is odd number above 2
  1852. return scaleFactor > 2 && scaleFactor % 2 === 1
  1853. }