Skip to content

Commit b965e54

Browse files
miniakCheng Zhao
authored andcommitted
fix: <webview> not working with contextIsolation + sandbox (electron#16469)
1 parent a9ac75c commit b965e54

File tree

8 files changed

+164
-82
lines changed

8 files changed

+164
-82
lines changed

atom/renderer/atom_sandboxed_renderer_client.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,30 @@ void AtomSandboxedRendererClient::DidCreateScriptContext(
202202
&preload_bundle_params, &preload_bundle_args, nullptr);
203203
}
204204

205+
void AtomSandboxedRendererClient::SetupMainWorldOverrides(
206+
v8::Handle<v8::Context> context,
207+
content::RenderFrame* render_frame) {
208+
// Setup window overrides in the main world context
209+
// Wrap the bundle into a function that receives the isolatedWorld as
210+
// an argument.
211+
auto* isolate = context->GetIsolate();
212+
213+
mate::Dictionary process = mate::Dictionary::CreateEmpty(isolate);
214+
process.SetMethod("binding", GetBinding);
215+
216+
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
217+
node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"),
218+
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld")};
219+
220+
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
221+
process.GetHandle(),
222+
GetContext(render_frame->GetWebFrame(), isolate)->Global()};
223+
224+
node::per_process::native_module_loader.CompileAndCall(
225+
context, "electron/js2c/isolated_bundle", &isolated_bundle_params,
226+
&isolated_bundle_args, nullptr);
227+
}
228+
205229
void AtomSandboxedRendererClient::WillReleaseScriptContext(
206230
v8::Handle<v8::Context> context,
207231
content::RenderFrame* render_frame) {

atom/renderer/atom_sandboxed_renderer_client.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class AtomSandboxedRendererClient : public RendererClientBase {
2929
void WillReleaseScriptContext(v8::Handle<v8::Context> context,
3030
content::RenderFrame* render_frame) override;
3131
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
32-
content::RenderFrame* render_frame) override {}
32+
content::RenderFrame* render_frame) override;
3333
// content::ContentRendererClient:
3434
void RenderFrameCreated(content::RenderFrame*) override;
3535
void RenderViewCreated(content::RenderView*) override;

filenames.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ filenames = {
7474
"lib/renderer/web-view/web-view-constants.js",
7575
"lib/renderer/web-view/web-view-element.js",
7676
"lib/renderer/web-view/web-view-impl.js",
77+
"lib/renderer/web-view/web-view-init.js",
7778
"lib/renderer/api/exports/electron.js",
7879
"lib/renderer/api/crash-reporter.js",
7980
"lib/renderer/api/ipc-renderer.js",

lib/isolated_renderer/init.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
/* global nodeProcess, isolatedWorld */
44

5-
// Note: Don't use "process", as it will be replaced by browserify's one.
6-
const v8Util = nodeProcess.atomBinding('v8_util')
5+
const atomBinding = require('@electron/internal/common/atom-binding-setup')(nodeProcess.binding, 'renderer')
76

8-
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args')
9-
const { webViewImpl, ipcRenderer, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs
7+
const v8Util = atomBinding('v8_util')
8+
9+
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl')
1010

1111
if (webViewImpl) {
1212
// Must setup the WebView element in main world.
1313
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
1414
setupWebView(v8Util, webViewImpl)
1515
}
1616

17-
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
17+
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args')
18+
19+
if (isolatedWorldArgs) {
20+
const { ipcRenderer, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs
21+
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
22+
}

lib/renderer/init.js

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,33 +58,29 @@ if (preloadScript) {
5858
preloadScripts.push(preloadScript)
5959
}
6060

61-
if (window.location.protocol === 'chrome-devtools:') {
62-
// Override some inspector APIs.
63-
require('@electron/internal/renderer/inspector')
64-
} else if (window.location.protocol === 'chrome-extension:') {
65-
// Add implementations of chrome API.
66-
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
67-
} else if (window.location.protocol === 'chrome:') {
68-
// Disable node integration for chrome UI scheme.
69-
} else {
70-
// Override default web functions.
71-
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
72-
73-
// Inject content scripts.
74-
require('@electron/internal/renderer/content-scripts-injector')
61+
switch (window.location.protocol) {
62+
case 'chrome-devtools:': {
63+
// Override some inspector APIs.
64+
require('@electron/internal/renderer/inspector')
65+
break
66+
}
67+
case 'chrome-extension:': {
68+
// Inject the chrome.* APIs that chrome extensions require
69+
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
70+
break
71+
}
72+
default: {
73+
// Override default web functions.
74+
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
7575

76-
// Load webview tag implementation.
77-
if (webviewTag && guestInstanceId == null) {
78-
const webViewImpl = require('@electron/internal/renderer/web-view/web-view-impl')
79-
if (contextIsolation) {
80-
Object.assign(isolatedWorldArgs, { webViewImpl })
81-
} else {
82-
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
83-
setupWebView(v8Util, webViewImpl)
84-
}
76+
// Inject content scripts.
77+
require('@electron/internal/renderer/content-scripts-injector')
8578
}
8679
}
8780

81+
// Load webview tag implementation.
82+
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, webviewTag, guestInstanceId)
83+
8884
// Pass the arguments to isolatedWorld.
8985
if (contextIsolation) {
9086
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
@@ -163,15 +159,3 @@ for (const preloadScript of preloadScripts) {
163159

164160
// Warn about security issues
165161
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
166-
167-
// Report focus/blur events of webview to browser.
168-
// Note that while Chromium content APIs have observer for focus/blur, they
169-
// unfortunately do not work for webview.
170-
if (guestInstanceId) {
171-
window.addEventListener('focus', () => {
172-
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId)
173-
})
174-
window.addEventListener('blur', () => {
175-
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId)
176-
})
177-
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
4+
const v8Util = process.atomBinding('v8_util')
5+
6+
function handleFocusBlur (guestInstanceId) {
7+
// Note that while Chromium content APIs have observer for focus/blur, they
8+
// unfortunately do not work for webview.
9+
10+
window.addEventListener('focus', () => {
11+
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId)
12+
})
13+
14+
window.addEventListener('blur', () => {
15+
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId)
16+
})
17+
}
18+
19+
module.exports = function (contextIsolation, webviewTag, guestInstanceId) {
20+
// Load webview tag implementation.
21+
if (webviewTag && guestInstanceId == null) {
22+
const webViewImpl = require('@electron/internal/renderer/web-view/web-view-impl')
23+
if (contextIsolation) {
24+
v8Util.setHiddenValue(window, 'web-view-impl', webViewImpl)
25+
} else {
26+
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
27+
setupWebView(v8Util, webViewImpl)
28+
}
29+
}
30+
31+
if (guestInstanceId) {
32+
// Report focus/blur events of webview to browser.
33+
handleFocusBlur(guestInstanceId)
34+
}
35+
}

lib/sandboxed_renderer/init.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ function preloadRequire (module) {
105105
throw new Error('module not found')
106106
}
107107

108+
// Process command line arguments.
108109
const { hasSwitch } = process.atomBinding('command_line')
109110

111+
const isBackgroundPage = hasSwitch('background-page')
112+
const contextIsolation = hasSwitch('context-isolation')
113+
110114
switch (window.location.protocol) {
111115
case 'chrome-devtools:': {
112116
// Override some inspector APIs.
@@ -115,22 +119,15 @@ switch (window.location.protocol) {
115119
}
116120
case 'chrome-extension:': {
117121
// Inject the chrome.* APIs that chrome extensions require
118-
const isBackgroundPage = hasSwitch('background-page')
119122
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
120123
break
121124
}
122125
}
123126

124-
if (binding.guestInstanceId) {
125-
process.guestInstanceId = parseInt(binding.guestInstanceId)
126-
}
127+
const guestInstanceId = binding.guestInstanceId && parseInt(binding.guestInstanceId)
127128

128-
// Don't allow recursive `<webview>`.
129-
if (!process.guestInstanceId && isWebViewTagEnabled) {
130-
const webViewImpl = require('@electron/internal/renderer/web-view/web-view-impl')
131-
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
132-
setupWebView(v8Util, webViewImpl)
133-
}
129+
// Load webview tag implementation.
130+
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, isWebViewTagEnabled, guestInstanceId)
134131

135132
const errorUtils = require('@electron/internal/common/error-utils')
136133

spec/webview-spec.js

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ describe('<webview> tag', function () {
7676
await emittedOnce(ipcMain, 'pong')
7777
})
7878

79+
it('works with sandbox', async () => {
80+
const w = await openTheWindow({
81+
show: false,
82+
webPreferences: {
83+
webviewTag: true,
84+
nodeIntegration: true,
85+
sandbox: true
86+
}
87+
})
88+
w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'))
89+
await emittedOnce(ipcMain, 'pong')
90+
})
91+
7992
it('works with contextIsolation', async () => {
8093
const w = await openTheWindow({
8194
show: false,
@@ -89,6 +102,20 @@ describe('<webview> tag', function () {
89102
await emittedOnce(ipcMain, 'pong')
90103
})
91104

105+
it('works with contextIsolation + sandbox', async () => {
106+
const w = await openTheWindow({
107+
show: false,
108+
webPreferences: {
109+
webviewTag: true,
110+
nodeIntegration: true,
111+
contextIsolation: true,
112+
sandbox: true
113+
}
114+
})
115+
w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'))
116+
await emittedOnce(ipcMain, 'pong')
117+
})
118+
92119
it('is disabled by default', async () => {
93120
const w = await openTheWindow({
94121
show: false,
@@ -1349,47 +1376,56 @@ describe('<webview> tag', function () {
13491376
if (div != null) div.remove()
13501377
})
13511378

1352-
it('emits resize events', async () => {
1353-
const firstResizeSignal = waitForEvent(webview, 'resize')
1354-
const domReadySignal = waitForEvent(webview, 'dom-ready')
1379+
const generateSpecs = (description, sandbox) => {
1380+
describe(description, () => {
1381+
it('emits resize events', async () => {
1382+
const firstResizeSignal = waitForEvent(webview, 'resize')
1383+
const domReadySignal = waitForEvent(webview, 'dom-ready')
13551384

1356-
webview.src = `file://${fixtures}/pages/a.html`
1357-
div.appendChild(webview)
1358-
document.body.appendChild(div)
1385+
webview.src = `file://${fixtures}/pages/a.html`
1386+
webview.webpreferences = `sandbox=${sandbox ? 'yes' : 'no'}`
1387+
div.appendChild(webview)
1388+
document.body.appendChild(div)
13591389

1360-
const firstResizeEvent = await firstResizeSignal
1361-
expect(firstResizeEvent.target).to.equal(webview)
1362-
expect(firstResizeEvent.newWidth).to.equal(100)
1363-
expect(firstResizeEvent.newHeight).to.equal(10)
1390+
const firstResizeEvent = await firstResizeSignal
1391+
expect(firstResizeEvent.target).to.equal(webview)
1392+
expect(firstResizeEvent.newWidth).to.equal(100)
1393+
expect(firstResizeEvent.newHeight).to.equal(10)
13641394

1365-
await domReadySignal
1395+
await domReadySignal
13661396

1367-
const secondResizeSignal = waitForEvent(webview, 'resize')
1397+
const secondResizeSignal = waitForEvent(webview, 'resize')
13681398

1369-
const newWidth = 1234
1370-
const newHeight = 789
1371-
div.style.width = `${newWidth}px`
1372-
div.style.height = `${newHeight}px`
1399+
const newWidth = 1234
1400+
const newHeight = 789
1401+
div.style.width = `${newWidth}px`
1402+
div.style.height = `${newHeight}px`
13731403

1374-
const secondResizeEvent = await secondResizeSignal
1375-
expect(secondResizeEvent.target).to.equal(webview)
1376-
expect(secondResizeEvent.newWidth).to.equal(newWidth)
1377-
expect(secondResizeEvent.newHeight).to.equal(newHeight)
1378-
})
1404+
const secondResizeEvent = await secondResizeSignal
1405+
expect(secondResizeEvent.target).to.equal(webview)
1406+
expect(secondResizeEvent.newWidth).to.equal(newWidth)
1407+
expect(secondResizeEvent.newHeight).to.equal(newHeight)
1408+
})
13791409

1380-
it('emits focus event', async () => {
1381-
const domReadySignal = waitForEvent(webview, 'dom-ready')
1382-
webview.src = `file://${fixtures}/pages/a.html`
1383-
document.body.appendChild(webview)
1410+
it('emits focus event', async () => {
1411+
const domReadySignal = waitForEvent(webview, 'dom-ready')
1412+
webview.src = `file://${fixtures}/pages/a.html`
1413+
webview.webpreferences = `sandbox=${sandbox ? 'yes' : 'no'}`
1414+
document.body.appendChild(webview)
13841415

1385-
await domReadySignal
1416+
await domReadySignal
13861417

1387-
// If this test fails, check if webview.focus() still works.
1388-
const focusSignal = waitForEvent(webview, 'focus')
1389-
webview.focus()
1418+
// If this test fails, check if webview.focus() still works.
1419+
const focusSignal = waitForEvent(webview, 'focus')
1420+
webview.focus()
13901421

1391-
await focusSignal
1392-
})
1422+
await focusSignal
1423+
})
1424+
})
1425+
}
1426+
1427+
generateSpecs('without sandbox', false)
1428+
generateSpecs('with sandbox', true)
13931429
})
13941430

13951431
describe('zoom behavior', () => {

0 commit comments

Comments
 (0)