diff --git a/filenames.auto.gni b/filenames.auto.gni index 6bc1b2ae5b50f..8ade8b6b2eaee 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -180,11 +180,8 @@ auto_filenames = { "lib/common/error-utils.ts", "lib/common/webpack-globals-provider.ts", "lib/isolated_renderer/init.js", - "lib/renderer/ipc-renderer-internal-utils.ts", - "lib/renderer/ipc-renderer-internal.ts", "lib/renderer/web-view/web-view-constants.ts", "lib/renderer/web-view/web-view-element.ts", - "lib/renderer/window-setup.ts", "package.json", "tsconfig.electron.json", "tsconfig.json", @@ -195,6 +192,7 @@ auto_filenames = { "lib/common/error-utils.ts", "lib/common/webpack-globals-provider.ts", "lib/content_script/init.js", + "lib/renderer/api/context-bridge.ts", "lib/renderer/chrome-api.ts", "lib/renderer/extensions/event.ts", "lib/renderer/extensions/i18n.ts", diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 2b6112de62449..08ba1a5df1495 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -412,6 +412,11 @@ WebContents.prototype._init = function () { const referrer = { url: '', policy: 'default' }; internalWindowOpen(event, url, referrer, frameName, disposition, options); }); + + const prefs = this.getWebPreferences() || {}; + if (prefs.webviewTag && prefs.contextIsolation) { + electron.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/isolated_renderer/init.js b/lib/isolated_renderer/init.js index 7e2f7f9b26489..60e00eafc2f87 100644 --- a/lib/isolated_renderer/init.js +++ b/lib/isolated_renderer/init.js @@ -6,10 +6,6 @@ process.electronBinding = require('@electron/internal/common/electron-binding-se const v8Util = process.electronBinding('v8_util'); -// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the -// "ipc-internal" hidden value -v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal')); - const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl'); if (webViewImpl) { @@ -17,11 +13,3 @@ if (webViewImpl) { const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element'); setupWebView(v8Util, webViewImpl); } - -const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args'); - -if (isolatedWorldArgs) { - const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs; - const { windowSetup } = require('@electron/internal/renderer/window-setup'); - windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen); -} diff --git a/lib/renderer/api/context-bridge.ts b/lib/renderer/api/context-bridge.ts index ff72c672ff74a..819cf0a7eefaa 100644 --- a/lib/renderer/api/context-bridge.ts +++ b/lib/renderer/api/context-bridge.ts @@ -18,3 +18,18 @@ const contextBridge = { if (!binding._debugGCMaps) delete contextBridge.debugGC; export default contextBridge; + +export const internalContextBridge = { + contextIsolationEnabled, + overrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => { + return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false); + }, + overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (keys: string[], value: any) => { + return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true); + }, + overrideGlobalPropertyFromIsolatedWorld: (keys: string[], getter: Function, setter?: Function) => { + return binding._overrideGlobalPropertyFromIsolatedWorld(keys, getter, setter || null); + }, + isInMainWorld: () => binding._isCalledFromMainWorld() as boolean, + isInIsolatedWorld: () => binding._isCalledFromIsolatedWorld() as boolean +}; diff --git a/lib/renderer/window-setup.ts b/lib/renderer/window-setup.ts index 0716dc393ca3d..eb55c94bde885 100644 --- a/lib/renderer/window-setup.ts +++ b/lib/renderer/window-setup.ts @@ -1,11 +1,11 @@ import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'; +import { internalContextBridge } from '@electron/internal/renderer/api/context-bridge'; -// This file implements the following APIs: -// - window.history.back() -// - window.history.forward() -// - window.history.go() -// - window.history.length +const { contextIsolationEnabled, isInIsolatedWorld } = internalContextBridge; +const shouldUseContextBridge = contextIsolationEnabled && isInIsolatedWorld(); + +// This file implements the following APIs over the ctx bridge: // - window.open() // - window.opener.blur() // - window.opener.close() @@ -13,7 +13,12 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte // - window.opener.focus() // - window.opener.location // - window.opener.print() +// - window.opener.closed // - window.opener.postMessage() +// - window.history.back() +// - window.history.forward() +// - window.history.go() +// - window.history.length // - window.prompt() // - document.hidden // - document.visibilityState @@ -30,13 +35,13 @@ const toString = (value: any) => { const windowProxies: Record = {}; -const getOrCreateProxy = (guestId: number) => { +const getOrCreateProxy = (guestId: number): SafelyBoundBrowserWindowProxy => { let proxy = windowProxies[guestId]; if (proxy == null) { proxy = new BrowserWindowProxy(guestId); windowProxies[guestId] = proxy; } - return proxy; + return proxy.getSafe(); }; const removeProxy = (guestId: number) => { @@ -64,6 +69,8 @@ class LocationProxy { */ private static ProxyProperty (target: LocationProxy, propertyKey: LocationProperties) { Object.defineProperty(target, propertyKey, { + enumerable: true, + configurable: true, get: function (this: LocationProxy): T | string { const guestURL = this.getGuestURL(); const value = guestURL ? guestURL[propertyKey] : ''; @@ -82,6 +89,30 @@ class LocationProxy { }); } + public getSafe = () => { + const that = this; + return { + get href () { return that.href; }, + set href (newValue) { that.href = newValue; }, + get hash () { return that.hash; }, + set hash (newValue) { that.hash = newValue; }, + get host () { return that.host; }, + set host (newValue) { that.host = newValue; }, + get hostname () { return that.hostname; }, + set hostname (newValue) { that.hostname = newValue; }, + get origin () { return that.origin; }, + set origin (newValue) { that.origin = newValue; }, + get pathname () { return that.pathname; }, + set pathname (newValue) { that.pathname = newValue; }, + get port () { return that.port; }, + set port (newValue) { that.port = newValue; }, + get protocol () { return that.protocol; }, + set protocol (newValue) { that.protocol = newValue; }, + get search () { return that.search; }, + set search (newValue) { that.search = newValue; } + }; + } + constructor (guestId: number) { // eslint will consider the constructor "useless" // unless we assign them in the body. It's fine, that's what @@ -114,6 +145,17 @@ class LocationProxy { } } +interface SafelyBoundBrowserWindowProxy { + location: WindowProxy['location']; + blur: WindowProxy['blur']; + close: WindowProxy['close']; + eval: typeof eval; // eslint-disable-line no-eval + focus: WindowProxy['focus']; + print: WindowProxy['print']; + postMessage: WindowProxy['postMessage']; + closed: boolean; +} + class BrowserWindowProxy { public closed: boolean = false @@ -124,7 +166,7 @@ class BrowserWindowProxy { // so for now, we'll have to make do with an "any" in the mix. // https://github.com/Microsoft/TypeScript/issues/2521 public get location (): LocationProxy | any { - return this._location; + return this._location.getSafe(); } public set location (url: string | any) { url = resolveURL(url, this.location.href); @@ -141,27 +183,48 @@ class BrowserWindowProxy { }); } - public close () { + public getSafe = (): SafelyBoundBrowserWindowProxy => { + const that = this; + return { + postMessage: this.postMessage, + blur: this.blur, + close: this.close, + focus: this.focus, + print: this.print, + eval: this.eval, + get location () { + return that.location; + }, + set location (url: string | any) { + that.location = url; + }, + get closed () { + return that.closed; + } + }; + } + + public close = () => { this._invokeWindowMethod('destroy'); } - public focus () { + public focus = () => { this._invokeWindowMethod('focus'); } - public blur () { + public blur = () => { this._invokeWindowMethod('blur'); } - public print () { + public print = () => { this._invokeWebContentsMethod('print'); } - public postMessage (message: any, targetOrigin: string) { + public postMessage = (message: any, targetOrigin: string) => { ipcRendererUtils.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin); } - public eval (code: string) { + public eval = (code: string) => { this._invokeWebContentsMethod('executeJavaScript', code); } @@ -182,9 +245,11 @@ export const windowSetup = ( window.close = function () { ipcRendererInternal.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE'); }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['close'], window.close); } if (!usesNativeWindowOpen) { + // TODO(MarshallOfSound): Make compatible with ctx isolation without hole-punch // Make the browser window or guest view emit "new-window" event. (window as any).open = function (url?: string, frameName?: string, features?: string) { if (url != null && url !== '') { @@ -197,16 +262,19 @@ export const windowSetup = ( return null; } }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['open'], window.open); } if (openerId != null) { window.opener = getOrCreateProxy(openerId); + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['opener'], window.opener); } // But we do not support prompt(). window.prompt = function () { throw new Error('prompt() is and will not be supported.'); }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['prompt'], window.prompt); if (!usesNativeWindowOpen || openerId != null) { ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function ( @@ -233,20 +301,25 @@ export const windowSetup = ( window.history.back = function () { ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK'); }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['history', 'back'], window.history.back); window.history.forward = function () { ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD'); }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['history', 'forward'], window.history.forward); window.history.go = function (offset: number) { ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset); }; + if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['history', 'go'], window.history.go); + const getHistoryLength = () => ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH'); Object.defineProperty(window.history, 'length', { - get: function () { - return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH'); - } + get: getHistoryLength, + set () {} }); + // TODO(MarshallOfSound): Fix so that the internal context bridge can override a non-configurable property + // if (shouldUseContextBridge) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['history', 'length'], getHistoryLength); } if (guestInstanceId != null) { @@ -268,16 +341,16 @@ export const windowSetup = ( }); // Make document.hidden and document.visibilityState return the correct value. + const getDocumentHidden = () => cachedVisibilityState !== 'visible'; Object.defineProperty(document, 'hidden', { - get: function () { - return cachedVisibilityState !== 'visible'; - } + get: getDocumentHidden }); + if (shouldUseContextBridge) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'hidden'], getDocumentHidden); + const getDocumentVisibilityState = () => cachedVisibilityState; Object.defineProperty(document, 'visibilityState', { - get: function () { - return cachedVisibilityState; - } + get: getDocumentVisibilityState }); + if (shouldUseContextBridge) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'visibilityState'], getDocumentVisibilityState); } }; diff --git a/shell/renderer/api/atom_api_context_bridge.cc b/shell/renderer/api/atom_api_context_bridge.cc index 90666dbfeb36c..4b84cdb4dccb3 100644 --- a/shell/renderer/api/atom_api_context_bridge.cc +++ b/shell/renderer/api/atom_api_context_bridge.cc @@ -143,6 +143,7 @@ v8::MaybeLocal PassValueToOtherContext( v8::Local destination_context, v8::Local value, context_bridge::RenderFramePersistenceStore* store, + bool support_dynamic_properties, int recursion_depth) { if (recursion_depth >= kMaxRecursion) { v8::Context::Scope source_scope(source_context); @@ -176,7 +177,8 @@ v8::MaybeLocal PassValueToOtherContext( { v8::Local proxy_func = BindRepeatingFunctionToV8( destination_context->GetIsolate(), - base::BindRepeating(&ProxyFunctionWrapper, store, func_id)); + base::BindRepeating(&ProxyFunctionWrapper, store, func_id, + support_dynamic_properties)); FunctionLifeMonitor::BindTo(destination_context->GetIsolate(), v8::Local::Cast(proxy_func), store->GetWeakPtr(), func_id); @@ -201,9 +203,10 @@ v8::MaybeLocal PassValueToOtherContext( v8::Global global_destination_context, context_bridge::RenderFramePersistenceStore* store, v8::Local result) { - auto val = PassValueToOtherContext( - global_source_context.Get(isolate), - global_destination_context.Get(isolate), result, store, 0); + auto val = + PassValueToOtherContext(global_source_context.Get(isolate), + global_destination_context.Get(isolate), + result, store, false, 0); if (!val.IsEmpty()) proxied_promise->Resolve(val.ToLocalChecked()); delete proxied_promise; @@ -219,9 +222,10 @@ v8::MaybeLocal PassValueToOtherContext( v8::Global global_destination_context, context_bridge::RenderFramePersistenceStore* store, v8::Local result) { - auto val = PassValueToOtherContext( - global_source_context.Get(isolate), - global_destination_context.Get(isolate), result, store, 0); + auto val = + PassValueToOtherContext(global_source_context.Get(isolate), + global_destination_context.Get(isolate), + result, store, false, 0); if (!val.IsEmpty()) proxied_promise->Reject(val.ToLocalChecked()); delete proxied_promise; @@ -267,7 +271,7 @@ v8::MaybeLocal PassValueToOtherContext( auto value_for_array = PassValueToOtherContext( source_context, destination_context, arr->Get(source_context, i).ToLocalChecked(), store, - recursion_depth + 1); + support_dynamic_properties, recursion_depth + 1); if (value_for_array.IsEmpty()) return v8::MaybeLocal(); @@ -285,9 +289,9 @@ v8::MaybeLocal PassValueToOtherContext( // Proxy all objects if (IsPlainObject(value)) { auto object_value = v8::Local::Cast(value); - auto passed_value = - CreateProxyForAPI(object_value, source_context, destination_context, - store, recursion_depth + 1); + auto passed_value = CreateProxyForAPI( + object_value, source_context, destination_context, store, + support_dynamic_properties, recursion_depth + 1); if (passed_value.IsEmpty()) return v8::MaybeLocal(); return v8::MaybeLocal(passed_value.ToLocalChecked()); @@ -316,6 +320,7 @@ v8::MaybeLocal PassValueToOtherContext( v8::Local ProxyFunctionWrapper( context_bridge::RenderFramePersistenceStore* store, size_t func_id, + bool support_dynamic_properties, mate::Arguments* args) { // Context the proxy function was called from v8::Local calling_context = args->isolate()->GetCurrentContext(); @@ -333,8 +338,9 @@ v8::Local ProxyFunctionWrapper( args->GetRemaining(&original_args); for (auto value : original_args) { - auto arg = PassValueToOtherContext(calling_context, func_owning_context, - value, store, 0); + auto arg = + PassValueToOtherContext(calling_context, func_owning_context, value, + store, support_dynamic_properties, 0); if (arg.IsEmpty()) return v8::Undefined(args->isolate()); proxied_args.push_back(arg.ToLocalChecked()); @@ -372,9 +378,9 @@ v8::Local ProxyFunctionWrapper( if (maybe_return_value.IsEmpty()) return v8::Undefined(args->isolate()); - auto ret = - PassValueToOtherContext(func_owning_context, calling_context, - maybe_return_value.ToLocalChecked(), store, 0); + auto ret = PassValueToOtherContext(func_owning_context, calling_context, + maybe_return_value.ToLocalChecked(), + store, support_dynamic_properties, 0); if (ret.IsEmpty()) return v8::Undefined(args->isolate()); return ret.ToLocalChecked(); @@ -386,6 +392,7 @@ v8::MaybeLocal CreateProxyForAPI( const v8::Local& source_context, const v8::Local& destination_context, context_bridge::RenderFramePersistenceStore* store, + bool support_dynamic_properties, int recursion_depth) { mate::Dictionary api(source_context->GetIsolate(), api_object); v8::Context::Scope destination_context_scope(destination_context); @@ -410,13 +417,52 @@ v8::MaybeLocal CreateProxyForAPI( if (!mate::ConvertFromV8(api.isolate(), key, &key_str)) { continue; } + if (support_dynamic_properties) { + v8::Context::Scope source_context_scope(source_context); + auto maybe_desc = api.GetHandle()->GetOwnPropertyDescriptor( + source_context, v8::Local::Cast(key)); + v8::Local desc_value; + if (!maybe_desc.ToLocal(&desc_value) || !desc_value->IsObject()) + continue; + mate::Dictionary desc(api.isolate(), desc_value.As()); + if (desc.Has("get") || desc.Has("set")) { + v8::Local getter; + v8::Local setter; + desc.Get("get", &getter); + desc.Get("set", &setter); + + { + v8::Context::Scope destination_context_scope(destination_context); + v8::Local getter_proxy; + v8::Local setter_proxy; + if (!getter.IsEmpty()) { + if (!PassValueToOtherContext(source_context, destination_context, + getter, store, false, 1) + .ToLocal(&getter_proxy)) + continue; + } + if (!setter.IsEmpty()) { + if (!PassValueToOtherContext(source_context, destination_context, + setter, store, false, 1) + .ToLocal(&setter_proxy)) + continue; + } + + v8::PropertyDescriptor desc(getter_proxy, setter_proxy); + ignore_result(proxy.GetHandle()->DefineProperty( + destination_context, mate::StringToV8(api.isolate(), key_str), + desc)); + } + continue; + } + } v8::Local value; if (!api.Get(key_str, &value)) continue; - auto passed_value = - PassValueToOtherContext(source_context, destination_context, value, - store, recursion_depth + 1); + auto passed_value = PassValueToOtherContext( + source_context, destination_context, value, store, + support_dynamic_properties, recursion_depth + 1); if (passed_value.IsEmpty()) return v8::MaybeLocal(); proxy.Set(key_str, passed_value.ToLocalChecked()); @@ -476,8 +522,8 @@ void ExposeAPIInMainWorld(const std::string& key, v8::Context::Scope main_context_scope(main_context); { - v8::MaybeLocal maybe_proxy = - CreateProxyForAPI(api_object, isolated_context, main_context, store, 0); + v8::MaybeLocal maybe_proxy = CreateProxyForAPI( + api_object, isolated_context, main_context, store, false, 0); if (maybe_proxy.IsEmpty()) return; auto proxy = maybe_proxy.ToLocalChecked(); @@ -488,6 +534,111 @@ void ExposeAPIInMainWorld(const std::string& key, } } +mate::Dictionary TraceKeyPath(const mate::Dictionary& start, + const std::vector& key_path) { + mate::Dictionary current = start; + for (size_t i = 0; i < key_path.size() - 1; i++) { + CHECK(current.Get(key_path[i], ¤t)); + } + return current; +} + +void OverrideGlobalValueFromIsolatedWorld( + const std::vector& key_path, + v8::Local value, + bool support_dynamic_properties) { + if (key_path.size() == 0) + return; + + auto* render_frame = GetRenderFrame(value); + CHECK(render_frame); + context_bridge::RenderFramePersistenceStore* store = + GetOrCreateStore(render_frame); + auto* frame = render_frame->GetWebFrame(); + CHECK(frame); + v8::Local main_context = frame->MainWorldScriptContext(); + mate::Dictionary global(main_context->GetIsolate(), main_context->Global()); + + const std::string final_key = key_path[key_path.size() - 1]; + mate::Dictionary target_object = TraceKeyPath(global, key_path); + + { + v8::Context::Scope main_context_scope(main_context); + v8::MaybeLocal maybe_proxy = + PassValueToOtherContext(value->CreationContext(), main_context, value, + store, support_dynamic_properties, 1); + DCHECK(!maybe_proxy.IsEmpty()); + auto proxy = maybe_proxy.ToLocalChecked(); + + target_object.Set(final_key, proxy); + } +} + +bool OverrideGlobalPropertyFromIsolatedWorld( + const std::vector& key_path, + v8::Local getter, + v8::Local setter, + mate::Arguments* args) { + if (key_path.size() == 0) + return false; + + auto* render_frame = GetRenderFrame(getter); + CHECK(render_frame); + context_bridge::RenderFramePersistenceStore* store = + GetOrCreateStore(render_frame); + auto* frame = render_frame->GetWebFrame(); + CHECK(frame); + v8::Local main_context = frame->MainWorldScriptContext(); + mate::Dictionary global(main_context->GetIsolate(), main_context->Global()); + + const std::string final_key = key_path[key_path.size() - 1]; + v8::Local target_object = + TraceKeyPath(global, key_path).GetHandle(); + + { + v8::Context::Scope main_context_scope(main_context); + v8::Local getter_proxy; + v8::Local setter_proxy; + if (!getter->IsNullOrUndefined()) { + v8::MaybeLocal maybe_getter_proxy = PassValueToOtherContext( + getter->CreationContext(), main_context, getter, store, false, 1); + DCHECK(!maybe_getter_proxy.IsEmpty()); + getter_proxy = maybe_getter_proxy.ToLocalChecked(); + } + if (!setter->IsNullOrUndefined() && setter->IsObject()) { + v8::MaybeLocal maybe_setter_proxy = PassValueToOtherContext( + getter->CreationContext(), main_context, setter, store, false, 1); + DCHECK(!maybe_setter_proxy.IsEmpty()); + setter_proxy = maybe_setter_proxy.ToLocalChecked(); + } + + v8::PropertyDescriptor desc(getter_proxy, setter_proxy); + bool success = mate::internal::IsTrue(target_object->DefineProperty( + main_context, mate::StringToV8(args->isolate(), final_key), desc)); + DCHECK(success); + return success; + } +} + +bool IsCalledFromMainWorld(v8::Isolate* isolate) { + auto* render_frame = GetRenderFrame(isolate->GetCurrentContext()->Global()); + CHECK(render_frame); + auto* frame = render_frame->GetWebFrame(); + CHECK(frame); + v8::Local main_context = frame->MainWorldScriptContext(); + return isolate->GetCurrentContext() == main_context; +} + +bool IsCalledFromIsolatedWorld(v8::Isolate* isolate) { + auto* render_frame = GetRenderFrame(isolate->GetCurrentContext()->Global()); + CHECK(render_frame); + auto* frame = render_frame->GetWebFrame(); + CHECK(frame); + v8::Local isolated_context = + frame->WorldScriptContext(isolate, World::ISOLATED_WORLD); + return isolate->GetCurrentContext() == isolated_context; +} + } // namespace api } // namespace electron @@ -501,6 +652,14 @@ void Initialize(v8::Local exports, v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.SetMethod("exposeAPIInMainWorld", &electron::api::ExposeAPIInMainWorld); + dict.SetMethod("_overrideGlobalValueFromIsolatedWorld", + &electron::api::OverrideGlobalValueFromIsolatedWorld); + dict.SetMethod("_overrideGlobalPropertyFromIsolatedWorld", + &electron::api::OverrideGlobalPropertyFromIsolatedWorld); + dict.SetMethod("_isCalledFromMainWorld", + &electron::api::IsCalledFromMainWorld); + dict.SetMethod("_isCalledFromIsolatedWorld", + &electron::api::IsCalledFromIsolatedWorld); #ifdef DCHECK_IS_ON dict.SetMethod("_debugGCMaps", &electron::api::DebugGC); #endif diff --git a/shell/renderer/api/atom_api_context_bridge.h b/shell/renderer/api/atom_api_context_bridge.h index a855e68f6eede..6c86c48834aa6 100644 --- a/shell/renderer/api/atom_api_context_bridge.h +++ b/shell/renderer/api/atom_api_context_bridge.h @@ -25,6 +25,7 @@ namespace api { v8::Local ProxyFunctionWrapper( context_bridge::RenderFramePersistenceStore* store, size_t func_id, + bool support_dynamic_properties, mate::Arguments* args); v8::MaybeLocal CreateProxyForAPI( @@ -32,6 +33,7 @@ v8::MaybeLocal CreateProxyForAPI( const v8::Local& source_context, const v8::Local& target_context, context_bridge::RenderFramePersistenceStore* store, + bool support_dynamic_properties, int recursion_depth); } // namespace api diff --git a/shell/renderer/atom_renderer_client.cc b/shell/renderer/atom_renderer_client.cc index 4ba202344ea17..8c3c313255a50 100644 --- a/shell/renderer/atom_renderer_client.cc +++ b/shell/renderer/atom_renderer_client.cc @@ -212,6 +212,9 @@ void AtomRendererClient::WillDestroyWorkerContextOnWorkerThread( void AtomRendererClient::SetupMainWorldOverrides( v8::Handle context, content::RenderFrame* render_frame) { + // We only need to run the isolated bundle if webview is enabled + if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWebviewTag)) + return; // Setup window overrides in the main world context // Wrap the bundle into a function that receives the isolatedWorld as // an argument. diff --git a/shell/renderer/atom_sandboxed_renderer_client.cc b/shell/renderer/atom_sandboxed_renderer_client.cc index 3120ab4aa3f00..84c21d453d5c3 100644 --- a/shell/renderer/atom_sandboxed_renderer_client.cc +++ b/shell/renderer/atom_sandboxed_renderer_client.cc @@ -235,6 +235,10 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( void AtomSandboxedRendererClient::SetupMainWorldOverrides( v8::Handle context, content::RenderFrame* render_frame) { + // We only need to run the isolated bundle if webview is enabled + if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWebviewTag)) + return; + // Setup window overrides in the main world context // Wrap the bundle into a function that receives the isolatedWorld as // an argument. diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index 208f55e12719c..defe79a6527cf 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/spec-main/api-context-bridge-spec.ts @@ -378,19 +378,23 @@ describe('contextBridge', () => { require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC())) contextBridge.exposeInMainWorld('example', { getFunction: () => () => 123 - }) - }) - expect((await getGCInfo()).functionCount).to.equal(2) + }); + }); await callWithBindings(async (root: any) => { - root.x = [root.example.getFunction()] - }) - expect((await getGCInfo()).functionCount).to.equal(3) + root.GCRunner.run(); + }); + const baseValue = (await getGCInfo()).functionCount; await callWithBindings(async (root: any) => { root.x = [] - root.GCRunner.run() - }) - expect((await getGCInfo()).functionCount).to.equal(2) - }) + root.x = [root.example.getFunction()]; + }); + expect((await getGCInfo()).functionCount).to.equal(baseValue + 1); + await callWithBindings(async (root: any) => { + root.x = []; + root.GCRunner.run(); + }); + expect((await getGCInfo()).functionCount).to.equal(baseValue); + }); it('should release the global hold on objects sent across contexts when the object proxy is de-reffed', async () => { await makeBindingWindow(() => { @@ -408,9 +412,9 @@ describe('contextBridge', () => { }) // Initial Setup let info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + const baseFrom = info.liveFromValues; + const baseProxy = info.liveProxyValues; + const baseCount = info.objectCount; // Create Reference await callWithBindings(async (root: any) => { @@ -419,9 +423,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(4) - expect(info.liveProxyValues).to.equal(4) - expect(info.objectCount).to.equal(8) + expect(info.liveFromValues).to.equal(baseFrom + 1) + expect(info.liveProxyValues).to.equal(baseProxy + 1) + expect(info.objectCount).to.equal(baseCount + 2) // Release Reference await callWithBindings(async (root: any) => { @@ -429,9 +433,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + expect(info.liveFromValues).to.equal(baseFrom) + expect(info.liveProxyValues).to.equal(baseProxy) + expect(info.objectCount).to.equal(baseCount) }) it('should release the global hold on objects sent across contexts when the object source is de-reffed', async () => { @@ -450,9 +454,9 @@ describe('contextBridge', () => { }) // Initial Setup let info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + const baseFrom = info.liveFromValues; + const baseProxy = info.liveProxyValues; + const baseCount = info.objectCount; // Create Reference await callWithBindings(async (root: any) => { @@ -461,9 +465,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(4) - expect(info.liveProxyValues).to.equal(4) - expect(info.objectCount).to.equal(8) + expect(info.liveFromValues).to.equal(baseFrom + 1) + expect(info.liveProxyValues).to.equal(baseProxy + 1) + expect(info.objectCount).to.equal(baseCount + 2) // Release Reference await callWithBindings(async (root: any) => { @@ -471,9 +475,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + expect(info.liveFromValues).to.equal(baseFrom) + expect(info.liveProxyValues).to.equal(baseProxy) + expect(info.objectCount).to.equal(baseCount) }) it('should not crash when the object source is de-reffed AND the object proxy is de-reffed', async () => { @@ -492,9 +496,9 @@ describe('contextBridge', () => { }) // Initial Setup let info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + const baseFrom = info.liveFromValues; + const baseProxy = info.liveProxyValues; + const baseCount = info.objectCount; // Create Reference await callWithBindings(async (root: any) => { @@ -503,9 +507,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(4) - expect(info.liveProxyValues).to.equal(4) - expect(info.objectCount).to.equal(8) + expect(info.liveFromValues).to.equal(baseFrom + 1) + expect(info.liveProxyValues).to.equal(baseProxy + 1) + expect(info.objectCount).to.equal(baseCount + 2) // Release Reference await callWithBindings(async (root: any) => { @@ -514,9 +518,9 @@ describe('contextBridge', () => { root.GCRunner.run() }) info = await getGCInfo() - expect(info.liveFromValues).to.equal(3) - expect(info.liveProxyValues).to.equal(3) - expect(info.objectCount).to.equal(6) + expect(info.liveFromValues).to.equal(baseFrom) + expect(info.liveProxyValues).to.equal(baseProxy) + expect(info.objectCount).to.equal(baseCount) }) } @@ -526,19 +530,19 @@ describe('contextBridge', () => { require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC())) contextBridge.exposeInMainWorld('example', { getFunction: () => () => 123 - }) - require('electron').ipcRenderer.send('window-ready-for-tasking') - }) - const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking') - expect((await getGCInfo()).functionCount).to.equal(1) + }); + require('electron').ipcRenderer.send('window-ready-for-tasking'); + }); + const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking'); + const baseValue = (await getGCInfo()).functionCount; await callWithBindings((root: any) => { root.location.reload() }) await loadPromise // If this is ever "2" it means we leaked the exposed function and // therefore the entire context after a reload - expect((await getGCInfo()).functionCount).to.equal(1) - }) + expect((await getGCInfo()).functionCount).to.equal(baseValue); + }); } it('it should not let you overwrite existing exposed things', async () => { diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 321bf95919e9e..fd2456b5f70ff 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -305,13 +305,6 @@ describe('chromium feature', () => { }); describe('window.open', () => { - it('returns a BrowserWindowProxy object', () => { - const b = window.open('about:blank', '', 'show=no'); - expect(b.closed).to.be.false(); - expect(b.constructor.name).to.equal('BrowserWindowProxy'); - b.close(); - }); - it('accepts "nodeIntegration" as feature', (done) => { let b = null; listener = (event) => { @@ -754,8 +747,8 @@ describe('chromium feature', () => { let b = null; listener = (event) => { window.removeEventListener('message', listener); + expect(event.source).to.deep.equal(b); b.close(); - expect(event.source).to.equal(b); expect(event.origin).to.equal('file://'); done(); }; diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html index ea97e593f5b76..e0a42b8b9c369 100644 --- a/spec/fixtures/pages/window-open-postMessage.html +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -5,7 +5,7 @@ window.opener.postMessage(JSON.stringify({ origin: e.origin, data: e.data, - sourceEqualsOpener: e.source === window.opener + sourceEqualsOpener: e.source.location.href === window.opener.location.href }), '*'); }); window.opener.postMessage("ready", "*")