browser-window.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { BaseWindow, WebContents, BrowserView } from 'electron/main';
  2. import type { BrowserWindow as BWT } from 'electron/main';
  3. const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT };
  4. Object.setPrototypeOf(BrowserWindow.prototype, BaseWindow.prototype);
  5. BrowserWindow.prototype._init = function (this: BWT) {
  6. // Call parent class's _init.
  7. (BaseWindow.prototype as any)._init.call(this);
  8. // Avoid recursive require.
  9. const { app } = require('electron');
  10. // Set ID at construction time so it's accessible after
  11. // underlying window destruction.
  12. const id = this.id;
  13. Object.defineProperty(this, 'id', {
  14. value: id,
  15. writable: false
  16. });
  17. const nativeSetBounds = this.setBounds;
  18. this.setBounds = (bounds, ...opts) => {
  19. bounds = {
  20. ...this.getBounds(),
  21. ...bounds
  22. };
  23. nativeSetBounds.call(this, bounds, ...opts);
  24. };
  25. // Redirect focus/blur event to app instance too.
  26. this.on('blur', (event: Electron.Event) => {
  27. app.emit('browser-window-blur', event, this);
  28. });
  29. this.on('focus', (event: Electron.Event) => {
  30. app.emit('browser-window-focus', event, this);
  31. });
  32. let unresponsiveEvent: NodeJS.Timeout | null = null;
  33. const emitUnresponsiveEvent = () => {
  34. unresponsiveEvent = null;
  35. if (!this.isDestroyed() && this.isEnabled()) { this.emit('unresponsive'); }
  36. };
  37. this.webContents.on('unresponsive', () => {
  38. if (!unresponsiveEvent) { unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50); }
  39. });
  40. this.webContents.on('responsive', () => {
  41. if (unresponsiveEvent) {
  42. clearTimeout(unresponsiveEvent);
  43. unresponsiveEvent = null;
  44. }
  45. this.emit('responsive');
  46. });
  47. this.on('close', (event) => {
  48. queueMicrotask(() => {
  49. if (!unresponsiveEvent && !event.defaultPrevented) {
  50. unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 5000);
  51. }
  52. });
  53. });
  54. this.webContents.on('destroyed', () => {
  55. if (unresponsiveEvent) clearTimeout(unresponsiveEvent);
  56. unresponsiveEvent = null;
  57. });
  58. // Subscribe to visibilityState changes and pass to renderer process.
  59. let isVisible = this.isVisible() && !this.isMinimized();
  60. const visibilityChanged = () => {
  61. const newState = this.isVisible() && !this.isMinimized();
  62. if (isVisible !== newState) {
  63. isVisible = newState;
  64. const visibilityState = isVisible ? 'visible' : 'hidden';
  65. this.webContents.emit('-window-visibility-change', visibilityState);
  66. }
  67. };
  68. const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
  69. for (const event of visibilityEvents) {
  70. this.on(event as any, visibilityChanged);
  71. }
  72. this._browserViews = [];
  73. this.on('closed', () => {
  74. this._browserViews.forEach(b => b.webContents?.close({ waitForBeforeUnload: true }));
  75. });
  76. // Notify the creation of the window.
  77. app.emit('browser-window-created', { preventDefault () {} }, this);
  78. Object.defineProperty(this, 'devToolsWebContents', {
  79. enumerable: true,
  80. configurable: false,
  81. get () {
  82. return this.webContents.devToolsWebContents;
  83. }
  84. });
  85. };
  86. const isBrowserWindow = (win: any) => {
  87. return win && win.constructor.name === 'BrowserWindow';
  88. };
  89. BrowserWindow.fromId = (id: number) => {
  90. const win = BaseWindow.fromId(id);
  91. return isBrowserWindow(win) ? win as any as BWT : null;
  92. };
  93. BrowserWindow.getAllWindows = () => {
  94. return BaseWindow.getAllWindows().filter(isBrowserWindow) as any[] as BWT[];
  95. };
  96. BrowserWindow.getFocusedWindow = () => {
  97. for (const window of BrowserWindow.getAllWindows()) {
  98. if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
  99. if (window.isFocused() || window.webContents.isDevToolsFocused()) return window;
  100. }
  101. }
  102. return null;
  103. };
  104. BrowserWindow.fromWebContents = (webContents: WebContents) => {
  105. return webContents.getOwnerBrowserWindow();
  106. };
  107. BrowserWindow.fromBrowserView = (browserView: BrowserView) => {
  108. return BrowserWindow.fromWebContents(browserView.webContents);
  109. };
  110. // Forwarded to webContents:
  111. BrowserWindow.prototype.loadURL = function (...args) {
  112. return this.webContents.loadURL(...args);
  113. };
  114. BrowserWindow.prototype.getURL = function () {
  115. return this.webContents.getURL();
  116. };
  117. BrowserWindow.prototype.loadFile = function (...args) {
  118. return this.webContents.loadFile(...args);
  119. };
  120. BrowserWindow.prototype.reload = function (...args) {
  121. return this.webContents.reload(...args);
  122. };
  123. BrowserWindow.prototype.send = function (...args) {
  124. return this.webContents.send(...args);
  125. };
  126. BrowserWindow.prototype.openDevTools = function (...args) {
  127. return this.webContents.openDevTools(...args);
  128. };
  129. BrowserWindow.prototype.closeDevTools = function () {
  130. return this.webContents.closeDevTools();
  131. };
  132. BrowserWindow.prototype.isDevToolsOpened = function () {
  133. return this.webContents.isDevToolsOpened();
  134. };
  135. BrowserWindow.prototype.isDevToolsFocused = function () {
  136. return this.webContents.isDevToolsFocused();
  137. };
  138. BrowserWindow.prototype.toggleDevTools = function () {
  139. return this.webContents.toggleDevTools();
  140. };
  141. BrowserWindow.prototype.inspectElement = function (...args) {
  142. return this.webContents.inspectElement(...args);
  143. };
  144. BrowserWindow.prototype.inspectSharedWorker = function () {
  145. return this.webContents.inspectSharedWorker();
  146. };
  147. BrowserWindow.prototype.inspectServiceWorker = function () {
  148. return this.webContents.inspectServiceWorker();
  149. };
  150. BrowserWindow.prototype.showDefinitionForSelection = function () {
  151. return this.webContents.showDefinitionForSelection();
  152. };
  153. BrowserWindow.prototype.capturePage = function (...args) {
  154. return this.webContents.capturePage(...args);
  155. };
  156. BrowserWindow.prototype.getBackgroundThrottling = function () {
  157. return this.webContents.getBackgroundThrottling();
  158. };
  159. BrowserWindow.prototype.setBackgroundThrottling = function (allowed: boolean) {
  160. return this.webContents.setBackgroundThrottling(allowed);
  161. };
  162. BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
  163. if (browserView.ownerWindow) { browserView.ownerWindow.removeBrowserView(browserView); }
  164. this.contentView.addChildView(browserView.webContentsView);
  165. browserView.ownerWindow = this;
  166. browserView.webContents._setOwnerWindow(this);
  167. this._browserViews.push(browserView);
  168. };
  169. BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
  170. this._browserViews.forEach(bv => {
  171. this.removeBrowserView(bv);
  172. });
  173. if (browserView) { this.addBrowserView(browserView); }
  174. };
  175. BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
  176. const idx = this._browserViews.indexOf(browserView);
  177. if (idx >= 0) {
  178. this.contentView.removeChildView(browserView.webContentsView);
  179. browserView.ownerWindow = null;
  180. this._browserViews.splice(idx, 1);
  181. }
  182. };
  183. BrowserWindow.prototype.getBrowserView = function () {
  184. if (this._browserViews.length > 1) {
  185. throw new Error('This BrowserWindow has multiple BrowserViews - use getBrowserViews() instead');
  186. }
  187. return this._browserViews[0] ?? null;
  188. };
  189. BrowserWindow.prototype.getBrowserViews = function () {
  190. return [...this._browserViews];
  191. };
  192. BrowserWindow.prototype.setTopBrowserView = function (browserView: BrowserView) {
  193. if (browserView.ownerWindow !== this) {
  194. throw new Error('Given BrowserView is not attached to the window');
  195. }
  196. const idx = this._browserViews.indexOf(browserView);
  197. if (idx >= 0) {
  198. this.contentView.addChildView(browserView.webContentsView);
  199. this._browserViews.splice(idx, 1);
  200. this._browserViews.push(browserView);
  201. }
  202. };
  203. module.exports = BrowserWindow;