diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index f495ed80fa4e..a84cca7a005c 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -70,6 +70,7 @@ #include "third_party/WebKit/public/web/WebFindOptions.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/display/screen.h" +#include "ui/events/keycodes/dom/keycode_converter.h" #if !defined(OS_MACOSX) #include "ui/aura/window.h" @@ -488,6 +489,34 @@ void WebContents::HandleKeyboardEvent( } } +bool WebContents::PreHandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + const char* type = + event.type == blink::WebInputEvent::Type::RawKeyDown ? "keyDown" : + event.type == blink::WebInputEvent::Type::KeyUp ? "keyUp" : + nullptr; + if (!type) { + // This should never happen. + assert(false); + return false; + } + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate()); + dict.Set("type", type); + dict.Set("key", ui::KeycodeConverter::DomKeyToKeyString(event.domKey)); + + using Modifiers = blink::WebInputEvent::Modifiers; + dict.Set("isAutoRepeat", (event.modifiers & Modifiers::IsAutoRepeat) != 0); + dict.Set("shift", (event.modifiers & Modifiers::ShiftKey) != 0); + dict.Set("control", (event.modifiers & Modifiers::ControlKey) != 0); + dict.Set("alt", (event.modifiers & Modifiers::AltKey) != 0); + dict.Set("meta", (event.modifiers & Modifiers::MetaKey) != 0); + + return Emit("before-input-event", dict); +} + void WebContents::EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) { auto permission_helper = diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index ff2823a282db..68669e7f4b10 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -244,6 +244,9 @@ class WebContents : public mate::TrackableObject, void HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) override; + bool PreHandleKeyboardEvent(content::WebContents* source, + const content::NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) override; void EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) override; void ExitFullscreenModeForTab(content::WebContents* source) override; diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 30fcca58d9cf..8f4ca02ea137 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -232,6 +232,26 @@ Emitted when a plugin process has crashed. Emitted when `webContents` is destroyed. +#### Event: 'before-input-event' + +Returns: + +* `event` Event +* `input` Object - Input properties + * `type` String - Either `keyUp` or `keyDown` + * `key` String - Equivalent to [KeyboardEvent.key](keyboardevent) + * `isAutoRepeat` Boolean - Equivalent to [KeyboardEvent.repeat](keyboardevent) + * `shift` Boolean - Equivalent to [KeyboardEvent.shiftKey](keyboardevent) + * `control` Boolean - Equivalent to [KeyboardEvent.controlKey](keyboardevent) + * `alt` Boolean - Equivalent to [KeyboardEvent.altKey](keyboardevent) + * `meta` Boolean - Equivalent to [KeyboardEvent.metaKey](keyboardevent) + +Emitted before dispatching the `keydown` and `keyup` events in the page. +Calling `event.preventDefault` will prevent the page `keydown`/`keyup` events +from being dispatched. + +[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent + #### Event: 'devtools-opened' Emitted when DevTools is opened. diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index d7a2ae50f3cf..b3875367206a 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -98,6 +98,124 @@ describe('webContents module', function () { }) }) + describe('will-navigate event', function () { + it('can be prevented', (done) => { + const targetURL = 'file://' + path.join(__dirname, 'fixtures', 'pages', 'location-change.html') + w.loadURL(targetURL) + w.webContents.once('did-finish-load', () => { + + w.webContents.on('will-navigate', (event, url) => { + assert.ok(url, targetURL) + }) + + setTimeout(done, 5000) + + w.webContents.on('did-navigate', (event, url) => { + assert.ok(url, targetURL) + }) + }) + }) + }) + + describe('before-input-event event', () => { + it('can prevent document keyboard events', (done) => { + w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'key-events.html')) + w.webContents.once('did-finish-load', () => { + w.webContents.once('before-input-event', (event, input) => { + assert.equal(input.key, 'a') + event.preventDefault() + }) + + ipcMain.once('keydown', (event, key) => { + assert.equal(key, 'b') + done() + }) + + w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'a'}) + w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'b'}) + }) + }) + + it('has the correct properties', (done) => { + w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'base-page.html')) + w.webContents.once('did-finish-load', () => { + const testBeforeInput = (opts) => { + return new Promise((resolve, reject) => { + w.webContents.once('before-input-event', (event, input) => { + assert.equal(input.type, opts.type) + assert.equal(input.key, opts.key) + assert.equal(input.isAutoRepeat, opts.isAutoRepeat) + assert.equal(input.shift, opts.shift) + assert.equal(input.control, opts.control) + assert.equal(input.alt, opts.alt) + assert.equal(input.meta, opts.meta) + resolve() + }) + + const modifiers = [] + if (opts.shift) modifiers.push('shift') + if (opts.control) modifiers.push('control') + if (opts.alt) modifiers.push('alt') + if (opts.meta) modifiers.push('meta') + if (opts.isAutoRepeat) modifiers.push('isAutoRepeat') + + w.webContents.sendInputEvent({ + type: opts.type, + keyCode: opts.keyCode, + modifiers: modifiers + }) + }) + } + + Promise.resolve().then(() => { + return testBeforeInput({ + type: 'keyDown', + key: 'A', + keyCode: 'a', + shift: true, + control: true, + alt: true, + meta: true, + isAutoRepeat: true + }) + }).then(() => { + return testBeforeInput({ + type: 'keyUp', + key: '.', + keyCode: '.', + shift: false, + control: true, + alt: true, + meta: false, + isAutoRepeat: false + }) + }).then(() => { + return testBeforeInput({ + type: 'keyUp', + key: '!', + keyCode: '1', + shift: true, + control: false, + alt: false, + meta: true, + isAutoRepeat: false + }) + }).then(() => { + return testBeforeInput({ + type: 'keyUp', + key: 'Tab', + keyCode: 'Tab', + shift: false, + control: true, + alt: false, + meta: false, + isAutoRepeat: true + }) + }).then(done).catch(done) + }) + }) + }) + describe('sendInputEvent(event)', function () { beforeEach(function (done) { w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'key-events.html'))