diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/browser/bluetooth | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/content/browser/bluetooth')
-rw-r--r-- | chromium/content/browser/bluetooth/web_bluetooth_service_impl.cc | 704 | ||||
-rw-r--r-- | chromium/content/browser/bluetooth/web_bluetooth_service_impl.h | 120 |
2 files changed, 530 insertions, 294 deletions
diff --git a/chromium/content/browser/bluetooth/web_bluetooth_service_impl.cc b/chromium/content/browser/bluetooth/web_bluetooth_service_impl.cc index 847c0750756..a8271551e12 100644 --- a/chromium/content/browser/bluetooth/web_bluetooth_service_impl.cc +++ b/chromium/content/browser/bluetooth/web_bluetooth_service_impl.cc @@ -48,6 +48,7 @@ #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h" using device::BluetoothAdapterFactoryWrapper; +using device::BluetoothGattCharacteristic; using device::BluetoothUUID; namespace content { @@ -171,6 +172,181 @@ bool IsValidRequestScanOptions( } // namespace +class WebBluetoothServiceImpl::AdvertisementClient { + public: + virtual void SendEvent(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)) { + // 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))); + } + virtual ~AdvertisementClient() = default; + + mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> client_; +}; + +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 = default; + + // AdvertisementClient implementation: + void SendEvent(blink::mojom::WebBluetoothAdvertisingEvent& event) override { + if (event.device->id == device_id_) + client_->AdvertisingEvent(event.Clone()); + } + + 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 = default; + + void SetPromptController( + BluetoothDeviceScanningPromptController* prompt_controller) { + prompt_controller_ = prompt_controller; + } + + // AdvertisingClient implementation: + void SendEvent(blink::mojom::WebBluetoothAdvertisingEvent& event) override { + if (options_->accept_all_advertisements) { + if (prompt_controller_) + AddFilteredDeviceToPrompt(event.device->id.str(), event.name); + + if (allow_send_event_) + client_->AdvertisingEvent(event.Clone()); + + 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 (!event.name.has_value() || + filter->name.value() != event.name.value()) { + continue; + } + } + + // Check if there is a name prefix match + if (filter->name_prefix.has_value()) { + if (!event.name.has_value() || + !base::StartsWith(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(), + [&event](const BluetoothUUID& filter_uuid) { + return base::Contains(event.uuids, filter_uuid); + }); + if (it == filter->services.value().end()) + continue; + } + + // TODO(crbug.com/707635): Support manufacturerData and serviceData + // filters. + + if (prompt_controller_) + AddFilteredDeviceToPrompt(event.device->id.str(), event.name); + + if (allow_send_event_) + client_->AdvertisingEvent(event.Clone()); + 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 base::Optional<std::string>& device_name) { + bool should_update_name = device_name.has_value(); + base::string16 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 base::Optional< std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>>& filters) { @@ -351,115 +527,6 @@ void WebBluetoothServiceImpl::OnBluetoothScanningPromptEvent( } } -WebBluetoothServiceImpl::ScanningClient::ScanningClient( - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client, - blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, - RequestScanningStartCallback callback, - BluetoothDeviceScanningPromptController* prompt_controller) - : client_(std::move(client)), - options_(std::move(options)), - callback_(std::move(callback)), - prompt_controller_(prompt_controller) { - DCHECK(options_->filters.has_value() || options_->accept_all_advertisements); - client_.set_disconnect_handler(base::BindOnce( - &ScanningClient::DisconnectionHandler, base::Unretained(this))); -} - -WebBluetoothServiceImpl::ScanningClient::~ScanningClient() {} - -bool WebBluetoothServiceImpl::ScanningClient::SendEvent( - blink::mojom::WebBluetoothAdvertisingEventPtr result) { - if (disconnected_) - return false; - - if (options_->accept_all_advertisements) { - if (prompt_controller_) - AddFilteredDeviceToPrompt(result->device->id.str(), result->name); - - if (allow_send_event_) - client_->AdvertisingEvent(std::move(result)); - - return true; - } - - 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 (!result->name.has_value()) - continue; - - if (filter->name.value() != result->name.value()) - continue; - } - - // Check if there is a name prefix match - if (filter->name_prefix.has_value()) { - if (!result->name.has_value()) - continue; - - if (!base::StartsWith(result->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()) { - bool found_uuid_match = false; - for (auto& filter_uuid : filter->services.value()) { - found_uuid_match = base::Contains(result->uuids, filter_uuid); - if (found_uuid_match) - break; - } - if (!found_uuid_match) - continue; - } - // TODO(crbug.com/707635): Support manufacturerData and serviceData filters. - - if (prompt_controller_) - AddFilteredDeviceToPrompt(result->device->id.str(), result->name); - - if (allow_send_event_) - client_->AdvertisingEvent(std::move(result)); - return true; - } - - // Event was filtered out. - return true; -} - -void WebBluetoothServiceImpl::ScanningClient::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 WebBluetoothServiceImpl::ScanningClient::DisconnectionHandler() { - disconnected_ = true; -} - -void WebBluetoothServiceImpl::ScanningClient::AddFilteredDeviceToPrompt( - const std::string& device_id, - const base::Optional<std::string>& device_name) { - bool should_update_name = device_name.has_value(); - base::string16 device_name_for_display = - base::UTF8ToUTF16(device_name.value_or("")); - prompt_controller_->AddFilteredDevice(device_id, should_update_name, - device_name_for_display); -} - void WebBluetoothServiceImpl::DidFinishNavigation( NavigationHandle* navigation_handle) { if (navigation_handle->HasCommitted() && @@ -472,13 +539,13 @@ void WebBluetoothServiceImpl::DidFinishNavigation( void WebBluetoothServiceImpl::OnVisibilityChanged(Visibility visibility) { if (visibility == content::Visibility::HIDDEN || visibility == content::Visibility::OCCLUDED) { - ClearDeviceAdvertisementClients(); + ClearAdvertisementClients(); } } void WebBluetoothServiceImpl::OnWebContentsLostFocus( RenderWidgetHost* render_widget_host) { - ClearDeviceAdvertisementClients(); + ClearAdvertisementClients(); } void WebBluetoothServiceImpl::AdapterPoweredChanged( @@ -530,73 +597,66 @@ void WebBluetoothServiceImpl::DeviceAdvertisementReceived( const device::BluetoothDevice::ManufacturerDataMap& manufacturer_data_map) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (!discovery_session_ || !discovery_session_->IsActive()) - return; - - auto client = scanning_clients_.begin(); - while (client != scanning_clients_.end()) { - 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; + if (!HasActiveDiscoverySession()) + return; - auto result = blink::mojom::WebBluetoothAdvertisingEvent::New(); - result->device = std::move(device); + // 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; - result->name = advertisement_name; + auto result = blink::mojom::WebBluetoothAdvertisingEvent::New(); + result->device = std::move(device); - // 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->name = advertisement_name; - result->rssi_is_set = rssi.has_value(); - result->rssi = rssi.value_or(/*invalid value=*/128); + // 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->tx_power_is_set = tx_power.has_value(); - result->tx_power = tx_power.value_or(/*invalid value=*/128); + result->rssi_is_set = rssi.has_value(); + result->rssi = rssi.value_or(/*invalid value=*/128); - std::vector<device::BluetoothUUID> uuids; - for (auto& uuid : advertised_uuids) - uuids.push_back(device::BluetoothUUID(uuid.canonical_value())); - result->uuids = std::move(uuids); + result->tx_power_is_set = tx_power.has_value(); + result->tx_power = tx_power.value_or(/*invalid value=*/128); - auto& manufacturer_data = result->manufacturer_data; - for (auto& it : manufacturer_data_map) - manufacturer_data.emplace(it.first, it.second); + std::vector<device::BluetoothUUID> uuids; + for (auto& uuid : advertised_uuids) + uuids.push_back(device::BluetoothUUID(uuid.canonical_value())); + result->uuids = std::move(uuids); - base::flat_map<std::string, std::vector<uint8_t>> services; - for (auto& it : service_data_map) - services[it.first.canonical_value()] = it.second; - result->service_data = std::move(services); + auto& manufacturer_data = result->manufacturer_data; + manufacturer_data.insert(manufacturer_data_map.begin(), + manufacturer_data_map.end()); - bool okay = (*client)->SendEvent(std::move(result)); - if (!okay) { - client = scanning_clients_.erase(client); - continue; - } + std::vector<std::pair<std::string, std::vector<uint8_t>>> services; + for (auto& it : service_data_map) + services.emplace_back(it.first.canonical_value(), it.second); + result->service_data = base::flat_map<std::string, std::vector<uint8_t>>( + services.begin(), services.end()); - ++client; - } + // 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); - // If we don't have any bound clients, clean things up. - if (scanning_clients_.empty()) { - discovery_session_->Stop(); - discovery_session_ = nullptr; - return; - } + for (const auto& watch_advertisements_client : watch_advertisements_clients_) + watch_advertisements_client->SendEvent(*result); + + MaybeStopDiscovery(); } void WebBluetoothServiceImpl::GattServicesDiscovered( @@ -1049,6 +1109,7 @@ void WebBluetoothServiceImpl::RemoteCharacteristicReadValue( 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); @@ -1084,13 +1145,30 @@ void WebBluetoothServiceImpl::RemoteCharacteristicWriteValue( // TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating // the callee interface. auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - query_result.characteristic->WriteRemoteCharacteristic( - value, - base::BindOnce( - &WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess, - weak_ptr_factory_.GetWeakPtr(), copyable_callback), + base::OnceClosure write_callback = base::BindOnce( + &WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess, + weak_ptr_factory_.GetWeakPtr(), copyable_callback); + device::BluetoothGattCharacteristic::ErrorCallback write_error_callback = base::BindOnce(&WebBluetoothServiceImpl::OnCharacteristicWriteValueFailed, - weak_ptr_factory_.GetWeakPtr(), copyable_callback)); + weak_ptr_factory_.GetWeakPtr(), copyable_callback); + using WebBluetoothWriteType = blink::mojom::WebBluetoothWriteType; + using WriteType = device::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( @@ -1263,15 +1341,30 @@ void WebBluetoothServiceImpl::RequestScanningStart( RequestScanningStartCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> client( - std::move(client_info)); + 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), + weak_ptr_factory_.GetWeakPtr(), std::move(client_info), std::move(options), std::move(callback))); return; } @@ -1280,52 +1373,96 @@ void WebBluetoothServiceImpl::RequestScanningStart( return; } - RequestScanningStartImpl(std::move(client), std::move(options), + RequestScanningStartImpl(std::move(client_info), std::move(options), std::move(callback), GetAdapter()); } -void WebBluetoothServiceImpl::RequestScanningStartImpl( - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client, - blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, - RequestScanningStartCallback callback, - scoped_refptr<device::BluetoothAdapter> adapter) { +void WebBluetoothServiceImpl::WatchAdvertisementsForDevice( + const blink::WebBluetoothDeviceId& device_id, + mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> + client_info, + WatchAdvertisementsForDeviceCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - // The renderer should never send invalid options. - if (!IsValidRequestScanOptions(options)) { + 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 (!adapter) { + 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; } - const url::Origin requesting_origin = - render_frame_host_->GetLastCommittedOrigin(); - const url::Origin embedding_origin = - web_contents()->GetMainFrame()->GetLastCommittedOrigin(); + WatchAdvertisementsForDeviceImpl(std::move(device_id), std::move(client_info), + std::move(callback), GetAdapter()); +} - bool blocked = GetContentClient()->browser()->IsBluetoothScanningBlocked( - web_contents()->GetBrowserContext(), requesting_origin, embedding_origin); +void WebBluetoothServiceImpl::RemoveDisconnectedClients() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (blocked) { - std::move(callback).Run(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED); + // 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<device::BluetoothAdapter> adapter) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (!adapter) { + std::move(callback).Run( + blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE); return; } - if (discovery_callback_) { + if (request_scanning_start_callback_) { std::move(callback).Run(blink::mojom::WebBluetoothResult::PROMPT_CANCELED); return; } - if (discovery_session_) { - if (AreScanFiltersAllowed(options->filters)) { - auto scanning_client = std::make_unique<ScanningClient>( - std::move(client), std::move(options), std::move(callback), nullptr); + 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); @@ -1338,41 +1475,43 @@ void WebBluetoothServiceImpl::RequestScanningStartImpl( device_scanning_prompt_controller_ = std::make_unique<BluetoothDeviceScanningPromptController>( this, render_frame_host_); - - scanning_clients_.push_back(std::make_unique<ScanningClient>( - std::move(client), std::move(options), std::move(callback), - device_scanning_prompt_controller_.get())); + scanning_client->SetPromptController( + device_scanning_prompt_controller_.get()); + scanning_clients_.push_back(std::move(scanning_client)); device_scanning_prompt_controller_->ShowPermissionPrompt(); return; } - discovery_callback_ = std::move(callback); + 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::OnStartDiscoverySession, - weak_ptr_factory_.GetWeakPtr(), std::move(client), - std::move(options)), - base::BindOnce(&WebBluetoothServiceImpl::OnDiscoverySessionError, - weak_ptr_factory_.GetWeakPtr())); + 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::OnStartDiscoverySession( - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client, +void WebBluetoothServiceImpl::OnStartDiscoverySessionForScanning( + mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> + client_info, blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, std::unique_ptr<device::BluetoothDiscoverySession> session) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - DCHECK(!discovery_session_); + DCHECK(!ble_scan_discovery_session_); - discovery_session_ = std::move(session); + ble_scan_discovery_session_ = std::move(session); - if (AreScanFiltersAllowed(options->filters)) { - auto scanning_client = std::make_unique<ScanningClient>( - std::move(client), std::move(options), std::move(discovery_callback_), - nullptr); + 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); @@ -1383,20 +1522,20 @@ void WebBluetoothServiceImpl::OnStartDiscoverySession( device_scanning_prompt_controller_ = std::make_unique<BluetoothDeviceScanningPromptController>( this, render_frame_host_); - - scanning_clients_.push_back(std::make_unique<ScanningClient>( - std::move(client), std::move(options), std::move(discovery_callback_), - device_scanning_prompt_controller_.get())); + scanning_client->SetPromptController( + device_scanning_prompt_controller_.get()); + scanning_clients_.push_back(std::move(scanning_client)); device_scanning_prompt_controller_->ShowPermissionPrompt(); } -void WebBluetoothServiceImpl::OnDiscoverySessionError() { +void WebBluetoothServiceImpl::OnDiscoverySessionErrorForScanning() { DCHECK_CURRENTLY_ON(BrowserThread::UI); device_scanning_prompt_controller_.reset(); - std::move(discovery_callback_) + std::move(request_scanning_start_callback_) .Run(blink::mojom::WebBluetoothResult::NO_BLUETOOTH_ADAPTER); + ClearAdvertisementClients(); } void WebBluetoothServiceImpl::RequestDeviceImpl( @@ -1410,11 +1549,11 @@ void WebBluetoothServiceImpl::RequestDeviceImpl( } // 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. + // 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_.reset(new BluetoothDeviceChooserController( @@ -1447,9 +1586,9 @@ void WebBluetoothServiceImpl::GetDevicesImpl( 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. + // 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 = @@ -1463,6 +1602,91 @@ void WebBluetoothServiceImpl::GetDevicesImpl( 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<device::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<device::BluetoothDiscoverySession> session) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(!watch_advertisements_discovery_session_); + watch_advertisements_discovery_session_ = std::move(session); + + for (auto& callback_and_client : + watch_advertisements_callbacks_and_clients_) { + if (callback_and_client.second->is_connected()) { + watch_advertisements_clients_.push_back( + std::move(callback_and_client.second)); + std::move(callback_and_client.first) + .Run(blink::mojom::WebBluetoothResult::SUCCESS); + continue; + } + + std::move(callback_and_client.first) + .Run(blink::mojom::WebBluetoothResult::WATCH_ADVERTISEMENTS_ABORTED); + } + + 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, @@ -1920,8 +2144,8 @@ bool WebBluetoothServiceImpl::AreScanFiltersAllowed( return false; } - // If each |filter| in |filters| can be found in |allowed_scan_filters_|, - // then |filters| are allowed, otherwise |filters| are not allowed. + // 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_) { @@ -1952,12 +2176,13 @@ void WebBluetoothServiceImpl::ClearState() { new FrameConnectedBluetoothDevices(render_frame_host_)); device_chooser_controller_.reset(); device_scanning_prompt_controller_.reset(); - ClearDeviceAdvertisementClients(); + ClearAdvertisementClients(); BluetoothAdapterFactoryWrapper::Get().ReleaseAdapter(this); } -void WebBluetoothServiceImpl::ClearDeviceAdvertisementClients() { +void WebBluetoothServiceImpl::ClearAdvertisementClients() { scanning_clients_.clear(); + watch_advertisements_clients_.clear(); allowed_scan_filters_.clear(); accept_all_advertisements_ = false; } @@ -1993,4 +2218,11 @@ bool WebBluetoothServiceImpl::IsAllowedToAccessService( } } +bool WebBluetoothServiceImpl::HasActiveDiscoverySession() { + return (ble_scan_discovery_session_ && + ble_scan_discovery_session_->IsActive()) || + (watch_advertisements_discovery_session_ && + watch_advertisements_discovery_session_->IsActive()); +} + } // namespace content diff --git a/chromium/content/browser/bluetooth/web_bluetooth_service_impl.h b/chromium/content/browser/bluetooth/web_bluetooth_service_impl.h index ee48a997ce9..219bf42a5a3 100644 --- a/chromium/content/browser/bluetooth/web_bluetooth_service_impl.h +++ b/chromium/content/browser/bluetooth/web_bluetooth_service_impl.h @@ -10,6 +10,7 @@ #include <unordered_map> #include <vector> +#include "base/callback.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/optional.h" @@ -113,50 +114,9 @@ class CONTENT_EXPORT WebBluetoothServiceImpl base::OnceCallback<void(device::BluetoothDevice*)>; using ScanFilters = std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>; - class ScanningClient { - public: - ScanningClient(mojo::AssociatedRemote< - blink::mojom::WebBluetoothAdvertisementClient> client, - blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, - RequestScanningStartCallback callback, - BluetoothDeviceScanningPromptController* prompt_controller); - ~ScanningClient(); - bool SendEvent(blink::mojom::WebBluetoothAdvertisingEventPtr 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; - } - - void RunRequestScanningStartCallback( - blink::mojom::WebBluetoothResult result); - - const blink::mojom::WebBluetoothRequestLEScanOptions& scan_options() { - return *options_; - } - - private: - void DisconnectionHandler(); - void AddFilteredDeviceToPrompt( - const std::string& device_id, - const base::Optional<std::string>& device_name); - - bool disconnected_ = false; - bool allow_send_event_ = false; - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client_; - blink::mojom::WebBluetoothRequestLEScanOptionsPtr options_; - RequestScanningStartCallback callback_; - BluetoothDeviceScanningPromptController* prompt_controller_; - }; + class AdvertisementClient; + class WatchAdvertisementsClient; + class ScanningClient; // WebContentsObserver: // These functions should always check that the affected RenderFrameHost @@ -225,6 +185,7 @@ class CONTENT_EXPORT WebBluetoothServiceImpl void RemoteCharacteristicWriteValue( const std::string& characteristic_instance_id, const std::vector<uint8_t>& value, + blink::mojom::WebBluetoothWriteType write_type, RemoteCharacteristicWriteValueCallback callback) override; void RemoteCharacteristicStartNotifications( const std::string& characteristic_instance_id, @@ -248,9 +209,14 @@ class CONTENT_EXPORT WebBluetoothServiceImpl RemoteDescriptorWriteValueCallback callback) override; void RequestScanningStart( mojo::PendingAssociatedRemote< - blink::mojom::WebBluetoothAdvertisementClient> client, + blink::mojom::WebBluetoothAdvertisementClient> client_info, blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, RequestScanningStartCallback callback) override; + void WatchAdvertisementsForDevice( + const blink::WebBluetoothDeviceId& device_id, + mojo::PendingAssociatedRemote< + blink::mojom::WebBluetoothAdvertisementClient> client_info, + WatchAdvertisementsForDeviceCallback callback) override; void RequestDeviceImpl( blink::mojom::WebBluetoothRequestDeviceOptionsPtr options, @@ -260,20 +226,38 @@ class CONTENT_EXPORT WebBluetoothServiceImpl void GetDevicesImpl(GetDevicesCallback callback, scoped_refptr<device::BluetoothAdapter> adapter); + // Callbacks for BLE scanning. void RequestScanningStartImpl( - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client, + mojo::PendingAssociatedRemote< + blink::mojom::WebBluetoothAdvertisementClient> client_info, blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, RequestScanningStartCallback callback, scoped_refptr<device::BluetoothAdapter> adapter); - - void OnStartDiscoverySession( - mojo::AssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> - client, + void OnStartDiscoverySessionForScanning( + mojo::PendingAssociatedRemote< + blink::mojom::WebBluetoothAdvertisementClient> client_info, blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, std::unique_ptr<device::BluetoothDiscoverySession> session); + void OnDiscoverySessionErrorForScanning(); - void OnDiscoverySessionError(); + // Callbacks for watch advertisements for device. + void WatchAdvertisementsForDeviceImpl( + const blink::WebBluetoothDeviceId& device_id, + mojo::PendingAssociatedRemote< + blink::mojom::WebBluetoothAdvertisementClient> client_info, + WatchAdvertisementsForDeviceCallback callback, + scoped_refptr<device::BluetoothAdapter> adapter); + void OnStartDiscoverySessionForWatchAdvertisements( + std::unique_ptr<device::BluetoothDiscoverySession> session); + void OnDiscoverySessionErrorForWatchAdvertisements(); + + // Remove WatchAdvertisementsClients and ScanningClients with disconnected + // WebBluetoothAdvertisementClients from their respective containers. + void RemoveDisconnectedClients(); + + // Stop active discovery sessions and destroy them if there aren't any active + // AdvertisementClients. + void MaybeStopDiscovery(); // Should only be run after the services have been discovered for // |device_address|. @@ -392,13 +376,17 @@ class CONTENT_EXPORT WebBluetoothServiceImpl void ClearState(); // Clears state associated with Bluetooth LE Scanning. - void ClearDeviceAdvertisementClients(); + void ClearAdvertisementClients(); bool IsAllowedToAccessAtLeastOneService( const blink::WebBluetoothDeviceId& device_id); bool IsAllowedToAccessService(const blink::WebBluetoothDeviceId& device_id, const device::BluetoothUUID& service); + // Returns true if at least |ble_scan_discovery_session_| or + // |watch_advertisements_discovery_session_| is active. + bool HasActiveDiscoverySession(); + // Used to open a BluetoothChooser and start a device discovery session. std::unique_ptr<BluetoothDeviceChooserController> device_chooser_controller_; @@ -429,14 +417,30 @@ class CONTENT_EXPORT WebBluetoothServiceImpl RenderFrameHost* render_frame_host_; // Keeps track of our BLE scanning session. - std::unique_ptr<device::BluetoothDiscoverySession> discovery_session_; - - // This queues up start callback so that we only have one - // BluetoothDiscoverySession start request at a time. - RequestScanningStartCallback discovery_callback_; + std::unique_ptr<device::BluetoothDiscoverySession> + ble_scan_discovery_session_; + + // Keeps track of our watch advertisements discovery session. + std::unique_ptr<device::BluetoothDiscoverySession> + watch_advertisements_discovery_session_; + + // This queues up a scanning start callback so that we only have one + // BluetoothDiscoverySession start request at a time for a BLE scan. + RequestScanningStartCallback request_scanning_start_callback_; + + // This queues up pending watch advertisements callbacks and clients so that + // we only have one BluetoothDiscoverySession start request at a time for + // watching device advertisements. + using WatchAdvertisementsCallbackAndClient = + std::pair<WatchAdvertisementsForDeviceCallback, + std::unique_ptr<WatchAdvertisementsClient>>; + std::vector<WatchAdvertisementsCallbackAndClient> + watch_advertisements_callbacks_and_clients_; // List of clients that we must broadcast scan changes to. std::vector<std::unique_ptr<ScanningClient>> scanning_clients_; + std::vector<std::unique_ptr<WatchAdvertisementsClient>> + watch_advertisements_clients_; // Allowed Bluetooth scanning filters. ScanFilters allowed_scan_filters_; |