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