Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow cancelling of bluetooth requests #37720

Merged
merged 1 commit into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 15 additions & 8 deletions docs/api/web-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -713,20 +713,24 @@ Returns:
* `callback` Function
* `deviceId` string

Emitted when bluetooth device needs to be selected on call to
`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api
`webBluetooth` should be enabled. If `event.preventDefault` is not called,
first available device will be selected. `callback` should be called with
`deviceId` to be selected, passing empty string to `callback` will
cancel the request.
Emitted when a bluetooth device needs to be selected when a call to
`navigator.bluetooth.requestDevice` is made. `callback` should be called with
the `deviceId` of the device to be selected. Passing an empty string to
`callback` will cancel the request.

If no event listener is added for this event, all bluetooth requests will be cancelled.
If an event listener is not added for this event, or if `event.preventDefault`
is not called when handling this event, the first available device will be
automatically selected.

Due to the nature of bluetooth, scanning for devices when
`navigator.bluetooth.requestDevice` is called may take time and will cause
`select-bluetooth-device` to fire multiple times until `callback` is called
with either a device id or an empty string to cancel the request.

```javascript title='main.js'
const { app, BrowserWindow } = require('electron')

let win = null
app.commandLine.appendSwitch('enable-experimental-web-platform-features')

app.whenReady().then(() => {
win = new BrowserWindow({ width: 800, height: 600 })
Expand All @@ -736,6 +740,9 @@ app.whenReady().then(() => {
return device.deviceName === 'test'
})
if (!result) {
// The device wasn't found so we need to either wait longer (eg until the
// device is turned on) or cancel the request by calling the callback
// with an empty string.
callback('')
} else {
callback(result.deviceId)
Expand Down
1 change: 1 addition & 0 deletions docs/fiddles/features/web-bluetooth/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<h1>Web Bluetooth API</h1>

<button id="clickme">Test Bluetooth</button>
<button id="cancel">Cancel Bluetooth Request</button>

<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>

Expand Down
27 changes: 21 additions & 6 deletions docs/fiddles/features/web-bluetooth/main.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

let bluetoothPinCallback
let selectBluetoothCallback

function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
}
})

mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault()
if (deviceList && deviceList.length > 0) {
callback(deviceList[0].deviceId)
}
selectBluetoothCallback = callback
const result = deviceList.find((device) => {
return device.deviceName === 'test'
})
if (result) {
callback(result.deviceId)
} else {
// The device wasn't found so we need to either wait longer (eg until the
// device is turned on) or until the user cancels the request
}
})

ipcMain.on('cancel-bluetooth-request', (event) => {
selectBluetoothCallback('')
})


// Listen for a message from the renderer to get the response for the Bluetooth pairing.
ipcMain.on('bluetooth-pairing-response', (event, response) => {
Expand All @@ -27,14 +42,14 @@ function createWindow () {
bluetoothPinCallback = callback
// Send a message to the renderer to prompt the user to confirm the pairing.
mainWindow.webContents.send('bluetooth-pairing-request', details)
})
})

mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()

app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
Expand Down
1 change: 1 addition & 0 deletions docs/fiddles/features/web-bluetooth/preload.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: (callback) => ipcRenderer.send('cancel-bluetooth-request', callback),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
})
10 changes: 8 additions & 2 deletions docs/fiddles/features/web-bluetooth/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ async function testIt() {

document.getElementById('clickme').addEventListener('click',testIt)

function cancelRequest() {
window.electronAPI.cancelBluetoothRequest()
}

document.getElementById('cancel').addEventListener('click', cancelRequest)

window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {}

switch (details.pairingKind) {
case 'confirm': {
response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}?`)
Expand All @@ -31,4 +37,4 @@ window.electronAPI.bluetoothPairingRequest((event, details) => {
}

window.electronAPI.bluetoothPairingResponse(response)
})
})
34 changes: 15 additions & 19 deletions shell/browser/lib/bluetooth_chooser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ namespace electron {

namespace {

const int kMaxScanRetries = 5;

void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler,
const std::string& device_id) {
if (device_id.empty()) {
Expand Down Expand Up @@ -66,29 +64,15 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
}

void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
bool idle_state = false;
switch (state) {
case DiscoveryState::FAILED_TO_START:
refreshing_ = false;
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
break;
return;
case DiscoveryState::IDLE:
refreshing_ = false;
if (device_map_.empty()) {
auto event = ++num_retries_ > kMaxScanRetries
? content::BluetoothChooserEvent::CANCELLED
: content::BluetoothChooserEvent::RESCAN;
event_handler_.Run(event, "");
} else {
bool prevent_default = api_web_contents_->Emit(
"select-bluetooth-device", GetDeviceList(),
base::BindOnce(&OnDeviceChosen, event_handler_));
if (!prevent_default) {
auto it = device_map_.begin();
auto device_id = it->first;
event_handler_.Run(content::BluetoothChooserEvent::SELECTED,
device_id);
}
}
idle_state = true;
break;
case DiscoveryState::DISCOVERING:
// The first time this state fires is due to a rescan triggering so set a
Expand All @@ -101,6 +85,18 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
}
break;
}
bool prevent_default =
api_web_contents_->Emit("select-bluetooth-device", GetDeviceList(),
base::BindOnce(&OnDeviceChosen, event_handler_));
if (!prevent_default && idle_state) {
if (device_map_.empty()) {
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
} else {
auto it = device_map_.begin();
auto device_id = it->first;
event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id);
}
}
}

void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id,
Expand Down
1 change: 0 additions & 1 deletion shell/browser/lib/bluetooth_chooser.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class BluetoothChooser : public content::BluetoothChooser {
std::map<std::string, std::u16string> device_map_;
api::WebContents* api_web_contents_;
EventHandler event_handler_;
int num_retries_ = 0;
bool refreshing_ = false;
bool rescan_ = false;
};
Expand Down