Allow webview guests to be resized manually
authorBirunthan Mohanathas <birunthan@mohanathas.com>
Tue, 25 Oct 2016 05:17:38 +0000 (22:17 -0700)
committerBirunthan Mohanathas <birunthan@mohanathas.com>
Tue, 15 Nov 2016 19:00:09 +0000 (11:00 -0800)
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.

docs/api/web-contents.md
docs/api/web-view-tag.md
lib/renderer/web-view/web-view-attributes.js
lib/renderer/web-view/web-view-constants.js
lib/renderer/web-view/web-view.js
spec/fixtures/pages/resize.html [new file with mode: 0644]
spec/fixtures/pages/webview-guest-resize.html [new file with mode: 0644]
spec/fixtures/pages/webview-no-guest-resize.html [new file with mode: 0644]
spec/webview-spec.js

index 322d8fba4c53fa18bb44c062d3e3b3c8c331bcd8..f4c1f58d3a3e2ff76a26c942fb5dc3dec6498014 100644 (file)
@@ -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.
index 78ed927abca9ee3f5cef7cf73161b98edc80f887..84e0efb2528b8bed257579a3358b6d6d9cedfcf9 100644 (file)
@@ -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:
index f42fcc5e781b932432233773ace7027da1d45d01..204046bd60542657e9213f2b12e5c126ee10ea33 100644 (file)
@@ -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]
index 80fcaf91f24b3fcf60cf9536fa6d861d0e9458b1..bf2601822d3319f1e30bb90b6dc7542cdba35ab6 100644 (file)
@@ -18,6 +18,7 @@ module.exports = {
   ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
   ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
   ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
+  ATTRIBUTE_DISABLEGUESTRESIZE: 'disableguestresize',
   ATTRIBUTE_WEBPREFERENCES: 'webpreferences',
 
   // Internal attribute.
index 27cb07ffcad7db98f45416a2a0c5bcfdb5febb22..c8033bea0468fc1a9da2d4509384534ea4b99bc8 100644 (file)
@@ -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
       })
diff --git a/spec/fixtures/pages/resize.html b/spec/fixtures/pages/resize.html
new file mode 100644 (file)
index 0000000..08f45b0
--- /dev/null
@@ -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>
diff --git a/spec/fixtures/pages/webview-guest-resize.html b/spec/fixtures/pages/webview-guest-resize.html
new file mode 100644 (file)
index 0000000..2970db0
--- /dev/null
@@ -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>
diff --git a/spec/fixtures/pages/webview-no-guest-resize.html b/spec/fixtures/pages/webview-no-guest-resize.html
new file mode 100644 (file)
index 0000000..1e13efd
--- /dev/null
@@ -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>
index cc848f9ac5478940c893fa595d44915e170b1ce3..f5741128d807d4ebe9c0084960fdbf7feb4ca84a 100644 (file)
@@ -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
+              }
+            })
+          }
+        }
+      })
+    })
+  })
 })