From 6a6908c4c887c5f685f5e172ff01afc02ebcbc65 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Mon, 27 Mar 2023 09:31:15 -0400 Subject: [PATCH] fix: allow cancelling of bluetooth requests (#37601) * fix: allow cancelling of bluetooth requests allows cancelling of bluetooth requests when no devices present * docs: update docs to reflect how bluetooth works. --- docs/api/web-contents.md | 23 ++++++++----- .../fiddles/features/web-bluetooth/index.html | 1 + docs/fiddles/features/web-bluetooth/main.js | 21 ++++++++++-- .../fiddles/features/web-bluetooth/preload.js | 1 + .../features/web-bluetooth/renderer.js | 6 ++++ shell/browser/lib/bluetooth_chooser.cc | 34 ++++++++----------- shell/browser/lib/bluetooth_chooser.h | 1 - 7 files changed, 56 insertions(+), 31 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c8b0ef8c011a..47f6e8a41c0c 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -777,20 +777,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 }) @@ -800,6 +804,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) diff --git a/docs/fiddles/features/web-bluetooth/index.html b/docs/fiddles/features/web-bluetooth/index.html index b2be53d400a6..5b0158fa3642 100644 --- a/docs/fiddles/features/web-bluetooth/index.html +++ b/docs/fiddles/features/web-bluetooth/index.html @@ -9,6 +9,7 @@

Web Bluetooth API

+

Currently selected bluetooth device:

diff --git a/docs/fiddles/features/web-bluetooth/main.js b/docs/fiddles/features/web-bluetooth/main.js index be44dc050731..4d6e6d38e27c 100644 --- a/docs/fiddles/features/web-bluetooth/main.js +++ b/docs/fiddles/features/web-bluetooth/main.js @@ -1,6 +1,9 @@ const {app, BrowserWindow, ipcMain} = require('electron') const path = require('path') +let bluetoothPinCallback +let selectBluetoothCallback + function createWindow () { const mainWindow = new BrowserWindow({ width: 800, @@ -12,11 +15,23 @@ function createWindow () { 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) => { bluetoothPinCallback(response) diff --git a/docs/fiddles/features/web-bluetooth/preload.js b/docs/fiddles/features/web-bluetooth/preload.js index 0c21fcce9388..a8d30560368e 100644 --- a/docs/fiddles/features/web-bluetooth/preload.js +++ b/docs/fiddles/features/web-bluetooth/preload.js @@ -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) }) \ No newline at end of file diff --git a/docs/fiddles/features/web-bluetooth/renderer.js b/docs/fiddles/features/web-bluetooth/renderer.js index c51a06ca9177..241c253de015 100644 --- a/docs/fiddles/features/web-bluetooth/renderer.js +++ b/docs/fiddles/features/web-bluetooth/renderer.js @@ -7,6 +7,12 @@ 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 = {} diff --git a/shell/browser/lib/bluetooth_chooser.cc b/shell/browser/lib/bluetooth_chooser.cc index 7c9942d7ba58..7d84cff45e2c 100644 --- a/shell/browser/lib/bluetooth_chooser.cc +++ b/shell/browser/lib/bluetooth_chooser.cc @@ -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()) { @@ -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 @@ -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, diff --git a/shell/browser/lib/bluetooth_chooser.h b/shell/browser/lib/bluetooth_chooser.h index 8143e4ce5cfe..aed252c55686 100644 --- a/shell/browser/lib/bluetooth_chooser.h +++ b/shell/browser/lib/bluetooth_chooser.h @@ -44,7 +44,6 @@ class BluetoothChooser : public content::BluetoothChooser { std::map device_map_; api::WebContents* api_web_contents_; EventHandler event_handler_; - int num_retries_ = 0; bool refreshing_ = false; bool rescan_ = false; };