Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(195)

Side by Side Diff: content/browser/bluetooth/bluetooth_device_chooser_controller.cc

Issue 1922923002: bluetooth: Move requestDevice to mojo (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@bluetooth-separate-tests-request-device
Patch Set: Rebase Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 #include "content/browser/bluetooth/bluetooth_device_chooser_controller.h"
6
7 #include <set>
8 #include <string>
9 #include <unordered_set>
10
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/browser/bluetooth/bluetooth_blacklist.h"
16 #include "content/browser/bluetooth/bluetooth_metrics.h"
17 #include "content/browser/bluetooth/first_device_bluetooth_chooser.h"
18 #include "content/browser/bluetooth/web_bluetooth_service_impl.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/content_browser_client.h"
21 #include "content/public/browser/render_frame_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_delegate.h"
24 #include "device/bluetooth/bluetooth_adapter.h"
25 #include "device/bluetooth/bluetooth_discovery_session.h"
26
27 namespace content {
28
29 namespace {
30 constexpr size_t kMaxLengthForDeviceName =
31 29; // max length of device name in filter.
32
33 void LogRequestDeviceOptions(
34 const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
35 VLOG(1) << "requestDevice called with the following filters: ";
36 int i = 0;
37 for (const auto& filter : options->filters) {
38 VLOG(1) << "Filter #" << ++i;
39 if (!filter->name.is_null())
40 VLOG(1) << "Name: " << filter->name;
41
42 if (!filter->name_prefix.is_null())
43 VLOG(1) << "Name Prefix: " << filter->name_prefix;
44
45 if (!filter->services.is_null()) {
46 VLOG(1) << "Services: ";
47 VLOG(1) << "\t[";
48 for (const auto& service : filter->services)
49 VLOG(1) << "\t\t" << service;
50 VLOG(1) << "\t]";
51 }
52 }
53 }
54
55 bool IsValidUUID(const mojo::String& uuid) {
56 device::BluetoothUUID parsed_uuid(uuid);
57 return parsed_uuid.IsValid() &&
58 parsed_uuid.format() == device::BluetoothUUID::kFormat128Bit;
59 }
60
61 bool HasInvalidOptionalServices(
62 const mojo::Array<mojo::String>& optional_services) {
63 return optional_services.end() != std::find_if_not(optional_services.begin(),
64 optional_services.end(),
65 IsValidUUID);
66 }
67
68 bool IsEmptyOrInvalidFilter(
69 const blink::mojom::WebBluetoothScanFilterPtr& filter) {
70 // At least one member needs to be present.
71 if (filter->name.is_null() && filter->name_prefix.is_null() &&
72 filter->services.is_null())
73 return true;
74
75 // The renderer will never send a name or a name_prefix longer than
76 // kMaxLengthForDeviceName.
77 if (!filter->name.is_null() && filter->name.size() > kMaxLengthForDeviceName)
78 return true;
79 if (!filter->name_prefix.is_null() &&
80 filter->name_prefix.size() > kMaxLengthForDeviceName)
81 return true;
82
83 if (!filter->services.is_null()) {
84 const auto& services = filter->services;
85 return services.end() !=
86 std::find_if_not(services.begin(), services.end(), IsValidUUID);
87 }
88
89 return false;
90 }
91
92 bool HasEmptyOrInvalidFilter(
93 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
94 return filters.empty()
95 ? true
96 : filters.end() != std::find_if(filters.begin(), filters.end(),
97 IsEmptyOrInvalidFilter);
98 }
99
100 bool MatchesFilter(const device::BluetoothDevice& device,
101 const blink::mojom::WebBluetoothScanFilterPtr& filter) {
102 DCHECK(!IsEmptyOrInvalidFilter(filter));
103
104 const std::string device_name = base::UTF16ToUTF8(device.GetName());
105
106 if (!filter->name.is_null() && (device_name != filter->name)) {
107 return false;
108 }
109
110 if (!filter->name_prefix.is_null() &&
111 (!base::StartsWith(device_name, filter->name_prefix.get(),
112 base::CompareCase::SENSITIVE))) {
113 return false;
114 }
115
116 if (!filter->services.is_null()) {
117 const auto& device_uuid_list = device.GetUUIDs();
118 const std::set<device::BluetoothUUID> device_uuids(device_uuid_list.begin(),
119 device_uuid_list.end());
120 for (const auto& service : filter->services) {
121 if (!ContainsKey(device_uuids, device::BluetoothUUID(service))) {
122 return false;
123 }
124 }
125 }
126
127 return true;
128 }
129
130 bool MatchesFilters(
131 const device::BluetoothDevice& device,
132 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
133 DCHECK(!HasEmptyOrInvalidFilter(filters));
134 for (const auto& filter : filters) {
135 if (MatchesFilter(device, filter)) {
136 return true;
137 }
138 }
139 return false;
140 }
141
142 std::unique_ptr<device::BluetoothDiscoveryFilter> ComputeScanFilter(
143 const mojo::Array<blink::mojom::WebBluetoothScanFilterPtr>& filters) {
144 std::unordered_set<std::string> services;
145 for (const auto& filter : filters) {
146 for (const std::string& service : filter->services) {
147 services.insert(service);
148 }
149 }
150 auto discovery_filter = base::MakeUnique<device::BluetoothDiscoveryFilter>(
151 device::BluetoothDiscoveryFilter::TRANSPORT_DUAL);
152 for (const std::string& service : services) {
153 discovery_filter->AddUUID(device::BluetoothUUID(service));
154 }
155 return discovery_filter;
156 }
157
158 void StopDiscoverySession(
159 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
160 // Nothing goes wrong if the discovery session fails to stop, and we don't
161 // need to wait for it before letting the user's script proceed, so we ignore
162 // the results here.
163 discovery_session->Stop(base::Bind(&base::DoNothing),
164 base::Bind(&base::DoNothing));
165 }
166
167 UMARequestDeviceOutcome OutcomeFromChooserEvent(BluetoothChooser::Event event) {
168 switch (event) {
169 case BluetoothChooser::Event::DENIED_PERMISSION:
170 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_DENIED_PERMISSION;
171 case BluetoothChooser::Event::CANCELLED:
172 return UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_CANCELLED;
173 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP:
174 return UMARequestDeviceOutcome::BLUETOOTH_OVERVIEW_HELP_LINK_PRESSED;
175 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP:
176 return UMARequestDeviceOutcome::ADAPTER_OFF_HELP_LINK_PRESSED;
177 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP:
178 return UMARequestDeviceOutcome::NEED_LOCATION_HELP_LINK_PRESSED;
179 case BluetoothChooser::Event::SELECTED:
180 // We can't know if we are going to send a success message yet because
181 // the device could have vanished. This event should be histogramed
182 // manually after checking if the device is still around.
183 NOTREACHED();
184 return UMARequestDeviceOutcome::SUCCESS;
185 case BluetoothChooser::Event::RESCAN:
186 // Rescanning doesn't result in a IPC message for the request being sent
187 // so no need to histogram it.
188 NOTREACHED();
189 return UMARequestDeviceOutcome::SUCCESS;
190 }
191 NOTREACHED();
192 return UMARequestDeviceOutcome::SUCCESS;
193 }
194
195 } // namespace
196
197 BluetoothDeviceChooserController::BluetoothDeviceChooserController(
198 WebBluetoothServiceImpl* web_bluetooth_service,
199 RenderFrameHost* render_frame_host,
200 device::BluetoothAdapter* adapter,
201 base::TimeDelta scan_duration)
202 : adapter_(adapter),
203 web_bluetooth_service_(web_bluetooth_service),
204 render_frame_host_(render_frame_host),
205 web_contents_(WebContents::FromRenderFrameHost(render_frame_host_)),
206 discovery_session_timer_(
207 FROM_HERE,
208 // TODO(jyasskin): Add a way for tests to control the dialog
209 // directly, and change this to a reasonable discovery timeout.
210 scan_duration,
211 base::Bind(&BluetoothDeviceChooserController::StopDeviceDiscovery,
212 // base::Timer guarantees it won't call back after its
213 // destructor starts.
214 base::Unretained(this)),
215 /*is_repeating=*/false),
216 weak_ptr_factory_(this) {
217 CHECK(adapter_);
218 }
219
220 BluetoothDeviceChooserController::~BluetoothDeviceChooserController() {}
221
222 void BluetoothDeviceChooserController::GetDevice(
223 blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
224 const SuccessCallback& success_callback,
225 const ErrorCallback& error_callback) {
226 DCHECK_CURRENTLY_ON(BrowserThread::UI);
227
228 // GetDevice should only be called once.
229 DCHECK(success_callback_.is_null());
230 DCHECK(error_callback_.is_null());
231
232 success_callback_ = success_callback;
233 error_callback_ = error_callback;
234
235 // The renderer should never send empty filters.
236 if (HasEmptyOrInvalidFilter(options->filters) ||
237 HasInvalidOptionalServices(options->optional_services)) {
238 web_bluetooth_service_->CrashRendererAndClosePipe(
239 bad_message::BDH_EMPTY_OR_INVALID_FILTERS);
240 return;
241 }
242 options_ = std::move(options);
243 LogRequestDeviceOptions(options_);
244
245 // Check blacklist to reject invalid filters and adjust optional_services.
246 if (BluetoothBlacklist::Get().IsExcluded(options_->filters)) {
247 RecordRequestDeviceOutcome(
248 UMARequestDeviceOutcome::BLACKLISTED_SERVICE_IN_FILTER);
249 PostErrorCallback(
250 blink::mojom::WebBluetoothError::REQUEST_DEVICE_WITH_BLACKLISTED_UUID);
251 return;
252 }
253 BluetoothBlacklist::Get().RemoveExcludedUUIDs(options_.get());
254
255 const url::Origin requesting_origin =
256 render_frame_host_->GetLastCommittedOrigin();
257 const url::Origin embedding_origin =
258 web_contents_->GetMainFrame()->GetLastCommittedOrigin();
259
260 // TODO(crbug.com/518042): Enforce correctly-delegated permissions instead of
261 // matching origins. When relaxing this, take care to handle non-sandboxed
262 // unique origins.
263 if (!embedding_origin.IsSameOriginWith(requesting_origin)) {
264 PostErrorCallback(blink::mojom::WebBluetoothError::
265 REQUEST_DEVICE_FROM_CROSS_ORIGIN_IFRAME);
266 return;
267 }
268 // The above also excludes unique origins, which are not even same-origin with
269 // themselves.
270 DCHECK(!requesting_origin.unique());
271
272 if (!adapter_->IsPresent()) {
273 VLOG(1) << "Bluetooth Adapter not present. Can't serve requestDevice.";
274 RecordRequestDeviceOutcome(
275 UMARequestDeviceOutcome::BLUETOOTH_ADAPTER_NOT_PRESENT);
276 PostErrorCallback(blink::mojom::WebBluetoothError::NO_BLUETOOTH_ADAPTER);
277 return;
278 }
279
280 switch (GetContentClient()->browser()->AllowWebBluetooth(
281 web_contents_->GetBrowserContext(), requesting_origin,
282 embedding_origin)) {
283 case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_POLICY: {
284 RecordRequestDeviceOutcome(
285 UMARequestDeviceOutcome::BLUETOOTH_CHOOSER_POLICY_DISABLED);
286 PostErrorCallback(blink::mojom::WebBluetoothError::
287 CHOOSER_NOT_SHOWN_API_LOCALLY_DISABLED);
288 return;
289 }
290 case ContentBrowserClient::AllowWebBluetoothResult::
291 BLOCK_GLOBALLY_DISABLED: {
292 // Log to the developer console.
293 web_contents_->GetMainFrame()->AddMessageToConsole(
294 content::CONSOLE_MESSAGE_LEVEL_LOG,
295 "Bluetooth permission has been blocked.");
296 // Block requests.
297 RecordRequestDeviceOutcome(
298 UMARequestDeviceOutcome::BLUETOOTH_GLOBALLY_DISABLED);
299 PostErrorCallback(blink::mojom::WebBluetoothError::
300 CHOOSER_NOT_SHOWN_API_GLOBALLY_DISABLED);
301 return;
302 }
303 case ContentBrowserClient::AllowWebBluetoothResult::ALLOW:
304 break;
305 }
306
307 BluetoothChooser::EventHandler chooser_event_handler =
308 base::Bind(&BluetoothDeviceChooserController::OnBluetoothChooserEvent,
309 base::Unretained(this));
310
311 if (WebContentsDelegate* delegate = web_contents_->GetDelegate()) {
312 chooser_ = delegate->RunBluetoothChooser(render_frame_host_,
313 chooser_event_handler);
314 }
315
316 if (!chooser_.get()) {
317 LOG(WARNING)
318 << "No Bluetooth chooser implementation; falling back to first device.";
319 chooser_.reset(new FirstDeviceBluetoothChooser(chooser_event_handler));
320 }
321
322 if (!chooser_->CanAskForScanningPermission()) {
323 VLOG(1) << "Closing immediately because Chooser cannot obtain permission.";
324 OnBluetoothChooserEvent(BluetoothChooser::Event::DENIED_PERMISSION,
325 "" /* device_address */);
326 return;
327 }
328
329 // Populate the initial list of devices.
330 VLOG(1) << "Populating " << adapter_->GetDevices().size()
331 << " devices in chooser.";
332 for (const device::BluetoothDevice* device : adapter_->GetDevices()) {
333 AddFilteredDevice(*device);
334 }
335
336 if (!chooser_.get()) {
337 // If the dialog's closing, no need to do any of the rest of this.
338 return;
339 }
340
341 if (!adapter_->IsPowered()) {
342 chooser_->SetAdapterPresence(
343 BluetoothChooser::AdapterPresence::POWERED_OFF);
344 return;
345 }
346
347 StartDeviceDiscovery();
348 }
349
350 void BluetoothDeviceChooserController::AddFilteredDevice(
351 const device::BluetoothDevice& device) {
352 if (chooser_.get() && MatchesFilters(device, options_->filters)) {
353 VLOG(1) << "Adding device to chooser: " << device.GetAddress();
354 chooser_->AddDevice(device.GetAddress(), device.GetName());
355 }
356 }
357
358 void BluetoothDeviceChooserController::AdapterPoweredChanged(bool powered) {
359 if (!powered && discovery_session_.get()) {
360 StopDiscoverySession(std::move(discovery_session_));
361 }
362
363 if (chooser_.get()) {
364 chooser_->SetAdapterPresence(
365 powered ? BluetoothChooser::AdapterPresence::POWERED_ON
366 : BluetoothChooser::AdapterPresence::POWERED_OFF);
367 }
368
369 if (!powered) {
370 discovery_session_timer_.Stop();
371 }
372 }
373
374 void BluetoothDeviceChooserController::StartDeviceDiscovery() {
375 DCHECK_CURRENTLY_ON(BrowserThread::UI);
376
377 if (discovery_session_.get() && discovery_session_->IsActive()) {
378 // Already running; just increase the timeout.
379 discovery_session_timer_.Reset();
380 return;
381 }
382
383 chooser_->ShowDiscoveryState(BluetoothChooser::DiscoveryState::DISCOVERING);
384 adapter_->StartDiscoverySessionWithFilter(
385 ComputeScanFilter(options_->filters),
386 base::Bind(
387 &BluetoothDeviceChooserController::OnStartDiscoverySessionSuccess,
388 weak_ptr_factory_.GetWeakPtr()),
389 base::Bind(
390 &BluetoothDeviceChooserController::OnStartDiscoverySessionFailed,
391 weak_ptr_factory_.GetWeakPtr()));
392 }
393
394 void BluetoothDeviceChooserController::StopDeviceDiscovery() {
395 DCHECK_CURRENTLY_ON(BrowserThread::UI);
396 StopDiscoverySession(std::move(discovery_session_));
397 if (chooser_) {
398 chooser_->ShowDiscoveryState(BluetoothChooser::DiscoveryState::IDLE);
399 }
400 }
401
402 void BluetoothDeviceChooserController::OnStartDiscoverySessionSuccess(
403 std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
404 DCHECK_CURRENTLY_ON(BrowserThread::UI);
405 VLOG(1) << "Started discovery session.";
406 if (chooser_.get()) {
407 discovery_session_ = std::move(discovery_session);
408 discovery_session_timer_.Reset();
409 } else {
410 StopDiscoverySession(std::move(discovery_session));
411 }
412 }
413
414 void BluetoothDeviceChooserController::OnStartDiscoverySessionFailed() {
415 if (chooser_.get()) {
416 chooser_->ShowDiscoveryState(
417 BluetoothChooser::DiscoveryState::FAILED_TO_START);
418 }
419 }
420
421 void BluetoothDeviceChooserController::OnBluetoothChooserEvent(
422 BluetoothChooser::Event event,
423 const std::string& device_address) {
424 DCHECK_CURRENTLY_ON(BrowserThread::UI);
425 // Shouldn't recieve an event from a closed chooser.
426 DCHECK(chooser_.get());
427
428 switch (event) {
429 case BluetoothChooser::Event::RESCAN:
430 StartDeviceDiscovery();
431 // No need to close the chooser so we return.
432 return;
433 case BluetoothChooser::Event::DENIED_PERMISSION:
434 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
435 PostErrorCallback(blink::mojom::WebBluetoothError::
436 CHOOSER_NOT_SHOWN_USER_DENIED_PERMISSION_TO_SCAN);
437 break;
438 case BluetoothChooser::Event::CANCELLED:
439 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
440 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
441 break;
442 case BluetoothChooser::Event::SHOW_OVERVIEW_HELP:
443 VLOG(1) << "Overview Help link pressed.";
444 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
445 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
446 break;
447 case BluetoothChooser::Event::SHOW_ADAPTER_OFF_HELP:
448 VLOG(1) << "Adapter Off Help link pressed.";
449 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
450 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
451 break;
452 case BluetoothChooser::Event::SHOW_NEED_LOCATION_HELP:
453 VLOG(1) << "Need Location Help link pressed.";
454 RecordRequestDeviceOutcome(OutcomeFromChooserEvent(event));
455 PostErrorCallback(blink::mojom::WebBluetoothError::CHOOSER_CANCELLED);
456 break;
457 case BluetoothChooser::Event::SELECTED:
458 PostSuccessCallback(device_address);
459 break;
460 }
461 // Close chooser.
462 chooser_.reset();
463 }
464
465 void BluetoothDeviceChooserController::PostSuccessCallback(
466 const std::string& device_address) {
467 if (!base::ThreadTaskRunnerHandle::Get()->PostTask(
468 FROM_HERE,
469 base::Bind(success_callback_, base::Passed(std::move(options_)),
470 device_address))) {
471 LOG(WARNING) << "No TaskRunner.";
472 }
473 }
474
475 void BluetoothDeviceChooserController::PostErrorCallback(
476 blink::mojom::WebBluetoothError error) {
477 if (!base::ThreadTaskRunnerHandle::Get()->PostTask(
478 FROM_HERE, base::Bind(error_callback_, error))) {
479 LOG(WARNING) << "No TaskRunner.";
480 }
481 }
482
483 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698