Browse Source

fix: emit focus/blur events for webview (backport: 3-0-x) (#14359)

* fix: emit focus/blur events for webview

* test: webview can emit focus event
trop[bot] 6 years ago
parent
commit
b1c22ba531

+ 5 - 0
lib/browser/guest-view-manager.js

@@ -26,6 +26,7 @@ const supportedWebViewEvents = [
   'did-navigate',
   'did-frame-navigate',
   'did-navigate-in-page',
+  'focus-change',
   'close',
   'crashed',
   'gpu-crashed',
@@ -322,6 +323,10 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedder
   attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params)
 })
 
+ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
+  event.sender.emit('focus-change', {}, focus, guestInstanceId)
+})
+
 // Returns WebContents from its guest id.
 const getGuest = function (guestInstanceId) {
   const guestInstance = guestInstances[guestInstanceId]

+ 13 - 0
lib/renderer/init.js

@@ -25,6 +25,7 @@ var v8Util = process.atomBinding('v8_util')
 v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter())
 
 // Use electron module after everything is ready.
+const {ipcRenderer} = require('electron')
 
 const {
   warnAboutNodeWithRemoteContent,
@@ -184,3 +185,15 @@ window.addEventListener('load', function loadHandler () {
 
   window.removeEventListener('load', loadHandler)
 })
+
+// Report focus/blur events of webview to browser.
+// Note that while Chromium content APIs have observer for focus/blur, they
+// unfortunately do not work for webview.
+if (process.guestInstanceId) {
+  window.addEventListener('focus', () => {
+    ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, process.guestInstanceId)
+  })
+  window.addEventListener('blur', () => {
+    ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, process.guestInstanceId)
+  })
+}

+ 3 - 0
lib/renderer/web-view/guest-view-internal.js

@@ -24,6 +24,7 @@ const WEB_VIEW_EVENTS = {
   'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'],
   'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
   'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
+  'focus-change': ['focus', 'guestInstanceId'],
   'close': [],
   'crashed': [],
   'gpu-crashed': [],
@@ -55,6 +56,8 @@ const dispatchEvent = function (webView, eventName, eventKey, ...args) {
   webView.dispatchEvent(domEvent)
   if (eventName === 'load-commit') {
     webView.onLoadCommit(domEvent)
+  } else if (eventName === 'focus-change') {
+    webView.onFocusChange(domEvent)
   }
 }
 

+ 19 - 21
lib/renderer/web-view/web-view.js

@@ -30,6 +30,7 @@ class WebViewImpl {
     v8Util.setHiddenValue(this.webviewNode, 'internal', this)
     this.elementAttached = false
     this.beforeFirstNavigation = true
+    this.hasFocus = false
 
     // Check for removed attributes.
     for (const attributeName of removedAttributes) {
@@ -41,13 +42,21 @@ class WebViewImpl {
     // on* Event handlers.
     this.on = {}
 
+    // Create internal iframe element.
     this.internalElement = this.createInternalElement()
     const shadowRoot = this.webviewNode.attachShadow({mode: 'open'})
     shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>'
     this.setupWebViewAttributes()
-    this.setupFocusPropagation()
     this.viewInstanceId = getNextId()
     shadowRoot.appendChild(this.internalElement)
+
+    // Provide access to contentWindow.
+    Object.defineProperty(this.webviewNode, 'contentWindow', {
+      get: () => {
+        return this.internalElement.contentWindow
+      },
+      enumerable: true
+    })
   }
 
   createInternalElement () {
@@ -91,26 +100,6 @@ class WebViewImpl {
     })
   }
 
-  setupFocusPropagation () {
-    if (!this.webviewNode.hasAttribute('tabIndex')) {
-      // <webview> needs a tabIndex in order to be focusable.
-      // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
-      // to allow <webview> to be focusable.
-      // See http://crbug.com/231664.
-      this.webviewNode.setAttribute('tabIndex', -1)
-    }
-
-    // Focus the BrowserPlugin when the <webview> takes focus.
-    this.webviewNode.addEventListener('focus', () => {
-      this.internalElement.focus()
-    })
-
-    // Blur the BrowserPlugin when the <webview> loses focus.
-    this.webviewNode.addEventListener('blur', () => {
-      this.internalElement.blur()
-    })
-  }
-
   // This observer monitors mutations to attributes of the <webview> and
   // updates the BrowserPlugin properties accordingly. In turn, updating
   // a BrowserPlugin property will update the corresponding BrowserPlugin
@@ -185,6 +174,15 @@ class WebViewImpl {
     }
   }
 
+  // Emits focus/blur events.
+  onFocusChange () {
+    const hasFocus = document.activeElement === this.webviewNode
+    if (hasFocus !== this.hasFocus) {
+      this.hasFocus = hasFocus
+      this.dispatchEvent(new Event(hasFocus ? 'focus' : 'blur'))
+    }
+  }
+
   onAttach (storagePartitionId) {
     return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId)
   }

+ 13 - 0
spec/webview-spec.js

@@ -1283,6 +1283,19 @@ describe('<webview> tag', function () {
       expect(secondResizeEvent.newWidth).to.equal(newWidth)
       expect(secondResizeEvent.newHeight).to.equal(newHeight)
     })
+
+    it('emits focus event', async () => {
+      const domReadySignal = waitForEvent(webview, 'dom-ready')
+      webview.src = `file://${fixtures}/pages/a.html`
+      document.body.appendChild(webview)
+
+      await domReadySignal
+
+      const focusSignal = waitForEvent(webview, 'focus')
+      webview.contentWindow.focus()
+
+      await focusSignal
+    })
   })
 
   describe('zoom behavior', () => {