| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // ID Not In Map Note: | |
| 6 // A service, characteristic, or descriptor ID not in the corresponding | |
| 7 // BluetoothDispatcherHost map [service_to_device_, characteristic_to_service_, | |
| 8 // descriptor_to_characteristic_] implies a hostile renderer because a renderer | |
| 9 // obtains the corresponding ID from this class and it will be added to the map | |
| 10 // at that time. | |
| 11 | |
| 12 #include "content/browser/bluetooth/bluetooth_dispatcher_host.h" | |
| 13 | |
| 14 #include <stddef.h> | |
| 15 | |
| 16 #include <utility> | |
| 17 | |
| 18 #include "base/bind.h" | |
| 19 #include "base/single_thread_task_runner.h" | |
| 20 #include "base/strings/utf_string_conversions.h" | |
| 21 #include "base/threading/thread_task_runner_handle.h" | |
| 22 #include "content/browser/bluetooth/bluetooth_blacklist.h" | |
| 23 #include "content/browser/bluetooth/bluetooth_metrics.h" | |
| 24 #include "content/browser/bluetooth/cache_query_result.h" | |
| 25 #include "content/browser/bluetooth/first_device_bluetooth_chooser.h" | |
| 26 #include "content/browser/frame_host/render_frame_host_impl.h" | |
| 27 #include "content/browser/web_contents/web_contents_impl.h" | |
| 28 #include "content/public/browser/content_browser_client.h" | |
| 29 #include "content/public/browser/web_contents.h" | |
| 30 #include "content/public/browser/web_contents_delegate.h" | |
| 31 #include "device/bluetooth/bluetooth_adapter.h" | |
| 32 #include "device/bluetooth/bluetooth_adapter_factory.h" | |
| 33 #include "device/bluetooth/bluetooth_device.h" | |
| 34 #include "device/bluetooth/bluetooth_discovery_session.h" | |
| 35 | |
| 36 using blink::WebBluetoothError; | |
| 37 using device::BluetoothAdapter; | |
| 38 using device::BluetoothAdapterFactory; | |
| 39 using device::BluetoothUUID; | |
| 40 | |
| 41 namespace content { | |
| 42 | |
| 43 namespace { | |
| 44 | |
| 45 // TODO(ortuno): Once we have a chooser for scanning, a way to control that | |
| 46 // chooser from tests, and the right callback for discovered services we should | |
| 47 // delete these constants. | |
| 48 // https://crbug.com/436280 and https://crbug.com/484504 | |
| 49 const int kDelayTime = 5; // 5 seconds for scanning and discovering | |
| 50 const int kTestingDelayTime = 0; // No need to wait during tests | |
| 51 const size_t kMaxLengthForDeviceName = | |
| 52 29; // max length of device name in filter. | |
| 53 | |
| 54 bool IsEmptyOrInvalidFilter(const content::BluetoothScanFilter& filter) { | |
| 55 return filter.name.empty() && filter.namePrefix.empty() && | |
| 56 filter.services.empty() && | |
| 57 filter.name.length() > kMaxLengthForDeviceName && | |
| 58 filter.namePrefix.length() > kMaxLengthForDeviceName; | |
| 59 } | |
| 60 | |
| 61 bool HasEmptyOrInvalidFilter( | |
| 62 const std::vector<content::BluetoothScanFilter>& filters) { | |
| 63 return filters.empty() | |
| 64 ? true | |
| 65 : filters.end() != std::find_if(filters.begin(), filters.end(), | |
| 66 IsEmptyOrInvalidFilter); | |
| 67 } | |
| 68 | |
| 69 // Defined at | |
| 70 // https://webbluetoothchrome.github.io/web-bluetooth/#dfn-matches-a-filter | |
| 71 bool MatchesFilter(const device::BluetoothDevice& device, | |
| 72 const content::BluetoothScanFilter& filter) { | |
| 73 DCHECK(!IsEmptyOrInvalidFilter(filter)); | |
| 74 | |
| 75 const std::string device_name = base::UTF16ToUTF8(device.GetName()); | |
| 76 | |
| 77 if (!filter.name.empty() && (device_name != filter.name)) { | |
| 78 return false; | |
| 79 } | |
| 80 | |
| 81 if (!filter.namePrefix.empty() && | |
| 82 (!base::StartsWith(device_name, filter.namePrefix, | |
| 83 base::CompareCase::SENSITIVE))) { | |
| 84 return false; | |
| 85 } | |
| 86 | |
| 87 if (!filter.services.empty()) { | |
| 88 const auto& device_uuid_list = device.GetUUIDs(); | |
| 89 const std::set<BluetoothUUID> device_uuids(device_uuid_list.begin(), | |
| 90 device_uuid_list.end()); | |
| 91 for (const auto& service : filter.services) { | |
| 92 if (!ContainsKey(device_uuids, service)) { | |
| 93 return false; | |
| 94 } | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 return true; | |
| 99 } | |
| 100 | |
| 101 bool MatchesFilters(const device::BluetoothDevice& device, | |
| 102 const std::vector<content::BluetoothScanFilter>& filters) { | |
| 103 DCHECK(!HasEmptyOrInvalidFilter(filters)); | |
| 104 for (const content::BluetoothScanFilter& filter : filters) { | |
| 105 if (MatchesFilter(device, filter)) { | |
| 106 return true; | |
| 107 } | |
| 108 } | |
| 109 return false; | |
| 110 } | |
| 111 | |
| 112 void StopDiscoverySession( | |
| 113 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) { | |
| 114 // Nothing goes wrong if the discovery session fails to stop, and we don't | |
| 115 // need to wait for it before letting the user's script proceed, so we ignore | |
| 116 // the results here. | |
| 117 discovery_session->Stop(base::Bind(&base::DoNothing), | |
| 118 base::Bind(&base::DoNothing)); | |
| 119 } | |
| 120 | |
| 121 UMARequestDeviceOutcome OutcomeFromChooserEvent(BluetoothChooser::Event event) { | |
| 122 switch (event) { | |
| 123 case BluetoothChooser::Event::DENIED_PERMISSION: | |
| 124 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION; | |
| 125 case BluetoothChooser::Event::CANCELLED: | |
| 126 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED; | |
| 127 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP: | |
| 128 return UMARequestDeviceOutcome::BLUETOOTH_OVERVIEW_HELP_LINK_PRESSED; | |
| 129 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP: | |
| 130 return UMARequestDeviceOutcome::ADAPTER_OFF_HELP_LINK_PRESSED; | |
| 131 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP: | |
| 132 return UMARequestDeviceOutcome::NEED_LOCATION_HELP_LINK_PRESSED; | |
| 133 case BluetoothChooser::Event::SELECTED: | |
| 134 // We can't know if we are going to send a success message yet because | |
| 135 // the device could have vanished. This event should be histogramed | |
| 136 // manually after checking if the device is still around. | |
| 137 NOTREACHED(); | |
| 138 return UMARequestDeviceOutcome::SUCCESS; | |
| 139 case BluetoothChooser::Event::RESCAN: | |
| 140 // Rescanning doesn't result in a IPC message for the request being sent | |
| 141 // so no need to histogram it. | |
| 142 NOTREACHED(); | |
| 143 return UMARequestDeviceOutcome::SUCCESS; | |
| 144 } | |
| 145 NOTREACHED(); | |
| 146 return UMARequestDeviceOutcome::SUCCESS; | |
| 147 } | |
| 148 | |
| 149 } // namespace | |
| 150 | |
| 151 BluetoothDispatcherHost::BluetoothDispatcherHost(int render_process_id) | |
| 152 : BrowserMessageFilter(BluetoothMsgStart), | |
| 153 render_process_id_(render_process_id), | |
| 154 current_delay_time_(kDelayTime), | |
| 155 discovery_session_timer_( | |
| 156 FROM_HERE, | |
| 157 // TODO(jyasskin): Add a way for tests to control the dialog | |
| 158 // directly, and change this to a reasonable discovery timeout. | |
| 159 base::TimeDelta::FromSecondsD(current_delay_time_), | |
| 160 base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, | |
| 161 // base::Timer guarantees it won't call back after its | |
| 162 // destructor starts. | |
| 163 base::Unretained(this)), | |
| 164 /*is_repeating=*/false), | |
| 165 weak_ptr_factory_(this) { | |
| 166 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 167 | |
| 168 // Bind all future weak pointers to the UI thread. | |
| 169 weak_ptr_on_ui_thread_ = weak_ptr_factory_.GetWeakPtr(); | |
| 170 weak_ptr_on_ui_thread_.get(); // Associates with UI thread. | |
| 171 } | |
| 172 | |
| 173 void BluetoothDispatcherHost::OnDestruct() const { | |
| 174 // See class comment: UI Thread Note. | |
| 175 BrowserThread::DeleteOnUIThread::Destruct(this); | |
| 176 } | |
| 177 | |
| 178 void BluetoothDispatcherHost::OverrideThreadForMessage( | |
| 179 const IPC::Message& message, | |
| 180 content::BrowserThread::ID* thread) { | |
| 181 // See class comment: UI Thread Note. | |
| 182 *thread = BrowserThread::UI; | |
| 183 } | |
| 184 | |
| 185 bool BluetoothDispatcherHost::OnMessageReceived(const IPC::Message& message) { | |
| 186 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 187 bool handled = true; | |
| 188 IPC_BEGIN_MESSAGE_MAP(BluetoothDispatcherHost, message) | |
| 189 IPC_MESSAGE_HANDLER(BluetoothHostMsg_RequestDevice, OnRequestDevice) | |
| 190 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 191 IPC_END_MESSAGE_MAP() | |
| 192 return handled; | |
| 193 } | |
| 194 | |
| 195 void BluetoothDispatcherHost::SetBluetoothAdapterForTesting( | |
| 196 scoped_refptr<device::BluetoothAdapter> mock_adapter) { | |
| 197 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 198 | |
| 199 if (mock_adapter.get()) { | |
| 200 current_delay_time_ = kTestingDelayTime; | |
| 201 // Reset the discovery session timer to use the new delay time. | |
| 202 discovery_session_timer_.Start( | |
| 203 FROM_HERE, base::TimeDelta::FromSecondsD(current_delay_time_), | |
| 204 base::Bind(&BluetoothDispatcherHost::StopDeviceDiscovery, | |
| 205 // base::Timer guarantees it won't call back after its | |
| 206 // destructor starts. | |
| 207 base::Unretained(this))); | |
| 208 } else { | |
| 209 // The following data structures are used to store pending operations. | |
| 210 // They should never contain elements at the end of a test. | |
| 211 DCHECK(request_device_sessions_.IsEmpty()); | |
| 212 | |
| 213 // The following data structures are cleaned up when a | |
| 214 // device/service/characteristic is removed. | |
| 215 // Since this can happen after the test is done and the cleanup function is | |
| 216 // called, we clean them here. | |
| 217 allowed_devices_map_ = BluetoothAllowedDevicesMap(); | |
| 218 } | |
| 219 | |
| 220 set_adapter(std::move(mock_adapter)); | |
| 221 } | |
| 222 | |
| 223 BluetoothDispatcherHost::~BluetoothDispatcherHost() { | |
| 224 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 225 // Clear adapter, releasing observer references. | |
| 226 set_adapter(scoped_refptr<device::BluetoothAdapter>()); | |
| 227 } | |
| 228 | |
| 229 // Stores information associated with an in-progress requestDevice call. This | |
| 230 // will include the state of the active chooser dialog in a future patch. | |
| 231 struct BluetoothDispatcherHost::RequestDeviceSession { | |
| 232 public: | |
| 233 RequestDeviceSession(int thread_id, | |
| 234 int request_id, | |
| 235 url::Origin origin, | |
| 236 const std::vector<BluetoothScanFilter>& filters, | |
| 237 const std::vector<BluetoothUUID>& optional_services) | |
| 238 : thread_id(thread_id), | |
| 239 request_id(request_id), | |
| 240 origin(origin), | |
| 241 filters(filters), | |
| 242 optional_services(optional_services) {} | |
| 243 | |
| 244 void AddFilteredDevice(const device::BluetoothDevice& device) { | |
| 245 if (chooser && MatchesFilters(device, filters)) { | |
| 246 chooser->AddDevice(device.GetAddress(), device.GetName()); | |
| 247 } | |
| 248 } | |
| 249 | |
| 250 std::unique_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter() const { | |
| 251 std::set<BluetoothUUID> services; | |
| 252 for (const BluetoothScanFilter& filter : filters) { | |
| 253 services.insert(filter.services.begin(), filter.services.end()); | |
| 254 } | |
| 255 std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter( | |
| 256 new device::BluetoothDiscoveryFilter( | |
| 257 device::BluetoothDiscoveryFilter::TRANSPORT_DUAL)); | |
| 258 for (const BluetoothUUID& service : services) { | |
| 259 discovery_filter->AddUUID(service); | |
| 260 } | |
| 261 return discovery_filter; | |
| 262 } | |
| 263 | |
| 264 const int thread_id; | |
| 265 const int request_id; | |
| 266 const url::Origin origin; | |
| 267 const std::vector<BluetoothScanFilter> filters; | |
| 268 const std::vector<BluetoothUUID> optional_services; | |
| 269 std::unique_ptr<BluetoothChooser> chooser; | |
| 270 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session; | |
| 271 }; | |
| 272 | |
| 273 void BluetoothDispatcherHost::set_adapter( | |
| 274 scoped_refptr<device::BluetoothAdapter> adapter) { | |
| 275 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 276 if (adapter_.get()) { | |
| 277 adapter_->RemoveObserver(this); | |
| 278 for (device::BluetoothAdapter::Observer* observer : adapter_observers_) { | |
| 279 adapter_->RemoveObserver(observer); | |
| 280 } | |
| 281 } | |
| 282 adapter_ = adapter; | |
| 283 if (adapter_.get()) { | |
| 284 adapter_->AddObserver(this); | |
| 285 for (device::BluetoothAdapter::Observer* observer : adapter_observers_) { | |
| 286 adapter_->AddObserver(observer); | |
| 287 } | |
| 288 } else { | |
| 289 // Notify that the adapter has been removed and observers should clean up | |
| 290 // their state. | |
| 291 for (device::BluetoothAdapter::Observer* observer : adapter_observers_) { | |
| 292 observer->AdapterPresentChanged(nullptr, false); | |
| 293 } | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 void BluetoothDispatcherHost::StartDeviceDiscovery( | |
| 298 RequestDeviceSession* session, | |
| 299 int chooser_id) { | |
| 300 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 301 if (session->discovery_session) { | |
| 302 // Already running; just increase the timeout. | |
| 303 discovery_session_timer_.Reset(); | |
| 304 } else { | |
| 305 session->chooser->ShowDiscoveryState( | |
| 306 BluetoothChooser::DiscoveryState::DISCOVERING); | |
| 307 adapter_->StartDiscoverySessionWithFilter( | |
| 308 session->ComputeScanFilter(), | |
| 309 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, | |
| 310 weak_ptr_on_ui_thread_, chooser_id), | |
| 311 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, | |
| 312 weak_ptr_on_ui_thread_, chooser_id)); | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 void BluetoothDispatcherHost::StopDeviceDiscovery() { | |
| 317 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 318 for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | |
| 319 &request_device_sessions_); | |
| 320 !iter.IsAtEnd(); iter.Advance()) { | |
| 321 RequestDeviceSession* session = iter.GetCurrentValue(); | |
| 322 if (session->discovery_session) { | |
| 323 StopDiscoverySession(std::move(session->discovery_session)); | |
| 324 } | |
| 325 if (session->chooser) { | |
| 326 session->chooser->ShowDiscoveryState( | |
| 327 BluetoothChooser::DiscoveryState::IDLE); | |
| 328 } | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 void BluetoothDispatcherHost::AdapterPoweredChanged( | |
| 333 device::BluetoothAdapter* adapter, | |
| 334 bool powered) { | |
| 335 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 336 const BluetoothChooser::AdapterPresence presence = | |
| 337 powered ? BluetoothChooser::AdapterPresence::POWERED_ON | |
| 338 : BluetoothChooser::AdapterPresence::POWERED_OFF; | |
| 339 for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | |
| 340 &request_device_sessions_); | |
| 341 !iter.IsAtEnd(); iter.Advance()) { | |
| 342 RequestDeviceSession* session = iter.GetCurrentValue(); | |
| 343 | |
| 344 // Stop ongoing discovery session if power is off. | |
| 345 if (!powered && session->discovery_session) { | |
| 346 StopDiscoverySession(std::move(session->discovery_session)); | |
| 347 } | |
| 348 | |
| 349 if (session->chooser) | |
| 350 session->chooser->SetAdapterPresence(presence); | |
| 351 } | |
| 352 | |
| 353 // Stop the timer so that we don't change the state of the chooser | |
| 354 // when timer expires. | |
| 355 if (!powered) { | |
| 356 discovery_session_timer_.Stop(); | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 void BluetoothDispatcherHost::DeviceAdded(device::BluetoothAdapter* adapter, | |
| 361 device::BluetoothDevice* device) { | |
| 362 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 363 VLOG(1) << "Adding device to all choosers: " << device->GetAddress(); | |
| 364 for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | |
| 365 &request_device_sessions_); | |
| 366 !iter.IsAtEnd(); iter.Advance()) { | |
| 367 RequestDeviceSession* session = iter.GetCurrentValue(); | |
| 368 session->AddFilteredDevice(*device); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 void BluetoothDispatcherHost::DeviceRemoved(device::BluetoothAdapter* adapter, | |
| 373 device::BluetoothDevice* device) { | |
| 374 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 375 VLOG(1) << "Marking device removed on all choosers: " << device->GetAddress(); | |
| 376 for (IDMap<RequestDeviceSession, IDMapOwnPointer>::iterator iter( | |
| 377 &request_device_sessions_); | |
| 378 !iter.IsAtEnd(); iter.Advance()) { | |
| 379 RequestDeviceSession* session = iter.GetCurrentValue(); | |
| 380 if (session->chooser) { | |
| 381 session->chooser->RemoveDevice(device->GetAddress()); | |
| 382 } | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 void BluetoothDispatcherHost::OnRequestDevice( | |
| 387 int thread_id, | |
| 388 int request_id, | |
| 389 int frame_routing_id, | |
| 390 const std::vector<BluetoothScanFilter>& filters, | |
| 391 const std::vector<BluetoothUUID>& optional_services) { | |
| 392 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 393 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE); | |
| 394 RecordRequestDeviceArguments(filters, optional_services); | |
| 395 | |
| 396 if (!adapter_.get()) { | |
| 397 if (BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { | |
| 398 BluetoothAdapterFactory::GetAdapter(base::Bind( | |
| 399 &BluetoothDispatcherHost::OnGetAdapter, weak_ptr_on_ui_thread_, | |
| 400 base::Bind(&BluetoothDispatcherHost::OnRequestDeviceImpl, | |
| 401 weak_ptr_on_ui_thread_, thread_id, request_id, | |
| 402 frame_routing_id, filters, optional_services))); | |
| 403 return; | |
| 404 } | |
| 405 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER); | |
| 406 Send(new BluetoothMsg_RequestDeviceError( | |
| 407 thread_id, request_id, WebBluetoothError::NO_BLUETOOTH_ADAPTER)); | |
| 408 return; | |
| 409 } | |
| 410 OnRequestDeviceImpl(thread_id, request_id, frame_routing_id, filters, | |
| 411 optional_services); | |
| 412 } | |
| 413 | |
| 414 void BluetoothDispatcherHost::OnGetAdapter( | |
| 415 base::Closure continuation, | |
| 416 scoped_refptr<device::BluetoothAdapter> adapter) { | |
| 417 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 418 set_adapter(adapter); | |
| 419 continuation.Run(); | |
| 420 } | |
| 421 | |
| 422 void BluetoothDispatcherHost::OnRequestDeviceImpl( | |
| 423 int thread_id, | |
| 424 int request_id, | |
| 425 int frame_routing_id, | |
| 426 const std::vector<BluetoothScanFilter>& filters, | |
| 427 const std::vector<BluetoothUUID>& optional_services) { | |
| 428 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 429 | |
| 430 VLOG(1) << "requestDevice called with the following filters: "; | |
| 431 for (const BluetoothScanFilter& filter : filters) { | |
| 432 VLOG(1) << "Name: " << filter.name; | |
| 433 VLOG(1) << "Name Prefix: " << filter.namePrefix; | |
| 434 VLOG(1) << "Services:"; | |
| 435 VLOG(1) << "\t["; | |
| 436 for (const BluetoothUUID& service : filter.services) | |
| 437 VLOG(1) << "\t\t" << service.value(); | |
| 438 VLOG(1) << "\t]"; | |
| 439 } | |
| 440 | |
| 441 VLOG(1) << "requestDevice called with the following optional services: "; | |
| 442 for (const BluetoothUUID& service : optional_services) | |
| 443 VLOG(1) << "\t" << service.value(); | |
| 444 | |
| 445 // Check blacklist to reject invalid filters and adjust optional_services. | |
| 446 if (BluetoothBlacklist::Get().IsExcluded(filters)) { | |
| 447 RecordRequestDeviceOutcome( | |
| 448 UMARequestDeviceOutcome::BLACKLISTED_SERVICE_IN_FILTER); | |
| 449 Send(new BluetoothMsg_RequestDeviceError( | |
| 450 thread_id, request_id, | |
| 451 WebBluetoothError::REQUEST_DEVICE_WITH_BLACKLISTED_UUID)); | |
| 452 return; | |
| 453 } | |
| 454 std::vector<BluetoothUUID> optional_services_blacklist_filtered( | |
| 455 optional_services); | |
| 456 BluetoothBlacklist::Get().RemoveExcludedUuids( | |
| 457 &optional_services_blacklist_filtered); | |
| 458 | |
| 459 RenderFrameHostImpl* render_frame_host = | |
| 460 RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id); | |
| 461 WebContents* web_contents = | |
| 462 WebContents::FromRenderFrameHost(render_frame_host); | |
| 463 | |
| 464 if (!render_frame_host || !web_contents) { | |
| 465 DLOG(WARNING) << "Got a requestDevice IPC without a matching " | |
| 466 << "RenderFrameHost or WebContents: " << render_process_id_ | |
| 467 << ", " << frame_routing_id; | |
| 468 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME); | |
| 469 Send(new BluetoothMsg_RequestDeviceError( | |
| 470 thread_id, request_id, | |
| 471 WebBluetoothError::REQUEST_DEVICE_WITHOUT_FRAME)); | |
| 472 return; | |
| 473 } | |
| 474 | |
| 475 const url::Origin requesting_origin = | |
| 476 render_frame_host->GetLastCommittedOrigin(); | |
| 477 const url::Origin embedding_origin = | |
| 478 web_contents->GetMainFrame()->GetLastCommittedOrigin(); | |
| 479 | |
| 480 // TODO(crbug.com/518042): Enforce correctly-delegated permissions instead of | |
| 481 // matching origins. When relaxing this, take care to handle non-sandboxed | |
| 482 // unique origins. | |
| 483 if (!embedding_origin.IsSameOriginWith(requesting_origin)) { | |
| 484 Send(new BluetoothMsg_RequestDeviceError( | |
| 485 thread_id, request_id, | |
| 486 WebBluetoothError::REQUEST_DEVICE_FROM_CROSS_ORIGIN_IFRAME)); | |
| 487 return; | |
| 488 } | |
| 489 // The above also excludes unique origins, which are not even same-origin with | |
| 490 // themselves. | |
| 491 DCHECK(!requesting_origin.unique()); | |
| 492 | |
| 493 DCHECK(adapter_.get()); | |
| 494 | |
| 495 if (!adapter_->IsPresent()) { | |
| 496 VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice."; | |
| 497 RecordRequestDeviceOutcome( | |
| 498 UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT); | |
| 499 Send(new BluetoothMsg_RequestDeviceError( | |
| 500 thread_id, request_id, WebBluetoothError::NO_BLUETOOTH_ADAPTER)); | |
| 501 return; | |
| 502 } | |
| 503 | |
| 504 // The renderer should never send empty filters. | |
| 505 if (HasEmptyOrInvalidFilter(filters)) { | |
| 506 bad_message::ReceivedBadMessage(this, | |
| 507 bad_message::BDH_EMPTY_OR_INVALID_FILTERS); | |
| 508 return; | |
| 509 } | |
| 510 | |
| 511 switch (GetContentClient()->browser()->AllowWebBluetooth( | |
| 512 web_contents->GetBrowserContext(), requesting_origin, embedding_origin)) { | |
| 513 case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_POLICY: { | |
| 514 RecordRequestDeviceOutcome( | |
| 515 UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_POLICY_DISABLED); | |
| 516 Send(new BluetoothMsg_RequestDeviceError( | |
| 517 thread_id, request_id, | |
| 518 WebBluetoothError::CHOOSER_NOT_SHOWN_API_LOCALLY_DISABLED)); | |
| 519 return; | |
| 520 } | |
| 521 case ContentBrowserClient::AllowWebBluetoothResult:: | |
| 522 BLOCK_GLOBALLY_DISABLED: { | |
| 523 // Log to the developer console. | |
| 524 web_contents->GetMainFrame()->AddMessageToConsole( | |
| 525 content::CONSOLE_MESSAGE_LEVEL_LOG, | |
| 526 "Bluetooth permission has been blocked."); | |
| 527 // Block requests. | |
| 528 RecordRequestDeviceOutcome( | |
| 529 UMARequestDeviceOutcome::BLUETOOTH_GLOBALLY_DISABLED); | |
| 530 Send(new BluetoothMsg_RequestDeviceError( | |
| 531 thread_id, request_id, | |
| 532 WebBluetoothError::CHOOSER_NOT_SHOWN_API_GLOBALLY_DISABLED)); | |
| 533 return; | |
| 534 } | |
| 535 case ContentBrowserClient::AllowWebBluetoothResult::ALLOW: | |
| 536 break; | |
| 537 } | |
| 538 | |
| 539 // Create storage for the information that backs the chooser, and show the | |
| 540 // chooser. | |
| 541 RequestDeviceSession* const session = | |
| 542 new RequestDeviceSession(thread_id, request_id, requesting_origin, | |
| 543 filters, optional_services_blacklist_filtered); | |
| 544 int chooser_id = request_device_sessions_.Add(session); | |
| 545 | |
| 546 BluetoothChooser::EventHandler chooser_event_handler = | |
| 547 base::Bind(&BluetoothDispatcherHost::OnBluetoothChooserEvent, | |
| 548 weak_ptr_on_ui_thread_, chooser_id); | |
| 549 if (WebContentsDelegate* delegate = web_contents->GetDelegate()) { | |
| 550 session->chooser = | |
| 551 delegate->RunBluetoothChooser(render_frame_host, chooser_event_handler); | |
| 552 } | |
| 553 if (!session->chooser) { | |
| 554 LOG(WARNING) | |
| 555 << "No Bluetooth chooser implementation; falling back to first device."; | |
| 556 session->chooser.reset( | |
| 557 new FirstDeviceBluetoothChooser(chooser_event_handler)); | |
| 558 } | |
| 559 | |
| 560 if (!session->chooser->CanAskForScanningPermission()) { | |
| 561 VLOG(1) << "Closing immediately because Chooser cannot obtain permission."; | |
| 562 OnBluetoothChooserEvent(chooser_id, | |
| 563 BluetoothChooser::Event::DENIED_PERMISSION, ""); | |
| 564 return; | |
| 565 } | |
| 566 | |
| 567 // Populate the initial list of devices. | |
| 568 VLOG(1) << "Populating " << adapter_->GetDevices().size() | |
| 569 << " devices in chooser " << chooser_id; | |
| 570 for (const device::BluetoothDevice* device : adapter_->GetDevices()) { | |
| 571 VLOG(1) << "\t" << device->GetAddress(); | |
| 572 session->AddFilteredDevice(*device); | |
| 573 } | |
| 574 | |
| 575 if (!session->chooser) { | |
| 576 // If the dialog's closing, no need to do any of the rest of this. | |
| 577 return; | |
| 578 } | |
| 579 | |
| 580 if (!adapter_->IsPowered()) { | |
| 581 session->chooser->SetAdapterPresence( | |
| 582 BluetoothChooser::AdapterPresence::POWERED_OFF); | |
| 583 return; | |
| 584 } | |
| 585 | |
| 586 StartDeviceDiscovery(session, chooser_id); | |
| 587 } | |
| 588 | |
| 589 void BluetoothDispatcherHost::OnDiscoverySessionStarted( | |
| 590 int chooser_id, | |
| 591 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) { | |
| 592 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 593 VLOG(1) << "Started discovery session for " << chooser_id; | |
| 594 if (RequestDeviceSession* session = | |
| 595 request_device_sessions_.Lookup(chooser_id)) { | |
| 596 session->discovery_session = std::move(discovery_session); | |
| 597 | |
| 598 // Arrange to stop discovery later. | |
| 599 discovery_session_timer_.Reset(); | |
| 600 } else { | |
| 601 VLOG(1) << "Chooser " << chooser_id | |
| 602 << " was closed before the session finished starting. Stopping."; | |
| 603 StopDiscoverySession(std::move(discovery_session)); | |
| 604 } | |
| 605 } | |
| 606 | |
| 607 void BluetoothDispatcherHost::OnDiscoverySessionStartedError(int chooser_id) { | |
| 608 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 609 VLOG(1) << "Failed to start discovery session for " << chooser_id; | |
| 610 if (RequestDeviceSession* session = | |
| 611 request_device_sessions_.Lookup(chooser_id)) { | |
| 612 if (session->chooser && !session->discovery_session) { | |
| 613 session->chooser->ShowDiscoveryState( | |
| 614 BluetoothChooser::DiscoveryState::FAILED_TO_START); | |
| 615 } | |
| 616 } | |
| 617 // Ignore discovery session start errors when the dialog was already closed by | |
| 618 // the time they happen. | |
| 619 } | |
| 620 | |
| 621 void BluetoothDispatcherHost::OnBluetoothChooserEvent( | |
| 622 int chooser_id, | |
| 623 BluetoothChooser::Event event, | |
| 624 const std::string& device_id) { | |
| 625 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 626 RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); | |
| 627 DCHECK(session) << "Shouldn't receive an event (" << static_cast<int>(event) | |
| 628 << ") from a closed chooser."; | |
| 629 CHECK(session->chooser) << "Shouldn't receive an event (" | |
| 630 << static_cast<int>(event) | |
| 631 << ") from a closed chooser."; | |
| 632 switch (event) { | |
| 633 case BluetoothChooser::Event::RESCAN: | |
| 634 StartDeviceDiscovery(session, chooser_id); | |
| 635 // No need to close the chooser so we return. | |
| 636 return; | |
| 637 case BluetoothChooser::Event::DENIED_PERMISSION: | |
| 638 case BluetoothChooser::Event::CANCELLED: | |
| 639 case BluetoothChooser::Event::SELECTED: | |
| 640 break; | |
| 641 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP: | |
| 642 VLOG(1) << "Overview Help link pressed."; | |
| 643 break; | |
| 644 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP: | |
| 645 VLOG(1) << "Adapter Off Help link pressed."; | |
| 646 break; | |
| 647 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP: | |
| 648 VLOG(1) << "Need Location Help link pressed."; | |
| 649 break; | |
| 650 } | |
| 651 | |
| 652 // Synchronously ensure nothing else calls into the chooser after it has | |
| 653 // asked to be closed. | |
| 654 session->chooser.reset(); | |
| 655 | |
| 656 // Yield to the event loop to make sure we don't destroy the session | |
| 657 // within a BluetoothDispatcherHost stack frame. | |
| 658 if (!base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 659 FROM_HERE, | |
| 660 base::Bind(&BluetoothDispatcherHost::FinishClosingChooser, | |
| 661 weak_ptr_on_ui_thread_, chooser_id, event, device_id))) { | |
| 662 LOG(WARNING) << "No TaskRunner; not closing requestDevice dialog."; | |
| 663 } | |
| 664 } | |
| 665 | |
| 666 void BluetoothDispatcherHost::FinishClosingChooser( | |
| 667 int chooser_id, | |
| 668 BluetoothChooser::Event event, | |
| 669 const std::string& device_id) { | |
| 670 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 671 RequestDeviceSession* session = request_device_sessions_.Lookup(chooser_id); | |
| 672 DCHECK(session) << "Session removed unexpectedly."; | |
| 673 | |
| 674 if ((event != BluetoothChooser::Event::DENIED_PERMISSION) && | |
| 675 (event != BluetoothChooser::Event::SELECTED)) { | |
| 676 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event)); | |
| 677 Send(new BluetoothMsg_RequestDeviceError( | |
| 678 session->thread_id, session->request_id, | |
| 679 WebBluetoothError::CHOOSER_CANCELLED)); | |
| 680 request_device_sessions_.Remove(chooser_id); | |
| 681 return; | |
| 682 } | |
| 683 if (event == BluetoothChooser::Event::DENIED_PERMISSION) { | |
| 684 RecordRequestDeviceOutcome( | |
| 685 UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION); | |
| 686 VLOG(1) << "Bluetooth chooser denied permission"; | |
| 687 Send(new BluetoothMsg_RequestDeviceError( | |
| 688 session->thread_id, session->request_id, | |
| 689 WebBluetoothError::CHOOSER_NOT_SHOWN_USER_DENIED_PERMISSION_TO_SCAN)); | |
| 690 request_device_sessions_.Remove(chooser_id); | |
| 691 return; | |
| 692 } | |
| 693 DCHECK_EQ(static_cast<int>(event), | |
| 694 static_cast<int>(BluetoothChooser::Event::SELECTED)); | |
| 695 | |
| 696 // |device_id| is the Device Address that RequestDeviceSession passed to | |
| 697 // chooser->AddDevice(). | |
| 698 const device::BluetoothDevice* const device = adapter_->GetDevice(device_id); | |
| 699 if (device == nullptr) { | |
| 700 VLOG(1) << "Device " << device_id << " no longer in adapter"; | |
| 701 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::CHOSEN_DEVICE_VANISHED); | |
| 702 Send(new BluetoothMsg_RequestDeviceError( | |
| 703 session->thread_id, session->request_id, | |
| 704 WebBluetoothError::CHOSEN_DEVICE_VANISHED)); | |
| 705 request_device_sessions_.Remove(chooser_id); | |
| 706 return; | |
| 707 } | |
| 708 | |
| 709 const std::string& device_id_for_origin = allowed_devices_map_.AddDevice( | |
| 710 session->origin, device->GetAddress(), session->filters, | |
| 711 session->optional_services); | |
| 712 | |
| 713 VLOG(1) << "Device: " << device->GetName(); | |
| 714 VLOG(1) << "UUIDs: "; | |
| 715 | |
| 716 device::BluetoothDevice::UUIDList filtered_uuids; | |
| 717 for (BluetoothUUID uuid : device->GetUUIDs()) { | |
| 718 if (allowed_devices_map_.IsOriginAllowedToAccessService( | |
| 719 session->origin, device_id_for_origin, uuid.canonical_value())) { | |
| 720 VLOG(1) << "\t Allowed: " << uuid.canonical_value(); | |
| 721 filtered_uuids.push_back(uuid); | |
| 722 } else { | |
| 723 VLOG(1) << "\t Not Allowed: " << uuid.canonical_value(); | |
| 724 } | |
| 725 } | |
| 726 | |
| 727 content::BluetoothDevice device_ipc( | |
| 728 device_id_for_origin, // id | |
| 729 device->GetName(), // name | |
| 730 content::BluetoothDevice::UUIDsFromBluetoothUUIDs( | |
| 731 filtered_uuids)); // uuids | |
| 732 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); | |
| 733 Send(new BluetoothMsg_RequestDeviceSuccess(session->thread_id, | |
| 734 session->request_id, device_ipc)); | |
| 735 request_device_sessions_.Remove(chooser_id); | |
| 736 } | |
| 737 | |
| 738 CacheQueryResult BluetoothDispatcherHost::QueryCacheForDevice( | |
| 739 const url::Origin& origin, | |
| 740 const std::string& device_id) { | |
| 741 const std::string& device_address = | |
| 742 allowed_devices_map_.GetDeviceAddress(origin, device_id); | |
| 743 if (device_address.empty()) { | |
| 744 bad_message::ReceivedBadMessage( | |
| 745 this, bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN); | |
| 746 return CacheQueryResult(CacheQueryOutcome::BAD_RENDERER); | |
| 747 } | |
| 748 | |
| 749 CacheQueryResult result; | |
| 750 result.device = adapter_->GetDevice(device_address); | |
| 751 | |
| 752 // When a device can't be found in the BluetoothAdapter, that generally | |
| 753 // indicates that it's gone out of range. We reject with a NetworkError in | |
| 754 // that case. | |
| 755 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-con
nectgatt | |
| 756 if (result.device == nullptr) { | |
| 757 result.outcome = CacheQueryOutcome::NO_DEVICE; | |
| 758 } | |
| 759 return result; | |
| 760 } | |
| 761 | |
| 762 void BluetoothDispatcherHost::AddAdapterObserver( | |
| 763 device::BluetoothAdapter::Observer* observer) { | |
| 764 adapter_observers_.insert(observer); | |
| 765 if (adapter_) { | |
| 766 adapter_->AddObserver(observer); | |
| 767 } | |
| 768 } | |
| 769 | |
| 770 void BluetoothDispatcherHost::RemoveAdapterObserver( | |
| 771 device::BluetoothAdapter::Observer* observer) { | |
| 772 size_t removed = adapter_observers_.erase(observer); | |
| 773 DCHECK(removed); | |
| 774 if (adapter_) { | |
| 775 adapter_->RemoveObserver(observer); | |
| 776 } | |
| 777 } | |
| 778 | |
| 779 } // namespace content | |
| OLD | NEW |