From e1a19d735e7849a704f87b9855811166a15f4a5a Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Fri, 2 Oct 2020 20:18:42 +0200 Subject: [PATCH] chore: convert guest-window-manager.js to TypeScript (#25709) --- filenames.auto.gni | 2 +- lib/browser/api/web-contents.ts | 4 +- ...dow-manager.js => guest-window-manager.ts} | 89 +++++++++---------- typings/internal-ambient.d.ts | 1 + typings/internal-electron.d.ts | 3 + 5 files changed, 51 insertions(+), 48 deletions(-) rename lib/browser/{guest-window-manager.js => guest-window-manager.ts} (73%) diff --git a/filenames.auto.gni b/filenames.auto.gni index 73aa6408e933..31c2ef639fed 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -228,7 +228,7 @@ auto_filenames = { "lib/browser/desktop-capturer.ts", "lib/browser/devtools.ts", "lib/browser/guest-view-manager.js", - "lib/browser/guest-window-manager.js", + "lib/browser/guest-window-manager.ts", "lib/browser/init.ts", "lib/browser/ipc-main-impl.ts", "lib/browser/ipc-main-internal-utils.ts", diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 71c447c845c6..87bc36ffd8ba 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -566,7 +566,7 @@ WebContents.prototype._init = function () { if (this.getType() !== 'remote') { // Make new windows requested by links behave like "window.open". this.on('-new-window' as any, (event: any, url: string, frameName: string, disposition: string, - rawFeatures: string, referrer: string, postData: string) => { + rawFeatures: string, referrer: string, postData: Electron.UploadRawData[]) => { const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures); const mergedOptions = { show: true, @@ -584,7 +584,7 @@ WebContents.prototype._init = function () { // "window.open", used in sandbox and nativeWindowOpen mode. this.on('-add-new-contents' as any, (event: any, webContents: Electron.WebContents, disposition: string, userGesture: boolean, left: number, top: number, width: number, height: number, url: string, frameName: string, - referrer: string, rawFeatures: string, postData: string) => { + referrer: string, rawFeatures: string, postData: Electron.UploadRawData[]) => { if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && disposition !== 'background-tab')) { event.preventDefault(); diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.ts similarity index 73% rename from lib/browser/guest-window-manager.js rename to lib/browser/guest-window-manager.ts index 008714917507..44b4ff2ecace 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.ts @@ -1,14 +1,13 @@ -'use strict'; +import * as electron from 'electron/main'; +import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; +import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; +import { parseFeatures } from '@electron/internal/common/parse-features-string'; -const electron = require('electron'); -const { BrowserWindow } = electron; const { isSameOrigin } = process._linkedBinding('electron_common_v8_util'); -const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); -const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); -const { parseFeatures } = require('@electron/internal/common/parse-features-string'); +const { BrowserWindow } = electron; const hasProp = {}.hasOwnProperty; -const frameToGuest = new Map(); +const frameToGuest = new Map(); // Security options that child windows will always inherit from parent windows const inheritedWebPreferences = new Map([ @@ -24,7 +23,7 @@ const inheritedWebPreferences = new Map([ ]); // Copy attribute of |parent| to |child| if it is not defined in |child|. -const mergeOptions = function (child, parent, visited) { +const mergeOptions = function (child: Record, parent: Record, visited?: Set>) { // Check for circular reference. if (visited == null) visited = new Set(); if (visited.has(parent)) return; @@ -48,7 +47,7 @@ const mergeOptions = function (child, parent, visited) { }; // Merge |options| with the |embedder|'s window's options. -const mergeBrowserWindowOptions = function (embedder, options) { +const mergeBrowserWindowOptions = function (embedder: Electron.WebContents, options: Record) { if (options.webPreferences == null) { options.webPreferences = {}; } @@ -75,7 +74,7 @@ const mergeBrowserWindowOptions = function (embedder, options) { // Inherit certain option values from parent window const webPreferences = embedder.getLastWebPreferences(); for (const [name, value] of inheritedWebPreferences) { - if (webPreferences[name] === value) { + if ((webPreferences as any)[name] === value) { options.webPreferences[name] = value; } } @@ -90,7 +89,7 @@ const mergeBrowserWindowOptions = function (embedder, options) { const MULTIPART_CONTENT_TYPE = 'multipart/form-data'; const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded'; -function makeContentTypeHeader ({ contentType, boundary }) { +function makeContentTypeHeader ({ contentType, boundary }: { contentType: string, boundary?: string }) { const header = `content-type: ${contentType};`; if (contentType === MULTIPART_CONTENT_TYPE) { return `${header} boundary=${boundary}`; @@ -99,7 +98,7 @@ function makeContentTypeHeader ({ contentType, boundary }) { } // Figure out appropriate headers for post data. -const parseContentTypeFormat = function (postData) { +const parseContentTypeFormat = function (postData: Electron.UploadRawData[]) { if (postData.length) { // For multipart forms, the first element will start with the boundary // notice, which looks something like `------WebKitFormBoundary12345678` @@ -123,7 +122,7 @@ const parseContentTypeFormat = function (postData) { }; // Setup a new guest with |embedder| -const setupGuest = function (embedder, frameName, guest, options) { +const setupGuest = function (embedder: Electron.WebContents, frameName: string, guest: Electron.BrowserWindow) { // When |embedder| is destroyed we should also destroy attached guest, and if // guest is closed by user then we should prevent |embedder| from double // closing guest. @@ -134,9 +133,9 @@ const setupGuest = function (embedder, frameName, guest, options) { }; const closedByUser = function () { embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId); - embedder.removeListener('current-render-view-deleted', closedByEmbedder); + embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder); }; - embedder.once('current-render-view-deleted', closedByEmbedder); + embedder.once('current-render-view-deleted' as any, closedByEmbedder); guest.once('closed', closedByUser); if (frameName) { frameToGuest.set(frameName, guest); @@ -149,7 +148,8 @@ const setupGuest = function (embedder, frameName, guest, options) { }; // Create a new guest created by |embedder| with |options|. -const createGuest = function (embedder, url, referrer, frameName, options, postData) { +const createGuest = function (embedder: Electron.webContents, url: string, referrer: string | Electron.Referrer, + frameName: string, options: Record, postData?: Electron.UploadRawData[]) { let guest = frameToGuest.get(frameName); if (frameName && (guest != null)) { guest.loadURL(url); @@ -168,7 +168,7 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD // // Navigating to the url when creating the window from an existing // webContents is not necessary (it will navigate there anyway). - const loadOptions = { + const loadOptions: Electron.LoadURLOptions = { httpReferrer: referrer }; if (postData != null) { @@ -178,10 +178,10 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD guest.loadURL(url, loadOptions); } - return setupGuest(embedder, frameName, guest, options); + return setupGuest(embedder, frameName, guest); }; -const getGuestWindow = function (guestContents) { +const getGuestWindow = function (guestContents: Electron.WebContents) { let guestWindow = BrowserWindow.fromWebContents(guestContents); if (guestWindow == null) { const hostContents = guestContents.hostWebContents; @@ -195,31 +195,31 @@ const getGuestWindow = function (guestContents) { return guestWindow; }; -const isChildWindow = function (sender, target) { +const isChildWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { return target.getLastWebPreferences().openerId === sender.id; }; -const isRelatedWindow = function (sender, target) { +const isRelatedWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { return isChildWindow(sender, target) || isChildWindow(target, sender); }; -const isScriptableWindow = function (sender, target) { +const isScriptableWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL()); }; -const isNodeIntegrationEnabled = function (sender) { +const isNodeIntegrationEnabled = function (sender: Electron.WebContents) { return sender.getLastWebPreferences().nodeIntegration === true; }; // Checks whether |sender| can access the |target|: -const canAccessWindow = function (sender, target) { +const canAccessWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { return isChildWindow(sender, target) || isScriptableWindow(sender, target) || isNodeIntegrationEnabled(sender); }; // Routed window.open messages with raw options -ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { +ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url: string, frameName: string, features: string) => { // This should only be allowed for senders that have nativeWindowOpen: false const lastWebPreferences = event.sender.getLastWebPreferences(); if (lastWebPreferences.nativeWindowOpen || lastWebPreferences.sandbox) { @@ -233,14 +233,15 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra const disposition = 'new-window'; const { options, webPreferences, additionalFeatures } = parseFeatures(features); if (!options.title) options.title = frameName; - options.webPreferences = webPreferences; + (options as Electron.BrowserWindowConstructorOptions).webPreferences = webPreferences; - const referrer = { url: '', policy: 'default' }; - internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, null); + const referrer: Electron.Referrer = { url: '', policy: 'default' }; + internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures); }); // Routed window.open messages with fully parsed options -function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) { +export function internalWindowOpen (event: ElectronInternal.IpcMainInternalEvent, url: string, referrer: string | Electron.Referrer, + frameName: string, disposition: string, options: Record, additionalFeatures: string[], postData?: Electron.UploadRawData[]) { options = mergeBrowserWindowOptions(event.sender, options); const postBody = postData ? { data: postData, @@ -248,15 +249,15 @@ function internalWindowOpen (event, url, referrer, frameName, disposition, optio } : null; event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer, postBody); - const { newGuest } = event; + const { newGuest } = event as unknown as { newGuest: Electron.BrowserWindow }; if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { if (newGuest != null) { if (options.webContents === newGuest.webContents) { // the webContents is not changed, so set defaultPrevented to false to // stop the callers of this event from destroying the webContents. - event.defaultPrevented = false; + (event as any).defaultPrevented = false; } - event.returnValue = setupGuest(event.sender, frameName, newGuest, options); + event.returnValue = setupGuest(event.sender, frameName, newGuest); } else { event.returnValue = null; } @@ -265,8 +266,8 @@ function internalWindowOpen (event, url, referrer, frameName, disposition, optio } } -const makeSafeHandler = function (handler) { - return (event, guestId, ...args) => { +const makeSafeHandler = function (handler: (event: Electron.IpcMainInvokeEvent, guestContents: Electron.webContents, ...args: any[]) => T) { + return (event: Electron.IpcMainInvokeEvent, guestId: number, ...args: any[]) => { // Access webContents via electron to prevent circular require. const guestContents = electron.webContents.fromId(guestId); if (!guestContents) { @@ -277,15 +278,15 @@ const makeSafeHandler = function (handler) { }; }; -const handleMessage = function (channel, handler) { +const handleMessage = function (channel: string, handler: (event: Electron.IpcMainInvokeEvent, guestContents: Electron.webContents, ...args: any[]) => any) { ipcMainInternal.handle(channel, makeSafeHandler(handler)); }; -const handleMessageSync = function (channel, handler) { +const handleMessageSync = function (channel: string, handler: (event: Electron.IpcMainInvokeEvent, guestContents: Electron.webContents, ...args: any[]) => any) { ipcMainUtils.handleSync(channel, makeSafeHandler(handler)); }; -const securityCheck = function (contents, guestContents, check) { +const securityCheck = function (contents: Electron.WebContents, guestContents: Electron.WebContents, check: (sender: Electron.WebContents, target: Electron.WebContents) => boolean) { if (!check(contents, guestContents)) { console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`); throw new Error(`Access denied to guestId: ${guestContents.id}`); @@ -298,7 +299,7 @@ const windowMethods = new Set([ 'blur' ]); -handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => { +handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method: string, ...args: any[]) => { securityCheck(event.sender, guestContents, canAccessWindow); if (!windowMethods.has(method)) { @@ -306,7 +307,7 @@ handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestConten throw new Error(`Invalid method: ${method}`); } - return getGuestWindow(guestContents)[method](...args); + return (getGuestWindow(guestContents) as any)[method](...args); }); handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => { @@ -331,7 +332,7 @@ const webContentsMethodsAsync = new Set([ 'print' ]); -handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { +handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method: string, ...args: any[]) => { securityCheck(event.sender, guestContents, canAccessWindow); if (!webContentsMethodsAsync.has(method)) { @@ -339,14 +340,14 @@ handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guest throw new Error(`Invalid method: ${method}`); } - return guestContents[method](...args); + return (guestContents as any)[method](...args); }); const webContentsMethodsSync = new Set([ 'getURL' ]); -handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { +handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method: string, ...args: any[]) => { securityCheck(event.sender, guestContents, canAccessWindow); if (!webContentsMethodsSync.has(method)) { @@ -354,7 +355,5 @@ handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, g throw new Error(`Invalid method: ${method}`); } - return guestContents[method](...args); + return (guestContents as any)[method](...args); }); - -exports.internalWindowOpen = internalWindowOpen; diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 15dc286cfa4d..8846510f009d 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -45,6 +45,7 @@ declare namespace NodeJS { getWeaklyTrackedValues(): any[]; addRemoteObjectRef(contextId: string, id: number): void; triggerFatalErrorForTesting(): void; + isSameOrigin(left: string, right: string): boolean; } type AsarFileInfo = { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 13c526637292..6933fefc00ec 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -26,6 +26,7 @@ declare namespace Electron { _setTouchBarItems: (items: TouchBarItemType[]) => void; _setEscapeTouchBarItem: (item: TouchBarItemType | {}) => void; _refreshTouchBarItem: (itemID: string) => void; + frameName: string; on(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this; removeListener(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this; } @@ -57,6 +58,7 @@ declare namespace Electron { _getPreloadPaths(): string[]; equal(other: WebContents): boolean; _initiallyShown: boolean; + browserWindowOptions: BrowserWindowConstructorOptions; _send(internal: boolean, sendToAll: boolean, channel: string, args: any): boolean; _sendToFrame(internal: boolean, sendToAll: boolean, frameId: number, channel: string, args: any): boolean; _sendToFrameInternal(frameId: number, channel: string, args: any): boolean; @@ -80,6 +82,7 @@ declare namespace Electron { interface WebPreferences { guestInstanceId?: number; openerId?: number; + disablePopups?: boolean } interface Menu {