diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 85d2ee83406e..37876c608966 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -534,14 +534,55 @@ To only prevent the menu shortcuts, use [`setIgnoreMenuShortcuts`](#contentssetignoremenushortcutsignore): ```js -const { BrowserWindow } = require('electron') +const { app, BrowserWindow } = require('electron') -const win = new BrowserWindow({ width: 800, height: 600 }) +app.whenReady().then(() => { + const win = new BrowserWindow({ width: 800, height: 600 }) -win.webContents.on('before-input-event', (event, input) => { - // For example, only enable application menu keyboard shortcuts when - // Ctrl/Cmd are down. - win.webContents.setIgnoreMenuShortcuts(!input.control && !input.meta) + win.webContents.on('before-input-event', (event, input) => { + // Enable application menu keyboard shortcuts when Ctrl/Cmd are down. + win.webContents.setIgnoreMenuShortcuts(!input.control && !input.meta) + }) +}) +``` + +#### Event: 'before-mouse-event' + +Returns: + +* `event` Event +* `mouse` [MouseInputEvent](structures/mouse-input-event.md) + +Emitted before dispatching mouse events in the page. + +Calling `event.preventDefault` will prevent the page mouse events. + +```js +const { app, BrowserWindow } = require('electron') + +app.whenReady().then(() => { + const win = new BrowserWindow({ width: 800, height: 600 }) + + win.webContents.on('before-mouse-event', (event, mouse) => { + // Prevent mouseDown events. + if (mouse.type === 'mouseDown') { + console.log(mouse) + /* + { + type: 'mouseDown', + clickCount: 1, + movementX: 0, + movementY: 0, + button: 'left', + x: 632.359375, + y: 480.6875, + globalX: 168.359375, + globalY: 193.6875 + } + */ + event.preventDefault() + } + }) }) ``` diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index b984d57f865b..271fe13ec49e 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -1356,6 +1356,12 @@ bool WebContents::PlatformHandleKeyboardEvent( } #endif +bool WebContents::PreHandleMouseEvent(content::WebContents* source, + const blink::WebMouseEvent& event) { + // |true| means that the event should be prevented. + return Emit("before-mouse-event", event); +} + content::KeyboardEventProcessingResult WebContents::PreHandleKeyboardEvent( content::WebContents* source, const input::NativeWebKeyboardEvent& event) { diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index 00547c5a8246..d31c94c97f4c 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -533,6 +533,8 @@ class WebContents final : public ExclusiveAccessContext, const input::NativeWebKeyboardEvent& event) override; bool PlatformHandleKeyboardEvent(content::WebContents* source, const input::NativeWebKeyboardEvent& event); + bool PreHandleMouseEvent(content::WebContents* source, + const blink::WebMouseEvent& event) override; content::KeyboardEventProcessingResult PreHandleKeyboardEvent( content::WebContents* source, const input::NativeWebKeyboardEvent& event) override; diff --git a/shell/common/gin_converters/blink_converter.cc b/shell/common/gin_converters/blink_converter.cc index f172c74cfd2e..2b79b6b2ac70 100644 --- a/shell/common/gin_converters/blink_converter.cc +++ b/shell/common/gin_converters/blink_converter.cc @@ -146,6 +146,26 @@ struct Converter { }); return FromV8WithLowerLookup(isolate, val, Lookup, out); } + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebMouseEvent::Button& in) { + switch (in) { + case blink::WebMouseEvent::Button::kLeft: + return StringToV8(isolate, "left"); + case blink::WebMouseEvent::Button::kMiddle: + return StringToV8(isolate, "middle"); + case blink::WebMouseEvent::Button::kRight: + return StringToV8(isolate, "right"); + case blink::WebMouseEvent::Button::kNoButton: + return StringToV8(isolate, "none"); + case blink::WebMouseEvent::Button::kBack: + return StringToV8(isolate, "back"); + case blink::WebMouseEvent::Button::kForward: + return StringToV8(isolate, "forward"); + case blink::WebMouseEvent::Button::kEraser: + return StringToV8(isolate, "eraser"); + } + return v8::Null(isolate); + } }; // clang-format off @@ -246,6 +266,9 @@ v8::Local Converter::ToV8( if (blink::WebInputEvent::IsKeyboardEventType(in.GetType())) return gin::ConvertToV8(isolate, *static_cast(&in)); + else if (blink::WebInputEvent::IsMouseEventType(in.GetType())) + return gin::ConvertToV8(isolate, + *static_cast(&in)); return gin::DataObjectBuilder(isolate) .Set("type", in.GetType()) .Set("modifiers", ModifiersToArray(in.GetModifiers())) @@ -379,6 +402,28 @@ bool Converter::FromV8(v8::Isolate* isolate, return true; } +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const blink::WebMouseEvent& in) { + auto dict = gin_helper::Dictionary::CreateEmpty(isolate); + + dict.Set("type", in.GetType()); + dict.Set("clickCount", in.ClickCount()); + dict.Set("movementX", in.movement_x); + dict.Set("movementY", in.movement_y); + dict.Set("button", in.button); + + gfx::PointF position_in_screen = in.PositionInScreen(); + dict.Set("globalX", position_in_screen.x()); + dict.Set("globalY", position_in_screen.y()); + + gfx::PointF position_in_widget = in.PositionInWidget(); + dict.Set("x", position_in_widget.x()); + dict.Set("y", position_in_widget.y()); + + return dict.GetHandle(); +} + bool Converter::FromV8( v8::Isolate* isolate, v8::Local val, diff --git a/shell/common/gin_converters/blink_converter.h b/shell/common/gin_converters/blink_converter.h index f293181c4a6b..6197a09de2ce 100644 --- a/shell/common/gin_converters/blink_converter.h +++ b/shell/common/gin_converters/blink_converter.h @@ -57,6 +57,8 @@ struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, blink::WebMouseEvent* out); + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebMouseEvent& in); }; template <> diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index 69e462c1df2c..4d0a3eebda20 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -1071,6 +1071,76 @@ describe('webContents module', () => { }); }); + describe('before-mouse-event event', () => { + afterEach(closeAllWindows); + it('can prevent document mouse events', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); + await w.loadFile(path.join(fixturesPath, 'pages', 'mouse-events.html')); + const mouseDown = new Promise(resolve => { + ipcMain.once('mousedown', (event, button) => resolve(button)); + }); + w.webContents.once('before-mouse-event', (event, input) => { + if (input.button === 'left') event.preventDefault(); + }); + w.webContents.sendInputEvent({ type: 'mouseDown', button: 'left', x: 100, y: 100 }); + w.webContents.sendInputEvent({ type: 'mouseDown', button: 'right', x: 100, y: 100 }); + expect(await mouseDown).to.equal(2); // Right button is 2 + }); + + it('has the correct properties', async () => { + const w = new BrowserWindow({ show: false }); + await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html')); + const testBeforeMouse = async (opts: Electron.MouseInputEvent) => { + const p = once(w.webContents, 'before-mouse-event'); + w.webContents.sendInputEvent({ + type: opts.type, + button: opts.button, + x: opts.x, + y: opts.y, + globalX: opts.globalX, + globalY: opts.globalY, + clickCount: opts.clickCount + }); + const [, input] = await p; + + expect(input.type).to.equal(opts.type); + expect(input.button).to.equal(opts.button); + expect(input.x).to.equal(opts.x); + expect(input.y).to.equal(opts.y); + expect(input.globalX).to.equal(opts.globalX); + expect(input.globalY).to.equal(opts.globalY); + expect(input.clickCount).to.equal(opts.clickCount); + }; + await testBeforeMouse({ + type: 'mouseDown', + button: 'left', + x: 100, + y: 100, + globalX: 200, + globalY: 200, + clickCount: 1 + }); + await testBeforeMouse({ + type: 'mouseUp', + button: 'right', + x: 150, + y: 150, + globalX: 250, + globalY: 250, + clickCount: 2 + }); + await testBeforeMouse({ + type: 'mouseMove', + button: 'middle', + x: 200, + y: 200, + globalX: 300, + globalY: 300, + clickCount: 0 + }); + }); + }); + describe('before-input-event event', () => { afterEach(closeAllWindows); it('can prevent document keyboard events', async () => { diff --git a/spec/fixtures/pages/mouse-events.html b/spec/fixtures/pages/mouse-events.html new file mode 100644 index 000000000000..51610f4a1e86 --- /dev/null +++ b/spec/fixtures/pages/mouse-events.html @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file