Browse Source

fix: allow cancelling of bluetooth requests (#37717)

* fix: allow cancelling of bluetooth requests

allows cancelling of bluetooth requests when no devices present

Co-authored-by: John Kleinschmidt <[email protected]>

* docs: update docs to reflect how bluetooth works.

Co-authored-by: John Kleinschmidt <[email protected]>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <[email protected]>
trop[bot] 2 years ago
parent
commit
ce82646f5c

+ 15 - 8
docs/api/web-contents.md

@@ -721,20 +721,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 })
@@ -744,6 +748,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)

+ 1 - 0
docs/fiddles/features/web-bluetooth/index.html

@@ -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>
 

+ 18 - 3
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,10 +15,22 @@ 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) => {

+ 1 - 0
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)
 })

+ 6 - 0
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 = {}
 

+ 15 - 19
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,

+ 0 - 1
shell/browser/lib/bluetooth_chooser.h

@@ -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;
 };