webview-spec.js 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499
  1. const assert = require('assert')
  2. const path = require('path')
  3. const http = require('http')
  4. const url = require('url')
  5. const {remote} = require('electron')
  6. const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = remote
  7. const {closeWindow} = require('./window-helpers')
  8. const isCI = remote.getGlobal('isCi')
  9. describe('<webview> tag', function () {
  10. this.timeout(3 * 60 * 1000)
  11. var fixtures = path.join(__dirname, 'fixtures')
  12. var webview = null
  13. let w = null
  14. beforeEach(function () {
  15. webview = new WebView()
  16. })
  17. afterEach(function () {
  18. if (!document.body.contains(webview)) {
  19. document.body.appendChild(webview)
  20. }
  21. webview.remove()
  22. return closeWindow(w).then(function () { w = null })
  23. })
  24. it('works without script tag in page', function (done) {
  25. w = new BrowserWindow({show: false})
  26. ipcMain.once('pong', function () {
  27. done()
  28. })
  29. w.loadURL('file://' + fixtures + '/pages/webview-no-script.html')
  30. })
  31. it('is disabled when nodeIntegration is disabled', function (done) {
  32. w = new BrowserWindow({
  33. show: false,
  34. webPreferences: {
  35. nodeIntegration: false,
  36. preload: path.join(fixtures, 'module', 'preload-webview.js')
  37. }
  38. })
  39. ipcMain.once('webview', function (event, type) {
  40. if (type === 'undefined') {
  41. done()
  42. } else {
  43. done('WebView still exists')
  44. }
  45. })
  46. w.loadURL('file://' + fixtures + '/pages/webview-no-script.html')
  47. })
  48. describe('src attribute', function () {
  49. it('specifies the page to load', function (done) {
  50. webview.addEventListener('console-message', function (e) {
  51. assert.equal(e.message, 'a')
  52. done()
  53. })
  54. webview.src = 'file://' + fixtures + '/pages/a.html'
  55. document.body.appendChild(webview)
  56. })
  57. it('navigates to new page when changed', function (done) {
  58. var listener = function () {
  59. webview.src = 'file://' + fixtures + '/pages/b.html'
  60. webview.addEventListener('console-message', function (e) {
  61. assert.equal(e.message, 'b')
  62. done()
  63. })
  64. webview.removeEventListener('did-finish-load', listener)
  65. }
  66. webview.addEventListener('did-finish-load', listener)
  67. webview.src = 'file://' + fixtures + '/pages/a.html'
  68. document.body.appendChild(webview)
  69. })
  70. it('resolves relative URLs', function (done) {
  71. var listener = function (e) {
  72. assert.equal(e.message, 'Window script is loaded before preload script')
  73. webview.removeEventListener('console-message', listener)
  74. done()
  75. }
  76. webview.addEventListener('console-message', listener)
  77. webview.src = '../fixtures/pages/e.html'
  78. document.body.appendChild(webview)
  79. })
  80. it('ignores empty values', function () {
  81. assert.equal(webview.src, '')
  82. webview.src = ''
  83. assert.equal(webview.src, '')
  84. webview.src = null
  85. assert.equal(webview.src, '')
  86. webview.src = undefined
  87. assert.equal(webview.src, '')
  88. })
  89. })
  90. describe('nodeintegration attribute', function () {
  91. it('inserts no node symbols when not set', function (done) {
  92. webview.addEventListener('console-message', function (e) {
  93. assert.equal(e.message, 'undefined undefined undefined undefined')
  94. done()
  95. })
  96. webview.src = 'file://' + fixtures + '/pages/c.html'
  97. document.body.appendChild(webview)
  98. })
  99. it('inserts node symbols when set', function (done) {
  100. webview.addEventListener('console-message', function (e) {
  101. assert.equal(e.message, 'function object object')
  102. done()
  103. })
  104. webview.setAttribute('nodeintegration', 'on')
  105. webview.src = 'file://' + fixtures + '/pages/d.html'
  106. document.body.appendChild(webview)
  107. })
  108. it('loads node symbols after POST navigation when set', function (done) {
  109. webview.addEventListener('console-message', function (e) {
  110. assert.equal(e.message, 'function object object')
  111. done()
  112. })
  113. webview.setAttribute('nodeintegration', 'on')
  114. webview.src = 'file://' + fixtures + '/pages/post.html'
  115. document.body.appendChild(webview)
  116. })
  117. it('disables node integration on child windows when it is disabled on the webview', function (done) {
  118. app.once('browser-window-created', function (event, window) {
  119. assert.equal(window.webContents.getWebPreferences().nodeIntegration, false)
  120. done()
  121. })
  122. webview.setAttribute('allowpopups', 'on')
  123. webview.src = url.format({
  124. pathname: `${fixtures}/pages/webview-opener-no-node-integration.html`,
  125. protocol: 'file',
  126. query: {
  127. p: `${fixtures}/pages/window-opener-node.html`
  128. },
  129. slashes: true
  130. })
  131. document.body.appendChild(webview)
  132. })
  133. if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) {
  134. it('loads native modules when navigation happens', function (done) {
  135. var listener = function () {
  136. webview.removeEventListener('did-finish-load', listener)
  137. var listener2 = function (e) {
  138. assert.equal(e.message, 'function')
  139. done()
  140. }
  141. webview.addEventListener('console-message', listener2)
  142. webview.reload()
  143. }
  144. webview.addEventListener('did-finish-load', listener)
  145. webview.setAttribute('nodeintegration', 'on')
  146. webview.src = 'file://' + fixtures + '/pages/native-module.html'
  147. document.body.appendChild(webview)
  148. })
  149. }
  150. })
  151. describe('preload attribute', function () {
  152. it('loads the script before other scripts in window', function (done) {
  153. var listener = function (e) {
  154. assert.equal(e.message, 'function object object')
  155. webview.removeEventListener('console-message', listener)
  156. done()
  157. }
  158. webview.addEventListener('console-message', listener)
  159. webview.setAttribute('preload', fixtures + '/module/preload.js')
  160. webview.src = 'file://' + fixtures + '/pages/e.html'
  161. document.body.appendChild(webview)
  162. })
  163. it('preload script can still use "process" in required modules when nodeintegration is off', function (done) {
  164. webview.addEventListener('console-message', function (e) {
  165. assert.equal(e.message, 'object undefined object')
  166. done()
  167. })
  168. webview.setAttribute('preload', fixtures + '/module/preload-node-off.js')
  169. webview.src = 'file://' + fixtures + '/api/blank.html'
  170. document.body.appendChild(webview)
  171. })
  172. it('receives ipc message in preload script', function (done) {
  173. var message = 'boom!'
  174. var listener = function (e) {
  175. assert.equal(e.channel, 'pong')
  176. assert.deepEqual(e.args, [message])
  177. webview.removeEventListener('ipc-message', listener)
  178. done()
  179. }
  180. var listener2 = function () {
  181. webview.send('ping', message)
  182. webview.removeEventListener('did-finish-load', listener2)
  183. }
  184. webview.addEventListener('ipc-message', listener)
  185. webview.addEventListener('did-finish-load', listener2)
  186. webview.setAttribute('preload', fixtures + '/module/preload-ipc.js')
  187. webview.src = 'file://' + fixtures + '/pages/e.html'
  188. document.body.appendChild(webview)
  189. })
  190. it('works without script tag in page', function (done) {
  191. var listener = function (e) {
  192. assert.equal(e.message, 'function object object')
  193. webview.removeEventListener('console-message', listener)
  194. done()
  195. }
  196. webview.addEventListener('console-message', listener)
  197. webview.setAttribute('preload', fixtures + '/module/preload.js')
  198. webview.src = 'file://' + fixtures + '/pages/base-page.html'
  199. document.body.appendChild(webview)
  200. })
  201. it('resolves relative URLs', function (done) {
  202. var listener = function (e) {
  203. assert.equal(e.message, 'function object object')
  204. webview.removeEventListener('console-message', listener)
  205. done()
  206. }
  207. webview.addEventListener('console-message', listener)
  208. webview.src = 'file://' + fixtures + '/pages/e.html'
  209. webview.preload = '../fixtures/module/preload.js'
  210. document.body.appendChild(webview)
  211. })
  212. it('ignores empty values', function () {
  213. assert.equal(webview.preload, '')
  214. webview.preload = ''
  215. assert.equal(webview.preload, '')
  216. webview.preload = null
  217. assert.equal(webview.preload, '')
  218. webview.preload = undefined
  219. assert.equal(webview.preload, '')
  220. })
  221. })
  222. describe('httpreferrer attribute', function () {
  223. it('sets the referrer url', function (done) {
  224. var referrer = 'http://github.com/'
  225. var listener = function (e) {
  226. assert.equal(e.message, referrer)
  227. webview.removeEventListener('console-message', listener)
  228. done()
  229. }
  230. webview.addEventListener('console-message', listener)
  231. webview.setAttribute('httpreferrer', referrer)
  232. webview.src = 'file://' + fixtures + '/pages/referrer.html'
  233. document.body.appendChild(webview)
  234. })
  235. })
  236. describe('useragent attribute', function () {
  237. it('sets the user agent', function (done) {
  238. var referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko'
  239. var listener = function (e) {
  240. assert.equal(e.message, referrer)
  241. webview.removeEventListener('console-message', listener)
  242. done()
  243. }
  244. webview.addEventListener('console-message', listener)
  245. webview.setAttribute('useragent', referrer)
  246. webview.src = 'file://' + fixtures + '/pages/useragent.html'
  247. document.body.appendChild(webview)
  248. })
  249. })
  250. describe('disablewebsecurity attribute', function () {
  251. it('does not disable web security when not set', function (done) {
  252. var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js')
  253. var src = `<script src='file://${jqueryPath}'></script> <script>console.log('ok');</script>`
  254. var encoded = btoa(unescape(encodeURIComponent(src)))
  255. var listener = function (e) {
  256. assert(/Not allowed to load local resource/.test(e.message))
  257. webview.removeEventListener('console-message', listener)
  258. done()
  259. }
  260. webview.addEventListener('console-message', listener)
  261. webview.src = 'data:text/html;base64,' + encoded
  262. document.body.appendChild(webview)
  263. })
  264. it('disables web security when set', function (done) {
  265. var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js')
  266. var src = `<script src='file://${jqueryPath}'></script> <script>console.log('ok');</script>`
  267. var encoded = btoa(unescape(encodeURIComponent(src)))
  268. var listener = function (e) {
  269. assert.equal(e.message, 'ok')
  270. webview.removeEventListener('console-message', listener)
  271. done()
  272. }
  273. webview.addEventListener('console-message', listener)
  274. webview.setAttribute('disablewebsecurity', '')
  275. webview.src = 'data:text/html;base64,' + encoded
  276. document.body.appendChild(webview)
  277. })
  278. it('does not break node integration', function (done) {
  279. webview.addEventListener('console-message', function (e) {
  280. assert.equal(e.message, 'function object object')
  281. done()
  282. })
  283. webview.setAttribute('nodeintegration', 'on')
  284. webview.setAttribute('disablewebsecurity', '')
  285. webview.src = 'file://' + fixtures + '/pages/d.html'
  286. document.body.appendChild(webview)
  287. })
  288. it('does not break preload script', function (done) {
  289. var listener = function (e) {
  290. assert.equal(e.message, 'function object object')
  291. webview.removeEventListener('console-message', listener)
  292. done()
  293. }
  294. webview.addEventListener('console-message', listener)
  295. webview.setAttribute('disablewebsecurity', '')
  296. webview.setAttribute('preload', fixtures + '/module/preload.js')
  297. webview.src = 'file://' + fixtures + '/pages/e.html'
  298. document.body.appendChild(webview)
  299. })
  300. })
  301. describe('partition attribute', function () {
  302. it('inserts no node symbols when not set', function (done) {
  303. webview.addEventListener('console-message', function (e) {
  304. assert.equal(e.message, 'undefined undefined undefined undefined')
  305. done()
  306. })
  307. webview.src = 'file://' + fixtures + '/pages/c.html'
  308. webview.partition = 'test1'
  309. document.body.appendChild(webview)
  310. })
  311. it('inserts node symbols when set', function (done) {
  312. webview.addEventListener('console-message', function (e) {
  313. assert.equal(e.message, 'function object object')
  314. done()
  315. })
  316. webview.setAttribute('nodeintegration', 'on')
  317. webview.src = 'file://' + fixtures + '/pages/d.html'
  318. webview.partition = 'test2'
  319. document.body.appendChild(webview)
  320. })
  321. it('isolates storage for different id', function (done) {
  322. var listener = function (e) {
  323. assert.equal(e.message, ' 0')
  324. webview.removeEventListener('console-message', listener)
  325. done()
  326. }
  327. window.localStorage.setItem('test', 'one')
  328. webview.addEventListener('console-message', listener)
  329. webview.src = 'file://' + fixtures + '/pages/partition/one.html'
  330. webview.partition = 'test3'
  331. document.body.appendChild(webview)
  332. })
  333. it('uses current session storage when no id is provided', function (done) {
  334. var listener = function (e) {
  335. assert.equal(e.message, 'one 1')
  336. webview.removeEventListener('console-message', listener)
  337. done()
  338. }
  339. window.localStorage.setItem('test', 'one')
  340. webview.addEventListener('console-message', listener)
  341. webview.src = 'file://' + fixtures + '/pages/partition/one.html'
  342. document.body.appendChild(webview)
  343. })
  344. })
  345. describe('allowpopups attribute', function () {
  346. if (process.env.TRAVIS === 'true' && process.platform === 'darwin') {
  347. return
  348. }
  349. it('can not open new window when not set', function (done) {
  350. var listener = function (e) {
  351. assert.equal(e.message, 'null')
  352. webview.removeEventListener('console-message', listener)
  353. done()
  354. }
  355. webview.addEventListener('console-message', listener)
  356. webview.src = 'file://' + fixtures + '/pages/window-open-hide.html'
  357. document.body.appendChild(webview)
  358. })
  359. it('can open new window when set', function (done) {
  360. var listener = function (e) {
  361. assert.equal(e.message, 'window')
  362. webview.removeEventListener('console-message', listener)
  363. done()
  364. }
  365. webview.addEventListener('console-message', listener)
  366. webview.setAttribute('allowpopups', 'on')
  367. webview.src = 'file://' + fixtures + '/pages/window-open-hide.html'
  368. document.body.appendChild(webview)
  369. })
  370. })
  371. describe('webpreferences attribute', function () {
  372. it('can enable nodeintegration', function (done) {
  373. webview.addEventListener('console-message', function (e) {
  374. assert.equal(e.message, 'function object object')
  375. done()
  376. })
  377. webview.setAttribute('webpreferences', 'nodeIntegration')
  378. webview.src = 'file://' + fixtures + '/pages/d.html'
  379. document.body.appendChild(webview)
  380. })
  381. it('can disables web security and enable nodeintegration', function (done) {
  382. var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js')
  383. var src = `<script src='file://${jqueryPath}'></script> <script>console.log('ok '+(typeof require));</script>`
  384. var encoded = btoa(unescape(encodeURIComponent(src)))
  385. var listener = function (e) {
  386. assert.equal(e.message, 'ok function')
  387. webview.removeEventListener('console-message', listener)
  388. done()
  389. }
  390. webview.addEventListener('console-message', listener)
  391. webview.setAttribute('webpreferences', 'webSecurity=no, nodeIntegration=yes')
  392. webview.src = 'data:text/html;base64,' + encoded
  393. document.body.appendChild(webview)
  394. })
  395. it('can enable context isolation', (done) => {
  396. ipcMain.once('isolated-world', (event, data) => {
  397. assert.deepEqual(data, {
  398. preloadContext: {
  399. preloadProperty: 'number',
  400. pageProperty: 'undefined',
  401. typeofRequire: 'function',
  402. typeofProcess: 'object',
  403. typeofArrayPush: 'function',
  404. typeofFunctionApply: 'function'
  405. },
  406. pageContext: {
  407. preloadProperty: 'undefined',
  408. pageProperty: 'string',
  409. typeofRequire: 'undefined',
  410. typeofProcess: 'undefined',
  411. typeofArrayPush: 'number',
  412. typeofFunctionApply: 'boolean',
  413. typeofPreloadExecuteJavaScriptProperty: 'number',
  414. typeofOpenedWindow: 'object',
  415. documentHidden: isCI,
  416. documentVisibilityState: isCI ? 'hidden' : 'visible'
  417. }
  418. })
  419. done()
  420. })
  421. webview.setAttribute('preload', path.join(fixtures, 'api', 'isolated-preload.js'))
  422. webview.setAttribute('allowpopups', 'yes')
  423. webview.setAttribute('webpreferences', 'contextIsolation=yes')
  424. webview.src = 'file://' + fixtures + '/api/isolated.html'
  425. document.body.appendChild(webview)
  426. })
  427. })
  428. describe('new-window event', function () {
  429. if (process.env.TRAVIS === 'true' && process.platform === 'darwin') {
  430. return
  431. }
  432. it('emits when window.open is called', function (done) {
  433. webview.addEventListener('new-window', function (e) {
  434. assert.equal(e.url, 'http://host/')
  435. assert.equal(e.frameName, 'host')
  436. done()
  437. })
  438. webview.src = 'file://' + fixtures + '/pages/window-open.html'
  439. document.body.appendChild(webview)
  440. })
  441. it('emits when link with target is called', function (done) {
  442. webview.addEventListener('new-window', function (e) {
  443. assert.equal(e.url, 'http://host/')
  444. assert.equal(e.frameName, 'target')
  445. done()
  446. })
  447. webview.src = 'file://' + fixtures + '/pages/target-name.html'
  448. document.body.appendChild(webview)
  449. })
  450. })
  451. describe('ipc-message event', function () {
  452. it('emits when guest sends a ipc message to browser', function (done) {
  453. webview.addEventListener('ipc-message', function (e) {
  454. assert.equal(e.channel, 'channel')
  455. assert.deepEqual(e.args, ['arg1', 'arg2'])
  456. done()
  457. })
  458. webview.src = 'file://' + fixtures + '/pages/ipc-message.html'
  459. webview.setAttribute('nodeintegration', 'on')
  460. document.body.appendChild(webview)
  461. })
  462. })
  463. describe('page-title-set event', function () {
  464. it('emits when title is set', function (done) {
  465. webview.addEventListener('page-title-set', function (e) {
  466. assert.equal(e.title, 'test')
  467. assert(e.explicitSet)
  468. done()
  469. })
  470. webview.src = 'file://' + fixtures + '/pages/a.html'
  471. document.body.appendChild(webview)
  472. })
  473. })
  474. describe('page-favicon-updated event', function () {
  475. it('emits when favicon urls are received', function (done) {
  476. webview.addEventListener('page-favicon-updated', function (e) {
  477. assert.equal(e.favicons.length, 2)
  478. var pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png'
  479. assert.equal(e.favicons[0], pageUrl)
  480. done()
  481. })
  482. webview.src = 'file://' + fixtures + '/pages/a.html'
  483. document.body.appendChild(webview)
  484. })
  485. })
  486. describe('will-navigate event', function () {
  487. it('emits when a url that leads to oustide of the page is clicked', function (done) {
  488. webview.addEventListener('will-navigate', function (e) {
  489. assert.equal(e.url, 'http://host/')
  490. done()
  491. })
  492. webview.src = 'file://' + fixtures + '/pages/webview-will-navigate.html'
  493. document.body.appendChild(webview)
  494. })
  495. })
  496. describe('did-navigate event', function () {
  497. var p = path.join(fixtures, 'pages', 'webview-will-navigate.html')
  498. p = p.replace(/\\/g, '/')
  499. var pageUrl = url.format({
  500. protocol: 'file',
  501. slashes: true,
  502. pathname: p
  503. })
  504. it('emits when a url that leads to outside of the page is clicked', function (done) {
  505. webview.addEventListener('did-navigate', function (e) {
  506. assert.equal(e.url, pageUrl)
  507. done()
  508. })
  509. webview.src = pageUrl
  510. document.body.appendChild(webview)
  511. })
  512. })
  513. describe('did-navigate-in-page event', function () {
  514. it('emits when an anchor link is clicked', function (done) {
  515. var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html')
  516. p = p.replace(/\\/g, '/')
  517. var pageUrl = url.format({
  518. protocol: 'file',
  519. slashes: true,
  520. pathname: p
  521. })
  522. webview.addEventListener('did-navigate-in-page', function (e) {
  523. assert.equal(e.url, pageUrl + '#test_content')
  524. done()
  525. })
  526. webview.src = pageUrl
  527. document.body.appendChild(webview)
  528. })
  529. it('emits when window.history.replaceState is called', function (done) {
  530. webview.addEventListener('did-navigate-in-page', function (e) {
  531. assert.equal(e.url, 'http://host/')
  532. done()
  533. })
  534. webview.src = 'file://' + fixtures + '/pages/webview-did-navigate-in-page-with-history.html'
  535. document.body.appendChild(webview)
  536. })
  537. it('emits when window.location.hash is changed', function (done) {
  538. var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html')
  539. p = p.replace(/\\/g, '/')
  540. var pageUrl = url.format({
  541. protocol: 'file',
  542. slashes: true,
  543. pathname: p
  544. })
  545. webview.addEventListener('did-navigate-in-page', function (e) {
  546. assert.equal(e.url, pageUrl + '#test')
  547. done()
  548. })
  549. webview.src = pageUrl
  550. document.body.appendChild(webview)
  551. })
  552. })
  553. describe('close event', function () {
  554. it('should fire when interior page calls window.close', function (done) {
  555. webview.addEventListener('close', function () {
  556. done()
  557. })
  558. webview.src = 'file://' + fixtures + '/pages/close.html'
  559. document.body.appendChild(webview)
  560. })
  561. })
  562. describe('devtools-opened event', function () {
  563. it('should fire when webview.openDevTools() is called', function (done) {
  564. var listener = function () {
  565. webview.removeEventListener('devtools-opened', listener)
  566. webview.closeDevTools()
  567. done()
  568. }
  569. webview.addEventListener('devtools-opened', listener)
  570. webview.addEventListener('dom-ready', function () {
  571. webview.openDevTools()
  572. })
  573. webview.src = 'file://' + fixtures + '/pages/base-page.html'
  574. document.body.appendChild(webview)
  575. })
  576. })
  577. describe('devtools-closed event', function () {
  578. it('should fire when webview.closeDevTools() is called', function (done) {
  579. var listener2 = function () {
  580. webview.removeEventListener('devtools-closed', listener2)
  581. done()
  582. }
  583. var listener = function () {
  584. webview.removeEventListener('devtools-opened', listener)
  585. webview.closeDevTools()
  586. }
  587. webview.addEventListener('devtools-opened', listener)
  588. webview.addEventListener('devtools-closed', listener2)
  589. webview.addEventListener('dom-ready', function () {
  590. webview.openDevTools()
  591. })
  592. webview.src = 'file://' + fixtures + '/pages/base-page.html'
  593. document.body.appendChild(webview)
  594. })
  595. })
  596. describe('devtools-focused event', function () {
  597. it('should fire when webview.openDevTools() is called', function (done) {
  598. var listener = function () {
  599. webview.removeEventListener('devtools-focused', listener)
  600. webview.closeDevTools()
  601. done()
  602. }
  603. webview.addEventListener('devtools-focused', listener)
  604. webview.addEventListener('dom-ready', function () {
  605. webview.openDevTools()
  606. })
  607. webview.src = 'file://' + fixtures + '/pages/base-page.html'
  608. document.body.appendChild(webview)
  609. })
  610. })
  611. describe('<webview>.reload()', function () {
  612. it('should emit beforeunload handler', function (done) {
  613. var listener = function (e) {
  614. assert.equal(e.channel, 'onbeforeunload')
  615. webview.removeEventListener('ipc-message', listener)
  616. done()
  617. }
  618. var listener2 = function () {
  619. webview.reload()
  620. webview.removeEventListener('did-finish-load', listener2)
  621. }
  622. webview.addEventListener('ipc-message', listener)
  623. webview.addEventListener('did-finish-load', listener2)
  624. webview.setAttribute('nodeintegration', 'on')
  625. webview.src = 'file://' + fixtures + '/pages/beforeunload-false.html'
  626. document.body.appendChild(webview)
  627. })
  628. })
  629. describe('<webview>.goForward()', function () {
  630. it('should work after a replaced history entry', function (done) {
  631. var loadCount = 1
  632. var listener = function (e) {
  633. if (loadCount === 1) {
  634. assert.equal(e.channel, 'history')
  635. assert.equal(e.args[0], 1)
  636. assert(!webview.canGoBack())
  637. assert(!webview.canGoForward())
  638. } else if (loadCount === 2) {
  639. assert.equal(e.channel, 'history')
  640. assert.equal(e.args[0], 2)
  641. assert(!webview.canGoBack())
  642. assert(webview.canGoForward())
  643. webview.removeEventListener('ipc-message', listener)
  644. }
  645. }
  646. var loadListener = function (e) {
  647. if (loadCount === 1) {
  648. webview.src = 'file://' + fixtures + '/pages/base-page.html'
  649. } else if (loadCount === 2) {
  650. assert(webview.canGoBack())
  651. assert(!webview.canGoForward())
  652. webview.goBack()
  653. } else if (loadCount === 3) {
  654. webview.goForward()
  655. } else if (loadCount === 4) {
  656. assert(webview.canGoBack())
  657. assert(!webview.canGoForward())
  658. webview.removeEventListener('did-finish-load', loadListener)
  659. done()
  660. }
  661. loadCount++
  662. }
  663. webview.addEventListener('ipc-message', listener)
  664. webview.addEventListener('did-finish-load', loadListener)
  665. webview.setAttribute('nodeintegration', 'on')
  666. webview.src = 'file://' + fixtures + '/pages/history-replace.html'
  667. document.body.appendChild(webview)
  668. })
  669. })
  670. describe('<webview>.clearHistory()', function () {
  671. it('should clear the navigation history', function (done) {
  672. var listener = function (e) {
  673. assert.equal(e.channel, 'history')
  674. assert.equal(e.args[0], 2)
  675. assert(webview.canGoBack())
  676. webview.clearHistory()
  677. assert(!webview.canGoBack())
  678. webview.removeEventListener('ipc-message', listener)
  679. done()
  680. }
  681. webview.addEventListener('ipc-message', listener)
  682. webview.setAttribute('nodeintegration', 'on')
  683. webview.src = 'file://' + fixtures + '/pages/history.html'
  684. document.body.appendChild(webview)
  685. })
  686. })
  687. describe('basic auth', function () {
  688. var auth = require('basic-auth')
  689. it('should authenticate with correct credentials', function (done) {
  690. var message = 'Authenticated'
  691. var server = http.createServer(function (req, res) {
  692. var credentials = auth(req)
  693. if (credentials.name === 'test' && credentials.pass === 'test') {
  694. res.end(message)
  695. } else {
  696. res.end('failed')
  697. }
  698. server.close()
  699. })
  700. server.listen(0, '127.0.0.1', function () {
  701. var port = server.address().port
  702. webview.addEventListener('ipc-message', function (e) {
  703. assert.equal(e.channel, message)
  704. done()
  705. })
  706. webview.src = 'file://' + fixtures + '/pages/basic-auth.html?port=' + port
  707. webview.setAttribute('nodeintegration', 'on')
  708. document.body.appendChild(webview)
  709. })
  710. })
  711. })
  712. describe('dom-ready event', function () {
  713. it('emits when document is loaded', function (done) {
  714. var server = http.createServer(function () {})
  715. server.listen(0, '127.0.0.1', function () {
  716. var port = server.address().port
  717. webview.addEventListener('dom-ready', function () {
  718. done()
  719. })
  720. webview.src = 'file://' + fixtures + '/pages/dom-ready.html?port=' + port
  721. document.body.appendChild(webview)
  722. })
  723. })
  724. it('throws a custom error when an API method is called before the event is emitted', function () {
  725. assert.throws(function () {
  726. webview.stop()
  727. }, 'Cannot call stop because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.')
  728. })
  729. })
  730. describe('executeJavaScript', function () {
  731. it('should support user gesture', function (done) {
  732. if (process.env.TRAVIS !== 'true' || process.platform === 'darwin') return done()
  733. var listener = function () {
  734. webview.removeEventListener('enter-html-full-screen', listener)
  735. done()
  736. }
  737. var listener2 = function () {
  738. var jsScript = "document.querySelector('video').webkitRequestFullscreen()"
  739. webview.executeJavaScript(jsScript, true)
  740. webview.removeEventListener('did-finish-load', listener2)
  741. }
  742. webview.addEventListener('enter-html-full-screen', listener)
  743. webview.addEventListener('did-finish-load', listener2)
  744. webview.src = 'file://' + fixtures + '/pages/fullscreen.html'
  745. document.body.appendChild(webview)
  746. })
  747. it('can return the result of the executed script', function (done) {
  748. if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return done()
  749. var listener = function () {
  750. var jsScript = "'4'+2"
  751. webview.executeJavaScript(jsScript, false, function (result) {
  752. assert.equal(result, '42')
  753. done()
  754. })
  755. webview.removeEventListener('did-finish-load', listener)
  756. }
  757. webview.addEventListener('did-finish-load', listener)
  758. webview.src = 'about:blank'
  759. document.body.appendChild(webview)
  760. })
  761. })
  762. describe('sendInputEvent', function () {
  763. it('can send keyboard event', function (done) {
  764. webview.addEventListener('ipc-message', function (e) {
  765. assert.equal(e.channel, 'keyup')
  766. assert.deepEqual(e.args, ['C', 'KeyC', 67, true, false])
  767. done()
  768. })
  769. webview.addEventListener('dom-ready', function () {
  770. webview.sendInputEvent({
  771. type: 'keyup',
  772. keyCode: 'c',
  773. modifiers: ['shift']
  774. })
  775. })
  776. webview.src = 'file://' + fixtures + '/pages/onkeyup.html'
  777. webview.setAttribute('nodeintegration', 'on')
  778. document.body.appendChild(webview)
  779. })
  780. it('can send mouse event', function (done) {
  781. webview.addEventListener('ipc-message', function (e) {
  782. assert.equal(e.channel, 'mouseup')
  783. assert.deepEqual(e.args, [10, 20, false, true])
  784. done()
  785. })
  786. webview.addEventListener('dom-ready', function () {
  787. webview.sendInputEvent({
  788. type: 'mouseup',
  789. modifiers: ['ctrl'],
  790. x: 10,
  791. y: 20
  792. })
  793. })
  794. webview.src = 'file://' + fixtures + '/pages/onmouseup.html'
  795. webview.setAttribute('nodeintegration', 'on')
  796. document.body.appendChild(webview)
  797. })
  798. })
  799. describe('media-started-playing media-paused events', function () {
  800. it('emits when audio starts and stops playing', function (done) {
  801. var audioPlayed = false
  802. webview.addEventListener('media-started-playing', function () {
  803. audioPlayed = true
  804. })
  805. webview.addEventListener('media-paused', function () {
  806. assert(audioPlayed)
  807. done()
  808. })
  809. webview.src = 'file://' + fixtures + '/pages/audio.html'
  810. document.body.appendChild(webview)
  811. })
  812. })
  813. describe('found-in-page event', function () {
  814. it('emits when a request is made', function (done) {
  815. let requestId = null
  816. let activeMatchOrdinal = []
  817. const listener = function (e) {
  818. assert.equal(e.result.requestId, requestId)
  819. assert.equal(e.result.matches, 3)
  820. activeMatchOrdinal.push(e.result.activeMatchOrdinal)
  821. if (e.result.activeMatchOrdinal === e.result.matches) {
  822. assert.deepEqual(activeMatchOrdinal, [1, 2, 3])
  823. webview.stopFindInPage('clearSelection')
  824. done()
  825. } else {
  826. listener2()
  827. }
  828. }
  829. const listener2 = function () {
  830. requestId = webview.findInPage('virtual')
  831. }
  832. webview.addEventListener('found-in-page', listener)
  833. webview.addEventListener('did-finish-load', listener2)
  834. webview.src = 'file://' + fixtures + '/pages/content.html'
  835. document.body.appendChild(webview)
  836. })
  837. })
  838. xdescribe('did-change-theme-color event', function () {
  839. it('emits when theme color changes', function (done) {
  840. webview.addEventListener('did-change-theme-color', function () {
  841. done()
  842. })
  843. webview.src = 'file://' + fixtures + '/pages/theme-color.html'
  844. document.body.appendChild(webview)
  845. })
  846. })
  847. describe('permission-request event', function () {
  848. function setUpRequestHandler (webview, requestedPermission, completed) {
  849. var listener = function (webContents, permission, callback) {
  850. if (webContents.getId() === webview.getId()) {
  851. assert.equal(permission, requestedPermission)
  852. callback(false)
  853. if (completed) completed()
  854. }
  855. }
  856. session.fromPartition(webview.partition).setPermissionRequestHandler(listener)
  857. }
  858. it('emits when using navigator.getUserMedia api', function (done) {
  859. webview.addEventListener('ipc-message', function (e) {
  860. assert.equal(e.channel, 'message')
  861. assert.deepEqual(e.args, ['PermissionDeniedError'])
  862. done()
  863. })
  864. webview.src = 'file://' + fixtures + '/pages/permissions/media.html'
  865. webview.partition = 'permissionTest'
  866. webview.setAttribute('nodeintegration', 'on')
  867. setUpRequestHandler(webview, 'media')
  868. document.body.appendChild(webview)
  869. })
  870. it('emits when using navigator.geolocation api', function (done) {
  871. webview.addEventListener('ipc-message', function (e) {
  872. assert.equal(e.channel, 'message')
  873. assert.deepEqual(e.args, ['User denied Geolocation'])
  874. done()
  875. })
  876. webview.src = 'file://' + fixtures + '/pages/permissions/geolocation.html'
  877. webview.partition = 'permissionTest'
  878. webview.setAttribute('nodeintegration', 'on')
  879. setUpRequestHandler(webview, 'geolocation')
  880. document.body.appendChild(webview)
  881. })
  882. it('emits when using navigator.requestMIDIAccess api', function (done) {
  883. webview.addEventListener('ipc-message', function (e) {
  884. assert.equal(e.channel, 'message')
  885. assert.deepEqual(e.args, ['SecurityError'])
  886. done()
  887. })
  888. webview.src = 'file://' + fixtures + '/pages/permissions/midi.html'
  889. webview.partition = 'permissionTest'
  890. webview.setAttribute('nodeintegration', 'on')
  891. setUpRequestHandler(webview, 'midiSysex')
  892. document.body.appendChild(webview)
  893. })
  894. it('emits when accessing external protocol', function (done) {
  895. webview.src = 'magnet:test'
  896. webview.partition = 'permissionTest'
  897. setUpRequestHandler(webview, 'openExternal', done)
  898. document.body.appendChild(webview)
  899. })
  900. it('emits when using Notification.requestPermission', function (done) {
  901. webview.addEventListener('ipc-message', function (e) {
  902. assert.equal(e.channel, 'message')
  903. assert.deepEqual(e.args, ['granted'])
  904. done()
  905. })
  906. webview.src = 'file://' + fixtures + '/pages/permissions/notification.html'
  907. webview.partition = 'permissionTest'
  908. webview.setAttribute('nodeintegration', 'on')
  909. session.fromPartition(webview.partition).setPermissionRequestHandler(function (webContents, permission, callback) {
  910. if (webContents.getId() === webview.getId()) {
  911. assert.equal(permission, 'notifications')
  912. setTimeout(function () {
  913. callback(true)
  914. }, 10)
  915. }
  916. })
  917. document.body.appendChild(webview)
  918. })
  919. })
  920. describe('<webview>.getWebContents', function () {
  921. it('can return the webcontents associated', function (done) {
  922. webview.addEventListener('did-finish-load', function () {
  923. const webviewContents = webview.getWebContents()
  924. assert(webviewContents)
  925. assert.equal(webviewContents.getURL(), 'about:blank')
  926. done()
  927. })
  928. webview.src = 'about:blank'
  929. document.body.appendChild(webview)
  930. })
  931. })
  932. describe('did-get-response-details event', function () {
  933. it('emits for the page and its resources', function (done) {
  934. // expected {fileName: resourceType} pairs
  935. var expectedResources = {
  936. 'did-get-response-details.html': 'mainFrame',
  937. 'logo.png': 'image'
  938. }
  939. var responses = 0
  940. webview.addEventListener('did-get-response-details', function (event) {
  941. responses++
  942. var fileName = event.newURL.slice(event.newURL.lastIndexOf('/') + 1)
  943. var expectedType = expectedResources[fileName]
  944. assert(!!expectedType, `Unexpected response details for ${event.newURL}`)
  945. assert(typeof event.status === 'boolean', 'status should be boolean')
  946. assert.equal(event.httpResponseCode, 200)
  947. assert.equal(event.requestMethod, 'GET')
  948. assert(typeof event.referrer === 'string', 'referrer should be string')
  949. assert(!!event.headers, 'headers should be present')
  950. assert(typeof event.headers === 'object', 'headers should be object')
  951. assert.equal(event.resourceType, expectedType, 'Incorrect resourceType')
  952. if (responses === Object.keys(expectedResources).length) {
  953. done()
  954. }
  955. })
  956. webview.src = 'file://' + path.join(fixtures, 'pages', 'did-get-response-details.html')
  957. document.body.appendChild(webview)
  958. })
  959. })
  960. it('inherits the zoomFactor of the parent window', function (done) {
  961. w = new BrowserWindow({
  962. show: false,
  963. webPreferences: {
  964. zoomFactor: 1.2
  965. }
  966. })
  967. ipcMain.once('pong', function (event, zoomFactor, zoomLevel) {
  968. assert.equal(zoomFactor, 1.2)
  969. assert.equal(zoomLevel, 1)
  970. done()
  971. })
  972. w.loadURL('file://' + fixtures + '/pages/webview-zoom-factor.html')
  973. })
  974. it('inherits the parent window visibility state and receives visibilitychange events', function (done) {
  975. w = new BrowserWindow({
  976. show: false
  977. })
  978. ipcMain.once('pong', function (event, visibilityState, hidden) {
  979. assert.equal(visibilityState, 'hidden')
  980. assert.equal(hidden, true)
  981. w.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', 'visible')
  982. ipcMain.once('pong', function (event, visibilityState, hidden) {
  983. assert.equal(visibilityState, 'visible')
  984. assert.equal(hidden, false)
  985. done()
  986. })
  987. })
  988. w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html')
  989. })
  990. it('loads devtools extensions registered on the parent window', function (done) {
  991. w = new BrowserWindow({
  992. show: false
  993. })
  994. BrowserWindow.removeDevToolsExtension('foo')
  995. var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo')
  996. BrowserWindow.addDevToolsExtension(extensionPath)
  997. w.loadURL('file://' + fixtures + '/pages/webview-devtools.html')
  998. ipcMain.once('answer', function (event, message) {
  999. assert.equal(message.runtimeId, 'foo')
  1000. assert.notEqual(message.tabId, w.webContents.id)
  1001. done()
  1002. })
  1003. })
  1004. describe('guestinstance attribute', function () {
  1005. it('before loading there is no attribute', function () {
  1006. document.body.appendChild(webview)
  1007. assert(!webview.hasAttribute('guestinstance'))
  1008. })
  1009. it('loading a page sets the guest view', function (done) {
  1010. var loadListener = function () {
  1011. webview.removeEventListener('did-finish-load', loadListener, false)
  1012. var instance = webview.getAttribute('guestinstance')
  1013. assert.equal(instance, parseInt(instance))
  1014. var guest = getGuestWebContents(parseInt(instance))
  1015. assert.equal(guest, webview.getWebContents())
  1016. done()
  1017. }
  1018. webview.addEventListener('did-finish-load', loadListener, false)
  1019. webview.src = 'file://' + fixtures + '/api/blank.html'
  1020. document.body.appendChild(webview)
  1021. })
  1022. it('deleting the attribute destroys the webview', function (done) {
  1023. var loadListener = function () {
  1024. webview.removeEventListener('did-finish-load', loadListener, false)
  1025. var destroyListener = function () {
  1026. webview.removeEventListener('destroyed', destroyListener, false)
  1027. assert.equal(getGuestWebContents(instance), null)
  1028. done()
  1029. }
  1030. webview.addEventListener('destroyed', destroyListener, false)
  1031. var instance = parseInt(webview.getAttribute('guestinstance'))
  1032. webview.removeAttribute('guestinstance')
  1033. }
  1034. webview.addEventListener('did-finish-load', loadListener, false)
  1035. webview.src = 'file://' + fixtures + '/api/blank.html'
  1036. document.body.appendChild(webview)
  1037. })
  1038. it('setting the attribute on a new webview moves the contents', function (done) {
  1039. var loadListener = function () {
  1040. webview.removeEventListener('did-finish-load', loadListener, false)
  1041. var webContents = webview.getWebContents()
  1042. var instance = webview.getAttribute('guestinstance')
  1043. var destroyListener = function () {
  1044. webview.removeEventListener('destroyed', destroyListener, false)
  1045. assert.equal(webContents, webview2.getWebContents())
  1046. // Make sure that events are hooked up to the right webview now
  1047. webview2.addEventListener('console-message', function (e) {
  1048. assert.equal(e.message, 'a')
  1049. document.body.removeChild(webview2)
  1050. done()
  1051. })
  1052. webview2.src = 'file://' + fixtures + '/pages/a.html'
  1053. }
  1054. webview.addEventListener('destroyed', destroyListener, false)
  1055. var webview2 = new WebView()
  1056. webview2.setAttribute('guestinstance', instance)
  1057. document.body.appendChild(webview2)
  1058. }
  1059. webview.addEventListener('did-finish-load', loadListener, false)
  1060. webview.src = 'file://' + fixtures + '/api/blank.html'
  1061. document.body.appendChild(webview)
  1062. })
  1063. it('setting the attribute to an invalid guestinstance does nothing', function (done) {
  1064. var loadListener = function () {
  1065. webview.removeEventListener('did-finish-load', loadListener, false)
  1066. webview.setAttribute('guestinstance', 55)
  1067. // Make sure that events are still hooked up to the webview
  1068. webview.addEventListener('console-message', function (e) {
  1069. assert.equal(e.message, 'a')
  1070. done()
  1071. })
  1072. webview.src = 'file://' + fixtures + '/pages/a.html'
  1073. }
  1074. webview.addEventListener('did-finish-load', loadListener, false)
  1075. webview.src = 'file://' + fixtures + '/api/blank.html'
  1076. document.body.appendChild(webview)
  1077. })
  1078. it('setting the attribute on an existing webview moves the contents', function (done) {
  1079. var load1Listener = function () {
  1080. webview.removeEventListener('did-finish-load', load1Listener, false)
  1081. var webContents = webview.getWebContents()
  1082. var instance = webview.getAttribute('guestinstance')
  1083. var destroyedInstance
  1084. var destroyListener = function () {
  1085. webview.removeEventListener('destroyed', destroyListener, false)
  1086. assert.equal(webContents, webview2.getWebContents())
  1087. assert.equal(null, getGuestWebContents(parseInt(destroyedInstance)))
  1088. // Make sure that events are hooked up to the right webview now
  1089. webview2.addEventListener('console-message', function (e) {
  1090. assert.equal(e.message, 'a')
  1091. document.body.removeChild(webview2)
  1092. done()
  1093. })
  1094. webview2.src = 'file://' + fixtures + '/pages/a.html'
  1095. }
  1096. webview.addEventListener('destroyed', destroyListener, false)
  1097. var webview2 = new WebView()
  1098. var load2Listener = function () {
  1099. webview2.removeEventListener('did-finish-load', load2Listener, false)
  1100. destroyedInstance = webview2.getAttribute('guestinstance')
  1101. assert.notEqual(instance, destroyedInstance)
  1102. webview2.setAttribute('guestinstance', instance)
  1103. }
  1104. webview2.addEventListener('did-finish-load', load2Listener, false)
  1105. webview2.src = 'file://' + fixtures + '/api/blank.html'
  1106. document.body.appendChild(webview2)
  1107. }
  1108. webview.addEventListener('did-finish-load', load1Listener, false)
  1109. webview.src = 'file://' + fixtures + '/api/blank.html'
  1110. document.body.appendChild(webview)
  1111. })
  1112. it('moving a guest back to its original webview should work', function (done) {
  1113. var loadListener = function () {
  1114. webview.removeEventListener('did-finish-load', loadListener, false)
  1115. var webContents = webview.getWebContents()
  1116. var instance = webview.getAttribute('guestinstance')
  1117. var destroy1Listener = function () {
  1118. webview.removeEventListener('destroyed', destroy1Listener, false)
  1119. assert.equal(webContents, webview2.getWebContents())
  1120. assert.equal(null, webview.getWebContents())
  1121. var destroy2Listener = function () {
  1122. webview2.removeEventListener('destroyed', destroy2Listener, false)
  1123. assert.equal(webContents, webview.getWebContents())
  1124. assert.equal(null, webview2.getWebContents())
  1125. // Make sure that events are hooked up to the right webview now
  1126. webview.addEventListener('console-message', function (e) {
  1127. assert.equal(e.message, 'a')
  1128. document.body.removeChild(webview2)
  1129. done()
  1130. })
  1131. webview.src = 'file://' + fixtures + '/pages/a.html'
  1132. }
  1133. webview2.addEventListener('destroyed', destroy2Listener, false)
  1134. webview.setAttribute('guestinstance', instance)
  1135. }
  1136. webview.addEventListener('destroyed', destroy1Listener, false)
  1137. var webview2 = new WebView()
  1138. webview2.setAttribute('guestinstance', instance)
  1139. document.body.appendChild(webview2)
  1140. }
  1141. webview.addEventListener('did-finish-load', loadListener, false)
  1142. webview.src = 'file://' + fixtures + '/api/blank.html'
  1143. document.body.appendChild(webview)
  1144. })
  1145. it('setting the attribute on a webview in a different window moves the contents', function (done) {
  1146. var loadListener = function () {
  1147. webview.removeEventListener('did-finish-load', loadListener, false)
  1148. var instance = webview.getAttribute('guestinstance')
  1149. w = new BrowserWindow({ show: false })
  1150. w.webContents.once('did-finish-load', function () {
  1151. ipcMain.once('pong', function () {
  1152. assert(!webview.hasAttribute('guestinstance'))
  1153. done()
  1154. })
  1155. w.webContents.send('guestinstance', instance)
  1156. })
  1157. w.loadURL('file://' + fixtures + '/pages/webview-move-to-window.html')
  1158. }
  1159. webview.addEventListener('did-finish-load', loadListener, false)
  1160. webview.src = 'file://' + fixtures + '/api/blank.html'
  1161. document.body.appendChild(webview)
  1162. })
  1163. it('does not delete the guestinstance attribute when moving the webview to another parent node', function (done) {
  1164. webview.addEventListener('dom-ready', function domReadyListener () {
  1165. webview.addEventListener('did-attach', function () {
  1166. assert(webview.guestinstance != null)
  1167. assert(webview.getWebContents() != null)
  1168. done()
  1169. })
  1170. document.body.replaceChild(webview, div)
  1171. })
  1172. webview.src = 'file://' + fixtures + '/pages/a.html'
  1173. const div = document.createElement('div')
  1174. div.appendChild(webview)
  1175. document.body.appendChild(div)
  1176. })
  1177. it('does not destroy the webContents when hiding/showing the webview (regression)', function (done) {
  1178. webview.addEventListener('dom-ready', function domReadyListener () {
  1179. const instance = webview.getAttribute('guestinstance')
  1180. assert(instance != null)
  1181. // Wait for event directly since attach happens asynchronously over IPC
  1182. ipcMain.once('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function () {
  1183. assert(webview.getWebContents() != null)
  1184. assert.equal(instance, webview.getAttribute('guestinstance'))
  1185. done()
  1186. })
  1187. webview.style.display = 'none'
  1188. webview.offsetHeight
  1189. webview.style.display = 'block'
  1190. })
  1191. webview.src = 'file://' + fixtures + '/pages/a.html'
  1192. document.body.appendChild(webview)
  1193. })
  1194. it('does not reload the webContents when hiding/showing the webview (regression)', function (done) {
  1195. webview.addEventListener('dom-ready', function domReadyListener () {
  1196. webview.addEventListener('did-start-loading', function () {
  1197. done(new Error('webview started loading unexpectedly'))
  1198. })
  1199. // Wait for event directly since attach happens asynchronously over IPC
  1200. webview.addEventListener('did-attach', function () {
  1201. done()
  1202. })
  1203. webview.style.display = 'none'
  1204. webview.offsetHeight
  1205. webview.style.display = 'block'
  1206. })
  1207. webview.src = 'file://' + fixtures + '/pages/a.html'
  1208. document.body.appendChild(webview)
  1209. })
  1210. })
  1211. describe('DOM events', function () {
  1212. let div
  1213. beforeEach(function () {
  1214. div = document.createElement('div')
  1215. div.style.width = '100px'
  1216. div.style.height = '10px'
  1217. div.style.overflow = 'hidden'
  1218. webview.style.height = '100%'
  1219. webview.style.width = '100%'
  1220. })
  1221. afterEach(function () {
  1222. if (div != null) div.remove()
  1223. })
  1224. it('emits resize events', function (done) {
  1225. webview.addEventListener('dom-ready', function () {
  1226. div.style.width = '1234px'
  1227. div.style.height = '789px'
  1228. })
  1229. webview.addEventListener('resize', function onResize (event) {
  1230. webview.removeEventListener('resize', onResize)
  1231. assert.equal(event.newWidth, 1234)
  1232. assert.equal(event.newHeight, 789)
  1233. assert.equal(event.target, webview)
  1234. done()
  1235. })
  1236. webview.src = `file://${fixtures}/pages/a.html`
  1237. div.appendChild(webview)
  1238. document.body.appendChild(div)
  1239. })
  1240. })
  1241. describe('disableguestresize attribute', () => {
  1242. it('does not have attribute by default', () => {
  1243. document.body.appendChild(webview)
  1244. assert(!webview.hasAttribute('disableguestresize'))
  1245. })
  1246. it('resizes guest when attribute is not present', done => {
  1247. w = new BrowserWindow({show: false, width: 200, height: 200})
  1248. w.loadURL('file://' + fixtures + '/pages/webview-guest-resize.html')
  1249. w.webContents.once('did-finish-load', () => {
  1250. const CONTENT_SIZE = 300
  1251. const elementResizePromise = new Promise(resolve => {
  1252. ipcMain.once('webview-element-resize', (event, width, height) => {
  1253. assert.equal(width, CONTENT_SIZE)
  1254. assert.equal(height, CONTENT_SIZE)
  1255. resolve()
  1256. })
  1257. })
  1258. const guestResizePromise = new Promise(resolve => {
  1259. ipcMain.once('webview-guest-resize', (event, width, height) => {
  1260. assert.equal(width, CONTENT_SIZE)
  1261. assert.equal(height, CONTENT_SIZE)
  1262. resolve()
  1263. })
  1264. })
  1265. Promise.all([elementResizePromise, guestResizePromise]).then(() => done())
  1266. w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
  1267. })
  1268. })
  1269. it('does not resize guest when attribute is present', done => {
  1270. w = new BrowserWindow({show: false, width: 200, height: 200})
  1271. w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
  1272. w.webContents.once('did-finish-load', () => {
  1273. const CONTENT_SIZE = 300
  1274. const elementResizePromise = new Promise(resolve => {
  1275. ipcMain.once('webview-element-resize', (event, width, height) => {
  1276. assert.equal(width, CONTENT_SIZE)
  1277. assert.equal(height, CONTENT_SIZE)
  1278. resolve()
  1279. })
  1280. })
  1281. const noGuestResizePromise = new Promise(resolve => {
  1282. const onGuestResize = (event, width, height) => {
  1283. done(new Error('Unexpected guest resize message'))
  1284. }
  1285. ipcMain.once('webview-guest-resize', onGuestResize)
  1286. setTimeout(() => {
  1287. ipcMain.removeListener('webview-guest-resize', onGuestResize)
  1288. resolve()
  1289. }, 500)
  1290. })
  1291. Promise.all([elementResizePromise, noGuestResizePromise]).then(() => done())
  1292. w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
  1293. })
  1294. })
  1295. it('dispatches element resize event even when attribute is present', done => {
  1296. w = new BrowserWindow({show: false, width: 200, height: 200})
  1297. w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
  1298. w.webContents.once('did-finish-load', () => {
  1299. const CONTENT_SIZE = 300
  1300. ipcMain.once('webview-element-resize', (event, width, height) => {
  1301. assert.equal(width, CONTENT_SIZE)
  1302. done()
  1303. })
  1304. w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
  1305. })
  1306. })
  1307. it('can be manually resized with setSize even when attribute is present', done => {
  1308. if (process.env.TRAVIS === 'true') return done()
  1309. w = new BrowserWindow({show: false, width: 200, height: 200})
  1310. w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
  1311. w.webContents.once('did-finish-load', () => {
  1312. const GUEST_WIDTH = 10
  1313. const GUEST_HEIGHT = 20
  1314. ipcMain.once('webview-guest-resize', (event, width, height) => {
  1315. assert.equal(width, GUEST_WIDTH)
  1316. assert.equal(height, GUEST_HEIGHT)
  1317. done()
  1318. })
  1319. for (const wc of webContents.getAllWebContents()) {
  1320. if (wc.hostWebContents &&
  1321. wc.hostWebContents.id === w.webContents.id) {
  1322. wc.setSize({
  1323. normal: {
  1324. width: GUEST_WIDTH,
  1325. height: GUEST_HEIGHT
  1326. }
  1327. })
  1328. }
  1329. }
  1330. })
  1331. })
  1332. })
  1333. })