fix: navigator.bluetooth.requestDevice (#27902)

* fix: navigator.bluetooth.requestDevice

* cleanup lint and add test

* update bluetooth test to handle no bluetooth adapter available

* update bluetooth test to handle bluetooth permission denied
This commit is contained in:
John Kleinschmidt 2021-02-26 14:10:27 -05:00 committed by GitHub
parent bd940b2904
commit d57fd6cef0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 269 additions and 2 deletions

View file

@ -334,6 +334,8 @@ filenames = {
"shell/browser/badging/badge_manager.h", "shell/browser/badging/badge_manager.h",
"shell/browser/badging/badge_manager_factory.cc", "shell/browser/badging/badge_manager_factory.cc",
"shell/browser/badging/badge_manager_factory.h", "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.cc",
"shell/browser/browser.h", "shell/browser/browser.h",
"shell/browser/browser_observer.h", "shell/browser/browser_observer.h",

View file

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

View file

@ -0,0 +1,127 @@
// 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;
}
void ElectronBluetoothDelegate::AddFramePermissionObserver(
FramePermissionObserver* observer) {
NOTIMPLEMENTED();
}
void ElectronBluetoothDelegate::RemoveFramePermissionObserver(
FramePermissionObserver* observer) {
NOTIMPLEMENTED();
}
std::vector<blink::mojom::WebBluetoothDevicePtr>
ElectronBluetoothDelegate::GetPermittedDevices(
content::RenderFrameHost* frame) {
std::vector<blink::mojom::WebBluetoothDevicePtr> permitted_devices;
NOTIMPLEMENTED();
return permitted_devices;
}
} // namespace electron

View file

@ -0,0 +1,90 @@
// 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;
void AddFramePermissionObserver(FramePermissionObserver* observer) override;
void RemoveFramePermissionObserver(
FramePermissionObserver* observer) override;
};
} // namespace electron
#endif // SHELL_BROWSER_BLUETOOTH_ELECTRON_BLUETOOTH_DELEGATE_H_

View file

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

View file

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

View file

@ -44,7 +44,9 @@ BluetoothChooser::BluetoothChooser(api::WebContents* contents,
const EventHandler& event_handler) const EventHandler& event_handler)
: api_web_contents_(contents), event_handler_(event_handler) {} : api_web_contents_(contents), event_handler_(event_handler) {}
BluetoothChooser::~BluetoothChooser() = default; BluetoothChooser::~BluetoothChooser() {
event_handler_.Reset();
}
void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) { void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
switch (presence) { switch (presence) {
@ -65,9 +67,11 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) { void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
switch (state) { switch (state) {
case DiscoveryState::FAILED_TO_START: case DiscoveryState::FAILED_TO_START:
refreshing_ = false;
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, ""); event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
break; break;
case DiscoveryState::IDLE: case DiscoveryState::IDLE:
refreshing_ = false;
if (device_map_.empty()) { if (device_map_.empty()) {
auto event = ++num_retries_ > kMaxScanRetries auto event = ++num_retries_ > kMaxScanRetries
? content::BluetoothChooserEvent::CANCELLED ? content::BluetoothChooserEvent::CANCELLED
@ -86,6 +90,14 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
} }
break; break;
case DiscoveryState::DISCOVERING: 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; break;
} }
} }
@ -96,6 +108,11 @@ void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id,
bool is_gatt_connected, bool is_gatt_connected,
bool is_paired, bool is_paired,
int signal_strength_level) { 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; bool changed = false;
auto entry = device_map_.find(device_id); auto entry = device_map_.find(device_id);
if (entry == device_map_.end()) { if (entry == device_map_.end()) {

View file

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

View file

@ -1613,3 +1613,24 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
expect(waitForBadgeCount(0)).to.eventually.equal(0); 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.']);
});
});