From c68c65f383f7b210c2ad93715dd70dbb9dcb4798 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Sat, 15 May 2021 09:42:07 +0200 Subject: [PATCH] refactor: implement using contextBridge (#29037) * refactor: implement using contextBridge * chore: address PR feedback * chore: address PR feedback * fix: check for HTMLIFrameElement instance in attachGuest --- filenames.auto.gni | 4 + lib/browser/api/web-contents.ts | 7 +- lib/common/type-utils.ts | 16 ++-- lib/isolated_renderer/init.ts | 12 +-- lib/renderer/web-view/guest-view-internal.ts | 68 +++++++++------ lib/renderer/web-view/web-view-element.ts | 36 ++++---- lib/renderer/web-view/web-view-impl.ts | 69 +++++++-------- lib/renderer/web-view/web-view-init.ts | 17 ++-- shell/renderer/api/electron_api_web_frame.cc | 35 +------- shell/renderer/electron_renderer_client.cc | 27 ------ shell/renderer/electron_renderer_client.h | 2 - .../electron_sandboxed_renderer_client.cc | 28 ------ .../electron_sandboxed_renderer_client.h | 2 - shell/renderer/renderer_client_base.cc | 87 ++++++++++++++++++- shell/renderer/renderer_client_base.h | 8 +- typings/internal-ambient.d.ts | 11 ++- typings/internal-electron.d.ts | 5 -- 17 files changed, 220 insertions(+), 214 deletions(-) diff --git a/filenames.auto.gni b/filenames.auto.gni index b88878ea1898c..0de22fca09fbc 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -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", diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 4ed02f2b7506d..245b1a3b27579 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -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) => { diff --git a/lib/common/type-utils.ts b/lib/common/type-utils.ts index 8033b3ae60345..59b1705b3e123 100644 --- a/lib/common/type-utils.ts +++ b/lib/common/type-utils.ts @@ -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; } diff --git a/lib/isolated_renderer/init.ts b/lib/isolated_renderer/init.ts index 49824021068e6..d2cbf4207fa98 100644 --- a/lib/isolated_renderer/init.ts +++ b/lib/isolated_renderer/init.ts @@ -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); } diff --git a/lib/renderer/web-view/guest-view-internal.ts b/lib/renderer/web-view/guest-view-internal.ts index d4feb223bb5e4..c3701311e3f9a 100644 --- a/lib/renderer/web-view/guest-view-internal.ts +++ b/lib/renderer/web-view/guest-view-internal.ts @@ -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): void; + reset(): void; +} + const DEPRECATED_EVENTS: Record = { 'page-title-updated': 'page-title-set' } as const; -const dispatchEvent = function ( - webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array -) { +const dispatchEvent = function (delegate: GuestViewDelegate, eventName: string, eventKey: string, ...args: Array) { if (DEPRECATED_EVENTS[eventName] != null) { - dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args); + dispatchEvent(delegate, DEPRECATED_EVENTS[eventName], eventKey, ...args); } const props: Record = {}; @@ -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): Promise { return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_GUEST, params); } -export function attachGuest ( - elementInstanceId: number, guestInstanceId: number, params: Record, contentWindow: Window -) { - const embedderFrameId = webFrame.getWebFrameId(contentWindow); +export function attachGuest (iframe: HTMLIFrameElement, elementInstanceId: number, guestInstanceId: number, params: Record) { + 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); +} diff --git a/lib/renderer/web-view/web-view-element.ts b/lib/renderer/web-view/web-view-element.ts index 051550d5d815d..84ac5a9ba79c4 100644 --- a/lib/renderer/web-view/web-view-element.ts +++ b/lib/renderer/web-view/web-view-element.ts @@ -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(); +const internals = new WeakMap(); // 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 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 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); }; diff --git a/lib/renderer/web-view/web-view-impl.ts b/lib/renderer/web-view/web-view-impl.ts index 51f39d9f5bae0..75bde104b7a31 100644 --- a/lib/renderer/web-view/web-view-impl.ts +++ b/lib/renderer/web-view/web-view-impl.ts @@ -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; - 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 = {}) { - 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' 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) { - return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, this.getWebContentsId(), method, args); - }; - }; - for (const method of syncMethods) { - (WebViewElement.prototype as Record)[method] = createBlockHandler(method); - } - - const createNonBlockHandler = function (method: string) { - return function (this: ElectronInternal.WebViewElement, ...args: Array) { - return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, this.getWebContentsId(), method, args); + (WebViewElement.prototype as Record)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array) { + return hooks.guestViewInternal.invokeSync(this.getWebContentsId(), method, args); }; - }; + } for (const method of asyncMethods) { - (WebViewElement.prototype as Record)[method] = createNonBlockHandler(method); + (WebViewElement.prototype as Record)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array) { + 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); }; }; diff --git a/lib/renderer/web-view/web-view-init.ts b/lib/renderer/web-view/web-view-init.ts index ff8799dae9f9d..1da1ce18c6751 100644 --- a/lib/renderer/web-view/web-view-init.ts +++ b/lib/renderer/web-view/web-view-init.ts @@ -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 ``. 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) + }); } } diff --git a/shell/renderer/api/electron_api_web_frame.cc b/shell/renderer/api/electron_api_web_frame.cc index 839a53c5de61e..936ba140dc86c 100644 --- a/shell/renderer/api/electron_api_web_frame.cc +++ b/shell/renderer/api/electron_api_web_frame.cc @@ -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, .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, web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level); } - void AllowGuestViewElementDefinition(v8::Isolate* isolate, - v8::Local context, - v8::Local 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 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(); diff --git a/shell/renderer/electron_renderer_client.cc b/shell/renderer/electron_renderer_client.cc index 59d511ba2ba3e..2518832c976c0 100644 --- a/shell/renderer/electron_renderer_client.cc +++ b/shell/renderer/electron_renderer_client.cc @@ -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 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> 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> 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()) diff --git a/shell/renderer/electron_renderer_client.h b/shell/renderer/electron_renderer_client.h index 57dae75238be9..102008d44cb36 100644 --- a/shell/renderer/electron_renderer_client.h +++ b/shell/renderer/electron_renderer_client.h @@ -31,8 +31,6 @@ class ElectronRendererClient : public RendererClientBase { content::RenderFrame* render_frame) override; void WillReleaseScriptContext(v8::Handle context, content::RenderFrame* render_frame) override; - void SetupMainWorldOverrides(v8::Handle context, - content::RenderFrame* render_frame) override; private: // content::ContentRendererClient: diff --git a/shell/renderer/electron_sandboxed_renderer_client.cc b/shell/renderer/electron_sandboxed_renderer_client.cc index afa958fcde6e7..8eb90becef9f1 100644 --- a/shell/renderer/electron_sandboxed_renderer_client.cc +++ b/shell/renderer/electron_sandboxed_renderer_client.cc @@ -241,34 +241,6 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext( InvokeHiddenCallback(context, kLifecycleKey, "onLoaded"); } -void ElectronSandboxedRendererClient::SetupMainWorldOverrides( - v8::Handle 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> isolated_bundle_params = { - node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"), - node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld")}; - - std::vector> 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 context, content::RenderFrame* render_frame) { diff --git a/shell/renderer/electron_sandboxed_renderer_client.h b/shell/renderer/electron_sandboxed_renderer_client.h index 30cd4e1739a06..67c9002ec658c 100644 --- a/shell/renderer/electron_sandboxed_renderer_client.h +++ b/shell/renderer/electron_sandboxed_renderer_client.h @@ -27,8 +27,6 @@ class ElectronSandboxedRendererClient : public RendererClientBase { content::RenderFrame* render_frame) override; void WillReleaseScriptContext(v8::Handle context, content::RenderFrame* render_frame) override; - void SetupMainWorldOverrides(v8::Handle context, - content::RenderFrame* render_frame) override; // content::ContentRendererClient: void RenderFrameCreated(content::RenderFrame*) override; void RenderViewCreated(content::RenderView*) override; diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index a0e3fb05d5867..bb98b77e3e9b3 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -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 value) { + v8::Local 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 object) { + gin_helper::Dictionary dict(isolate, object); + dict.SetHidden("isWebView", true); +} + std::vector 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 internal; - if (!frame_element_dict.GetHidden("internal", &internal)) - return false; + bool is_webview = false; + return frame_element_dict.GetHidden("isWebView", &is_webview) && is_webview; +} + +void RendererClientBase::SetupMainWorldOverrides( + v8::Handle 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 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> isolated_bundle_params = { + node::FIXED_ONE_BYTE_STRING(isolate, "isolatedApi")}; - return !internal.IsEmpty(); + std::vector> 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 context, + v8::Local 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 diff --git a/shell/renderer/renderer_client_base.h b/shell/renderer/renderer_client_base.h index 4193ab622f988..6c75a706da862 100644 --- a/shell/renderer/renderer_client_base.h +++ b/shell/renderer/renderer_client_base.h @@ -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 context, - content::RenderFrame* render_frame) = 0; + content::RenderFrame* render_frame); std::unique_ptr CreatePrescientNetworking( content::RenderFrame* render_frame) override; @@ -86,7 +86,11 @@ class RendererClientBase : public content::ContentRendererClient static v8::Local RunScript(v8::Local context, v8::Local source); - // v8Util.getHiddenValue(window.frameElement, 'internal') + static void AllowGuestViewElementDefinition( + v8::Isolate* isolate, + v8::Local context, + v8::Local register_cb); + bool IsWebViewFrame(v8::Handle context, content::RenderFrame* render_frame) const; diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 6079b445b0014..1f18b7efa3327 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -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(name: K): InternalWebPreferences[K]; + getWebFrameId(window: Window): number; + allowGuestViewElementDefinition(context: object, callback: Function): void; } interface WebFrameBinding { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 1e0cb010d461f..5cc680b7a4ad1 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -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;