diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 22963112a0..7f14fed95d 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -119,6 +119,17 @@ integration and can use node APIs like `require` and `process` to access low level system resources. Node integration is disabled by default in the guest page. +### `nodeintegrationinsubframes` + +```html + +``` + +Experimental option for enabling NodeJS support in sub-frames such as iframes +inside the `webview`. All your preloads will load for every iframe, you can +use `process.isMainFrame` to determine if you are in the main frame or not. +This option is disabled by default in the guest page. + ### `enableremotemodule` ```html diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index d0da665f75..9f232026f3 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -209,6 +209,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn const webPreferences = { guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, + nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false, enableRemoteModule: params.enableremotemodule, plugins: params.plugins, zoomFactor: embedder.getZoomFactor(), diff --git a/lib/renderer/web-view/web-view-attributes.ts b/lib/renderer/web-view/web-view-attributes.ts index 68425a3ecb..9d094bfbd5 100644 --- a/lib/renderer/web-view/web-view-attributes.ts +++ b/lib/renderer/web-view/web-view-attributes.ts @@ -280,6 +280,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this) + this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this) diff --git a/lib/renderer/web-view/web-view-constants.ts b/lib/renderer/web-view/web-view-constants.ts index 6a31dbc0d0..433d12ae3b 100644 --- a/lib/renderer/web-view/web-view-constants.ts +++ b/lib/renderer/web-view/web-view-constants.ts @@ -5,6 +5,7 @@ export const enum WEB_VIEW_CONSTANTS { ATTRIBUTE_SRC = 'src', ATTRIBUTE_HTTPREFERRER = 'httpreferrer', ATTRIBUTE_NODEINTEGRATION = 'nodeintegration', + ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES = 'nodeintegrationinsubframes', ATTRIBUTE_ENABLEREMOTEMODULE = 'enableremotemodule', ATTRIBUTE_PLUGINS = 'plugins', ATTRIBUTE_DISABLEWEBSECURITY = 'disablewebsecurity', diff --git a/lib/renderer/web-view/web-view-element.ts b/lib/renderer/web-view/web-view-element.ts index 098dca0ff8..cdb81308e2 100644 --- a/lib/renderer/web-view/web-view-element.ts +++ b/lib/renderer/web-view/web-view-element.ts @@ -24,6 +24,7 @@ const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER, WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT, WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, + WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, diff --git a/spec/api-subframe-spec.js b/spec/api-subframe-spec.js index df56873490..568fbf8067 100644 --- a/spec/api-subframe-spec.js +++ b/spec/api-subframe-spec.js @@ -10,6 +10,7 @@ const { BrowserWindow } = remote describe('renderer nodeIntegrationInSubFrames', () => { const generateTests = (description, webPreferences) => { describe(description, () => { + const fixtureSuffix = webPreferences.webviewTag ? '-webview' : '' let w beforeEach(async () => { @@ -18,11 +19,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { show: false, width: 400, height: 400, - webPreferences: { - preload: path.resolve(__dirname, 'fixtures/sub-frames/preload.js'), - nodeIntegrationInSubFrames: true, - ...webPreferences - } + webPreferences }) }) @@ -34,7 +31,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should load preload scripts in top level iframes', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`)) const [event1, event2] = await detailsPromise expect(event1[0].frameId).to.not.equal(event2[0].frameId) expect(event1[0].frameId).to.equal(event1[2]) @@ -43,7 +40,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should load preload scripts in nested iframes', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 3) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-with-frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`)) const [event1, event2, event3] = await detailsPromise expect(event1[0].frameId).to.not.equal(event2[0].frameId) expect(event1[0].frameId).to.not.equal(event3[0].frameId) @@ -55,7 +52,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should correctly reply to the main frame with using event.reply', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`)) const [event1] = await detailsPromise const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong') event1[0].reply('preload-ping') @@ -65,7 +62,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should correctly reply to the sub-frames with using event.reply', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`)) const [, event2] = await detailsPromise const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong') event2[0].reply('preload-ping') @@ -75,7 +72,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should correctly reply to the nested sub-frames with using event.reply', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 3) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-with-frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`)) const [, , event3] = await detailsPromise const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong') event3[0].reply('preload-ping') @@ -85,7 +82,7 @@ describe('renderer nodeIntegrationInSubFrames', () => { it('should not expose globals in main world', async () => { const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2) - w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html')) + w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`)) const details = await detailsPromise const senders = details.map(event => event[0].sender) await new Promise((resolve) => { @@ -108,8 +105,50 @@ describe('renderer nodeIntegrationInSubFrames', () => { }) } - generateTests('without sandbox', {}) - generateTests('with sandbox', { sandbox: true }) - generateTests('with contextIsolation', { contextIsolation: true }) - generateTests('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true }) + const generateConfigs = (webPreferences, ...permutations) => { + const configs = [{ webPreferences, names: [] }] + for (let i = 0; i < permutations.length; i++) { + const length = configs.length + for (let j = 0; j < length; j++) { + const newConfig = Object.assign({}, configs[j]) + newConfig.webPreferences = Object.assign({}, + newConfig.webPreferences, permutations[i].webPreferences) + newConfig.names = newConfig.names.slice(0) + newConfig.names.push(permutations[i].name) + configs.push(newConfig) + } + } + + return configs.map(config => { + if (config.names.length > 0) { + config.title = `with ${config.names.join(', ')} on` + } else { + config.title = `without anything special turned on` + } + delete config.names + + return config + }) + } + + generateConfigs( + { + preload: path.resolve(__dirname, 'fixtures/sub-frames/preload.js'), + nodeIntegrationInSubFrames: true + }, + { + name: 'sandbox', + webPreferences: { sandbox: true } + }, + { + name: 'context isolation', + webPreferences: { contextIsolation: true } + }, + { + name: 'webview', + webPreferences: { webviewTag: true, preload: false } + } + ).forEach(config => { + generateTests(config.title, config.webPreferences) + }) }) diff --git a/spec/fixtures/sub-frames/frame-container-webview.html b/spec/fixtures/sub-frames/frame-container-webview.html new file mode 100644 index 0000000000..aabc9e87e1 --- /dev/null +++ b/spec/fixtures/sub-frames/frame-container-webview.html @@ -0,0 +1,13 @@ + + + + + + + Document + + + This is the root page with a webview + + + diff --git a/spec/fixtures/sub-frames/frame-with-frame-container-webview.html b/spec/fixtures/sub-frames/frame-with-frame-container-webview.html new file mode 100644 index 0000000000..880862f967 --- /dev/null +++ b/spec/fixtures/sub-frames/frame-with-frame-container-webview.html @@ -0,0 +1,13 @@ + + + + + + + Document + + + This is the root page with a webview + + +