Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // NETWORK_ERROR Note: | 5 // NETWORK_ERROR Note: |
| 6 // When a device can't be found in the BluetoothAdapter, that generally | 6 // When a device can't be found in the BluetoothAdapter, that generally |
| 7 // indicates that it's gone out of range. We reject with a NetworkError in that | 7 // indicates that it's gone out of range. We reject with a NetworkError in that |
| 8 // case. | 8 // case. |
| 9 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-conne ctgatt | 9 // https://webbluetoothchrome.github.io/web-bluetooth/#dom-bluetoothdevice-conne ctgatt |
| 10 | 10 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 51 NO_MATCHING_DEVICES_FOUND = 5, | 51 NO_MATCHING_DEVICES_FOUND = 5, |
| 52 BLUETOOTH_ADAPTER_NOT_PRESENT = 6, | 52 BLUETOOTH_ADAPTER_NOT_PRESENT = 6, |
| 53 BLUETOOTH_ADAPTER_OFF = 7, | 53 BLUETOOTH_ADAPTER_OFF = 7, |
| 54 // NOTE: Add new requestDevice() outcomes immediately above this line. Make | 54 // NOTE: Add new requestDevice() outcomes immediately above this line. Make |
| 55 // sure to update the enum list in | 55 // sure to update the enum list in |
| 56 // tools/metrics/histogram/histograms.xml accordingly. | 56 // tools/metrics/histogram/histograms.xml accordingly. |
| 57 COUNT | 57 COUNT |
| 58 }; | 58 }; |
| 59 | 59 |
| 60 void RecordRequestDeviceOutcome(UMARequestDeviceOutcome outcome) { | 60 void RecordRequestDeviceOutcome(UMARequestDeviceOutcome outcome) { |
| 61 UMA_HISTOGRAM_ENUMERATION("Bluetooth.RequestDevice.Outcome", | 61 UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.RequestDevice.Outcome", |
| 62 static_cast<int>(outcome), | 62 static_cast<int>(outcome), |
| 63 static_cast<int>(UMARequestDeviceOutcome::COUNT)); | 63 static_cast<int>(UMARequestDeviceOutcome::COUNT)); |
| 64 } | 64 } |
| 65 | 65 |
| 66 enum class UMAGATTServices { | |
| 67 UNKNOWN, | |
| 68 ALERT_NOTIFICATION, | |
| 69 AUTOMATION_IO, | |
| 70 BATTERY_SERVICE, | |
| 71 BLOOD_PRESSURE, | |
| 72 BODY_COMPOSITION, | |
| 73 BOND_MANAGEMENT, | |
| 74 CONTINUOUS_GLUCOSE_MONITORING, | |
| 75 CURRENT_TIME, | |
| 76 CYCLING_POWER, | |
| 77 CYCLING_SPEED_AND_CADENCE, | |
| 78 DEVICE_INFORMATION, | |
| 79 ENVIRONMENTAL_SENSING, | |
| 80 GENERIC_ACCESS, | |
| 81 GENERIC_ATTRIBUTE, | |
| 82 GLUCOSE, | |
| 83 HEALTH_THERMOMETER, | |
| 84 HEART_RATE, | |
| 85 HUMAN_INTERFACE_DEVICE, | |
| 86 IMMEDIATE_ALERT, | |
| 87 INDOOR_POSITIONING, | |
| 88 INTERNET_PROTOCOL_SUPPORT, | |
| 89 LINK_LOSS, | |
| 90 LOCATION_AND_NAVIGATION, | |
| 91 NEXT_DST_CHANGE, | |
| 92 PHONE_ALERT_STATUS, | |
| 93 PULSE_OXIMETER, | |
| 94 REFERENCE_TIME_UPDATE, | |
| 95 RUNNING_SPEED_AND_CADENCE, | |
| 96 SCAN_PARAMETERS, | |
| 97 TX_POWER, | |
| 98 USER_DATA, | |
| 99 WEIGHT_SCALE, | |
| 100 // NOTE: Add new services immediately above this line. Make sure to update the | |
| 101 // enum list in tools/metrics/histogram/histograms.xml accordingly. | |
| 102 COUNT | |
| 103 }; | |
| 104 | |
| 105 typedef std::map<BluetoothUUID, UMAGATTServices> BluetoothUUIDToServicesMap; | |
| 106 | |
| 107 std::map<BluetoothUUID, UMAGATTServices>* getServiceToEnumMap() { | |
|
Jeffrey Yasskin
2015/08/05 19:03:49
I don't really like having to maintain yet another
ortuno
2015/08/10 20:05:50
Done.
| |
| 108 CR_DEFINE_STATIC_LOCAL(BluetoothUUIDToServicesMap, services, ()); | |
| 109 if (services.empty()) { | |
| 110 services.insert(std::make_pair(BluetoothUUID("1811"), | |
| 111 UMAGATTServices::ALERT_NOTIFICATION)); | |
| 112 services.insert(std::make_pair(BluetoothUUID("180F"), | |
| 113 UMAGATTServices::BATTERY_SERVICE)); | |
| 114 services.insert( | |
| 115 std::make_pair(BluetoothUUID("1810"), UMAGATTServices::BLOOD_PRESSURE)); | |
| 116 services.insert(std::make_pair(BluetoothUUID("181B"), | |
| 117 UMAGATTServices::BODY_COMPOSITION)); | |
| 118 services.insert(std::make_pair(BluetoothUUID("181E"), | |
| 119 UMAGATTServices::BOND_MANAGEMENT)); | |
| 120 services.insert(std::make_pair( | |
| 121 BluetoothUUID("181F"), UMAGATTServices::CONTINUOUS_GLUCOSE_MONITORING)); | |
| 122 services.insert( | |
| 123 std::make_pair(BluetoothUUID("1805"), UMAGATTServices::CURRENT_TIME)); | |
| 124 services.insert( | |
| 125 std::make_pair(BluetoothUUID("1818"), UMAGATTServices::CYCLING_POWER)); | |
| 126 services.insert(std::make_pair(BluetoothUUID("1816"), | |
| 127 UMAGATTServices::CYCLING_SPEED_AND_CADENCE)); | |
| 128 services.insert(std::make_pair(BluetoothUUID("180A"), | |
| 129 UMAGATTServices::DEVICE_INFORMATION)); | |
| 130 services.insert(std::make_pair(BluetoothUUID("181A"), | |
| 131 UMAGATTServices::ENVIRONMENTAL_SENSING)); | |
| 132 services.insert( | |
| 133 std::make_pair(BluetoothUUID("1800"), UMAGATTServices::GENERIC_ACCESS)); | |
| 134 services.insert(std::make_pair(BluetoothUUID("1801"), | |
| 135 UMAGATTServices::GENERIC_ATTRIBUTE)); | |
| 136 services.insert( | |
| 137 std::make_pair(BluetoothUUID("1808"), UMAGATTServices::GLUCOSE)); | |
| 138 services.insert(std::make_pair(BluetoothUUID("1809"), | |
| 139 UMAGATTServices::HEALTH_THERMOMETER)); | |
| 140 services.insert( | |
| 141 std::make_pair(BluetoothUUID("180D"), UMAGATTServices::HEART_RATE)); | |
| 142 services.insert(std::make_pair(BluetoothUUID("1812"), | |
| 143 UMAGATTServices::HUMAN_INTERFACE_DEVICE)); | |
| 144 services.insert(std::make_pair(BluetoothUUID("1802"), | |
| 145 UMAGATTServices::IMMEDIATE_ALERT)); | |
| 146 services.insert(std::make_pair(BluetoothUUID("1821"), | |
| 147 UMAGATTServices::INDOOR_POSITIONING)); | |
| 148 services.insert(std::make_pair(BluetoothUUID("1820"), | |
| 149 UMAGATTServices::INTERNET_PROTOCOL_SUPPORT)); | |
| 150 services.insert( | |
| 151 std::make_pair(BluetoothUUID("1803"), UMAGATTServices::LINK_LOSS)); | |
| 152 services.insert(std::make_pair(BluetoothUUID("1819"), | |
| 153 UMAGATTServices::LOCATION_AND_NAVIGATION)); | |
| 154 services.insert(std::make_pair(BluetoothUUID("1807"), | |
| 155 UMAGATTServices::NEXT_DST_CHANGE)); | |
| 156 services.insert(std::make_pair(BluetoothUUID("180E"), | |
| 157 UMAGATTServices::PHONE_ALERT_STATUS)); | |
| 158 services.insert(std::make_pair(BluetoothUUID("1806"), | |
| 159 UMAGATTServices::REFERENCE_TIME_UPDATE)); | |
| 160 services.insert(std::make_pair(BluetoothUUID("1814"), | |
| 161 UMAGATTServices::RUNNING_SPEED_AND_CADENCE)); | |
| 162 services.insert(std::make_pair(BluetoothUUID("1813"), | |
| 163 UMAGATTServices::SCAN_PARAMETERS)); | |
| 164 services.insert( | |
| 165 std::make_pair(BluetoothUUID("1804"), UMAGATTServices::TX_POWER)); | |
| 166 services.insert( | |
| 167 std::make_pair(BluetoothUUID("181C"), UMAGATTServices::USER_DATA)); | |
| 168 services.insert( | |
| 169 std::make_pair(BluetoothUUID("181D"), UMAGATTServices::WEIGHT_SCALE)); | |
| 170 } | |
| 171 return &services; | |
| 172 } | |
| 173 | |
| 174 int GetServiceBucket(const BluetoothUUID& service) { | |
| 175 std::map<BluetoothUUID, UMAGATTServices>* services = getServiceToEnumMap(); | |
| 176 auto it = services->find(service); | |
| 177 if (it == services->end()) | |
| 178 return static_cast<int>(UMAGATTServices::UNKNOWN); | |
| 179 return static_cast<int>(it->second); | |
| 180 } | |
| 181 | |
| 182 void RecordRequestDeviceFilters( | |
| 183 const std::vector<content::BluetoothScanFilter>& filters) { | |
| 184 UMA_HISTOGRAM_COUNTS("Bluetooth.Web.RequestDevice.Filters.Count", | |
| 185 filters.size()); | |
| 186 for (const content::BluetoothScanFilter& filter : filters) { | |
| 187 for (const BluetoothUUID& service : filter.services) { | |
| 188 UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.RequestDevice.Filters.Services", | |
| 189 GetServiceBucket(service), | |
| 190 static_cast<int>(UMAGATTServices::COUNT)); | |
| 191 } | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 void RecordRequestDeviceOptionalServices( | |
| 196 const std::vector<BluetoothUUID>& optional_services) { | |
| 197 UMA_HISTOGRAM_COUNTS("Bluetooth.Web.RequestDevice.OptionalServices.Count", | |
| 198 optional_services.size()); | |
| 199 for (const BluetoothUUID& service : optional_services) { | |
| 200 UMA_HISTOGRAM_ENUMERATION( | |
| 201 "Bluetooth.Web.RequestDevice.OptionalServices.Services", | |
| 202 GetServiceBucket(service), static_cast<int>(UMAGATTServices::COUNT)); | |
| 203 } | |
| 204 } | |
| 205 | |
| 66 enum class UMAWebBluetoothFunction { | 206 enum class UMAWebBluetoothFunction { |
| 67 REQUEST_DEVICE, | 207 REQUEST_DEVICE, |
| 68 CONNECT_GATT, | 208 CONNECT_GATT, |
| 69 GET_PRIMARY_SERVICE, | 209 GET_PRIMARY_SERVICE, |
| 70 GET_CHARACTERISTIC, | 210 GET_CHARACTERISTIC, |
| 71 CHARACTERISTIC_READ_VALUE, | 211 CHARACTERISTIC_READ_VALUE, |
| 72 CHARACTERISTIC_WRITE_VALUE, | 212 CHARACTERISTIC_WRITE_VALUE, |
| 73 // NOTE: Add new actions immediately above this line. Make sure to update the | 213 // NOTE: Add new actions immediately above this line. Make sure to update the |
| 74 // enum list in tools/metrics/histogram/histograms.xml accordingly. | 214 // enum list in tools/metrics/histogram/histograms.xml accordingly. |
| 75 COUNT | 215 COUNT |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 264 } | 404 } |
| 265 | 405 |
| 266 void BluetoothDispatcherHost::OnRequestDevice( | 406 void BluetoothDispatcherHost::OnRequestDevice( |
| 267 int thread_id, | 407 int thread_id, |
| 268 int request_id, | 408 int request_id, |
| 269 int frame_routing_id, | 409 int frame_routing_id, |
| 270 const std::vector<BluetoothScanFilter>& filters, | 410 const std::vector<BluetoothScanFilter>& filters, |
| 271 const std::vector<BluetoothUUID>& optional_services) { | 411 const std::vector<BluetoothUUID>& optional_services) { |
| 272 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 412 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 273 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE); | 413 RecordWebBluetoothFunctionCall(UMAWebBluetoothFunction::REQUEST_DEVICE); |
| 414 RecordRequestDeviceFilters(filters); | |
| 415 RecordRequestDeviceOptionalServices(optional_services); | |
| 416 | |
| 417 VLOG(1) << "requestDevice called with the following filters: "; | |
| 418 for (const BluetoothScanFilter& filter : filters) { | |
| 419 VLOG(1) << "["; | |
| 420 for (const BluetoothUUID& service : filter.services) | |
| 421 VLOG(1) << "\t" << service.value(); | |
| 422 VLOG(1) << "]"; | |
| 423 } | |
| 424 | |
| 425 VLOG(1) << "requestDevice called with the following optional services: "; | |
| 426 for (const BluetoothUUID& service : optional_services) | |
| 427 VLOG(1) << "\t" << service.value(); | |
| 274 | 428 |
| 275 RenderFrameHostImpl* render_frame_host = | 429 RenderFrameHostImpl* render_frame_host = |
| 276 RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id); | 430 RenderFrameHostImpl::FromID(render_process_id_, frame_routing_id); |
| 277 | 431 |
| 278 if (!render_frame_host) { | 432 if (!render_frame_host) { |
| 279 DLOG(WARNING) | 433 VLOG(1) << "Got a requestDevice IPC without a matching RenderFrameHost: " |
| 280 << "Got a requestDevice IPC without a matching RenderFrameHost: " | 434 << render_process_id_ << ", " << frame_routing_id; |
| 281 << render_process_id_ << ", " << frame_routing_id; | |
| 282 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME); | 435 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_RENDER_FRAME); |
| 283 Send(new BluetoothMsg_RequestDeviceError( | 436 Send(new BluetoothMsg_RequestDeviceError( |
| 284 thread_id, request_id, WebBluetoothError::RequestDeviceWithoutFrame)); | 437 thread_id, request_id, WebBluetoothError::RequestDeviceWithoutFrame)); |
| 285 } | 438 } |
| 286 | 439 |
| 287 // TODO(scheib): Device selection UI: crbug.com/436280 | 440 // TODO(scheib): Device selection UI: crbug.com/436280 |
| 288 // TODO(scheib): Utilize BluetoothAdapter::Observer::DeviceAdded/Removed. | 441 // TODO(scheib): Utilize BluetoothAdapter::Observer::DeviceAdded/Removed. |
| 289 if (adapter_.get()) { | 442 if (adapter_.get()) { |
| 290 if (!request_device_sessions_ | 443 if (!request_device_sessions_ |
| 291 .insert(std::make_pair( | 444 .insert(std::make_pair( |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 314 thread_id, request_id, WebBluetoothError::BluetoothAdapterOff)); | 467 thread_id, request_id, WebBluetoothError::BluetoothAdapterOff)); |
| 315 return; | 468 return; |
| 316 } | 469 } |
| 317 adapter_->StartDiscoverySessionWithFilter( | 470 adapter_->StartDiscoverySessionWithFilter( |
| 318 ComputeScanFilter(filters), | 471 ComputeScanFilter(filters), |
| 319 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, | 472 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStarted, |
| 320 weak_ptr_factory_.GetWeakPtr(), thread_id, request_id), | 473 weak_ptr_factory_.GetWeakPtr(), thread_id, request_id), |
| 321 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, | 474 base::Bind(&BluetoothDispatcherHost::OnDiscoverySessionStartedError, |
| 322 weak_ptr_factory_.GetWeakPtr(), thread_id, request_id)); | 475 weak_ptr_factory_.GetWeakPtr(), thread_id, request_id)); |
| 323 } else { | 476 } else { |
| 324 DLOG(WARNING) << "No BluetoothAdapter. Can't serve requestDevice."; | 477 VLOG(1) << "No BluetoothAdapter. Can't serve requestDevice."; |
| 325 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER); | 478 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::NO_BLUETOOTH_ADAPTER); |
| 326 Send(new BluetoothMsg_RequestDeviceError( | 479 Send(new BluetoothMsg_RequestDeviceError( |
| 327 thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); | 480 thread_id, request_id, WebBluetoothError::NoBluetoothAdapter)); |
| 328 } | 481 } |
| 329 return; | 482 return; |
| 330 } | 483 } |
| 331 | 484 |
| 332 void BluetoothDispatcherHost::OnConnectGATT( | 485 void BluetoothDispatcherHost::OnConnectGATT( |
| 333 int thread_id, | 486 int thread_id, |
| 334 int request_id, | 487 int request_id, |
| (...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 598 } | 751 } |
| 599 | 752 |
| 600 void BluetoothDispatcherHost::OnDiscoverySessionStopped(int thread_id, | 753 void BluetoothDispatcherHost::OnDiscoverySessionStopped(int thread_id, |
| 601 int request_id) { | 754 int request_id) { |
| 602 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 755 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 603 auto session = | 756 auto session = |
| 604 request_device_sessions_.find(std::make_pair(thread_id, request_id)); | 757 request_device_sessions_.find(std::make_pair(thread_id, request_id)); |
| 605 CHECK(session != request_device_sessions_.end()); | 758 CHECK(session != request_device_sessions_.end()); |
| 606 BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); | 759 BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); |
| 607 for (device::BluetoothDevice* device : devices) { | 760 for (device::BluetoothDevice* device : devices) { |
| 761 VLOG(1) << "Device: " << device->GetName(); | |
| 762 VLOG(1) << "UUIDs: "; | |
| 763 for (BluetoothUUID uuid : device->GetUUIDs()) | |
| 764 VLOG(1) << "\t" << uuid.canonical_value(); | |
| 608 if (MatchesFilters(*device, session->second.filters)) { | 765 if (MatchesFilters(*device, session->second.filters)) { |
| 609 content::BluetoothDevice device_ipc( | 766 content::BluetoothDevice device_ipc( |
| 610 device->GetAddress(), // instance_id | 767 device->GetAddress(), // instance_id |
| 611 device->GetName(), // name | 768 device->GetName(), // name |
| 612 device->GetBluetoothClass(), // device_class | 769 device->GetBluetoothClass(), // device_class |
| 613 device->GetVendorIDSource(), // vendor_id_source | 770 device->GetVendorIDSource(), // vendor_id_source |
| 614 device->GetVendorID(), // vendor_id | 771 device->GetVendorID(), // vendor_id |
| 615 device->GetProductID(), // product_id | 772 device->GetProductID(), // product_id |
| 616 device->GetDeviceID(), // product_version | 773 device->GetDeviceID(), // product_version |
| 617 device->IsPaired(), // paired | 774 device->IsPaired(), // paired |
| 618 content::BluetoothDevice::UUIDsFromBluetoothUUIDs( | 775 content::BluetoothDevice::UUIDsFromBluetoothUUIDs( |
| 619 device->GetUUIDs())); // uuids | 776 device->GetUUIDs())); // uuids |
| 620 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); | 777 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::SUCCESS); |
| 621 Send(new BluetoothMsg_RequestDeviceSuccess(thread_id, request_id, | 778 Send(new BluetoothMsg_RequestDeviceSuccess(thread_id, request_id, |
| 622 device_ipc)); | 779 device_ipc)); |
| 623 request_device_sessions_.erase(session); | 780 request_device_sessions_.erase(session); |
| 624 return; | 781 return; |
| 625 } | 782 } |
| 626 } | 783 } |
| 627 RecordRequestDeviceOutcome( | 784 RecordRequestDeviceOutcome( |
| 628 UMARequestDeviceOutcome::NO_MATCHING_DEVICES_FOUND); | 785 UMARequestDeviceOutcome::NO_MATCHING_DEVICES_FOUND); |
| 786 VLOG(1) << "No matching Bluetooth Devices found"; | |
| 629 Send(new BluetoothMsg_RequestDeviceError(thread_id, request_id, | 787 Send(new BluetoothMsg_RequestDeviceError(thread_id, request_id, |
| 630 WebBluetoothError::NoDevicesFound)); | 788 WebBluetoothError::NoDevicesFound)); |
| 631 request_device_sessions_.erase(session); | 789 request_device_sessions_.erase(session); |
| 632 } | 790 } |
| 633 | 791 |
| 634 void BluetoothDispatcherHost::OnDiscoverySessionStoppedError(int thread_id, | 792 void BluetoothDispatcherHost::OnDiscoverySessionStoppedError(int thread_id, |
| 635 int request_id) { | 793 int request_id) { |
| 636 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 794 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 637 DLOG(WARNING) << "BluetoothDispatcherHost::OnDiscoverySessionStoppedError"; | 795 DLOG(WARNING) << "BluetoothDispatcherHost::OnDiscoverySessionStoppedError"; |
| 638 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::DISCOVERY_STOP_FAILED); | 796 RecordRequestDeviceOutcome(UMARequestDeviceOutcome::DISCOVERY_STOP_FAILED); |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 721 | 879 |
| 722 void BluetoothDispatcherHost::OnWriteValueFailed( | 880 void BluetoothDispatcherHost::OnWriteValueFailed( |
| 723 int thread_id, | 881 int thread_id, |
| 724 int request_id, | 882 int request_id, |
| 725 device::BluetoothGattService::GattErrorCode error_code) { | 883 device::BluetoothGattService::GattErrorCode error_code) { |
| 726 Send(new BluetoothMsg_WriteCharacteristicValueError( | 884 Send(new BluetoothMsg_WriteCharacteristicValueError( |
| 727 thread_id, request_id, TranslateGATTError(error_code))); | 885 thread_id, request_id, TranslateGATTError(error_code))); |
| 728 } | 886 } |
| 729 | 887 |
| 730 } // namespace content | 888 } // namespace content |
| OLD | NEW |