Browse Source

Allow webview guests to be resized manually

This adds the `disableguestresize` property for webviews to prevent the
webview guest from reacting to size changes of the webview element. This
also partially documents the `webContents.setSize` function in order to
manually control the webview guest size.

These two features can be combined to improve resize performance for
e.g. webviews that span the entire window. This greatly reduces the lag
described in #6905.
Birunthan Mohanathas 8 years ago
parent
commit
2986b7bc4a

+ 9 - 0
docs/api/web-contents.md

@@ -1131,6 +1131,15 @@ win.webContents.on('did-finish-load', () => {
 
 Shows pop-up dictionary that searches the selected word on the page.
 
+#### `contents.setSize(options)`
+
+Controls the bounds of the [`<webview>`](web-view-tag.md) guest.
+
+* `options` Object
+  * `normal` Object (optional) - New size of the webview guest. This is can be used in combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) attribute to manually resize the webview guest.
+    * `width` Integer
+    * `height` Integer
+
 #### `contents.isOffscreen()`
 
 Returns `Boolean` - Indicates whether *offscreen rendering* is enabled.

+ 38 - 0
docs/api/web-view-tag.md

@@ -243,6 +243,44 @@ webview.
 The existing webview will see the `destroy` event and will then create a new
 webContents when a new url is loaded.
 
+### `disableguestresize`
+
+```html
+<webview src="https://www.github.com/" disableguestresize></webview>
+```
+
+Prevents the webview contents from resizing when the webview element itself is
+resized.
+
+This can be used in combination with
+[`webContents.setSize`](web-view-tag.md#contentssetsize) to manually
+resize the webview contents in reaction to e.g. window size changes. This can
+make resizing faster compared to relying on the webview element bounds to
+automatically resize the contents.
+
+```javascript
+const {webContents} = require('electron')
+
+// We assume that `win` points to a `BrowserWindow` instance containing a
+// `<webview>` with `disableguestresize`.
+
+win.on('resize', () => {
+  const [width, height] = win.getContentSize()
+  for (let wc of webContents.getAllWebContents()) {
+    // Check if `wc` belongs to a webview in the `win` window.
+    if (wc.hostWebContents &&
+        wc.hostWebContents.id === win.webContents.id) {
+      wc.setSize({
+        normal: {
+          width: width,
+          height: height
+        }
+      })
+    }
+  }
+})
+```
+
 ## Methods
 
 The `webview` tag has the following methods:

+ 1 - 0
lib/renderer/web-view/web-view-attributes.js

@@ -327,6 +327,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () {
   this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
   this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
   this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this)
+  this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE, this)
   this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this)
 
   const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]

+ 1 - 0
lib/renderer/web-view/web-view-constants.js

@@ -18,6 +18,7 @@ module.exports = {
   ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
   ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
   ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
+  ATTRIBUTE_DISABLEGUESTRESIZE: 'disableguestresize',
   ATTRIBUTE_WEBPREFERENCES: 'webpreferences',
 
   // Internal attribute.

+ 2 - 1
lib/renderer/web-view/web-view.js

@@ -172,7 +172,8 @@ class WebViewImpl {
     resizeEvent.newWidth = newSize.width
     resizeEvent.newHeight = newSize.height
     this.dispatchEvent(resizeEvent)
-    if (this.guestInstanceId) {
+    if (this.guestInstanceId &&
+        !this.attributes[webViewConstants.ATTRIBUTE_DISABLEGUESTRESIZE].getValue()) {
       guestViewInternal.setSize(this.guestInstanceId, {
         normal: newSize
       })

+ 9 - 0
spec/fixtures/pages/resize.html

@@ -0,0 +1,9 @@
+<html>
+<script type="text/javascript" charset="utf-8">
+  const {ipcRenderer} = require('electron')
+
+  window.addEventListener('resize', () => {
+    ipcRenderer.send('webview-guest-resize', window.innerWidth, window.innerHeight)
+  }, false);
+</script>
+</html>

+ 20 - 0
spec/fixtures/pages/webview-guest-resize.html

@@ -0,0 +1,20 @@
+<html>
+<style>
+* {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+}
+</style>
+<body>
+<webview id="webview" nodeintegration src="resize.html"/>
+</body>
+<script type="text/javascript" charset="utf-8">
+  const {ipcRenderer} = require('electron')
+
+  const webview = document.getElementById('webview')
+  webview.addEventListener('resize', event => {
+    ipcRenderer.send('webview-element-resize', event.newWidth, event.newHeight)
+  }, false)
+</script>
+</html>

+ 20 - 0
spec/fixtures/pages/webview-no-guest-resize.html

@@ -0,0 +1,20 @@
+<html>
+<style>
+* {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+}
+</style>
+<body>
+<webview id="webview" nodeintegration disableguestresize src="resize.html"/>
+</body>
+<script type="text/javascript" charset="utf-8">
+  const {ipcRenderer} = require('electron')
+
+  const webview = document.getElementById('webview')
+  webview.addEventListener('resize', event => {
+    ipcRenderer.send('webview-element-resize', event.newWidth, event.newHeight)
+  }, false)
+</script>
+</html>

+ 112 - 2
spec/webview-spec.js

@@ -2,11 +2,11 @@ const assert = require('assert')
 const path = require('path')
 const http = require('http')
 const url = require('url')
-const {app, session, getGuestWebContents, ipcMain, BrowserWindow} = require('electron').remote
+const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = require('electron').remote
 const {closeWindow} = require('./window-helpers')
 
 describe('<webview> tag', function () {
-  this.timeout(20000)
+  this.timeout(60000)
 
   var fixtures = path.join(__dirname, 'fixtures')
 
@@ -1346,4 +1346,114 @@ describe('<webview> tag', function () {
       document.body.appendChild(div)
     })
   })
+
+  describe('disableguestresize attribute', () => {
+    it('does not have attribute by default', () => {
+      document.body.appendChild(webview)
+      assert(!webview.hasAttribute('disableguestresize'))
+    })
+
+    it('resizes guest when attribute is not present', done => {
+      w = new BrowserWindow({ show: false, width: 200, height: 200 })
+      w.loadURL('file://' + fixtures + '/pages/webview-guest-resize.html')
+
+      w.webContents.once('did-finish-load', () => {
+        const CONTENT_SIZE = 300
+
+        const elementResizePromise = new Promise(resolve => {
+          ipcMain.once('webview-element-resize', (event, width, height) => {
+            assert.equal(width, CONTENT_SIZE)
+            resolve()
+          })
+        })
+
+        const guestResizePromise = new Promise(resolve => {
+          ipcMain.once('webview-guest-resize', (event, width, height) => {
+            assert.equal(width, CONTENT_SIZE)
+            resolve()
+          })
+        })
+
+        Promise.all([elementResizePromise, guestResizePromise]).then(() => done())
+
+        w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
+      })
+    })
+
+    it('does not resize guest when attribute is present', done => {
+      w = new BrowserWindow({ show: false, width: 200, height: 200 })
+      w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
+
+      w.webContents.once('did-finish-load', () => {
+        const CONTENT_SIZE = 300
+
+        const elementResizePromise = new Promise(resolve => {
+          ipcMain.once('webview-element-resize', (event, width, height) => {
+            assert.equal(width, CONTENT_SIZE)
+            resolve()
+          })
+        })
+
+        const noGuestResizePromise = new Promise(resolve => {
+          const onGuestResize = (event, width, height) => {
+            assert(false, 'unexpected guest resize message')
+          }
+          ipcMain.once('webview-guest-resize', onGuestResize)
+
+          setTimeout(() => {
+            ipcMain.removeListener('webview-guest-resize', onGuestResize)
+            resolve()
+          }, 500)
+        })
+
+        Promise.all([elementResizePromise, noGuestResizePromise]).then(() => done())
+
+        w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
+      })
+    })
+
+    it('dispatches element resize event even when attribute is present', done => {
+      w = new BrowserWindow({ show: false, width: 200, height: 200 })
+      w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
+
+      w.webContents.once('did-finish-load', () => {
+        const CONTENT_SIZE = 300
+
+        ipcMain.once('webview-element-resize', (event, width, height) => {
+          assert.equal(width, CONTENT_SIZE)
+          done()
+        })
+
+        w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
+      })
+    })
+
+    it('can be manually resized with setSize even when attribute is present', done => {
+      w = new BrowserWindow({ show: false, width: 200, height: 200 })
+      w.loadURL('file://' + fixtures + '/pages/webview-no-guest-resize.html')
+
+      w.webContents.once('did-finish-load', () => {
+        const GUEST_WIDTH = 10
+        const GUEST_HEIGHT = 20
+
+        ipcMain.once('webview-guest-resize', (event, width, height) => {
+          assert.equal(width, GUEST_WIDTH)
+          assert.equal(height, GUEST_HEIGHT)
+          done()
+        })
+
+        for (let wc of webContents.getAllWebContents()) {
+          if (wc.hostWebContents &&
+              wc.hostWebContents.id === w.webContents.id) {
+            wc.setSize({
+              normal: {
+                width: GUEST_WIDTH,
+                height: GUEST_HEIGHT
+              }
+            })
+          }
+        }
+      })
+    })
+  })
 })