| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "modules/bluetooth/BluetoothRemoteGATTCharacteristic.h" |
| |
| #include "bindings/core/v8/ScriptPromise.h" |
| #include "bindings/core/v8/ScriptPromiseResolver.h" |
| #include "core/events/Event.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "modules/bluetooth/Bluetooth.h" |
| #include "modules/bluetooth/BluetoothCharacteristicProperties.h" |
| #include "modules/bluetooth/BluetoothDevice.h" |
| #include "modules/bluetooth/BluetoothError.h" |
| #include "modules/bluetooth/BluetoothRemoteGATTDescriptor.h" |
| #include "modules/bluetooth/BluetoothRemoteGATTService.h" |
| #include "modules/bluetooth/BluetoothRemoteGATTUtils.h" |
| #include "modules/bluetooth/BluetoothUUID.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| namespace blink { |
| |
| BluetoothRemoteGATTCharacteristic::BluetoothRemoteGATTCharacteristic( |
| ExecutionContext* context, |
| mojom::blink::WebBluetoothRemoteGATTCharacteristicPtr characteristic, |
| BluetoothRemoteGATTService* service, |
| BluetoothDevice* device) |
| : ContextLifecycleObserver(context), |
| m_characteristic(std::move(characteristic)), |
| m_service(service), |
| m_stopped(false), |
| m_device(device) { |
| m_properties = |
| BluetoothCharacteristicProperties::create(m_characteristic->properties); |
| } |
| |
| BluetoothRemoteGATTCharacteristic* BluetoothRemoteGATTCharacteristic::create( |
| ExecutionContext* context, |
| mojom::blink::WebBluetoothRemoteGATTCharacteristicPtr characteristic, |
| BluetoothRemoteGATTService* service, |
| BluetoothDevice* device) { |
| return new BluetoothRemoteGATTCharacteristic( |
| context, std::move(characteristic), service, device); |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::setValue(DOMDataView* domDataView) { |
| m_value = domDataView; |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::dispatchCharacteristicValueChanged( |
| const Vector<uint8_t>& value) { |
| this->setValue(BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value)); |
| dispatchEvent(Event::create(EventTypeNames::characteristicvaluechanged)); |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::contextDestroyed(ExecutionContext*) { |
| notifyCharacteristicObjectRemoved(); |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::dispose() { |
| notifyCharacteristicObjectRemoved(); |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::notifyCharacteristicObjectRemoved() { |
| if (!m_stopped) { |
| m_stopped = true; |
| m_device->bluetooth()->characteristicObjectRemoved( |
| m_characteristic->instance_id); |
| } |
| } |
| |
| const WTF::AtomicString& BluetoothRemoteGATTCharacteristic::interfaceName() |
| const { |
| return EventTargetNames::BluetoothRemoteGATTCharacteristic; |
| } |
| |
| ExecutionContext* BluetoothRemoteGATTCharacteristic::getExecutionContext() |
| const { |
| return ContextLifecycleObserver::getExecutionContext(); |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::addedEventListener( |
| const AtomicString& eventType, |
| RegisteredEventListener& registeredListener) { |
| EventTargetWithInlineData::addedEventListener(eventType, registeredListener); |
| // We will also need to unregister a characteristic once all the event |
| // listeners have been removed. See http://crbug.com/541390 |
| if (eventType == EventTypeNames::characteristicvaluechanged) { |
| m_device->bluetooth()->registerCharacteristicObject( |
| m_characteristic->instance_id, this); |
| } |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::ReadValueCallback( |
| ScriptPromiseResolver* resolver, |
| mojom::blink::WebBluetoothResult result, |
| const Optional<Vector<uint8_t>>& value) { |
| if (!resolver->getExecutionContext() || |
| resolver->getExecutionContext()->isContextDestroyed()) |
| return; |
| |
| // If the resolver is not in the set of ActiveAlgorithms then the frame |
| // disconnected so we reject. |
| if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) { |
| resolver->reject(BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerDisconnected)); |
| return; |
| } |
| |
| if (result == mojom::blink::WebBluetoothResult::SUCCESS) { |
| DCHECK(value); |
| DOMDataView* domDataView = |
| BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value.value()); |
| setValue(domDataView); |
| resolver->resolve(domDataView); |
| } else { |
| resolver->reject(BluetoothError::take(resolver, result)); |
| } |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::readValue( |
| ScriptState* scriptState) { |
| // We always check that the device is connected. |
| if (!getGatt()->connected()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected)); |
| } |
| |
| if (!getGatt()->device()->isValidCharacteristic( |
| m_characteristic->instance_id)) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kInvalidCharacteristic)); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| ScriptPromise promise = resolver->promise(); |
| getGatt()->AddToActiveAlgorithms(resolver); |
| |
| mojom::blink::WebBluetoothService* service = m_device->bluetooth()->service(); |
| service->RemoteCharacteristicReadValue( |
| m_characteristic->instance_id, |
| convertToBaseCallback( |
| WTF::bind(&BluetoothRemoteGATTCharacteristic::ReadValueCallback, |
| wrapPersistent(this), wrapPersistent(resolver)))); |
| |
| return promise; |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::WriteValueCallback( |
| ScriptPromiseResolver* resolver, |
| const Vector<uint8_t>& value, |
| mojom::blink::WebBluetoothResult result) { |
| if (!resolver->getExecutionContext() || |
| resolver->getExecutionContext()->isContextDestroyed()) |
| return; |
| |
| // If the resolver is not in the set of ActiveAlgorithms then the frame |
| // disconnected so we reject. |
| if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) { |
| resolver->reject(BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerDisconnected)); |
| return; |
| } |
| |
| if (result == mojom::blink::WebBluetoothResult::SUCCESS) { |
| setValue(BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value)); |
| resolver->resolve(); |
| } else { |
| resolver->reject(BluetoothError::take(resolver, result)); |
| } |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::writeValue( |
| ScriptState* scriptState, |
| const DOMArrayPiece& value) { |
| // We always check that the device is connected. |
| if (!getGatt()->connected()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected)); |
| } |
| |
| if (!getGatt()->device()->isValidCharacteristic( |
| m_characteristic->instance_id)) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kInvalidCharacteristic)); |
| } |
| |
| // Partial implementation of writeValue algorithm: |
| // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue |
| |
| // If bytes is more than 512 bytes long (the maximum length of an attribute |
| // value, per Long Attribute Values) return a promise rejected with an |
| // InvalidModificationError and abort. |
| if (value.byteLength() > 512) |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, DOMException::create(InvalidModificationError, |
| "Value can't exceed 512 bytes.")); |
| |
| // Let valueVector be a copy of the bytes held by value. |
| Vector<uint8_t> valueVector; |
| valueVector.append(value.bytes(), value.byteLength()); |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| ScriptPromise promise = resolver->promise(); |
| getGatt()->AddToActiveAlgorithms(resolver); |
| |
| mojom::blink::WebBluetoothService* service = m_device->bluetooth()->service(); |
| service->RemoteCharacteristicWriteValue( |
| m_characteristic->instance_id, valueVector, |
| convertToBaseCallback(WTF::bind( |
| &BluetoothRemoteGATTCharacteristic::WriteValueCallback, |
| wrapPersistent(this), wrapPersistent(resolver), valueVector))); |
| |
| return promise; |
| } |
| |
| void BluetoothRemoteGATTCharacteristic::NotificationsCallback( |
| ScriptPromiseResolver* resolver, |
| mojom::blink::WebBluetoothResult result) { |
| if (!resolver->getExecutionContext() || |
| resolver->getExecutionContext()->isContextDestroyed()) |
| return; |
| |
| // If the resolver is not in the set of ActiveAlgorithms then the frame |
| // disconnected so we reject. |
| if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) { |
| resolver->reject(BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerDisconnected)); |
| return; |
| } |
| |
| if (result == mojom::blink::WebBluetoothResult::SUCCESS) { |
| resolver->resolve(this); |
| } else { |
| resolver->reject(BluetoothError::take(resolver, result)); |
| } |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::startNotifications( |
| ScriptState* scriptState) { |
| // We always check that the device is connected. |
| if (!getGatt()->connected()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected)); |
| } |
| |
| if (!getGatt()->device()->isValidCharacteristic( |
| m_characteristic->instance_id)) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kInvalidCharacteristic)); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| ScriptPromise promise = resolver->promise(); |
| getGatt()->AddToActiveAlgorithms(resolver); |
| |
| mojom::blink::WebBluetoothService* service = m_device->bluetooth()->service(); |
| service->RemoteCharacteristicStartNotifications( |
| m_characteristic->instance_id, |
| convertToBaseCallback( |
| WTF::bind(&BluetoothRemoteGATTCharacteristic::NotificationsCallback, |
| wrapPersistent(this), wrapPersistent(resolver)))); |
| |
| return promise; |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::stopNotifications( |
| ScriptState* scriptState) { |
| // We always check that the device is connected. |
| if (!getGatt()->connected()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected)); |
| } |
| |
| if (!getGatt()->device()->isValidCharacteristic( |
| m_characteristic->instance_id)) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kInvalidCharacteristic)); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| ScriptPromise promise = resolver->promise(); |
| getGatt()->AddToActiveAlgorithms(resolver); |
| |
| mojom::blink::WebBluetoothService* service = m_device->bluetooth()->service(); |
| service->RemoteCharacteristicStopNotifications( |
| m_characteristic->instance_id, |
| convertToBaseCallback( |
| WTF::bind(&BluetoothRemoteGATTCharacteristic::NotificationsCallback, |
| wrapPersistent(this), wrapPersistent(resolver), |
| mojom::blink::WebBluetoothResult::SUCCESS))); |
| return promise; |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptor( |
| ScriptState* scriptState, |
| const StringOrUnsignedLong& descriptorUUID, |
| ExceptionState& exceptionState) { |
| String descriptor = |
| BluetoothUUID::getDescriptor(descriptorUUID, exceptionState); |
| if (exceptionState.hadException()) |
| return exceptionState.reject(scriptState); |
| |
| return getDescriptorsImpl(scriptState, |
| mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE, |
| descriptor); |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptors( |
| ScriptState* scriptState, |
| ExceptionState&) { |
| return getDescriptorsImpl( |
| scriptState, mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE); |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptors( |
| ScriptState* scriptState, |
| const StringOrUnsignedLong& descriptorUUID, |
| ExceptionState& exceptionState) { |
| String descriptor = |
| BluetoothUUID::getDescriptor(descriptorUUID, exceptionState); |
| if (exceptionState.hadException()) |
| return exceptionState.reject(scriptState); |
| |
| return getDescriptorsImpl( |
| scriptState, mojom::blink::WebBluetoothGATTQueryQuantity::MULTIPLE, |
| descriptor); |
| } |
| |
| ScriptPromise BluetoothRemoteGATTCharacteristic::getDescriptorsImpl( |
| ScriptState* scriptState, |
| mojom::blink::WebBluetoothGATTQueryQuantity quantity, |
| const String& descriptor) { |
| if (!getGatt()->connected()) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected)); |
| } |
| |
| if (!getGatt()->device()->isValidCharacteristic( |
| m_characteristic->instance_id)) { |
| return ScriptPromise::rejectWithDOMException( |
| scriptState, |
| BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kInvalidCharacteristic)); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
| ScriptPromise promise = resolver->promise(); |
| getGatt()->AddToActiveAlgorithms(resolver); |
| |
| mojom::blink::WebBluetoothService* service = m_device->bluetooth()->service(); |
| WTF::Optional<String> uuid = WTF::nullopt; |
| if (!descriptor.isEmpty()) |
| uuid = descriptor; |
| service->RemoteCharacteristicGetDescriptors( |
| m_characteristic->instance_id, quantity, uuid, |
| convertToBaseCallback( |
| WTF::bind(&BluetoothRemoteGATTCharacteristic::GetDescriptorsCallback, |
| wrapPersistent(this), m_characteristic->instance_id, |
| quantity, wrapPersistent(resolver)))); |
| |
| return promise; |
| } |
| |
| // Callback that allows us to resolve the promise with a single descriptor |
| // or with a vector owning the descriptors. |
| void BluetoothRemoteGATTCharacteristic::GetDescriptorsCallback( |
| const String& characteristicInstanceId, |
| mojom::blink::WebBluetoothGATTQueryQuantity quantity, |
| ScriptPromiseResolver* resolver, |
| mojom::blink::WebBluetoothResult result, |
| Optional<Vector<mojom::blink::WebBluetoothRemoteGATTDescriptorPtr>> |
| descriptors) { |
| if (!resolver->getExecutionContext() || |
| resolver->getExecutionContext()->isContextDestroyed()) |
| return; |
| |
| // If the resolver is not in the set of ActiveAlgorithms then the frame |
| // disconnected so we reject. |
| if (!service()->device()->gatt()->RemoveFromActiveAlgorithms(resolver)) { |
| resolver->reject(BluetoothRemoteGATTUtils::CreateDOMException( |
| BluetoothRemoteGATTUtils::ExceptionType::kGATTServerDisconnected)); |
| return; |
| } |
| |
| if (result == mojom::blink::WebBluetoothResult::SUCCESS) { |
| DCHECK(descriptors); |
| |
| if (quantity == mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE) { |
| DCHECK_EQ(1u, descriptors->size()); |
| resolver->resolve( |
| service()->device()->getOrCreateBluetoothRemoteGATTDescriptor( |
| std::move(descriptors.value()[0]), this)); |
| return; |
| } |
| |
| HeapVector<Member<BluetoothRemoteGATTDescriptor>> gattDescriptors; |
| gattDescriptors.reserveInitialCapacity(descriptors->size()); |
| for (auto& descriptor : descriptors.value()) { |
| gattDescriptors.push_back( |
| service()->device()->getOrCreateBluetoothRemoteGATTDescriptor( |
| std::move(descriptor), this)); |
| } |
| resolver->resolve(gattDescriptors); |
| } else { |
| resolver->reject(BluetoothError::take(resolver, result)); |
| } |
| } |
| |
| DEFINE_TRACE(BluetoothRemoteGATTCharacteristic) { |
| visitor->trace(m_service); |
| visitor->trace(m_properties); |
| visitor->trace(m_value); |
| visitor->trace(m_device); |
| EventTargetWithInlineData::trace(visitor); |
| ContextLifecycleObserver::trace(visitor); |
| } |
| |
| } // namespace blink |