chromium-spec.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. const assert = require('assert')
  2. const fs = require('fs')
  3. const http = require('http')
  4. const path = require('path')
  5. const ws = require('ws')
  6. const url = require('url')
  7. const ChildProcess = require('child_process')
  8. const {ipcRenderer, remote} = require('electron')
  9. const {closeWindow} = require('./window-helpers')
  10. const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote
  11. const isCI = remote.getGlobal('isCi')
  12. const features = process.atomBinding('features')
  13. /* Most of the APIs here don't use standard callbacks */
  14. /* eslint-disable standard/no-callback-literal */
  15. describe('chromium feature', () => {
  16. const fixtures = path.resolve(__dirname, 'fixtures')
  17. let listener = null
  18. let w = null
  19. afterEach(() => {
  20. if (listener != null) {
  21. window.removeEventListener('message', listener)
  22. }
  23. listener = null
  24. })
  25. describe('command line switches', () => {
  26. describe('--lang switch', () => {
  27. const currentLocale = app.getLocale()
  28. const testLocale = (locale, result, done) => {
  29. const appPath = path.join(__dirname, 'fixtures', 'api', 'locale-check')
  30. const electronPath = remote.getGlobal('process').execPath
  31. let output = ''
  32. let appProcess = ChildProcess.spawn(electronPath, [appPath, `--lang=${locale}`])
  33. appProcess.stdout.on('data', (data) => { output += data })
  34. appProcess.stdout.on('end', () => {
  35. output = output.replace(/(\r\n|\n|\r)/gm, '')
  36. assert.equal(output, result)
  37. done()
  38. })
  39. }
  40. it('should set the locale', (done) => testLocale('fr', 'fr', done))
  41. it('should not set an invalid locale', (done) => testLocale('asdfkl', currentLocale, done))
  42. })
  43. })
  44. afterEach(() => closeWindow(w).then(() => { w = null }))
  45. describe('heap snapshot', () => {
  46. it('does not crash', function () {
  47. process.atomBinding('v8_util').takeHeapSnapshot()
  48. })
  49. })
  50. describe('sending request of http protocol urls', () => {
  51. it('does not crash', (done) => {
  52. const server = http.createServer((req, res) => {
  53. res.end()
  54. server.close()
  55. done()
  56. })
  57. server.listen(0, '127.0.0.1', () => {
  58. const port = server.address().port
  59. $.get(`http://127.0.0.1:${port}`)
  60. })
  61. })
  62. })
  63. describe('navigator.webkitGetUserMedia', () => {
  64. it('calls its callbacks', (done) => {
  65. navigator.webkitGetUserMedia({
  66. audio: true,
  67. video: false
  68. }, () => done(),
  69. () => done())
  70. })
  71. })
  72. describe('navigator.mediaDevices', () => {
  73. if (isCI) return
  74. it('can return labels of enumerated devices', (done) => {
  75. navigator.mediaDevices.enumerateDevices().then((devices) => {
  76. const labels = devices.map((device) => device.label)
  77. const labelFound = labels.some((label) => !!label)
  78. if (labelFound) {
  79. done()
  80. } else {
  81. done(new Error(`No device labels found: ${JSON.stringify(labels)}`))
  82. }
  83. }).catch(done)
  84. })
  85. it('can return new device id when cookie storage is cleared', (done) => {
  86. const options = {
  87. origin: null,
  88. storages: ['cookies']
  89. }
  90. const deviceIds = []
  91. const ses = session.fromPartition('persist:media-device-id')
  92. w = new BrowserWindow({
  93. show: false,
  94. webPreferences: {
  95. session: ses
  96. }
  97. })
  98. w.webContents.on('ipc-message', (event, args) => {
  99. if (args[0] === 'deviceIds') deviceIds.push(args[1])
  100. if (deviceIds.length === 2) {
  101. assert.notDeepEqual(deviceIds[0], deviceIds[1])
  102. closeWindow(w).then(() => {
  103. w = null
  104. done()
  105. }).catch((error) => done(error))
  106. } else {
  107. ses.clearStorageData(options, () => {
  108. w.webContents.reload()
  109. })
  110. }
  111. })
  112. w.loadURL(`file://${fixtures}/pages/media-id-reset.html`)
  113. })
  114. })
  115. describe('navigator.language', () => {
  116. it('should not be empty', () => {
  117. assert.notEqual(navigator.language, '')
  118. })
  119. })
  120. describe('navigator.languages', (done) => {
  121. it('should return the system locale only', () => {
  122. let appLocale = app.getLocale()
  123. assert.equal(navigator.languages.length, 1)
  124. assert.equal(navigator.languages[0], appLocale)
  125. })
  126. })
  127. describe('navigator.serviceWorker', () => {
  128. it('should register for file scheme', (done) => {
  129. w = new BrowserWindow({
  130. show: false,
  131. webPreferences: {
  132. partition: 'sw-file-scheme-spec'
  133. }
  134. })
  135. w.webContents.on('ipc-message', (event, args) => {
  136. if (args[0] === 'reload') {
  137. w.webContents.reload()
  138. } else if (args[0] === 'error') {
  139. done(args[1])
  140. } else if (args[0] === 'response') {
  141. assert.equal(args[1], 'Hello from serviceWorker!')
  142. session.fromPartition('sw-file-scheme-spec').clearStorageData({
  143. storages: ['serviceworkers']
  144. }, () => done())
  145. }
  146. })
  147. w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
  148. w.loadURL(`file://${fixtures}/pages/service-worker/index.html`)
  149. })
  150. it('should register for intercepted file scheme', (done) => {
  151. const customSession = session.fromPartition('intercept-file')
  152. customSession.protocol.interceptBufferProtocol('file', (request, callback) => {
  153. let file = url.parse(request.url).pathname
  154. if (file[0] === '/' && process.platform === 'win32') file = file.slice(1)
  155. const content = fs.readFileSync(path.normalize(file))
  156. const ext = path.extname(file)
  157. let type = 'text/html'
  158. if (ext === '.js') type = 'application/javascript'
  159. callback({data: content, mimeType: type})
  160. }, (error) => {
  161. if (error) done(error)
  162. })
  163. w = new BrowserWindow({
  164. show: false,
  165. webPreferences: { session: customSession }
  166. })
  167. w.webContents.on('ipc-message', (event, args) => {
  168. if (args[0] === 'reload') {
  169. w.webContents.reload()
  170. } else if (args[0] === 'error') {
  171. done(`unexpected error : ${args[1]}`)
  172. } else if (args[0] === 'response') {
  173. assert.equal(args[1], 'Hello from serviceWorker!')
  174. customSession.clearStorageData({
  175. storages: ['serviceworkers']
  176. }, () => {
  177. customSession.protocol.uninterceptProtocol('file', (error) => done(error))
  178. })
  179. }
  180. })
  181. w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
  182. w.loadURL(`file://${fixtures}/pages/service-worker/index.html`)
  183. })
  184. })
  185. describe('window.open', () => {
  186. it('returns a BrowserWindowProxy object', () => {
  187. const b = window.open('about:blank', '', 'show=no')
  188. assert.equal(b.closed, false)
  189. assert.equal(b.constructor.name, 'BrowserWindowProxy')
  190. b.close()
  191. })
  192. it('accepts "nodeIntegration" as feature', (done) => {
  193. let b
  194. listener = (event) => {
  195. assert.equal(event.data.isProcessGlobalUndefined, true)
  196. b.close()
  197. done()
  198. }
  199. window.addEventListener('message', listener)
  200. b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no')
  201. })
  202. it('inherit options of parent window', (done) => {
  203. let b
  204. listener = (event) => {
  205. const ref1 = remote.getCurrentWindow().getSize()
  206. const width = ref1[0]
  207. const height = ref1[1]
  208. assert.equal(event.data, `size: ${width} ${height}`)
  209. b.close()
  210. done()
  211. }
  212. window.addEventListener('message', listener)
  213. b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no')
  214. })
  215. for (const show of [true, false]) {
  216. it(`inherits parent visibility over parent {show=${show}} option`, (done) => {
  217. const w = new BrowserWindow({show})
  218. // toggle visibility
  219. if (show) {
  220. w.hide()
  221. } else {
  222. w.show()
  223. }
  224. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  225. assert.equal(options.show, w.isVisible())
  226. w.close()
  227. done()
  228. })
  229. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  230. })
  231. }
  232. it('disables node integration when it is disabled on the parent window', (done) => {
  233. let b
  234. listener = (event) => {
  235. assert.equal(event.data.isProcessGlobalUndefined, true)
  236. b.close()
  237. done()
  238. }
  239. window.addEventListener('message', listener)
  240. const windowUrl = require('url').format({
  241. pathname: `${fixtures}/pages/window-opener-no-node-integration.html`,
  242. protocol: 'file',
  243. query: {
  244. p: `${fixtures}/pages/window-opener-node.html`
  245. },
  246. slashes: true
  247. })
  248. b = window.open(windowUrl, '', 'nodeIntegration=no,show=no')
  249. })
  250. it('disables webviewTag when node integration is disabled on the parent window', (done) => {
  251. let b
  252. listener = (event) => {
  253. assert.equal(event.data.isWebViewUndefined, true)
  254. b.close()
  255. done()
  256. }
  257. window.addEventListener('message', listener)
  258. const windowUrl = require('url').format({
  259. pathname: `${fixtures}/pages/window-opener-no-web-view-tag.html`,
  260. protocol: 'file',
  261. query: {
  262. p: `${fixtures}/pages/window-opener-web-view.html`
  263. },
  264. slashes: true
  265. })
  266. b = window.open(windowUrl, '', 'nodeIntegration=no,show=no')
  267. })
  268. it('disables node integration when it is disabled on the parent window for chrome devtools URLs', (done) => {
  269. let b
  270. app.once('web-contents-created', (event, contents) => {
  271. contents.once('did-finish-load', () => {
  272. contents.executeJavaScript('typeof process').then((typeofProcessGlobal) => {
  273. assert.equal(typeofProcessGlobal, 'undefined')
  274. b.close()
  275. done()
  276. }).catch(done)
  277. })
  278. })
  279. b = window.open('chrome-devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no')
  280. })
  281. it('disables JavaScript when it is disabled on the parent window', (done) => {
  282. let b
  283. app.once('web-contents-created', (event, contents) => {
  284. contents.once('did-finish-load', () => {
  285. app.once('browser-window-created', (event, window) => {
  286. const preferences = window.webContents.getLastWebPreferences()
  287. assert.equal(preferences.javascript, false)
  288. window.destroy()
  289. b.close()
  290. done()
  291. })
  292. // Click link on page
  293. contents.sendInputEvent({type: 'mouseDown', clickCount: 1, x: 1, y: 1})
  294. contents.sendInputEvent({type: 'mouseUp', clickCount: 1, x: 1, y: 1})
  295. })
  296. })
  297. const windowUrl = require('url').format({
  298. pathname: `${fixtures}/pages/window-no-javascript.html`,
  299. protocol: 'file',
  300. slashes: true
  301. })
  302. b = window.open(windowUrl, '', 'javascript=no,show=no')
  303. })
  304. it('disables the <webview> tag when it is disabled on the parent window', (done) => {
  305. let b
  306. listener = (event) => {
  307. assert.equal(event.data.isWebViewGlobalUndefined, true)
  308. b.close()
  309. done()
  310. }
  311. window.addEventListener('message', listener)
  312. const windowUrl = require('url').format({
  313. pathname: `${fixtures}/pages/window-opener-no-webview-tag.html`,
  314. protocol: 'file',
  315. query: {
  316. p: `${fixtures}/pages/window-opener-webview.html`
  317. },
  318. slashes: true
  319. })
  320. b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no')
  321. })
  322. it('does not override child options', (done) => {
  323. let b
  324. const size = {
  325. width: 350,
  326. height: 450
  327. }
  328. listener = (event) => {
  329. assert.equal(event.data, `size: ${size.width} ${size.height}`)
  330. b.close()
  331. done()
  332. }
  333. window.addEventListener('message', listener)
  334. b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height)
  335. })
  336. it('handles cycles when merging the parent options into the child options', (done) => {
  337. w = BrowserWindow.fromId(ipcRenderer.sendSync('create-window-with-options-cycle'))
  338. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  339. w.webContents.once('new-window', (event, url, frameName, disposition, options) => {
  340. assert.equal(options.show, false)
  341. assert.deepEqual(options.foo, {
  342. bar: null,
  343. baz: {
  344. hello: {
  345. world: true
  346. }
  347. },
  348. baz2: {
  349. hello: {
  350. world: true
  351. }
  352. }
  353. })
  354. done()
  355. })
  356. })
  357. it('defines a window.location getter', (done) => {
  358. let b
  359. let targetURL
  360. if (process.platform === 'win32') {
  361. targetURL = `file:///${fixtures.replace(/\\/g, '/')}/pages/base-page.html`
  362. } else {
  363. targetURL = `file://${fixtures}/pages/base-page.html`
  364. }
  365. app.once('browser-window-created', (event, window) => {
  366. window.webContents.once('did-finish-load', () => {
  367. assert.equal(b.location, targetURL)
  368. b.close()
  369. done()
  370. })
  371. })
  372. b = window.open(targetURL)
  373. })
  374. it('defines a window.location setter', (done) => {
  375. let b
  376. app.once('browser-window-created', (event, {webContents}) => {
  377. webContents.once('did-finish-load', () => {
  378. // When it loads, redirect
  379. b.location = `file://${fixtures}/pages/base-page.html`
  380. webContents.once('did-finish-load', () => {
  381. // After our second redirect, cleanup and callback
  382. b.close()
  383. done()
  384. })
  385. })
  386. })
  387. b = window.open('about:blank')
  388. })
  389. it('open a blank page when no URL is specified', (done) => {
  390. let b
  391. app.once('browser-window-created', (event, {webContents}) => {
  392. webContents.once('did-finish-load', () => {
  393. const {location} = b
  394. b.close()
  395. assert.equal(location, 'about:blank')
  396. let c
  397. app.once('browser-window-created', (event, {webContents}) => {
  398. webContents.once('did-finish-load', () => {
  399. const {location} = c
  400. c.close()
  401. assert.equal(location, 'about:blank')
  402. done()
  403. })
  404. })
  405. c = window.open('')
  406. })
  407. })
  408. b = window.open()
  409. })
  410. it('throws an exception when the arguments cannot be converted to strings', () => {
  411. assert.throws(() => {
  412. window.open('', {toString: null})
  413. }, /Cannot convert object to primitive value/)
  414. assert.throws(() => {
  415. window.open('', '', {toString: 3})
  416. }, /Cannot convert object to primitive value/)
  417. })
  418. it('sets the window title to the specified frameName', (done) => {
  419. let b
  420. app.once('browser-window-created', (event, createdWindow) => {
  421. assert.equal(createdWindow.getTitle(), 'hello')
  422. b.close()
  423. done()
  424. })
  425. b = window.open('', 'hello')
  426. })
  427. it('does not throw an exception when the frameName is a built-in object property', (done) => {
  428. let b
  429. app.once('browser-window-created', (event, createdWindow) => {
  430. assert.equal(createdWindow.getTitle(), '__proto__')
  431. b.close()
  432. done()
  433. })
  434. b = window.open('', '__proto__')
  435. })
  436. it('does not throw an exception when the features include webPreferences', () => {
  437. let b
  438. assert.doesNotThrow(() => {
  439. b = window.open('', '', 'webPreferences=')
  440. })
  441. b.close()
  442. })
  443. })
  444. describe('window.opener', () => {
  445. let url = `file://${fixtures}/pages/window-opener.html`
  446. it('is null for main window', (done) => {
  447. w = new BrowserWindow({ show: false })
  448. w.webContents.once('ipc-message', (event, args) => {
  449. assert.deepEqual(args, ['opener', null])
  450. done()
  451. })
  452. w.loadURL(url)
  453. })
  454. it('is not null for window opened by window.open', (done) => {
  455. let b
  456. listener = (event) => {
  457. assert.equal(event.data, 'object')
  458. b.close()
  459. done()
  460. }
  461. window.addEventListener('message', listener)
  462. b = window.open(url, '', 'show=no')
  463. })
  464. })
  465. describe('window.opener access from BrowserWindow', () => {
  466. const scheme = 'other'
  467. let url = `${scheme}://${fixtures}/pages/window-opener-location.html`
  468. let w = null
  469. before((done) => {
  470. protocol.registerFileProtocol(scheme, (request, callback) => {
  471. callback(`${fixtures}/pages/window-opener-location.html`)
  472. }, (error) => done(error))
  473. })
  474. after(() => {
  475. protocol.unregisterProtocol(scheme)
  476. })
  477. afterEach(() => {
  478. w.close()
  479. })
  480. it('does nothing when origin of current window does not match opener', (done) => {
  481. listener = (event) => {
  482. assert.equal(event.data, undefined)
  483. done()
  484. }
  485. window.addEventListener('message', listener)
  486. w = window.open(url, '', 'show=no,nodeIntegration=no')
  487. })
  488. it('works when origin matches', (done) => {
  489. listener = (event) => {
  490. assert.equal(event.data, location.href)
  491. done()
  492. }
  493. window.addEventListener('message', listener)
  494. w = window.open(`file://${fixtures}/pages/window-opener-location.html`, '', 'show=no,nodeIntegration=no')
  495. })
  496. it('works when origin does not match opener but has node integration', (done) => {
  497. listener = (event) => {
  498. assert.equal(event.data, location.href)
  499. done()
  500. }
  501. window.addEventListener('message', listener)
  502. w = window.open(url, '', 'show=no,nodeIntegration=yes')
  503. })
  504. })
  505. describe('window.opener access from <webview>', () => {
  506. const scheme = 'other'
  507. const srcPath = `${fixtures}/pages/webview-opener-postMessage.html`
  508. const pageURL = `file://${fixtures}/pages/window-opener-location.html`
  509. let webview = null
  510. before((done) => {
  511. protocol.registerFileProtocol(scheme, (request, callback) => {
  512. callback(srcPath)
  513. }, (error) => done(error))
  514. })
  515. after(() => {
  516. protocol.unregisterProtocol(scheme)
  517. })
  518. afterEach(() => {
  519. if (webview != null) webview.remove()
  520. })
  521. it('does nothing when origin of webview src URL does not match opener', (done) => {
  522. webview = new WebView()
  523. webview.addEventListener('console-message', (e) => {
  524. assert.equal(e.message, 'null')
  525. done()
  526. })
  527. webview.setAttribute('allowpopups', 'on')
  528. webview.src = url.format({
  529. pathname: srcPath,
  530. protocol: scheme,
  531. query: {
  532. p: pageURL
  533. },
  534. slashes: true
  535. })
  536. document.body.appendChild(webview)
  537. })
  538. it('works when origin matches', (done) => {
  539. webview = new WebView()
  540. webview.addEventListener('console-message', (e) => {
  541. assert.equal(e.message, webview.src)
  542. done()
  543. })
  544. webview.setAttribute('allowpopups', 'on')
  545. webview.src = url.format({
  546. pathname: srcPath,
  547. protocol: 'file',
  548. query: {
  549. p: pageURL
  550. },
  551. slashes: true
  552. })
  553. document.body.appendChild(webview)
  554. })
  555. it('works when origin does not match opener but has node integration', (done) => {
  556. webview = new WebView()
  557. webview.addEventListener('console-message', (e) => {
  558. webview.remove()
  559. assert.equal(e.message, webview.src)
  560. done()
  561. })
  562. webview.setAttribute('allowpopups', 'on')
  563. webview.setAttribute('nodeintegration', 'on')
  564. webview.src = url.format({
  565. pathname: srcPath,
  566. protocol: scheme,
  567. query: {
  568. p: pageURL
  569. },
  570. slashes: true
  571. })
  572. document.body.appendChild(webview)
  573. })
  574. })
  575. describe('window.postMessage', () => {
  576. it('sets the source and origin correctly', (done) => {
  577. let b
  578. listener = (event) => {
  579. window.removeEventListener('message', listener)
  580. b.close()
  581. const message = JSON.parse(event.data)
  582. assert.equal(message.data, 'testing')
  583. assert.equal(message.origin, 'file://')
  584. assert.equal(message.sourceEqualsOpener, true)
  585. assert.equal(event.origin, 'file://')
  586. done()
  587. }
  588. window.addEventListener('message', listener)
  589. app.once('browser-window-created', (event, {webContents}) => {
  590. webContents.once('did-finish-load', () => {
  591. b.postMessage('testing', '*')
  592. })
  593. })
  594. b = window.open(`file://${fixtures}/pages/window-open-postMessage.html`, '', 'show=no')
  595. })
  596. it('throws an exception when the targetOrigin cannot be converted to a string', () => {
  597. const b = window.open('')
  598. assert.throws(() => {
  599. b.postMessage('test', {toString: null})
  600. }, /Cannot convert object to primitive value/)
  601. b.close()
  602. })
  603. })
  604. describe('window.opener.postMessage', () => {
  605. it('sets source and origin correctly', (done) => {
  606. let b
  607. listener = (event) => {
  608. window.removeEventListener('message', listener)
  609. b.close()
  610. assert.equal(event.source, b)
  611. assert.equal(event.origin, 'file://')
  612. done()
  613. }
  614. window.addEventListener('message', listener)
  615. b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no')
  616. })
  617. it('supports windows opened from a <webview>', (done) => {
  618. const webview = new WebView()
  619. webview.addEventListener('console-message', (e) => {
  620. webview.remove()
  621. assert.equal(e.message, 'message')
  622. done()
  623. })
  624. webview.allowpopups = true
  625. webview.src = url.format({
  626. pathname: `${fixtures}/pages/webview-opener-postMessage.html`,
  627. protocol: 'file',
  628. query: {
  629. p: `${fixtures}/pages/window-opener-postMessage.html`
  630. },
  631. slashes: true
  632. })
  633. document.body.appendChild(webview)
  634. })
  635. describe('targetOrigin argument', () => {
  636. let serverURL
  637. let server
  638. beforeEach((done) => {
  639. server = http.createServer((req, res) => {
  640. res.writeHead(200)
  641. const filePath = path.join(fixtures, 'pages', 'window-opener-targetOrigin.html')
  642. res.end(fs.readFileSync(filePath, 'utf8'))
  643. })
  644. server.listen(0, '127.0.0.1', () => {
  645. serverURL = `http://127.0.0.1:${server.address().port}`
  646. done()
  647. })
  648. })
  649. afterEach(() => {
  650. server.close()
  651. })
  652. it('delivers messages that match the origin', (done) => {
  653. let b
  654. listener = (event) => {
  655. window.removeEventListener('message', listener)
  656. b.close()
  657. assert.equal(event.data, 'deliver')
  658. done()
  659. }
  660. window.addEventListener('message', listener)
  661. b = window.open(serverURL, '', 'show=no')
  662. })
  663. })
  664. })
  665. describe('creating a Uint8Array under browser side', () => {
  666. it('does not crash', () => {
  667. const RUint8Array = remote.getGlobal('Uint8Array')
  668. const arr = new RUint8Array()
  669. assert(arr)
  670. })
  671. })
  672. describe('webgl', () => {
  673. before(function () {
  674. if (isCI && process.platform === 'win32') {
  675. this.skip()
  676. }
  677. })
  678. it('can be get as context in canvas', () => {
  679. if (process.platform === 'linux') {
  680. // FIXME(alexeykuzmin): Skip the test.
  681. // this.skip()
  682. return
  683. }
  684. const webgl = document.createElement('canvas').getContext('webgl')
  685. assert.notEqual(webgl, null)
  686. })
  687. })
  688. describe('web workers', () => {
  689. it('Worker can work', (done) => {
  690. const worker = new Worker('../fixtures/workers/worker.js')
  691. const message = 'ping'
  692. worker.onmessage = (event) => {
  693. assert.equal(event.data, message)
  694. worker.terminate()
  695. done()
  696. }
  697. worker.postMessage(message)
  698. })
  699. it('Worker has no node integration by default', (done) => {
  700. let worker = new Worker('../fixtures/workers/worker_node.js')
  701. worker.onmessage = (event) => {
  702. assert.equal(event.data, 'undefined undefined undefined undefined')
  703. worker.terminate()
  704. done()
  705. }
  706. })
  707. it('Worker has node integration with nodeIntegrationInWorker', (done) => {
  708. let webview = new WebView()
  709. webview.addEventListener('ipc-message', (e) => {
  710. assert.equal(e.channel, 'object function object function')
  711. webview.remove()
  712. done()
  713. })
  714. webview.src = `file://${fixtures}/pages/worker.html`
  715. webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
  716. document.body.appendChild(webview)
  717. })
  718. it('SharedWorker can work', (done) => {
  719. const worker = new SharedWorker('../fixtures/workers/shared_worker.js')
  720. const message = 'ping'
  721. worker.port.onmessage = (event) => {
  722. assert.equal(event.data, message)
  723. done()
  724. }
  725. worker.port.postMessage(message)
  726. })
  727. it('SharedWorker has no node integration by default', (done) => {
  728. let worker = new SharedWorker('../fixtures/workers/shared_worker_node.js')
  729. worker.port.onmessage = (event) => {
  730. assert.equal(event.data, 'undefined undefined undefined undefined')
  731. done()
  732. }
  733. })
  734. it('SharedWorker has node integration with nodeIntegrationInWorker', (done) => {
  735. let webview = new WebView()
  736. webview.addEventListener('console-message', (e) => {
  737. console.log(e)
  738. })
  739. webview.addEventListener('ipc-message', (e) => {
  740. assert.equal(e.channel, 'object function object function')
  741. webview.remove()
  742. done()
  743. })
  744. webview.src = `file://${fixtures}/pages/shared_worker.html`
  745. webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
  746. document.body.appendChild(webview)
  747. })
  748. })
  749. describe('iframe', () => {
  750. let iframe = null
  751. beforeEach(() => {
  752. iframe = document.createElement('iframe')
  753. })
  754. afterEach(() => {
  755. document.body.removeChild(iframe)
  756. })
  757. it('does not have node integration', (done) => {
  758. iframe.src = `file://${fixtures}/pages/set-global.html`
  759. document.body.appendChild(iframe)
  760. iframe.onload = () => {
  761. assert.equal(iframe.contentWindow.test, 'undefined undefined undefined')
  762. done()
  763. }
  764. })
  765. })
  766. describe('storage', () => {
  767. it('requesting persitent quota works', (done) => {
  768. navigator.webkitPersistentStorage.requestQuota(1024 * 1024, (grantedBytes) => {
  769. assert.equal(grantedBytes, 1048576)
  770. done()
  771. })
  772. })
  773. describe('custom non standard schemes', () => {
  774. const protocolName = 'storage'
  775. let contents = null
  776. before((done) => {
  777. const handler = (request, callback) => {
  778. let parsedUrl = url.parse(request.url)
  779. let filename
  780. switch (parsedUrl.pathname) {
  781. case '/localStorage' : filename = 'local_storage.html'; break
  782. case '/sessionStorage' : filename = 'session_storage.html'; break
  783. case '/WebSQL' : filename = 'web_sql.html'; break
  784. case '/indexedDB' : filename = 'indexed_db.html'; break
  785. case '/cookie' : filename = 'cookie.html'; break
  786. default : filename = ''
  787. }
  788. callback({path: `${fixtures}/pages/storage/${filename}`})
  789. }
  790. protocol.registerFileProtocol(protocolName, handler, (error) => done(error))
  791. })
  792. after((done) => {
  793. protocol.unregisterProtocol(protocolName, () => done())
  794. })
  795. beforeEach(() => {
  796. contents = webContents.create({})
  797. })
  798. afterEach(() => {
  799. contents.destroy()
  800. contents = null
  801. })
  802. it('cannot access localStorage', (done) => {
  803. ipcMain.once('local-storage-response', (event, error) => {
  804. assert.equal(
  805. error,
  806. 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.')
  807. done()
  808. })
  809. contents.loadURL(protocolName + '://host/localStorage')
  810. })
  811. it('cannot access sessionStorage', (done) => {
  812. ipcMain.once('session-storage-response', (event, error) => {
  813. assert.equal(
  814. error,
  815. 'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.')
  816. done()
  817. })
  818. contents.loadURL(`${protocolName}://host/sessionStorage`)
  819. })
  820. it('cannot access WebSQL database', (done) => {
  821. ipcMain.once('web-sql-response', (event, error) => {
  822. assert.equal(
  823. error,
  824. 'An attempt was made to break through the security policy of the user agent.')
  825. done()
  826. })
  827. contents.loadURL(`${protocolName}://host/WebSQL`)
  828. })
  829. it('cannot access indexedDB', (done) => {
  830. ipcMain.once('indexed-db-response', (event, error) => {
  831. assert.equal(error, 'The user denied permission to access the database.')
  832. done()
  833. })
  834. contents.loadURL(`${protocolName}://host/indexedDB`)
  835. })
  836. it('cannot access cookie', (done) => {
  837. ipcMain.once('cookie-response', (event, cookie) => {
  838. assert(!cookie)
  839. done()
  840. })
  841. contents.loadURL(`${protocolName}://host/cookie`)
  842. })
  843. })
  844. })
  845. describe('websockets', () => {
  846. let wss = null
  847. let server = null
  848. const WebSocketServer = ws.Server
  849. afterEach(() => {
  850. wss.close()
  851. server.close()
  852. })
  853. it('has user agent', (done) => {
  854. server = http.createServer()
  855. server.listen(0, '127.0.0.1', () => {
  856. const port = server.address().port
  857. wss = new WebSocketServer({ server: server })
  858. wss.on('error', done)
  859. wss.on('connection', (ws) => {
  860. if (ws.upgradeReq.headers['user-agent']) {
  861. done()
  862. } else {
  863. done('user agent is empty')
  864. }
  865. })
  866. const socket = new WebSocket(`ws://127.0.0.1:${port}`)
  867. assert(socket)
  868. })
  869. })
  870. })
  871. describe('Promise', () => {
  872. it('resolves correctly in Node.js calls', (done) => {
  873. document.registerElement('x-element', {
  874. prototype: Object.create(HTMLElement.prototype, {
  875. createdCallback: {
  876. value: () => {}
  877. }
  878. })
  879. })
  880. setImmediate(() => {
  881. let called = false
  882. Promise.resolve().then(() => {
  883. done(called ? void 0 : new Error('wrong sequence'))
  884. })
  885. document.createElement('x-element')
  886. called = true
  887. })
  888. })
  889. it('resolves correctly in Electron calls', (done) => {
  890. document.registerElement('y-element', {
  891. prototype: Object.create(HTMLElement.prototype, {
  892. createdCallback: {
  893. value: () => {}
  894. }
  895. })
  896. })
  897. remote.getGlobal('setImmediate')(() => {
  898. let called = false
  899. Promise.resolve().then(() => {
  900. done(called ? void 0 : new Error('wrong sequence'))
  901. })
  902. document.createElement('y-element')
  903. called = true
  904. })
  905. })
  906. })
  907. describe('fetch', () => {
  908. it('does not crash', (done) => {
  909. const server = http.createServer((req, res) => {
  910. res.end('test')
  911. server.close()
  912. })
  913. server.listen(0, '127.0.0.1', () => {
  914. const port = server.address().port
  915. fetch(`http://127.0.0.1:${port}`).then((res) => res.body.getReader())
  916. .then((reader) => {
  917. reader.read().then((r) => {
  918. reader.cancel()
  919. done()
  920. })
  921. }).catch((e) => done(e))
  922. })
  923. })
  924. })
  925. describe('PDF Viewer', () => {
  926. before(function () {
  927. if (!features.isPDFViewerEnabled()) {
  928. return this.skip()
  929. }
  930. })
  931. beforeEach(() => {
  932. this.pdfSource = url.format({
  933. pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
  934. protocol: 'file',
  935. slashes: true
  936. })
  937. this.pdfSourceWithParams = url.format({
  938. pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
  939. query: {
  940. a: 1,
  941. b: 2
  942. },
  943. protocol: 'file',
  944. slashes: true
  945. })
  946. this.createBrowserWindow = ({plugins, preload}) => {
  947. w = new BrowserWindow({
  948. show: false,
  949. webPreferences: {
  950. preload: path.join(fixtures, 'module', preload),
  951. plugins: plugins
  952. }
  953. })
  954. }
  955. this.testPDFIsLoadedInSubFrame = (page, preloadFile, done) => {
  956. const pagePath = url.format({
  957. pathname: path.join(fixtures, 'pages', page).replace(/\\/g, '/'),
  958. protocol: 'file',
  959. slashes: true
  960. })
  961. this.createBrowserWindow({plugins: true, preload: preloadFile})
  962. ipcMain.once('pdf-loaded', (event, state) => {
  963. assert.equal(state, 'success')
  964. done()
  965. })
  966. w.webContents.on('page-title-updated', () => {
  967. const parsedURL = url.parse(w.webContents.getURL(), true)
  968. assert.equal(parsedURL.protocol, 'chrome:')
  969. assert.equal(parsedURL.hostname, 'pdf-viewer')
  970. assert.equal(parsedURL.query.src, pagePath)
  971. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  972. })
  973. w.webContents.loadURL(pagePath)
  974. }
  975. })
  976. it('opens when loading a pdf resource as top level navigation', (done) => {
  977. this.createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
  978. ipcMain.once('pdf-loaded', (event, state) => {
  979. assert.equal(state, 'success')
  980. done()
  981. })
  982. w.webContents.on('page-title-updated', () => {
  983. const parsedURL = url.parse(w.webContents.getURL(), true)
  984. assert.equal(parsedURL.protocol, 'chrome:')
  985. assert.equal(parsedURL.hostname, 'pdf-viewer')
  986. assert.equal(parsedURL.query.src, this.pdfSource)
  987. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  988. })
  989. w.webContents.loadURL(this.pdfSource)
  990. })
  991. it('opens a pdf link given params, the query string should be escaped', (done) => {
  992. this.createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
  993. ipcMain.once('pdf-loaded', (event, state) => {
  994. assert.equal(state, 'success')
  995. done()
  996. })
  997. w.webContents.on('page-title-updated', () => {
  998. const parsedURL = url.parse(w.webContents.getURL(), true)
  999. assert.equal(parsedURL.protocol, 'chrome:')
  1000. assert.equal(parsedURL.hostname, 'pdf-viewer')
  1001. assert.equal(parsedURL.query.src, this.pdfSourceWithParams)
  1002. assert.equal(parsedURL.query.b, undefined)
  1003. assert(parsedURL.search.endsWith('%3Fa%3D1%26b%3D2'))
  1004. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  1005. })
  1006. w.webContents.loadURL(this.pdfSourceWithParams)
  1007. })
  1008. it('should download a pdf when plugins are disabled', (done) => {
  1009. this.createBrowserWindow({plugins: false, preload: 'preload-pdf-loaded.js'})
  1010. ipcRenderer.sendSync('set-download-option', false, false)
  1011. ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => {
  1012. assert.equal(state, 'completed')
  1013. assert.equal(filename, 'cat.pdf')
  1014. assert.equal(mimeType, 'application/pdf')
  1015. fs.unlinkSync(path.join(fixtures, 'mock.pdf'))
  1016. done()
  1017. })
  1018. w.webContents.loadURL(this.pdfSource)
  1019. })
  1020. it('should not open when pdf is requested as sub resource', (done) => {
  1021. fetch(this.pdfSource).then((res) => {
  1022. assert.equal(res.status, 200)
  1023. assert.notEqual(document.title, 'cat.pdf')
  1024. done()
  1025. }).catch((e) => done(e))
  1026. })
  1027. it('opens when loading a pdf resource in a iframe', (done) => {
  1028. this.testPDFIsLoadedInSubFrame('pdf-in-iframe.html', 'preload-pdf-loaded-in-subframe.js', done)
  1029. })
  1030. it('opens when loading a pdf resource in a nested iframe', (done) => {
  1031. this.testPDFIsLoadedInSubFrame('pdf-in-nested-iframe.html', 'preload-pdf-loaded-in-nested-subframe.js', done)
  1032. })
  1033. })
  1034. describe('window.alert(message, title)', () => {
  1035. it('throws an exception when the arguments cannot be converted to strings', () => {
  1036. assert.throws(() => {
  1037. window.alert({toString: null})
  1038. }, /Cannot convert object to primitive value/)
  1039. })
  1040. })
  1041. describe('window.confirm(message, title)', () => {
  1042. it('throws an exception when the arguments cannot be converted to strings', () => {
  1043. assert.throws(() => {
  1044. window.confirm({toString: null}, 'title')
  1045. }, /Cannot convert object to primitive value/)
  1046. })
  1047. })
  1048. describe('window.history', () => {
  1049. describe('window.history.go(offset)', () => {
  1050. it('throws an exception when the argumnet cannot be converted to a string', () => {
  1051. assert.throws(() => {
  1052. window.history.go({toString: null})
  1053. }, /Cannot convert object to primitive value/)
  1054. })
  1055. })
  1056. describe('window.history.pushState', () => {
  1057. it('should push state after calling history.pushState() from the same url', (done) => {
  1058. w = new BrowserWindow({ show: false })
  1059. w.webContents.once('did-finish-load', () => {
  1060. // History should have current page by now.
  1061. assert.equal(w.webContents.length(), 1)
  1062. w.webContents.executeJavaScript('window.history.pushState({}, "")', () => {
  1063. // Initial page + pushed state
  1064. assert.equal(w.webContents.length(), 2)
  1065. done()
  1066. })
  1067. })
  1068. w.loadURL('about:blank')
  1069. })
  1070. })
  1071. })
  1072. })