| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // ID Not In Map Note: A service, characteristic, or descriptor ID not in the |
| // corresponding WebBluetoothServiceImpl map [service_id_to_device_address_, |
| // characteristic_id_to_service_id_, descriptor_id_to_characteristic_id_] |
| // implies a hostile renderer because a renderer obtains the corresponding ID |
| // from this class and it will be added to the map at that time. |
| |
| #include "content/browser/bluetooth/web_bluetooth_service_impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/contains.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "content/browser/bluetooth/bluetooth_adapter_factory_wrapper.h" |
| #include "content/browser/bluetooth/bluetooth_blocklist.h" |
| #include "content/browser/bluetooth/bluetooth_device_chooser_controller.h" |
| #include "content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h" |
| #include "content/browser/bluetooth/bluetooth_metrics.h" |
| #include "content/browser/bluetooth/bluetooth_util.h" |
| #include "content/browser/bluetooth/frame_connected_bluetooth_devices.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/bluetooth_delegate.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_descriptor.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver.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 device::BluetoothAdapter; |
| using device::BluetoothDevice; |
| using device::BluetoothDiscoverySession; |
| using device::BluetoothGattCharacteristic; |
| using device::BluetoothGattConnection; |
| using device::BluetoothGattNotifySession; |
| using device::BluetoothGattService; |
| using device::BluetoothRemoteGattCharacteristic; |
| using device::BluetoothRemoteGattDescriptor; |
| using device::BluetoothRemoteGattService; |
| using device::BluetoothUUID; |
| |
| namespace content { |
| |
| namespace { |
| |
| blink::mojom::WebBluetoothResult TranslateGATTErrorAndRecord( |
| device::BluetoothRemoteGattService::GattErrorCode error_code, |
| UMAGATTOperation operation) { |
| switch (error_code) { |
| case device::BluetoothRemoteGattService::GATT_ERROR_UNKNOWN: |
| RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::kUnknown); |
| return blink::mojom::WebBluetoothResult::GATT_UNKNOWN_ERROR; |
| case device::BluetoothRemoteGattService::GATT_ERROR_FAILED: |
| RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::kFailed); |
| return blink::mojom::WebBluetoothResult::GATT_UNKNOWN_FAILURE; |
| case device::BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kInProgress); |
| return blink::mojom::WebBluetoothResult::GATT_OPERATION_IN_PROGRESS; |
| case device::BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kInvalidLength); |
| return blink::mojom::WebBluetoothResult::GATT_INVALID_ATTRIBUTE_LENGTH; |
| case device::BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kNotPermitted); |
| return blink::mojom::WebBluetoothResult::GATT_NOT_PERMITTED; |
| case device::BluetoothRemoteGattService::GATT_ERROR_NOT_AUTHORIZED: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kNotAuthorized); |
| return blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED; |
| case device::BluetoothRemoteGattService::GATT_ERROR_NOT_PAIRED: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kNotPaired); |
| return blink::mojom::WebBluetoothResult::GATT_NOT_PAIRED; |
| case device::BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED: |
| RecordGATTOperationOutcome(operation, |
| UMAGATTOperationOutcome::kNotSupported); |
| return blink::mojom::WebBluetoothResult::GATT_NOT_SUPPORTED; |
| } |
| NOTREACHED(); |
| return blink::mojom::WebBluetoothResult::GATT_UNTRANSLATED_ERROR_CODE; |
| } |
| |
| // Max length of device name in filter. Bluetooth 5.0 3.C.3.2.2.3 states that |
| // the maximum device name length is 248 bytes (UTF-8 encoded). |
| constexpr size_t kMaxLengthForDeviceName = 248; |
| |
| bool IsValidFilter(const blink::mojom::WebBluetoothLeScanFilterPtr& filter) { |
| // At least one member needs to be present. |
| if (!filter->name && !filter->name_prefix && !filter->services && |
| !filter->manufacturer_data) { |
| return false; |
| } |
| |
| // The |services| should not be empty. |
| if (filter->services && filter->services->empty()) |
| return false; |
| |
| // The renderer will never send a |name| or a |name_prefix| longer than |
| // kMaxLengthForDeviceName. |
| if (filter->name && filter->name->size() > kMaxLengthForDeviceName) |
| return false; |
| |
| if (filter->name_prefix && |
| filter->name_prefix->size() > kMaxLengthForDeviceName) |
| return false; |
| |
| // The |name_prefix| should not be empty. |
| if (filter->name_prefix && filter->name_prefix->empty()) |
| return false; |
| |
| // The |manufacturer_data| should not be empty. |
| if (filter->manufacturer_data && filter->manufacturer_data->empty()) |
| return false; |
| |
| return true; |
| } |
| |
| bool IsValidRequestDeviceOptions( |
| const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) { |
| if (options->accept_all_devices) |
| return !options->filters.has_value(); |
| |
| return HasValidFilter(options->filters); |
| } |
| |
| bool IsValidRequestScanOptions( |
| const blink::mojom::WebBluetoothRequestLEScanOptionsPtr& options) { |
| if (options->accept_all_advertisements) |
| return !options->filters.has_value(); |
| |
| return HasValidFilter(options->filters); |
| } |
| |
| } // namespace |
| |
| // static |
| blink::mojom::WebBluetoothResult |
| WebBluetoothServiceImpl::TranslateConnectErrorAndRecord( |
| BluetoothDevice::ConnectErrorCode error_code) { |
| switch (error_code) { |
| case BluetoothDevice::ERROR_UNKNOWN: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNKNOWN); |
| return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_ERROR; |
| case BluetoothDevice::ERROR_INPROGRESS: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::IN_PROGRESS); |
| return blink::mojom::WebBluetoothResult::CONNECT_ALREADY_IN_PROGRESS; |
| case BluetoothDevice::ERROR_FAILED: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::FAILED); |
| return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_FAILURE; |
| case BluetoothDevice::ERROR_AUTH_FAILED: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_FAILED); |
| return blink::mojom::WebBluetoothResult::CONNECT_AUTH_FAILED; |
| case BluetoothDevice::ERROR_AUTH_CANCELED: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_CANCELED); |
| return blink::mojom::WebBluetoothResult::CONNECT_AUTH_CANCELED; |
| case BluetoothDevice::ERROR_AUTH_REJECTED: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_REJECTED); |
| return blink::mojom::WebBluetoothResult::CONNECT_AUTH_REJECTED; |
| case BluetoothDevice::ERROR_AUTH_TIMEOUT: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::AUTH_TIMEOUT); |
| return blink::mojom::WebBluetoothResult::CONNECT_AUTH_TIMEOUT; |
| case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::UNSUPPORTED_DEVICE); |
| return blink::mojom::WebBluetoothResult::CONNECT_UNSUPPORTED_DEVICE; |
| case BluetoothDevice::NUM_CONNECT_ERROR_CODES: |
| NOTREACHED(); |
| return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_FAILURE; |
| } |
| NOTREACHED(); |
| return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_FAILURE; |
| } |
| |
| class WebBluetoothServiceImpl::AdvertisementClient { |
| public: |
| virtual void SendEvent( |
| const blink::mojom::WebBluetoothAdvertisingEvent& event) = 0; |
| |
| bool is_connected() { return client_.is_connected(); } |
| |
| protected: |
| explicit AdvertisementClient( |
| WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote< |
| blink::mojom::WebBluetoothAdvertisementClient> client_info) |
| : client_(std::move(client_info)), |
| web_contents_(static_cast<WebContentsImpl*>( |
| WebContents::FromRenderFrameHost(service->render_frame_host_))), |
| service_(service) { |
| // Using base::Unretained() is safe here because all instances of this class |
| // will be owned by |service|. |
| client_.set_disconnect_handler( |
| base::BindOnce(&WebBluetoothServiceImpl::RemoveDisconnectedClients, |
| base::Unretained(service))); |
| web_contents_->IncrementBluetoothScanningSessionsCount(); |
| } |
| virtual ~AdvertisementClient() = default; |
| |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> client_; |
| WebContentsImpl* web_contents_; |
| WebBluetoothServiceImpl* service_; |
| }; |
| |
| class WebBluetoothServiceImpl::WatchAdvertisementsClient |
| : public WebBluetoothServiceImpl::AdvertisementClient { |
| public: |
| WatchAdvertisementsClient( |
| WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote< |
| blink::mojom::WebBluetoothAdvertisementClient> client_info, |
| blink::WebBluetoothDeviceId device_id) |
| : AdvertisementClient(service, std::move(client_info)), |
| device_id_(device_id) { |
| DCHECK(device_id_.IsValid()); |
| } |
| |
| ~WatchAdvertisementsClient() override { |
| web_contents_->DecrementBluetoothScanningSessionsCount(); |
| } |
| |
| // AdvertisementClient implementation: |
| void SendEvent( |
| const blink::mojom::WebBluetoothAdvertisingEvent& event) override { |
| if (event.device->id != device_id_) |
| return; |
| |
| auto filtered_event = event.Clone(); |
| base::EraseIf(filtered_event->uuids, [this](const BluetoothUUID& uuid) { |
| return !service_->IsAllowedToAccessService(device_id_, uuid); |
| }); |
| base::EraseIf( |
| filtered_event->service_data, |
| [this](const std::pair<BluetoothUUID, std::vector<uint8_t>>& entry) { |
| return !service_->IsAllowedToAccessService(device_id_, entry.first); |
| }); |
| base::EraseIf( |
| filtered_event->manufacturer_data, |
| [this](const std::pair<uint16_t, std::vector<uint8_t>>& entry) { |
| return !service_->IsAllowedToAccessManufacturerData(device_id_, |
| entry.first); |
| }); |
| client_->AdvertisingEvent(std::move(filtered_event)); |
| } |
| |
| blink::WebBluetoothDeviceId device_id() const { return device_id_; } |
| |
| private: |
| blink::WebBluetoothDeviceId device_id_; |
| }; |
| |
| class WebBluetoothServiceImpl::ScanningClient |
| : public WebBluetoothServiceImpl::AdvertisementClient { |
| public: |
| ScanningClient(WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote< |
| blink::mojom::WebBluetoothAdvertisementClient> client_info, |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, |
| RequestScanningStartCallback callback) |
| : AdvertisementClient(service, std::move(client_info)), |
| options_(std::move(options)), |
| callback_(std::move(callback)) { |
| DCHECK(options_->filters.has_value() || |
| options_->accept_all_advertisements); |
| } |
| |
| ~ScanningClient() override { |
| web_contents_->DecrementBluetoothScanningSessionsCount(); |
| } |
| |
| void SetPromptController( |
| BluetoothDeviceScanningPromptController* prompt_controller) { |
| prompt_controller_ = prompt_controller; |
| } |
| |
| // AdvertisingClient implementation: |
| void SendEvent( |
| const blink::mojom::WebBluetoothAdvertisingEvent& event) override { |
| // TODO(https://crbug.com/1108958): Filter out advertisement data if not |
| // included in the filters, optionalServices, or optionalManufacturerData. |
| auto filtered_event = event.Clone(); |
| if (options_->accept_all_advertisements) { |
| if (prompt_controller_) |
| AddFilteredDeviceToPrompt(filtered_event->device->id.str(), |
| filtered_event->name); |
| |
| if (allow_send_event_) |
| client_->AdvertisingEvent(std::move(filtered_event)); |
| |
| return; |
| } |
| |
| DCHECK(options_->filters.has_value()); |
| |
| // For every filter, we're going to check to see if a |name|, |name_prefix|, |
| // or |services| have been set. If one of these is set, we will check the |
| // scan result to see if it matches the filter's value. If it doesn't, |
| // we'll just continue with the next filter. If all of the properties in a |
| // filter have a match, we can post the AdvertisingEvent. Otherwise, we are |
| // going to drop it. This logic can be reduced a bit, but I think clarity |
| // will decrease. |
| for (auto& filter : options_->filters.value()) { |
| // Check to see if there is a direct match against the advertisement name |
| if (filter->name.has_value()) { |
| if (!filtered_event->name.has_value() || |
| filter->name.value() != filtered_event->name.value()) { |
| continue; |
| } |
| } |
| |
| // Check if there is a name prefix match |
| if (filter->name_prefix.has_value()) { |
| if (!filtered_event->name.has_value() || |
| !base::StartsWith(filtered_event->name.value(), |
| filter->name_prefix.value(), |
| base::CompareCase::SENSITIVE)) { |
| continue; |
| } |
| } |
| |
| // Check to see if there is a service uuid match |
| if (filter->services.has_value()) { |
| auto it = std::find_if( |
| filter->services.value().begin(), filter->services.value().end(), |
| [&filtered_event](const BluetoothUUID& filter_uuid) { |
| return base::Contains(filtered_event->uuids, filter_uuid); |
| }); |
| if (it == filter->services.value().end()) |
| continue; |
| } |
| |
| // TODO(crbug.com/707635): Support manufacturerData and serviceData |
| // filters. |
| |
| if (prompt_controller_) |
| AddFilteredDeviceToPrompt(filtered_event->device->id.str(), |
| filtered_event->name); |
| |
| if (allow_send_event_) |
| client_->AdvertisingEvent(std::move(filtered_event)); |
| return; |
| } |
| } |
| |
| void RunRequestScanningStartCallback( |
| blink::mojom::WebBluetoothResult result) { |
| DCHECK(result == blink::mojom::WebBluetoothResult::SUCCESS || |
| result == blink::mojom::WebBluetoothResult::SCANNING_BLOCKED || |
| result == blink::mojom::WebBluetoothResult::PROMPT_CANCELED); |
| std::move(callback_).Run(result); |
| } |
| |
| void set_prompt_controller( |
| BluetoothDeviceScanningPromptController* prompt_controller) { |
| prompt_controller_ = prompt_controller; |
| } |
| |
| BluetoothDeviceScanningPromptController* prompt_controller() { |
| return prompt_controller_; |
| } |
| |
| void set_allow_send_event(bool allow_send_event) { |
| allow_send_event_ = allow_send_event; |
| } |
| |
| const blink::mojom::WebBluetoothRequestLEScanOptions& scan_options() { |
| return *options_; |
| } |
| |
| private: |
| void AddFilteredDeviceToPrompt( |
| const std::string& device_id, |
| const absl::optional<std::string>& device_name) { |
| bool should_update_name = device_name.has_value(); |
| std::u16string device_name_for_display = |
| base::UTF8ToUTF16(device_name.value_or("")); |
| prompt_controller_->AddFilteredDevice(device_id, should_update_name, |
| device_name_for_display); |
| } |
| |
| bool allow_send_event_ = false; |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options_; |
| RequestScanningStartCallback callback_; |
| BluetoothDeviceScanningPromptController* prompt_controller_ = nullptr; |
| }; |
| |
| bool HasValidFilter( |
| const absl::optional< |
| std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>>& filters) { |
| if (!filters) { |
| return false; |
| } |
| |
| return !filters->empty() && |
| std::all_of(filters->begin(), filters->end(), IsValidFilter); |
| } |
| |
| // Struct that holds the result of a cache query. |
| struct CacheQueryResult { |
| CacheQueryResult() : outcome(CacheQueryOutcome::SUCCESS) {} |
| |
| explicit CacheQueryResult(CacheQueryOutcome outcome) : outcome(outcome) {} |
| |
| ~CacheQueryResult() {} |
| |
| blink::mojom::WebBluetoothResult GetWebResult() const { |
| switch (outcome) { |
| case CacheQueryOutcome::SUCCESS: |
| case CacheQueryOutcome::BAD_RENDERER: |
| NOTREACHED(); |
| return blink::mojom::WebBluetoothResult::DEVICE_NO_LONGER_IN_RANGE; |
| case CacheQueryOutcome::NO_DEVICE: |
| return blink::mojom::WebBluetoothResult::DEVICE_NO_LONGER_IN_RANGE; |
| case CacheQueryOutcome::NO_SERVICE: |
| return blink::mojom::WebBluetoothResult::SERVICE_NO_LONGER_EXISTS; |
| case CacheQueryOutcome::NO_CHARACTERISTIC: |
| return blink::mojom::WebBluetoothResult:: |
| CHARACTERISTIC_NO_LONGER_EXISTS; |
| case CacheQueryOutcome::NO_DESCRIPTOR: |
| return blink::mojom::WebBluetoothResult::DESCRIPTOR_NO_LONGER_EXISTS; |
| } |
| NOTREACHED(); |
| return blink::mojom::WebBluetoothResult::DEVICE_NO_LONGER_IN_RANGE; |
| } |
| |
| BluetoothDevice* device = nullptr; |
| BluetoothRemoteGattService* service = nullptr; |
| BluetoothRemoteGattCharacteristic* characteristic = nullptr; |
| BluetoothRemoteGattDescriptor* descriptor = nullptr; |
| CacheQueryOutcome outcome; |
| }; |
| |
| struct GATTNotifySessionAndCharacteristicClient { |
| GATTNotifySessionAndCharacteristicClient( |
| std::unique_ptr<BluetoothGattNotifySession> session, |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> |
| client) |
| : gatt_notify_session(std::move(session)), |
| characteristic_client(std::move(client)) {} |
| |
| std::unique_ptr<BluetoothGattNotifySession> gatt_notify_session; |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> |
| characteristic_client; |
| }; |
| |
| WebBluetoothServiceImpl::WebBluetoothServiceImpl( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) |
| : WebContentsObserver(WebContents::FromRenderFrameHost(render_frame_host)), |
| connected_devices_(new FrameConnectedBluetoothDevices(render_frame_host)), |
| render_frame_host_(render_frame_host), |
| receiver_(this, std::move(receiver)), |
| pairing_manager_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| CHECK(web_contents()); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (delegate) { |
| observer_.Observe(delegate); |
| } |
| } |
| } |
| |
| WebBluetoothServiceImpl::~WebBluetoothServiceImpl() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ClearState(); |
| } |
| |
| void WebBluetoothServiceImpl::SetClientConnectionErrorHandler( |
| base::OnceClosure closure) { |
| receiver_.set_disconnect_handler(std::move(closure)); |
| } |
| |
| blink::mojom::WebBluetoothResult |
| WebBluetoothServiceImpl::GetBluetoothAllowed() { |
| const url::Origin& requesting_origin = |
| render_frame_host_->GetLastCommittedOrigin(); |
| const url::Origin& embedding_origin = |
| web_contents()->GetMainFrame()->GetLastCommittedOrigin(); |
| |
| // TODO(crbug.com/518042): Enforce correctly-delegated permissions instead of |
| // matching origins. When relaxing this, take care to handle non-sandboxed |
| // unique origins. |
| if (!embedding_origin.IsSameOriginWith(requesting_origin)) { |
| return blink::mojom::WebBluetoothResult:: |
| REQUEST_DEVICE_FROM_CROSS_ORIGIN_IFRAME; |
| } |
| // IsSameOriginWith() no longer excludes opaque origins. |
| // TODO(https://crbug.com/994454): Exclude opaque origins explicitly. |
| |
| // Some embedders that don't support Web Bluetooth indicate this by not |
| // returning a chooser. |
| // TODO(https://crbug.com/993829): Perform this check once there is a way to |
| // check if a platform is capable of producing a chooser and return a |
| // |blink::mojom::WebBluetoothResult::WEB_BLUETOOTH_NOT_SUPPORTED| error. |
| switch (GetContentClient()->browser()->AllowWebBluetooth( |
| web_contents()->GetBrowserContext(), requesting_origin, |
| embedding_origin)) { |
| case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_POLICY: |
| return blink::mojom::WebBluetoothResult:: |
| CHOOSER_NOT_SHOWN_API_LOCALLY_DISABLED; |
| case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_GLOBALLY_DISABLED: |
| return blink::mojom::WebBluetoothResult:: |
| CHOOSER_NOT_SHOWN_API_GLOBALLY_DISABLED; |
| case ContentBrowserClient::AllowWebBluetoothResult::ALLOW: |
| return blink::mojom::WebBluetoothResult::SUCCESS; |
| } |
| } |
| |
| bool WebBluetoothServiceImpl::IsDevicePaired( |
| const std::string& device_address) { |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return false; |
| return delegate->GetWebBluetoothDeviceId(render_frame_host_, device_address) |
| .IsValid(); |
| } |
| return allowed_devices().GetDeviceId(device_address) != nullptr; |
| } |
| |
| void WebBluetoothServiceImpl::OnBluetoothScanningPromptEvent( |
| BluetoothScanningPrompt::Event event, |
| BluetoothDeviceScanningPromptController* prompt_controller) { |
| // It is possible for |scanning_clients_| to be empty if a Mojo connection |
| // error has occurred before this method was called. |
| if (scanning_clients_.empty()) |
| return; |
| |
| auto& client = scanning_clients_.back(); |
| |
| DCHECK(client->prompt_controller() == prompt_controller); |
| |
| auto result = blink::mojom::WebBluetoothResult::SUCCESS; |
| if (event == BluetoothScanningPrompt::Event::kAllow) { |
| result = blink::mojom::WebBluetoothResult::SUCCESS; |
| StoreAllowedScanOptions(client->scan_options()); |
| } else if (event == BluetoothScanningPrompt::Event::kBlock) { |
| result = blink::mojom::WebBluetoothResult::SCANNING_BLOCKED; |
| const url::Origin requesting_origin = |
| render_frame_host_->GetLastCommittedOrigin(); |
| const url::Origin embedding_origin = |
| web_contents()->GetMainFrame()->GetLastCommittedOrigin(); |
| GetContentClient()->browser()->BlockBluetoothScanning( |
| web_contents()->GetBrowserContext(), requesting_origin, |
| embedding_origin); |
| } else if (event == BluetoothScanningPrompt::Event::kCanceled) { |
| result = blink::mojom::WebBluetoothResult::PROMPT_CANCELED; |
| } else { |
| NOTREACHED(); |
| } |
| |
| client->RunRequestScanningStartCallback(std::move(result)); |
| client->set_prompt_controller(nullptr); |
| if (event == BluetoothScanningPrompt::Event::kAllow) { |
| client->set_allow_send_event(true); |
| } else if (event == BluetoothScanningPrompt::Event::kBlock) { |
| // Here because user explicitly blocks the permission to do Bluetooth |
| // scanning in one request, it can be interpreted as user wants the current |
| // and all previous scanning to be blocked, so remove all existing scanning |
| // clients. |
| scanning_clients_.clear(); |
| allowed_scan_filters_.clear(); |
| accept_all_advertisements_ = false; |
| } else if (event == BluetoothScanningPrompt::Event::kCanceled) { |
| scanning_clients_.pop_back(); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::OnPermissionRevoked(const url::Origin& origin) { |
| if (render_frame_host_->GetMainFrame()->GetLastCommittedOrigin() != origin) { |
| return; |
| } |
| |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return; |
| |
| std::set<blink::WebBluetoothDeviceId> permitted_ids; |
| for (const auto& device : delegate->GetPermittedDevices(render_frame_host_)) |
| permitted_ids.insert(device->id); |
| |
| connected_devices_->CloseConnectionsToDevicesNotInList(permitted_ids); |
| |
| base::EraseIf(watch_advertisements_clients_, |
| [&](const std::unique_ptr<WatchAdvertisementsClient>& client) { |
| return !base::Contains(permitted_ids, client->device_id()); |
| }); |
| |
| MaybeStopDiscovery(); |
| } |
| |
| content::RenderFrameHost* WebBluetoothServiceImpl::GetRenderFrameHost() { |
| return render_frame_host_; |
| } |
| |
| void WebBluetoothServiceImpl::DidFinishNavigation( |
| NavigationHandle* navigation_handle) { |
| if (navigation_handle->HasCommitted() && |
| navigation_handle->GetRenderFrameHost() == render_frame_host_ && |
| !navigation_handle->IsSameDocument()) { |
| ClearState(); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::OnVisibilityChanged(Visibility visibility) { |
| if (visibility == content::Visibility::HIDDEN || |
| visibility == content::Visibility::OCCLUDED) { |
| ClearAdvertisementClients(); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::OnWebContentsLostFocus( |
| RenderWidgetHost* render_widget_host) { |
| ClearAdvertisementClients(); |
| } |
| |
| void WebBluetoothServiceImpl::AdapterPoweredChanged(BluetoothAdapter* adapter, |
| bool powered) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (device_chooser_controller_.get()) { |
| device_chooser_controller_->AdapterPoweredChanged(powered); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::DeviceAdded(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (device_chooser_controller_.get()) { |
| device_chooser_controller_->AddFilteredDevice(*device); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::DeviceChanged(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (device_chooser_controller_.get()) { |
| device_chooser_controller_->AddFilteredDevice(*device); |
| } |
| |
| if (!device->IsGattConnected()) { |
| absl::optional<blink::WebBluetoothDeviceId> device_id = |
| connected_devices_->CloseConnectionToDeviceWithAddress( |
| device->GetAddress()); |
| |
| // Since the device disconnected we need to send an error for pending |
| // primary services requests. |
| RunPendingPrimaryServicesRequests(device); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::DeviceAdvertisementReceived( |
| const std::string& device_address, |
| const absl::optional<std::string>& device_name, |
| const absl::optional<std::string>& advertisement_name, |
| absl::optional<int8_t> rssi, |
| absl::optional<int8_t> tx_power, |
| absl::optional<uint16_t> appearance, |
| const BluetoothDevice::UUIDList& advertised_uuids, |
| const BluetoothDevice::ServiceDataMap& service_data_map, |
| const BluetoothDevice::ManufacturerDataMap& manufacturer_data_map) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!HasActiveDiscoverySession()) |
| return; |
| |
| // Construct the WebBluetoothAdvertisingEvent. |
| auto device = blink::mojom::WebBluetoothDevice::New(); |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return; |
| device->id = delegate->AddScannedDevice(render_frame_host_, device_address); |
| } else { |
| device->id = allowed_devices().AddDevice(device_address); |
| } |
| device->name = device_name; |
| |
| auto result = blink::mojom::WebBluetoothAdvertisingEvent::New(); |
| result->device = std::move(device); |
| |
| result->name = advertisement_name; |
| |
| // Note about the default value for these optional types. On the other side of |
| // this IPC, the receiver will be checking to see if |*_is_set| is true before |
| // using the value. Here we chose reasonable defaults in case the other side |
| // does something incorrect. We have to do this manual serialization because |
| // mojo does not support optional primitive types. |
| result->appearance_is_set = appearance.has_value(); |
| result->appearance = appearance.value_or(/*not present=*/0xffc0); |
| |
| result->rssi_is_set = rssi.has_value(); |
| result->rssi = rssi.value_or(/*invalid value=*/128); |
| |
| result->tx_power_is_set = tx_power.has_value(); |
| result->tx_power = tx_power.value_or(/*invalid value=*/128); |
| |
| std::vector<BluetoothUUID> uuids; |
| for (auto& uuid : advertised_uuids) |
| uuids.push_back(BluetoothUUID(uuid.canonical_value())); |
| result->uuids = std::move(uuids); |
| |
| auto& manufacturer_data = result->manufacturer_data; |
| manufacturer_data.insert(manufacturer_data_map.begin(), |
| manufacturer_data_map.end()); |
| |
| auto& service_data = result->service_data; |
| service_data.insert(service_data_map.begin(), service_data_map.end()); |
| |
| // TODO(https://crbug.com/1087007): These two classes can potentially be |
| // combined into the same container. |
| for (const auto& scanning_client : scanning_clients_) |
| scanning_client->SendEvent(*result); |
| |
| for (const auto& watch_advertisements_client : watch_advertisements_clients_) |
| watch_advertisements_client->SendEvent(*result); |
| |
| MaybeStopDiscovery(); |
| } |
| |
| void WebBluetoothServiceImpl::GattServicesDiscovered(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| DVLOG(1) << "Services discovered for device: " << device->GetAddress(); |
| |
| if (device_chooser_controller_.get()) { |
| device_chooser_controller_->AddFilteredDevice(*device); |
| } |
| |
| RunPendingPrimaryServicesRequests(device); |
| } |
| |
| void WebBluetoothServiceImpl::GattCharacteristicValueChanged( |
| BluetoothAdapter* adapter, |
| BluetoothRemoteGattCharacteristic* characteristic, |
| const std::vector<uint8_t>& value) { |
| // Don't notify of characteristics that we haven't returned. |
| if (!base::Contains(characteristic_id_to_service_id_, |
| characteristic->GetIdentifier())) { |
| return; |
| } |
| |
| // TODO(crbug.com/541390): Don't send notifications when they haven't been |
| // requested by the client. |
| |
| // On Chrome OS and Linux, GattCharacteristicValueChanged is called before the |
| // success callback for ReadRemoteCharacteristic is called, which could result |
| // in an event being fired before the readValue promise is resolved. |
| if (!base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &WebBluetoothServiceImpl::NotifyCharacteristicValueChanged, |
| weak_ptr_factory_.GetWeakPtr(), characteristic->GetIdentifier(), |
| value))) { |
| LOG(WARNING) << "No TaskRunner."; |
| } |
| } |
| |
| void WebBluetoothServiceImpl::NotifyCharacteristicValueChanged( |
| const std::string& characteristic_instance_id, |
| const std::vector<uint8_t>& value) { |
| auto iter = |
| characteristic_id_to_notify_session_.find(characteristic_instance_id); |
| if (iter != characteristic_id_to_notify_session_.end()) { |
| iter->second->characteristic_client->RemoteCharacteristicValueChanged( |
| value); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::GetAvailability( |
| GetAvailabilityCallback callback) { |
| if (GetBluetoothAllowed() != blink::mojom::WebBluetoothResult::SUCCESS) { |
| std::move(callback).Run(/*result=*/false); |
| return; |
| } |
| |
| if (!BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) { |
| std::move(callback).Run(/*result=*/false); |
| return; |
| } |
| |
| auto get_availability_impl = base::BindOnce( |
| [](GetAvailabilityCallback callback, |
| scoped_refptr<BluetoothAdapter> adapter) { |
| std::move(callback).Run(adapter->IsPresent()); |
| }, |
| std::move(callback)); |
| |
| auto* adapter = GetAdapter(); |
| if (adapter) { |
| std::move(get_availability_impl).Run(adapter); |
| return; |
| } |
| |
| BluetoothAdapterFactoryWrapper::Get().AcquireAdapter( |
| this, std::move(get_availability_impl)); |
| } |
| |
| void WebBluetoothServiceImpl::RequestDevice( |
| blink::mojom::WebBluetoothRequestDeviceOptionsPtr options, |
| RequestDeviceCallback callback) { |
| RecordRequestDeviceOptions(options); |
| |
| if (!GetAdapter()) { |
| if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) { |
| BluetoothAdapterFactoryWrapper::Get().AcquireAdapter( |
| this, base::BindOnce(&WebBluetoothServiceImpl::RequestDeviceImpl, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(options), std::move(callback))); |
| return; |
| } |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE, |
| nullptr /* device */); |
| return; |
| } |
| RequestDeviceImpl(std::move(options), std::move(callback), GetAdapter()); |
| } |
| |
| void WebBluetoothServiceImpl::GetDevices(GetDevicesCallback callback) { |
| if (GetBluetoothAllowed() != blink::mojom::WebBluetoothResult::SUCCESS || |
| !BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| auto* adapter = GetAdapter(); |
| if (adapter) { |
| GetDevicesImpl(std::move(callback), adapter); |
| return; |
| } |
| |
| BluetoothAdapterFactoryWrapper::Get().AcquireAdapter( |
| this, |
| base::BindOnce(&WebBluetoothServiceImpl::GetDevicesImpl, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteServerConnect( |
| const blink::WebBluetoothDeviceId& device_id, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothServerClient> |
| client, |
| RemoteServerConnectCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| bool is_connect_allowed = false; |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (delegate) { |
| is_connect_allowed = |
| delegate->HasDevicePermission(render_frame_host_, device_id); |
| } |
| } else { |
| is_connect_allowed = allowed_devices().IsAllowedToGATTConnect(device_id); |
| } |
| if (!is_connect_allowed) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED); |
| return; |
| } |
| |
| const CacheQueryResult query_result = QueryCacheForDevice(device_id); |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| RecordConnectGATTOutcome(query_result.outcome); |
| std::move(callback).Run(query_result.GetWebResult()); |
| return; |
| } |
| |
| if (connected_devices_->IsConnectedToDeviceWithId(device_id)) { |
| DVLOG(1) << "Already connected."; |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| // It's possible for WebBluetoothServiceImpl to issue two successive |
| // connection requests for which it would get two successive responses |
| // and consequently try to insert two BluetoothGattConnections for the |
| // same device. WebBluetoothServiceImpl should reject or queue connection |
| // requests if there is a pending connection already, but the platform |
| // abstraction doesn't currently support checking for pending connections. |
| // TODO(ortuno): CHECK that this never happens once the platform |
| // abstraction allows to check for pending connections. |
| // http://crbug.com/583544 |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothServerClient> |
| web_bluetooth_server_client(std::move(client)); |
| |
| query_result.device->CreateGattConnection(base::BindOnce( |
| &WebBluetoothServiceImpl::OnCreateGATTConnection, |
| weak_ptr_factory_.GetWeakPtr(), device_id, start_time, |
| std::move(web_bluetooth_server_client), std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteServerDisconnect( |
| const blink::WebBluetoothDeviceId& device_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (connected_devices_->IsConnectedToDeviceWithId(device_id)) { |
| DVLOG(1) << "Disconnecting device: " << device_id.str(); |
| connected_devices_->CloseConnectionToDeviceWithId(device_id); |
| } |
| } |
| |
| void WebBluetoothServiceImpl::RemoteServerGetPrimaryServices( |
| const blink::WebBluetoothDeviceId& device_id, |
| blink::mojom::WebBluetoothGATTQueryQuantity quantity, |
| const absl::optional<BluetoothUUID>& services_uuid, |
| RemoteServerGetPrimaryServicesCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RecordGetPrimaryServicesServices(quantity, services_uuid); |
| |
| if (!IsAllowedToAccessAtLeastOneService(device_id)) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_ANY_SERVICE, |
| /*service=*/absl::nullopt); |
| return; |
| } |
| |
| if (services_uuid && |
| !IsAllowedToAccessService(device_id, services_uuid.value())) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_SERVICE, |
| /*service=*/absl::nullopt); |
| return; |
| } |
| |
| const CacheQueryResult query_result = QueryCacheForDevice(device_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| RecordGetPrimaryServicesOutcome(quantity, query_result.outcome); |
| std::move(callback).Run(query_result.GetWebResult(), |
| absl::nullopt /* service */); |
| return; |
| } |
| |
| const std::string& device_address = query_result.device->GetAddress(); |
| |
| // We can't know if a service is present or not until GATT service discovery |
| // is complete for the device. |
| if (query_result.device->IsGattServicesDiscoveryComplete()) { |
| RemoteServerGetPrimaryServicesImpl(device_id, quantity, services_uuid, |
| std::move(callback), |
| query_result.device); |
| return; |
| } |
| |
| DVLOG(1) << "Services not yet discovered."; |
| pending_primary_services_requests_[device_address].push_back(base::BindOnce( |
| &WebBluetoothServiceImpl::RemoteServerGetPrimaryServicesImpl, |
| base::Unretained(this), device_id, quantity, services_uuid, |
| std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteServiceGetCharacteristics( |
| const std::string& service_instance_id, |
| blink::mojom::WebBluetoothGATTQueryQuantity quantity, |
| const absl::optional<BluetoothUUID>& characteristics_uuid, |
| RemoteServiceGetCharacteristicsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RecordGetCharacteristicsCharacteristic(quantity, characteristics_uuid); |
| |
| if (characteristics_uuid && |
| BluetoothBlocklist::Get().IsExcluded(characteristics_uuid.value())) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLOCKLISTED_CHARACTERISTIC_UUID, |
| absl::nullopt /* characteristics */); |
| return; |
| } |
| |
| const CacheQueryResult query_result = |
| QueryCacheForService(service_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| std::move(callback).Run(query_result.GetWebResult(), |
| absl::nullopt /* characteristics */); |
| return; |
| } |
| |
| std::vector<BluetoothRemoteGattCharacteristic*> characteristics = |
| characteristics_uuid ? query_result.service->GetCharacteristicsByUUID( |
| characteristics_uuid.value()) |
| : query_result.service->GetCharacteristics(); |
| |
| std::vector<blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr> |
| response_characteristics; |
| for (BluetoothRemoteGattCharacteristic* characteristic : characteristics) { |
| if (BluetoothBlocklist::Get().IsExcluded(characteristic->GetUUID())) { |
| continue; |
| } |
| std::string characteristic_instance_id = characteristic->GetIdentifier(); |
| auto insert_result = characteristic_id_to_service_id_.insert( |
| std::make_pair(characteristic_instance_id, service_instance_id)); |
| // If value is already in map, DCHECK it's valid. |
| if (!insert_result.second) |
| DCHECK(insert_result.first->second == service_instance_id); |
| |
| blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr characteristic_ptr = |
| blink::mojom::WebBluetoothRemoteGATTCharacteristic::New(); |
| characteristic_ptr->instance_id = characteristic_instance_id; |
| characteristic_ptr->uuid = characteristic->GetUUID(); |
| characteristic_ptr->properties = |
| static_cast<uint32_t>(characteristic->GetProperties()); |
| response_characteristics.push_back(std::move(characteristic_ptr)); |
| |
| if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) { |
| break; |
| } |
| } |
| |
| if (!response_characteristics.empty()) { |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, |
| std::move(response_characteristics)); |
| return; |
| } |
| |
| std::move(callback).Run( |
| characteristics_uuid |
| ? blink::mojom::WebBluetoothResult::CHARACTERISTIC_NOT_FOUND |
| : blink::mojom::WebBluetoothResult::NO_CHARACTERISTICS_FOUND, |
| absl::nullopt /* characteristics */); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteCharacteristicGetDescriptors( |
| const std::string& characteristic_instance_id, |
| blink::mojom::WebBluetoothGATTQueryQuantity quantity, |
| const absl::optional<BluetoothUUID>& descriptors_uuid, |
| RemoteCharacteristicGetDescriptorsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (descriptors_uuid && |
| BluetoothBlocklist::Get().IsExcluded(descriptors_uuid.value())) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLOCKLISTED_DESCRIPTOR_UUID, |
| absl::nullopt /* descriptor */); |
| return; |
| } |
| |
| const CacheQueryResult query_result = |
| QueryCacheForCharacteristic(characteristic_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| std::move(callback).Run(query_result.GetWebResult(), |
| absl::nullopt /* descriptor */); |
| return; |
| } |
| |
| auto descriptors = descriptors_uuid |
| ? query_result.characteristic->GetDescriptorsByUUID( |
| descriptors_uuid.value()) |
| : query_result.characteristic->GetDescriptors(); |
| |
| std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr> |
| response_descriptors; |
| for (BluetoothRemoteGattDescriptor* descriptor : descriptors) { |
| if (BluetoothBlocklist::Get().IsExcluded(descriptor->GetUUID())) { |
| continue; |
| } |
| std::string descriptor_instance_id = descriptor->GetIdentifier(); |
| auto insert_result = descriptor_id_to_characteristic_id_.insert( |
| {descriptor_instance_id, characteristic_instance_id}); |
| // If value is already in map, DCHECK it's valid. |
| if (!insert_result.second) |
| DCHECK(insert_result.first->second == characteristic_instance_id); |
| |
| auto descriptor_ptr(blink::mojom::WebBluetoothRemoteGATTDescriptor::New()); |
| descriptor_ptr->instance_id = descriptor_instance_id; |
| descriptor_ptr->uuid = descriptor->GetUUID(); |
| response_descriptors.push_back(std::move(descriptor_ptr)); |
| |
| if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) { |
| break; |
| } |
| } |
| |
| if (!response_descriptors.empty()) { |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, |
| std::move(response_descriptors)); |
| return; |
| } |
| std::move(callback).Run( |
| descriptors_uuid ? blink::mojom::WebBluetoothResult::DESCRIPTOR_NOT_FOUND |
| : blink::mojom::WebBluetoothResult::NO_DESCRIPTORS_FOUND, |
| absl::nullopt /* descriptors */); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteCharacteristicReadValue( |
| const std::string& characteristic_instance_id, |
| RemoteCharacteristicReadValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const CacheQueryResult query_result = |
| QueryCacheForCharacteristic(characteristic_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| RecordCharacteristicReadValueOutcome(query_result.outcome); |
| std::move(callback).Run(query_result.GetWebResult(), |
| absl::nullopt /* value */); |
| return; |
| } |
| |
| if (BluetoothBlocklist::Get().IsExcludedFromReads( |
| query_result.characteristic->GetUUID())) { |
| RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::kBlocklisted); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ, |
| absl::nullopt /* value */); |
| return; |
| } |
| |
| query_result.characteristic->ReadRemoteCharacteristic( |
| base::BindOnce(&WebBluetoothServiceImpl::OnCharacteristicReadValue, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteCharacteristicWriteValue( |
| const std::string& characteristic_instance_id, |
| const std::vector<uint8_t>& value, |
| blink::mojom::WebBluetoothWriteType write_type, |
| RemoteCharacteristicWriteValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // We perform the length check on the renderer side. So if we |
| // get a value with length > 512, we can assume it's a hostile |
| // renderer and kill it. |
| if (value.size() > 512) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_WRITE_VALUE_LENGTH); |
| return; |
| } |
| |
| const CacheQueryResult query_result = |
| QueryCacheForCharacteristic(characteristic_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| RecordCharacteristicWriteValueOutcome(query_result.outcome); |
| std::move(callback).Run(query_result.GetWebResult()); |
| return; |
| } |
| |
| if (BluetoothBlocklist::Get().IsExcludedFromWrites( |
| query_result.characteristic->GetUUID())) { |
| RecordCharacteristicWriteValueOutcome( |
| UMAGATTOperationOutcome::kBlocklisted); |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| base::OnceClosure write_callback = base::BindOnce( |
| &WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess, |
| weak_ptr_factory_.GetWeakPtr(), std::move(split_callback.first)); |
| BluetoothGattCharacteristic::ErrorCallback write_error_callback = |
| base::BindOnce(&WebBluetoothServiceImpl::OnCharacteristicWriteValueFailed, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(split_callback.second)); |
| using WebBluetoothWriteType = blink::mojom::WebBluetoothWriteType; |
| using WriteType = BluetoothRemoteGattCharacteristic::WriteType; |
| switch (write_type) { |
| case WebBluetoothWriteType::kWriteDefaultDeprecated: |
| query_result.characteristic->DeprecatedWriteRemoteCharacteristic( |
| value, std::move(write_callback), std::move(write_error_callback)); |
| break; |
| case WebBluetoothWriteType::kWriteWithResponse: |
| query_result.characteristic->WriteRemoteCharacteristic( |
| value, WriteType::kWithResponse, std::move(write_callback), |
| std::move(write_error_callback)); |
| break; |
| case WebBluetoothWriteType::kWriteWithoutResponse: |
| query_result.characteristic->WriteRemoteCharacteristic( |
| value, WriteType::kWithoutResponse, std::move(write_callback), |
| std::move(write_error_callback)); |
| break; |
| } |
| } |
| |
| void WebBluetoothServiceImpl::RemoteCharacteristicStartNotifications( |
| const std::string& characteristic_instance_id, |
| mojo::PendingAssociatedRemote< |
| blink::mojom::WebBluetoothCharacteristicClient> client, |
| RemoteCharacteristicStartNotificationsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto iter = |
| characteristic_id_to_notify_session_.find(characteristic_instance_id); |
| if (iter != characteristic_id_to_notify_session_.end() && |
| iter->second->gatt_notify_session->IsActive()) { |
| // If the frame has already started notifications and the notifications |
| // are active we return SUCCESS. |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| const CacheQueryResult query_result = |
| QueryCacheForCharacteristic(characteristic_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| RecordStartNotificationsOutcome(UMAGATTOperationOutcome::kNotSupported); |
| std::move(callback).Run(query_result.GetWebResult()); |
| return; |
| } |
| |
| BluetoothRemoteGattCharacteristic::Properties notify_or_indicate = |
| query_result.characteristic->GetProperties() & |
| (BluetoothRemoteGattCharacteristic::PROPERTY_NOTIFY | |
| BluetoothRemoteGattCharacteristic::PROPERTY_INDICATE); |
| if (!notify_or_indicate) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::GATT_NOT_SUPPORTED); |
| return; |
| } |
| |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> |
| characteristic_client(std::move(client)); |
| |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| query_result.characteristic->StartNotifySession( |
| base::BindOnce(&WebBluetoothServiceImpl::OnStartNotifySessionSuccess, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(characteristic_client), |
| std::move(split_callback.first)), |
| base::BindOnce(&WebBluetoothServiceImpl::OnStartNotifySessionFailed, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(split_callback.second))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteCharacteristicStopNotifications( |
| const std::string& characteristic_instance_id, |
| RemoteCharacteristicStopNotificationsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const CacheQueryResult query_result = |
| QueryCacheForCharacteristic(characteristic_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| auto notify_session_iter = |
| characteristic_id_to_notify_session_.find(characteristic_instance_id); |
| if (notify_session_iter == characteristic_id_to_notify_session_.end()) { |
| // If the frame hasn't subscribed to notifications before we just |
| // run the callback. |
| std::move(callback).Run(); |
| return; |
| } |
| notify_session_iter->second->gatt_notify_session->Stop( |
| base::BindOnce(&WebBluetoothServiceImpl::OnStopNotifySessionComplete, |
| weak_ptr_factory_.GetWeakPtr(), characteristic_instance_id, |
| std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteDescriptorReadValue( |
| const std::string& descriptor_instance_id, |
| RemoteDescriptorReadValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const CacheQueryResult query_result = |
| QueryCacheForDescriptor(descriptor_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| std::move(callback).Run(query_result.GetWebResult(), |
| absl::nullopt /* value */); |
| return; |
| } |
| |
| if (BluetoothBlocklist::Get().IsExcludedFromReads( |
| query_result.descriptor->GetUUID())) { |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ, |
| absl::nullopt /* value */); |
| return; |
| } |
| |
| query_result.descriptor->ReadRemoteDescriptor( |
| base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorReadValue, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteDescriptorWriteValue( |
| const std::string& descriptor_instance_id, |
| const std::vector<uint8_t>& value, |
| RemoteDescriptorWriteValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // We perform the length check on the renderer side. So if we |
| // get a value with length > 512, we can assume it's a hostile |
| // renderer and kill it. |
| if (value.size() > 512) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_WRITE_VALUE_LENGTH); |
| return; |
| } |
| |
| const CacheQueryResult query_result = |
| QueryCacheForDescriptor(descriptor_instance_id); |
| |
| if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) { |
| return; |
| } |
| |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) { |
| std::move(callback).Run(query_result.GetWebResult()); |
| return; |
| } |
| |
| if (BluetoothBlocklist::Get().IsExcludedFromWrites( |
| query_result.descriptor->GetUUID())) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE); |
| return; |
| } |
| |
| // TODO(crbug.com/730593): Remove SplitOnceCallback() by updating |
| // the callee interface. |
| auto split_callback = base::SplitOnceCallback(std::move(callback)); |
| query_result.descriptor->WriteRemoteDescriptor( |
| value, |
| base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(split_callback.first)), |
| base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorWriteValueFailed, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(split_callback.second))); |
| } |
| |
| void WebBluetoothServiceImpl::RequestScanningStart( |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_info, |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, |
| RequestScanningStartCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const url::Origin requesting_origin = |
| render_frame_host_->GetLastCommittedOrigin(); |
| const url::Origin embedding_origin = |
| web_contents()->GetMainFrame()->GetLastCommittedOrigin(); |
| |
| bool blocked = GetContentClient()->browser()->IsBluetoothScanningBlocked( |
| web_contents()->GetBrowserContext(), requesting_origin, embedding_origin); |
| if (blocked) { |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED); |
| return; |
| } |
| |
| // The renderer should never send invalid options. |
| if (!IsValidRequestScanOptions(options)) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_OPTIONS); |
| return; |
| } |
| |
| if (!GetAdapter()) { |
| if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) { |
| BluetoothAdapterFactoryWrapper::Get().AcquireAdapter( |
| this, |
| base::BindOnce(&WebBluetoothServiceImpl::RequestScanningStartImpl, |
| weak_ptr_factory_.GetWeakPtr(), std::move(client_info), |
| std::move(options), std::move(callback))); |
| return; |
| } |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE); |
| return; |
| } |
| |
| RequestScanningStartImpl(std::move(client_info), std::move(options), |
| std::move(callback), GetAdapter()); |
| } |
| |
| void WebBluetoothServiceImpl::WatchAdvertisementsForDevice( |
| const blink::WebBluetoothDeviceId& device_id, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_info, |
| WatchAdvertisementsForDeviceCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| blink::mojom::WebBluetoothResult allowed_result = GetBluetoothAllowed(); |
| if (allowed_result != blink::mojom::WebBluetoothResult::SUCCESS) { |
| std::move(callback).Run(allowed_result); |
| return; |
| } |
| |
| // The renderer should never send an invalid |device_id|. |
| if (!device_id.IsValid()) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_OPTIONS); |
| return; |
| } |
| |
| if (!GetAdapter()) { |
| if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) { |
| BluetoothAdapterFactoryWrapper::Get().AcquireAdapter( |
| this, base::BindOnce( |
| &WebBluetoothServiceImpl::WatchAdvertisementsForDeviceImpl, |
| weak_ptr_factory_.GetWeakPtr(), device_id, |
| std::move(client_info), std::move(callback))); |
| return; |
| } |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE); |
| return; |
| } |
| |
| WatchAdvertisementsForDeviceImpl(std::move(device_id), std::move(client_info), |
| std::move(callback), GetAdapter()); |
| } |
| |
| void WebBluetoothServiceImpl::RemoveDisconnectedClients() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // TODO(https://crbug.com/1087007): These two classes can potentially be |
| // combined into the same container. |
| base::EraseIf(scanning_clients_, |
| [](const std::unique_ptr<ScanningClient>& client) { |
| return !client->is_connected(); |
| }); |
| base::EraseIf(watch_advertisements_clients_, |
| [](const std::unique_ptr<WatchAdvertisementsClient>& client) { |
| return !client->is_connected(); |
| }); |
| MaybeStopDiscovery(); |
| } |
| |
| void WebBluetoothServiceImpl::MaybeStopDiscovery() { |
| if (scanning_clients_.empty()) |
| ble_scan_discovery_session_.reset(); |
| |
| if (watch_advertisements_clients_.empty()) |
| watch_advertisements_discovery_session_.reset(); |
| } |
| |
| void WebBluetoothServiceImpl::RequestScanningStartImpl( |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_info, |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, |
| RequestScanningStartCallback callback, |
| scoped_refptr<BluetoothAdapter> adapter) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!adapter) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE); |
| return; |
| } |
| |
| if (request_scanning_start_callback_) { |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::PROMPT_CANCELED); |
| return; |
| } |
| |
| if (ble_scan_discovery_session_) { |
| auto scanning_client = std::make_unique<ScanningClient>( |
| /*service=*/this, std::move(client_info), std::move(options), |
| std::move(callback)); |
| |
| if (AreScanFiltersAllowed(scanning_client->scan_options().filters)) { |
| scanning_client->RunRequestScanningStartCallback( |
| blink::mojom::WebBluetoothResult::SUCCESS); |
| scanning_client->set_allow_send_event(true); |
| scanning_clients_.push_back(std::move(scanning_client)); |
| return; |
| } |
| |
| // By resetting |device_scanning_prompt_controller_|, it returns an error if |
| // there are duplicate calls to RequestScanningStart(). |
| device_scanning_prompt_controller_ = |
| std::make_unique<BluetoothDeviceScanningPromptController>( |
| this, render_frame_host_); |
| scanning_client->SetPromptController( |
| device_scanning_prompt_controller_.get()); |
| scanning_clients_.push_back(std::move(scanning_client)); |
| device_scanning_prompt_controller_->ShowPermissionPrompt(); |
| return; |
| } |
| |
| request_scanning_start_callback_ = std::move(callback); |
| |
| // TODO(https://crbug.com/969109): Since scanning without a filter wastes |
| // resources, we need use StartDiscoverySessionWithFilter() instead of |
| // StartDiscoverySession() here. |
| adapter->StartDiscoverySession( |
| base::BindOnce( |
| &WebBluetoothServiceImpl::OnStartDiscoverySessionForScanning, |
| weak_ptr_factory_.GetWeakPtr(), std::move(client_info), |
| std::move(options)), |
| base::BindOnce( |
| &WebBluetoothServiceImpl::OnDiscoverySessionErrorForScanning, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void WebBluetoothServiceImpl::OnStartDiscoverySessionForScanning( |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_info, |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, |
| std::unique_ptr<BluetoothDiscoverySession> session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!ble_scan_discovery_session_); |
| |
| ble_scan_discovery_session_ = std::move(session); |
| |
| auto scanning_client = std::make_unique<ScanningClient>( |
| /*service=*/this, std::move(client_info), std::move(options), |
| std::move(request_scanning_start_callback_)); |
| |
| if (AreScanFiltersAllowed(scanning_client->scan_options().filters)) { |
| scanning_client->RunRequestScanningStartCallback( |
| blink::mojom::WebBluetoothResult::SUCCESS); |
| scanning_client->set_allow_send_event(true); |
| scanning_clients_.push_back(std::move(scanning_client)); |
| return; |
| } |
| |
| device_scanning_prompt_controller_ = |
| std::make_unique<BluetoothDeviceScanningPromptController>( |
| this, render_frame_host_); |
| scanning_client->SetPromptController( |
| device_scanning_prompt_controller_.get()); |
| scanning_clients_.push_back(std::move(scanning_client)); |
| device_scanning_prompt_controller_->ShowPermissionPrompt(); |
| } |
| |
| void WebBluetoothServiceImpl::OnDiscoverySessionErrorForScanning() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| device_scanning_prompt_controller_.reset(); |
| |
| std::move(request_scanning_start_callback_) |
| .Run(blink::mojom::WebBluetoothResult::NO_BLUETOOTH_ADAPTER); |
| ClearAdvertisementClients(); |
| } |
| |
| void WebBluetoothServiceImpl::RequestDeviceImpl( |
| blink::mojom::WebBluetoothRequestDeviceOptionsPtr options, |
| RequestDeviceCallback callback, |
| scoped_refptr<BluetoothAdapter> adapter) { |
| // The renderer should never send invalid options. |
| if (!IsValidRequestDeviceOptions(options)) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_OPTIONS); |
| return; |
| } |
| |
| // Calls to requestDevice() require user activation (user gestures). We |
| // should close any opened chooser when a duplicate requestDevice call is |
| // made with the same user activation or when any gesture occurs outside |
| // of the opened chooser. This does not happen on all platforms so we |
| // don't DCHECK that the old one is closed. We destroy the old chooser |
| // before constructing the new one to make sure they can't conflict. |
| device_chooser_controller_.reset(); |
| |
| device_chooser_controller_ = |
| std::make_unique<BluetoothDeviceChooserController>( |
| this, render_frame_host_, std::move(adapter)); |
| device_chooser_controller_->GetDevice( |
| std::move(options), |
| base::BindOnce(&WebBluetoothServiceImpl::OnGetDevice, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebBluetoothServiceImpl::GetDevicesImpl( |
| GetDevicesCallback callback, |
| scoped_refptr<BluetoothAdapter> adapter) { |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| std::move(callback).Run(delegate->GetPermittedDevices(render_frame_host_)); |
| return; |
| } |
| |
| // BluetoothAllowedDevices does not provide a way to get all of the |
| // permitted devices, so instead return all of the allowed devices that |
| // are currently known to the system. |
| std::vector<blink::mojom::WebBluetoothDevicePtr> web_bluetooth_devices; |
| for (const auto* device : adapter->GetDevices()) { |
| const blink::WebBluetoothDeviceId* device_id = |
| allowed_devices().GetDeviceId(device->GetAddress()); |
| if (!device_id || !allowed_devices().IsAllowedToGATTConnect(*device_id)) |
| continue; |
| |
| web_bluetooth_devices.push_back( |
| blink::mojom::WebBluetoothDevice::New(*device_id, device->GetName())); |
| } |
| std::move(callback).Run(std::move(web_bluetooth_devices)); |
| } |
| |
| void WebBluetoothServiceImpl::WatchAdvertisementsForDeviceImpl( |
| const blink::WebBluetoothDeviceId& device_id, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_info, |
| WatchAdvertisementsForDeviceCallback callback, |
| scoped_refptr<BluetoothAdapter> adapter) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!adapter) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE); |
| return; |
| } |
| |
| auto watch_advertisements_client = |
| std::make_unique<WatchAdvertisementsClient>( |
| /*service=*/this, std::move(client_info), std::move(device_id)); |
| if (watch_advertisements_discovery_session_) { |
| watch_advertisements_clients_.push_back( |
| std::move(watch_advertisements_client)); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| // If |watch_advertismeents_callbacks_and_clients_| has more than one entry, |
| // then it means that a previous watch advertisements operation has already |
| // started a discovery session, so the |callback| and |client| for this |
| // operation needs to be stored until the start discovery operation is |
| // complete. |
| watch_advertisements_callbacks_and_clients_.emplace_back( |
| std::move(callback), std::move(watch_advertisements_client)); |
| if (watch_advertisements_callbacks_and_clients_.size() > 1) |
| return; |
| |
| // Not all platforms support filtering by address. |
| // TODO(https://crbug.com/969109): Use StartDiscoverySessionWithFilter() to |
| // filter out by MAC address when platforms provide this capability. |
| adapter->StartDiscoverySession( |
| base::BindOnce(&WebBluetoothServiceImpl:: |
| OnStartDiscoverySessionForWatchAdvertisements, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&WebBluetoothServiceImpl:: |
| OnDiscoverySessionErrorForWatchAdvertisements, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void WebBluetoothServiceImpl::OnStartDiscoverySessionForWatchAdvertisements( |
| std::unique_ptr<BluetoothDiscoverySession> session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!watch_advertisements_discovery_session_); |
| watch_advertisements_discovery_session_ = std::move(session); |
| |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| |
| for (auto& callback_and_client : |
| watch_advertisements_callbacks_and_clients_) { |
| if (!callback_and_client.second->is_connected()) { |
| std::move(callback_and_client.first) |
| .Run(blink::mojom::WebBluetoothResult::WATCH_ADVERTISEMENTS_ABORTED); |
| continue; |
| } |
| |
| // If the new permissions backend is enabled, verify the permission using |
| // the delegate. |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend) && |
| (!delegate || |
| !delegate->HasDevicePermission( |
| render_frame_host_, callback_and_client.second->device_id()))) { |
| std::move(callback_and_client.first) |
| .Run(blink::mojom::WebBluetoothResult:: |
| NOT_ALLOWED_TO_ACCESS_ANY_SERVICE); |
| continue; |
| } |
| |
| // Otherwise verify it via |allowed_devices|. |
| if (!base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend) && |
| !allowed_devices().IsAllowedToGATTConnect( |
| callback_and_client.second->device_id())) { |
| std::move(callback_and_client.first) |
| .Run(blink::mojom::WebBluetoothResult:: |
| NOT_ALLOWED_TO_ACCESS_ANY_SERVICE); |
| continue; |
| } |
| |
| watch_advertisements_clients_.push_back( |
| std::move(callback_and_client.second)); |
| std::move(callback_and_client.first) |
| .Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| } |
| |
| watch_advertisements_callbacks_and_clients_.clear(); |
| |
| // If a client was disconncted while a discovery session was being started, |
| // then there may not be any valid clients, so discovery should be stopped. |
| MaybeStopDiscovery(); |
| } |
| |
| void WebBluetoothServiceImpl::OnDiscoverySessionErrorForWatchAdvertisements() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| for (auto& callback_and_client : |
| watch_advertisements_callbacks_and_clients_) { |
| std::move(callback_and_client.first) |
| .Run(blink::mojom::WebBluetoothResult::NO_BLUETOOTH_ADAPTER); |
| } |
| watch_advertisements_callbacks_and_clients_.clear(); |
| ClearAdvertisementClients(); |
| } |
| |
| void WebBluetoothServiceImpl::RemoteServerGetPrimaryServicesImpl( |
| const blink::WebBluetoothDeviceId& device_id, |
| blink::mojom::WebBluetoothGATTQueryQuantity quantity, |
| const absl::optional<BluetoothUUID>& services_uuid, |
| RemoteServerGetPrimaryServicesCallback callback, |
| BluetoothDevice* device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!device->IsGattConnected()) { |
| // The device disconnected while discovery was pending. The returned error |
| // does not matter because the renderer ignores the error if the device |
| // disconnected. |
| RecordGetPrimaryServicesOutcome( |
| quantity, UMAGetPrimaryServiceOutcome::DEVICE_DISCONNECTED); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND, |
| absl::nullopt /* services */); |
| return; |
| } |
| |
| DCHECK(device->IsGattServicesDiscoveryComplete()); |
| |
| std::vector<BluetoothRemoteGattService*> services = |
| services_uuid ? device->GetPrimaryServicesByUUID(services_uuid.value()) |
| : device->GetPrimaryServices(); |
| |
| std::vector<blink::mojom::WebBluetoothRemoteGATTServicePtr> response_services; |
| for (BluetoothRemoteGattService* service : services) { |
| if (!IsAllowedToAccessService(device_id, service->GetUUID())) |
| continue; |
| |
| std::string service_instance_id = service->GetIdentifier(); |
| const std::string& device_address = device->GetAddress(); |
| auto insert_result = service_id_to_device_address_.insert( |
| make_pair(service_instance_id, device_address)); |
| // If value is already in map, DCHECK it's valid. |
| if (!insert_result.second) |
| DCHECK_EQ(insert_result.first->second, device_address); |
| |
| blink::mojom::WebBluetoothRemoteGATTServicePtr service_ptr = |
| blink::mojom::WebBluetoothRemoteGATTService::New(); |
| service_ptr->instance_id = service_instance_id; |
| service_ptr->uuid = service->GetUUID(); |
| response_services.push_back(std::move(service_ptr)); |
| |
| if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) { |
| break; |
| } |
| } |
| |
| if (!response_services.empty()) { |
| DVLOG(1) << "Services found in device."; |
| RecordGetPrimaryServicesOutcome(quantity, |
| UMAGetPrimaryServiceOutcome::SUCCESS); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, |
| std::move(response_services)); |
| return; |
| } |
| |
| DVLOG(1) << "Services not found in device."; |
| RecordGetPrimaryServicesOutcome( |
| quantity, services_uuid ? UMAGetPrimaryServiceOutcome::NOT_FOUND |
| : UMAGetPrimaryServiceOutcome::NO_SERVICES); |
| std::move(callback).Run( |
| services_uuid ? blink::mojom::WebBluetoothResult::SERVICE_NOT_FOUND |
| : blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND, |
| absl::nullopt /* services */); |
| } |
| |
| void WebBluetoothServiceImpl::OnGetDevice( |
| RequestDeviceCallback callback, |
| blink::mojom::WebBluetoothResult result, |
| blink::mojom::WebBluetoothRequestDeviceOptionsPtr options, |
| const std::string& device_address) { |
| device_chooser_controller_.reset(); |
| |
| if (result != blink::mojom::WebBluetoothResult::SUCCESS) { |
| // Errors are recorded by |device_chooser_controller_|. |
| std::move(callback).Run(result, /*device=*/nullptr); |
| return; |
| } |
| |
| const BluetoothDevice* const device = GetAdapter()->GetDevice(device_address); |
| if (device == nullptr) { |
| DVLOG(1) << "Device " << device_address << " no longer in adapter"; |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::CHOSEN_DEVICE_VANISHED, |
| nullptr /* device */); |
| return; |
| } |
| |
| DVLOG(1) << "Device: " << device->GetNameForDisplay(); |
| |
| auto web_bluetooth_device = blink::mojom::WebBluetoothDevice::New(); |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) { |
| std::move(callback).Run( |
| blink::mojom::WebBluetoothResult::WEB_BLUETOOTH_NOT_SUPPORTED, |
| /*device=*/nullptr); |
| return; |
| } |
| web_bluetooth_device->id = delegate->GrantServiceAccessPermission( |
| render_frame_host_, device, options.get()); |
| } else { |
| web_bluetooth_device->id = |
| allowed_devices().AddDevice(device_address, options); |
| } |
| web_bluetooth_device->name = device->GetName(); |
| |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, |
| std::move(web_bluetooth_device)); |
| } |
| |
| void WebBluetoothServiceImpl::OnCreateGATTConnection( |
| const blink::WebBluetoothDeviceId& device_id, |
| base::TimeTicks start_time, |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothServerClient> client, |
| RemoteServerConnectCallback callback, |
| std::unique_ptr<BluetoothGattConnection> connection, |
| absl::optional<device::BluetoothDevice::ConnectErrorCode> error_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error_code.has_value()) { |
| RecordConnectGATTTimeFailed(base::TimeTicks::Now() - start_time); |
| std::move(callback).Run(TranslateConnectErrorAndRecord(error_code.value())); |
| return; |
| } |
| RecordConnectGATTTimeSuccess(base::TimeTicks::Now() - start_time); |
| RecordConnectGATTOutcome(UMAConnectGATTOutcome::SUCCESS); |
| |
| if (connected_devices_->IsConnectedToDeviceWithId(device_id)) { |
| DVLOG(1) << "Already connected."; |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| connected_devices_->Insert(device_id, std::move(connection), |
| std::move(client)); |
| } |
| |
| void WebBluetoothServiceImpl::OnCharacteristicReadValue( |
| RemoteCharacteristicReadValueCallback callback, |
| absl::optional<BluetoothGattService::GattErrorCode> error_code, |
| const std::vector<uint8_t>& value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error_code.has_value()) { |
| std::move(callback).Run( |
| TranslateGATTErrorAndRecord(error_code.value(), |
| UMAGATTOperation::kCharacteristicRead), |
| /*value=*/absl::nullopt); |
| return; |
| } |
| RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::kSuccess); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, value); |
| } |
| |
| void WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess( |
| RemoteCharacteristicWriteValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::kSuccess); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| } |
| |
| void WebBluetoothServiceImpl::OnCharacteristicWriteValueFailed( |
| RemoteCharacteristicWriteValueCallback callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(TranslateGATTErrorAndRecord( |
| error_code, UMAGATTOperation::kCharacteristicWrite)); |
| } |
| |
| void WebBluetoothServiceImpl::OnStartNotifySessionSuccess( |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> |
| client, |
| RemoteCharacteristicStartNotificationsCallback callback, |
| std::unique_ptr<BluetoothGattNotifySession> notify_session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Copy Characteristic Instance ID before passing a unique pointer because |
| // compilers may evaluate arguments in any order. |
| std::string characteristic_instance_id = |
| notify_session->GetCharacteristicIdentifier(); |
| |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| // Saving the BluetoothGattNotifySession keeps notifications active. |
| auto gatt_notify_session_and_client = |
| std::make_unique<GATTNotifySessionAndCharacteristicClient>( |
| std::move(notify_session), std::move(client)); |
| characteristic_id_to_notify_session_[characteristic_instance_id] = |
| std::move(gatt_notify_session_and_client); |
| } |
| |
| void WebBluetoothServiceImpl::OnStartNotifySessionFailed( |
| RemoteCharacteristicStartNotificationsCallback callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(TranslateGATTErrorAndRecord( |
| error_code, UMAGATTOperation::kStartNotifications)); |
| } |
| |
| void WebBluetoothServiceImpl::OnStopNotifySessionComplete( |
| const std::string& characteristic_instance_id, |
| RemoteCharacteristicStopNotificationsCallback callback) { |
| characteristic_id_to_notify_session_.erase(characteristic_instance_id); |
| std::move(callback).Run(); |
| } |
| |
| void WebBluetoothServiceImpl::OnDescriptorReadValue( |
| RemoteDescriptorReadValueCallback callback, |
| absl::optional<BluetoothGattService::GattErrorCode> error_code, |
| const std::vector<uint8_t>& value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error_code.has_value()) { |
| std::move(callback).Run( |
| TranslateGATTErrorAndRecord(error_code.value(), |
| UMAGATTOperation::kDescriptorReadObsolete), |
| /*value=*/absl::nullopt); |
| return; |
| } |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, value); |
| } |
| |
| void WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess( |
| RemoteDescriptorWriteValueCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS); |
| } |
| |
| void WebBluetoothServiceImpl::OnDescriptorWriteValueFailed( |
| RemoteDescriptorWriteValueCallback callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(TranslateGATTErrorAndRecord( |
| error_code, UMAGATTOperation::kDescriptorWriteObsolete)); |
| } |
| |
| CacheQueryResult WebBluetoothServiceImpl::QueryCacheForDevice( |
| const blink::WebBluetoothDeviceId& device_id) { |
| std::string device_address; |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (delegate) { |
| device_address = |
| delegate->GetDeviceAddress(render_frame_host_, device_id); |
| } |
| } else { |
| device_address = allowed_devices().GetDeviceAddress(device_id); |
| } |
| |
| if (device_address.empty()) { |
| CrashRendererAndClosePipe(bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| |
| CacheQueryResult result; |
| result.device = GetAdapter()->GetDevice(device_address); |
| |
| // When a device can't be found in the BluetoothAdapter, that generally |
| // indicates that it's gone out of range. We reject with a NetworkError in |
| // that case. |
| if (result.device == nullptr) { |
| result.outcome = CacheQueryOutcome::NO_DEVICE; |
| } |
| return result; |
| } |
| |
| CacheQueryResult WebBluetoothServiceImpl::QueryCacheForService( |
| const std::string& service_instance_id) { |
| auto device_iter = service_id_to_device_address_.find(service_instance_id); |
| |
| // Kill the render, see "ID Not in Map Note" above. |
| if (device_iter == service_id_to_device_address_.end()) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_SERVICE_ID); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| |
| blink::WebBluetoothDeviceId device_id; |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (delegate) { |
| device_id = delegate->GetWebBluetoothDeviceId(render_frame_host_, |
| device_iter->second); |
| } |
| } else { |
| const blink::WebBluetoothDeviceId* device_id_ptr = |
| allowed_devices().GetDeviceId(device_iter->second); |
| if (device_id_ptr) |
| device_id = *device_id_ptr; |
| } |
| // Kill the renderer if origin is not allowed to access the device. |
| if (!device_id.IsValid()) { |
| CrashRendererAndClosePipe(bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| |
| CacheQueryResult result = QueryCacheForDevice(device_id); |
| if (result.outcome != CacheQueryOutcome::SUCCESS) |
| return result; |
| |
| result.service = result.device->GetGattService(service_instance_id); |
| if (result.service == nullptr) { |
| result.outcome = CacheQueryOutcome::NO_SERVICE; |
| return result; |
| } |
| |
| if (!IsAllowedToAccessService(device_id, result.service->GetUUID())) { |
| CrashRendererAndClosePipe(bad_message::BDH_SERVICE_NOT_ALLOWED_FOR_ORIGIN); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| return result; |
| } |
| |
| CacheQueryResult WebBluetoothServiceImpl::QueryCacheForCharacteristic( |
| const std::string& characteristic_instance_id) { |
| auto characteristic_iter = |
| characteristic_id_to_service_id_.find(characteristic_instance_id); |
| |
| // Kill the render, see "ID Not in Map Note" above. |
| if (characteristic_iter == characteristic_id_to_service_id_.end()) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_CHARACTERISTIC_ID); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| |
| CacheQueryResult result = QueryCacheForService(characteristic_iter->second); |
| |
| if (result.outcome != CacheQueryOutcome::SUCCESS) { |
| return result; |
| } |
| |
| result.characteristic = |
| result.service->GetCharacteristic(characteristic_instance_id); |
| |
| if (result.characteristic == nullptr) { |
| result.outcome = CacheQueryOutcome::NO_CHARACTERISTIC; |
| } |
| |
| return result; |
| } |
| |
| CacheQueryResult WebBluetoothServiceImpl::QueryCacheForDescriptor( |
| const std::string& descriptor_instance_id) { |
| auto descriptor_iter = |
| descriptor_id_to_characteristic_id_.find(descriptor_instance_id); |
| |
| // Kill the render, see "ID Not in Map Note" above. |
| if (descriptor_iter == descriptor_id_to_characteristic_id_.end()) { |
| CrashRendererAndClosePipe(bad_message::BDH_INVALID_DESCRIPTOR_ID); |
| return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); |
| } |
| |
| CacheQueryResult result = |
| QueryCacheForCharacteristic(descriptor_iter->second); |
| |
| if (result.outcome != CacheQueryOutcome::SUCCESS) { |
| return result; |
| } |
| |
| result.descriptor = |
| result.characteristic->GetDescriptor(descriptor_instance_id); |
| |
| if (result.descriptor == nullptr) { |
| result.outcome = CacheQueryOutcome::NO_DESCRIPTOR; |
| } |
| |
| return result; |
| } |
| |
| void WebBluetoothServiceImpl::RunPendingPrimaryServicesRequests( |
| BluetoothDevice* device) { |
| const std::string& device_address = device->GetAddress(); |
| |
| auto iter = pending_primary_services_requests_.find(device_address); |
| if (iter == pending_primary_services_requests_.end()) { |
| return; |
| } |
| std::vector<PrimaryServicesRequestCallback> requests = |
| std::move(iter->second); |
| pending_primary_services_requests_.erase(iter); |
| |
| for (PrimaryServicesRequestCallback& request : requests) { |
| std::move(request).Run(device); |
| } |
| |
| // Sending get-service responses unexpectedly queued another request. |
| DCHECK(!base::Contains(pending_primary_services_requests_, device_address)); |
| } |
| |
| RenderProcessHost* WebBluetoothServiceImpl::GetRenderProcessHost() { |
| return render_frame_host_->GetProcess(); |
| } |
| |
| BluetoothAdapter* WebBluetoothServiceImpl::GetAdapter() { |
| return BluetoothAdapterFactoryWrapper::Get().GetAdapter(this); |
| } |
| |
| void WebBluetoothServiceImpl::CrashRendererAndClosePipe( |
| bad_message::BadMessageReason reason) { |
| bad_message::ReceivedBadMessage(GetRenderProcessHost(), reason); |
| receiver_.reset(); |
| } |
| |
| url::Origin WebBluetoothServiceImpl::GetOrigin() { |
| return render_frame_host_->GetLastCommittedOrigin(); |
| } |
| |
| BluetoothAllowedDevices& WebBluetoothServiceImpl::allowed_devices() { |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| web_contents()->GetBrowserContext()->GetDefaultStoragePartition()); |
| return partition->GetBluetoothAllowedDevicesMap()->GetOrCreateAllowedDevices( |
| GetOrigin()); |
| } |
| |
| void WebBluetoothServiceImpl::StoreAllowedScanOptions( |
| const blink::mojom::WebBluetoothRequestLEScanOptions& options) { |
| if (options.filters.has_value()) { |
| for (const auto& filter : options.filters.value()) |
| allowed_scan_filters_.push_back(filter.Clone()); |
| } else { |
| accept_all_advertisements_ = true; |
| } |
| } |
| |
| bool WebBluetoothServiceImpl::AreScanFiltersAllowed( |
| const absl::optional<ScanFilters>& filters) const { |
| if (accept_all_advertisements_) { |
| // Previously allowed accepting all advertisements and no filters. In this |
| // case since filtered advertisements are a subset of all advertisements, |
| // any filters should be allowed. |
| return true; |
| } |
| |
| if (!filters.has_value()) { |
| // |acceptAllAdvertisements| is set in the Bluetooth scanning options, but |
| // accepting all advertisements has not been allowed yet, in this case the |
| // permission prompt needs to be shown to the user. |
| return false; |
| } |
| |
| // If each |filter| in |filters| can be found in |allowed_scan_filters_|, then |
| // |filters| are allowed, otherwise |filters| are not allowed. |
| for (const auto& filter : filters.value()) { |
| bool allowed = false; |
| for (const auto& allowed_filter : allowed_scan_filters_) { |
| if (AreScanFiltersSame(*filter, *allowed_filter)) { |
| allowed = true; |
| break; |
| } |
| } |
| |
| if (!allowed) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void WebBluetoothServiceImpl::ClearState() { |
| // Releasing the adapter will drop references to callbacks that have not yet |
| // been executed. The receiver must be closed first so that this is allowed. |
| receiver_.reset(); |
| |
| characteristic_id_to_notify_session_.clear(); |
| pending_primary_services_requests_.clear(); |
| descriptor_id_to_characteristic_id_.clear(); |
| characteristic_id_to_service_id_.clear(); |
| service_id_to_device_address_.clear(); |
| connected_devices_ = |
| std::make_unique<FrameConnectedBluetoothDevices>(render_frame_host_); |
| device_chooser_controller_.reset(); |
| device_scanning_prompt_controller_.reset(); |
| ClearAdvertisementClients(); |
| BluetoothAdapterFactoryWrapper::Get().ReleaseAdapter(this); |
| } |
| |
| void WebBluetoothServiceImpl::ClearAdvertisementClients() { |
| scanning_clients_.clear(); |
| watch_advertisements_clients_.clear(); |
| allowed_scan_filters_.clear(); |
| accept_all_advertisements_ = false; |
| } |
| |
| bool WebBluetoothServiceImpl::IsAllowedToAccessAtLeastOneService( |
| const blink::WebBluetoothDeviceId& device_id) { |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return false; |
| return delegate->IsAllowedToAccessAtLeastOneService(render_frame_host_, |
| device_id); |
| } |
| return allowed_devices().IsAllowedToAccessAtLeastOneService(device_id); |
| } |
| |
| bool WebBluetoothServiceImpl::IsAllowedToAccessService( |
| const blink::WebBluetoothDeviceId& device_id, |
| const BluetoothUUID& service) { |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return false; |
| return delegate->IsAllowedToAccessService(render_frame_host_, device_id, |
| service); |
| } |
| return allowed_devices().IsAllowedToAccessService(device_id, service); |
| } |
| |
| bool WebBluetoothServiceImpl::IsAllowedToAccessManufacturerData( |
| const blink::WebBluetoothDeviceId& device_id, |
| uint16_t manufacturer_code) { |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (!delegate) |
| return false; |
| return delegate->IsAllowedToAccessManufacturerData( |
| render_frame_host_, device_id, manufacturer_code); |
| } |
| return allowed_devices().IsAllowedToAccessManufacturerData(device_id, |
| manufacturer_code); |
| } |
| |
| bool WebBluetoothServiceImpl::HasActiveDiscoverySession() { |
| return (ble_scan_discovery_session_ && |
| ble_scan_discovery_session_->IsActive()) || |
| (watch_advertisements_discovery_session_ && |
| watch_advertisements_discovery_session_->IsActive()); |
| } |
| |
| blink::WebBluetoothDeviceId WebBluetoothServiceImpl::GetCharacteristicDeviceID( |
| const std::string& characteristic_instance_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| blink::WebBluetoothDeviceId device_id; |
| |
| auto characteristic_iter = |
| characteristic_id_to_service_id_.find(characteristic_instance_id); |
| if (characteristic_iter == characteristic_id_to_service_id_.end()) |
| return device_id; |
| auto device_iter = |
| service_id_to_device_address_.find(characteristic_iter->second); |
| if (device_iter == service_id_to_device_address_.end()) |
| return device_id; |
| |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| BluetoothDelegate* delegate = |
| GetContentClient()->browser()->GetBluetoothDelegate(); |
| if (delegate) { |
| device_id = delegate->GetWebBluetoothDeviceId(render_frame_host_, |
| device_iter->second); |
| } |
| } else { |
| const blink::WebBluetoothDeviceId* device_id_ptr = |
| allowed_devices().GetDeviceId(device_iter->second); |
| if (device_id_ptr) |
| device_id = *device_id_ptr; |
| } |
| return device_id; |
| } |
| |
| BluetoothDevice* WebBluetoothServiceImpl::GetCachedDevice( |
| const blink::WebBluetoothDeviceId& device_id) { |
| DCHECK(device_id.IsValid()); |
| CacheQueryResult query_result = QueryCacheForDevice(device_id); |
| if (query_result.outcome != CacheQueryOutcome::SUCCESS) |
| return nullptr; |
| |
| return query_result.device; |
| } |
| |
| blink::WebBluetoothDeviceId WebBluetoothServiceImpl::GetDescriptorDeviceId( |
| const std::string& descriptor_instance_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto iter = descriptor_id_to_characteristic_id_.find(descriptor_instance_id); |
| if (iter == descriptor_id_to_characteristic_id_.end()) |
| return blink::WebBluetoothDeviceId(); |
| |
| return GetCharacteristicDeviceID(iter->second); |
| } |
| |
| void WebBluetoothServiceImpl::PairDevice( |
| const blink::WebBluetoothDeviceId& device_id, |
| BluetoothDevice::PairingDelegate* pairing_delegate, |
| BluetoothDevice::ConnectCallback callback) { |
| if (!device_id.IsValid()) { |
| std::move(callback).Run(BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN); |
| return; |
| } |
| |
| BluetoothDevice* device = GetCachedDevice(device_id); |
| if (!device) { |
| std::move(callback).Run(BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN); |
| return; |
| } |
| |
| DCHECK(!device->IsPaired()); |
| |
| device->Pair(pairing_delegate, std::move(callback)); |
| } |
| |
| void WebBluetoothServiceImpl::CancelPairing( |
| const blink::WebBluetoothDeviceId& device_id) { |
| DCHECK(device_id.IsValid()); |
| |
| BluetoothDevice* device = GetCachedDevice(device_id); |
| if (!device) |
| return; |
| |
| device->CancelPairing(); |
| } |
| |
| } // namespace content |