Browse Source

fix: navigator.bluetooth.requestDevice (#27927)

* fix: navigator.bluetooth.requestDevice

* remove permission observer methods not implemented in 12-x-y

Co-authored-by: John Kleinschmidt <[email protected]>
Keeley Hammond 4 years ago
parent
commit
0baf9997ad

+ 2 - 0
filenames.gni

@@ -332,6 +332,8 @@ filenames = {
     "shell/browser/badging/badge_manager.h",
     "shell/browser/badging/badge_manager_factory.cc",
     "shell/browser/badging/badge_manager_factory.h",
+    "shell/browser/bluetooth/electron_bluetooth_delegate.cc",
+    "shell/browser/bluetooth/electron_bluetooth_delegate.h",
     "shell/browser/browser.cc",
     "shell/browser/browser.h",
     "shell/browser/browser_observer.h",

+ 0 - 1
shell/browser/api/electron_api_web_contents.cc

@@ -88,7 +88,6 @@
 #include "shell/browser/electron_browser_main_parts.h"
 #include "shell/browser/electron_javascript_dialog_manager.h"
 #include "shell/browser/electron_navigation_throttle.h"
-#include "shell/browser/lib/bluetooth_chooser.h"
 #include "shell/browser/native_window.h"
 #include "shell/browser/session_preferences.h"
 #include "shell/browser/ui/drag_util.h"

+ 117 - 0
shell/browser/bluetooth/electron_bluetooth_delegate.cc

@@ -0,0 +1,117 @@
+// Copyright (c) 2020 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "shell/browser/bluetooth/electron_bluetooth_delegate.h"
+
+#include <memory>
+
+#include "base/scoped_observer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
+#include "shell/browser/api/electron_api_web_contents.h"
+#include "shell/browser/lib/bluetooth_chooser.h"
+#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
+
+using blink::WebBluetoothDeviceId;
+using content::RenderFrameHost;
+using content::WebContents;
+using device::BluetoothUUID;
+
+namespace electron {
+
+ElectronBluetoothDelegate::ElectronBluetoothDelegate() = default;
+
+ElectronBluetoothDelegate::~ElectronBluetoothDelegate() = default;
+
+std::unique_ptr<content::BluetoothChooser>
+ElectronBluetoothDelegate::RunBluetoothChooser(
+    content::RenderFrameHost* frame,
+    const content::BluetoothChooser::EventHandler& event_handler) {
+  auto* api_web_contents =
+      api::WebContents::From(content::WebContents::FromRenderFrameHost(frame));
+  return std::make_unique<BluetoothChooser>(api_web_contents, event_handler);
+}
+
+// The following methods are not currently called in Electron.
+std::unique_ptr<content::BluetoothScanningPrompt>
+ElectronBluetoothDelegate::ShowBluetoothScanningPrompt(
+    content::RenderFrameHost* frame,
+    const content::BluetoothScanningPrompt::EventHandler& event_handler) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+WebBluetoothDeviceId ElectronBluetoothDelegate::GetWebBluetoothDeviceId(
+    RenderFrameHost* frame,
+    const std::string& device_address) {
+  NOTIMPLEMENTED();
+  return WebBluetoothDeviceId::Create();
+}
+
+std::string ElectronBluetoothDelegate::GetDeviceAddress(
+    RenderFrameHost* frame,
+    const WebBluetoothDeviceId& device_id) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+WebBluetoothDeviceId ElectronBluetoothDelegate::AddScannedDevice(
+    RenderFrameHost* frame,
+    const std::string& device_address) {
+  NOTIMPLEMENTED();
+  return WebBluetoothDeviceId::Create();
+}
+
+WebBluetoothDeviceId ElectronBluetoothDelegate::GrantServiceAccessPermission(
+    RenderFrameHost* frame,
+    const device::BluetoothDevice* device,
+    const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
+  NOTIMPLEMENTED();
+  return WebBluetoothDeviceId::Create();
+}
+
+bool ElectronBluetoothDelegate::HasDevicePermission(
+    RenderFrameHost* frame,
+    const WebBluetoothDeviceId& device_id) {
+  NOTIMPLEMENTED();
+  return true;
+}
+
+bool ElectronBluetoothDelegate::IsAllowedToAccessService(
+    RenderFrameHost* frame,
+    const WebBluetoothDeviceId& device_id,
+    const BluetoothUUID& service) {
+  NOTIMPLEMENTED();
+  return true;
+}
+
+bool ElectronBluetoothDelegate::IsAllowedToAccessAtLeastOneService(
+    RenderFrameHost* frame,
+    const WebBluetoothDeviceId& device_id) {
+  NOTIMPLEMENTED();
+  return true;
+}
+
+bool ElectronBluetoothDelegate::IsAllowedToAccessManufacturerData(
+    RenderFrameHost* frame,
+    const WebBluetoothDeviceId& device_id,
+    uint16_t manufacturer_code) {
+  NOTIMPLEMENTED();
+  return true;
+}
+
+std::vector<blink::mojom::WebBluetoothDevicePtr>
+ElectronBluetoothDelegate::GetPermittedDevices(
+    content::RenderFrameHost* frame) {
+  std::vector<blink::mojom::WebBluetoothDevicePtr> permitted_devices;
+  NOTIMPLEMENTED();
+  return permitted_devices;
+}
+
+}  // namespace electron

+ 87 - 0
shell/browser/bluetooth/electron_bluetooth_delegate.h

@@ -0,0 +1,87 @@
+// Copyright (c) 2020 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_BROWSER_BLUETOOTH_ELECTRON_BLUETOOTH_DELEGATE_H_
+#define SHELL_BROWSER_BLUETOOTH_ELECTRON_BLUETOOTH_DELEGATE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/observer_list.h"
+#include "base/scoped_observation.h"
+#include "content/public/browser/bluetooth_delegate.h"
+#include "content/public/browser/render_frame_host.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-forward.h"
+
+namespace blink {
+class WebBluetoothDeviceId;
+}  // namespace blink
+
+namespace content {
+class RenderFrameHost;
+}  // namespace content
+
+namespace device {
+class BluetoothDevice;
+class BluetoothUUID;
+}  // namespace device
+
+namespace electron {
+
+// Provides an interface for managing device permissions for Web Bluetooth and
+// Web Bluetooth Scanning API. This is the Electron-specific implementation of
+// the BluetoothDelegate.
+class ElectronBluetoothDelegate : public content::BluetoothDelegate {
+ public:
+  ElectronBluetoothDelegate();
+  ~ElectronBluetoothDelegate() override;
+
+  // Move-only class.
+  ElectronBluetoothDelegate(const ElectronBluetoothDelegate&) = delete;
+  ElectronBluetoothDelegate& operator=(const ElectronBluetoothDelegate&) =
+      delete;
+
+  // BluetoothDelegate implementation:
+  std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
+      content::RenderFrameHost* frame,
+      const content::BluetoothChooser::EventHandler& event_handler) override;
+
+  std::unique_ptr<content::BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
+      content::RenderFrameHost* frame,
+      const content::BluetoothScanningPrompt::EventHandler& event_handler)
+      override;
+  blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
+      content::RenderFrameHost* frame,
+      const std::string& device_address) override;
+  std::string GetDeviceAddress(
+      content::RenderFrameHost* frame,
+      const blink::WebBluetoothDeviceId& device_id) override;
+  blink::WebBluetoothDeviceId AddScannedDevice(
+      content::RenderFrameHost* frame,
+      const std::string& device_address) override;
+  blink::WebBluetoothDeviceId GrantServiceAccessPermission(
+      content::RenderFrameHost* frame,
+      const device::BluetoothDevice* device,
+      const blink::mojom::WebBluetoothRequestDeviceOptions* options) override;
+  bool HasDevicePermission(
+      content::RenderFrameHost* frame,
+      const blink::WebBluetoothDeviceId& device_id) override;
+  bool IsAllowedToAccessService(content::RenderFrameHost* frame,
+                                const blink::WebBluetoothDeviceId& device_id,
+                                const device::BluetoothUUID& service) override;
+  bool IsAllowedToAccessAtLeastOneService(
+      content::RenderFrameHost* frame,
+      const blink::WebBluetoothDeviceId& device_id) override;
+  bool IsAllowedToAccessManufacturerData(
+      content::RenderFrameHost* frame,
+      const blink::WebBluetoothDeviceId& device_id,
+      uint16_t manufacturer_code) override;
+  std::vector<blink::mojom::WebBluetoothDevicePtr> GetPermittedDevices(
+      content::RenderFrameHost* frame) override;
+};
+
+}  // namespace electron
+
+#endif  // SHELL_BROWSER_BLUETOOTH_ELECTRON_BLUETOOTH_DELEGATE_H_

+ 6 - 0
shell/browser/electron_browser_client.cc

@@ -1775,4 +1775,10 @@ content::SerialDelegate* ElectronBrowserClient::GetSerialDelegate() {
   return serial_delegate_.get();
 }
 
+content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
+  if (!bluetooth_delegate_)
+    bluetooth_delegate_ = std::make_unique<ElectronBluetoothDelegate>();
+  return bluetooth_delegate_.get();
+}
+
 }  // namespace electron

+ 4 - 0
shell/browser/electron_browser_client.h

@@ -19,6 +19,7 @@
 #include "electron/buildflags/buildflags.h"
 #include "net/ssl/client_cert_identity.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "shell/browser/bluetooth/electron_bluetooth_delegate.h"
 #include "shell/browser/serial/electron_serial_delegate.h"
 
 namespace content {
@@ -86,6 +87,8 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
   bool CanUseCustomSiteInstance() override;
   content::SerialDelegate* GetSerialDelegate() override;
 
+  content::BluetoothDelegate* GetBluetoothDelegate() override;
+
  protected:
   void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
   content::SpeechRecognitionManagerDelegate*
@@ -336,6 +339,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
   uint64_t next_id_ = 0;
 
   std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
+  std::unique_ptr<ElectronBluetoothDelegate> bluetooth_delegate_;
 
   DISALLOW_COPY_AND_ASSIGN(ElectronBrowserClient);
 };

+ 18 - 1
shell/browser/lib/bluetooth_chooser.cc

@@ -44,7 +44,9 @@ BluetoothChooser::BluetoothChooser(api::WebContents* contents,
                                    const EventHandler& event_handler)
     : api_web_contents_(contents), event_handler_(event_handler) {}
 
-BluetoothChooser::~BluetoothChooser() = default;
+BluetoothChooser::~BluetoothChooser() {
+  event_handler_.Reset();
+}
 
 void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
   switch (presence) {
@@ -60,9 +62,11 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
 void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
   switch (state) {
     case DiscoveryState::FAILED_TO_START:
+      refreshing_ = false;
       event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
       break;
     case DiscoveryState::IDLE:
+      refreshing_ = false;
       if (device_map_.empty()) {
         auto event = ++num_retries_ > kMaxScanRetries
                          ? content::BluetoothChooserEvent::CANCELLED
@@ -81,6 +85,14 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
       }
       break;
     case DiscoveryState::DISCOVERING:
+      // The first time this state fires is due to a rescan triggering so set a
+      // flag to ignore devices
+      if (!refreshing_) {
+        refreshing_ = true;
+      } else {
+        // The second time this state fires we are now safe to pick a device
+        refreshing_ = false;
+      }
       break;
   }
 }
@@ -91,6 +103,11 @@ void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id,
                                          bool is_gatt_connected,
                                          bool is_paired,
                                          int signal_strength_level) {
+  if (refreshing_) {
+    // If the list of bluetooth devices is currently being generated don't fire
+    // an event
+    return;
+  }
   bool changed = false;
   auto entry = device_map_.find(device_id);
   if (entry == device_map_.end()) {

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

@@ -41,6 +41,7 @@ class BluetoothChooser : public content::BluetoothChooser {
   api::WebContents* api_web_contents_;
   EventHandler event_handler_;
   int num_retries_ = 0;
+  bool refreshing_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(BluetoothChooser);
 };

+ 21 - 0
spec-main/chromium-spec.ts

@@ -1611,3 +1611,24 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
     expect(waitForBadgeCount(0)).to.eventually.equal(0);
   });
 });
+
+describe('navigator.bluetooth', () => {
+  let w: BrowserWindow;
+  before(async () => {
+    w = new BrowserWindow({
+      show: false,
+      webPreferences: {
+        enableBlinkFeatures: 'WebBluetooth'
+      }
+    });
+    await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
+  });
+
+  after(closeAllWindows);
+
+  it('can request bluetooth devices', async () => {
+    const bluetooth = await w.webContents.executeJavaScript(`
+    navigator.bluetooth.requestDevice({ acceptAllDevices: true}).then(device => "Found a device!").catch(err => err.message);`, true);
+    expect(bluetooth).to.be.oneOf(['Found a device!', 'Bluetooth adapter not available.', 'User cancelled the requestDevice() chooser.']);
+  });
+});