fix: load window-setup in sandboxed renderer (#21416)
This commit is contained in:
parent
d56f67b7af
commit
31c93fec67
7 changed files with 89 additions and 61 deletions
|
@ -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",
|
||||||
|
|
|
@ -75,8 +75,10 @@ const mergeBrowserWindowOptions = function (embedder, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets correct openerId here to give correct options to 'new-window' event handler
|
if (!webPreferences.nativeWindowOpen) {
|
||||||
options.webPreferences.openerId = embedder.id
|
// Sets correct openerId here to give correct options to 'new-window' event handler
|
||||||
|
options.webPreferences.openerId = embedder.id
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,10 +197,10 @@ 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().
|
||||||
|
@ -208,43 +208,47 @@ export const windowSetup = (
|
||||||
throw new Error('prompt() is and will not be supported.')
|
throw new Error('prompt() is and will not be supported.')
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
if (!usesNativeWindowOpen || openerId != null) {
|
||||||
_event, sourceId: number, message: any, sourceOrigin: string
|
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
||||||
) {
|
_event, sourceId: number, message: any, sourceOrigin: string
|
||||||
// Manually dispatch event instead of using postMessage because we also need to
|
) {
|
||||||
// set event.source.
|
// Manually dispatch event instead of using postMessage because we also need to
|
||||||
//
|
// set event.source.
|
||||||
// Why any? We can't construct a MessageEvent and we can't
|
//
|
||||||
// use `as MessageEvent` because you're not supposed to override
|
// Why any? We can't construct a MessageEvent and we can't
|
||||||
// data, origin, and source
|
// use `as MessageEvent` because you're not supposed to override
|
||||||
const event: any = document.createEvent('Event')
|
// data, origin, and source
|
||||||
event.initEvent('message', false, false)
|
const event: any = document.createEvent('Event')
|
||||||
|
event.initEvent('message', false, false)
|
||||||
|
|
||||||
event.data = message
|
event.data = message
|
||||||
event.origin = sourceOrigin
|
event.origin = sourceOrigin
|
||||||
event.source = getOrCreateProxy(sourceId)
|
event.source = getOrCreateProxy(sourceId)
|
||||||
|
|
||||||
window.dispatchEvent(event as MessageEvent)
|
window.dispatchEvent(event as MessageEvent)
|
||||||
})
|
})
|
||||||
|
|
||||||
window.history.back = function () {
|
|
||||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.history.forward = function () {
|
if (!process.sandboxed) {
|
||||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD')
|
window.history.back = function () {
|
||||||
}
|
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
|
||||||
|
}
|
||||||
|
|
||||||
window.history.go = function (offset: number) {
|
window.history.forward = function () {
|
||||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset)
|
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD')
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(window.history, 'length', {
|
window.history.go = function (offset: number) {
|
||||||
get: function () {
|
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset)
|
||||||
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH')
|
}
|
||||||
},
|
|
||||||
set () {}
|
Object.defineProperty(window.history, 'length', {
|
||||||
})
|
get: function () {
|
||||||
|
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH')
|
||||||
|
},
|
||||||
|
set () {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (guestInstanceId != null) {
|
if (guestInstanceId != null) {
|
||||||
// Webview `document.visibilityState` tracks window visibility (and ignores
|
// Webview `document.visibilityState` tracks window visibility (and ignores
|
||||||
|
|
|
@ -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:
|
||||||
//
|
//
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -766,22 +766,27 @@ 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]) {
|
||||||
it(description, async () => {
|
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`
|
||||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } })
|
it(description, async () => {
|
||||||
await w.loadURL(parent)
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } })
|
||||||
const childOpenerLocation = await w.webContents.executeJavaScript(`new Promise(resolve => {
|
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
|
||||||
window.addEventListener('message', function f(e) {
|
options!.webPreferences!.sandbox = sandboxPopup
|
||||||
resolve(e.data)
|
|
||||||
})
|
})
|
||||||
window.open(${JSON.stringify(child)}, "", "show=no,nodeIntegration=${nodeIntegration ? 'yes' : 'no'}")
|
await w.loadURL(parent)
|
||||||
})`)
|
const childOpenerLocation = await w.webContents.executeJavaScript(`new Promise(resolve => {
|
||||||
if (openerAccessible) {
|
window.addEventListener('message', function f(e) {
|
||||||
expect(childOpenerLocation).to.be.a('string')
|
resolve(e.data)
|
||||||
} else {
|
})
|
||||||
expect(childOpenerLocation).to.be.null()
|
window.open(${JSON.stringify(child)}, "", "show=no,nodeIntegration=${nodeIntegration ? 'yes' : 'no'}")
|
||||||
}
|
})`)
|
||||||
})
|
if (openerAccessible) {
|
||||||
|
expect(childOpenerLocation).to.be.a('string')
|
||||||
|
} else {
|
||||||
|
expect(childOpenerLocation).to.be.null()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue