fix: load window-setup in sandboxed renderer (#21416)

This commit is contained in:
Milan Burda 2020-01-06 22:23:03 +01:00 committed by Alexey Kuzmin
parent d56f67b7af
commit 31c93fec67
7 changed files with 89 additions and 61 deletions

View file

@ -165,6 +165,7 @@ auto_filenames = {
"lib/renderer/web-view/web-view-element.ts", "lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts", "lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts", "lib/renderer/web-view/web-view-init.ts",
"lib/renderer/window-setup.ts",
"lib/sandboxed_renderer/api/exports/electron.ts", "lib/sandboxed_renderer/api/exports/electron.ts",
"lib/sandboxed_renderer/api/module-list.ts", "lib/sandboxed_renderer/api/module-list.ts",
"lib/sandboxed_renderer/init.js", "lib/sandboxed_renderer/init.js",

View file

@ -75,8 +75,10 @@ const mergeBrowserWindowOptions = function (embedder, options) {
} }
} }
if (!webPreferences.nativeWindowOpen) {
// Sets correct openerId here to give correct options to 'new-window' event handler // Sets correct openerId here to give correct options to 'new-window' event handler
options.webPreferences.openerId = embedder.id options.webPreferences.openerId = embedder.id
}
return options return options
} }

View file

@ -112,11 +112,15 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event)
contentScripts = getContentScripts() contentScripts = getContentScripts()
} }
const webPreferences = event.sender.getLastWebPreferences() || {}
return { return {
contentScripts, contentScripts,
preloadScripts: await Promise.all(preloadPaths.map(path => getPreloadScript(path))), preloadScripts: await Promise.all(preloadPaths.map(path => getPreloadScript(path))),
isRemoteModuleEnabled: isRemoteModuleEnabled(event.sender), isRemoteModuleEnabled: isRemoteModuleEnabled(event.sender),
isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender), isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender),
guestInstanceId: webPreferences.guestInstanceId,
openerId: webPreferences.openerId,
process: { process: {
arch: process.arch, arch: process.arch,
platform: process.platform, platform: process.platform,

View file

@ -177,7 +177,7 @@ class BrowserWindowProxy {
export const windowSetup = ( export const windowSetup = (
guestInstanceId: number, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean guestInstanceId: number, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean
) => { ) => {
if (guestInstanceId == null) { if (!process.sandboxed && guestInstanceId == null) {
// Override default window.close. // Override default window.close.
window.close = function () { window.close = function () {
ipcRendererInternal.send('ELECTRON_BROWSER_WINDOW_CLOSE') ipcRendererInternal.send('ELECTRON_BROWSER_WINDOW_CLOSE')
@ -197,17 +197,18 @@ export const windowSetup = (
return null return null
} }
} }
}
if (openerId != null) { if (openerId != null) {
window.opener = getOrCreateProxy(openerId) window.opener = getOrCreateProxy(openerId)
} }
}
// But we do not support prompt(). // But we do not support prompt().
window.prompt = function () { window.prompt = function () {
throw new Error('prompt() is and will not be supported.') throw new Error('prompt() is and will not be supported.')
} }
if (!usesNativeWindowOpen || openerId != null) {
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function ( ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
_event, sourceId: number, message: any, sourceOrigin: string _event, sourceId: number, message: any, sourceOrigin: string
) { ) {
@ -226,7 +227,9 @@ export const windowSetup = (
window.dispatchEvent(event as MessageEvent) window.dispatchEvent(event as MessageEvent)
}) })
}
if (!process.sandboxed) {
window.history.back = function () { window.history.back = function () {
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK') ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
} }
@ -245,6 +248,7 @@ export const windowSetup = (
}, },
set () {} set () {}
}) })
}
if (guestInstanceId != null) { if (guestInstanceId != null) {
// Webview `document.visibilityState` tracks window visibility (and ignores // Webview `document.visibilityState` tracks window visibility (and ignores

View file

@ -30,7 +30,13 @@ const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-rendere
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
const { const {
contentScripts, preloadScripts, isRemoteModuleEnabled, isWebViewTagEnabled, process: processProps contentScripts,
preloadScripts,
isRemoteModuleEnabled,
isWebViewTagEnabled,
guestInstanceId,
openerId,
process: processProps
} = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_SANDBOX_LOAD') } = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_SANDBOX_LOAD')
process.isRemoteModuleEnabled = isRemoteModuleEnabled process.isRemoteModuleEnabled = isRemoteModuleEnabled
@ -109,6 +115,11 @@ function preloadRequire (module) {
const { hasSwitch } = process.electronBinding('command_line') const { hasSwitch } = process.electronBinding('command_line')
const contextIsolation = hasSwitch('context-isolation') const contextIsolation = hasSwitch('context-isolation')
const isHiddenPage = hasSwitch('hidden-page')
const usesNativeWindowOpen = true
// The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen }
switch (window.location.protocol) { switch (window.location.protocol) {
case 'devtools:': { case 'devtools:': {
@ -127,6 +138,10 @@ switch (window.location.protocol) {
break break
} }
default: { default: {
// Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup')
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
// Inject content scripts. // Inject content scripts.
if (!process.electronBinding('features').isExtensionsEnabled()) { if (!process.electronBinding('features').isExtensionsEnabled()) {
require('@electron/internal/renderer/content-scripts-injector')(contentScripts) require('@electron/internal/renderer/content-scripts-injector')(contentScripts)
@ -134,14 +149,17 @@ switch (window.location.protocol) {
} }
} }
const guestInstanceId = binding.guestInstanceId && parseInt(binding.guestInstanceId)
// Load webview tag implementation. // Load webview tag implementation.
if (process.isMainFrame) { if (process.isMainFrame) {
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init') const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init')
webViewInit(contextIsolation, isWebViewTagEnabled, guestInstanceId) webViewInit(contextIsolation, isWebViewTagEnabled, guestInstanceId)
} }
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
}
// Wrap the script into a function executed in global scope. It won't have // Wrap the script into a function executed in global scope. It won't have
// access to the current scope, so we'll expose a few objects as arguments: // access to the current scope, so we'll expose a few objects as arguments:
// //

View file

@ -141,12 +141,6 @@ void AtomSandboxedRendererClient::InitializeBindings(
process.SetReadOnly("sandboxed", true); process.SetReadOnly("sandboxed", true);
process.SetReadOnly("type", "renderer"); process.SetReadOnly("type", "renderer");
process.SetReadOnly("isMainFrame", is_main_frame); process.SetReadOnly("isMainFrame", is_main_frame);
// Pass in CLI flags needed to setup the renderer
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kGuestInstanceID))
b.Set(options::kGuestInstanceID,
command_line->GetSwitchValueASCII(switches::kGuestInstanceID));
} }
void AtomSandboxedRendererClient::RenderFrameCreated( void AtomSandboxedRendererClient::RenderFrameCreated(

View file

@ -766,9 +766,13 @@ describe('chromium features', () => {
describe('when opened from main window', () => { describe('when opened from main window', () => {
for (const { parent, child, nodeIntegration, nativeWindowOpen, openerAccessible } of table) { for (const { parent, child, nodeIntegration, nativeWindowOpen, openerAccessible } of table) {
const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration} nativeWindowOpen=${nativeWindowOpen}, child should ${openerAccessible ? '' : 'not '}be able to access opener` for (const sandboxPopup of [false, true]) {
const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration} nativeWindowOpen=${nativeWindowOpen} sandboxPopup=${sandboxPopup}, child should ${openerAccessible ? '' : 'not '}be able to access opener`
it(description, async () => { it(description, async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } }) const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } })
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
options!.webPreferences!.sandbox = sandboxPopup
})
await w.loadURL(parent) await w.loadURL(parent)
const childOpenerLocation = await w.webContents.executeJavaScript(`new Promise(resolve => { const childOpenerLocation = await w.webContents.executeJavaScript(`new Promise(resolve => {
window.addEventListener('message', function f(e) { window.addEventListener('message', function f(e) {
@ -783,6 +787,7 @@ describe('chromium features', () => {
} }
}) })
} }
}
}) })
describe('when opened from <webview>', () => { describe('when opened from <webview>', () => {