api-browser-window-spec.js 104 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381
  1. 'use strict'
  2. const assert = require('assert')
  3. const {expect} = require('chai')
  4. const fs = require('fs')
  5. const path = require('path')
  6. const os = require('os')
  7. const qs = require('querystring')
  8. const http = require('http')
  9. const {closeWindow} = require('./window-helpers')
  10. const {emittedOnce} = require('./events-helpers')
  11. const {ipcRenderer, remote, screen} = require('electron')
  12. const {app, ipcMain, BrowserWindow, BrowserView, protocol, session, webContents} = remote
  13. const features = process.atomBinding('features')
  14. const isCI = remote.getGlobal('isCi')
  15. const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled')
  16. describe('BrowserWindow module', () => {
  17. const fixtures = path.resolve(__dirname, 'fixtures')
  18. let w = null
  19. let ws = null
  20. let server
  21. let postData
  22. const defaultOptions = {
  23. show: false,
  24. width: 400,
  25. height: 400,
  26. webPreferences: {
  27. backgroundThrottling: false
  28. }
  29. }
  30. const openTheWindow = async (options = defaultOptions) => {
  31. // The `afterEach` hook isn't called if a test fails,
  32. // we should make sure that the window is closed ourselves.
  33. await closeTheWindow()
  34. w = new BrowserWindow(options)
  35. return w
  36. }
  37. const closeTheWindow = function () {
  38. return closeWindow(w).then(() => { w = null })
  39. }
  40. before((done) => {
  41. const filePath = path.join(fixtures, 'pages', 'a.html')
  42. const fileStats = fs.statSync(filePath)
  43. postData = [
  44. {
  45. type: 'rawData',
  46. bytes: Buffer.from('username=test&file=')
  47. },
  48. {
  49. type: 'file',
  50. filePath: filePath,
  51. offset: 0,
  52. length: fileStats.size,
  53. modificationTime: fileStats.mtime.getTime() / 1000
  54. }
  55. ]
  56. server = http.createServer((req, res) => {
  57. function respond () {
  58. if (req.method === 'POST') {
  59. let body = ''
  60. req.on('data', (data) => {
  61. if (data) body += data
  62. })
  63. req.on('end', () => {
  64. let parsedData = qs.parse(body)
  65. fs.readFile(filePath, (err, data) => {
  66. if (err) return
  67. if (parsedData.username === 'test' &&
  68. parsedData.file === data.toString()) {
  69. res.end()
  70. }
  71. })
  72. })
  73. } else {
  74. res.end()
  75. }
  76. }
  77. setTimeout(respond, req.url.includes('slow') ? 200 : 0)
  78. })
  79. server.listen(0, '127.0.0.1', () => {
  80. server.url = `http://127.0.0.1:${server.address().port}`
  81. done()
  82. })
  83. })
  84. after(() => {
  85. server.close()
  86. server = null
  87. })
  88. beforeEach(() => {
  89. w = new BrowserWindow({
  90. show: false,
  91. width: 400,
  92. height: 400,
  93. webPreferences: {
  94. backgroundThrottling: false
  95. }
  96. })
  97. })
  98. afterEach(closeTheWindow)
  99. describe('BrowserWindow constructor', () => {
  100. it('allows passing void 0 as the webContents', () => {
  101. w.close()
  102. w = null
  103. w = new BrowserWindow({
  104. webContents: void 0
  105. })
  106. })
  107. })
  108. describe('BrowserWindow.close()', () => {
  109. let server
  110. before((done) => {
  111. server = http.createServer((request, response) => {
  112. switch (request.url) {
  113. case '/404':
  114. response.statusCode = '404'
  115. response.end()
  116. break
  117. case '/301':
  118. response.statusCode = '301'
  119. response.setHeader('Location', '/200')
  120. response.end()
  121. break
  122. case '/200':
  123. response.statusCode = '200'
  124. response.end('hello')
  125. break
  126. case '/title':
  127. response.statusCode = '200'
  128. response.end('<title>Hello</title>')
  129. break
  130. default:
  131. done('unsupported endpoint')
  132. }
  133. }).listen(0, '127.0.0.1', () => {
  134. server.url = 'http://127.0.0.1:' + server.address().port
  135. done()
  136. })
  137. })
  138. after(() => {
  139. server.close()
  140. server = null
  141. })
  142. it('should emit unload handler', (done) => {
  143. w.webContents.on('did-finish-load', () => { w.close() })
  144. w.once('closed', () => {
  145. const test = path.join(fixtures, 'api', 'unload')
  146. const content = fs.readFileSync(test)
  147. fs.unlinkSync(test)
  148. assert.equal(String(content), 'unload')
  149. done()
  150. })
  151. w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html'))
  152. })
  153. it('should emit beforeunload handler', (done) => {
  154. w.once('onbeforeunload', () => { done() })
  155. w.webContents.on('did-finish-load', () => { w.close() })
  156. w.loadURL(`file://${path.join(fixtures, 'api', 'beforeunload-false.html')}`)
  157. })
  158. it('should not crash when invoked synchronously inside navigation observer', (done) => {
  159. const events = [
  160. { name: 'did-start-loading', url: `${server.url}/200` },
  161. { name: 'dom-ready', url: `${server.url}/200` },
  162. { name: 'page-title-updated', url: `${server.url}/title` },
  163. { name: 'did-stop-loading', url: `${server.url}/200` },
  164. { name: 'did-finish-load', url: `${server.url}/200` },
  165. { name: 'did-frame-finish-load', url: `${server.url}/200` },
  166. { name: 'did-fail-load', url: `${server.url}/404` }
  167. ]
  168. const responseEvent = 'window-webContents-destroyed'
  169. function * genNavigationEvent () {
  170. let eventOptions = null
  171. while ((eventOptions = events.shift()) && events.length) {
  172. let w = new BrowserWindow({show: false})
  173. eventOptions.id = w.id
  174. eventOptions.responseEvent = responseEvent
  175. ipcRenderer.send('test-webcontents-navigation-observer', eventOptions)
  176. yield 1
  177. }
  178. }
  179. let gen = genNavigationEvent()
  180. ipcRenderer.on(responseEvent, () => {
  181. if (!gen.next().value) done()
  182. })
  183. gen.next()
  184. })
  185. })
  186. describe('window.close()', () => {
  187. it('should emit unload handler', (done) => {
  188. w.once('closed', () => {
  189. const test = path.join(fixtures, 'api', 'close')
  190. const content = fs.readFileSync(test)
  191. fs.unlinkSync(test)
  192. assert.equal(String(content), 'close')
  193. done()
  194. })
  195. w.loadURL(`file://${path.join(fixtures, 'api', 'close.html')}`)
  196. })
  197. it('should emit beforeunload handler', (done) => {
  198. w.once('onbeforeunload', () => { done() })
  199. w.loadURL(`file://${path.join(fixtures, 'api', 'close-beforeunload-false.html')}`)
  200. })
  201. })
  202. describe('BrowserWindow.destroy()', () => {
  203. it('prevents users to access methods of webContents', () => {
  204. const contents = w.webContents
  205. w.destroy()
  206. assert.throws(() => {
  207. contents.getId()
  208. }, /Object has been destroyed/)
  209. })
  210. it('should not crash when destroying windows with pending events', (done) => {
  211. const responseEvent = 'destroy-test-completed'
  212. ipcRenderer.on(responseEvent, () => done())
  213. ipcRenderer.send('test-browserwindow-destroy', { responseEvent })
  214. })
  215. })
  216. describe('BrowserWindow.loadURL(url)', () => {
  217. it('should emit did-start-loading event', (done) => {
  218. w.webContents.on('did-start-loading', () => { done() })
  219. w.loadURL('about:blank')
  220. })
  221. it('should emit ready-to-show event', (done) => {
  222. w.on('ready-to-show', () => { done() })
  223. w.loadURL('about:blank')
  224. })
  225. it('should emit did-fail-load event for files that do not exist', (done) => {
  226. w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => {
  227. assert.equal(code, -6)
  228. assert.equal(desc, 'ERR_FILE_NOT_FOUND')
  229. assert.equal(isMainFrame, true)
  230. done()
  231. })
  232. w.loadURL('file://a.txt')
  233. })
  234. it('should emit did-fail-load event for invalid URL', (done) => {
  235. w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => {
  236. assert.equal(desc, 'ERR_INVALID_URL')
  237. assert.equal(code, -300)
  238. assert.equal(isMainFrame, true)
  239. done()
  240. })
  241. w.loadURL('http://example:port')
  242. })
  243. it('should set `mainFrame = false` on did-fail-load events in iframes', (done) => {
  244. w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => {
  245. assert.equal(isMainFrame, false)
  246. done()
  247. })
  248. w.loadURL(`file://${path.join(fixtures, 'api', 'did-fail-load-iframe.html')}`)
  249. })
  250. it('does not crash in did-fail-provisional-load handler', (done) => {
  251. w.webContents.once('did-fail-provisional-load', () => {
  252. w.loadURL('http://127.0.0.1:11111')
  253. done()
  254. })
  255. w.loadURL('http://127.0.0.1:11111')
  256. })
  257. it('should emit did-fail-load event for URL exceeding character limit', (done) => {
  258. w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => {
  259. assert.equal(desc, 'ERR_INVALID_URL')
  260. assert.equal(code, -300)
  261. assert.equal(isMainFrame, true)
  262. done()
  263. })
  264. const data = Buffer.alloc(2 * 1024 * 1024).toString('base64')
  265. w.loadURL(`data:image/png;base64,${data}`)
  266. })
  267. describe('POST navigations', () => {
  268. afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) })
  269. it('supports specifying POST data', (done) => {
  270. w.webContents.on('did-finish-load', () => done())
  271. w.loadURL(server.url, {postData: postData})
  272. })
  273. it('sets the content type header on URL encoded forms', (done) => {
  274. w.webContents.on('did-finish-load', () => {
  275. w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
  276. assert.equal(details.requestHeaders['content-type'], 'application/x-www-form-urlencoded')
  277. done()
  278. })
  279. w.webContents.executeJavaScript(`
  280. form = document.createElement('form')
  281. document.body.appendChild(form)
  282. form.method = 'POST'
  283. form.target = '_blank'
  284. form.submit()
  285. `)
  286. })
  287. w.loadURL(server.url)
  288. })
  289. it('sets the content type header on multi part forms', (done) => {
  290. w.webContents.on('did-finish-load', () => {
  291. w.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
  292. assert(details.requestHeaders['content-type'].startsWith('multipart/form-data; boundary=----WebKitFormBoundary'))
  293. done()
  294. })
  295. w.webContents.executeJavaScript(`
  296. form = document.createElement('form')
  297. document.body.appendChild(form)
  298. form.method = 'POST'
  299. form.target = '_blank'
  300. form.enctype = 'multipart/form-data'
  301. file = document.createElement('input')
  302. file.type = 'file'
  303. file.name = 'file'
  304. form.appendChild(file)
  305. form.submit()
  306. `)
  307. })
  308. w.loadURL(server.url)
  309. })
  310. })
  311. it('should support support base url for data urls', (done) => {
  312. ipcMain.once('answer', (event, test) => {
  313. assert.equal(test, 'test')
  314. done()
  315. })
  316. w.loadURL('data:text/html,<script src="loaded-from-dataurl.js"></script>', {baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}`})
  317. })
  318. })
  319. describe('will-navigate event', () => {
  320. it('allows the window to be closed from the event listener', (done) => {
  321. ipcRenderer.send('close-on-will-navigate', w.id)
  322. ipcRenderer.once('closed-on-will-navigate', () => { done() })
  323. w.loadURL(`file://${fixtures}/pages/will-navigate.html`)
  324. })
  325. })
  326. describe('BrowserWindow.show()', () => {
  327. before(function () {
  328. if (isCI) {
  329. this.skip()
  330. }
  331. })
  332. it('should focus on window', () => {
  333. w.show()
  334. assert(w.isFocused())
  335. })
  336. it('should make the window visible', () => {
  337. w.show()
  338. assert(w.isVisible())
  339. })
  340. it('emits when window is shown', (done) => {
  341. w.once('show', () => {
  342. assert.equal(w.isVisible(), true)
  343. done()
  344. })
  345. w.show()
  346. })
  347. })
  348. describe('BrowserWindow.hide()', () => {
  349. before(function () {
  350. if (isCI) {
  351. this.skip()
  352. }
  353. })
  354. it('should defocus on window', () => {
  355. w.hide()
  356. assert(!w.isFocused())
  357. })
  358. it('should make the window not visible', () => {
  359. w.show()
  360. w.hide()
  361. assert(!w.isVisible())
  362. })
  363. it('emits when window is hidden', (done) => {
  364. w.show()
  365. w.once('hide', () => {
  366. assert.equal(w.isVisible(), false)
  367. done()
  368. })
  369. w.hide()
  370. })
  371. })
  372. describe('BrowserWindow.showInactive()', () => {
  373. it('should not focus on window', () => {
  374. w.showInactive()
  375. assert(!w.isFocused())
  376. })
  377. })
  378. describe('BrowserWindow.focus()', () => {
  379. it('does not make the window become visible', () => {
  380. assert.equal(w.isVisible(), false)
  381. w.focus()
  382. assert.equal(w.isVisible(), false)
  383. })
  384. })
  385. describe('BrowserWindow.blur()', () => {
  386. it('removes focus from window', () => {
  387. w.blur()
  388. assert(!w.isFocused())
  389. })
  390. })
  391. describe('BrowserWindow.getFocusedWindow()', (done) => {
  392. it('returns the opener window when dev tools window is focused', (done) => {
  393. w.show()
  394. w.webContents.once('devtools-focused', () => {
  395. assert.deepEqual(BrowserWindow.getFocusedWindow(), w)
  396. done()
  397. })
  398. w.webContents.openDevTools({mode: 'undocked'})
  399. })
  400. })
  401. describe('BrowserWindow.capturePage(rect, callback)', () => {
  402. it('calls the callback with a Buffer', (done) => {
  403. w.capturePage({
  404. x: 0,
  405. y: 0,
  406. width: 100,
  407. height: 100
  408. }, (image) => {
  409. assert.equal(image.isEmpty(), true)
  410. done()
  411. })
  412. })
  413. it('preserves transparency', (done) => {
  414. w.close()
  415. const width = 400
  416. const height = 400
  417. w = new BrowserWindow({
  418. show: false,
  419. width: width,
  420. height: height,
  421. transparent: true
  422. })
  423. w.loadURL('data:text/html,<html><body background-color: rgba(255,255,255,0)></body></html>')
  424. w.once('ready-to-show', () => {
  425. w.show()
  426. w.capturePage((image) => {
  427. let imgBuffer = image.toPNG()
  428. // Check 25th byte in the PNG
  429. // Values can be 0,2,3,4, or 6. We want 6, which is RGB + Alpha
  430. assert.equal(imgBuffer[25], 6)
  431. done()
  432. })
  433. })
  434. })
  435. })
  436. describe('BrowserWindow.setSize(width, height)', () => {
  437. it('sets the window size', (done) => {
  438. const size = [300, 400]
  439. w.once('resize', () => {
  440. assertBoundsEqual(w.getSize(), size)
  441. done()
  442. })
  443. w.setSize(size[0], size[1])
  444. })
  445. })
  446. describe('BrowserWindow.setMinimum/MaximumSize(width, height)', () => {
  447. it('sets the maximum and minimum size of the window', () => {
  448. assert.deepEqual(w.getMinimumSize(), [0, 0])
  449. assert.deepEqual(w.getMaximumSize(), [0, 0])
  450. w.setMinimumSize(100, 100)
  451. assertBoundsEqual(w.getMinimumSize(), [100, 100])
  452. assertBoundsEqual(w.getMaximumSize(), [0, 0])
  453. w.setMaximumSize(900, 600)
  454. assertBoundsEqual(w.getMinimumSize(), [100, 100])
  455. assertBoundsEqual(w.getMaximumSize(), [900, 600])
  456. })
  457. })
  458. describe('BrowserWindow.setAspectRatio(ratio)', () => {
  459. it('resets the behaviour when passing in 0', (done) => {
  460. const size = [300, 400]
  461. w.setAspectRatio(1 / 2)
  462. w.setAspectRatio(0)
  463. w.once('resize', () => {
  464. assertBoundsEqual(w.getSize(), size)
  465. done()
  466. })
  467. w.setSize(size[0], size[1])
  468. })
  469. })
  470. describe('BrowserWindow.setPosition(x, y)', () => {
  471. it('sets the window position', (done) => {
  472. const pos = [10, 10]
  473. w.once('move', () => {
  474. const newPos = w.getPosition()
  475. assert.equal(newPos[0], pos[0])
  476. assert.equal(newPos[1], pos[1])
  477. done()
  478. })
  479. w.setPosition(pos[0], pos[1])
  480. })
  481. })
  482. describe('BrowserWindow.setContentSize(width, height)', () => {
  483. it('sets the content size', () => {
  484. const size = [400, 400]
  485. w.setContentSize(size[0], size[1])
  486. var after = w.getContentSize()
  487. assert.equal(after[0], size[0])
  488. assert.equal(after[1], size[1])
  489. })
  490. it('works for a frameless window', () => {
  491. w.destroy()
  492. w = new BrowserWindow({
  493. show: false,
  494. frame: false,
  495. width: 400,
  496. height: 400
  497. })
  498. const size = [400, 400]
  499. w.setContentSize(size[0], size[1])
  500. const after = w.getContentSize()
  501. assert.equal(after[0], size[0])
  502. assert.equal(after[1], size[1])
  503. })
  504. })
  505. describe('BrowserWindow.setContentBounds(bounds)', () => {
  506. it('sets the content size and position', (done) => {
  507. const bounds = {x: 10, y: 10, width: 250, height: 250}
  508. w.once('resize', () => {
  509. assertBoundsEqual(w.getContentBounds(), bounds)
  510. done()
  511. })
  512. w.setContentBounds(bounds)
  513. })
  514. it('works for a frameless window', (done) => {
  515. w.destroy()
  516. w = new BrowserWindow({
  517. show: false,
  518. frame: false,
  519. width: 300,
  520. height: 300
  521. })
  522. const bounds = {x: 10, y: 10, width: 250, height: 250}
  523. w.once('resize', () => {
  524. assert.deepEqual(w.getContentBounds(), bounds)
  525. done()
  526. })
  527. w.setContentBounds(bounds)
  528. })
  529. })
  530. describe('BrowserWindow.setProgressBar(progress)', () => {
  531. it('sets the progress', () => {
  532. assert.doesNotThrow(() => {
  533. if (process.platform === 'darwin') {
  534. app.dock.setIcon(path.join(fixtures, 'assets', 'logo.png'))
  535. }
  536. w.setProgressBar(0.5)
  537. if (process.platform === 'darwin') {
  538. app.dock.setIcon(null)
  539. }
  540. w.setProgressBar(-1)
  541. })
  542. })
  543. it('sets the progress using "paused" mode', () => {
  544. assert.doesNotThrow(() => {
  545. w.setProgressBar(0.5, {mode: 'paused'})
  546. })
  547. })
  548. it('sets the progress using "error" mode', () => {
  549. assert.doesNotThrow(() => {
  550. w.setProgressBar(0.5, {mode: 'error'})
  551. })
  552. })
  553. it('sets the progress using "normal" mode', () => {
  554. assert.doesNotThrow(() => {
  555. w.setProgressBar(0.5, {mode: 'normal'})
  556. })
  557. })
  558. })
  559. describe('BrowserWindow.setAlwaysOnTop(flag, level)', () => {
  560. it('sets the window as always on top', () => {
  561. assert.equal(w.isAlwaysOnTop(), false)
  562. w.setAlwaysOnTop(true, 'screen-saver')
  563. assert.equal(w.isAlwaysOnTop(), true)
  564. w.setAlwaysOnTop(false)
  565. assert.equal(w.isAlwaysOnTop(), false)
  566. w.setAlwaysOnTop(true)
  567. assert.equal(w.isAlwaysOnTop(), true)
  568. })
  569. it('raises an error when relativeLevel is out of bounds', function () {
  570. if (process.platform !== 'darwin') {
  571. // FIXME(alexeykuzmin): Skip the test instead of marking it as passed.
  572. // afterEach hook won't be run if a test is skipped dynamically.
  573. // If afterEach isn't run current window won't be destroyed
  574. // and the next test will fail on assertion in `closeWindow()`.
  575. // this.skip()
  576. return
  577. }
  578. assert.throws(() => {
  579. w.setAlwaysOnTop(true, '', -2147483644)
  580. })
  581. assert.throws(() => {
  582. w.setAlwaysOnTop(true, '', 2147483632)
  583. })
  584. })
  585. })
  586. describe('BrowserWindow.alwaysOnTop() resets level on minimize', () => {
  587. before(function () {
  588. if (process.platform !== 'darwin') {
  589. this.skip()
  590. }
  591. })
  592. it('resets the windows level on minimize', () => {
  593. assert.equal(w.isAlwaysOnTop(), false)
  594. w.setAlwaysOnTop(true, 'screen-saver')
  595. assert.equal(w.isAlwaysOnTop(), true)
  596. w.minimize()
  597. assert.equal(w.isAlwaysOnTop(), false)
  598. w.restore()
  599. assert.equal(w.isAlwaysOnTop(), true)
  600. })
  601. })
  602. describe('BrowserWindow.setAutoHideCursor(autoHide)', () => {
  603. describe('on macOS', () => {
  604. before(function () {
  605. if (process.platform !== 'darwin') {
  606. this.skip()
  607. }
  608. })
  609. it('allows changing cursor auto-hiding', () => {
  610. assert.doesNotThrow(() => {
  611. w.setAutoHideCursor(false)
  612. w.setAutoHideCursor(true)
  613. })
  614. })
  615. })
  616. describe('on non-macOS platforms', () => {
  617. before(function () {
  618. if (process.platform === 'darwin') {
  619. this.skip()
  620. }
  621. })
  622. it('is not available', () => {
  623. assert.ok(!w.setAutoHideCursor)
  624. })
  625. })
  626. })
  627. describe('BrowserWindow.selectPreviousTab()', () => {
  628. before(function () {
  629. if (process.platform !== 'darwin') {
  630. this.skip()
  631. }
  632. })
  633. it('does not throw', () => {
  634. assert.doesNotThrow(() => {
  635. w.selectPreviousTab()
  636. })
  637. })
  638. })
  639. describe('BrowserWindow.selectNextTab()', () => {
  640. before(function () {
  641. if (process.platform !== 'darwin') {
  642. this.skip()
  643. }
  644. })
  645. it('does not throw', () => {
  646. assert.doesNotThrow(() => {
  647. w.selectNextTab()
  648. })
  649. })
  650. })
  651. describe('BrowserWindow.mergeAllWindows()', () => {
  652. before(function () {
  653. if (process.platform !== 'darwin') {
  654. this.skip()
  655. }
  656. })
  657. it('does not throw', () => {
  658. assert.doesNotThrow(() => {
  659. w.mergeAllWindows()
  660. })
  661. })
  662. })
  663. describe('BrowserWindow.moveTabToNewWindow()', () => {
  664. before(function () {
  665. if (process.platform !== 'darwin') {
  666. this.skip()
  667. }
  668. })
  669. it('does not throw', () => {
  670. assert.doesNotThrow(() => {
  671. w.moveTabToNewWindow()
  672. })
  673. })
  674. })
  675. describe('BrowserWindow.toggleTabBar()', () => {
  676. before(function () {
  677. if (process.platform !== 'darwin') {
  678. this.skip()
  679. }
  680. })
  681. it('does not throw', () => {
  682. assert.doesNotThrow(() => {
  683. w.toggleTabBar()
  684. })
  685. })
  686. })
  687. describe('BrowserWindow.addTabbedWindow()', () => {
  688. before(function () {
  689. if (process.platform !== 'darwin') {
  690. this.skip()
  691. }
  692. })
  693. it('does not throw', (done) => {
  694. const tabbedWindow = new BrowserWindow({})
  695. assert.doesNotThrow(() => {
  696. w.addTabbedWindow(tabbedWindow)
  697. })
  698. assert.equal(BrowserWindow.getAllWindows().length, 3) // Test window + w + tabbedWindow
  699. closeWindow(tabbedWindow, {assertSingleWindow: false}).then(() => {
  700. assert.equal(BrowserWindow.getAllWindows().length, 2) // Test window + w
  701. done()
  702. })
  703. })
  704. it('throws when called on itself', () => {
  705. assert.throws(() => {
  706. w.addTabbedWindow(w)
  707. }, /AddTabbedWindow cannot be called by a window on itself./)
  708. })
  709. })
  710. describe('BrowserWindow.setVibrancy(type)', () => {
  711. it('allows setting, changing, and removing the vibrancy', () => {
  712. assert.doesNotThrow(() => {
  713. w.setVibrancy('light')
  714. w.setVibrancy('dark')
  715. w.setVibrancy(null)
  716. w.setVibrancy('ultra-dark')
  717. w.setVibrancy('')
  718. })
  719. })
  720. })
  721. describe('BrowserWindow.setAppDetails(options)', () => {
  722. before(function () {
  723. if (process.platform !== 'win32') {
  724. this.skip()
  725. }
  726. })
  727. it('supports setting the app details', () => {
  728. const iconPath = path.join(fixtures, 'assets', 'icon.ico')
  729. assert.doesNotThrow(() => {
  730. w.setAppDetails({appId: 'my.app.id'})
  731. w.setAppDetails({appIconPath: iconPath, appIconIndex: 0})
  732. w.setAppDetails({appIconPath: iconPath})
  733. w.setAppDetails({relaunchCommand: 'my-app.exe arg1 arg2', relaunchDisplayName: 'My app name'})
  734. w.setAppDetails({relaunchCommand: 'my-app.exe arg1 arg2'})
  735. w.setAppDetails({relaunchDisplayName: 'My app name'})
  736. w.setAppDetails({
  737. appId: 'my.app.id',
  738. appIconPath: iconPath,
  739. appIconIndex: 0,
  740. relaunchCommand: 'my-app.exe arg1 arg2',
  741. relaunchDisplayName: 'My app name'
  742. })
  743. w.setAppDetails({})
  744. })
  745. assert.throws(() => {
  746. w.setAppDetails()
  747. }, /Insufficient number of arguments\./)
  748. })
  749. })
  750. describe('BrowserWindow.fromId(id)', () => {
  751. it('returns the window with id', () => {
  752. assert.equal(w.id, BrowserWindow.fromId(w.id).id)
  753. })
  754. })
  755. describe('BrowserWindow.fromWebContents(webContents)', () => {
  756. let contents = null
  757. beforeEach(() => { contents = webContents.create({}) })
  758. afterEach(() => { contents.destroy() })
  759. it('returns the window with the webContents', () => {
  760. assert.equal(BrowserWindow.fromWebContents(w.webContents).id, w.id)
  761. assert.equal(BrowserWindow.fromWebContents(contents), undefined)
  762. })
  763. })
  764. describe('BrowserWindow.fromDevToolsWebContents(webContents)', () => {
  765. let contents = null
  766. beforeEach(() => { contents = webContents.create({}) })
  767. afterEach(() => { contents.destroy() })
  768. it('returns the window with the webContents', (done) => {
  769. w.webContents.once('devtools-opened', () => {
  770. assert.equal(BrowserWindow.fromDevToolsWebContents(w.devToolsWebContents).id, w.id)
  771. assert.equal(BrowserWindow.fromDevToolsWebContents(w.webContents), undefined)
  772. assert.equal(BrowserWindow.fromDevToolsWebContents(contents), undefined)
  773. done()
  774. })
  775. w.webContents.openDevTools()
  776. })
  777. })
  778. describe('BrowserWindow.openDevTools()', () => {
  779. it('does not crash for frameless window', () => {
  780. w.destroy()
  781. w = new BrowserWindow({ show: false })
  782. w.openDevTools()
  783. })
  784. })
  785. describe('BrowserWindow.fromBrowserView(browserView)', () => {
  786. let bv = null
  787. beforeEach(() => {
  788. bv = new BrowserView()
  789. w.setBrowserView(bv)
  790. })
  791. afterEach(() => {
  792. w.setBrowserView(null)
  793. bv.destroy()
  794. })
  795. it('returns the window with the browserView', () => {
  796. assert.equal(BrowserWindow.fromBrowserView(bv).id, w.id)
  797. })
  798. it('returns undefined if not attached', () => {
  799. w.setBrowserView(null)
  800. assert.equal(BrowserWindow.fromBrowserView(bv), undefined)
  801. })
  802. })
  803. describe('BrowserWindow.setOpacity(opacity)', () => {
  804. it('make window with initial opacity', () => {
  805. w.destroy()
  806. w = new BrowserWindow({
  807. show: false,
  808. width: 400,
  809. height: 400,
  810. opacity: 0.5
  811. })
  812. assert.equal(w.getOpacity(), 0.5)
  813. })
  814. it('allows setting the opacity', () => {
  815. assert.doesNotThrow(() => {
  816. w.setOpacity(0.0)
  817. assert.equal(w.getOpacity(), 0.0)
  818. w.setOpacity(0.5)
  819. assert.equal(w.getOpacity(), 0.5)
  820. w.setOpacity(1.0)
  821. assert.equal(w.getOpacity(), 1.0)
  822. })
  823. })
  824. })
  825. describe('BrowserWindow.setShape(rects)', () => {
  826. it('allows setting shape', () => {
  827. assert.doesNotThrow(() => {
  828. w.setShape([])
  829. w.setShape([{x: 0, y: 0, width: 100, height: 100}])
  830. w.setShape([{x: 0, y: 0, width: 100, height: 100}, {x: 0, y: 200, width: 1000, height: 100}])
  831. w.setShape([])
  832. })
  833. })
  834. })
  835. describe('"useContentSize" option', () => {
  836. it('make window created with content size when used', () => {
  837. w.destroy()
  838. w = new BrowserWindow({
  839. show: false,
  840. width: 400,
  841. height: 400,
  842. useContentSize: true
  843. })
  844. const contentSize = w.getContentSize()
  845. assert.equal(contentSize[0], 400)
  846. assert.equal(contentSize[1], 400)
  847. })
  848. it('make window created with window size when not used', () => {
  849. const size = w.getSize()
  850. assert.equal(size[0], 400)
  851. assert.equal(size[1], 400)
  852. })
  853. it('works for a frameless window', () => {
  854. w.destroy()
  855. w = new BrowserWindow({
  856. show: false,
  857. frame: false,
  858. width: 400,
  859. height: 400,
  860. useContentSize: true
  861. })
  862. const contentSize = w.getContentSize()
  863. assert.equal(contentSize[0], 400)
  864. assert.equal(contentSize[1], 400)
  865. const size = w.getSize()
  866. assert.equal(size[0], 400)
  867. assert.equal(size[1], 400)
  868. })
  869. })
  870. describe('"titleBarStyle" option', () => {
  871. before(function () {
  872. if (process.platform !== 'darwin') {
  873. this.skip()
  874. }
  875. if (parseInt(os.release().split('.')[0]) < 14) {
  876. this.skip()
  877. }
  878. })
  879. it('creates browser window with hidden title bar', () => {
  880. w.destroy()
  881. w = new BrowserWindow({
  882. show: false,
  883. width: 400,
  884. height: 400,
  885. titleBarStyle: 'hidden'
  886. })
  887. const contentSize = w.getContentSize()
  888. assert.equal(contentSize[1], 400)
  889. })
  890. it('creates browser window with hidden inset title bar', () => {
  891. w.destroy()
  892. w = new BrowserWindow({
  893. show: false,
  894. width: 400,
  895. height: 400,
  896. titleBarStyle: 'hiddenInset'
  897. })
  898. const contentSize = w.getContentSize()
  899. assert.equal(contentSize[1], 400)
  900. })
  901. })
  902. describe('enableLargerThanScreen" option', () => {
  903. before(function () {
  904. if (process.platform === 'linux') {
  905. this.skip()
  906. }
  907. })
  908. beforeEach(() => {
  909. w.destroy()
  910. w = new BrowserWindow({
  911. show: true,
  912. width: 400,
  913. height: 400,
  914. enableLargerThanScreen: true
  915. })
  916. })
  917. it('can move the window out of screen', () => {
  918. w.setPosition(-10, -10)
  919. const after = w.getPosition()
  920. assert.equal(after[0], -10)
  921. assert.equal(after[1], -10)
  922. })
  923. it('can set the window larger than screen', () => {
  924. const size = screen.getPrimaryDisplay().size
  925. size.width += 100
  926. size.height += 100
  927. w.setSize(size.width, size.height)
  928. assertBoundsEqual(w.getSize(), [size.width, size.height])
  929. })
  930. })
  931. describe('"zoomToPageWidth" option', () => {
  932. before(function () {
  933. if (process.platform !== 'darwin') {
  934. this.skip()
  935. }
  936. })
  937. it('sets the window width to the page width when used', () => {
  938. w.destroy()
  939. w = new BrowserWindow({
  940. show: false,
  941. width: 500,
  942. height: 400,
  943. zoomToPageWidth: true
  944. })
  945. w.maximize()
  946. assert.equal(w.getSize()[0], 500)
  947. })
  948. })
  949. describe('"tabbingIdentifier" option', () => {
  950. it('can be set on a window', () => {
  951. w.destroy()
  952. w = new BrowserWindow({
  953. tabbingIdentifier: 'group1'
  954. })
  955. w.destroy()
  956. w = new BrowserWindow({
  957. tabbingIdentifier: 'group2',
  958. frame: false
  959. })
  960. })
  961. })
  962. describe('"webPreferences" option', () => {
  963. afterEach(() => { ipcMain.removeAllListeners('answer') })
  964. describe('"preload" option', () => {
  965. it('loads the script before other scripts in window', (done) => {
  966. const preload = path.join(fixtures, 'module', 'set-global.js')
  967. ipcMain.once('answer', (event, test) => {
  968. assert.equal(test, 'preload')
  969. done()
  970. })
  971. w.destroy()
  972. w = new BrowserWindow({
  973. show: false,
  974. webPreferences: {
  975. preload: preload
  976. }
  977. })
  978. w.loadURL(`file://${path.join(fixtures, 'api', 'preload.html')}`)
  979. })
  980. it('can successfully delete the Buffer global', (done) => {
  981. const preload = path.join(fixtures, 'module', 'delete-buffer.js')
  982. ipcMain.once('answer', (event, test) => {
  983. assert.equal(test.toString(), 'buffer')
  984. done()
  985. })
  986. w.destroy()
  987. w = new BrowserWindow({
  988. show: false,
  989. webPreferences: {
  990. preload: preload
  991. }
  992. })
  993. w.loadURL(`file://${path.join(fixtures, 'api', 'preload.html')}`)
  994. })
  995. })
  996. describe('session preload scripts', function () {
  997. const preloads = [
  998. path.join(fixtures, 'module', 'set-global-preload-1.js'),
  999. path.join(fixtures, 'module', 'set-global-preload-2.js')
  1000. ]
  1001. const defaultSession = session.defaultSession
  1002. beforeEach(() => {
  1003. assert.deepEqual(defaultSession.getPreloads(), [])
  1004. defaultSession.setPreloads(preloads)
  1005. })
  1006. afterEach(() => {
  1007. defaultSession.setPreloads([])
  1008. })
  1009. it('can set multiple session preload script', function () {
  1010. assert.deepEqual(defaultSession.getPreloads(), preloads)
  1011. })
  1012. it('loads the script before other scripts in window including normal preloads', function (done) {
  1013. ipcMain.once('vars', function (event, preload1, preload2, preload3) {
  1014. assert.equal(preload1, 'preload-1')
  1015. assert.equal(preload2, 'preload-1-2')
  1016. assert.equal(preload3, 'preload-1-2-3')
  1017. done()
  1018. })
  1019. w.destroy()
  1020. w = new BrowserWindow({
  1021. show: false,
  1022. webPreferences: {
  1023. preload: path.join(fixtures, 'module', 'set-global-preload-3.js')
  1024. }
  1025. })
  1026. w.loadURL('file://' + path.join(fixtures, 'api', 'preloads.html'))
  1027. })
  1028. })
  1029. describe('"additionalArguments" option', () => {
  1030. it('adds extra args to process.argv in the renderer process', (done) => {
  1031. const preload = path.join(fixtures, 'module', 'check-arguments.js')
  1032. ipcMain.once('answer', (event, argv) => {
  1033. assert.ok(argv.includes('--my-magic-arg'))
  1034. done()
  1035. })
  1036. w.destroy()
  1037. w = new BrowserWindow({
  1038. show: false,
  1039. webPreferences: {
  1040. preload: preload,
  1041. additionalArguments: ['--my-magic-arg']
  1042. }
  1043. })
  1044. w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`)
  1045. })
  1046. it('adds extra value args to process.argv in the renderer process', (done) => {
  1047. const preload = path.join(fixtures, 'module', 'check-arguments.js')
  1048. ipcMain.once('answer', (event, argv) => {
  1049. assert.ok(argv.includes('--my-magic-arg=foo'))
  1050. done()
  1051. })
  1052. w.destroy()
  1053. w = new BrowserWindow({
  1054. show: false,
  1055. webPreferences: {
  1056. preload: preload,
  1057. additionalArguments: ['--my-magic-arg=foo']
  1058. }
  1059. })
  1060. w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`)
  1061. })
  1062. })
  1063. describe('"node-integration" option', () => {
  1064. it('disables node integration when specified to false', (done) => {
  1065. const preload = path.join(fixtures, 'module', 'send-later.js')
  1066. ipcMain.once('answer', (event, typeofProcess, typeofBuffer) => {
  1067. assert.equal(typeofProcess, 'undefined')
  1068. assert.equal(typeofBuffer, 'undefined')
  1069. done()
  1070. })
  1071. w.destroy()
  1072. w = new BrowserWindow({
  1073. show: false,
  1074. webPreferences: {
  1075. preload: preload,
  1076. nodeIntegration: false
  1077. }
  1078. })
  1079. w.loadURL(`file://${path.join(fixtures, 'api', 'blank.html')}`)
  1080. })
  1081. })
  1082. describe('"sandbox" option', () => {
  1083. function waitForEvents (emitter, events, callback) {
  1084. let count = events.length
  1085. for (let event of events) {
  1086. emitter.once(event, () => {
  1087. if (!--count) callback()
  1088. })
  1089. }
  1090. }
  1091. const preload = path.join(fixtures, 'module', 'preload-sandbox.js')
  1092. // http protocol to simulate accessing another domain. This is required
  1093. // because the code paths for cross domain popups is different.
  1094. function crossDomainHandler (request, callback) {
  1095. // Disabled due to false positive in StandardJS
  1096. // eslint-disable-next-line standard/no-callback-literal
  1097. callback({
  1098. mimeType: 'text/html',
  1099. data: `<html><body><h1>${request.url}</h1></body></html>`
  1100. })
  1101. }
  1102. before((done) => {
  1103. protocol.interceptStringProtocol('http', crossDomainHandler, () => {
  1104. done()
  1105. })
  1106. })
  1107. after((done) => {
  1108. protocol.uninterceptProtocol('http', () => {
  1109. done()
  1110. })
  1111. })
  1112. it('exposes ipcRenderer to preload script', (done) => {
  1113. ipcMain.once('answer', function (event, test) {
  1114. assert.equal(test, 'preload')
  1115. done()
  1116. })
  1117. w.destroy()
  1118. w = new BrowserWindow({
  1119. show: false,
  1120. webPreferences: {
  1121. sandbox: true,
  1122. preload: preload
  1123. }
  1124. })
  1125. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  1126. })
  1127. it('exposes ipcRenderer to preload script (path has special chars)', function (done) {
  1128. const preloadSpecialChars = path.join(fixtures, 'module', 'preload-sandboxæø åü.js')
  1129. ipcMain.once('answer', function (event, test) {
  1130. assert.equal(test, 'preload')
  1131. done()
  1132. })
  1133. w.destroy()
  1134. w = new BrowserWindow({
  1135. show: false,
  1136. webPreferences: {
  1137. sandbox: true,
  1138. preload: preloadSpecialChars
  1139. }
  1140. })
  1141. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  1142. })
  1143. it('exposes "loaded" event to preload script', function (done) {
  1144. w.destroy()
  1145. w = new BrowserWindow({
  1146. show: false,
  1147. webPreferences: {
  1148. sandbox: true,
  1149. preload
  1150. }
  1151. })
  1152. ipcMain.once('process-loaded', () => done())
  1153. w.loadURL('about:blank')
  1154. })
  1155. it('exposes "exit" event to preload script', function (done) {
  1156. w.destroy()
  1157. w = new BrowserWindow({
  1158. show: false,
  1159. webPreferences: {
  1160. sandbox: true,
  1161. preload: preload
  1162. }
  1163. })
  1164. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?exit-event')
  1165. const pageUrl = 'file://' + htmlPath
  1166. w.loadURL(pageUrl)
  1167. ipcMain.once('answer', function (event, url) {
  1168. let expectedUrl = pageUrl
  1169. if (process.platform === 'win32') {
  1170. expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
  1171. }
  1172. assert.equal(url, expectedUrl)
  1173. done()
  1174. })
  1175. })
  1176. it('should open windows in same domain with cross-scripting enabled', (done) => {
  1177. w.destroy()
  1178. w = new BrowserWindow({
  1179. show: false,
  1180. webPreferences: {
  1181. sandbox: true,
  1182. preload: preload
  1183. }
  1184. })
  1185. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preload)
  1186. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open')
  1187. const pageUrl = 'file://' + htmlPath
  1188. w.loadURL(pageUrl)
  1189. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  1190. let expectedUrl = pageUrl
  1191. if (process.platform === 'win32') {
  1192. expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
  1193. }
  1194. assert.equal(url, expectedUrl)
  1195. assert.equal(frameName, 'popup!')
  1196. assert.equal(options.width, 500)
  1197. assert.equal(options.height, 600)
  1198. ipcMain.once('answer', function (event, html) {
  1199. assert.equal(html, '<h1>scripting from opener</h1>')
  1200. done()
  1201. })
  1202. })
  1203. })
  1204. it('should open windows in another domain with cross-scripting disabled', async () => {
  1205. const w = await openTheWindow({
  1206. show: false,
  1207. webPreferences: {
  1208. sandbox: true,
  1209. preload
  1210. }
  1211. })
  1212. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preload)
  1213. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open-external')
  1214. w.loadURL(`file://${htmlPath}`)
  1215. const expectedPopupUrl = 'http://www.google.com/#q=electron' // Set in the "sandbox.html".
  1216. // The page is going to open a popup that it won't be able to close.
  1217. // We have to close it from here later.
  1218. // XXX(alexeykuzmin): It will leak if the test fails too soon.
  1219. const [, popupWindow] = await emittedOnce(app, 'browser-window-created')
  1220. // Wait for a message from the popup's preload script.
  1221. const [, openerIsNull, html, locationHref] = await emittedOnce(ipcMain, 'child-loaded')
  1222. expect(openerIsNull).to.be.true('window.opener is not null')
  1223. expect(html).to.equal(`<h1>${expectedPopupUrl}</h1>`,
  1224. 'looks like a http: request has not been intercepted locally')
  1225. expect(locationHref).to.equal(expectedPopupUrl)
  1226. // Ask the page to access the popup.
  1227. w.webContents.send('touch-the-popup')
  1228. const [, exceptionMessage] = await emittedOnce(ipcMain, 'answer')
  1229. // We don't need the popup anymore, and its parent page can't close it,
  1230. // so let's close it from here before we run any checks.
  1231. await closeWindow(popupWindow, { assertSingleWindow: false })
  1232. expect(exceptionMessage).to.be.a('string',
  1233. `child's .document is accessible from its parent window`)
  1234. expect(exceptionMessage).to.match(/^Blocked a frame with origin/)
  1235. })
  1236. it('should inherit the sandbox setting in opened windows', (done) => {
  1237. w.destroy()
  1238. w = new BrowserWindow({
  1239. show: false,
  1240. webPreferences: {
  1241. sandbox: true
  1242. }
  1243. })
  1244. const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js')
  1245. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preloadPath)
  1246. ipcMain.once('answer', (event, args) => {
  1247. assert.equal(args.includes('--enable-sandbox'), true)
  1248. done()
  1249. })
  1250. w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`)
  1251. })
  1252. it('should open windows with the options configured via new-window event listeners', (done) => {
  1253. w.destroy()
  1254. w = new BrowserWindow({
  1255. show: false,
  1256. webPreferences: {
  1257. sandbox: true
  1258. }
  1259. })
  1260. const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js')
  1261. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preloadPath)
  1262. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'foo', 'bar')
  1263. ipcMain.once('answer', (event, args, webPreferences) => {
  1264. assert.equal(webPreferences.foo, 'bar')
  1265. done()
  1266. })
  1267. w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`)
  1268. })
  1269. it('should set ipc event sender correctly', (done) => {
  1270. w.destroy()
  1271. w = new BrowserWindow({
  1272. show: false,
  1273. webPreferences: {
  1274. sandbox: true,
  1275. preload: preload
  1276. }
  1277. })
  1278. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preload)
  1279. let htmlPath = path.join(fixtures, 'api', 'sandbox.html?verify-ipc-sender')
  1280. const pageUrl = 'file://' + htmlPath
  1281. let childWc
  1282. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  1283. childWc = options.webContents
  1284. assert.notEqual(w.webContents, childWc)
  1285. })
  1286. ipcMain.once('parent-ready', function (event) {
  1287. assert.equal(w.webContents, event.sender)
  1288. event.sender.send('verified')
  1289. })
  1290. ipcMain.once('child-ready', function (event) {
  1291. assert(childWc)
  1292. assert.equal(childWc, event.sender)
  1293. event.sender.send('verified')
  1294. })
  1295. waitForEvents(ipcMain, [
  1296. 'parent-answer',
  1297. 'child-answer'
  1298. ], done)
  1299. w.loadURL(pageUrl)
  1300. })
  1301. describe('event handling', () => {
  1302. it('works for window events', (done) => {
  1303. waitForEvents(w, [
  1304. 'page-title-updated'
  1305. ], done)
  1306. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?window-events'))
  1307. })
  1308. it('works for stop events', (done) => {
  1309. waitForEvents(w.webContents, [
  1310. 'did-navigate',
  1311. 'did-fail-load',
  1312. 'did-stop-loading'
  1313. ], done)
  1314. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-stop'))
  1315. })
  1316. it('works for web contents events', (done) => {
  1317. waitForEvents(w.webContents, [
  1318. 'did-finish-load',
  1319. 'did-frame-finish-load',
  1320. 'did-navigate-in-page',
  1321. 'will-navigate',
  1322. 'did-start-loading',
  1323. 'did-stop-loading',
  1324. 'did-frame-finish-load',
  1325. 'dom-ready'
  1326. ], done)
  1327. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-events'))
  1328. })
  1329. })
  1330. it('can get printer list', (done) => {
  1331. w.destroy()
  1332. w = new BrowserWindow({
  1333. show: false,
  1334. webPreferences: {
  1335. sandbox: true,
  1336. preload: preload
  1337. }
  1338. })
  1339. w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E')
  1340. w.webContents.once('did-finish-load', () => {
  1341. const printers = w.webContents.getPrinters()
  1342. assert.equal(Array.isArray(printers), true)
  1343. done()
  1344. })
  1345. })
  1346. it('can print to PDF', (done) => {
  1347. w.destroy()
  1348. w = new BrowserWindow({
  1349. show: false,
  1350. webPreferences: {
  1351. sandbox: true,
  1352. preload: preload
  1353. }
  1354. })
  1355. w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E')
  1356. w.webContents.once('did-finish-load', () => {
  1357. w.webContents.printToPDF({}, function (error, data) {
  1358. assert.equal(error, null)
  1359. assert.equal(data instanceof Buffer, true)
  1360. assert.notEqual(data.length, 0)
  1361. done()
  1362. })
  1363. })
  1364. })
  1365. it('supports calling preventDefault on new-window events', (done) => {
  1366. w.destroy()
  1367. w = new BrowserWindow({
  1368. show: false,
  1369. webPreferences: {
  1370. sandbox: true
  1371. }
  1372. })
  1373. const initialWebContents = webContents.getAllWebContents().map((i) => i.id)
  1374. ipcRenderer.send('prevent-next-new-window', w.webContents.id)
  1375. w.webContents.once('new-window', () => {
  1376. // We need to give it some time so the windows get properly disposed (at least on OSX).
  1377. setTimeout(() => {
  1378. const currentWebContents = webContents.getAllWebContents().map((i) => i.id)
  1379. assert.deepEqual(currentWebContents, initialWebContents)
  1380. done()
  1381. }, 100)
  1382. })
  1383. w.loadURL('file://' + path.join(fixtures, 'pages', 'window-open.html'))
  1384. })
  1385. it('releases memory after popup is closed', (done) => {
  1386. w.destroy()
  1387. w = new BrowserWindow({
  1388. show: false,
  1389. webPreferences: {
  1390. preload: preload,
  1391. sandbox: true
  1392. }
  1393. })
  1394. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?allocate-memory'))
  1395. ipcMain.once('answer', function (event, {bytesBeforeOpen, bytesAfterOpen, bytesAfterClose}) {
  1396. const memoryIncreaseByOpen = bytesAfterOpen - bytesBeforeOpen
  1397. const memoryDecreaseByClose = bytesAfterOpen - bytesAfterClose
  1398. // decreased memory should be less than increased due to factors we
  1399. // can't control, but given the amount of memory allocated in the
  1400. // fixture, we can reasonably expect decrease to be at least 70% of
  1401. // increase
  1402. assert(memoryDecreaseByClose > memoryIncreaseByOpen * 0.7)
  1403. done()
  1404. })
  1405. })
  1406. // see #9387
  1407. it('properly manages remote object references after page reload', (done) => {
  1408. w.destroy()
  1409. w = new BrowserWindow({
  1410. show: false,
  1411. webPreferences: {
  1412. preload: preload,
  1413. sandbox: true
  1414. }
  1415. })
  1416. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?reload-remote'))
  1417. ipcMain.on('get-remote-module-path', (event) => {
  1418. event.returnValue = path.join(fixtures, 'module', 'hello.js')
  1419. })
  1420. let reload = false
  1421. ipcMain.on('reloaded', (event) => {
  1422. event.returnValue = reload
  1423. reload = !reload
  1424. })
  1425. ipcMain.once('reload', (event) => {
  1426. event.sender.reload()
  1427. })
  1428. ipcMain.once('answer', (event, arg) => {
  1429. ipcMain.removeAllListeners('reloaded')
  1430. ipcMain.removeAllListeners('get-remote-module-path')
  1431. assert.equal(arg, 'hi')
  1432. done()
  1433. })
  1434. })
  1435. it('properly manages remote object references after page reload in child window', (done) => {
  1436. w.destroy()
  1437. w = new BrowserWindow({
  1438. show: false,
  1439. webPreferences: {
  1440. preload: preload,
  1441. sandbox: true
  1442. }
  1443. })
  1444. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preload)
  1445. w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?reload-remote-child'))
  1446. ipcMain.on('get-remote-module-path', (event) => {
  1447. event.returnValue = path.join(fixtures, 'module', 'hello-child.js')
  1448. })
  1449. let reload = false
  1450. ipcMain.on('reloaded', (event) => {
  1451. event.returnValue = reload
  1452. reload = !reload
  1453. })
  1454. ipcMain.once('reload', (event) => {
  1455. event.sender.reload()
  1456. })
  1457. ipcMain.once('answer', (event, arg) => {
  1458. ipcMain.removeAllListeners('reloaded')
  1459. ipcMain.removeAllListeners('get-remote-module-path')
  1460. assert.equal(arg, 'hi child window')
  1461. done()
  1462. })
  1463. })
  1464. it('validates process APIs access in sandboxed renderer', (done) => {
  1465. ipcMain.once('answer', function (event, test) {
  1466. assert.equal(test.platform, remote.process.platform)
  1467. assert.deepEqual(test.env, remote.process.env)
  1468. assert.equal(test.execPath, remote.process.helperExecPath)
  1469. done()
  1470. })
  1471. remote.process.env.sandboxmain = 'foo'
  1472. w.destroy()
  1473. w = new BrowserWindow({
  1474. show: false,
  1475. webPreferences: {
  1476. sandbox: true,
  1477. preload: preload
  1478. }
  1479. })
  1480. w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
  1481. })
  1482. it('webview in sandbox renderer', async () => {
  1483. w.destroy()
  1484. w = new BrowserWindow({
  1485. show: false,
  1486. webPreferences: {
  1487. sandbox: true,
  1488. preload: preload,
  1489. webviewTag: true
  1490. }
  1491. })
  1492. w.loadURL(`file://${fixtures}/pages/webview-did-attach-event.html`)
  1493. const [, webContents] = await emittedOnce(w.webContents, 'did-attach-webview')
  1494. const [, id] = await emittedOnce(ipcMain, 'webview-dom-ready')
  1495. expect(webContents.id).to.equal(id)
  1496. })
  1497. })
  1498. describe('nativeWindowOpen option', () => {
  1499. beforeEach(() => {
  1500. w.destroy()
  1501. w = new BrowserWindow({
  1502. show: false,
  1503. webPreferences: {
  1504. nativeWindowOpen: true
  1505. }
  1506. })
  1507. })
  1508. it('opens window of about:blank with cross-scripting enabled', (done) => {
  1509. ipcMain.once('answer', (event, content) => {
  1510. assert.equal(content, 'Hello')
  1511. done()
  1512. })
  1513. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}`)
  1514. })
  1515. it('opens window of same domain with cross-scripting enabled', (done) => {
  1516. ipcMain.once('answer', (event, content) => {
  1517. assert.equal(content, 'Hello')
  1518. done()
  1519. })
  1520. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-file.html')}`)
  1521. })
  1522. it('blocks accessing cross-origin frames', (done) => {
  1523. ipcMain.once('answer', (event, content) => {
  1524. assert.equal(content, 'Blocked a frame with origin "file://" from accessing a cross-origin frame.')
  1525. done()
  1526. })
  1527. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}`)
  1528. })
  1529. it('opens window from <iframe> tags', (done) => {
  1530. ipcMain.once('answer', (event, content) => {
  1531. assert.equal(content, 'Hello')
  1532. done()
  1533. })
  1534. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-iframe.html')}`)
  1535. })
  1536. it('loads native addons correctly after reload', (done) => {
  1537. if (!nativeModulesEnabled) return done()
  1538. ipcMain.once('answer', (event, content) => {
  1539. assert.equal(content, 'function')
  1540. ipcMain.once('answer', (event, content) => {
  1541. assert.equal(content, 'function')
  1542. done()
  1543. })
  1544. w.reload()
  1545. })
  1546. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-native-addon.html')}`)
  1547. })
  1548. it('should inherit the nativeWindowOpen setting in opened windows', (done) => {
  1549. w.destroy()
  1550. w = new BrowserWindow({
  1551. show: false,
  1552. webPreferences: {
  1553. nativeWindowOpen: true
  1554. }
  1555. })
  1556. const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js')
  1557. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preloadPath)
  1558. ipcMain.once('answer', (event, args) => {
  1559. assert.equal(args.includes('--native-window-open'), true)
  1560. done()
  1561. })
  1562. w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`)
  1563. })
  1564. it('should open windows with the options configured via new-window event listeners', (done) => {
  1565. w.destroy()
  1566. w = new BrowserWindow({
  1567. show: false,
  1568. webPreferences: {
  1569. nativeWindowOpen: true
  1570. }
  1571. })
  1572. const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js')
  1573. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preloadPath)
  1574. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'foo', 'bar')
  1575. ipcMain.once('answer', (event, args, webPreferences) => {
  1576. assert.equal(webPreferences.foo, 'bar')
  1577. done()
  1578. })
  1579. w.loadURL(`file://${path.join(fixtures, 'api', 'new-window.html')}`)
  1580. })
  1581. it('retains the original web preferences when window.location is changed to a new origin', async () => {
  1582. await serveFileFromProtocol('foo', path.join(fixtures, 'api', 'window-open-location-change.html'))
  1583. await serveFileFromProtocol('bar', path.join(fixtures, 'api', 'window-open-location-final.html'))
  1584. w.destroy()
  1585. w = new BrowserWindow({
  1586. show: true,
  1587. webPreferences: {
  1588. nodeIntegration: false,
  1589. nativeWindowOpen: true
  1590. }
  1591. })
  1592. return new Promise((resolve, reject) => {
  1593. ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', path.join(fixtures, 'api', 'window-open-preload.js'))
  1594. ipcMain.once('answer', (event, args, typeofProcess) => {
  1595. assert.equal(args.includes('--node-integration=false'), true)
  1596. assert.equal(args.includes('--native-window-open'), true)
  1597. assert.equal(typeofProcess, 'undefined')
  1598. resolve()
  1599. })
  1600. w.loadURL(`file://${path.join(fixtures, 'api', 'window-open-location-open.html')}`)
  1601. })
  1602. })
  1603. })
  1604. })
  1605. describe('nativeWindowOpen + contextIsolation options', () => {
  1606. beforeEach(() => {
  1607. w.destroy()
  1608. w = new BrowserWindow({
  1609. show: false,
  1610. webPreferences: {
  1611. nativeWindowOpen: true,
  1612. contextIsolation: true,
  1613. preload: path.join(fixtures, 'api', 'native-window-open-isolated-preload.js')
  1614. }
  1615. })
  1616. })
  1617. it('opens window with cross-scripting enabled from isolated context', (done) => {
  1618. ipcMain.once('answer', (event, content) => {
  1619. assert.equal(content, 'Hello')
  1620. done()
  1621. })
  1622. w.loadURL(`file://${path.join(fixtures, 'api', 'native-window-open-isolated.html')}`)
  1623. })
  1624. })
  1625. describe('beforeunload handler', () => {
  1626. it('returning undefined would not prevent close', (done) => {
  1627. w.once('closed', () => { done() })
  1628. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-undefined.html'))
  1629. })
  1630. it('returning false would prevent close', (done) => {
  1631. w.once('onbeforeunload', () => { done() })
  1632. w.loadURL(`file://${path.join(fixtures, 'api', 'close-beforeunload-false.html')}`)
  1633. })
  1634. it('returning empty string would prevent close', (done) => {
  1635. w.once('onbeforeunload', () => { done() })
  1636. w.loadURL(`file://${path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')}`)
  1637. })
  1638. it('emits for each close attempt', (done) => {
  1639. let beforeUnloadCount = 0
  1640. w.on('onbeforeunload', () => {
  1641. beforeUnloadCount += 1
  1642. if (beforeUnloadCount < 3) {
  1643. w.close()
  1644. } else if (beforeUnloadCount === 3) {
  1645. done()
  1646. }
  1647. })
  1648. w.webContents.once('did-finish-load', () => { w.close() })
  1649. w.loadURL(`file://${path.join(fixtures, 'api', 'beforeunload-false-prevent3.html')}`)
  1650. })
  1651. it('emits for each reload attempt', (done) => {
  1652. let beforeUnloadCount = 0
  1653. w.on('onbeforeunload', () => {
  1654. beforeUnloadCount += 1
  1655. if (beforeUnloadCount < 3) {
  1656. w.reload()
  1657. } else if (beforeUnloadCount === 3) {
  1658. done()
  1659. }
  1660. })
  1661. w.webContents.once('did-finish-load', () => {
  1662. w.webContents.once('did-finish-load', () => {
  1663. assert.fail('Reload was not prevented')
  1664. })
  1665. w.reload()
  1666. })
  1667. w.loadURL(`file://${path.join(fixtures, 'api', 'beforeunload-false-prevent3.html')}`)
  1668. })
  1669. it('emits for each navigation attempt', (done) => {
  1670. let beforeUnloadCount = 0
  1671. w.on('onbeforeunload', () => {
  1672. beforeUnloadCount += 1
  1673. if (beforeUnloadCount < 3) {
  1674. w.loadURL('about:blank')
  1675. } else if (beforeUnloadCount === 3) {
  1676. done()
  1677. }
  1678. })
  1679. w.webContents.once('did-finish-load', () => {
  1680. w.webContents.once('did-finish-load', () => {
  1681. assert.fail('Navigation was not prevented')
  1682. })
  1683. w.loadURL('about:blank')
  1684. })
  1685. w.loadURL(`file://${path.join(fixtures, 'api', 'beforeunload-false-prevent3.html')}`)
  1686. })
  1687. })
  1688. describe('document.visibilityState/hidden', () => {
  1689. beforeEach(() => { w.destroy() })
  1690. function onVisibilityChange (callback) {
  1691. ipcMain.on('pong', (event, visibilityState, hidden) => {
  1692. if (event.sender.id === w.webContents.id) {
  1693. callback(visibilityState, hidden)
  1694. }
  1695. })
  1696. }
  1697. function onNextVisibilityChange (callback) {
  1698. ipcMain.once('pong', (event, visibilityState, hidden) => {
  1699. if (event.sender.id === w.webContents.id) {
  1700. callback(visibilityState, hidden)
  1701. }
  1702. })
  1703. }
  1704. afterEach(() => { ipcMain.removeAllListeners('pong') })
  1705. it('visibilityState is initially visible despite window being hidden', (done) => {
  1706. w = new BrowserWindow({ show: false, width: 100, height: 100 })
  1707. let readyToShow = false
  1708. w.once('ready-to-show', () => {
  1709. readyToShow = true
  1710. })
  1711. onNextVisibilityChange((visibilityState, hidden) => {
  1712. assert.equal(readyToShow, false)
  1713. assert.equal(visibilityState, 'visible')
  1714. assert.equal(hidden, false)
  1715. done()
  1716. })
  1717. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1718. })
  1719. it('visibilityState changes when window is hidden', (done) => {
  1720. w = new BrowserWindow({width: 100, height: 100})
  1721. onNextVisibilityChange((visibilityState, hidden) => {
  1722. assert.equal(visibilityState, 'visible')
  1723. assert.equal(hidden, false)
  1724. onNextVisibilityChange((visibilityState, hidden) => {
  1725. assert.equal(visibilityState, 'hidden')
  1726. assert.equal(hidden, true)
  1727. done()
  1728. })
  1729. w.hide()
  1730. })
  1731. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1732. })
  1733. it('visibilityState changes when window is shown', (done) => {
  1734. w = new BrowserWindow({width: 100, height: 100})
  1735. onNextVisibilityChange((visibilityState, hidden) => {
  1736. onVisibilityChange((visibilityState, hidden) => {
  1737. if (!hidden) {
  1738. assert.equal(visibilityState, 'visible')
  1739. done()
  1740. }
  1741. })
  1742. w.hide()
  1743. w.show()
  1744. })
  1745. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1746. })
  1747. it('visibilityState changes when window is shown inactive', function (done) {
  1748. if (isCI && process.platform === 'win32') {
  1749. // FIXME(alexeykuzmin): Skip the test instead of marking it as passed.
  1750. // afterEach hook won't be run if a test is skipped dynamically.
  1751. // If afterEach isn't run current window won't be destroyed
  1752. // and the next test will fail on assertion in `closeWindow()`.
  1753. // this.skip()
  1754. return done()
  1755. }
  1756. w = new BrowserWindow({width: 100, height: 100})
  1757. onNextVisibilityChange((visibilityState, hidden) => {
  1758. onVisibilityChange((visibilityState, hidden) => {
  1759. if (!hidden) {
  1760. assert.equal(visibilityState, 'visible')
  1761. done()
  1762. }
  1763. })
  1764. w.hide()
  1765. w.showInactive()
  1766. })
  1767. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1768. })
  1769. it('visibilityState changes when window is minimized', function (done) {
  1770. if (isCI && process.platform === 'linux') {
  1771. // FIXME(alexeykuzmin): Skip the test instead of marking it as passed.
  1772. // afterEach hook won't be run if a test is skipped dynamically.
  1773. // If afterEach isn't run current window won't be destroyed
  1774. // and the next test will fail on assertion in `closeWindow()`.
  1775. // this.skip()
  1776. return done()
  1777. }
  1778. w = new BrowserWindow({width: 100, height: 100})
  1779. onNextVisibilityChange((visibilityState, hidden) => {
  1780. assert.equal(visibilityState, 'visible')
  1781. assert.equal(hidden, false)
  1782. onNextVisibilityChange((visibilityState, hidden) => {
  1783. assert.equal(visibilityState, 'hidden')
  1784. assert.equal(hidden, true)
  1785. done()
  1786. })
  1787. w.minimize()
  1788. })
  1789. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1790. })
  1791. it('visibilityState remains visible if backgroundThrottling is disabled', (done) => {
  1792. w = new BrowserWindow({
  1793. show: false,
  1794. width: 100,
  1795. height: 100,
  1796. webPreferences: {
  1797. backgroundThrottling: false
  1798. }
  1799. })
  1800. onNextVisibilityChange((visibilityState, hidden) => {
  1801. assert.equal(visibilityState, 'visible')
  1802. assert.equal(hidden, false)
  1803. onNextVisibilityChange((visibilityState, hidden) => {
  1804. done(new Error(`Unexpected visibility change event. visibilityState: ${visibilityState} hidden: ${hidden}`))
  1805. })
  1806. })
  1807. w.once('show', () => {
  1808. w.once('hide', () => {
  1809. w.once('show', () => {
  1810. done()
  1811. })
  1812. w.show()
  1813. })
  1814. w.hide()
  1815. })
  1816. w.show()
  1817. w.loadURL(`file://${path.join(fixtures, 'pages', 'visibilitychange.html')}`)
  1818. })
  1819. })
  1820. describe('new-window event', () => {
  1821. before(function () {
  1822. if (isCI && process.platform === 'darwin') {
  1823. this.skip()
  1824. }
  1825. })
  1826. it('emits when window.open is called', (done) => {
  1827. w.webContents.once('new-window', (e, url, frameName, disposition, options, additionalFeatures) => {
  1828. e.preventDefault()
  1829. assert.equal(url, 'http://host/')
  1830. assert.equal(frameName, 'host')
  1831. assert.equal(additionalFeatures[0], 'this-is-not-a-standard-feature')
  1832. done()
  1833. })
  1834. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  1835. })
  1836. it('emits when window.open is called with no webPreferences', (done) => {
  1837. w.destroy()
  1838. w = new BrowserWindow({ show: false })
  1839. w.webContents.once('new-window', function (e, url, frameName, disposition, options, additionalFeatures) {
  1840. e.preventDefault()
  1841. assert.equal(url, 'http://host/')
  1842. assert.equal(frameName, 'host')
  1843. assert.equal(additionalFeatures[0], 'this-is-not-a-standard-feature')
  1844. done()
  1845. })
  1846. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  1847. })
  1848. it('emits when link with target is called', (done) => {
  1849. w.webContents.once('new-window', (e, url, frameName) => {
  1850. e.preventDefault()
  1851. assert.equal(url, 'http://host/')
  1852. assert.equal(frameName, 'target')
  1853. done()
  1854. })
  1855. w.loadURL(`file://${fixtures}/pages/target-name.html`)
  1856. })
  1857. })
  1858. describe('maximize event', () => {
  1859. if (isCI) return
  1860. it('emits when window is maximized', (done) => {
  1861. w.once('maximize', () => { done() })
  1862. w.show()
  1863. w.maximize()
  1864. })
  1865. })
  1866. describe('unmaximize event', () => {
  1867. if (isCI) return
  1868. it('emits when window is unmaximized', (done) => {
  1869. w.once('unmaximize', () => { done() })
  1870. w.show()
  1871. w.maximize()
  1872. w.unmaximize()
  1873. })
  1874. })
  1875. describe('minimize event', () => {
  1876. if (isCI) return
  1877. it('emits when window is minimized', (done) => {
  1878. w.once('minimize', () => { done() })
  1879. w.show()
  1880. w.minimize()
  1881. })
  1882. })
  1883. describe('sheet-begin event', () => {
  1884. let sheet = null
  1885. before(function () {
  1886. if (process.platform !== 'darwin') {
  1887. this.skip()
  1888. }
  1889. })
  1890. afterEach(() => {
  1891. return closeWindow(sheet, {assertSingleWindow: false}).then(() => { sheet = null })
  1892. })
  1893. it('emits when window opens a sheet', (done) => {
  1894. w.show()
  1895. w.once('sheet-begin', () => {
  1896. sheet.close()
  1897. done()
  1898. })
  1899. sheet = new BrowserWindow({
  1900. modal: true,
  1901. parent: w
  1902. })
  1903. })
  1904. })
  1905. describe('sheet-end event', () => {
  1906. let sheet = null
  1907. before(function () {
  1908. if (process.platform !== 'darwin') {
  1909. this.skip()
  1910. }
  1911. })
  1912. afterEach(() => {
  1913. return closeWindow(sheet, {assertSingleWindow: false}).then(() => { sheet = null })
  1914. })
  1915. it('emits when window has closed a sheet', (done) => {
  1916. w.show()
  1917. sheet = new BrowserWindow({
  1918. modal: true,
  1919. parent: w
  1920. })
  1921. w.once('sheet-end', () => { done() })
  1922. sheet.close()
  1923. })
  1924. })
  1925. describe('beginFrameSubscription method', () => {
  1926. before(function () {
  1927. // This test is too slow, only test it on CI.
  1928. if (!isCI) {
  1929. this.skip()
  1930. }
  1931. // FIXME These specs crash on Linux when run in a docker container
  1932. if (isCI && process.platform === 'linux') {
  1933. this.skip()
  1934. }
  1935. })
  1936. it('subscribes to frame updates', (done) => {
  1937. let called = false
  1938. w.loadURL(`file://${fixtures}/api/frame-subscriber.html`)
  1939. w.webContents.on('dom-ready', () => {
  1940. w.webContents.beginFrameSubscription(function (data) {
  1941. // This callback might be called twice.
  1942. if (called) return
  1943. called = true
  1944. assert.notEqual(data.length, 0)
  1945. w.webContents.endFrameSubscription()
  1946. done()
  1947. })
  1948. })
  1949. })
  1950. it('subscribes to frame updates (only dirty rectangle)', (done) => {
  1951. let called = false
  1952. w.loadURL(`file://${fixtures}/api/frame-subscriber.html`)
  1953. w.webContents.on('dom-ready', () => {
  1954. w.webContents.beginFrameSubscription(true, (data) => {
  1955. // This callback might be called twice.
  1956. if (called) return
  1957. called = true
  1958. assert.notEqual(data.length, 0)
  1959. w.webContents.endFrameSubscription()
  1960. done()
  1961. })
  1962. })
  1963. })
  1964. it('throws error when subscriber is not well defined', (done) => {
  1965. w.loadURL(`file://${fixtures}'/api/frame-subscriber.html`)
  1966. try {
  1967. w.webContents.beginFrameSubscription(true, true)
  1968. } catch (e) {
  1969. done()
  1970. }
  1971. })
  1972. })
  1973. describe('savePage method', () => {
  1974. const savePageDir = path.join(fixtures, 'save_page')
  1975. const savePageHtmlPath = path.join(savePageDir, 'save_page.html')
  1976. const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js')
  1977. const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css')
  1978. after(() => {
  1979. try {
  1980. fs.unlinkSync(savePageCssPath)
  1981. fs.unlinkSync(savePageJsPath)
  1982. fs.unlinkSync(savePageHtmlPath)
  1983. fs.rmdirSync(path.join(savePageDir, 'save_page_files'))
  1984. fs.rmdirSync(savePageDir)
  1985. } catch (e) {
  1986. // Ignore error
  1987. }
  1988. })
  1989. it('should save page to disk', (done) => {
  1990. w.webContents.on('did-finish-load', () => {
  1991. w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function (error) {
  1992. assert.equal(error, null)
  1993. assert(fs.existsSync(savePageHtmlPath))
  1994. assert(fs.existsSync(savePageJsPath))
  1995. assert(fs.existsSync(savePageCssPath))
  1996. done()
  1997. })
  1998. })
  1999. w.loadURL('file://' + fixtures + '/pages/save_page/index.html')
  2000. })
  2001. })
  2002. describe('BrowserWindow options argument is optional', () => {
  2003. it('should create a window with default size (800x600)', () => {
  2004. w.destroy()
  2005. w = new BrowserWindow()
  2006. const size = w.getSize()
  2007. assert.equal(size[0], 800)
  2008. assert.equal(size[1], 600)
  2009. })
  2010. })
  2011. describe('window states', () => {
  2012. it('does not resize frameless windows when states change', () => {
  2013. w.destroy()
  2014. w = new BrowserWindow({
  2015. frame: false,
  2016. width: 300,
  2017. height: 200,
  2018. show: false
  2019. })
  2020. w.setMinimizable(false)
  2021. w.setMinimizable(true)
  2022. assert.deepEqual(w.getSize(), [300, 200])
  2023. w.setResizable(false)
  2024. w.setResizable(true)
  2025. assert.deepEqual(w.getSize(), [300, 200])
  2026. w.setMaximizable(false)
  2027. w.setMaximizable(true)
  2028. assert.deepEqual(w.getSize(), [300, 200])
  2029. w.setFullScreenable(false)
  2030. w.setFullScreenable(true)
  2031. assert.deepEqual(w.getSize(), [300, 200])
  2032. w.setClosable(false)
  2033. w.setClosable(true)
  2034. assert.deepEqual(w.getSize(), [300, 200])
  2035. })
  2036. describe('resizable state', () => {
  2037. it('can be changed with resizable option', () => {
  2038. w.destroy()
  2039. w = new BrowserWindow({show: false, resizable: false})
  2040. assert.equal(w.isResizable(), false)
  2041. if (process.platform === 'darwin') {
  2042. assert.equal(w.isMaximizable(), true)
  2043. }
  2044. })
  2045. it('can be changed with setResizable method', () => {
  2046. assert.equal(w.isResizable(), true)
  2047. w.setResizable(false)
  2048. assert.equal(w.isResizable(), false)
  2049. w.setResizable(true)
  2050. assert.equal(w.isResizable(), true)
  2051. })
  2052. it('works for a frameless window', () => {
  2053. w.destroy()
  2054. w = new BrowserWindow({show: false, frame: false})
  2055. assert.equal(w.isResizable(), true)
  2056. if (process.platform === 'win32') {
  2057. w.destroy()
  2058. w = new BrowserWindow({show: false, thickFrame: false})
  2059. assert.equal(w.isResizable(), false)
  2060. }
  2061. })
  2062. if (process.platform === 'win32') {
  2063. it('works for a window smaller than 64x64', () => {
  2064. w.destroy()
  2065. w = new BrowserWindow({
  2066. show: false,
  2067. frame: false,
  2068. resizable: false,
  2069. transparent: true
  2070. })
  2071. w.setContentSize(60, 60)
  2072. assertBoundsEqual(w.getContentSize(), [60, 60])
  2073. w.setContentSize(30, 30)
  2074. assertBoundsEqual(w.getContentSize(), [30, 30])
  2075. w.setContentSize(10, 10)
  2076. assertBoundsEqual(w.getContentSize(), [10, 10])
  2077. })
  2078. }
  2079. })
  2080. describe('loading main frame state', () => {
  2081. it('is true when the main frame is loading', (done) => {
  2082. w.webContents.on('did-start-loading', () => {
  2083. assert.equal(w.webContents.isLoadingMainFrame(), true)
  2084. done()
  2085. })
  2086. w.webContents.loadURL(server.url)
  2087. })
  2088. it('is false when only a subframe is loading', (done) => {
  2089. w.webContents.once('did-finish-load', () => {
  2090. assert.equal(w.webContents.isLoadingMainFrame(), false)
  2091. w.webContents.on('did-start-loading', () => {
  2092. assert.equal(w.webContents.isLoadingMainFrame(), false)
  2093. done()
  2094. })
  2095. w.webContents.executeJavaScript(`
  2096. var iframe = document.createElement('iframe')
  2097. iframe.src = '${server.url}/page2'
  2098. document.body.appendChild(iframe)
  2099. `)
  2100. })
  2101. w.webContents.loadURL(server.url)
  2102. })
  2103. it('is true when navigating to pages from the same origin', (done) => {
  2104. w.webContents.once('did-finish-load', () => {
  2105. assert.equal(w.webContents.isLoadingMainFrame(), false)
  2106. w.webContents.on('did-start-loading', () => {
  2107. assert.equal(w.webContents.isLoadingMainFrame(), true)
  2108. done()
  2109. })
  2110. w.webContents.loadURL(`${server.url}/page2`)
  2111. })
  2112. w.webContents.loadURL(server.url)
  2113. })
  2114. })
  2115. })
  2116. describe('window states (excluding Linux)', () => {
  2117. // FIXME(alexeykuzmin): Skip the tests instead of using the `return` here.
  2118. // Why it cannot be done now:
  2119. // - `.skip()` called in the 'before' hook doesn't affect
  2120. // nested `describe`s.
  2121. // - `.skip()` called in the 'beforeEach' hook prevents 'afterEach'
  2122. // hook from being called.
  2123. // Not implemented on Linux.
  2124. if (process.platform === 'linux') {
  2125. return
  2126. }
  2127. describe('movable state', () => {
  2128. it('can be changed with movable option', () => {
  2129. w.destroy()
  2130. w = new BrowserWindow({show: false, movable: false})
  2131. assert.equal(w.isMovable(), false)
  2132. })
  2133. it('can be changed with setMovable method', () => {
  2134. assert.equal(w.isMovable(), true)
  2135. w.setMovable(false)
  2136. assert.equal(w.isMovable(), false)
  2137. w.setMovable(true)
  2138. assert.equal(w.isMovable(), true)
  2139. })
  2140. })
  2141. describe('minimizable state', () => {
  2142. it('can be changed with minimizable option', () => {
  2143. w.destroy()
  2144. w = new BrowserWindow({show: false, minimizable: false})
  2145. assert.equal(w.isMinimizable(), false)
  2146. })
  2147. it('can be changed with setMinimizable method', () => {
  2148. assert.equal(w.isMinimizable(), true)
  2149. w.setMinimizable(false)
  2150. assert.equal(w.isMinimizable(), false)
  2151. w.setMinimizable(true)
  2152. assert.equal(w.isMinimizable(), true)
  2153. })
  2154. })
  2155. describe('maximizable state', () => {
  2156. it('can be changed with maximizable option', () => {
  2157. w.destroy()
  2158. w = new BrowserWindow({show: false, maximizable: false})
  2159. assert.equal(w.isMaximizable(), false)
  2160. })
  2161. it('can be changed with setMaximizable method', () => {
  2162. assert.equal(w.isMaximizable(), true)
  2163. w.setMaximizable(false)
  2164. assert.equal(w.isMaximizable(), false)
  2165. w.setMaximizable(true)
  2166. assert.equal(w.isMaximizable(), true)
  2167. })
  2168. it('is not affected when changing other states', () => {
  2169. w.setMaximizable(false)
  2170. assert.equal(w.isMaximizable(), false)
  2171. w.setMinimizable(false)
  2172. assert.equal(w.isMaximizable(), false)
  2173. w.setClosable(false)
  2174. assert.equal(w.isMaximizable(), false)
  2175. w.setMaximizable(true)
  2176. assert.equal(w.isMaximizable(), true)
  2177. w.setClosable(true)
  2178. assert.equal(w.isMaximizable(), true)
  2179. w.setFullScreenable(false)
  2180. assert.equal(w.isMaximizable(), true)
  2181. })
  2182. })
  2183. describe('maximizable state (Windows only)', () => {
  2184. // Only implemented on windows.
  2185. if (process.platform !== 'win32') return
  2186. it('is reset to its former state', () => {
  2187. w.setMaximizable(false)
  2188. w.setResizable(false)
  2189. w.setResizable(true)
  2190. assert.equal(w.isMaximizable(), false)
  2191. w.setMaximizable(true)
  2192. w.setResizable(false)
  2193. w.setResizable(true)
  2194. assert.strictEqual(w.isMaximizable(), true)
  2195. })
  2196. })
  2197. describe('fullscreenable state', () => {
  2198. before(function () {
  2199. // Only implemented on macOS.
  2200. if (process.platform !== 'darwin') {
  2201. this.skip()
  2202. }
  2203. })
  2204. it('can be changed with fullscreenable option', () => {
  2205. w.destroy()
  2206. w = new BrowserWindow({show: false, fullscreenable: false})
  2207. assert.equal(w.isFullScreenable(), false)
  2208. })
  2209. it('can be changed with setFullScreenable method', () => {
  2210. assert.equal(w.isFullScreenable(), true)
  2211. w.setFullScreenable(false)
  2212. assert.equal(w.isFullScreenable(), false)
  2213. w.setFullScreenable(true)
  2214. assert.equal(w.isFullScreenable(), true)
  2215. })
  2216. })
  2217. describe('kiosk state', () => {
  2218. before(function () {
  2219. // Only implemented on macOS.
  2220. if (process.platform !== 'darwin') {
  2221. this.skip()
  2222. }
  2223. })
  2224. it('can be changed with setKiosk method', (done) => {
  2225. w.destroy()
  2226. w = new BrowserWindow()
  2227. w.setKiosk(true)
  2228. assert.equal(w.isKiosk(), true)
  2229. w.once('enter-full-screen', () => {
  2230. w.setKiosk(false)
  2231. assert.equal(w.isKiosk(), false)
  2232. })
  2233. w.once('leave-full-screen', () => {
  2234. done()
  2235. })
  2236. })
  2237. })
  2238. describe('fullscreen state with resizable set', () => {
  2239. before(function () {
  2240. // Only implemented on macOS.
  2241. if (process.platform !== 'darwin') {
  2242. this.skip()
  2243. }
  2244. })
  2245. it('resizable flag should be set to true and restored', (done) => {
  2246. w.destroy()
  2247. w = new BrowserWindow({ resizable: false })
  2248. w.once('enter-full-screen', () => {
  2249. assert.equal(w.isResizable(), true)
  2250. w.setFullScreen(false)
  2251. })
  2252. w.once('leave-full-screen', () => {
  2253. assert.equal(w.isResizable(), false)
  2254. done()
  2255. })
  2256. w.setFullScreen(true)
  2257. })
  2258. })
  2259. describe('fullscreen state', () => {
  2260. before(function () {
  2261. // Only implemented on macOS.
  2262. if (process.platform !== 'darwin') {
  2263. this.skip()
  2264. }
  2265. })
  2266. it('can be changed with setFullScreen method', (done) => {
  2267. w.destroy()
  2268. w = new BrowserWindow()
  2269. w.once('enter-full-screen', () => {
  2270. assert.equal(w.isFullScreen(), true)
  2271. w.setFullScreen(false)
  2272. })
  2273. w.once('leave-full-screen', () => {
  2274. assert.equal(w.isFullScreen(), false)
  2275. done()
  2276. })
  2277. w.setFullScreen(true)
  2278. })
  2279. it('should not be changed by setKiosk method', (done) => {
  2280. w.destroy()
  2281. w = new BrowserWindow()
  2282. w.once('enter-full-screen', () => {
  2283. assert.equal(w.isFullScreen(), true)
  2284. w.setKiosk(true)
  2285. w.setKiosk(false)
  2286. assert.equal(w.isFullScreen(), true)
  2287. w.setFullScreen(false)
  2288. })
  2289. w.once('leave-full-screen', () => {
  2290. assert.equal(w.isFullScreen(), false)
  2291. done()
  2292. })
  2293. w.setFullScreen(true)
  2294. })
  2295. })
  2296. describe('closable state', () => {
  2297. it('can be changed with closable option', () => {
  2298. w.destroy()
  2299. w = new BrowserWindow({show: false, closable: false})
  2300. assert.equal(w.isClosable(), false)
  2301. })
  2302. it('can be changed with setClosable method', () => {
  2303. assert.equal(w.isClosable(), true)
  2304. w.setClosable(false)
  2305. assert.equal(w.isClosable(), false)
  2306. w.setClosable(true)
  2307. assert.equal(w.isClosable(), true)
  2308. })
  2309. })
  2310. describe('hasShadow state', () => {
  2311. // On Window there is no shadow by default and it can not be changed
  2312. // dynamically.
  2313. it('can be changed with hasShadow option', () => {
  2314. w.destroy()
  2315. let hasShadow = process.platform !== 'darwin'
  2316. w = new BrowserWindow({show: false, hasShadow: hasShadow})
  2317. assert.equal(w.hasShadow(), hasShadow)
  2318. })
  2319. it('can be changed with setHasShadow method', () => {
  2320. if (process.platform !== 'darwin') return
  2321. assert.equal(w.hasShadow(), true)
  2322. w.setHasShadow(false)
  2323. assert.equal(w.hasShadow(), false)
  2324. w.setHasShadow(true)
  2325. assert.equal(w.hasShadow(), true)
  2326. })
  2327. })
  2328. })
  2329. describe('BrowserWindow.restore()', () => {
  2330. it('should restore the previous window size', () => {
  2331. if (w != null) w.destroy()
  2332. w = new BrowserWindow({
  2333. minWidth: 800,
  2334. width: 800
  2335. })
  2336. const initialSize = w.getSize()
  2337. w.minimize()
  2338. w.restore()
  2339. assertBoundsEqual(w.getSize(), initialSize)
  2340. })
  2341. })
  2342. describe('BrowserWindow.unmaximize()', () => {
  2343. it('should restore the previous window position', () => {
  2344. if (w != null) w.destroy()
  2345. w = new BrowserWindow()
  2346. const initialPosition = w.getPosition()
  2347. w.maximize()
  2348. w.unmaximize()
  2349. assertBoundsEqual(w.getPosition(), initialPosition)
  2350. })
  2351. })
  2352. describe('BrowserWindow.setFullScreen(false)', () => {
  2353. before(function () {
  2354. // only applicable to windows: https://github.com/electron/electron/issues/6036
  2355. if (process.platform !== 'win32') {
  2356. this.skip()
  2357. }
  2358. })
  2359. it('should restore a normal visible window from a fullscreen startup state', (done) => {
  2360. w.webContents.once('did-finish-load', () => {
  2361. // start fullscreen and hidden
  2362. w.setFullScreen(true)
  2363. w.once('show', () => { w.setFullScreen(false) })
  2364. w.once('leave-full-screen', () => {
  2365. assert.equal(w.isVisible(), true)
  2366. assert.equal(w.isFullScreen(), false)
  2367. done()
  2368. })
  2369. w.show()
  2370. })
  2371. w.loadURL('about:blank')
  2372. })
  2373. it('should keep window hidden if already in hidden state', (done) => {
  2374. w.webContents.once('did-finish-load', () => {
  2375. w.once('leave-full-screen', () => {
  2376. assert.equal(w.isVisible(), false)
  2377. assert.equal(w.isFullScreen(), false)
  2378. done()
  2379. })
  2380. w.setFullScreen(false)
  2381. })
  2382. w.loadURL('about:blank')
  2383. })
  2384. })
  2385. describe('BrowserWindow.setFullScreen(false) when HTML fullscreen', () => {
  2386. before(function () {
  2387. if (process.platform !== 'darwin') {
  2388. this.skip()
  2389. }
  2390. })
  2391. it('exits HTML fullscreen when window leaves fullscreen', (done) => {
  2392. w.destroy()
  2393. w = new BrowserWindow()
  2394. w.webContents.once('did-finish-load', () => {
  2395. w.once('enter-full-screen', () => {
  2396. w.once('leave-html-full-screen', () => {
  2397. done()
  2398. })
  2399. w.setFullScreen(false)
  2400. })
  2401. w.webContents.executeJavaScript('document.body.webkitRequestFullscreen()', true)
  2402. })
  2403. w.loadURL('about:blank')
  2404. })
  2405. })
  2406. describe('parent window', () => {
  2407. let c = null
  2408. beforeEach(() => {
  2409. if (c != null) c.destroy()
  2410. c = new BrowserWindow({show: false, parent: w})
  2411. })
  2412. afterEach(() => {
  2413. if (c != null) c.destroy()
  2414. c = null
  2415. })
  2416. describe('parent option', () => {
  2417. it('sets parent window', () => {
  2418. assert.equal(c.getParentWindow(), w)
  2419. })
  2420. it('adds window to child windows of parent', () => {
  2421. assert.deepEqual(w.getChildWindows(), [c])
  2422. })
  2423. it('removes from child windows of parent when window is closed', (done) => {
  2424. c.once('closed', () => {
  2425. assert.deepEqual(w.getChildWindows(), [])
  2426. done()
  2427. })
  2428. c.close()
  2429. })
  2430. it('should not affect the show option', () => {
  2431. assert.equal(c.isVisible(), false)
  2432. assert.equal(c.getParentWindow().isVisible(), false)
  2433. })
  2434. })
  2435. describe('win.setParentWindow(parent)', () => {
  2436. before(function () {
  2437. if (process.platform === 'win32') {
  2438. this.skip()
  2439. }
  2440. })
  2441. beforeEach(() => {
  2442. if (c != null) c.destroy()
  2443. c = new BrowserWindow({show: false})
  2444. })
  2445. it('sets parent window', () => {
  2446. assert.equal(w.getParentWindow(), null)
  2447. assert.equal(c.getParentWindow(), null)
  2448. c.setParentWindow(w)
  2449. assert.equal(c.getParentWindow(), w)
  2450. c.setParentWindow(null)
  2451. assert.equal(c.getParentWindow(), null)
  2452. })
  2453. it('adds window to child windows of parent', () => {
  2454. assert.deepEqual(w.getChildWindows(), [])
  2455. c.setParentWindow(w)
  2456. assert.deepEqual(w.getChildWindows(), [c])
  2457. c.setParentWindow(null)
  2458. assert.deepEqual(w.getChildWindows(), [])
  2459. })
  2460. it('removes from child windows of parent when window is closed', (done) => {
  2461. c.once('closed', () => {
  2462. assert.deepEqual(w.getChildWindows(), [])
  2463. done()
  2464. })
  2465. c.setParentWindow(w)
  2466. c.close()
  2467. })
  2468. })
  2469. describe('modal option', () => {
  2470. before(function () {
  2471. // The isEnabled API is not reliable on macOS.
  2472. if (process.platform === 'darwin') {
  2473. this.skip()
  2474. }
  2475. })
  2476. beforeEach(() => {
  2477. if (c != null) c.destroy()
  2478. c = new BrowserWindow({show: false, parent: w, modal: true})
  2479. })
  2480. it('disables parent window', () => {
  2481. assert.equal(w.isEnabled(), true)
  2482. c.show()
  2483. assert.equal(w.isEnabled(), false)
  2484. })
  2485. it('re-enables an enabled parent window when closed', (done) => {
  2486. c.once('closed', () => {
  2487. assert.equal(w.isEnabled(), true)
  2488. done()
  2489. })
  2490. c.show()
  2491. c.close()
  2492. })
  2493. it('does not re-enable a disabled parent window when closed', (done) => {
  2494. c.once('closed', () => {
  2495. assert.strictEqual(w.isEnabled(), false)
  2496. done()
  2497. })
  2498. w.setEnabled(false)
  2499. c.show()
  2500. c.close()
  2501. })
  2502. it('disables parent window recursively', () => {
  2503. let c2 = new BrowserWindow({show: false, parent: w, modal: true})
  2504. c.show()
  2505. assert.equal(w.isEnabled(), false)
  2506. c2.show()
  2507. assert.equal(w.isEnabled(), false)
  2508. c.destroy()
  2509. assert.equal(w.isEnabled(), false)
  2510. c2.destroy()
  2511. assert.equal(w.isEnabled(), true)
  2512. })
  2513. })
  2514. })
  2515. describe('window.webContents.send(channel, args...)', () => {
  2516. it('throws an error when the channel is missing', () => {
  2517. assert.throws(() => {
  2518. w.webContents.send()
  2519. }, 'Missing required channel argument')
  2520. assert.throws(() => {
  2521. w.webContents.send(null)
  2522. }, 'Missing required channel argument')
  2523. })
  2524. })
  2525. describe('window.getNativeWindowHandle()', () => {
  2526. before(function () {
  2527. if (!nativeModulesEnabled) {
  2528. this.skip()
  2529. }
  2530. })
  2531. it('returns valid handle', () => {
  2532. // The module's source code is hosted at
  2533. // https://github.com/electron/node-is-valid-window
  2534. const isValidWindow = remote.require('is-valid-window')
  2535. assert.ok(isValidWindow(w.getNativeWindowHandle()))
  2536. })
  2537. })
  2538. describe('extensions and dev tools extensions', () => {
  2539. let showPanelTimeoutId
  2540. const showLastDevToolsPanel = () => {
  2541. w.webContents.once('devtools-opened', () => {
  2542. const show = () => {
  2543. if (w == null || w.isDestroyed()) return
  2544. const {devToolsWebContents} = w
  2545. if (devToolsWebContents == null || devToolsWebContents.isDestroyed()) {
  2546. return
  2547. }
  2548. const showLastPanel = () => {
  2549. const lastPanelId = UI.inspectorView._tabbedPane._tabs.peekLast().id
  2550. UI.inspectorView.showPanel(lastPanelId)
  2551. }
  2552. devToolsWebContents.executeJavaScript(`(${showLastPanel})()`, false, () => {
  2553. showPanelTimeoutId = setTimeout(show, 100)
  2554. })
  2555. }
  2556. showPanelTimeoutId = setTimeout(show, 100)
  2557. })
  2558. }
  2559. afterEach(() => {
  2560. clearTimeout(showPanelTimeoutId)
  2561. })
  2562. describe('BrowserWindow.addDevToolsExtension', () => {
  2563. describe('for invalid extensions', () => {
  2564. it('throws errors for missing manifest.json files', () => {
  2565. const nonexistentExtensionPath = path.join(__dirname, 'does-not-exist')
  2566. expect(() => {
  2567. BrowserWindow.addDevToolsExtension(nonexistentExtensionPath)
  2568. }).to.throw(/ENOENT: no such file or directory/)
  2569. })
  2570. it('throws errors for invalid manifest.json files', () => {
  2571. const badManifestExtensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest')
  2572. expect(() => {
  2573. BrowserWindow.addDevToolsExtension(badManifestExtensionPath)
  2574. }).to.throw(/Unexpected token }/)
  2575. })
  2576. })
  2577. describe('for a valid extension', () => {
  2578. const extensionName = 'foo'
  2579. const removeExtension = () => {
  2580. BrowserWindow.removeDevToolsExtension('foo')
  2581. expect(BrowserWindow.getDevToolsExtensions().hasOwnProperty(extensionName)).to.equal(false)
  2582. }
  2583. const addExtension = () => {
  2584. const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  2585. BrowserWindow.addDevToolsExtension(extensionPath)
  2586. expect(BrowserWindow.getDevToolsExtensions().hasOwnProperty(extensionName)).to.equal(true)
  2587. showLastDevToolsPanel()
  2588. w.loadURL('about:blank')
  2589. }
  2590. // After* hooks won't be called if a test fail.
  2591. // So let's make a clean-up in the before hook.
  2592. beforeEach(removeExtension)
  2593. describe('when the devtools is docked', () => {
  2594. beforeEach(function (done) {
  2595. addExtension()
  2596. w.webContents.openDevTools({mode: 'bottom'})
  2597. ipcMain.once('answer', (event, message) => {
  2598. this.message = message
  2599. done()
  2600. })
  2601. })
  2602. describe('created extension info', function () {
  2603. it('has proper "runtimeId"', function () {
  2604. expect(this.message).to.have.own.property('runtimeId')
  2605. expect(this.message.runtimeId).to.equal(extensionName)
  2606. })
  2607. it('has "tabId" matching webContents id', function () {
  2608. expect(this.message).to.have.own.property('tabId')
  2609. expect(this.message.tabId).to.equal(w.webContents.id)
  2610. })
  2611. it('has "i18nString" with proper contents', function () {
  2612. expect(this.message).to.have.own.property('i18nString')
  2613. expect(this.message.i18nString).to.equal('foo - bar (baz)')
  2614. })
  2615. it('has "storageItems" with proper contents', function () {
  2616. expect(this.message).to.have.own.property('storageItems')
  2617. expect(this.message.storageItems).to.deep.equal({
  2618. local: {
  2619. set: {hello: 'world', world: 'hello'},
  2620. remove: {world: 'hello'},
  2621. clear: {}
  2622. },
  2623. sync: {
  2624. set: {foo: 'bar', bar: 'foo'},
  2625. remove: {foo: 'bar'},
  2626. clear: {}
  2627. }
  2628. })
  2629. })
  2630. })
  2631. })
  2632. describe('when the devtools is undocked', () => {
  2633. beforeEach(function (done) {
  2634. addExtension()
  2635. w.webContents.openDevTools({mode: 'undocked'})
  2636. ipcMain.once('answer', (event, message, extensionId) => {
  2637. this.message = message
  2638. done()
  2639. })
  2640. })
  2641. describe('created extension info', function () {
  2642. it('has proper "runtimeId"', function () {
  2643. expect(this.message).to.have.own.property('runtimeId')
  2644. expect(this.message.runtimeId).to.equal(extensionName)
  2645. })
  2646. it('has "tabId" matching webContents id', function () {
  2647. expect(this.message).to.have.own.property('tabId')
  2648. expect(this.message.tabId).to.equal(w.webContents.id)
  2649. })
  2650. })
  2651. })
  2652. })
  2653. })
  2654. it('works when used with partitions', (done) => {
  2655. if (w != null) {
  2656. w.destroy()
  2657. }
  2658. w = new BrowserWindow({
  2659. show: false,
  2660. webPreferences: {
  2661. partition: 'temp'
  2662. }
  2663. })
  2664. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  2665. BrowserWindow.removeDevToolsExtension('foo')
  2666. BrowserWindow.addDevToolsExtension(extensionPath)
  2667. showLastDevToolsPanel()
  2668. w.loadURL('about:blank')
  2669. w.webContents.openDevTools({mode: 'bottom'})
  2670. ipcMain.once('answer', function (event, message) {
  2671. assert.equal(message.runtimeId, 'foo')
  2672. done()
  2673. })
  2674. })
  2675. it('serializes the registered extensions on quit', () => {
  2676. var extensionName = 'foo'
  2677. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName)
  2678. var serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions')
  2679. BrowserWindow.addDevToolsExtension(extensionPath)
  2680. app.emit('will-quit')
  2681. assert.deepEqual(JSON.parse(fs.readFileSync(serializedPath)), [extensionPath])
  2682. BrowserWindow.removeDevToolsExtension(extensionName)
  2683. app.emit('will-quit')
  2684. assert.equal(fs.existsSync(serializedPath), false)
  2685. })
  2686. describe('BrowserWindow.addExtension', () => {
  2687. beforeEach(() => {
  2688. BrowserWindow.removeExtension('foo')
  2689. assert.equal(BrowserWindow.getExtensions().hasOwnProperty('foo'), false)
  2690. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  2691. BrowserWindow.addExtension(extensionPath)
  2692. assert.equal(BrowserWindow.getExtensions().hasOwnProperty('foo'), true)
  2693. showLastDevToolsPanel()
  2694. w.loadURL('about:blank')
  2695. })
  2696. it('throws errors for missing manifest.json files', () => {
  2697. assert.throws(() => {
  2698. BrowserWindow.addExtension(path.join(__dirname, 'does-not-exist'))
  2699. }, /ENOENT: no such file or directory/)
  2700. })
  2701. it('throws errors for invalid manifest.json files', () => {
  2702. assert.throws(() => {
  2703. BrowserWindow.addExtension(path.join(__dirname, 'fixtures', 'devtools-extensions', 'bad-manifest'))
  2704. }, /Unexpected token }/)
  2705. })
  2706. })
  2707. })
  2708. describe('window.webContents.executeJavaScript', () => {
  2709. const expected = 'hello, world!'
  2710. const expectedErrorMsg = 'woops!'
  2711. const code = `(() => "${expected}")()`
  2712. const asyncCode = `(() => new Promise(r => setTimeout(() => r("${expected}"), 500)))()`
  2713. const badAsyncCode = `(() => new Promise((r, e) => setTimeout(() => e("${expectedErrorMsg}"), 500)))()`
  2714. const errorTypes = new Set([
  2715. Error,
  2716. ReferenceError,
  2717. EvalError,
  2718. RangeError,
  2719. SyntaxError,
  2720. TypeError,
  2721. URIError
  2722. ])
  2723. it('doesnt throw when no calback is provided', () => {
  2724. const result = ipcRenderer.sendSync('executeJavaScript', code, false)
  2725. assert.equal(result, 'success')
  2726. })
  2727. it('returns result when calback is provided', (done) => {
  2728. ipcRenderer.send('executeJavaScript', code, true)
  2729. ipcRenderer.once('executeJavaScript-response', function (event, result) {
  2730. assert.equal(result, expected)
  2731. done()
  2732. })
  2733. })
  2734. it('returns result if the code returns an asyncronous promise', (done) => {
  2735. ipcRenderer.send('executeJavaScript', asyncCode, true)
  2736. ipcRenderer.once('executeJavaScript-response', (event, result) => {
  2737. assert.equal(result, expected)
  2738. done()
  2739. })
  2740. })
  2741. it('resolves the returned promise with the result when a callback is specified', (done) => {
  2742. ipcRenderer.send('executeJavaScript', code, true)
  2743. ipcRenderer.once('executeJavaScript-promise-response', (event, result) => {
  2744. assert.equal(result, expected)
  2745. done()
  2746. })
  2747. })
  2748. it('resolves the returned promise with the result when no callback is specified', (done) => {
  2749. ipcRenderer.send('executeJavaScript', code, false)
  2750. ipcRenderer.once('executeJavaScript-promise-response', (event, result) => {
  2751. assert.equal(result, expected)
  2752. done()
  2753. })
  2754. })
  2755. it('resolves the returned promise with the result if the code returns an asyncronous promise', (done) => {
  2756. ipcRenderer.send('executeJavaScript', asyncCode, true)
  2757. ipcRenderer.once('executeJavaScript-promise-response', (event, result) => {
  2758. assert.equal(result, expected)
  2759. done()
  2760. })
  2761. })
  2762. it('rejects the returned promise if an async error is thrown', (done) => {
  2763. ipcRenderer.send('executeJavaScript', badAsyncCode, true)
  2764. ipcRenderer.once('executeJavaScript-promise-error', (event, error) => {
  2765. assert.equal(error, expectedErrorMsg)
  2766. done()
  2767. })
  2768. })
  2769. it('rejects the returned promise with an error if an Error.prototype is thrown', async () => {
  2770. for (const error in errorTypes) {
  2771. await new Promise((resolve) => {
  2772. ipcRenderer.send('executeJavaScript', `Promise.reject(new ${error.name}("Wamp-wamp")`, true)
  2773. ipcRenderer.once('executeJavaScript-promise-error-name', (event, name) => {
  2774. assert.equal(name, error.name)
  2775. resolve()
  2776. })
  2777. })
  2778. }
  2779. })
  2780. it('works after page load and during subframe load', (done) => {
  2781. w.webContents.once('did-finish-load', () => {
  2782. // initiate a sub-frame load, then try and execute script during it
  2783. w.webContents.executeJavaScript(`
  2784. var iframe = document.createElement('iframe')
  2785. iframe.src = '${server.url}/slow'
  2786. document.body.appendChild(iframe)
  2787. `, () => {
  2788. w.webContents.executeJavaScript('console.log(\'hello\')', () => {
  2789. done()
  2790. })
  2791. })
  2792. })
  2793. w.loadURL(server.url)
  2794. })
  2795. it('executes after page load', (done) => {
  2796. w.webContents.executeJavaScript(code, (result) => {
  2797. assert.equal(result, expected)
  2798. done()
  2799. })
  2800. w.loadURL(server.url)
  2801. })
  2802. it('works with result objects that have DOM class prototypes', (done) => {
  2803. w.webContents.executeJavaScript('document.location', (result) => {
  2804. assert.equal(result.origin, server.url)
  2805. assert.equal(result.protocol, 'http:')
  2806. done()
  2807. })
  2808. w.loadURL(server.url)
  2809. })
  2810. })
  2811. describe('previewFile', () => {
  2812. before(function () {
  2813. if (process.platform !== 'darwin') {
  2814. this.skip()
  2815. }
  2816. })
  2817. it('opens the path in Quick Look on macOS', () => {
  2818. assert.doesNotThrow(() => {
  2819. w.previewFile(__filename)
  2820. w.closeFilePreview()
  2821. })
  2822. })
  2823. })
  2824. describe('contextIsolation option with and without sandbox option', () => {
  2825. const expectedContextData = {
  2826. preloadContext: {
  2827. preloadProperty: 'number',
  2828. pageProperty: 'undefined',
  2829. typeofRequire: 'function',
  2830. typeofProcess: 'object',
  2831. typeofArrayPush: 'function',
  2832. typeofFunctionApply: 'function'
  2833. },
  2834. pageContext: {
  2835. openedLocation: '',
  2836. preloadProperty: 'undefined',
  2837. pageProperty: 'string',
  2838. typeofRequire: 'undefined',
  2839. typeofProcess: 'undefined',
  2840. typeofArrayPush: 'number',
  2841. typeofFunctionApply: 'boolean',
  2842. typeofPreloadExecuteJavaScriptProperty: 'number',
  2843. typeofOpenedWindow: 'object'
  2844. }
  2845. }
  2846. beforeEach(() => {
  2847. if (w != null) w.destroy()
  2848. w = new BrowserWindow({
  2849. show: false,
  2850. webPreferences: {
  2851. contextIsolation: true,
  2852. preload: path.join(fixtures, 'api', 'isolated-preload.js')
  2853. }
  2854. })
  2855. if (ws != null) ws.destroy()
  2856. ws = new BrowserWindow({
  2857. show: false,
  2858. webPreferences: {
  2859. sandbox: true,
  2860. contextIsolation: true,
  2861. preload: path.join(fixtures, 'api', 'isolated-preload.js')
  2862. }
  2863. })
  2864. })
  2865. afterEach(() => {
  2866. if (ws != null) ws.destroy()
  2867. })
  2868. it('separates the page context from the Electron/preload context', (done) => {
  2869. ipcMain.once('isolated-world', (event, data) => {
  2870. assert.deepEqual(data, expectedContextData)
  2871. done()
  2872. })
  2873. w.loadURL(`file://${fixtures}/api/isolated.html`)
  2874. })
  2875. it('recreates the contexts on reload', (done) => {
  2876. w.webContents.once('did-finish-load', () => {
  2877. ipcMain.once('isolated-world', (event, data) => {
  2878. assert.deepEqual(data, expectedContextData)
  2879. done()
  2880. })
  2881. w.webContents.reload()
  2882. })
  2883. w.loadURL(`file://${fixtures}/api/isolated.html`)
  2884. })
  2885. it('enables context isolation on child windows', (done) => {
  2886. app.once('browser-window-created', (event, window) => {
  2887. assert.equal(window.webContents.getLastWebPreferences().contextIsolation, true)
  2888. done()
  2889. })
  2890. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  2891. })
  2892. it('separates the page context from the Electron/preload context with sandbox on', (done) => {
  2893. ipcMain.once('isolated-sandbox-world', (event, data) => {
  2894. assert.deepEqual(data, expectedContextData)
  2895. done()
  2896. })
  2897. w.loadURL(`file://${fixtures}/api/isolated.html`)
  2898. })
  2899. it('recreates the contexts on reload with sandbox on', (done) => {
  2900. w.webContents.once('did-finish-load', () => {
  2901. ipcMain.once('isolated-sandbox-world', (event, data) => {
  2902. assert.deepEqual(data, expectedContextData)
  2903. done()
  2904. })
  2905. w.webContents.reload()
  2906. })
  2907. w.loadURL(`file://${fixtures}/api/isolated.html`)
  2908. })
  2909. })
  2910. describe('offscreen rendering', () => {
  2911. beforeEach(function () {
  2912. if (!features.isOffscreenRenderingEnabled()) {
  2913. // XXX(alexeykuzmin): "afterEach" hook is not called
  2914. // for skipped tests, we have to close the window manually.
  2915. return closeTheWindow().then(() => { this.skip() })
  2916. }
  2917. if (w != null) w.destroy()
  2918. w = new BrowserWindow({
  2919. width: 100,
  2920. height: 100,
  2921. show: false,
  2922. webPreferences: {
  2923. backgroundThrottling: false,
  2924. offscreen: true
  2925. }
  2926. })
  2927. })
  2928. it('creates offscreen window with correct size', (done) => {
  2929. w.webContents.once('paint', function (event, rect, data) {
  2930. assert.notEqual(data.length, 0)
  2931. let size = data.getSize()
  2932. assertWithinDelta(size.width, 100, 2, 'width')
  2933. assertWithinDelta(size.height, 100, 2, 'height')
  2934. done()
  2935. })
  2936. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2937. })
  2938. describe('window.webContents.isOffscreen()', () => {
  2939. it('is true for offscreen type', () => {
  2940. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2941. assert.equal(w.webContents.isOffscreen(), true)
  2942. })
  2943. it('is false for regular window', () => {
  2944. let c = new BrowserWindow({show: false})
  2945. assert.equal(c.webContents.isOffscreen(), false)
  2946. c.destroy()
  2947. })
  2948. })
  2949. describe('window.webContents.isPainting()', () => {
  2950. it('returns whether is currently painting', (done) => {
  2951. w.webContents.once('paint', function (event, rect, data) {
  2952. assert.equal(w.webContents.isPainting(), true)
  2953. done()
  2954. })
  2955. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2956. })
  2957. })
  2958. describe('window.webContents.stopPainting()', () => {
  2959. it('stops painting', (done) => {
  2960. w.webContents.on('dom-ready', () => {
  2961. w.webContents.stopPainting()
  2962. assert.equal(w.webContents.isPainting(), false)
  2963. done()
  2964. })
  2965. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2966. })
  2967. })
  2968. describe('window.webContents.startPainting()', () => {
  2969. it('starts painting', (done) => {
  2970. w.webContents.on('dom-ready', () => {
  2971. w.webContents.stopPainting()
  2972. w.webContents.startPainting()
  2973. w.webContents.once('paint', function (event, rect, data) {
  2974. assert.equal(w.webContents.isPainting(), true)
  2975. done()
  2976. })
  2977. })
  2978. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2979. })
  2980. })
  2981. describe('window.webContents.getFrameRate()', () => {
  2982. it('has default frame rate', (done) => {
  2983. w.webContents.once('paint', function (event, rect, data) {
  2984. assert.equal(w.webContents.getFrameRate(), 60)
  2985. done()
  2986. })
  2987. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  2988. })
  2989. })
  2990. describe('window.webContents.setFrameRate(frameRate)', () => {
  2991. it('sets custom frame rate', (done) => {
  2992. w.webContents.on('dom-ready', () => {
  2993. w.webContents.setFrameRate(30)
  2994. w.webContents.once('paint', function (event, rect, data) {
  2995. assert.equal(w.webContents.getFrameRate(), 30)
  2996. done()
  2997. })
  2998. })
  2999. w.loadURL('file://' + fixtures + '/api/offscreen-rendering.html')
  3000. })
  3001. })
  3002. })
  3003. })
  3004. const assertBoundsEqual = (actual, expect) => {
  3005. if (!isScaleFactorRounding()) {
  3006. assert.deepEqual(expect, actual)
  3007. } else if (Array.isArray(actual)) {
  3008. assertWithinDelta(actual[0], expect[0], 1, 'x')
  3009. assertWithinDelta(actual[1], expect[1], 1, 'y')
  3010. } else {
  3011. assertWithinDelta(actual.x, expect.x, 1, 'x')
  3012. assertWithinDelta(actual.y, expect.y, 1, 'y')
  3013. assertWithinDelta(actual.width, expect.width, 1, 'width')
  3014. assertWithinDelta(actual.height, expect.height, 1, 'height')
  3015. }
  3016. }
  3017. const assertWithinDelta = (actual, expect, delta, label) => {
  3018. const result = Math.abs(actual - expect)
  3019. assert.ok(result <= delta, `${label} value of ${actual} was not within ${delta} of ${expect}`)
  3020. }
  3021. // Is the display's scale factor possibly causing rounding of pixel coordinate
  3022. // values?
  3023. const isScaleFactorRounding = () => {
  3024. const {scaleFactor} = screen.getPrimaryDisplay()
  3025. // Return true if scale factor is non-integer value
  3026. if (Math.round(scaleFactor) !== scaleFactor) return true
  3027. // Return true if scale factor is odd number above 2
  3028. return scaleFactor > 2 && scaleFactor % 2 === 1
  3029. }
  3030. function serveFileFromProtocol (protocolName, filePath) {
  3031. return new Promise((resolve, reject) => {
  3032. protocol.registerBufferProtocol(protocolName, (request, callback) => {
  3033. // Disabled due to false positive in StandardJS
  3034. // eslint-disable-next-line standard/no-callback-literal
  3035. callback({
  3036. mimeType: 'text/html',
  3037. data: fs.readFileSync(filePath)
  3038. })
  3039. }, (error) => {
  3040. if (error != null) {
  3041. reject(error)
  3042. } else {
  3043. resolve()
  3044. }
  3045. })
  3046. })
  3047. }