Commit ababc28c authored by ortuno's avatar ortuno Committed by Commit bot

bluetooth: Reject getPrimaryService(s) if frame is not connected

First of three patches to reject promises if the frame is not connected
or the frame disconnected while the promise was not fulfilled.

Implements:

1. The ActiveAlgorithm set[1] as m_activeAlgorithms in BluetoothRemoteGATTServer
2. gattServer-connection-checking wrapper[2] as GetPrimaryServicesCallback in
   BluetoothRemoteGattServer.cpp
3. Connection check of query bluetooth cache[3] in getPrimaryServicesImpl.

[1] https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-activealgorithms-slot
[2] https://webbluetoothcg.github.io/web-bluetooth/#connection-checking-wrapper
[3] https://webbluetoothcg.github.io/web-bluetooth/#query-the-bluetooth-cache

BUG=608538

Review-Url: https://codereview.chromium.org/2183903002
Cr-Commit-Position: refs/heads/master@{#408729}
parent b20a6b27
...@@ -130,6 +130,13 @@ void NotifyServicesDiscovered(MockBluetoothAdapter* adapter, ...@@ -130,6 +130,13 @@ void NotifyServicesDiscovered(MockBluetoothAdapter* adapter,
GattServicesDiscovered(adapter, device)); GattServicesDiscovered(adapter, device));
} }
// Notifies the adapter's observers that a device has changed.
void NotifyDeviceChanged(MockBluetoothAdapter* adapter,
MockBluetoothDevice* device) {
FOR_EACH_OBSERVER(BluetoothAdapter::Observer, adapter->GetObservers(),
DeviceChanged(adapter, device));
}
} // namespace } // namespace
namespace content { namespace content {
...@@ -164,6 +171,11 @@ LayoutTestBluetoothAdapterProvider::GetBluetoothAdapter( ...@@ -164,6 +171,11 @@ LayoutTestBluetoothAdapterProvider::GetBluetoothAdapter(
return GetTwoHeartRateServicesAdapter(); return GetTwoHeartRateServicesAdapter();
if (fake_adapter_name == "DisconnectingHeartRateAdapter") if (fake_adapter_name == "DisconnectingHeartRateAdapter")
return GetDisconnectingHeartRateAdapter(); return GetDisconnectingHeartRateAdapter();
if (fake_adapter_name == "DisconnectingDuringServiceRetrievalAdapter")
return GetServicesDiscoveredAfterReconnectionAdapter(true /* disconnect */);
if (fake_adapter_name == "ServicesDiscoveredAfterReconnectionAdapter")
return GetServicesDiscoveredAfterReconnectionAdapter(
false /* disconnect */);
if (fake_adapter_name == "BlacklistTestAdapter") if (fake_adapter_name == "BlacklistTestAdapter")
return GetBlacklistTestAdapter(); return GetBlacklistTestAdapter();
if (fake_adapter_name == "FailingConnectionsAdapter") if (fake_adapter_name == "FailingConnectionsAdapter")
...@@ -540,6 +552,72 @@ LayoutTestBluetoothAdapterProvider::GetDisconnectingHeartRateAdapter() { ...@@ -540,6 +552,72 @@ LayoutTestBluetoothAdapterProvider::GetDisconnectingHeartRateAdapter() {
return adapter; return adapter;
} }
// static
scoped_refptr<NiceMockBluetoothAdapter> LayoutTestBluetoothAdapterProvider::
GetServicesDiscoveredAfterReconnectionAdapter(bool disconnect) {
scoped_refptr<NiceMockBluetoothAdapter> adapter(GetEmptyAdapter());
NiceMockBluetoothAdapter* adapter_ptr = adapter.get();
std::unique_ptr<NiceMockBluetoothDevice> device(
GetHeartRateDevice(adapter.get()));
NiceMockBluetoothDevice* device_ptr = device.get();
// When called before IsGattDiscoveryComplete, run success callback with a new
// Gatt connection. When called after after IsGattDiscoveryComplete runs
// success callback with a new Gatt connection and notifies of services
// discovered.
ON_CALL(*device, CreateGattConnection(_, _))
.WillByDefault(RunCallbackWithResult<0 /* success_callback */>(
[adapter_ptr, device_ptr]() {
std::vector<BluetoothRemoteGattService*> services =
device_ptr->GetMockServices();
if (services.size() != 0) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&NotifyServicesDiscovered,
base::RetainedRef(adapter_ptr), device_ptr));
}
return base::MakeUnique<NiceMockBluetoothGattConnection>(
adapter_ptr, device_ptr->GetAddress());
}));
// The first time this function is called we:
// 1. Add a service (This indicates that this function has been called)
// 2. If |disconnect| is true, disconnect the device.
// 3. Return false.
// The second time this function is called we just return true.
ON_CALL(*device, IsGattServicesDiscoveryComplete())
.WillByDefault(Invoke([adapter_ptr, device_ptr, disconnect] {
std::vector<BluetoothRemoteGattService*> services =
device_ptr->GetMockServices();
if (services.size() == 0) {
std::unique_ptr<NiceMockBluetoothGattService> heart_rate(
GetBaseGATTService("Heart Rate", device_ptr,
kHeartRateServiceUUID));
device_ptr->AddMockService(GetGenericAccessService(device_ptr));
device_ptr->AddMockService(
GetHeartRateService(adapter_ptr, device_ptr));
if (disconnect) {
device_ptr->SetConnected(false);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&NotifyDeviceChanged, base::RetainedRef(adapter_ptr),
device_ptr));
}
DCHECK(services.size() == 0);
return false;
}
return true;
}));
adapter->AddMockDevice(std::move(device));
return adapter;
}
// static // static
scoped_refptr<NiceMockBluetoothAdapter> scoped_refptr<NiceMockBluetoothAdapter>
LayoutTestBluetoothAdapterProvider::GetBlacklistTestAdapter() { LayoutTestBluetoothAdapterProvider::GetBlacklistTestAdapter() {
......
...@@ -217,6 +217,29 @@ class LayoutTestBluetoothAdapterProvider { ...@@ -217,6 +217,29 @@ class LayoutTestBluetoothAdapterProvider {
static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
GetDisconnectingHeartRateAdapter(); GetDisconnectingHeartRateAdapter();
// |ServicesDiscoveredAfterReconnectionAdapter|(disconnect)
// Inherits from |HeartRateAdapter|
// Internal Structure:
// - Heart Rate Device
// - UUIDs:
// - Generic Access UUID (0x1800)
// - Heart Rate UUID (0x180d)
// - Services:
// - Generic Access Service - Characteristics as described in
// GetGenericAccessService.
// - Heart Rate Service - Characteristics as described in
// GetHeartRateService.
// - CreateGattConnection: When called before IsGattDiscoveryComplete,
// runs success callback with a new Gatt connection. When called
// after IsGattDiscoveryComplete runs success callback with a new
// Gatt connection and notifies of services discovered.
// - IsGattDiscoveryComplete: The first time this function is called,
// it adds two services (Generic Access and Heart Rate) and
// if |disconnect| is true disconnects the device and returns false.
// After that it just returns true.
static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
GetServicesDiscoveredAfterReconnectionAdapter(bool disconnect);
// |BlacklistTestAdapter| // |BlacklistTestAdapter|
// Inherits from |EmptyAdapter| // Inherits from |EmptyAdapter|
// Internal Structure: // Internal Structure:
......
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => requestDisconnection.writeValue(new Uint8Array([0])))
.then(() => assert_promise_rejects_with_message(
device.gatt.getPrimaryService('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
});
}, 'Device disconnects before getPrimaryService. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
device.gatt.getPrimaryService('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
});
});
}, 'Device disconnects during getPrimaryService. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('DisconnectingDuringServiceRetrievalAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}
))
.then(device => device.gatt.connect())
.then(gatt => {
let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
let promise = assert_promise_rejects_with_message(
gatt.getPrimaryService('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
return disconnected.then(() => gatt.connect()).then(() => promise);
});
}, 'Device disconnects during getPrimaryService. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
gattServer.disconnect();
return assert_promise_rejects_with_message(
gattServer.getPrimaryService('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError'));
});
}, 'disconnect() called before getPrimaryService. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryService('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
}, 'disconnect() called during getPrimaryService. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => assert_promise_rejects_with_message(
device.gatt.getPrimaryService('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
}, 'getPrimaryService() called before connecting. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('ServicesDiscoveredAfterReconnectionAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryService('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return gattServer.connect().then(() => promise);
});
}, 'disconnect() and connect() called during getPrimaryService. Reject with ' +
'NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => requestDisconnection.writeValue(new Uint8Array([0])))
.then(() => assert_promise_rejects_with_message(
device.gatt.getPrimaryServices('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
});
}, 'Device disconnects before getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => requestDisconnection.writeValue(new Uint8Array([0])))
.then(() => assert_promise_rejects_with_message(
device.gatt.getPrimaryServices(),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
});
}, 'Device disconnects before getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
device.gatt.getPrimaryServices('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
});
});
}, 'Device disconnects during getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('DisconnectingHeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: [request_disconnection_service_uuid]
}))
.then(device => {
return device.gatt.connect()
.then(gattServer => gattServer.getPrimaryService(request_disconnection_service_uuid))
.then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
.then(requestDisconnection => {
requestDisconnection.writeValue(new Uint8Array([0]));
return assert_promise_rejects_with_message(
device.gatt.getPrimaryServices(),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
});
});
}, 'Device disconnects during getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('DisconnectingDuringServiceRetrievalAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}
))
.then(device => device.gatt.connect())
.then(gatt => {
let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
let promise = assert_promise_rejects_with_message(
gatt.getPrimaryServices(),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
return disconnected.then(() => gatt.connect()).then(() => promise);
});
}, 'Device disconnects and we reconnect during getPrimaryServices. Reject ' +
'with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('DisconnectingDuringServiceRetrievalAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gatt => {
let disconnected = eventPromise(gatt.device, 'gattserverdisconnected');
let promise = assert_promise_rejects_with_message(
gatt.getPrimaryServices('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
return disconnected.then(() => gatt.connect()).then(() => promise);
});
}, 'Device disconnects and we reconnect during getPrimaryServices. Reject ' +
'with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('TwoHeartRateServicesAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: ['generic_access']}))
.then(device => device.gatt.connect())
.then(gattServer => {
gattServer.disconnect();
return assert_promise_rejects_with_message(
gattServer.getPrimaryServices('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError'));
});
}, 'disconnect() called before getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('TwoHeartRateServicesAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: ['generic_access']}))
.then(device => device.gatt.connect())
.then(gattServer => {
gattServer.disconnect();
return assert_promise_rejects_with_message(
gattServer.getPrimaryServices(),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError'));
});
}, 'disconnect() called before getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: ['generic_access']}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryServices('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
}, 'disconnect() called during getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('TwoHeartRateServicesAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}],
optionalServices: ['generic_access']}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryServices(),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return promise;
});
}, 'disconnect() called during getPrimaryServices. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => assert_promise_rejects_with_message(
device.gatt.getPrimaryServices('heart_rate'),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
}, 'getPrimaryServices called before connecting. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('HeartRateAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => assert_promise_rejects_with_message(
device.gatt.getPrimaryServices(),
new DOMException('GATT Server is disconnected. Cannot retrieve services.',
'NetworkError')));
}, 'getPrimaryServices() called before connecting. Reject with NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('ServicesDiscoveredAfterReconnectionAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryServices('heart_rate'),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return gattServer.connect().then(() => promise);
});
}, 'disconnect() and connect() called during getPrimaryServices. Reject with ' +
'NetworkError.');
</script>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('ServicesDiscoveredAfterReconnectionAdapter')
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => device.gatt.connect())
.then(gattServer => {
let promise = assert_promise_rejects_with_message(
gattServer.getPrimaryServices(),
new DOMException('GATT Server disconnected while retrieving services.',
'NetworkError'));
gattServer.disconnect();
return gattServer.connect().then(() => promise);
});
}, 'disconnect() and connect() called during getPrimaryServices. Reject with ' +
'NetworkError.');
</script>
...@@ -48,6 +48,7 @@ bool BluetoothDevice::disconnectGATTIfConnected() ...@@ -48,6 +48,7 @@ bool BluetoothDevice::disconnectGATTIfConnected()
{ {
if (m_gatt->connected()) { if (m_gatt->connected()) {
m_gatt->setConnected(false); m_gatt->setConnected(false);
m_gatt->ClearActiveAlgorithms();
BluetoothSupplement::fromExecutionContext(getExecutionContext())->disconnect(id()); BluetoothSupplement::fromExecutionContext(getExecutionContext())->disconnect(id());
return true; return true;
} }
...@@ -68,6 +69,7 @@ void BluetoothDevice::dispatchGattServerDisconnected() ...@@ -68,6 +69,7 @@ void BluetoothDevice::dispatchGattServerDisconnected()
{ {
if (m_gatt->connected()) { if (m_gatt->connected()) {
m_gatt->setConnected(false); m_gatt->setConnected(false);
m_gatt->ClearActiveAlgorithms();
dispatchEvent(Event::createBubble(EventTypeNames::gattserverdisconnected)); dispatchEvent(Event::createBubble(EventTypeNames::gattserverdisconnected));
} }
} }
......
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
namespace blink { namespace blink {
namespace {
const char kGATTServerDisconnected[] = "GATT Server disconnected while retrieving services.";
const char kGATTServerNotConnected[] = "GATT Server is disconnected. Cannot retrieve services.";
}
BluetoothRemoteGATTServer::BluetoothRemoteGATTServer(BluetoothDevice* device) BluetoothRemoteGATTServer::BluetoothRemoteGATTServer(BluetoothDevice* device)
: m_device(device) : m_device(device)
, m_connected(false) , m_connected(false)
...@@ -29,8 +36,24 @@ BluetoothRemoteGATTServer* BluetoothRemoteGATTServer::create(BluetoothDevice* de ...@@ -29,8 +36,24 @@ BluetoothRemoteGATTServer* BluetoothRemoteGATTServer::create(BluetoothDevice* de
return new BluetoothRemoteGATTServer(device); return new BluetoothRemoteGATTServer(device);
} }
void BluetoothRemoteGATTServer::AddToActiveAlgorithms(ScriptPromiseResolver* resolver)
{
auto result = m_activeAlgorithms.add(resolver);
CHECK(result.isNewEntry);
}
bool BluetoothRemoteGATTServer::RemoveFromActiveAlgorithms(ScriptPromiseResolver* resolver)
{
if (!m_activeAlgorithms.contains(resolver)) {
return false;
}
m_activeAlgorithms.remove(resolver);
return true;
}
DEFINE_TRACE(BluetoothRemoteGATTServer) DEFINE_TRACE(BluetoothRemoteGATTServer)
{ {
visitor->trace(m_activeAlgorithms);
visitor->trace(m_device); visitor->trace(m_device);
} }
...@@ -76,6 +99,7 @@ void BluetoothRemoteGATTServer::disconnect(ScriptState* scriptState) ...@@ -76,6 +99,7 @@ void BluetoothRemoteGATTServer::disconnect(ScriptState* scriptState)
if (!m_connected) if (!m_connected)
return; return;
m_connected = false; m_connected = false;
ClearActiveAlgorithms();
WebBluetooth* webbluetooth = BluetoothSupplement::fromScriptState(scriptState); WebBluetooth* webbluetooth = BluetoothSupplement::fromScriptState(scriptState);
webbluetooth->disconnect(device()->id()); webbluetooth->disconnect(device()->id());
device()->dispatchEvent(Event::createBubble(EventTypeNames::gattserverdisconnected)); device()->dispatchEvent(Event::createBubble(EventTypeNames::gattserverdisconnected));
...@@ -88,13 +112,26 @@ public: ...@@ -88,13 +112,26 @@ public:
GetPrimaryServicesCallback(BluetoothDevice* device, mojom::blink::WebBluetoothGATTQueryQuantity quantity, ScriptPromiseResolver* resolver) GetPrimaryServicesCallback(BluetoothDevice* device, mojom::blink::WebBluetoothGATTQueryQuantity quantity, ScriptPromiseResolver* resolver)
: m_device(device) : m_device(device)
, m_quantity(quantity) , m_quantity(quantity)
, m_resolver(resolver) {} , m_resolver(resolver)
{
// We always check that the device is connected before constructing this
// object.
CHECK(m_device->gatt()->connected());
m_device->gatt()->AddToActiveAlgorithms(m_resolver.get());
}
void onSuccess(const WebVector<WebBluetoothRemoteGATTService*>& webServices) override void onSuccess(const WebVector<WebBluetoothRemoteGATTService*>& webServices) override
{ {
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped()) if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return; return;
// If the resolver is not in the set of ActiveAlgorithms then the frame
// disconnected so we reject.
if (!m_device->gatt()->RemoveFromActiveAlgorithms(m_resolver.get())) {
m_resolver->reject(DOMException::create(NetworkError, kGATTServerDisconnected));
return;
}
if (m_quantity == mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE) { if (m_quantity == mojom::blink::WebBluetoothGATTQueryQuantity::SINGLE) {
DCHECK_EQ(1u, webServices.size()); DCHECK_EQ(1u, webServices.size());
m_resolver->resolve(BluetoothRemoteGATTService::take(m_resolver, wrapUnique(webServices[0]), m_device)); m_resolver->resolve(BluetoothRemoteGATTService::take(m_resolver, wrapUnique(webServices[0]), m_device));
...@@ -113,6 +150,8 @@ public: ...@@ -113,6 +150,8 @@ public:
{ {
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped()) if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return; return;
m_device->gatt()->RemoveFromActiveAlgorithms(m_resolver.get());
m_resolver->reject(BluetoothError::take(m_resolver, error)); m_resolver->reject(BluetoothError::take(m_resolver, error));
} }
private: private:
...@@ -146,6 +185,10 @@ ScriptPromise BluetoothRemoteGATTServer::getPrimaryServices(ScriptState* scriptS ...@@ -146,6 +185,10 @@ ScriptPromise BluetoothRemoteGATTServer::getPrimaryServices(ScriptState* scriptS
ScriptPromise BluetoothRemoteGATTServer::getPrimaryServicesImpl(ScriptState* scriptState, mojom::blink::WebBluetoothGATTQueryQuantity quantity, String servicesUUID) ScriptPromise BluetoothRemoteGATTServer::getPrimaryServicesImpl(ScriptState* scriptState, mojom::blink::WebBluetoothGATTQueryQuantity quantity, String servicesUUID)
{ {
if (!connected()) {
return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NetworkError, kGATTServerNotConnected));
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise(); ScriptPromise promise = resolver->promise();
......
...@@ -31,6 +31,16 @@ public: ...@@ -31,6 +31,16 @@ public:
void setConnected(bool connected) { m_connected = connected; } void setConnected(bool connected) { m_connected = connected; }
// Adds |resolver| to the set of Active Algorithms. CHECK-fails if
// |resolver| was already added.
void AddToActiveAlgorithms(ScriptPromiseResolver*);
// Returns false if |resolver| was not in the set of Active Algorithms.
// Otherwise it removes |resolver| from the set of Active Algorithms and
// returns true.
bool RemoveFromActiveAlgorithms(ScriptPromiseResolver*);
// Removes all ScriptPromiseResolvers from the set of Active Algorithms.
void ClearActiveAlgorithms() { m_activeAlgorithms.clear(); }
// Interface required by Garbage Collectoin: // Interface required by Garbage Collectoin:
DECLARE_VIRTUAL_TRACE(); DECLARE_VIRTUAL_TRACE();
...@@ -46,6 +56,12 @@ public: ...@@ -46,6 +56,12 @@ public:
private: private:
ScriptPromise getPrimaryServicesImpl(ScriptState*, mojom::blink::WebBluetoothGATTQueryQuantity, String serviceUUID = String()); ScriptPromise getPrimaryServicesImpl(ScriptState*, mojom::blink::WebBluetoothGATTQueryQuantity, String serviceUUID = String());
// Contains a ScriptPromiseResolver corresponding to each algorithm using
// this server’s connection. Disconnection i.e. disconnect() method or the
// device disconnecting by itself, empties this set so that the algorithm
// can tell whether its realm was ever disconnected while it was running.
HeapHashSet<Member<ScriptPromiseResolver>> m_activeAlgorithms;
Member<BluetoothDevice> m_device; Member<BluetoothDevice> m_device;
bool m_connected; bool m_connected;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment