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 = [
"lib/common/type-utils.ts",
"lib/common/web-view-methods.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-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"package.json",
"tsconfig.electron.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 * 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) => {

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) {
return (
@ -57,8 +59,8 @@ function serializeNativeImage (image: Electron.NativeImage) {
return { __ELECTRON_SERIALIZED_NativeImage__: true, representations };
}
function deserializeNativeImage (value: any) {
const image = nativeImage.createEmpty();
function deserializeNativeImage (value: any, createNativeImage: typeof Electron.nativeImage['createEmpty']) {
const image = createNativeImage();
// Use Buffer when there's only one representation for better perf.
// 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__) {
return deserializeNativeImage(value);
return deserializeNativeImage(value, createNativeImage);
} else if (Array.isArray(value)) {
return value.map(deserialize);
return value.map(value => deserialize(value, createNativeImage));
} else if (isSerializableObject(value)) {
return value;
} else if (value instanceof Object) {
return objectMap(value, deserialize);
return objectMap(value, value => deserialize(value, createNativeImage));
} else {
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';
process._linkedBinding = nodeProcess._linkedBinding;
const v8Util = process._linkedBinding('electron_common_v8_util');
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl');
if (webViewImpl) {
if (isolatedApi.guestViewInternal) {
// Must setup the WebView element in main world.
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 * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
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';
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> = {
'page-title-updated': 'page-title-set'
} as const;
const dispatchEvent = function (
webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array<any>
) {
const dispatchEvent = function (delegate: GuestViewDelegate, eventName: string, eventKey: string, ...args: Array<any>) {
if (DEPRECATED_EVENTS[eventName] != null) {
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args);
dispatchEvent(delegate, DEPRECATED_EVENTS[eventName], eventKey, ...args);
}
const props: Record<string, any> = {};
@ -22,28 +24,21 @@ const dispatchEvent = function (
props[prop] = args[index];
});
webView.dispatchEvent(eventName, props);
if (eventName === 'load-commit') {
webView.onLoadCommit(props);
} else if (eventName === '-focus-change') {
webView.onFocusChange();
}
delegate.dispatchEvent(eventName, props);
};
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 () {
webView.guestInstanceId = undefined;
webView.reset();
webView.dispatchEvent('destroyed');
delegate.reset();
delegate.dispatchEvent('destroyed', {});
});
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) {
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);
}
export function attachGuest (
elementInstanceId: number, guestInstanceId: number, params: Record<string, any>, contentWindow: Window
) {
const embedderFrameId = webFrame.getWebFrameId(contentWindow);
export function attachGuest (iframe: HTMLIFrameElement, elementInstanceId: number, guestInstanceId: number, params: Record<string, any>) {
if (!(iframe instanceof HTMLIFrameElement)) {
throw new Error('Invalid embedder frame');
}
const embedderFrameId = webFrame.getWebFrameId(iframe.contentWindow!);
if (embedderFrameId < 0) { // this error should not happen.
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) {
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.
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';
const internals = new WeakMap<HTMLElement, webViewImplModule.WebViewImpl>();
const internals = new WeakMap<HTMLElement, WebViewImpl>();
// Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
const { guestViewInternal, WebViewImpl } = webViewImpl;
const defineWebViewElement = (hooks: WebViewImplHooks) => {
return class WebViewElement extends HTMLElement {
static get observedAttributes () {
return [
@ -38,13 +37,7 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
constructor () {
super();
const internal = new WebViewImpl(this);
internal.dispatchEventInMainWorld = (eventName, props) => {
const event = new Event(eventName);
Object.assign(event, props);
return internal.webviewNode.dispatchEvent(event);
};
internals.set(this, internal);
internals.set(this, new WebViewImpl(this, hooks));
}
getWebContentsId () {
@ -61,7 +54,10 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
return;
}
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.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse();
}
@ -79,9 +75,9 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
if (!internal) {
return;
}
guestViewInternal.deregisterEvents(internal.viewInstanceId);
hooks.guestViewInternal.deregisterEvents(internal.viewInstanceId);
if (internal.guestInstanceId) {
guestViewInternal.detachGuest(internal.guestInstanceId);
hooks.guestViewInternal.detachGuest(internal.guestInstanceId);
}
internal.elementAttached = false;
internal.reset();
@ -90,15 +86,15 @@ const defineWebViewElement = (webViewImpl: typeof webViewImplModule) => {
};
// Register <webview> custom element.
const registerWebViewElement = (webViewImpl: typeof webViewImplModule) => {
const registerWebViewElement = (hooks: WebViewImplHooks) => {
// I wish eslint wasn't so stupid, but it is
// 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.
webViewImpl.webFrame.allowGuestViewElementDefinition(window, () => {
hooks.allowGuestViewElementDefinition(window, () => {
window.customElements.define('webview', WebViewElement);
window.WebView = WebViewElement;
@ -116,14 +112,14 @@ const registerWebViewElement = (webViewImpl: typeof webViewImplModule) => {
};
// Prepare to register the <webview> element.
export const setupWebView = (webViewImpl: typeof webViewImplModule) => {
export const setupWebView = (hooks: WebViewImplHooks) => {
const useCapture = true;
const listener = (event: Event) => {
if (document.readyState === 'loading') {
return;
}
registerWebViewElement(webViewImpl);
registerWebViewElement(hooks);
window.removeEventListener(event.type, listener, useCapture);
};

View file

@ -1,17 +1,9 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-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 type * as guestViewInternalModule from '@electron/internal/renderer/web-view/guest-view-internal';
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 type { WebViewAttribute, PartitionAttribute } 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 { 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.
let nextId = 0;
@ -20,6 +12,13 @@ const getNextId = function () {
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.
export class WebViewImpl {
public beforeFirstNavigation = true
@ -37,9 +36,7 @@ export class WebViewImpl {
public attributes: Map<string, WebViewAttribute>;
public dispatchEventInMainWorld?: (eventName: string, props: any) => boolean;
constructor (public webviewNode: HTMLElement) {
constructor (public webviewNode: HTMLElement, private hooks: WebViewImplHooks) {
// Create internal iframe element.
this.internalElement = this.createInternalElement();
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
@ -65,7 +62,7 @@ export class WebViewImpl {
iframeElement.style.width = '100%';
iframeElement.style.border = '0';
// used by RendererClientBase::IsWebViewFrame
v8Util.setHiddenValue(iframeElement, 'internal', this);
this.hooks.setIsWebView(iframeElement);
return iframeElement;
}
@ -118,13 +115,21 @@ export class WebViewImpl {
}
createGuest () {
guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => {
this.hooks.guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => {
this.attachGuestInstance(guestInstanceId);
});
}
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
@ -194,11 +199,11 @@ export class WebViewImpl {
this.internalInstanceId = getNextId();
this.guestInstanceId = guestInstanceId;
guestViewInternal.attachGuest(
this.hooks.guestViewInternal.attachGuest(
this.internalElement,
this.internalInstanceId,
this.guestInstanceId,
this.buildParams(),
this.internalElement.contentWindow!
this.buildParams()
);
// 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
// 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.
WebViewElement.prototype.focus = function () {
this.contentWindow.focus();
};
// 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) {
(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) {
(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) {
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) {
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) {
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 { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as webViewImpl from '@electron/internal/renderer/web-view/web-view-impl';
import type * as webViewElement from '@electron/internal/renderer/web-view/web-view-element';
import type * as webViewElementModule 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 { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
function handleFocusBlur () {
// 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) {
// Don't allow recursive `<webview>`.
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) {
v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule);
v8Util.setHiddenValue(window, 'guestViewInternal', guestViewInternal);
} else {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElement;
setupWebView(webViewImplModule);
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
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
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 {
public:
// for compatibility with the older version of this, error is after result
@ -377,7 +365,7 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
.SetMethod("setVisualZoomLevelLimits",
&WebFrameRenderer::SetVisualZoomLevelLimits)
.SetMethod("allowGuestViewElementDefinition",
&WebFrameRenderer::AllowGuestViewElementDefinition)
&RendererClientBase::AllowGuestViewElementDefinition)
.SetMethod("insertText", &WebFrameRenderer::InsertText)
.SetMethod("insertCSS", &WebFrameRenderer::InsertCSS)
.SetMethod("removeInsertedCSS", &WebFrameRenderer::RemoveInsertedCSS)
@ -541,31 +529,12 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
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) {
// Get the WebLocalFrame before (possibly) executing any user-space JS while
// getting the |params|. We track the status of the RenderFrame via an
// observer in case it is deleted during user code execution.
content::RenderFrame* render_frame = GetRenderFrame(content_window);
RenderFrameStatus render_frame_status(render_frame);
if (!render_frame_status.is_ok())
if (!render_frame)
return -1;
blink::WebLocalFrame* frame = render_frame->GetWebFrame();

View file

@ -16,7 +16,6 @@
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h"
#include "shell/renderer/electron_render_frame_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(
content::RenderFrame* render_frame) const {
if (injected_frames_.find(render_frame) == injected_frames_.end())

View file

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

View file

@ -241,34 +241,6 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext(
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(
v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) {

View file

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

View file

@ -22,10 +22,15 @@
#include "electron/buildflags/buildflags.h"
#include "media/blink/multibuffer_data_source.h"
#include "printing/buildflags/buildflags.h"
#include "shell/common/api/electron_api_native_image.h"
#include "shell/common/color_util.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/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/content_settings_observer.h"
#include "shell/renderer/electron_api_service_impl.h"
@ -85,6 +90,21 @@ namespace electron {
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,
const char* 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);
v8::Local<v8::Object> internal;
if (!frame_element_dict.GetHidden("internal", &internal))
return false;
bool is_webview = false;
return frame_element_dict.GetHidden("isWebView", &is_webview) && is_webview;
}
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

View file

@ -74,7 +74,7 @@ class RendererClientBase : public content::ContentRendererClient
content::RenderFrame* render_frame) = 0;
virtual void DidClearWindowObject(content::RenderFrame* render_frame);
virtual void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) = 0;
content::RenderFrame* render_frame);
std::unique_ptr<blink::WebPrescientNetworking> CreatePrescientNetworking(
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,
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,
content::RenderFrame* render_frame) const;

View file

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

View file

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