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 |