guest-window-proxy.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * Manage guest windows when using the default BrowserWindowProxy version of the
  3. * renderer's window.open (i.e. nativeWindowOpen off). This module mostly
  4. * consists of marshaling IPC requests from the BrowserWindowProxy to the
  5. * WebContents.
  6. */
  7. import { webContents, BrowserWindow } from 'electron/main';
  8. import type { WebContents } from 'electron/main';
  9. import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
  10. import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
  11. import { openGuestWindow } from '@electron/internal/browser/guest-window-manager';
  12. import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
  13. const { isSameOrigin } = process._linkedBinding('electron_common_v8_util');
  14. const getGuestWindow = function (guestContents: WebContents) {
  15. let guestWindow = BrowserWindow.fromWebContents(guestContents);
  16. if (guestWindow == null) {
  17. const hostContents = guestContents.hostWebContents;
  18. if (hostContents != null) {
  19. guestWindow = BrowserWindow.fromWebContents(hostContents);
  20. }
  21. }
  22. if (!guestWindow) {
  23. throw new Error('getGuestWindow failed');
  24. }
  25. return guestWindow;
  26. };
  27. const isChildWindow = function (sender: WebContents, target: WebContents) {
  28. return target.getLastWebPreferences().openerId === sender.id;
  29. };
  30. const isRelatedWindow = function (sender: WebContents, target: WebContents) {
  31. return isChildWindow(sender, target) || isChildWindow(target, sender);
  32. };
  33. const isScriptableWindow = function (sender: WebContents, target: WebContents) {
  34. return (
  35. isRelatedWindow(sender, target) &&
  36. isSameOrigin(sender.getURL(), target.getURL())
  37. );
  38. };
  39. const isNodeIntegrationEnabled = function (sender: WebContents) {
  40. return sender.getLastWebPreferences().nodeIntegration === true;
  41. };
  42. // Checks whether |sender| can access the |target|:
  43. const canAccessWindow = function (sender: WebContents, target: WebContents) {
  44. return (
  45. isChildWindow(sender, target) ||
  46. isScriptableWindow(sender, target) ||
  47. isNodeIntegrationEnabled(sender)
  48. );
  49. };
  50. // Routed window.open messages with raw options
  51. ipcMainInternal.on(
  52. IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_OPEN,
  53. (
  54. event,
  55. url: string,
  56. frameName: string,
  57. features: string
  58. ) => {
  59. // This should only be allowed for senders that have nativeWindowOpen: false
  60. const lastWebPreferences = event.sender.getLastWebPreferences();
  61. if (lastWebPreferences.nativeWindowOpen || lastWebPreferences.sandbox) {
  62. event.returnValue = null;
  63. throw new Error(
  64. 'GUEST_WINDOW_MANAGER_WINDOW_OPEN denied: expected native window.open'
  65. );
  66. }
  67. const referrer: Electron.Referrer = { url: '', policy: 'strict-origin-when-cross-origin' };
  68. const browserWindowOptions = event.sender._callWindowOpenHandler(event, { url, frameName, features, disposition: 'new-window', referrer });
  69. if (event.defaultPrevented) {
  70. event.returnValue = null;
  71. return;
  72. }
  73. const guest = openGuestWindow({
  74. event,
  75. embedder: event.sender,
  76. referrer,
  77. disposition: 'new-window',
  78. overrideBrowserWindowOptions: browserWindowOptions!,
  79. windowOpenArgs: {
  80. url: url || 'about:blank',
  81. frameName: frameName || '',
  82. features: features || ''
  83. }
  84. });
  85. event.returnValue = guest ? guest.webContents.id : null;
  86. }
  87. );
  88. type IpcHandler<T, Event> = (event: Event, guestContents: Electron.WebContents, ...args: any[]) => T;
  89. const makeSafeHandler = function<T, Event> (handler: IpcHandler<T, Event>) {
  90. return (event: Event, guestId: number, ...args: any[]) => {
  91. // Access webContents via electron to prevent circular require.
  92. const guestContents = webContents.fromId(guestId);
  93. if (!guestContents) {
  94. throw new Error(`Invalid guestId: ${guestId}`);
  95. }
  96. return handler(event, guestContents as Electron.WebContents, ...args);
  97. };
  98. };
  99. const handleMessage = function (channel: string, handler: IpcHandler<any, Electron.IpcMainInvokeEvent>) {
  100. ipcMainInternal.handle(channel, makeSafeHandler(handler));
  101. };
  102. const handleMessageSync = function (channel: string, handler: IpcHandler<any, ElectronInternal.IpcMainInternalEvent>) {
  103. ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
  104. };
  105. type ContentsCheck = (contents: WebContents, guestContents: WebContents) => boolean;
  106. const securityCheck = function (contents: WebContents, guestContents: WebContents, check: ContentsCheck) {
  107. if (!check(contents, guestContents)) {
  108. console.error(
  109. `Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`
  110. );
  111. throw new Error(`Access denied to guestId: ${guestContents.id}`);
  112. }
  113. };
  114. const windowMethods = new Set(['destroy', 'focus', 'blur']);
  115. handleMessage(
  116. IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_METHOD,
  117. (event, guestContents, method, ...args) => {
  118. securityCheck(event.sender, guestContents, canAccessWindow);
  119. if (!windowMethods.has(method)) {
  120. console.error(
  121. `Blocked ${event.senderFrame.url} from calling method: ${method}`
  122. );
  123. throw new Error(`Invalid method: ${method}`);
  124. }
  125. return (getGuestWindow(guestContents) as any)[method](...args);
  126. }
  127. );
  128. handleMessage(
  129. IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE,
  130. (event, guestContents, message, targetOrigin, sourceOrigin) => {
  131. if (targetOrigin == null) {
  132. targetOrigin = '*';
  133. }
  134. // The W3C does not seem to have word on how postMessage should work when the
  135. // origins do not match, so we do not do |canAccessWindow| check here since
  136. // postMessage across origins is useful and not harmful.
  137. securityCheck(event.sender, guestContents, isRelatedWindow);
  138. if (
  139. targetOrigin === '*' ||
  140. isSameOrigin(guestContents.getURL(), targetOrigin)
  141. ) {
  142. const sourceId = event.sender.id;
  143. guestContents._sendInternal(
  144. IPC_MESSAGES.GUEST_WINDOW_POSTMESSAGE,
  145. sourceId,
  146. message,
  147. sourceOrigin
  148. );
  149. }
  150. }
  151. );
  152. const webContentsMethodsAsync = new Set([
  153. 'loadURL',
  154. 'executeJavaScript',
  155. 'print'
  156. ]);
  157. handleMessage(
  158. IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD,
  159. (event, guestContents, method, ...args) => {
  160. securityCheck(event.sender, guestContents, canAccessWindow);
  161. if (!webContentsMethodsAsync.has(method)) {
  162. console.error(
  163. `Blocked ${event.sender.getURL()} from calling method: ${method}`
  164. );
  165. throw new Error(`Invalid method: ${method}`);
  166. }
  167. return (guestContents as any)[method](...args);
  168. }
  169. );
  170. const webContentsMethodsSync = new Set(['getURL']);
  171. handleMessageSync(
  172. IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD,
  173. (event, guestContents, method, ...args) => {
  174. securityCheck(event.sender, guestContents, canAccessWindow);
  175. if (!webContentsMethodsSync.has(method)) {
  176. console.error(
  177. `Blocked ${event.sender.getURL()} from calling method: ${method}`
  178. );
  179. throw new Error(`Invalid method: ${method}`);
  180. }
  181. return (guestContents as any)[method](...args);
  182. }
  183. );