feat: allow intercepting mouse events (#47364)
* feat: allow intercepting mouse events Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> * test: add specs Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> * Update spec/api-web-contents-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
2e2d1f1612
commit
3fdb77abf1
7 changed files with 192 additions and 6 deletions
|
@ -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.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()
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -146,6 +146,26 @@ struct Converter<blink::WebMouseEvent::Button> {
|
|||
});
|
||||
return FromV8WithLowerLookup(isolate, val, Lookup, out);
|
||||
}
|
||||
static v8::Local<v8::Value> 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<v8::Value> Converter<blink::WebInputEvent>::ToV8(
|
|||
if (blink::WebInputEvent::IsKeyboardEventType(in.GetType()))
|
||||
return gin::ConvertToV8(isolate,
|
||||
*static_cast<const blink::WebKeyboardEvent*>(&in));
|
||||
else if (blink::WebInputEvent::IsMouseEventType(in.GetType()))
|
||||
return gin::ConvertToV8(isolate,
|
||||
*static_cast<const blink::WebMouseEvent*>(&in));
|
||||
return gin::DataObjectBuilder(isolate)
|
||||
.Set("type", in.GetType())
|
||||
.Set("modifiers", ModifiersToArray(in.GetModifiers()))
|
||||
|
@ -379,6 +402,28 @@ bool Converter<blink::WebMouseEvent>::FromV8(v8::Isolate* isolate,
|
|||
return true;
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Converter<blink::WebMouseEvent>::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<blink::WebMouseWheelEvent>::FromV8(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
|
|
|
@ -57,6 +57,8 @@ struct Converter<blink::WebMouseEvent> {
|
|||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
blink::WebMouseEvent* out);
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const blink::WebMouseEvent& in);
|
||||
};
|
||||
|
||||
template <>
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
20
spec/fixtures/pages/mouse-events.html
vendored
Normal file
20
spec/fixtures/pages/mouse-events.html
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
|
||||
<body>
|
||||
<input type="text" id="input" autofocus />
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
document.onmousedown = function (e) {
|
||||
ipcRenderer.send('mousedown', e.button, e.x, e.screenX, e.y, e.screenY)
|
||||
}
|
||||
document.onmouseup = function (e) {
|
||||
ipcRenderer.send('mouseup', e.button, e.x, e.screenX, e.y, e.screenY)
|
||||
}
|
||||
document.onmousemove = function (e) {
|
||||
ipcRenderer.send('mousemove', e.button, e.x, e.screenX, e.y, e.screenY)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue