Index: content/renderer/bluetooth/bluetooth_dispatcher.cc |
diff --git a/content/renderer/bluetooth/bluetooth_dispatcher.cc b/content/renderer/bluetooth/bluetooth_dispatcher.cc |
index e6ff1cb8b6ddcd9a7f8e1db0195c77c0505a2df2..85fd71a29c39a6ff8ecbe1a2ea14c2db8cdf52bf 100644 |
--- a/content/renderer/bluetooth/bluetooth_dispatcher.cc |
+++ b/content/renderer/bluetooth/bluetooth_dispatcher.cc |
@@ -14,6 +14,7 @@ |
#include "third_party/WebKit/public/platform/WebPassOwnPtr.h" |
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothDevice.h" |
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothError.h" |
+#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothGATTCharacteristic.h" |
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothGATTCharacteristicInit.h" |
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothGATTRemoteServer.h" |
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothGATTService.h" |
@@ -31,6 +32,8 @@ using blink::WebBluetoothScanFilter; |
using blink::WebRequestDeviceOptions; |
using blink::WebString; |
using blink::WebVector; |
+using NotificationsRequestType = |
+ content::BluetoothDispatcher::NotificationsRequestType; |
struct BluetoothPrimaryServiceRequest { |
BluetoothPrimaryServiceRequest( |
@@ -62,6 +65,31 @@ struct BluetoothCharacteristicRequest { |
scoped_ptr<blink::WebBluetoothGetCharacteristicCallbacks> callbacks; |
}; |
+// Struct that holds a pending Start/StopNotifications request. |
+struct BluetoothNotificationsRequest { |
+ BluetoothNotificationsRequest( |
+ const std::string characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic, |
+ blink::WebBluetoothNotificationsCallbacks* callbacks, |
+ NotificationsRequestType type) |
+ : characteristic_instance_id(characteristic_instance_id), |
+ characteristic(characteristic), |
+ callbacks(callbacks), |
+ type(type) {} |
+ ~BluetoothNotificationsRequest() {} |
+ |
+ const std::string characteristic_instance_id; |
+ // The characteristic object is owned by the execution context on |
+ // the blink side which can destroy the object at any point. Since the |
+ // object implements ActiveDOMObject, the object calls Stop when is getting |
+ // destroyed, which in turn calls characteristicObjectRemoved. |
+ // characteristicObjectRemoved will null any pointers to the object |
+ // and queue a stop notifications request if necessary. |
+ blink::WebBluetoothGATTCharacteristic* characteristic; |
+ scoped_ptr<blink::WebBluetoothNotificationsCallbacks> callbacks; |
+ NotificationsRequestType type; |
+}; |
+ |
namespace content { |
namespace { |
@@ -143,6 +171,12 @@ void BluetoothDispatcher::OnMessageReceived(const IPC::Message& msg) { |
OnWriteValueSuccess); |
IPC_MESSAGE_HANDLER(BluetoothMsg_WriteCharacteristicValueError, |
OnWriteValueError); |
+ IPC_MESSAGE_HANDLER(BluetoothMsg_StartNotificationsSuccess, |
+ OnStartNotificationsSuccess) |
+ IPC_MESSAGE_HANDLER(BluetoothMsg_StartNotificationsError, |
+ OnStartNotificationsError) |
+ IPC_MESSAGE_HANDLER(BluetoothMsg_StopNotificationsSuccess, |
+ OnStopNotificationsSuccess) |
IPC_MESSAGE_UNHANDLED(handled = false) |
IPC_END_MESSAGE_MAP() |
DCHECK(handled) << "Unhandled message:" << msg.type(); |
@@ -225,10 +259,247 @@ void BluetoothDispatcher::writeValue( |
CurrentWorkerId(), request_id, characteristic_instance_id.utf8(), value)); |
} |
+void BluetoothDispatcher::startNotifications( |
+ const blink::WebString& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic, |
+ blink::WebBluetoothNotificationsCallbacks* callbacks) { |
+ int request_id = QueueNotificationRequest(characteristic_instance_id.utf8(), |
+ characteristic, callbacks, |
+ NotificationsRequestType::START); |
+ // The Notification subscription's state can change after a request |
+ // finishes. To avoid resolving with a soon-to-be-invalid state we queue |
+ // requests. |
+ if (HasNotificationRequestResponsePending( |
+ characteristic_instance_id.utf8())) { |
+ return; |
+ } |
+ |
+ ResolveOrSendStartNotificationRequest(request_id); |
+} |
+ |
+void BluetoothDispatcher::stopNotifications( |
+ const blink::WebString& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic, |
+ blink::WebBluetoothNotificationsCallbacks* callbacks) { |
+ int request_id = QueueNotificationRequest(characteristic_instance_id.utf8(), |
+ characteristic, callbacks, |
+ NotificationsRequestType::STOP); |
+ if (HasNotificationRequestResponsePending( |
+ characteristic_instance_id.utf8())) { |
+ return; |
+ } |
+ |
+ ResolveOrSendStopNotificationsRequest(request_id); |
+} |
+ |
+void BluetoothDispatcher::characteristicObjectRemoved( |
+ const blink::WebString& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic) { |
+ // A characteristic object is in the queue waiting for a response |
+ // or in the set of active notifications. |
+ |
+ // If the object is in the queue we null the characteristic. If this is the |
+ // first object waiting for a response OnStartNotificationsSuccess will make |
+ // sure not to add the characteristic to the map and it will queue a Stop |
+ // request. Otherwise ResolveOrSendStartNotificationRequest will make sure not |
+ // to add it to active notification subscriptions. |
+ bool found = false; |
+ for (IDMap<BluetoothNotificationsRequest, IDMapOwnPointer>::iterator iter( |
+ &pending_notifications_requests_); |
+ !iter.IsAtEnd(); iter.Advance()) { |
+ if (iter.GetCurrentValue()->characteristic == characteristic) { |
+ found = true; |
+ iter.GetCurrentValue()->characteristic = nullptr; |
+ } |
+ } |
+ |
+ if (found) { |
+ // A characteristic will never be in the set of active notifications |
+ // and in the queue at the same time. |
+ auto subscriptions_iter = active_notification_subscriptions_.find( |
+ characteristic_instance_id.utf8()); |
+ if (subscriptions_iter != active_notification_subscriptions_.end()) { |
+ DCHECK(!ContainsKey(subscriptions_iter->second, characteristic)); |
+ } |
+ return; |
+ } |
+ |
+ // If the object is not in the queue then: |
+ // 1. The subscription was inactive already: this characteristic |
+ // object didn't subscribe to notifications. |
+ // 2. The subscription will become inactive: the characteristic |
+ // object that subscribed to notifications is getting destroyed. |
+ // 3. The subscription will still be active: there are other |
+ // characteristic objects subscribed to notifications. |
+ |
+ if (!HasActiveNotificationSubscription(characteristic_instance_id.utf8())) { |
+ return; |
+ } |
+ |
+ // For 2 and 3 calling ResolveOrSendStopNotificationsRequest ensures the |
+ // notification subscription is released. |
+ // We pass in the characteristic so that ResolveOrSendStopNotificationsRequest |
+ // can remove the characteristic from ActiveNotificationSubscriptions. |
+ ResolveOrSendStopNotificationsRequest(QueueNotificationRequest( |
+ characteristic_instance_id.utf8(), characteristic, |
+ nullptr /* callbacks */, NotificationsRequestType::STOP)); |
+} |
+ |
void BluetoothDispatcher::WillStopCurrentWorkerThread() { |
delete this; |
} |
+int BluetoothDispatcher::QueueNotificationRequest( |
+ const std::string& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic, |
+ blink::WebBluetoothNotificationsCallbacks* callbacks, |
+ NotificationsRequestType type) { |
+ int request_id = |
+ pending_notifications_requests_.Add(new BluetoothNotificationsRequest( |
+ characteristic_instance_id, characteristic, callbacks, type)); |
+ notification_requests_queues_[characteristic_instance_id].push(request_id); |
+ |
+ return request_id; |
+} |
+ |
+void BluetoothDispatcher::PopNotificationRequestQueueAndProcessNext( |
+ int request_id) { |
+ BluetoothNotificationsRequest* old_request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ |
+ auto queue_iter = notification_requests_queues_.find( |
+ old_request->characteristic_instance_id); |
+ |
+ CHECK(queue_iter != notification_requests_queues_.end()); |
+ |
+ std::queue<int>& request_queue = queue_iter->second; |
+ |
+ DCHECK(request_queue.front() == request_id); |
+ |
+ // Remove old request and clean map if necessary. |
+ request_queue.pop(); |
+ pending_notifications_requests_.Remove(request_id); |
+ if (request_queue.empty()) { |
+ notification_requests_queues_.erase(queue_iter); |
+ return; |
+ } |
+ |
+ int next_request_id = request_queue.front(); |
+ BluetoothNotificationsRequest* next_request = |
+ pending_notifications_requests_.Lookup(next_request_id); |
+ |
+ switch (next_request->type) { |
+ case NotificationsRequestType::START: |
+ ResolveOrSendStartNotificationRequest(next_request_id); |
+ return; |
+ case NotificationsRequestType::STOP: |
+ ResolveOrSendStopNotificationsRequest(next_request_id); |
+ return; |
+ } |
+} |
+ |
+bool BluetoothDispatcher::HasNotificationRequestResponsePending( |
+ const std::string& characteristic_instance_id) { |
+ return ContainsKey(notification_requests_queues_, |
+ characteristic_instance_id) && |
+ (notification_requests_queues_[characteristic_instance_id].size() > 1); |
+} |
+ |
+bool BluetoothDispatcher::HasActiveNotificationSubscription( |
+ const std::string& characteristic_instance_id) { |
+ return ContainsKey(active_notification_subscriptions_, |
+ characteristic_instance_id); |
+} |
+ |
+void BluetoothDispatcher::AddToActiveNotificationSubscriptions( |
+ const std::string& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic) { |
+ active_notification_subscriptions_[characteristic_instance_id].insert( |
+ characteristic); |
+} |
+ |
+bool BluetoothDispatcher::RemoveFromActiveNotificationSubscriptions( |
+ const std::string& characteristic_instance_id, |
+ blink::WebBluetoothGATTCharacteristic* characteristic) { |
+ auto active_map = |
+ active_notification_subscriptions_.find(characteristic_instance_id); |
+ |
+ if (active_map == active_notification_subscriptions_.end()) { |
+ return false; |
+ } |
+ |
+ active_map->second.erase(characteristic); |
+ |
+ if (active_map->second.empty()) { |
+ active_notification_subscriptions_.erase(active_map); |
+ DCHECK(!HasActiveNotificationSubscription(characteristic_instance_id)); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void BluetoothDispatcher::ResolveOrSendStartNotificationRequest( |
+ int request_id) { |
+ BluetoothNotificationsRequest* request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ const std::string& characteristic_instance_id = |
+ request->characteristic_instance_id; |
+ blink::WebBluetoothGATTCharacteristic* characteristic = |
+ request->characteristic; |
+ blink::WebBluetoothNotificationsCallbacks* callbacks = |
+ request->callbacks.get(); |
+ |
+ // If an object is already subscribed to notifications from the characteristic |
+ // no need to send an IPC again. |
+ if (HasActiveNotificationSubscription(characteristic_instance_id)) { |
+ // The object could have been destroyed while we waited. |
+ if (characteristic != nullptr) { |
+ AddToActiveNotificationSubscriptions(characteristic_instance_id, |
+ characteristic); |
+ } |
+ callbacks->onSuccess(); |
+ |
+ PopNotificationRequestQueueAndProcessNext(request_id); |
+ return; |
+ } |
+ |
+ Send(new BluetoothHostMsg_StartNotifications(CurrentWorkerId(), request_id, |
+ characteristic_instance_id)); |
+} |
+ |
+void BluetoothDispatcher::ResolveOrSendStopNotificationsRequest( |
+ int request_id) { |
+ // The Notification subscription's state can change after a request |
+ // finishes. To avoid resolving with a soon-to-be-invalid state we queue |
+ // requests. |
+ BluetoothNotificationsRequest* request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ const std::string& characteristic_instance_id = |
+ request->characteristic_instance_id; |
+ blink::WebBluetoothGATTCharacteristic* characteristic = |
+ request->characteristic; |
+ blink::WebBluetoothNotificationsCallbacks* callbacks = |
+ request->callbacks.get(); |
+ |
+ // If removing turns the subscription inactive then stop. |
+ if (RemoveFromActiveNotificationSubscriptions(characteristic_instance_id, |
+ characteristic)) { |
+ Send(new BluetoothHostMsg_StopNotifications(CurrentWorkerId(), request_id, |
+ characteristic_instance_id)); |
+ return; |
+ } |
+ |
+ // We could be processing a request with nullptr callbacks due to: |
+ // 1) OnStartNotificationSuccess queues a Stop request because the object |
+ // got destroyed in characteristicObjectRemoved. |
+ // 2) The last characteristic object that held this subscription got |
+ // destroyed in characteristicObjectRemoved. |
+ if (callbacks != nullptr) { |
+ callbacks->onSuccess(); |
+ } |
+ PopNotificationRequestQueueAndProcessNext(request_id); |
+} |
+ |
void BluetoothDispatcher::OnRequestDeviceSuccess( |
int thread_id, |
int request_id, |
@@ -369,4 +640,80 @@ void BluetoothDispatcher::OnWriteValueError(int thread_id, |
pending_write_value_requests_.Remove(request_id); |
} |
+void BluetoothDispatcher::OnStartNotificationsSuccess(int thread_id, |
+ int request_id) { |
+ DCHECK(pending_notifications_requests_.Lookup(request_id)) << request_id; |
+ |
+ BluetoothNotificationsRequest* request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ |
+ DCHECK(notification_requests_queues_[request->characteristic_instance_id] |
+ .front() == request_id); |
+ |
+ // We only send an IPC for inactive notifications. |
+ DCHECK( |
+ !HasActiveNotificationSubscription(request->characteristic_instance_id)); |
+ |
+ AddToActiveNotificationSubscriptions(request->characteristic_instance_id, |
+ request->characteristic); |
+ |
+ // The object requesting the notification could have been destroyed |
+ // while waiting for the subscription. characteristicRemoved |
+ // nulls the characteristic when the corresponding js object gets destroyed. |
+ // A Stop request must be issued as the subscription is no longer needed. |
+ // The Stop requets is added to the end of the queue in case another |
+ // Start request exists in the queue from another characteristic object, |
+ // which would result in the subscription continuing. |
+ if (request->characteristic == nullptr) { |
+ QueueNotificationRequest( |
+ request->characteristic_instance_id, nullptr /* characteristic */, |
+ nullptr /* callbacks */, NotificationsRequestType::STOP); |
+ } |
+ |
+ request->callbacks->onSuccess(); |
+ |
+ PopNotificationRequestQueueAndProcessNext(request_id); |
+} |
+ |
+void BluetoothDispatcher::OnStartNotificationsError(int thread_id, |
+ int request_id, |
+ WebBluetoothError error) { |
+ DCHECK(pending_notifications_requests_.Lookup(request_id)) << request_id; |
+ |
+ BluetoothNotificationsRequest* request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ |
+ DCHECK(notification_requests_queues_[request->characteristic_instance_id] |
+ .front() == request_id); |
+ |
+ // We only send an IPC for inactive notifications. |
+ DCHECK( |
+ !HasActiveNotificationSubscription(request->characteristic_instance_id)); |
+ |
+ request->callbacks->onError(error); |
+ |
+ PopNotificationRequestQueueAndProcessNext(request_id); |
+} |
+ |
+void BluetoothDispatcher::OnStopNotificationsSuccess(int thread_id, |
+ int request_id) { |
+ DCHECK(pending_notifications_requests_.Lookup(request_id)) << request_id; |
+ |
+ BluetoothNotificationsRequest* request = |
+ pending_notifications_requests_.Lookup(request_id); |
+ |
+ DCHECK(notification_requests_queues_[request->characteristic_instance_id] |
+ .front() == request_id); |
+ |
+ // We only send an IPC for inactive notifications. |
+ DCHECK( |
+ !HasActiveNotificationSubscription(request->characteristic_instance_id)); |
+ |
+ if (request->callbacks != nullptr) { |
+ request->callbacks->onSuccess(); |
+ } |
+ |
+ PopNotificationRequestQueueAndProcessNext(request_id); |
+} |
+ |
} // namespace content |