chore: convert guest-view-manager.js to TypeScript (#25825)
This commit is contained in:
parent
d78d7b3a55
commit
f827acc3be
8 changed files with 86 additions and 67 deletions
|
@ -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",
|
||||
|
|
|
@ -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<string, GuestInstance> = {};
|
||||
const embedderElementsMap: Record<string, number> = {};
|
||||
|
||||
function sanitizeOptionsForGuest (options) {
|
||||
function sanitizeOptionsForGuest (options: Record<string, any>) {
|
||||
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<string, any>) {
|
||||
// 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<string, any>) {
|
||||
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<Electron.WebContents>();
|
||||
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<Event extends { sender: Electron.WebContents }> (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 <webview>
|
||||
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;
|
|
@ -266,7 +266,7 @@ export function internalWindowOpen (event: ElectronInternal.IpcMainInternalEvent
|
|||
}
|
||||
}
|
||||
|
||||
const makeSafeHandler = function<T, Event> (handler: (event: Event, guestContents: Electron.webContents, ...args: any[]) => T) {
|
||||
const makeSafeHandler = function<Event> (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);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
import { WebContents } from 'electron/main';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
/* global nodeProcess, isolatedWorld */
|
||||
|
||||
process._linkedBinding = nodeProcess._linkedBinding;
|
||||
|
|
6
typings/internal-ambient.d.ts
vendored
6
typings/internal-ambient.d.ts
vendored
|
@ -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<void>;
|
||||
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'];
|
||||
|
|
18
typings/internal-electron.d.ts
vendored
18
typings/internal-electron.d.ts
vendored
|
@ -1,6 +1,6 @@
|
|||
/// <reference path="../electron.d.ts" />
|
||||
|
||||
/**
|
||||
/**
|
||||
* 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;
|
||||
// <webview>
|
||||
attachToIframe(embedderWebContents: Electron.WebContents, embedderFrameId: number): void;
|
||||
detachFromOuterFrame(): void;
|
||||
setEmbedder(embedder: Electron.WebContents): void;
|
||||
attachParams?: Record<string, any>;
|
||||
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<Electron.NativeImage>;
|
||||
}
|
||||
|
||||
class WebContents extends Electron.WebContents {
|
||||
static create(opts: Electron.WebPreferences): Electron.WebContents;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace Chrome {
|
||||
|
|
Loading…
Reference in a new issue