diff --git a/filenames.auto.gni b/filenames.auto.gni index 3f659aec0ed7..94d69cea3661 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -230,7 +230,7 @@ auto_filenames = { "lib/browser/default-menu.ts", "lib/browser/desktop-capturer.ts", "lib/browser/devtools.ts", - "lib/browser/guest-view-manager.js", + "lib/browser/guest-view-manager.ts", "lib/browser/guest-window-manager.ts", "lib/browser/init.ts", "lib/browser/ipc-main-impl.ts", diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.ts similarity index 73% rename from lib/browser/guest-view-manager.js rename to lib/browser/guest-view-manager.ts index 2470d7f7dc17..fd8ac76d70f7 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.ts @@ -1,22 +1,26 @@ -'use strict'; +import { webContents } from 'electron/main'; +import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; +import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; +import { parseWebViewWebPreferences } from '@electron/internal/common/parse-features-string'; +import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods'; +import { webViewEvents } from '@electron/internal/common/web-view-events'; +import { serialize } from '@electron/internal/common/type-utils'; -const { webContents } = require('electron'); -const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); -const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); -const { parseWebViewWebPreferences } = require('@electron/internal/common/parse-features-string'); -const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods'); -const { webViewEvents } = require('@electron/internal/common/web-view-events'); -const { serialize } = require('@electron/internal/common/type-utils'); +interface GuestInstance { + elementInstanceId?: number; + visibilityState?: VisibilityState; + embedder: Electron.WebContents; + guest: Electron.WebContents; +} -// Doesn't exist in early initialization. -let webViewManager = null; +const webViewManager = process._linkedBinding('electron_browser_web_view_manager'); const supportedWebViewEvents = Object.keys(webViewEvents); -const guestInstances = {}; -const embedderElementsMap = {}; +const guestInstances: Record = {}; +const embedderElementsMap: Record = {}; -function sanitizeOptionsForGuest (options) { +function sanitizeOptionsForGuest (options: Record) { const ret = { ...options }; // WebContents values can't be sent over IPC. delete ret.webContents; @@ -24,12 +28,9 @@ function sanitizeOptionsForGuest (options) { } // Create a new guest instance. -const createGuest = function (embedder, params) { - if (webViewManager == null) { - webViewManager = process._linkedBinding('electron_browser_web_view_manager'); - } - - const guest = webContents.create({ +const createGuest = function (embedder: Electron.WebContents, params: Record) { + // eslint-disable-next-line no-undef + const guest = (webContents as typeof ElectronInternal.WebContents).create({ type: 'webview', partition: params.partition, embedder: embedder @@ -48,8 +49,8 @@ const createGuest = function (embedder, params) { }); // Init guest web view after attached. - guest.once('did-attach', function (event) { - params = this.attachParams; + guest.once('did-attach' as any, function (this: Electron.WebContents, event: Electron.Event) { + params = this.attachParams!; delete this.attachParams; const previouslyAttached = this.viewInstanceId != null; @@ -61,7 +62,7 @@ const createGuest = function (embedder, params) { } if (params.src) { - const opts = {}; + const opts: Electron.LoadURLOptions = {}; if (params.httpreferrer) { opts.httpReferrer = params.httpreferrer; } @@ -73,15 +74,15 @@ const createGuest = function (embedder, params) { embedder.emit('did-attach-webview', event, guest); }); - const sendToEmbedder = (channel, ...args) => { + const sendToEmbedder = (channel: string, ...args: any[]) => { if (!embedder.isDestroyed()) { embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args); } }; // Dispatch events to embedder. - const fn = function (event) { - guest.on(event, function (_, ...args) { + const fn = function (event: string) { + guest.on(event as any, function (_, ...args: any[]) { sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args); }); }; @@ -98,7 +99,7 @@ const createGuest = function (embedder, params) { }); // Dispatch guest's IPC messages to embedder. - guest.on('ipc-message-host', function (_, channel, args) { + guest.on('ipc-message-host' as any, function (_: Electron.Event, channel: string, args: any[]) { sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args); }); @@ -115,7 +116,8 @@ const createGuest = function (embedder, params) { }; // Attach the guest to an element of embedder. -const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { +const attachGuest = function (event: Electron.IpcMainInvokeEvent, + embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params: Record) { const embedder = event.sender; // Destroy the old guest when attaching. const key = `${embedder.id}-${elementInstanceId}`; @@ -138,7 +140,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`); } const { guest } = guestInstance; - if (guest.hostWebContents !== event.sender) { + if (guest.hostWebContents !== embedder) { throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`); } @@ -161,7 +163,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn ? parseWebViewWebPreferences(params.webpreferences) : null; - const webPreferences = { + const webPreferences: Electron.WebPreferences = { guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false, @@ -194,8 +196,8 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn // Inherit certain option values from embedder const lastWebPreferences = embedder.getLastWebPreferences(); for (const [name, value] of inheritedWebPreferences) { - if (lastWebPreferences[name] === value) { - webPreferences[name] = value; + if ((lastWebPreferences as any)[name] === value) { + (webPreferences as any)[name] = value; } } @@ -220,7 +222,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn }; // Remove an guest-embedder relationship. -const detachGuest = function (embedder, guestInstanceId) { +const detachGuest = function (embedder: Electron.WebContents, guestInstanceId: number) { const guestInstance = guestInstances[guestInstanceId]; if (!guestInstance) return; @@ -238,15 +240,15 @@ const detachGuest = function (embedder, guestInstanceId) { // Once an embedder has had a guest attached we watch it for destruction to // destroy any remaining guests. -const watchedEmbedders = new Set(); -const watchEmbedder = function (embedder) { +const watchedEmbedders = new Set(); +const watchEmbedder = function (embedder: Electron.WebContents) { if (watchedEmbedders.has(embedder)) { return; } watchedEmbedders.add(embedder); // Forward embedder window visiblity change events to guest - const onVisibilityChange = function (visibilityState) { + const onVisibilityChange = function (visibilityState: VisibilityState) { for (const guestInstanceId of Object.keys(guestInstances)) { const guestInstance = guestInstances[guestInstanceId]; guestInstance.visibilityState = visibilityState; @@ -255,9 +257,9 @@ const watchEmbedder = function (embedder) { } } }; - embedder.on('-window-visibility-change', onVisibilityChange); + embedder.on('-window-visibility-change' as any, onVisibilityChange); - embedder.once('will-destroy', () => { + embedder.once('will-destroy' as any, () => { // Usually the guestInstances is cleared when guest is destroyed, but it // may happen that the embedder gets manually destroyed earlier than guest, // and the embedder will be invalid in the usual code path. @@ -268,14 +270,14 @@ const watchEmbedder = function (embedder) { } } // Clear the listeners. - embedder.removeListener('-window-visibility-change', onVisibilityChange); + embedder.removeListener('-window-visibility-change' as any, onVisibilityChange); watchedEmbedders.delete(embedder); }); }; const isWebViewTagEnabledCache = new WeakMap(); -const isWebViewTagEnabled = function (contents) { +export const isWebViewTagEnabled = function (contents: Electron.WebContents) { if (!isWebViewTagEnabledCache.has(contents)) { const webPreferences = contents.getLastWebPreferences() || {}; isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag); @@ -284,8 +286,8 @@ const isWebViewTagEnabled = function (contents) { return isWebViewTagEnabledCache.get(contents); }; -const makeSafeHandler = function (channel, handler) { - return (event, ...args) => { +const makeSafeHandler = function (channel: string, handler: (event: Event, ...args: any[]) => any) { + return (event: Event, ...args: any[]) => { if (isWebViewTagEnabled(event.sender)) { return handler(event, ...args); } else { @@ -295,11 +297,11 @@ const makeSafeHandler = function (channel, handler) { }; }; -const handleMessage = function (channel, handler) { +const handleMessage = function (channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) { ipcMainInternal.handle(channel, makeSafeHandler(channel, handler)); }; -const handleMessageSync = function (channel, handler) { +const handleMessageSync = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any) { ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler)); }; @@ -307,7 +309,7 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, param return createGuest(event.sender, params); }); -handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params) { try { attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params); } catch (error) { @@ -315,12 +317,12 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embed } }); -handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_DETACH_GUEST', function (event, guestInstanceId) { +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_DETACH_GUEST', function (event, guestInstanceId: number) { return detachGuest(event.sender, guestInstanceId); }); // this message is sent by the actual -ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) { +ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event: ElectronInternal.IpcMainInternalEvent, focus: boolean, guestInstanceId: number) { const guest = getGuest(guestInstanceId); if (guest === event.sender) { event.sender.emit('focus-change', {}, focus, guestInstanceId); @@ -329,50 +331,50 @@ ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, } }); -handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId: number, method: string, args: any[]) { const guest = getGuestForWebContents(guestInstanceId, event.sender); if (!asyncMethods.has(method)) { throw new Error(`Invalid method: ${method}`); } - return guest[method](...args); + return (guest as any)[method](...args); }); -handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId: number, method: string, args: any[]) { const guest = getGuestForWebContents(guestInstanceId, event.sender); if (!syncMethods.has(method)) { throw new Error(`Invalid method: ${method}`); } - return guest[method](...args); + return (guest as any)[method](...args); }); -handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId, property) { +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId: number, property: string) { const guest = getGuestForWebContents(guestInstanceId, event.sender); if (!properties.has(property)) { throw new Error(`Invalid property: ${property}`); } - return guest[property]; + return (guest as any)[property]; }); -handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId, property, val) { +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId: number, property: string, val: any) { const guest = getGuestForWebContents(guestInstanceId, event.sender); if (!properties.has(property)) { throw new Error(`Invalid property: ${property}`); } - guest[property] = val; + (guest as any)[property] = val; }); -handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) { +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId: number, args: any[]) { const guest = getGuestForWebContents(guestInstanceId, event.sender); return serialize(await guest.capturePage(...args)); }); // Returns WebContents from its guest id hosted in given webContents. -const getGuestForWebContents = function (guestInstanceId, contents) { +const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) { const guest = getGuest(guestInstanceId); if (!guest) { throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`); @@ -384,9 +386,7 @@ const getGuestForWebContents = function (guestInstanceId, contents) { }; // Returns WebContents from its guest id. -const getGuest = function (guestInstanceId) { +const getGuest = function (guestInstanceId: number) { const guestInstance = guestInstances[guestInstanceId]; if (guestInstance != null) return guestInstance.guest; }; - -exports.isWebViewTagEnabled = isWebViewTagEnabled; diff --git a/lib/browser/guest-window-manager.ts b/lib/browser/guest-window-manager.ts index ad13efc2c3c5..38225a3d5757 100644 --- a/lib/browser/guest-window-manager.ts +++ b/lib/browser/guest-window-manager.ts @@ -266,7 +266,7 @@ export function internalWindowOpen (event: ElectronInternal.IpcMainInternalEvent } } -const makeSafeHandler = function (handler: (event: Event, guestContents: Electron.webContents, ...args: any[]) => T) { +const makeSafeHandler = function (handler: (event: Event, guestContents: Electron.webContents, ...args: any[]) => any) { return (event: Event, guestId: number, ...args: any[]) => { // Access webContents via electron to prevent circular require. const guestContents = electron.webContents.fromId(guestId); diff --git a/lib/browser/init.ts b/lib/browser/init.ts index 20bfc9dd878c..d04e0f480ab7 100644 --- a/lib/browser/init.ts +++ b/lib/browser/init.ts @@ -142,6 +142,9 @@ if (BUILDFLAG(ENABLE_REMOTE_MODULE)) { // Load protocol module to ensure it is populated on app ready require('@electron/internal/browser/api/protocol'); +// Load web-contents module to ensure it is populated on app ready +require('@electron/internal/browser/api/web-contents'); + // Set main startup script of the app. const mainStartupScript = packageJson.main || 'index.js'; diff --git a/lib/browser/remote/objects-registry.ts b/lib/browser/remote/objects-registry.ts index 365936b92411..5bf2d976fe24 100644 --- a/lib/browser/remote/objects-registry.ts +++ b/lib/browser/remote/objects-registry.ts @@ -1,5 +1,3 @@ -'use strict'; - import { WebContents } from 'electron/main'; const v8Util = process._linkedBinding('electron_common_v8_util'); diff --git a/lib/isolated_renderer/init.ts b/lib/isolated_renderer/init.ts index c816d9fc5791..9a2b4fcc09b3 100644 --- a/lib/isolated_renderer/init.ts +++ b/lib/isolated_renderer/init.ts @@ -1,5 +1,3 @@ -'use strict'; - /* global nodeProcess, isolatedWorld */ process._linkedBinding = nodeProcess._linkedBinding; diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index d443963f9e42..0cc17c926abf 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -97,6 +97,11 @@ declare namespace NodeJS { setListeningForShutdown(listening: boolean): void; } + interface WebViewManagerBinding { + addGuest(guestInstanceId: number, elementInstanceId: number, embedder: Electron.WebContents, guest: Electron.WebContents, webPreferences: Electron.WebPreferences): void; + removeGuest(embedder: Electron.WebContents, guestInstanceId: number): void; + } + type DataPipe = { write: (buf: Uint8Array) => Promise; done: () => void; @@ -202,6 +207,7 @@ declare namespace NodeJS { _linkedBinding(name: 'electron_browser_tray'): { Tray: Electron.Tray }; _linkedBinding(name: 'electron_browser_view'): { View: Electron.View }; _linkedBinding(name: 'electron_browser_web_contents_view'): { WebContentsView: typeof Electron.WebContentsView }; + _linkedBinding(name: 'electron_browser_web_view_manager'): WebViewManagerBinding; _linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter; _linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding }; log: NodeJS.WriteStream['write']; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 13f07fd650aa..38d0e739419e 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -1,6 +1,6 @@ /// - /** +/** * This file augments the Electron TS namespace with the internal APIs * that are not documented but are used by Electron internally */ @@ -77,6 +77,13 @@ declare namespace Electron { canGoToIndex(index: number): boolean; getActiveIndex(): number; length(): number; + destroy(): void; + // + attachToIframe(embedderWebContents: Electron.WebContents, embedderFrameId: number): void; + detachFromOuterFrame(): void; + setEmbedder(embedder: Electron.WebContents): void; + attachParams?: Record; + viewInstanceId: number; } interface WebFrame { @@ -87,7 +94,10 @@ declare namespace Electron { interface WebPreferences { guestInstanceId?: number; openerId?: number; - disablePopups?: boolean + disablePopups?: boolean; + preloadURL?: string; + embedder?: Electron.WebContents; + type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen'; } interface Menu { @@ -266,6 +276,10 @@ declare namespace ElectronInternal { public getWebContentsId(): number; public capturePage(rect?: Electron.Rectangle): Promise; } + + class WebContents extends Electron.WebContents { + static create(opts: Electron.WebPreferences): Electron.WebContents; + } } declare namespace Chrome {