refactor: implement <webview> using contextBridge (#29037)

* refactor: implement <webview> using contextBridge

* chore: address PR feedback

* chore: address PR feedback

* fix: check for HTMLIFrameElement instance in attachGuest
This commit is contained in:
Milan Burda 2021-05-15 09:42:07 +02:00 committed by GitHub
parent 5e6f8349ec
commit c68c65f383
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 220 additions and 214 deletions

View file

@ -167,9 +167,13 @@ auto_filenames = {
] ]
isolated_bundle_deps = [ isolated_bundle_deps = [
"lib/common/type-utils.ts",
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts", "lib/isolated_renderer/init.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts", "lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts", "lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"package.json", "package.json",
"tsconfig.electron.json", "tsconfig.electron.json",
"tsconfig.json", "tsconfig.json",

View file

@ -1,4 +1,4 @@
import { app, ipcMain, session, deprecate, webFrameMain } from 'electron/main'; import { app, ipcMain, session, webFrameMain } from 'electron/main';
import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron/main'; import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron/main';
import * as url from 'url'; import * as url from 'url';
@ -709,11 +709,6 @@ WebContents.prototype._init = function () {
} }
}); });
}); });
const prefs = this.getWebPreferences() || {};
if (prefs.webviewTag && prefs.contextIsolation) {
deprecate.log('Security Warning: A WebContents was just created with both webviewTag and contextIsolation enabled. This combination is fundamentally less secure and effectively bypasses the protections of contextIsolation. We strongly recommend you move away from webviews to OOPIF or BrowserView in order for your app to be more secure');
}
} }
this.on('login', (event, ...args) => { this.on('login', (event, ...args) => {

View file

@ -1,4 +1,6 @@
const { nativeImage } = process._linkedBinding('electron_common_native_image'); function getCreateNativeImage () {
return process._linkedBinding('electron_common_native_image').nativeImage.createEmpty;
}
export function isPromise (val: any) { export function isPromise (val: any) {
return ( return (
@ -57,8 +59,8 @@ function serializeNativeImage (image: Electron.NativeImage) {
return { __ELECTRON_SERIALIZED_NativeImage__: true, representations }; return { __ELECTRON_SERIALIZED_NativeImage__: true, representations };
} }
function deserializeNativeImage (value: any) { function deserializeNativeImage (value: any, createNativeImage: typeof Electron.nativeImage['createEmpty']) {
const image = nativeImage.createEmpty(); const image = createNativeImage();
// Use Buffer when there's only one representation for better perf. // Use Buffer when there's only one representation for better perf.
// This avoids compressing to/from PNG where it's not necessary to // This avoids compressing to/from PNG where it's not necessary to
@ -93,15 +95,15 @@ export function serialize (value: any): any {
} }
} }
export function deserialize (value: any): any { export function deserialize (value: any, createNativeImage: typeof Electron.nativeImage['createEmpty'] = getCreateNativeImage()): any {
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) { if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
return deserializeNativeImage(value); return deserializeNativeImage(value, createNativeImage);
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
return value.map(deserialize); return value.map(value => deserialize(value, createNativeImage));
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
return value; return value;
} else if (value instanceof Object) { } else if (value instanceof Object) {
return objectMap(value, deserialize); return objectMap(value, value => deserialize(value, createNativeImage));
} else { } else {
return value; return value;
} }

View file

@ -1,15 +1,9 @@
/* global nodeProcess, isolatedWorld */ /* global isolatedApi */
import type * as webViewElementModule from '@electron/internal/renderer/web-view/web-view-element'; import type * as webViewElementModule from '@electron/internal/renderer/web-view/web-view-element';
process._linkedBinding = nodeProcess._linkedBinding; if (isolatedApi.guestViewInternal) {
const v8Util = process._linkedBinding('electron_common_v8_util');
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl');
if (webViewImpl) {
// Must setup the WebView element in main world. // Must setup the WebView element in main world.
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule; const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
setupWebView(webViewImpl as any); setupWebView(isolatedApi);
} }

View file

@ -1,20 +1,22 @@
import { webFrame } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'; import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import { webViewEvents } from '@electron/internal/common/web-view-events'; import { webViewEvents } from '@electron/internal/common/web-view-events';
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
export interface GuestViewDelegate {
dispatchEvent (eventName: string, props: Record<string, any>): void;
reset(): void;
}
const DEPRECATED_EVENTS: Record<string, string> = { const DEPRECATED_EVENTS: Record<string, string> = {
'page-title-updated': 'page-title-set' 'page-title-updated': 'page-title-set'
} as const; } as const;
const dispatchEvent = function ( const dispatchEvent = function (delegate: GuestViewDelegate, eventName: string, eventKey: string, ...args: Array<any>) {
webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array<any>
) {
if (DEPRECATED_EVENTS[eventName] != null) { if (DEPRECATED_EVENTS[eventName] != null) {
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args); dispatchEvent(delegate, DEPRECATED_EVENTS[eventName], eventKey, ...args);
} }
const props: Record<string, any> = {}; const props: Record<string, any> = {};
@ -22,28 +24,21 @@ const dispatchEvent = function (
props[prop] = args[index]; props[prop] = args[index];
}); });
webView.dispatchEvent(eventName, props); delegate.dispatchEvent(eventName, props);
if (eventName === 'load-commit') {
webView.onLoadCommit(props);
} else if (eventName === '-focus-change') {
webView.onFocusChange();
}
}; };
export function registerEvents (webView: WebViewImpl, viewInstanceId: number) { export function registerEvents (viewInstanceId: number, delegate: GuestViewDelegate) {
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`, function () { ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`, function () {
webView.guestInstanceId = undefined; delegate.reset();
webView.reset(); delegate.dispatchEvent('destroyed', {});
webView.dispatchEvent('destroyed');
}); });
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, ...args) { ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, ...args) {
dispatchEvent(webView, eventName, eventName, ...args); dispatchEvent(delegate, eventName, eventName, ...args);
}); });
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE}-${viewInstanceId}`, function (event, channel, ...args) { ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE}-${viewInstanceId}`, function (event, channel, ...args) {
webView.dispatchEvent('ipc-message', { channel, args }); delegate.dispatchEvent('ipc-message', { channel, args });
}); });
} }
@ -57,16 +52,39 @@ export function createGuest (params: Record<string, any>): Promise<number> {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_GUEST, params); return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_GUEST, params);
} }
export function attachGuest ( export function attachGuest (iframe: HTMLIFrameElement, elementInstanceId: number, guestInstanceId: number, params: Record<string, any>) {
elementInstanceId: number, guestInstanceId: number, params: Record<string, any>, contentWindow: Window if (!(iframe instanceof HTMLIFrameElement)) {
) { throw new Error('Invalid embedder frame');
const embedderFrameId = webFrame.getWebFrameId(contentWindow); }
const embedderFrameId = webFrame.getWebFrameId(iframe.contentWindow!);
if (embedderFrameId < 0) { // this error should not happen. if (embedderFrameId < 0) { // this error should not happen.
throw new Error('Invalid embedder frame'); throw new Error('Invalid embedder frame');
} }
ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_ATTACH_GUEST, embedderFrameId, elementInstanceId, guestInstanceId, params);
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_ATTACH_GUEST, embedderFrameId, elementInstanceId, guestInstanceId, params);
} }
export function detachGuest (guestInstanceId: number) { export function detachGuest (guestInstanceId: number) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, guestInstanceId); return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, guestInstanceId);
} }
export function capturePage (guestInstanceId: number, args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CAPTURE_PAGE, guestInstanceId, args);
}
export function invoke (guestInstanceId: number, method: string, args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
}
export function invokeSync (guestInstanceId: number, method: string, args: any[]) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
}
export function propertyGet (guestInstanceId: number, name: string) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, guestInstanceId, name);
}
export function propertySet (guestInstanceId: number, name: string, value: any) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, guestInstanceId, name, value);
}

View file

@ -9,14 +9,13 @@
// modules must be passed from outside, all included files must be plain JS. // modules must be passed from outside, all included files must be plain JS.
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'; import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants';
import type * as webViewImplModule from '@electron/internal/renderer/web-view/web-view-impl'; import { WebViewImpl, WebViewImplHooks, setupMethods } from '@electron/internal/renderer/web-view/web-view-impl';
import type { SrcAttribute } from '@electron/internal/renderer/web-view/web-view-attributes'; import type { SrcAttribute } from '@electron/internal/renderer/web-view/web-view-attributes';
const internals = new WeakMap<HTMLElement, webViewImplModule.WebViewImpl>(); const internals = new WeakMap<HTMLElement, WebViewImpl>();
// Return a WebViewElement class that is defined in this context. // Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => { const defineWebViewElement = (hooks: WebViewImplHooks) => {
const { guestViewInternal, WebViewImpl } = webViewImpl;
return class WebViewElement extends HTMLElement { return class WebViewElement extends HTMLElement {
static get observedAttributes () { static get observedAttributes () {
return [ return [
@ -38,13 +37,7 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
constructor () { constructor () {
super(); super();
const internal = new WebViewImpl(this); internals.set(this, new WebViewImpl(this, hooks));
internal.dispatchEventInMainWorld = (eventName, props) => {
const event = new Event(eventName);
Object.assign(event, props);
return internal.webviewNode.dispatchEvent(event);
};
internals.set(this, internal);
} }
getWebContentsId () { getWebContentsId () {
@ -61,7 +54,10 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
return; return;
} }
if (!internal.elementAttached) { if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId); hooks.guestViewInternal.registerEvents(internal.viewInstanceId, {
dispatchEvent: internal.dispatchEvent.bind(internal),
reset: internal.reset.bind(internal)
});
internal.elementAttached = true; internal.elementAttached = true;
(internal.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse(); (internal.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse();
} }
@ -79,9 +75,9 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
if (!internal) { if (!internal) {
return; return;
} }
guestViewInternal.deregisterEvents(internal.viewInstanceId); hooks.guestViewInternal.deregisterEvents(internal.viewInstanceId);
if (internal.guestInstanceId) { if (internal.guestInstanceId) {
guestViewInternal.detachGuest(internal.guestInstanceId); hooks.guestViewInternal.detachGuest(internal.guestInstanceId);
} }
internal.elementAttached = false; internal.elementAttached = false;
internal.reset(); internal.reset();
@ -90,15 +86,15 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
}; };
// Register <webview> custom element. // Register <webview> custom element.
const registerWebViewElement = (webViewImpl: typeof webViewImplModule) => { const registerWebViewElement = (hooks: WebViewImplHooks) => {
// I wish eslint wasn't so stupid, but it is // I wish eslint wasn't so stupid, but it is
// eslint-disable-next-line // eslint-disable-next-line
const WebViewElement = defineWebViewElement(webViewImpl) as unknown as typeof ElectronInternal.WebViewElement const WebViewElement = defineWebViewElement(hooks) as unknown as typeof ElectronInternal.WebViewElement
webViewImpl.setupMethods(WebViewElement); setupMethods(WebViewElement, hooks);
// The customElements.define has to be called in a special scope. // The customElements.define has to be called in a special scope.
webViewImpl.webFrame.allowGuestViewElementDefinition(window, () => { hooks.allowGuestViewElementDefinition(window, () => {
window.customElements.define('webview', WebViewElement); window.customElements.define('webview', WebViewElement);
window.WebView = WebViewElement; window.WebView = WebViewElement;
@ -116,14 +112,14 @@ const registerWebViewElement = (webViewImpl: typeof webViewImplModule) => {
}; };
// Prepare to register the <webview> element. // Prepare to register the <webview> element.
export const setupWebView = (webViewImpl: typeof webViewImplModule) => { export const setupWebView = (hooks: WebViewImplHooks) => {
const useCapture = true; const useCapture = true;
const listener = (event: Event) => { const listener = (event: Event) => {
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
return; return;
} }
registerWebViewElement(webViewImpl); registerWebViewElement(hooks);
window.removeEventListener(event.type, listener, useCapture); window.removeEventListener(event.type, listener, useCapture);
}; };

View file

@ -1,17 +1,9 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; import type * as guestViewInternalModule from '@electron/internal/renderer/web-view/guest-view-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal';
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'; import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants';
import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods'; import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods';
import type { WebViewAttribute, PartitionAttribute } from '@electron/internal/renderer/web-view/web-view-attributes'; import type { WebViewAttribute, PartitionAttribute } from '@electron/internal/renderer/web-view/web-view-attributes';
import { setupWebViewAttributes } from '@electron/internal/renderer/web-view/web-view-attributes'; import { setupWebViewAttributes } from '@electron/internal/renderer/web-view/web-view-attributes';
import { deserialize } from '@electron/internal/common/type-utils'; import { deserialize } from '@electron/internal/common/type-utils';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
export { webFrame } from 'electron';
export * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal';
const v8Util = process._linkedBinding('electron_common_v8_util');
// ID generator. // ID generator.
let nextId = 0; let nextId = 0;
@ -20,6 +12,13 @@ const getNextId = function () {
return ++nextId; return ++nextId;
}; };
export interface WebViewImplHooks {
readonly guestViewInternal: typeof guestViewInternalModule;
readonly allowGuestViewElementDefinition: NodeJS.InternalWebFrame['allowGuestViewElementDefinition'];
readonly setIsWebView: (iframe: HTMLIFrameElement) => void;
readonly createNativeImage?: typeof Electron.nativeImage['createEmpty'];
}
// Represents the internal state of the WebView node. // Represents the internal state of the WebView node.
export class WebViewImpl { export class WebViewImpl {
public beforeFirstNavigation = true public beforeFirstNavigation = true
@ -37,9 +36,7 @@ export class WebViewImpl {
public attributes: Map<string, WebViewAttribute>; public attributes: Map<string, WebViewAttribute>;
public dispatchEventInMainWorld?: (eventName: string, props: any) => boolean; constructor (public webviewNode: HTMLElement, private hooks: WebViewImplHooks) {
constructor (public webviewNode: HTMLElement) {
// Create internal iframe element. // Create internal iframe element.
this.internalElement = this.createInternalElement(); this.internalElement = this.createInternalElement();
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' }); const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
@ -65,7 +62,7 @@ export class WebViewImpl {
iframeElement.style.width = '100%'; iframeElement.style.width = '100%';
iframeElement.style.border = '0'; iframeElement.style.border = '0';
// used by RendererClientBase::IsWebViewFrame // used by RendererClientBase::IsWebViewFrame
v8Util.setHiddenValue(iframeElement, 'internal', this); this.hooks.setIsWebView(iframeElement);
return iframeElement; return iframeElement;
} }
@ -118,13 +115,21 @@ export class WebViewImpl {
} }
createGuest () { createGuest () {
guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => { this.hooks.guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => {
this.attachGuestInstance(guestInstanceId); this.attachGuestInstance(guestInstanceId);
}); });
} }
dispatchEvent (eventName: string, props: Record<string, any> = {}) { dispatchEvent (eventName: string, props: Record<string, any> = {}) {
this.dispatchEventInMainWorld!(eventName, props); const event = new Event(eventName);
Object.assign(event, props);
this.webviewNode.dispatchEvent(event);
if (eventName === 'load-commit') {
this.onLoadCommit(props);
} else if (eventName === '-focus-change') {
this.onFocusChange();
}
} }
// Adds an 'on<event>' property on the webview, which can be used to set/unset // Adds an 'on<event>' property on the webview, which can be used to set/unset
@ -194,11 +199,11 @@ export class WebViewImpl {
this.internalInstanceId = getNextId(); this.internalInstanceId = getNextId();
this.guestInstanceId = guestInstanceId; this.guestInstanceId = guestInstanceId;
guestViewInternal.attachGuest( this.hooks.guestViewInternal.attachGuest(
this.internalElement,
this.internalInstanceId, this.internalInstanceId,
this.guestInstanceId, this.guestInstanceId,
this.buildParams(), this.buildParams()
this.internalElement.contentWindow!
); );
// TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not // TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not
@ -210,46 +215,38 @@ export class WebViewImpl {
// I wish eslint wasn't so stupid, but it is // I wish eslint wasn't so stupid, but it is
// eslint-disable-next-line // eslint-disable-next-line
export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElement) => { export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElement, hooks: WebViewImplHooks) => {
// Focusing the webview should move page focus to the underlying iframe. // Focusing the webview should move page focus to the underlying iframe.
WebViewElement.prototype.focus = function () { WebViewElement.prototype.focus = function () {
this.contentWindow.focus(); this.contentWindow.focus();
}; };
// Forward proto.foo* method calls to WebViewImpl.foo*. // Forward proto.foo* method calls to WebViewImpl.foo*.
const createBlockHandler = function (method: string) {
return function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, this.getWebContentsId(), method, args);
};
};
for (const method of syncMethods) { for (const method of syncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = createBlockHandler(method); (WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return hooks.guestViewInternal.invokeSync(this.getWebContentsId(), method, args);
};
} }
const createNonBlockHandler = function (method: string) {
return function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, this.getWebContentsId(), method, args);
};
};
for (const method of asyncMethods) { for (const method of asyncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method); (WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return hooks.guestViewInternal.invoke(this.getWebContentsId(), method, args);
};
} }
WebViewElement.prototype.capturePage = async function (...args) { WebViewElement.prototype.capturePage = async function (...args) {
return deserialize(await ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CAPTURE_PAGE, this.getWebContentsId(), args)); return deserialize(await hooks.guestViewInternal.capturePage(this.getWebContentsId(), args), hooks.createNativeImage);
}; };
const createPropertyGetter = function (property: string) { const createPropertyGetter = function (property: string) {
return function (this: ElectronInternal.WebViewElement) { return function (this: ElectronInternal.WebViewElement) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, this.getWebContentsId(), property); return hooks.guestViewInternal.propertyGet(this.getWebContentsId(), property);
}; };
}; };
const createPropertySetter = function (property: string) { const createPropertySetter = function (property: string) {
return function (this: ElectronInternal.WebViewElement, arg: any) { return function (this: ElectronInternal.WebViewElement, arg: any) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, this.getWebContentsId(), property, arg); return hooks.guestViewInternal.propertySet(this.getWebContentsId(), property, arg);
}; };
}; };

View file

@ -1,10 +1,11 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as webViewImpl from '@electron/internal/renderer/web-view/web-view-impl'; import type * as webViewElementModule from '@electron/internal/renderer/web-view/web-view-element';
import type * as webViewElement from '@electron/internal/renderer/web-view/web-view-element'; import type * as guestViewInternalModule from '@electron/internal/renderer/web-view/guest-view-internal';
const v8Util = process._linkedBinding('electron_common_v8_util'); const v8Util = process._linkedBinding('electron_common_v8_util');
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
function handleFocusBlur () { function handleFocusBlur () {
// Note that while Chromium content APIs have observer for focus/blur, they // Note that while Chromium content APIs have observer for focus/blur, they
@ -22,12 +23,16 @@ function handleFocusBlur () {
export function webViewInit (contextIsolation: boolean, webviewTag: boolean, guestInstanceId: number) { export function webViewInit (contextIsolation: boolean, webviewTag: boolean, guestInstanceId: number) {
// Don't allow recursive `<webview>`. // Don't allow recursive `<webview>`.
if (webviewTag && !guestInstanceId) { if (webviewTag && !guestInstanceId) {
const webViewImplModule = require('@electron/internal/renderer/web-view/web-view-impl') as typeof webViewImpl; const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
if (contextIsolation) { if (contextIsolation) {
v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule); v8Util.setHiddenValue(window, 'guestViewInternal', guestViewInternal);
} else { } else {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElement; const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
setupWebView(webViewImplModule); setupWebView({
guestViewInternal,
allowGuestViewElementDefinition: webFrame.allowGuestViewElementDefinition,
setIsWebView: iframe => v8Util.setHiddenValue(iframe, 'isWebView', true)
});
} }
} }

View file

@ -130,18 +130,6 @@ bool SpellCheckWord(content::RenderFrame* render_frame,
#endif #endif
class RenderFrameStatus final : public content::RenderFrameObserver {
public:
explicit RenderFrameStatus(content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame) {}
~RenderFrameStatus() final = default;
bool is_ok() { return render_frame() != nullptr; }
// RenderFrameObserver implementation.
void OnDestruct() final {}
};
class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
public: public:
// for compatibility with the older version of this, error is after result // for compatibility with the older version of this, error is after result
@ -377,7 +365,7 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
.SetMethod("setVisualZoomLevelLimits", .SetMethod("setVisualZoomLevelLimits",
&WebFrameRenderer::SetVisualZoomLevelLimits) &WebFrameRenderer::SetVisualZoomLevelLimits)
.SetMethod("allowGuestViewElementDefinition", .SetMethod("allowGuestViewElementDefinition",
&WebFrameRenderer::AllowGuestViewElementDefinition) &RendererClientBase::AllowGuestViewElementDefinition)
.SetMethod("insertText", &WebFrameRenderer::InsertText) .SetMethod("insertText", &WebFrameRenderer::InsertText)
.SetMethod("insertCSS", &WebFrameRenderer::InsertCSS) .SetMethod("insertCSS", &WebFrameRenderer::InsertCSS)
.SetMethod("removeInsertedCSS", &WebFrameRenderer::RemoveInsertedCSS) .SetMethod("removeInsertedCSS", &WebFrameRenderer::RemoveInsertedCSS)
@ -541,31 +529,12 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level); web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level);
} }
void AllowGuestViewElementDefinition(v8::Isolate* isolate,
v8::Local<v8::Object> context,
v8::Local<v8::Function> register_cb) {
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context->CreationContext());
blink::WebCustomElement::EmbedderNamesAllowedScope embedder_names_scope;
content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "allowGuestViewElementDefinition",
&render_frame))
return;
render_frame->GetWebFrame()->RequestExecuteV8Function(
context->CreationContext(), register_cb, v8::Null(isolate), 0, nullptr,
nullptr);
}
static int GetWebFrameId(v8::Local<v8::Object> content_window) { static int GetWebFrameId(v8::Local<v8::Object> content_window) {
// Get the WebLocalFrame before (possibly) executing any user-space JS while // Get the WebLocalFrame before (possibly) executing any user-space JS while
// getting the |params|. We track the status of the RenderFrame via an // getting the |params|. We track the status of the RenderFrame via an
// observer in case it is deleted during user code execution. // observer in case it is deleted during user code execution.
content::RenderFrame* render_frame = GetRenderFrame(content_window); content::RenderFrame* render_frame = GetRenderFrame(content_window);
RenderFrameStatus render_frame_status(render_frame); if (!render_frame)
if (!render_frame_status.is_ok())
return -1; return -1;
blink::WebLocalFrame* frame = render_frame->GetWebFrame(); blink::WebLocalFrame* frame = render_frame->GetWebFrame();

View file

@ -16,7 +16,6 @@
#include "shell/common/gin_helper/event_emitter_caller.h" #include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_bindings.h" #include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h" #include "shell/common/options_switches.h"
#include "shell/renderer/electron_render_frame_observer.h" #include "shell/renderer/electron_render_frame_observer.h"
#include "shell/renderer/web_worker_observer.h" #include "shell/renderer/web_worker_observer.h"
@ -198,32 +197,6 @@ void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread(
} }
} }
void ElectronRendererClient::SetupMainWorldOverrides(
v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) {
auto prefs = render_frame->GetBlinkPreferences();
// We only need to run the isolated bundle if webview is enabled
if (!prefs.webview_tag)
return;
// Setup window overrides in the main world context
// Wrap the bundle into a function that receives the isolatedWorld as
// an argument.
auto* isolate = context->GetIsolate();
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"),
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld")};
auto* env = GetEnvironment(render_frame);
DCHECK(env);
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
env->process_object(),
GetContext(render_frame->GetWebFrame(), isolate)->Global()};
util::CompileAndCall(context, "electron/js2c/isolated_bundle",
&isolated_bundle_params, &isolated_bundle_args, nullptr);
}
node::Environment* ElectronRendererClient::GetEnvironment( node::Environment* ElectronRendererClient::GetEnvironment(
content::RenderFrame* render_frame) const { content::RenderFrame* render_frame) const {
if (injected_frames_.find(render_frame) == injected_frames_.end()) if (injected_frames_.find(render_frame) == injected_frames_.end())

View file

@ -31,8 +31,6 @@ class ElectronRendererClient : public RendererClientBase {
content::RenderFrame* render_frame) override; content::RenderFrame* render_frame) override;
void WillReleaseScriptContext(v8::Handle<v8::Context> context, void WillReleaseScriptContext(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) override; content::RenderFrame* render_frame) override;
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) override;
private: private:
// content::ContentRendererClient: // content::ContentRendererClient:

View file

@ -241,34 +241,6 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext(
InvokeHiddenCallback(context, kLifecycleKey, "onLoaded"); InvokeHiddenCallback(context, kLifecycleKey, "onLoaded");
} }
void ElectronSandboxedRendererClient::SetupMainWorldOverrides(
v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) {
auto prefs = render_frame->GetBlinkPreferences();
// We only need to run the isolated bundle if webview is enabled
if (!prefs.webview_tag)
return;
// Setup window overrides in the main world context
// Wrap the bundle into a function that receives the isolatedWorld as
// an argument.
auto* isolate = context->GetIsolate();
gin_helper::Dictionary process = gin::Dictionary::CreateEmpty(isolate);
process.SetMethod("_linkedBinding", GetBinding);
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"),
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld")};
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
process.GetHandle(),
GetContext(render_frame->GetWebFrame(), isolate)->Global()};
util::CompileAndCall(context, "electron/js2c/isolated_bundle",
&isolated_bundle_params, &isolated_bundle_args, nullptr);
}
void ElectronSandboxedRendererClient::WillReleaseScriptContext( void ElectronSandboxedRendererClient::WillReleaseScriptContext(
v8::Handle<v8::Context> context, v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) { content::RenderFrame* render_frame) {

View file

@ -27,8 +27,6 @@ class ElectronSandboxedRendererClient : public RendererClientBase {
content::RenderFrame* render_frame) override; content::RenderFrame* render_frame) override;
void WillReleaseScriptContext(v8::Handle<v8::Context> context, void WillReleaseScriptContext(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) override; content::RenderFrame* render_frame) override;
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) override;
// content::ContentRendererClient: // content::ContentRendererClient:
void RenderFrameCreated(content::RenderFrame*) override; void RenderFrameCreated(content::RenderFrame*) override;
void RenderViewCreated(content::RenderView*) override; void RenderViewCreated(content::RenderView*) override;

View file

@ -22,10 +22,15 @@
#include "electron/buildflags/buildflags.h" #include "electron/buildflags/buildflags.h"
#include "media/blink/multibuffer_data_source.h" #include "media/blink/multibuffer_data_source.h"
#include "printing/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h"
#include "shell/common/api/electron_api_native_image.h"
#include "shell/common/color_util.h" #include "shell/common/color_util.h"
#include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/dictionary.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h" #include "shell/common/options_switches.h"
#include "shell/common/world_ids.h" #include "shell/common/world_ids.h"
#include "shell/renderer/api/context_bridge/object_cache.h"
#include "shell/renderer/api/electron_api_context_bridge.h"
#include "shell/renderer/browser_exposed_renderer_interfaces.h" #include "shell/renderer/browser_exposed_renderer_interfaces.h"
#include "shell/renderer/content_settings_observer.h" #include "shell/renderer/content_settings_observer.h"
#include "shell/renderer/electron_api_service_impl.h" #include "shell/renderer/electron_api_service_impl.h"
@ -85,6 +90,21 @@ namespace electron {
namespace { namespace {
content::RenderFrame* GetRenderFrame(v8::Local<v8::Object> value) {
v8::Local<v8::Context> context = value->CreationContext();
if (context.IsEmpty())
return nullptr;
blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context);
if (!frame)
return nullptr;
return content::RenderFrame::FromWebFrame(frame);
}
void SetIsWebView(v8::Isolate* isolate, v8::Local<v8::Object> object) {
gin_helper::Dictionary dict(isolate, object);
dict.SetHidden("isWebView", true);
}
std::vector<std::string> ParseSchemesCLISwitch(base::CommandLine* command_line, std::vector<std::string> ParseSchemesCLISwitch(base::CommandLine* command_line,
const char* switch_name) { const char* switch_name) {
std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name); std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name);
@ -496,11 +516,70 @@ bool RendererClientBase::IsWebViewFrame(
gin_helper::Dictionary frame_element_dict(isolate, frame_element); gin_helper::Dictionary frame_element_dict(isolate, frame_element);
v8::Local<v8::Object> internal; bool is_webview = false;
if (!frame_element_dict.GetHidden("internal", &internal)) return frame_element_dict.GetHidden("isWebView", &is_webview) && is_webview;
return false; }
return !internal.IsEmpty(); void RendererClientBase::SetupMainWorldOverrides(
v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) {
auto prefs = render_frame->GetBlinkPreferences();
// We only need to run the isolated bundle if webview is enabled
if (!prefs.webview_tag)
return;
// Setup window overrides in the main world context
// Wrap the bundle into a function that receives the isolatedApi as
// an argument.
auto* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
gin_helper::Dictionary isolated_api = gin::Dictionary::CreateEmpty(isolate);
isolated_api.SetMethod("allowGuestViewElementDefinition",
&AllowGuestViewElementDefinition);
isolated_api.SetMethod("setIsWebView", &SetIsWebView);
isolated_api.SetMethod("createNativeImage", &api::NativeImage::CreateEmpty);
auto source_context = GetContext(render_frame->GetWebFrame(), isolate);
gin_helper::Dictionary global(isolate, source_context->Global());
v8::Local<v8::Value> guest_view_internal;
if (global.GetHidden("guestViewInternal", &guest_view_internal)) {
api::context_bridge::ObjectCache object_cache;
auto result = api::PassValueToOtherContext(
source_context, context, guest_view_internal, &object_cache, false, 0);
if (!result.IsEmpty()) {
isolated_api.Set("guestViewInternal", result.ToLocalChecked());
}
}
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedApi")};
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
isolated_api.GetHandle()};
util::CompileAndCall(context, "electron/js2c/isolated_bundle",
&isolated_bundle_params, &isolated_bundle_args, nullptr);
}
// static
void RendererClientBase::AllowGuestViewElementDefinition(
v8::Isolate* isolate,
v8::Local<v8::Object> context,
v8::Local<v8::Function> register_cb) {
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context->CreationContext());
blink::WebCustomElement::EmbedderNamesAllowedScope embedder_names_scope;
content::RenderFrame* render_frame = GetRenderFrame(context);
if (!render_frame)
return;
render_frame->GetWebFrame()->RequestExecuteV8Function(
context->CreationContext(), register_cb, v8::Null(isolate), 0, nullptr,
nullptr);
} }
} // namespace electron } // namespace electron

View file

@ -74,7 +74,7 @@ class RendererClientBase : public content::ContentRendererClient
content::RenderFrame* render_frame) = 0; content::RenderFrame* render_frame) = 0;
virtual void DidClearWindowObject(content::RenderFrame* render_frame); virtual void DidClearWindowObject(content::RenderFrame* render_frame);
virtual void SetupMainWorldOverrides(v8::Handle<v8::Context> context, virtual void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) = 0; content::RenderFrame* render_frame);
std::unique_ptr<blink::WebPrescientNetworking> CreatePrescientNetworking( std::unique_ptr<blink::WebPrescientNetworking> CreatePrescientNetworking(
content::RenderFrame* render_frame) override; content::RenderFrame* render_frame) override;
@ -86,7 +86,11 @@ class RendererClientBase : public content::ContentRendererClient
static v8::Local<v8::Value> RunScript(v8::Local<v8::Context> context, static v8::Local<v8::Value> RunScript(v8::Local<v8::Context> context,
v8::Local<v8::String> source); v8::Local<v8::String> source);
// v8Util.getHiddenValue(window.frameElement, 'internal') static void AllowGuestViewElementDefinition(
v8::Isolate* isolate,
v8::Local<v8::Object> context,
v8::Local<v8::Function> register_cb);
bool IsWebViewFrame(v8::Handle<v8::Context> context, bool IsWebViewFrame(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) const; content::RenderFrame* render_frame) const;

View file

@ -1,9 +1,14 @@
/* eslint-disable no-var */ /* eslint-disable no-var */
declare var internalBinding: any; declare var internalBinding: any;
declare var nodeProcess: any;
declare var isolatedWorld: any;
declare var binding: { get: (name: string) => any; process: NodeJS.Process; createPreloadScript: (src: string) => Function }; declare var binding: { get: (name: string) => any; process: NodeJS.Process; createPreloadScript: (src: string) => Function };
declare var isolatedApi: {
guestViewInternal: any;
allowGuestViewElementDefinition: NodeJS.InternalWebFrame['allowGuestViewElementDefinition'];
setIsWebView: (iframe: HTMLIFrameElement) => void;
createNativeImage: typeof Electron.nativeImage['createEmpty'];
}
declare const BUILDFLAG: (flag: boolean) => boolean; declare const BUILDFLAG: (flag: boolean) => boolean;
declare const ENABLE_DESKTOP_CAPTURER: boolean; declare const ENABLE_DESKTOP_CAPTURER: boolean;
@ -115,6 +120,8 @@ declare namespace NodeJS {
interface InternalWebFrame extends Electron.WebFrame { interface InternalWebFrame extends Electron.WebFrame {
getWebPreference<K extends keyof InternalWebPreferences>(name: K): InternalWebPreferences[K]; getWebPreference<K extends keyof InternalWebPreferences>(name: K): InternalWebPreferences[K];
getWebFrameId(window: Window): number;
allowGuestViewElementDefinition(context: object, callback: Function): void;
} }
interface WebFrameBinding { interface WebFrameBinding {

View file

@ -91,11 +91,6 @@ declare namespace Electron {
viewInstanceId: number; viewInstanceId: number;
} }
interface WebFrame {
getWebFrameId(window: Window): number;
allowGuestViewElementDefinition(window: Window, context: any): void;
}
interface WebFrameMain { interface WebFrameMain {
_send(internal: boolean, channel: string, args: any): void; _send(internal: boolean, channel: string, args: any): void;
_sendInternal(channel: string, ...args: any[]): void; _sendInternal(channel: string, ...args: any[]): void;