browser-view.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { BrowserWindow, AutoResizeOptions, Rectangle, WebContentsView, WebPreferences, WebContents } from 'electron/main';
  2. const v8Util = process._linkedBinding('electron_common_v8_util');
  3. export default class BrowserView {
  4. #webContentsView: WebContentsView;
  5. #ownerWindow: BrowserWindow | null = null;
  6. #destroyListener: ((e: any) => void) | null = null;
  7. // AutoResize state
  8. #resizeListener: ((...args: any[]) => void) | null = null;
  9. #lastWindowSize: {width: number, height: number} = { width: 0, height: 0 };
  10. #autoResizeFlags: AutoResizeOptions = {};
  11. constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) {
  12. const { webPreferences = {}, webContents } = options;
  13. if (webContents) {
  14. v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
  15. }
  16. webPreferences.type = 'browserView';
  17. this.#webContentsView = new WebContentsView({ webPreferences });
  18. this.#destroyListener = this.#onDestroy.bind(this);
  19. this.#webContentsView.webContents.once('destroyed', this.#destroyListener);
  20. }
  21. get webContents () {
  22. return this.#webContentsView.webContents;
  23. }
  24. setBounds (bounds: Rectangle) {
  25. this.#webContentsView.setBounds(bounds);
  26. this.#autoHorizontalProportion = null;
  27. this.#autoVerticalProportion = null;
  28. }
  29. getBounds () {
  30. return this.#webContentsView.getBounds();
  31. }
  32. setAutoResize (options: AutoResizeOptions) {
  33. if (options == null || typeof options !== 'object') {
  34. throw new Error('Invalid auto resize options');
  35. }
  36. this.#autoResizeFlags = {
  37. width: !!options.width,
  38. height: !!options.height,
  39. horizontal: !!options.horizontal,
  40. vertical: !!options.vertical
  41. };
  42. this.#autoHorizontalProportion = null;
  43. this.#autoVerticalProportion = null;
  44. }
  45. setBackgroundColor (color: string) {
  46. this.#webContentsView.setBackgroundColor(color);
  47. }
  48. // Internal methods
  49. get ownerWindow (): BrowserWindow | null {
  50. return this.#ownerWindow;
  51. }
  52. // We can't rely solely on the webContents' owner window because
  53. // a webContents can be closed by the user while the BrowserView
  54. // remains alive and attached to a BrowserWindow.
  55. set ownerWindow (w: BrowserWindow | null) {
  56. this.#removeResizeListener();
  57. if (this.webContents && !this.webContents.isDestroyed()) {
  58. this.webContents._setOwnerWindow(w);
  59. }
  60. this.#ownerWindow = w;
  61. if (w) {
  62. this.#lastWindowSize = w.getBounds();
  63. w.on('resize', this.#resizeListener = this.#autoResize.bind(this));
  64. w.on('closed', () => {
  65. this.#removeResizeListener();
  66. this.#ownerWindow = null;
  67. this.#destroyListener = null;
  68. });
  69. }
  70. }
  71. #onDestroy () {
  72. // Ensure that if #webContentsView's webContents is destroyed,
  73. // the WebContentsView is removed from the view hierarchy.
  74. this.#ownerWindow?.contentView.removeChildView(this.webContentsView);
  75. }
  76. #removeResizeListener () {
  77. if (this.#ownerWindow && this.#resizeListener) {
  78. this.#ownerWindow.off('resize', this.#resizeListener);
  79. this.#resizeListener = null;
  80. }
  81. }
  82. #autoHorizontalProportion: {width: number, left: number} | null = null;
  83. #autoVerticalProportion: {height: number, top: number} | null = null;
  84. #autoResize () {
  85. if (!this.ownerWindow) {
  86. throw new Error('Electron bug: #autoResize called without owner window');
  87. };
  88. if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
  89. const viewBounds = this.#webContentsView.getBounds();
  90. this.#autoHorizontalProportion = {
  91. width: this.#lastWindowSize.width / viewBounds.width,
  92. left: this.#lastWindowSize.width / viewBounds.x
  93. };
  94. }
  95. if (this.#autoResizeFlags.vertical && this.#autoVerticalProportion == null) {
  96. const viewBounds = this.#webContentsView.getBounds();
  97. this.#autoVerticalProportion = {
  98. height: this.#lastWindowSize.height / viewBounds.height,
  99. top: this.#lastWindowSize.height / viewBounds.y
  100. };
  101. }
  102. const newBounds = this.ownerWindow.getBounds();
  103. let widthDelta = newBounds.width - this.#lastWindowSize.width;
  104. let heightDelta = newBounds.height - this.#lastWindowSize.height;
  105. if (!this.#autoResizeFlags.width) widthDelta = 0;
  106. if (!this.#autoResizeFlags.height) heightDelta = 0;
  107. const newViewBounds = this.#webContentsView.getBounds();
  108. if (widthDelta || heightDelta) {
  109. this.#webContentsView.setBounds({
  110. ...newViewBounds,
  111. width: newViewBounds.width + widthDelta,
  112. height: newViewBounds.height + heightDelta
  113. });
  114. }
  115. if (this.#autoHorizontalProportion) {
  116. newViewBounds.width = newBounds.width / this.#autoHorizontalProportion.width;
  117. newViewBounds.x = newBounds.width / this.#autoHorizontalProportion.left;
  118. }
  119. if (this.#autoVerticalProportion) {
  120. newViewBounds.height = newBounds.height / this.#autoVerticalProportion.height;
  121. newViewBounds.y = newBounds.y / this.#autoVerticalProportion.top;
  122. }
  123. if (this.#autoHorizontalProportion || this.#autoVerticalProportion) {
  124. this.#webContentsView.setBounds(newViewBounds);
  125. }
  126. // Update #lastWindowSize value after browser windows resize
  127. this.#lastWindowSize = {
  128. width: newBounds.width,
  129. height: newBounds.height
  130. };
  131. }
  132. get webContentsView () {
  133. return this.#webContentsView;
  134. }
  135. }