api-protocol-spec.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. const assert = require('assert')
  2. const http = require('http')
  3. const path = require('path')
  4. const qs = require('querystring')
  5. const {closeWindow} = require('./window-helpers')
  6. const {remote} = require('electron')
  7. const {BrowserWindow, ipcMain, protocol, session, webContents} = remote
  8. // The RPC API doesn't seem to support calling methods on remote objects very
  9. // well. In order to test stream protocol, we must work around this limitation
  10. // and use Stream instances created in the browser process.
  11. const stream = remote.require('stream')
  12. /* The whole protocol API doesn't use standard callbacks */
  13. /* eslint-disable standard/no-callback-literal */
  14. describe('protocol module', () => {
  15. const protocolName = 'sp'
  16. const text = 'valar morghulis'
  17. const postData = {
  18. name: 'post test',
  19. type: 'string'
  20. }
  21. function delay (ms) {
  22. return new Promise((resolve) => {
  23. setTimeout(resolve, ms)
  24. })
  25. }
  26. function getStream (chunkSize = text.length, data = text) {
  27. const body = stream.PassThrough()
  28. async function sendChunks () {
  29. let buf = Buffer.from(data)
  30. for (;;) {
  31. body.push(buf.slice(0, chunkSize))
  32. buf = buf.slice(chunkSize)
  33. if (!buf.length) {
  34. break
  35. }
  36. // emulate network delay
  37. await delay(50)
  38. }
  39. body.push(null)
  40. }
  41. sendChunks()
  42. return body
  43. }
  44. afterEach((done) => {
  45. protocol.unregisterProtocol(protocolName, () => {
  46. protocol.uninterceptProtocol('http', () => done())
  47. })
  48. })
  49. describe('protocol.register(Any)Protocol', () => {
  50. const emptyHandler = (request, callback) => callback()
  51. it('throws error when scheme is already registered', (done) => {
  52. protocol.registerStringProtocol(protocolName, emptyHandler, (error) => {
  53. assert.equal(error, null)
  54. protocol.registerBufferProtocol(protocolName, emptyHandler, (error) => {
  55. assert.notEqual(error, null)
  56. done()
  57. })
  58. })
  59. })
  60. it('does not crash when handler is called twice', (done) => {
  61. const doubleHandler = (request, callback) => {
  62. try {
  63. callback(text)
  64. callback()
  65. } catch (error) {
  66. // Ignore error
  67. }
  68. }
  69. protocol.registerStringProtocol(protocolName, doubleHandler, (error) => {
  70. if (error) return done(error)
  71. $.ajax({
  72. url: protocolName + '://fake-host',
  73. cache: false,
  74. success: (data) => {
  75. assert.equal(data, text)
  76. done()
  77. },
  78. error: (xhr, errorType, error) => done(error)
  79. })
  80. })
  81. })
  82. it('sends error when callback is called with nothing', (done) => {
  83. protocol.registerBufferProtocol(protocolName, emptyHandler, (error) => {
  84. if (error) return done(error)
  85. $.ajax({
  86. url: protocolName + '://fake-host',
  87. cache: false,
  88. success: () => done('request succeeded but it should not'),
  89. error: (xhr, errorType) => {
  90. assert.equal(errorType, 'error')
  91. return done()
  92. }
  93. })
  94. })
  95. })
  96. it('does not crash when callback is called in next tick', (done) => {
  97. const handler = (request, callback) => {
  98. setImmediate(() => callback(text))
  99. }
  100. protocol.registerStringProtocol(protocolName, handler, (error) => {
  101. if (error) return done(error)
  102. $.ajax({
  103. url: protocolName + '://fake-host',
  104. cache: false,
  105. success: (data) => {
  106. assert.equal(data, text)
  107. done()
  108. },
  109. error: (xhr, errorType, error) => done(error)
  110. })
  111. })
  112. })
  113. })
  114. describe('protocol.unregisterProtocol', () => {
  115. it('returns error when scheme does not exist', (done) => {
  116. protocol.unregisterProtocol('not-exist', (error) => {
  117. assert.notEqual(error, null)
  118. done()
  119. })
  120. })
  121. })
  122. describe('protocol.registerStringProtocol', () => {
  123. it('sends string as response', (done) => {
  124. const handler = (request, callback) => callback(text)
  125. protocol.registerStringProtocol(protocolName, handler, (error) => {
  126. if (error) return done(error)
  127. $.ajax({
  128. url: protocolName + '://fake-host',
  129. cache: false,
  130. success: (data) => {
  131. assert.equal(data, text)
  132. done()
  133. },
  134. error: (xhr, errorType, error) => done(error)
  135. })
  136. })
  137. })
  138. it('sets Access-Control-Allow-Origin', (done) => {
  139. const handler = (request, callback) => callback(text)
  140. protocol.registerStringProtocol(protocolName, handler, (error) => {
  141. if (error) return done(error)
  142. $.ajax({
  143. url: protocolName + '://fake-host',
  144. cache: false,
  145. success: (data, status, request) => {
  146. assert.equal(data, text)
  147. assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  148. done()
  149. },
  150. error: (xhr, errorType, error) => done(error)
  151. })
  152. })
  153. })
  154. it('sends object as response', (done) => {
  155. const handler = (request, callback) => {
  156. callback({
  157. data: text,
  158. mimeType: 'text/html'
  159. })
  160. }
  161. protocol.registerStringProtocol(protocolName, handler, (error) => {
  162. if (error) return done(error)
  163. $.ajax({
  164. url: protocolName + '://fake-host',
  165. cache: false,
  166. success: (data) => {
  167. assert.equal(data, text)
  168. done()
  169. },
  170. error: (xhr, errorType, error) => done(error)
  171. })
  172. })
  173. })
  174. it('fails when sending object other than string', (done) => {
  175. const handler = (request, callback) => callback(new Date())
  176. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  177. if (error) return done(error)
  178. $.ajax({
  179. url: protocolName + '://fake-host',
  180. cache: false,
  181. success: () => done('request succeeded but it should not'),
  182. error: (xhr, errorType) => {
  183. assert.equal(errorType, 'error')
  184. done()
  185. }
  186. })
  187. })
  188. })
  189. })
  190. describe('protocol.registerBufferProtocol', () => {
  191. const buffer = Buffer.from(text)
  192. it('sends Buffer as response', (done) => {
  193. const handler = (request, callback) => callback(buffer)
  194. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  195. if (error) return done(error)
  196. $.ajax({
  197. url: protocolName + '://fake-host',
  198. cache: false,
  199. success: (data) => {
  200. assert.equal(data, text)
  201. done()
  202. },
  203. error: (xhr, errorType, error) => done(error)
  204. })
  205. })
  206. })
  207. it('sets Access-Control-Allow-Origin', (done) => {
  208. const handler = (request, callback) => callback(buffer)
  209. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  210. if (error) return done(error)
  211. $.ajax({
  212. url: protocolName + '://fake-host',
  213. cache: false,
  214. success: (data, status, request) => {
  215. assert.equal(data, text)
  216. assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  217. done()
  218. },
  219. error: (xhr, errorType, error) => done(error)
  220. })
  221. })
  222. })
  223. it('sends object as response', (done) => {
  224. const handler = (request, callback) => {
  225. callback({
  226. data: buffer,
  227. mimeType: 'text/html'
  228. })
  229. }
  230. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  231. if (error) return done(error)
  232. $.ajax({
  233. url: protocolName + '://fake-host',
  234. cache: false,
  235. success: (data) => {
  236. assert.equal(data, text)
  237. done()
  238. },
  239. error: (xhr, errorType, error) => done(error)
  240. })
  241. })
  242. })
  243. it('fails when sending string', (done) => {
  244. const handler = (request, callback) => callback(text)
  245. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  246. if (error) return done(error)
  247. $.ajax({
  248. url: protocolName + '://fake-host',
  249. cache: false,
  250. success: () => done('request succeeded but it should not'),
  251. error: (xhr, errorType) => {
  252. assert.equal(errorType, 'error')
  253. done()
  254. }
  255. })
  256. })
  257. })
  258. })
  259. describe('protocol.registerFileProtocol', () => {
  260. const filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1')
  261. const fileContent = require('fs').readFileSync(filePath)
  262. const normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html')
  263. const normalContent = require('fs').readFileSync(normalPath)
  264. it('sends file path as response', (done) => {
  265. const handler = (request, callback) => callback(filePath)
  266. protocol.registerFileProtocol(protocolName, handler, (error) => {
  267. if (error) return done(error)
  268. $.ajax({
  269. url: protocolName + '://fake-host',
  270. cache: false,
  271. success: (data) => {
  272. assert.equal(data, String(fileContent))
  273. return done()
  274. },
  275. error: (xhr, errorType, error) => done(error)
  276. })
  277. })
  278. })
  279. it('sets Access-Control-Allow-Origin', (done) => {
  280. const handler = (request, callback) => callback(filePath)
  281. protocol.registerFileProtocol(protocolName, handler, (error) => {
  282. if (error) return done(error)
  283. $.ajax({
  284. url: protocolName + '://fake-host',
  285. cache: false,
  286. success: (data, status, request) => {
  287. assert.equal(data, String(fileContent))
  288. assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  289. done()
  290. },
  291. error: (xhr, errorType, error) => {
  292. done(error)
  293. }
  294. })
  295. })
  296. })
  297. it('sends object as response', (done) => {
  298. const handler = (request, callback) => callback({ path: filePath })
  299. protocol.registerFileProtocol(protocolName, handler, (error) => {
  300. if (error) return done(error)
  301. $.ajax({
  302. url: protocolName + '://fake-host',
  303. cache: false,
  304. success: (data) => {
  305. assert.equal(data, String(fileContent))
  306. done()
  307. },
  308. error: (xhr, errorType, error) => done(error)
  309. })
  310. })
  311. })
  312. it('can send normal file', (done) => {
  313. const handler = (request, callback) => callback(normalPath)
  314. protocol.registerFileProtocol(protocolName, handler, (error) => {
  315. if (error) return done(error)
  316. $.ajax({
  317. url: protocolName + '://fake-host',
  318. cache: false,
  319. success: (data) => {
  320. assert.equal(data, String(normalContent))
  321. done()
  322. },
  323. error: (xhr, errorType, error) => done(error)
  324. })
  325. })
  326. })
  327. it('fails when sending unexist-file', (done) => {
  328. const fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist')
  329. const handler = (request, callback) => callback(fakeFilePath)
  330. protocol.registerFileProtocol(protocolName, handler, (error) => {
  331. if (error) return done(error)
  332. $.ajax({
  333. url: protocolName + '://fake-host',
  334. cache: false,
  335. success: () => done('request succeeded but it should not'),
  336. error: (xhr, errorType) => {
  337. assert.equal(errorType, 'error')
  338. done()
  339. }
  340. })
  341. })
  342. })
  343. it('fails when sending unsupported content', (done) => {
  344. const handler = (request, callback) => callback(new Date())
  345. protocol.registerFileProtocol(protocolName, handler, (error) => {
  346. if (error) return done(error)
  347. $.ajax({
  348. url: protocolName + '://fake-host',
  349. cache: false,
  350. success: () => done('request succeeded but it should not'),
  351. error: (xhr, errorType) => {
  352. assert.equal(errorType, 'error')
  353. done()
  354. }
  355. })
  356. })
  357. })
  358. })
  359. describe('protocol.registerHttpProtocol', () => {
  360. it('sends url as response', (done) => {
  361. const server = http.createServer((req, res) => {
  362. assert.notEqual(req.headers.accept, '')
  363. res.end(text)
  364. server.close()
  365. })
  366. server.listen(0, '127.0.0.1', () => {
  367. const port = server.address().port
  368. const url = 'http://127.0.0.1:' + port
  369. const handler = (request, callback) => callback({url})
  370. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  371. if (error) return done(error)
  372. $.ajax({
  373. url: protocolName + '://fake-host',
  374. cache: false,
  375. success: (data) => {
  376. assert.equal(data, text)
  377. done()
  378. },
  379. error: (xhr, errorType, error) => done(error)
  380. })
  381. })
  382. })
  383. })
  384. it('fails when sending invalid url', (done) => {
  385. const handler = (request, callback) => callback({url: 'url'})
  386. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  387. if (error) return done(error)
  388. $.ajax({
  389. url: protocolName + '://fake-host',
  390. cache: false,
  391. success: () => done('request succeeded but it should not'),
  392. error: (xhr, errorType) => {
  393. assert.equal(errorType, 'error')
  394. done()
  395. }
  396. })
  397. })
  398. })
  399. it('fails when sending unsupported content', (done) => {
  400. const handler = (request, callback) => callback(new Date())
  401. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  402. if (error) return done(error)
  403. $.ajax({
  404. url: protocolName + '://fake-host',
  405. cache: false,
  406. success: () => {
  407. done('request succeeded but it should not')
  408. },
  409. error: (xhr, errorType) => {
  410. assert.equal(errorType, 'error')
  411. done()
  412. }
  413. })
  414. })
  415. })
  416. it('works when target URL redirects', (done) => {
  417. let contents = null
  418. const server = http.createServer((req, res) => {
  419. if (req.url === '/serverRedirect') {
  420. res.statusCode = 301
  421. res.setHeader('Location', `http://${req.rawHeaders[1]}`)
  422. res.end()
  423. } else {
  424. res.end(text)
  425. }
  426. })
  427. server.listen(0, '127.0.0.1', () => {
  428. const port = server.address().port
  429. const url = `${protocolName}://fake-host`
  430. const redirectURL = `http://127.0.0.1:${port}/serverRedirect`
  431. const handler = (request, callback) => callback({url: redirectURL})
  432. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  433. if (error) return done(error)
  434. contents = webContents.create({})
  435. contents.on('did-finish-load', () => {
  436. assert.equal(contents.getURL(), url)
  437. server.close()
  438. contents.destroy()
  439. done()
  440. })
  441. contents.loadURL(url)
  442. })
  443. })
  444. })
  445. })
  446. describe('protocol.registerStreamProtocol', () => {
  447. it('sends Stream as response', (done) => {
  448. const handler = (request, callback) => callback(getStream())
  449. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  450. if (error) return done(error)
  451. $.ajax({
  452. url: protocolName + '://fake-host',
  453. cache: false,
  454. success: (data) => {
  455. assert.equal(data, text)
  456. done()
  457. },
  458. error: (xhr, errorType, error) => {
  459. done(error || new Error(`Request failed: ${xhr.status}`))
  460. }
  461. })
  462. })
  463. })
  464. it('sends object as response', (done) => {
  465. const handler = (request, callback) => callback({data: getStream()})
  466. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  467. if (error) return done(error)
  468. $.ajax({
  469. url: protocolName + '://fake-host',
  470. cache: false,
  471. success: (data, _, request) => {
  472. assert.equal(request.status, 200)
  473. assert.equal(data, text)
  474. done()
  475. },
  476. error: (xhr, errorType, error) => {
  477. done(error || new Error(`Request failed: ${xhr.status}`))
  478. }
  479. })
  480. })
  481. })
  482. it('sends custom response headers', (done) => {
  483. const handler = (request, callback) => callback({
  484. data: getStream(3),
  485. headers: {
  486. 'x-electron': ['a', 'b']
  487. }
  488. })
  489. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  490. if (error) return done(error)
  491. $.ajax({
  492. url: protocolName + '://fake-host',
  493. cache: false,
  494. success: (data, _, request) => {
  495. assert.equal(request.status, 200)
  496. assert.equal(request.getResponseHeader('x-electron'), 'a,b')
  497. assert.equal(data, text)
  498. done()
  499. },
  500. error: (xhr, errorType, error) => {
  501. done(error || new Error(`Request failed: ${xhr.status}`))
  502. }
  503. })
  504. })
  505. })
  506. it('sends custom status code', (done) => {
  507. const handler = (request, callback) => callback({
  508. statusCode: 204,
  509. data: null
  510. })
  511. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  512. if (error) return done(error)
  513. $.ajax({
  514. url: protocolName + '://fake-host',
  515. cache: false,
  516. success: (data, _, request) => {
  517. assert.equal(request.status, 204)
  518. assert.equal(data, undefined)
  519. done()
  520. },
  521. error: (xhr, errorType, error) => {
  522. done(error || new Error(`Request failed: ${xhr.status}`))
  523. }
  524. })
  525. })
  526. })
  527. it('receives request headers', (done) => {
  528. const handler = (request, callback) => {
  529. callback({
  530. headers: {
  531. 'content-type': 'application/json'
  532. },
  533. data: getStream(5, JSON.stringify(Object.assign({}, request.headers)))
  534. })
  535. }
  536. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  537. if (error) return done(error)
  538. $.ajax({
  539. url: protocolName + '://fake-host',
  540. headers: {
  541. 'x-return-headers': 'yes'
  542. },
  543. cache: false,
  544. success: (data) => {
  545. assert.equal(data['x-return-headers'], 'yes')
  546. done()
  547. },
  548. error: (xhr, errorType, error) => {
  549. done(error || new Error(`Request failed: ${xhr.status}`))
  550. }
  551. })
  552. })
  553. })
  554. })
  555. describe('protocol.isProtocolHandled', () => {
  556. it('returns true for about:', (done) => {
  557. protocol.isProtocolHandled('about', (result) => {
  558. assert.equal(result, true)
  559. done()
  560. })
  561. })
  562. it('returns true for file:', (done) => {
  563. protocol.isProtocolHandled('file', (result) => {
  564. assert.equal(result, true)
  565. done()
  566. })
  567. })
  568. it('returns true for http:', (done) => {
  569. protocol.isProtocolHandled('http', (result) => {
  570. assert.equal(result, true)
  571. done()
  572. })
  573. })
  574. it('returns true for https:', (done) => {
  575. protocol.isProtocolHandled('https', (result) => {
  576. assert.equal(result, true)
  577. done()
  578. })
  579. })
  580. it('returns false when scheme is not registered', (done) => {
  581. protocol.isProtocolHandled('no-exist', (result) => {
  582. assert.equal(result, false)
  583. done()
  584. })
  585. })
  586. it('returns true for custom protocol', (done) => {
  587. const emptyHandler = (request, callback) => callback()
  588. protocol.registerStringProtocol(protocolName, emptyHandler, (error) => {
  589. assert.equal(error, null)
  590. protocol.isProtocolHandled(protocolName, (result) => {
  591. assert.equal(result, true)
  592. done()
  593. })
  594. })
  595. })
  596. it('returns true for intercepted protocol', (done) => {
  597. const emptyHandler = (request, callback) => callback()
  598. protocol.interceptStringProtocol('http', emptyHandler, (error) => {
  599. assert.equal(error, null)
  600. protocol.isProtocolHandled('http', (result) => {
  601. assert.equal(result, true)
  602. done()
  603. })
  604. })
  605. })
  606. })
  607. describe('protocol.intercept(Any)Protocol', () => {
  608. const emptyHandler = (request, callback) => callback()
  609. it('throws error when scheme is already intercepted', (done) => {
  610. protocol.interceptStringProtocol('http', emptyHandler, (error) => {
  611. assert.equal(error, null)
  612. protocol.interceptBufferProtocol('http', emptyHandler, (error) => {
  613. assert.notEqual(error, null)
  614. done()
  615. })
  616. })
  617. })
  618. it('does not crash when handler is called twice', (done) => {
  619. var doubleHandler = (request, callback) => {
  620. try {
  621. callback(text)
  622. callback()
  623. } catch (error) {
  624. // Ignore error
  625. }
  626. }
  627. protocol.interceptStringProtocol('http', doubleHandler, (error) => {
  628. if (error) return done(error)
  629. $.ajax({
  630. url: 'http://fake-host',
  631. cache: false,
  632. success: (data) => {
  633. assert.equal(data, text)
  634. done()
  635. },
  636. error: (xhr, errorType, error) => done(error)
  637. })
  638. })
  639. })
  640. it('sends error when callback is called with nothing', function (done) {
  641. protocol.interceptBufferProtocol('http', emptyHandler, (error) => {
  642. if (error) return done(error)
  643. $.ajax({
  644. url: 'http://fake-host',
  645. cache: false,
  646. success: () => done('request succeeded but it should not'),
  647. error: (xhr, errorType) => {
  648. assert.equal(errorType, 'error')
  649. done()
  650. }
  651. })
  652. })
  653. })
  654. })
  655. describe('protocol.interceptStringProtocol', () => {
  656. it('can intercept http protocol', (done) => {
  657. const handler = (request, callback) => callback(text)
  658. protocol.interceptStringProtocol('http', handler, (error) => {
  659. if (error) return done(error)
  660. $.ajax({
  661. url: 'http://fake-host',
  662. cache: false,
  663. success: (data) => {
  664. assert.equal(data, text)
  665. done()
  666. },
  667. error: (xhr, errorType, error) => done(error)
  668. })
  669. })
  670. })
  671. it('can set content-type', (done) => {
  672. const handler = (request, callback) => {
  673. callback({
  674. mimeType: 'application/json',
  675. data: '{"value": 1}'
  676. })
  677. }
  678. protocol.interceptStringProtocol('http', handler, (error) => {
  679. if (error) return done(error)
  680. $.ajax({
  681. url: 'http://fake-host',
  682. cache: false,
  683. success: (data) => {
  684. assert.equal(typeof data, 'object')
  685. assert.equal(data.value, 1)
  686. done()
  687. },
  688. error: (xhr, errorType, error) => done(error)
  689. })
  690. })
  691. })
  692. it('can receive post data', (done) => {
  693. const handler = (request, callback) => {
  694. const uploadData = request.uploadData[0].bytes.toString()
  695. callback({data: uploadData})
  696. }
  697. protocol.interceptStringProtocol('http', handler, (error) => {
  698. if (error) return done(error)
  699. $.ajax({
  700. url: 'http://fake-host',
  701. cache: false,
  702. type: 'POST',
  703. data: postData,
  704. success: (data) => {
  705. assert.deepEqual(qs.parse(data), postData)
  706. done()
  707. },
  708. error: (xhr, errorType, error) => done(error)
  709. })
  710. })
  711. })
  712. })
  713. describe('protocol.interceptBufferProtocol', () => {
  714. it('can intercept http protocol', (done) => {
  715. const handler = (request, callback) => callback(Buffer.from(text))
  716. protocol.interceptBufferProtocol('http', handler, (error) => {
  717. if (error) return done(error)
  718. $.ajax({
  719. url: 'http://fake-host',
  720. cache: false,
  721. success: (data) => {
  722. assert.equal(data, text)
  723. done()
  724. },
  725. error: (xhr, errorType, error) => done(error)
  726. })
  727. })
  728. })
  729. it('can receive post data', (done) => {
  730. const handler = (request, callback) => {
  731. const uploadData = request.uploadData[0].bytes
  732. callback(uploadData)
  733. }
  734. protocol.interceptBufferProtocol('http', handler, (error) => {
  735. if (error) return done(error)
  736. $.ajax({
  737. url: 'http://fake-host',
  738. cache: false,
  739. type: 'POST',
  740. data: postData,
  741. success: (data) => {
  742. assert.equal(data, $.param(postData))
  743. done()
  744. },
  745. error: (xhr, errorType, error) => done(error)
  746. })
  747. })
  748. })
  749. })
  750. describe('protocol.interceptHttpProtocol', () => {
  751. it('can send POST request', (done) => {
  752. const server = http.createServer((req, res) => {
  753. let body = ''
  754. req.on('data', (chunk) => {
  755. body += chunk
  756. })
  757. req.on('end', () => {
  758. res.end(body)
  759. })
  760. server.close()
  761. })
  762. server.listen(0, '127.0.0.1', () => {
  763. const port = server.address().port
  764. const url = `http://127.0.0.1:${port}`
  765. const handler = (request, callback) => {
  766. const data = {
  767. url: url,
  768. method: 'POST',
  769. uploadData: {
  770. contentType: 'application/x-www-form-urlencoded',
  771. data: request.uploadData[0].bytes.toString()
  772. },
  773. session: null
  774. }
  775. callback(data)
  776. }
  777. protocol.interceptHttpProtocol('http', handler, (error) => {
  778. if (error) return done(error)
  779. $.ajax({
  780. url: 'http://fake-host',
  781. cache: false,
  782. type: 'POST',
  783. data: postData,
  784. success: (data) => {
  785. assert.deepEqual(qs.parse(data), postData)
  786. done()
  787. },
  788. error: (xhr, errorType, error) => done(error)
  789. })
  790. })
  791. })
  792. })
  793. it('can use custom session', (done) => {
  794. const customSession = session.fromPartition('custom-ses', {cache: false})
  795. customSession.webRequest.onBeforeRequest((details, callback) => {
  796. assert.equal(details.url, 'http://fake-host/')
  797. callback({cancel: true})
  798. })
  799. const handler = (request, callback) => {
  800. callback({
  801. url: request.url,
  802. session: customSession
  803. })
  804. }
  805. protocol.interceptHttpProtocol('http', handler, (error) => {
  806. if (error) return done(error)
  807. fetch('http://fake-host').then(() => {
  808. done('request succeeded but it should not')
  809. }).catch(() => {
  810. customSession.webRequest.onBeforeRequest(null)
  811. done()
  812. })
  813. })
  814. })
  815. })
  816. describe('protocol.interceptStreamProtocol', () => {
  817. it('can intercept http protocol', (done) => {
  818. const handler = (request, callback) => callback(getStream())
  819. protocol.interceptStreamProtocol('http', handler, (error) => {
  820. if (error) return done(error)
  821. $.ajax({
  822. url: 'http://fake-host',
  823. cache: false,
  824. success: (data) => {
  825. assert.equal(data, text)
  826. done()
  827. },
  828. error: (xhr, errorType, error) => {
  829. done(error || new Error(`Request failed: ${xhr.status}`))
  830. }
  831. })
  832. })
  833. })
  834. it('can receive post data', (done) => {
  835. const handler = (request, callback) => {
  836. callback(getStream(3, request.uploadData[0].bytes.toString()))
  837. }
  838. protocol.interceptStreamProtocol('http', handler, (error) => {
  839. if (error) return done(error)
  840. $.ajax({
  841. url: 'http://fake-host',
  842. cache: false,
  843. type: 'POST',
  844. data: postData,
  845. success: (data) => {
  846. assert.deepEqual(qs.parse(data), postData)
  847. done()
  848. },
  849. error: (xhr, errorType, error) => {
  850. done(error || new Error(`Request failed: ${xhr.status}`))
  851. }
  852. })
  853. })
  854. })
  855. it('can execute redirects', (done) => {
  856. const handler = (request, callback) => {
  857. if (request.url.indexOf('http://fake-host') === 0) {
  858. setTimeout(() => {
  859. callback({
  860. data: null,
  861. statusCode: 302,
  862. headers: {
  863. Location: 'http://fake-redirect'
  864. }
  865. })
  866. }, 300)
  867. } else {
  868. assert.equal(request.url.indexOf('http://fake-redirect'), 0)
  869. callback(getStream(1, 'redirect'))
  870. }
  871. }
  872. protocol.interceptStreamProtocol('http', handler, (error) => {
  873. if (error) return done(error)
  874. $.ajax({
  875. url: 'http://fake-host',
  876. cache: false,
  877. success: (data) => {
  878. assert.equal(data, 'redirect')
  879. done()
  880. },
  881. error: (xhr, errorType, error) => {
  882. done(error || new Error(`Request failed: ${xhr.status}`))
  883. }
  884. })
  885. })
  886. })
  887. })
  888. describe('protocol.uninterceptProtocol', () => {
  889. it('returns error when scheme does not exist', (done) => {
  890. protocol.uninterceptProtocol('not-exist', (error) => {
  891. assert.notEqual(error, null)
  892. done()
  893. })
  894. })
  895. it('returns error when scheme is not intercepted', (done) => {
  896. protocol.uninterceptProtocol('http', (error) => {
  897. assert.notEqual(error, null)
  898. done()
  899. })
  900. })
  901. })
  902. describe('protocol.registerStandardSchemes', () => {
  903. const standardScheme = remote.getGlobal('standardScheme')
  904. const origin = `${standardScheme}://fake-host`
  905. const imageURL = `${origin}/test.png`
  906. const filePath = path.join(__dirname, 'fixtures', 'pages', 'b.html')
  907. const fileContent = '<img src="/test.png" />'
  908. let w = null
  909. let success = null
  910. beforeEach(() => {
  911. w = new BrowserWindow({show: false})
  912. success = false
  913. })
  914. afterEach((done) => {
  915. protocol.unregisterProtocol(standardScheme, () => {
  916. closeWindow(w).then(() => {
  917. w = null
  918. done()
  919. })
  920. })
  921. })
  922. it('resolves relative resources', (done) => {
  923. const handler = (request, callback) => {
  924. if (request.url === imageURL) {
  925. success = true
  926. callback()
  927. } else {
  928. callback(filePath)
  929. }
  930. }
  931. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  932. if (error) return done(error)
  933. w.webContents.on('did-finish-load', () => {
  934. assert(success)
  935. done()
  936. })
  937. w.loadURL(origin)
  938. })
  939. })
  940. it('resolves absolute resources', (done) => {
  941. const handler = (request, callback) => {
  942. if (request.url === imageURL) {
  943. success = true
  944. callback()
  945. } else {
  946. callback({
  947. data: fileContent,
  948. mimeType: 'text/html'
  949. })
  950. }
  951. }
  952. protocol.registerStringProtocol(standardScheme, handler, (error) => {
  953. if (error) return done(error)
  954. w.webContents.on('did-finish-load', () => {
  955. assert(success)
  956. done()
  957. })
  958. w.loadURL(origin)
  959. })
  960. })
  961. it('can have fetch working in it', (done) => {
  962. const content = '<html><script>fetch("http://github.com")</script></html>'
  963. const handler = (request, callback) => callback({data: content, mimeType: 'text/html'})
  964. protocol.registerStringProtocol(standardScheme, handler, (error) => {
  965. if (error) return done(error)
  966. w.webContents.on('crashed', () => done('WebContents crashed'))
  967. w.webContents.on('did-finish-load', () => done())
  968. w.loadURL(origin)
  969. })
  970. })
  971. it('can access files through the FileSystem API', (done) => {
  972. let filePath = path.join(__dirname, 'fixtures', 'pages', 'filesystem.html')
  973. const handler = (request, callback) => callback({path: filePath})
  974. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  975. if (error) return done(error)
  976. w.loadURL(origin)
  977. })
  978. ipcMain.once('file-system-error', (event, err) => done(err))
  979. ipcMain.once('file-system-write-end', () => done())
  980. })
  981. it('registers secure, when {secure: true}', (done) => {
  982. let filePath = path.join(__dirname, 'fixtures', 'pages', 'cache-storage.html')
  983. const handler = (request, callback) => callback({path: filePath})
  984. ipcMain.once('success', () => done())
  985. ipcMain.once('failure', (event, err) => done(err))
  986. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  987. if (error) return done(error)
  988. w.loadURL(origin)
  989. })
  990. })
  991. })
  992. })