api-web-contents-view-spec.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { BaseWindow, View, WebContentsView } from 'electron/main';
  2. import { expect } from 'chai';
  3. import { once } from 'node:events';
  4. import { closeAllWindows } from './lib/window-helpers';
  5. describe('WebContentsView', () => {
  6. afterEach(closeAllWindows);
  7. it('can be instantiated with no arguments', () => {
  8. // eslint-disable-next-line no-new
  9. new WebContentsView();
  10. });
  11. it('can be instantiated with no webPreferences', () => {
  12. // eslint-disable-next-line no-new
  13. new WebContentsView({});
  14. });
  15. it('can be used as content view', () => {
  16. const w = new BaseWindow({ show: false });
  17. w.setContentView(new WebContentsView());
  18. });
  19. it('can be removed after a close', async () => {
  20. const w = new BaseWindow({ show: false });
  21. const v = new View();
  22. const wcv = new WebContentsView();
  23. w.setContentView(v);
  24. v.addChildView(wcv);
  25. await wcv.webContents.loadURL('about:blank');
  26. const destroyed = once(wcv.webContents, 'destroyed');
  27. wcv.webContents.executeJavaScript('window.close()');
  28. await destroyed;
  29. expect(wcv.webContents.isDestroyed()).to.be.true();
  30. v.removeChildView(wcv);
  31. });
  32. it('correctly reorders children', () => {
  33. const w = new BaseWindow({ show: false });
  34. const cv = new View();
  35. w.setContentView(cv);
  36. const wcv1 = new WebContentsView();
  37. const wcv2 = new WebContentsView();
  38. const wcv3 = new WebContentsView();
  39. w.contentView.addChildView(wcv1);
  40. w.contentView.addChildView(wcv2);
  41. w.contentView.addChildView(wcv3);
  42. expect(w.contentView.children).to.deep.equal([wcv1, wcv2, wcv3]);
  43. w.contentView.addChildView(wcv1);
  44. w.contentView.addChildView(wcv2);
  45. expect(w.contentView.children).to.deep.equal([wcv3, wcv1, wcv2]);
  46. });
  47. function triggerGCByAllocation () {
  48. const arr = [];
  49. for (let i = 0; i < 1000000; i++) {
  50. arr.push([]);
  51. }
  52. return arr;
  53. }
  54. it('doesn\'t crash when GCed during allocation', (done) => {
  55. // eslint-disable-next-line no-new
  56. new WebContentsView();
  57. setTimeout(() => {
  58. // NB. the crash we're testing for is the lack of a current `v8::Context`
  59. // when emitting an event in WebContents's destructor. V8 is inconsistent
  60. // about whether or not there's a current context during garbage
  61. // collection, and it seems that `v8Util.requestGarbageCollectionForTesting`
  62. // causes a GC in which there _is_ a current context, so the crash isn't
  63. // triggered. Thus, we force a GC by other means: namely, by allocating a
  64. // bunch of stuff.
  65. triggerGCByAllocation();
  66. done();
  67. });
  68. });
  69. it('can be fullscreened', async () => {
  70. const w = new BaseWindow();
  71. const v = new WebContentsView();
  72. w.setContentView(v);
  73. await v.webContents.loadURL('data:text/html,<div id="div">This is a simple div.</div>');
  74. const enterFullScreen = once(w, 'enter-full-screen');
  75. await v.webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
  76. await enterFullScreen;
  77. expect(w.isFullScreen()).to.be.true('isFullScreen');
  78. });
  79. describe('visibilityState', () => {
  80. it('is initially hidden', async () => {
  81. const v = new WebContentsView();
  82. await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
  83. expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('hidden');
  84. });
  85. it('becomes visible when attached', async () => {
  86. const v = new WebContentsView();
  87. await v.webContents.loadURL('about:blank');
  88. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  89. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  90. // Ensure that the above listener has been registered before we add the
  91. // view to the window, or else the visibilitychange event might be
  92. // dispatched before the listener is registered.
  93. // executeJavaScript calls are sequential so if this one's finished then
  94. // the previous one must also have been finished :)
  95. await v.webContents.executeJavaScript('undefined');
  96. const w = new BaseWindow();
  97. w.setContentView(v);
  98. await p;
  99. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  100. });
  101. it('is initially visible if load happens after attach', async () => {
  102. const w = new BaseWindow();
  103. const v = new WebContentsView();
  104. w.contentView = v;
  105. await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
  106. expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('visible');
  107. });
  108. it('becomes hidden when parent window is hidden', async () => {
  109. const w = new BaseWindow();
  110. const v = new WebContentsView();
  111. w.setContentView(v);
  112. await v.webContents.loadURL('about:blank');
  113. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  114. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  115. // We have to wait until the listener above is fully registered before hiding the window.
  116. // On Windows, the executeJavaScript and the visibilitychange can happen out of order
  117. // without this.
  118. await v.webContents.executeJavaScript('0');
  119. w.hide();
  120. await p;
  121. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  122. });
  123. it('becomes visible when parent window is shown', async () => {
  124. const w = new BaseWindow({ show: false });
  125. const v = new WebContentsView();
  126. w.setContentView(v);
  127. await v.webContents.loadURL('about:blank');
  128. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  129. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  130. // We have to wait until the listener above is fully registered before hiding the window.
  131. // On Windows, the executeJavaScript and the visibilitychange can happen out of order
  132. // without this.
  133. await v.webContents.executeJavaScript('0');
  134. w.show();
  135. await p;
  136. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  137. });
  138. it('does not change when view is moved between two visible windows', async () => {
  139. const w = new BaseWindow();
  140. const v = new WebContentsView();
  141. w.setContentView(v);
  142. await v.webContents.loadURL('about:blank');
  143. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  144. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
  145. // Ensure the listener has been registered.
  146. await v.webContents.executeJavaScript('undefined');
  147. const w2 = new BaseWindow();
  148. w2.setContentView(v);
  149. // Wait for the visibility state to settle as "visible".
  150. // On macOS one visibilitychange event is fired but visibilityState
  151. // remains "visible". On Win/Linux, two visibilitychange events are
  152. // fired, a "hidden" and a "visible" one. Reconcile these two models
  153. // by waiting until at least one event has been fired, and then waiting
  154. // until the visibility state settles as "visible".
  155. let visibilityState = await p;
  156. for (let attempts = 0; visibilityState !== 'visible' && attempts < 10; attempts++) {
  157. visibilityState = await v.webContents.executeJavaScript('new Promise(resolve => document.visibilityState === "visible" ? resolve("visible") : document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
  158. }
  159. expect(visibilityState).to.equal('visible');
  160. });
  161. });
  162. });