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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { closeAllWindows } from './lib/window-helpers';
  2. import { expect } from 'chai';
  3. import { BaseWindow, View, WebContentsView } from 'electron/main';
  4. import { once } from 'node:events';
  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. function triggerGCByAllocation () {
  33. const arr = [];
  34. for (let i = 0; i < 1000000; i++) {
  35. arr.push([]);
  36. }
  37. return arr;
  38. }
  39. it('doesn\'t crash when GCed during allocation', (done) => {
  40. // eslint-disable-next-line no-new
  41. new WebContentsView();
  42. setTimeout(() => {
  43. // NB. the crash we're testing for is the lack of a current `v8::Context`
  44. // when emitting an event in WebContents's destructor. V8 is inconsistent
  45. // about whether or not there's a current context during garbage
  46. // collection, and it seems that `v8Util.requestGarbageCollectionForTesting`
  47. // causes a GC in which there _is_ a current context, so the crash isn't
  48. // triggered. Thus, we force a GC by other means: namely, by allocating a
  49. // bunch of stuff.
  50. triggerGCByAllocation();
  51. done();
  52. });
  53. });
  54. describe('visibilityState', () => {
  55. it('is initially hidden', async () => {
  56. const v = new WebContentsView();
  57. await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
  58. expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('hidden');
  59. });
  60. it('becomes visibile when attached', async () => {
  61. const v = new WebContentsView();
  62. await v.webContents.loadURL('about:blank');
  63. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  64. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  65. // Ensure that the above listener has been registered before we add the
  66. // view to the window, or else the visibilitychange event might be
  67. // dispatched before the listener is registered.
  68. // executeJavaScript calls are sequential so if this one's finished then
  69. // the previous one must also have been finished :)
  70. await v.webContents.executeJavaScript('undefined');
  71. const w = new BaseWindow();
  72. w.setContentView(v);
  73. await p;
  74. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  75. });
  76. it('is initially visible if load happens after attach', async () => {
  77. const w = new BaseWindow();
  78. const v = new WebContentsView();
  79. w.contentView = v;
  80. await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
  81. expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('visible');
  82. });
  83. it('becomes hidden when parent window is hidden', async () => {
  84. const w = new BaseWindow();
  85. const v = new WebContentsView();
  86. w.setContentView(v);
  87. await v.webContents.loadURL('about:blank');
  88. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  89. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  90. // We have to wait until the listener above is fully registered before hiding the window.
  91. // On Windows, the executeJavaScript and the visibilitychange can happen out of order
  92. // without this.
  93. await v.webContents.executeJavaScript('0');
  94. w.hide();
  95. await p;
  96. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  97. });
  98. it('becomes visible when parent window is shown', async () => {
  99. const w = new BaseWindow({ show: false });
  100. const v = new WebContentsView();
  101. w.setContentView(v);
  102. await v.webContents.loadURL('about:blank');
  103. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
  104. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
  105. // We have to wait until the listener above is fully registered before hiding the window.
  106. // On Windows, the executeJavaScript and the visibilitychange can happen out of order
  107. // without this.
  108. await v.webContents.executeJavaScript('0');
  109. w.show();
  110. await p;
  111. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  112. });
  113. it('does not change when view is moved between two visible windows', async () => {
  114. const w = new BaseWindow();
  115. const v = new WebContentsView();
  116. w.setContentView(v);
  117. await v.webContents.loadURL('about:blank');
  118. expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
  119. const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
  120. // Ensure the listener has been registered.
  121. await v.webContents.executeJavaScript('undefined');
  122. const w2 = new BaseWindow();
  123. w2.setContentView(v);
  124. // Wait for the visibility state to settle as "visible".
  125. // On macOS one visibilitychange event is fired but visibilityState
  126. // remains "visible". On Win/Linux, two visibilitychange events are
  127. // fired, a "hidden" and a "visible" one. Reconcile these two models
  128. // by waiting until at least one event has been fired, and then waiting
  129. // until the visibility state settles as "visible".
  130. let visibilityState = await p;
  131. for (let attempts = 0; visibilityState !== 'visible' && attempts < 10; attempts++) {
  132. visibilityState = await v.webContents.executeJavaScript('new Promise(resolve => document.visibilityState === "visible" ? resolve("visible") : document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
  133. }
  134. expect(visibilityState).to.equal('visible');
  135. });
  136. });
  137. });