chore: convert guest-view-manager.js to TypeScript (#25825)

This commit is contained in:
Milan Burda 2020-10-13 03:29:08 +02:00 committed by GitHub
parent d78d7b3a55
commit f827acc3be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 67 deletions

View file

@ -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",

View file

@ -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;

View file

@ -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);

View file

@ -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';

View file

@ -1,5 +1,3 @@
'use strict';
import { WebContents } from 'electron/main';
const v8Util = process._linkedBinding('electron_common_v8_util');

View file

@ -1,5 +1,3 @@
'use strict';
/* global nodeProcess, isolatedWorld */
process._linkedBinding = nodeProcess._linkedBinding;

View file

@ -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'];

View file

@ -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 {