Browse Source

fix: do not allow the window to grab focus when tabbing / shift+tabbing (#16042)

* fix: do not allow the window to grab focus when tabbing / shift+tabbing

* test: add tests.
Pedro Pontes 6 years ago
parent
commit
9c783f53ba

+ 13 - 0
atom/browser/common_web_contents_delegate.cc

@@ -346,6 +346,19 @@ blink::WebSecurityStyle CommonWebContentsDelegate::GetSecurityStyle(
                                           security_style_explanations);
 }
 
+bool CommonWebContentsDelegate::TakeFocus(content::WebContents* source,
+                                          bool reverse) {
+  if (source && source->GetOutermostWebContents() == source) {
+    // If this is the outermost web contents and the user has tabbed or
+    // shift + tabbed through all the elements, reset the focus back to
+    // the first or last element so that it doesn't stay in the body.
+    source->FocusThroughTabTraversal(reverse);
+    return true;
+  }
+
+  return false;
+}
+
 void CommonWebContentsDelegate::DevToolsSaveToFile(const std::string& url,
                                                    const std::string& content,
                                                    bool save_as) {

+ 1 - 0
atom/browser/common_web_contents_delegate.h

@@ -98,6 +98,7 @@ class CommonWebContentsDelegate : public content::WebContentsDelegate,
   blink::WebSecurityStyle GetSecurityStyle(
       content::WebContents* web_contents,
       content::SecurityStyleExplanations* explanations) override;
+  bool TakeFocus(content::WebContents* source, bool reverse) override;
   void HandleKeyboardEvent(
       content::WebContents* source,
       const content::NativeWebKeyboardEvent& event) override;

+ 105 - 0
spec/chromium-spec.js

@@ -1410,6 +1410,111 @@ describe('chromium feature', () => {
       await new Promise((resolve) => { utter.onend = resolve })
     })
   })
+
+  describe('focus handling', () => {
+    let webviewContents = null
+
+    beforeEach(async () => {
+      w = new BrowserWindow({
+        show: true
+      })
+
+      const webviewReady = emittedOnce(w.webContents, 'did-attach-webview')
+      await w.loadFile(path.join(fixtures, 'pages', 'tab-focus-loop-elements.html'))
+      const [, wvContents] = await webviewReady
+      webviewContents = wvContents
+      await emittedOnce(webviewContents, 'did-finish-load')
+      w.focus()
+    })
+
+    afterEach(() => {
+      webviewContents = null
+    })
+
+    const expectFocusChange = async () => {
+      const [, focusedElementId] = await emittedOnce(ipcMain, 'focus-changed')
+      return focusedElementId
+    }
+
+    describe('a TAB press', () => {
+      const tabPressEvent = {
+        type: 'keyDown',
+        keyCode: 'Tab'
+      }
+
+      it('moves focus to the next focusable item', async () => {
+        let focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(tabPressEvent)
+        let focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-1', `should start focused in element-1, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(tabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(tabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-wv-element-1', `focus should've moved to the webview's element-1, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        webviewContents.sendInputEvent(tabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-wv-element-2', `focus should've moved to the webview's element-2, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        webviewContents.sendInputEvent(tabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-3', `focus should've moved to element-3, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(tabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-1', `focus should've looped back to element-1, it's instead in ${focusedElementId}`)
+      })
+    })
+
+    describe('a SHIFT + TAB press', () => {
+      const shiftTabPressEvent = {
+        type: 'keyDown',
+        modifiers: ['Shift'],
+        keyCode: 'Tab'
+      }
+
+      it('moves focus to the previous focusable item', async () => {
+        let focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(shiftTabPressEvent)
+        let focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-3', `should start focused in element-3, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(shiftTabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-wv-element-2', `focus should've moved to the webview's element-2, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        webviewContents.sendInputEvent(shiftTabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-wv-element-1', `focus should've moved to the webview's element-1, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        webviewContents.sendInputEvent(shiftTabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-2', `focus should've moved to element-2, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(shiftTabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-1', `focus should've moved to element-1, it's instead in ${focusedElementId}`)
+
+        focusChange = expectFocusChange()
+        w.webContents.sendInputEvent(shiftTabPressEvent)
+        focusedElementId = await focusChange
+        assert.strictEqual(focusedElementId, 'BUTTON-element-3', `focus should've looped back to element-3, it's instead in ${focusedElementId}`)
+      })
+    })
+  })
 })
 
 describe('font fallback', () => {

+ 25 - 0
spec/fixtures/pages/tab-focus-loop-elements-wv.html

@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title></title>
+  <body>
+    <div>
+      <button id="wv-element-1">Button 1</button>
+      <button id="wv-element-2">Button 2</button>
+    </div>
+    <script>
+      const { ipcRenderer } = require('electron')
+
+      function handleFocusChange(event) {
+        if (event.target.tagName) {
+          const elementId = event.target.id ? `-${event.target.id}` : ''
+          const elementIdentifier = `${event.target.tagName}${elementId}`
+          ipcRenderer.send('focus-changed', elementIdentifier)
+        }
+      }
+
+      addEventListener('focus', handleFocusChange, true)
+    </script>
+  </body>
+</html>

+ 27 - 0
spec/fixtures/pages/tab-focus-loop-elements.html

@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title></title>
+  <script>
+    const { ipcRenderer } = require('electron')
+
+    function handleFocusChange(event) {
+      if (event.target.tagName && event.target.tagName !== 'WEBVIEW') {
+        const elementId = event.target.id ? `-${event.target.id}` : ''
+        const elementIdentifier = `${event.target.tagName}${elementId}`
+        ipcRenderer.send('focus-changed', elementIdentifier)
+      }
+    }
+
+    addEventListener('focus', handleFocusChange, true)
+  </script>
+  <body>
+    <div>
+      <button id="element-1">Button 1</button>
+      <button id="element-2">Button 2</button>
+      <webview src="tab-focus-loop-elements-wv.html" nodeintegration></webview>
+      <button id="element-3">Button 3</button>
+    </div>
+  </body>
+</html>