Browse Source

feat: enable NodeIntegrationInSubFrames for webview (#17226)

* feat: enable nodeIntegrationInSubFrames for webview

* test: add tests

* docs: document webview's nodeintegrationinsubframes

* lint: fix indent

* fix: resolve some merge bloopers
Heilig Benedek 6 years ago
parent
commit
43ef561d48

+ 11 - 0
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
+<webview src="http://www.google.com/" nodeintegrationinsubframes></webview>
+```
+
+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

+ 1 - 0
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(),

+ 1 - 0
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)

+ 1 - 0
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',

+ 1 - 0
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,

+ 54 - 15
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)
+  })
 })

+ 13 - 0
spec/fixtures/sub-frames/frame-container-webview.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>Document</title>
+</head>
+<body>
+  This is the root page with a webview
+  <webview src="./frame-container.html" sandbox nodeIntegrationInSubFrames preload="./preload.js"></webview>
+</body>
+</html>

+ 13 - 0
spec/fixtures/sub-frames/frame-with-frame-container-webview.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>Document</title>
+</head>
+<body>
+  This is the root page with a webview
+  <webview src="./frame-with-frame-container.html" sandbox nodeIntegrationInSubFrames preload="./preload.js"></webview>
+</body>
+</html>