visibility-state-spec.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { expect } from 'chai';
  2. import * as cp from 'node:child_process';
  3. import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, WebContents, WebContentsView } from 'electron/main';
  4. import * as path from 'node:path';
  5. import { closeWindow } from './lib/window-helpers';
  6. import { ifdescribe } from './lib/spec-helpers';
  7. import { once } from 'node:events';
  8. import { setTimeout } from 'node:timers/promises';
  9. // visibilityState specs pass on linux with a real window manager but on CI
  10. // the environment does not let these specs pass
  11. ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
  12. let w: BaseWindow & {webContents: WebContents};
  13. afterEach(() => {
  14. return closeWindow(w);
  15. });
  16. const load = () => w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));
  17. const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => {
  18. it(name, async function (...args) {
  19. w = new BrowserWindow({
  20. ...options,
  21. paintWhenInitiallyHidden: false,
  22. webPreferences: {
  23. ...(options.webPreferences || {}),
  24. nodeIntegration: true,
  25. contextIsolation: false
  26. }
  27. });
  28. await Promise.resolve(fn.apply(this, args));
  29. });
  30. it(name + ' with BaseWindow', async function (...args) {
  31. const baseWindow = new BaseWindow({
  32. ...options
  33. });
  34. const wcv = new WebContentsView({ webPreferences: { ...(options.webPreferences ?? {}), nodeIntegration: true, contextIsolation: false } });
  35. baseWindow.contentView = wcv;
  36. w = Object.assign(baseWindow, { webContents: wcv.webContents });
  37. await Promise.resolve(fn.apply(this, args));
  38. });
  39. };
  40. itWithOptions('should be visible when the window is initially shown by default', {}, async () => {
  41. load();
  42. const [, state] = await once(ipcMain, 'initial-visibility-state');
  43. expect(state).to.equal('visible');
  44. });
  45. itWithOptions('should be visible when the window is initially shown', {
  46. show: true
  47. }, async () => {
  48. load();
  49. const [, state] = await once(ipcMain, 'initial-visibility-state');
  50. expect(state).to.equal('visible');
  51. });
  52. itWithOptions('should be hidden when the window is initially hidden', {
  53. show: false
  54. }, async () => {
  55. load();
  56. const [, state] = await once(ipcMain, 'initial-visibility-state');
  57. expect(state).to.equal('hidden');
  58. });
  59. itWithOptions('should be visible when the window is initially hidden but shown before the page is loaded', {
  60. show: false
  61. }, async () => {
  62. w.show();
  63. load();
  64. const [, state] = await once(ipcMain, 'initial-visibility-state');
  65. expect(state).to.equal('visible');
  66. });
  67. itWithOptions('should be hidden when the window is initially shown but hidden before the page is loaded', {
  68. show: true
  69. }, async () => {
  70. // TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users
  71. if (process.platform === 'darwin') {
  72. // Wait for a tick, the window being "shown" takes 1 tick on macOS
  73. await setTimeout(10000);
  74. }
  75. w.hide();
  76. load();
  77. const [, state] = await once(ipcMain, 'initial-visibility-state');
  78. expect(state).to.equal('hidden');
  79. });
  80. itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => {
  81. load();
  82. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  83. expect(initialState).to.equal('visible');
  84. w.hide();
  85. await once(ipcMain, 'visibility-change-hidden');
  86. w.show();
  87. await once(ipcMain, 'visibility-change-visible');
  88. });
  89. itWithOptions('should become hidden when a window is minimized', {}, async () => {
  90. load();
  91. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  92. expect(initialState).to.equal('visible');
  93. w.minimize();
  94. const p = once(ipcMain, 'visibility-change-hidden');
  95. w.minimize();
  96. await p;
  97. });
  98. itWithOptions('should become visible when a window is restored', {}, async () => {
  99. load();
  100. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  101. expect(initialState).to.equal('visible');
  102. w.minimize();
  103. await once(ipcMain, 'visibility-change-hidden');
  104. w.restore();
  105. await once(ipcMain, 'visibility-change-visible');
  106. });
  107. ifdescribe(process.platform === 'darwin')('on platforms that support occlusion detection', () => {
  108. let child: cp.ChildProcess;
  109. const makeOtherWindow = (opts: { x: number; y: number; width: number; height: number; }) => {
  110. child = cp.spawn(process.execPath, [path.resolve(__dirname, 'fixtures', 'chromium', 'other-window.js'), `${opts.x}`, `${opts.y}`, `${opts.width}`, `${opts.height}`]);
  111. return new Promise<void>(resolve => {
  112. child.stdout!.on('data', (chunk) => {
  113. if (chunk.toString().includes('__ready__')) resolve();
  114. });
  115. });
  116. };
  117. afterEach(() => {
  118. if (child && !child.killed) {
  119. child.kill('SIGTERM');
  120. }
  121. });
  122. itWithOptions('should be visible when two windows are on screen', {
  123. x: 0,
  124. y: 0,
  125. width: 200,
  126. height: 200
  127. }, async () => {
  128. await makeOtherWindow({
  129. x: 200,
  130. y: 0,
  131. width: 200,
  132. height: 200
  133. });
  134. load();
  135. const [, state] = await once(ipcMain, 'initial-visibility-state');
  136. expect(state).to.equal('visible');
  137. });
  138. itWithOptions('should be visible when two windows are on screen that overlap partially', {
  139. x: 50,
  140. y: 50,
  141. width: 150,
  142. height: 150
  143. }, async () => {
  144. await makeOtherWindow({
  145. x: 100,
  146. y: 0,
  147. width: 200,
  148. height: 200
  149. });
  150. load();
  151. const [, state] = await once(ipcMain, 'initial-visibility-state');
  152. expect(state).to.equal('visible');
  153. });
  154. itWithOptions('should be hidden when a second window completely occludes the current window', {
  155. x: 50,
  156. y: 50,
  157. width: 50,
  158. height: 50
  159. }, async function () {
  160. this.timeout(240000);
  161. load();
  162. const [, state] = await once(ipcMain, 'initial-visibility-state');
  163. expect(state).to.equal('visible');
  164. makeOtherWindow({
  165. x: 0,
  166. y: 0,
  167. width: 300,
  168. height: 300
  169. });
  170. await once(ipcMain, 'visibility-change-hidden');
  171. });
  172. });
  173. });