diff --git a/filenames.gni b/filenames.gni index a70b1137e0e4..797dfb4c87e9 100644 --- a/filenames.gni +++ b/filenames.gni @@ -334,6 +334,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", diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 37d1bcbf740c..c74a17b4cfaa 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/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" diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.cc b/shell/browser/bluetooth/electron_bluetooth_delegate.cc new file mode 100644 index 000000000000..74edf8f61647 --- /dev/null +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.cc @@ -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 + +#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 +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(api_web_contents, event_handler); +} + +// The following methods are not currently called in Electron. +std::unique_ptr +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 +ElectronBluetoothDelegate::GetPermittedDevices( + content::RenderFrameHost* frame) { + std::vector permitted_devices; + NOTIMPLEMENTED(); + return permitted_devices; +} + +} // namespace electron diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.h b/shell/browser/bluetooth/electron_bluetooth_delegate.h new file mode 100644 index 000000000000..883eef110005 --- /dev/null +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.h @@ -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 +#include +#include + +#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 RunBluetoothChooser( + content::RenderFrameHost* frame, + const content::BluetoothChooser::EventHandler& event_handler) override; + + std::unique_ptr 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 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_ diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index a5bc994c091d..c00a61155acf 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -1770,4 +1770,10 @@ content::SerialDelegate* ElectronBrowserClient::GetSerialDelegate() { return serial_delegate_.get(); } +content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() { + if (!bluetooth_delegate_) + bluetooth_delegate_ = std::make_unique(); + return bluetooth_delegate_.get(); +} + } // namespace electron diff --git a/shell/browser/electron_browser_client.h b/shell/browser/electron_browser_client.h index eba0011543fa..abda113521ee 100644 --- a/shell/browser/electron_browser_client.h +++ b/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* @@ -335,6 +338,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient, uint64_t next_id_ = 0; std::unique_ptr serial_delegate_; + std::unique_ptr bluetooth_delegate_; DISALLOW_COPY_AND_ASSIGN(ElectronBrowserClient); }; diff --git a/shell/browser/lib/bluetooth_chooser.cc b/shell/browser/lib/bluetooth_chooser.cc index eec2a0da0607..3233bb94ed2e 100644 --- a/shell/browser/lib/bluetooth_chooser.cc +++ b/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) { @@ -65,9 +67,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 @@ -86,6 +90,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; } } @@ -96,6 +108,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()) { diff --git a/shell/browser/lib/bluetooth_chooser.h b/shell/browser/lib/bluetooth_chooser.h index d74684ff3ba2..1cff5df0bdb8 100644 --- a/shell/browser/lib/bluetooth_chooser.h +++ b/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); }; diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index 3b86498b24b0..4d5ef17e7a4c 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -1613,3 +1613,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.']); + }); +});