api-protocol-spec.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  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. })