Commit 2751bda5 authored by palakj's avatar palakj Committed by Commit bot

[IndexedDB] Propogating changes to observers

* Changes received from browser are multiplexed and passed on to each observer.
* Each observer extracts the required subset of observations from bulk observations of the connnection using its array of indices.
* The callback is fired with a map of object store to array of observations.

Current Implementation and Future Scope: http://goo.gl/r0eUpe

Reference: https://github.com/dmurph/indexed-db-observers/blob/gh-pages/EXPLAINER.md

BUG=609934

Review-Url: https://codereview.chromium.org/2125213002
Cr-Commit-Position: refs/heads/master@{#406722}
parent 605bf287
......@@ -9,7 +9,7 @@ namespace content {
IndexedDBObserver::Options::Options(bool include_transaction,
bool no_records,
bool values,
unsigned short types)
uint16_t types)
: include_transaction(include_transaction),
no_records(no_records),
values(values),
......
......@@ -31,8 +31,7 @@ class CONTENT_EXPORT IndexedDBObserver {
bool include_transaction;
bool no_records;
bool values;
// Each bit in operation type corresponds to blink::WebIDBOperationType
// values.
// Operation type bits are set corresponding to WebIDBOperationType.
std::bitset<blink::WebIDBOperationTypeCount> operation_types;
};
IndexedDBObserver(int32_t observer_id,
......
......@@ -19,6 +19,7 @@
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseCallbacks.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseError.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseException.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBObservation.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBValue.h"
using blink::WebBlobInfo;
......@@ -30,6 +31,7 @@ using blink::WebIDBDatabaseCallbacks;
using blink::WebIDBDatabaseError;
using blink::WebIDBKey;
using blink::WebIDBMetadata;
using blink::WebIDBObservation;
using blink::WebIDBObserver;
using blink::WebIDBValue;
using blink::WebString;
......@@ -125,6 +127,21 @@ WebIDBMetadata IndexedDBDispatcher::ConvertMetadata(
return web_metadata;
}
std::vector<WebIDBObservation> IndexedDBDispatcher::ConvertObservations(
const std::vector<IndexedDBMsg_Observation>& idb_observations) {
std::vector<WebIDBObservation> web_observations;
for (const auto& idb_observation : idb_observations) {
WebIDBObservation web_observation;
web_observation.objectStoreId = idb_observation.object_store_id;
web_observation.type = idb_observation.type;
web_observation.keyRange =
WebIDBKeyRangeBuilder::Build(idb_observation.key_range);
// TODO(palakj): Assign value to web_observation.
web_observations.push_back(std::move(web_observation));
}
return web_observations;
}
void IndexedDBDispatcher::OnMessageReceived(const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(IndexedDBDispatcher, msg)
......@@ -156,6 +173,8 @@ void IndexedDBDispatcher::OnMessageReceived(const IPC::Message& msg) {
OnVersionChange)
IPC_MESSAGE_HANDLER(IndexedDBMsg_DatabaseCallbacksAbort, OnAbort)
IPC_MESSAGE_HANDLER(IndexedDBMsg_DatabaseCallbacksComplete, OnComplete)
IPC_MESSAGE_HANDLER(IndexedDBMsg_DatabaseCallbacksChanges,
OnDatabaseChanges)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
// If a message gets here, IndexedDBMessageFilter already determined that it
......@@ -172,9 +191,15 @@ int32_t IndexedDBDispatcher::AddIDBObserver(
int32_t ipc_database_id,
int64_t transaction_id,
std::unique_ptr<WebIDBObserver> observer) {
int32_t observer_id = observers_.Add(observer.release());
IndexedDBHostMsg_DatabaseObserve_Params params;
// TODO(palakj): Other params are assigned values as a part of next cl.
static_assert(blink::WebIDBOperationTypeCount < sizeof(uint16_t) * CHAR_BIT,
"WebIDBOperationType Count exceeds size of uint16_t");
params.operation_types =
static_cast<uint16_t>(observer->operationTypes().to_ulong());
params.include_transaction = observer->transaction();
params.no_records = observer->noRecords();
params.values = observer->values();
int32_t observer_id = observers_.Add(observer.release());
params.ipc_database_id = ipc_database_id;
params.transaction_id = transaction_id;
params.observer_id = observer_id;
......@@ -801,6 +826,24 @@ void IndexedDBDispatcher::OnComplete(int32_t ipc_thread_id,
callbacks->onComplete(transaction_id);
}
void IndexedDBDispatcher::OnDatabaseChanges(
int32_t ipc_thread_id,
int32_t ipc_database_id,
const IndexedDBMsg_ObserverChanges& changes) {
DCHECK_EQ(ipc_thread_id, CurrentWorkerId());
std::vector<WebIDBObservation> observations(
ConvertObservations(changes.observations));
for (auto& it : changes.observation_index) {
WebIDBObserver* observer = observers_.Lookup(it.first);
// An observer can be removed from the renderer, but still exist in the
// backend. Moreover, observer might have recorded some changes before being
// removed from the backend and thus, have its id be present in changes.
if (!observer)
continue;
observer->onChange(observations, std::move(it.second));
}
}
void IndexedDBDispatcher::OnForcedClose(int32_t ipc_thread_id,
int32_t ipc_database_callbacks_id) {
DCHECK_EQ(ipc_thread_id, CurrentWorkerId());
......
......@@ -35,9 +35,12 @@ struct IndexedDBMsg_CallbacksSuccessIDBCursor_Params;
struct IndexedDBMsg_CallbacksSuccessArray_Params;
struct IndexedDBMsg_CallbacksSuccessValue_Params;
struct IndexedDBMsg_CallbacksUpgradeNeeded_Params;
struct IndexedDBMsg_Observation;
struct IndexedDBMsg_ObserverChanges;
namespace blink {
class WebData;
struct WebIDBObservation;
}
namespace content {
......@@ -69,6 +72,8 @@ class CONTENT_EXPORT IndexedDBDispatcher : public WorkerThread::Observer {
static blink::WebIDBMetadata ConvertMetadata(
const IndexedDBDatabaseMetadata& idb_metadata);
static std::vector<blink::WebIDBObservation> ConvertObservations(
const std::vector<IndexedDBMsg_Observation>& idb_observation);
void OnMessageReceived(const IPC::Message& msg);
......@@ -257,6 +262,10 @@ class CONTENT_EXPORT IndexedDBDispatcher : public WorkerThread::Observer {
void OnComplete(int32_t ipc_thread_id,
int32_t ipc_database_id,
int64_t transaction_id);
void OnDatabaseChanges(int32_t ipc_thread_id,
int32_t ipc_database_id,
const IndexedDBMsg_ObserverChanges&);
void OnForcedClose(int32_t ipc_thread_id, int32_t ipc_database_id);
void OnVersionChange(int32_t ipc_thread_id,
int32_t ipc_database_id,
......
......@@ -108,6 +108,13 @@ IndexedDBKeyRange IndexedDBKeyRangeBuilder::Build(
key_range.upperOpen());
}
WebIDBKeyRange WebIDBKeyRangeBuilder::Build(
const IndexedDBKeyRange& key_range) {
return WebIDBKeyRange(WebIDBKeyBuilder::Build(key_range.lower()),
WebIDBKeyBuilder::Build(key_range.upper()),
key_range.lower_open(), key_range.upper_open());
}
IndexedDBKeyPath IndexedDBKeyPathBuilder::Build(
const blink::WebIDBKeyPath& key_path) {
switch (key_path.keyPathType()) {
......
......@@ -44,6 +44,14 @@ class CONTENT_EXPORT IndexedDBKeyRangeBuilder {
DISALLOW_COPY_AND_ASSIGN(IndexedDBKeyRangeBuilder);
};
class CONTENT_EXPORT WebIDBKeyRangeBuilder {
public:
static blink::WebIDBKeyRange Build(const content::IndexedDBKeyRange& key);
private:
DISALLOW_COPY_AND_ASSIGN(WebIDBKeyRangeBuilder);
};
class CONTENT_EXPORT IndexedDBKeyPathBuilder {
public:
static IndexedDBKeyPath Build(const blink::WebIDBKeyPath& key_path);
......
......@@ -115,19 +115,18 @@ int32_t WebIDBDatabaseImpl::addObserver(
return observer_id;
}
bool WebIDBDatabaseImpl::containsObserverId(int32_t id) const {
return ContainsValue(observer_ids_, id);
}
void WebIDBDatabaseImpl::removeObservers(
const std::vector<int32_t>& observer_ids_to_remove) {
const WebVector<int32_t>& observer_ids_to_remove) {
std::vector<int32_t> remove_observer_ids(
observer_ids_to_remove.data(),
observer_ids_to_remove.data() + observer_ids_to_remove.size());
for (int32_t id : observer_ids_to_remove)
observer_ids_.erase(id);
IndexedDBDispatcher* dispatcher =
IndexedDBDispatcher::ThreadSpecificInstance(thread_safe_sender_.get());
dispatcher->RemoveIDBObserversFromDatabase(ipc_database_id_,
observer_ids_to_remove);
remove_observer_ids);
}
void WebIDBDatabaseImpl::get(long long transaction_id,
......
......@@ -7,6 +7,8 @@
#include <stdint.h>
#include <set>
#include "base/memory/ref_counted.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBCursor.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabase.h"
......@@ -48,9 +50,8 @@ class WebIDBDatabaseImpl : public blink::WebIDBDatabase {
int32_t addObserver(std::unique_ptr<blink::WebIDBObserver>,
long long transactionId) override;
bool containsObserverId(int32_t id) const override;
void removeObservers(
const std::vector<int32_t>& observer_ids_to_remove) override;
const blink::WebVector<int32_t>& observer_ids_to_remove) override;
void get(long long transactionId,
long long objectStoreId,
......
......@@ -194,7 +194,7 @@ IPC_STRUCT_BEGIN(IndexedDBHostMsg_DatabaseObserve_Params)
IPC_STRUCT_MEMBER(bool, include_transaction)
IPC_STRUCT_MEMBER(bool, no_records)
IPC_STRUCT_MEMBER(bool, values)
IPC_STRUCT_MEMBER(unsigned short, operation_types)
IPC_STRUCT_MEMBER(uint16_t, operation_types)
IPC_STRUCT_END()
// Used to set a value in an object store.
......
......@@ -2,6 +2,7 @@
<title>IndexedDB: Observer Worker Tests</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/generic-idb-operations.js"></script>
<script>
fetch_tests_from_worker(
new Worker('resources/observer.js'));
......
......@@ -3,4 +3,5 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/observer.js"></script>
<script src="resources/generic-idb-operations.js"></script>
</html>
if (this.importScripts) {
importScripts('../../../resources/testharness.js');
}
function compareChanges(actual, expected) {
assert_equals(actual.database.name, expected.dbName, 'The change record database should be the same as the database being acted on');
assert_equals(actual.records.size, expected.records.size, 'Incorrect number of objectStores recorded by observer');
for (var key in expected.records) {
assert_true(actual.records.has(key));
var actual_observation = actual.records.get(key);
var expected_observation = expected.records[key];
assert_equals(actual_observation.length, expected_observation.length, 'Number of observations recorded for objectStore '+key+ ' should match observed operations');
for (var i in expected_observation)
compareObservations(actual_observation[i], expected_observation[i]);
}
}
function compareObservations(actual, expected) {
assert_equals(actual.type, expected.type);
if (actual.type == 'clear') {
assert_equals(actual.key, undefined, 'clear operation has no key');
assert_equals(actual.value, null, 'clear operation has no value');
return;
}
// TODO(palakj): Type should return 'delete' instead of 'kDelete', once fixed. Issue crbug.com/609934.
if (actual.type == 'kDelete') {
assert_equals(actual.key.lower, expected.key.lower, 'Observed operation key lower bound should match operation performed');
assert_equals(actual.key.upper, expected.key.upper, 'Observed operation key upper bound should match operation performed');
assert_equals(actual.key.lower_open, expected.key.lower_open, 'Observed operation key lower open should match operation performed');
assert_equals(actual.key.upper_open, expected.key.upper_open, 'Observed operation key upper open should match operation performed');
// TODO(palakj): Value needs to be updated, once returned correctly. Issue crbug.com/609934.
assert_equals(actual.value, null, 'Delete operation has no value');
return;
}
assert_equals(actual.key.lower, expected.key, 'Observed operation key lower bound should match operation performed');
assert_equals(actual.key.upper, expected.key, 'Observed operation key upper bound should match operation performed');
// TODO(palakj): Value needs to be updated, once returned correctly. Issue crbug.com/609934.
assert_equals(actual.value, null, 'Put/Add operation value should be nil');
}
function countCallbacks(actual, expected) {
assert_equals(actual, expected, 'Number of callbacks fired for observer should match number of transactions it observed')
}
function createDatabase(db, stores) {
for (var i in stores)
db.createObjectStore(stores[i]);
}
function operateOnStore(store, operations) {
for (var i in operations ) {
var op = operations[i];
assert_in_array(op.type, ['put', 'add', 'delete', 'clear', 'get'], 'Operation type not defined');
if (op.type == 'put')
store.put(op.value, op.key);
else if (op.type == 'add')
store.add(op.value, op.key);
else if (op.type == 'delete')
store.delete(IDBKeyRange.bound(op.key.lower, op.key.upper));
else if (op.type == 'clear')
store.clear();
else
store.get(op.key)
}
}
if (this.importScripts) {
importScripts('../../../resources/testharness.js');
importScripts('generic-idb-operations.js');
}
function callback(){};
async_test(function(t) {
var dbname = location.pathname + ' - ' + 'empty transaction';
var openRequest = indexedDB.open(dbname);
var callback_count = 0;
var obs = new IDBObserver(t.step_func(function() { callback_count++; }), {operationTypes: ['put']});
openRequest.onupgradeneeded = t.step_func(function() {
createDatabase(openRequest.result, ['store']);
});
openRequest.onsuccess = t.step_func(function() {
var db = openRequest.result;
var tx1 = db.transaction('store', 'readwrite');
var tx2 = db.transaction('store', 'readwrite');
obs.observe(db, tx1);
tx2.objectStore('store').put(1, 1);
tx1.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 0);
});
tx2.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 1);
t.done();
});
tx1.onerror = t.unreached_func('transaction should not fail');
tx2.onerror = t.unreached_func('transaction should not fail');
});
}, 'Registering observe call with empty transaction');
async_test(function(t) {
var description = 'observer addition and removal test';
var dbname = location.pathname + ' - ' + description;
var dbname = location.pathname + ' - ' + 'observer in version change';
var openRequest = indexedDB.open(dbname);
var obs1 = new IDBObserver(callback, {transaction: true, values: true});
var obs2 = new IDBObserver(callback, {transaction: true, values: true});
var callback_count = 0;
var obs;
openRequest.onupgradeneeded = t.step_func(function() {
createDatabase(openRequest.result, ['store']);
obs = new IDBObserver(t.step_func(function(changes) { callback_count++; }), { operationTypes: ['put'] });
});
openRequest.onsuccess = t.step_func(function() {
var db = openRequest.result;
var tx1 = db.transaction('store', 'readwrite');
var tx2 = db.transaction('store', 'readwrite');
tx1.objectStore('store').get(1);
tx2.objectStore('store').put(1, 1);
obs.observe(db, tx1);
tx1.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 0);
});
tx2.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 1);
t.done();
});
tx1.onerror = t.unreached_func('transaction should not fail');
tx2.onerror = t.unreached_func('transaction should not fail');
});
}, 'Create IDBObserver during version change');
async_test(function(t) {
var dbname = location.pathname + ' - ' + 'ignore observe call';
var openRequest = indexedDB.open(dbname);
var callback_count = 0;
var obs = new IDBObserver(t.step_func(function() { callback_count++; }), { operationTypes: ['put'] });
openRequest.onupgradeneeded = t.step_func(function() {
var db = openRequest.result;
db.createObjectStore('store');
obs.observe(db, openRequest.transaction);
});
openRequest.onsuccess = t.step_func(function() {
var db = openRequest.result;
var tx = db.transaction('store', 'readwrite');
var store = tx.objectStore('store');
var put_request = store.put(1,1);
obs1.observe(db, tx);
obs1.unobserve(db);
obs1.observe(db, tx);
obs2.observe(db, tx);
tx.oncomplete = t.step_func(function(){
obs1.unobserve(db);
t.done();
});
});
}, 'observer addition and removal test');
done();
tx.objectStore('store').put(1, 1);
tx.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 0);
t.done();
});
tx.onerror = t.unreached_func('transaction should not fail');
});
}, 'Observe call during version change ignored');
async_test(function(t) {
var dbname = location.pathname + ' - ' + 'abort associated transaction';
var openRequest = indexedDB.open(dbname);
var callback_count = 0;
var obs = new IDBObserver(t.step_func(function() { callback_count++; }), { operationTypes: ['put'] });
openRequest.onupgradeneeded = t.step_func(function() {
createDatabase(openRequest.result, ['store']);
});
openRequest.onsuccess = t.step_func(function() {
var db = openRequest.result;
var tx1 = db.transaction('store', 'readwrite');
var tx2 = db.transaction('store', 'readwrite');
tx1.objectStore('store').get(1);
tx2.objectStore('store').put(1, 1);
obs.observe(db, tx1);
tx1.abort();
tx1.onabort = t.step_func(function(){
countCallbacks(callback_count, 0);
});
tx1.oncomplete = t.unreached_func('transaction should not complete');
tx2.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 0);
t.done();
});
tx2.onerror = t.unreached_func('transaction error should not fail');
});
}, 'Abort transaction associated with observer');
async_test(function(t) {
var dbname = location.pathname + ' - ' + 'abort transaction';
var openRequest = indexedDB.open(dbname);
var callback_count = 0;
var obs = new IDBObserver(t.step_func(function() { callback_count++; }), { operationTypes: ['put'] });
openRequest.onupgradeneeded = t.step_func(function() {
createDatabase(openRequest.result, ['store']);
});
openRequest.onsuccess = t.step_func(function() {
var db = openRequest.result;
var tx1 = db.transaction('store', 'readwrite');
var tx2 = db.transaction('store', 'readwrite');
var tx3 = db.transaction('store', 'readwrite');
tx1.objectStore('store').get(1);
tx2.objectStore('store').put(1, 1);
tx3.objectStore('store').put(1, 1);
obs.observe(db, tx1);
tx2.abort();
tx1.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 0);
});
tx2.oncomplete = t.unreached_func('transaction should not complete');
tx2.onabort = t.step_func(function() {
countCallbacks(callback_count, 0);
});
tx3.oncomplete = t.step_func(function() {
countCallbacks(callback_count, 1);
t.done();
});
tx1.onerror = t.unreached_func('transaction should not fail');
tx3.onerror = t.unreached_func('transaction should not fail');
});
}, 'Abort transaction recorded by observer');
done();
\ No newline at end of file
......@@ -5,9 +5,12 @@
#include "bindings/modules/v8/V8IDBObserverCallback.h"
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/ToV8.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8PrivateProperty.h"
#include "bindings/modules/v8/V8IDBObserver.h"
#include "bindings/modules/v8/V8IDBObserverChanges.h"
#include "wtf/Assertions.h"
namespace blink {
......@@ -24,6 +27,37 @@ V8IDBObserverCallback::~V8IDBObserverCallback()
{
}
void V8IDBObserverCallback::handleChanges(IDBObserverChanges& changes, IDBObserver& observer)
{
if (!canInvokeCallback())
return;
if (!m_scriptState->contextIsValid())
return;
ScriptState::Scope scope(m_scriptState.get());
if (m_callback.isEmpty())
return;
v8::Local<v8::Value> observerHandle = toV8(&observer, m_scriptState->context()->Global(), m_scriptState->isolate());
if (observerHandle.IsEmpty()) {
if (!isScriptControllerTerminating())
CRASH();
return;
}
if (!observerHandle->IsObject())
return;
v8::Local<v8::Object> thisObject = v8::Local<v8::Object>::Cast(observerHandle);
v8::Local<v8::Value> changesHandle = toV8(&changes, m_scriptState->context()->Global(), m_scriptState->isolate());
if (changesHandle.IsEmpty())
return;
v8::Local<v8::Value> argv[] = { changesHandle };
V8ScriptRunner::callFunction(m_callback.newLocal(m_scriptState->isolate()), m_scriptState->getExecutionContext(), thisObject, WTF_ARRAY_LENGTH(argv), argv, m_scriptState->isolate());
}
DEFINE_TRACE(V8IDBObserverCallback)
{
IDBObserverCallback::trace(visitor);
......
......@@ -21,6 +21,7 @@ public:
DECLARE_VIRTUAL_TRACE();
void handleChanges(IDBObserverChanges&, IDBObserver&) override;
ExecutionContext* getExecutionContext() const override { return ContextLifecycleObserver::getExecutionContext(); }
private:
ScopedPersistent<v8::Function> m_callback;
......
......@@ -170,6 +170,13 @@ DOMStringList* IDBDatabase::objectStoreNames() const
return objectStoreNames;
}
const String& IDBDatabase::getObjectStoreName(int64_t objectStoreId) const
{
const auto& it = m_metadata.objectStores.find(objectStoreId);
DCHECK(it != m_metadata.objectStores.end());
return it->value.name;
}
IDBObjectStore* IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionState& exceptionState)
{
IDB_TRACE("IDBDatabase::createObjectStore");
......
......@@ -69,6 +69,7 @@ public:
void indexDeleted(int64_t objectStoreId, int64_t indexId);
void transactionCreated(IDBTransaction*);
void transactionFinished(const IDBTransaction*);
const String& getObjectStoreName(int64_t objectStoreId) const;
// Implement the IDL
const String& name() const { return m_metadata.name; }
......
......@@ -11,8 +11,9 @@
#include "bindings/modules/v8/V8BindingForModules.h"
#include "modules/IndexedDBNames.h"
#include "modules/indexeddb/IDBAny.h"
#include "modules/indexeddb/IDBKey.h"
#include "modules/indexeddb/IDBKeyRange.h"
#include "modules/indexeddb/IDBValue.h"
#include "public/platform/modules/indexeddb/WebIDBObservation.h"
namespace blink {
......@@ -20,19 +21,18 @@ IDBObservation::~IDBObservation() {}
ScriptValue IDBObservation::key(ScriptState* scriptState)
{
return ScriptValue::from(scriptState, m_key);
if (!m_keyRange)
return ScriptValue::from(scriptState, v8::Undefined(scriptState->isolate()));
return ScriptValue::from(scriptState, m_keyRange);
}
ScriptValue IDBObservation::value(ScriptState* scriptState)
{
IDBAny* value;
if (!m_value) {
value = IDBAny::createUndefined();
} else {
value = IDBAny::create(m_value);
}
ScriptValue scriptValue = ScriptValue::from(scriptState, value);
return scriptValue;
if (!m_value)
return ScriptValue::from(scriptState, v8::Undefined(scriptState->isolate()));
return ScriptValue::from(scriptState, IDBAny::create(m_value));
}
WebIDBOperationType IDBObservation::stringToOperationType(const String& type)
......@@ -71,21 +71,21 @@ const String& IDBObservation::type() const
}
}
IDBObservation* IDBObservation::create(IDBKey* key, PassRefPtr<IDBValue> value, WebIDBOperationType type)
IDBObservation* IDBObservation::create(const WebIDBObservation& observation)
{
return new IDBObservation(key, value, type);
return new IDBObservation(observation);
}
IDBObservation::IDBObservation(IDBKey* key, PassRefPtr<IDBValue> value, WebIDBOperationType type)
: m_key(key)
, m_value(value)
, m_operationType(type)
IDBObservation::IDBObservation(const WebIDBObservation& observation)
: m_keyRange(observation.keyRange)
, m_value(IDBValue::create(observation.value))
, m_operationType(observation.type)
{
}
DEFINE_TRACE(IDBObservation)
{
visitor->trace(m_key);
visitor->trace(m_keyRange);
}
} // namespace blink
......@@ -13,16 +13,17 @@
namespace blink {
class IDBKey;
class IDBKeyRange;
class IDBValue;
class ScriptState;
struct WebIDBObservation;
class IDBObservation final : public GarbageCollectedFinalized<IDBObservation>, public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static WebIDBOperationType stringToOperationType(const String&);
static IDBObservation* create(IDBKey*, PassRefPtr<IDBValue>, WebIDBOperationType);
static IDBObservation* create(const WebIDBObservation&);
~IDBObservation();
DECLARE_TRACE();
......@@ -33,8 +34,8 @@ public:
const String& type() const;
private:
IDBObservation(IDBKey*, PassRefPtr<IDBValue>, WebIDBOperationType);
Member<IDBKey> m_key;
IDBObservation(const WebIDBObservation&);
Member<IDBKeyRange> m_keyRange;
RefPtr<IDBValue> m_value;
const WebIDBOperationType m_operationType;
};
......
......@@ -7,6 +7,7 @@
enum IDBObservationType {
"add",
"put",
// TODO(palakj): Operation type being returned as kDelete instead of delete.
"delete",
"clear"
};
......
......@@ -8,8 +8,10 @@
#include "bindings/modules/v8/ToV8ForModules.h"
#include "bindings/modules/v8/V8BindingForModules.h"
#include "core/dom/ExceptionCode.h"
#include "modules/IndexedDBNames.h"
#include "modules/indexeddb/IDBDatabase.h"
#include "modules/indexeddb/IDBObserverCallback.h"
#include "modules/indexeddb/IDBObserverChanges.h"
#include "modules/indexeddb/IDBObserverInit.h"
#include "modules/indexeddb/IDBTransaction.h"
#include "modules/indexeddb/WebIDBObserverImpl.h"
......@@ -27,6 +29,13 @@ IDBObserver::IDBObserver(IDBObserverCallback& callback, const IDBObserverInit& o
, m_values(options.values())
, m_noRecords(options.noRecords())
{
// TODO(palakj): Throw an exception if unknown operation type.
DCHECK_EQ(m_operationTypes.size(), static_cast<size_t>(WebIDBOperationTypeCount));
m_operationTypes.reset();
m_operationTypes[WebIDBAdd] = options.operationTypes().contains(IndexedDBNames::add);
m_operationTypes[WebIDBPut] = options.operationTypes().contains(IndexedDBNames::put);
m_operationTypes[WebIDBDelete] = options.operationTypes().contains(IndexedDBNames::kDelete);
m_operationTypes[WebIDBClear] = options.operationTypes().contains(IndexedDBNames::clear);
}
IDBObserver::~IDBObserver() {}
......@@ -49,7 +58,7 @@ void IDBObserver::observe(IDBDatabase* database, IDBTransaction* transaction, Ex
std::unique_ptr<WebIDBObserverImpl> observer = WebIDBObserverImpl::create(this);
WebIDBObserverImpl* observerPtr = observer.get();
int32_t observerId = database->backend()->addObserver(std::move(observer), transaction->id());
m_observerIds.insert(observerId);
m_observerIds.add(observerId, database);
observerPtr->setId(observerId);
}
......@@ -59,28 +68,34 @@ void IDBObserver::unobserve(IDBDatabase* database, ExceptionState& exceptionStat
exceptionState.throwDOMException(InvalidStateError, IDBDatabase::databaseClosedErrorMessage);
return;
}
auto it = m_observerIds.begin();
std::vector<int32_t> observerIdsToRemove;
while (it != m_observerIds.end()) {
if (database->backend()->containsObserverId(*it)) {
observerIdsToRemove.push_back(*it);
it = m_observerIds.erase(it);
} else {
it++;
}
Vector<int32_t> observerIdsToRemove;
for (const auto& it : m_observerIds) {
if (it.value == database)
observerIdsToRemove.append(it.key);
}
if (!observerIdsToRemove.empty())
m_observerIds.removeAll(observerIdsToRemove);
if (!observerIdsToRemove.isEmpty())
database->backend()->removeObservers(observerIdsToRemove);
}
void IDBObserver::removeObserver(int32_t id)
{
m_observerIds.erase(id);
m_observerIds.remove(id);
}
void IDBObserver::onChange(int32_t id, const WebVector<WebIDBObservation>& observations, const WebVector<int32_t>& observationIndex)
{
auto it = m_observerIds.find(id);
DCHECK(it != m_observerIds.end());
m_callback->handleChanges(*IDBObserverChanges::create(it->value, observations, observationIndex), *this);
}
DEFINE_TRACE(IDBObserver)
{
visitor->trace(m_callback);
visitor->trace(m_observerIds);
}
} // namespace blink
......@@ -8,7 +8,9 @@
#include "bindings/core/v8/ScriptWrappable.h"
#include "modules/ModulesExport.h"
#include "platform/heap/Handle.h"
#include <set>
#include "public/platform/WebVector.h"
#include "public/platform/modules/indexeddb/WebIDBTypes.h"
#include <bitset>
namespace blink {
......@@ -17,6 +19,7 @@ class IDBDatabase;
class IDBObserverCallback;
class IDBObserverInit;
class IDBTransaction;
struct WebIDBObservation;
class MODULES_EXPORT IDBObserver final : public GarbageCollectedFinalized<IDBObserver>, public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
......@@ -25,10 +28,18 @@ public:
static IDBObserver* create(IDBObserverCallback&, const IDBObserverInit&);
~IDBObserver();
// API methods
void removeObserver(int32_t id);
void onChange(int32_t id, const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex);
bool transaction() const { return m_transaction; }
bool noRecords() const { return m_noRecords; }
bool values() const { return m_values; }
const std::bitset<WebIDBOperationTypeCount>& operationTypes() const { return m_operationTypes; }
// Implement the IDBObserver IDL.
void observe(IDBDatabase*, IDBTransaction*, ExceptionState&);
void unobserve(IDBDatabase*, ExceptionState&);
void removeObserver(int32_t id);
DECLARE_TRACE();
......@@ -39,7 +50,9 @@ private:
bool m_transaction;
bool m_values;
bool m_noRecords;
std::set<int32_t> m_observerIds;
// Operation type bits are set corresponding to WebIDBOperationType.
std::bitset<WebIDBOperationTypeCount> m_operationTypes;
HeapHashMap<int32_t, WeakMember<IDBDatabase>> m_observerIds;
};
} // namespace blink
......
......@@ -11,10 +11,12 @@ namespace blink {
class ExecutionContext;
class IDBObserver;
class IDBObserverChanges;
class IDBObserverCallback : public GarbageCollectedFinalized<IDBObserverCallback> {
public:
virtual ~IDBObserverCallback() {}
virtual void handleChanges(IDBObserverChanges&, IDBObserver&) = 0;
virtual ExecutionContext* getExecutionContext() const = 0;
DEFINE_INLINE_VIRTUAL_TRACE() {}
};
......
......@@ -6,27 +6,44 @@
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/modules/v8/ToV8ForModules.h"
#include "bindings/modules/v8/V8BindingForModules.h"
#include "modules/indexeddb/IDBAny.h"
#include "modules/indexeddb/IDBObservation.h"
#include "public/platform/modules/indexeddb/WebIDBObservation.h"
namespace blink {
ScriptValue IDBObserverChanges::records(ScriptState* scriptState)
{
return ScriptValue::from(scriptState, m_records);
v8::Local<v8::Context> context(scriptState->context());
v8::Isolate* isolate(scriptState->isolate());
v8::Local<v8::Map> map = v8::Map::New(isolate);
for (const auto& it : m_records) {
v8::Local<v8::String> key = v8String(isolate, m_database->getObjectStoreName(it.key));
v8::Local<v8::Value> value = toV8(it.value, context->Global(), isolate);
v8CallOrCrash(map->Set(context, key, value));
}
return ScriptValue::from(scriptState, map);
}
IDBObserverChanges* IDBObserverChanges::create(IDBDatabase* database, IDBTransaction* transaction, IDBAny* records)
IDBObserverChanges* IDBObserverChanges::create(IDBDatabase* database, const WebVector<WebIDBObservation>& observations, const WebVector<int32_t>& observationIndex)
{
return new IDBObserverChanges(database, transaction, records);
return new IDBObserverChanges(database, observations, observationIndex);
}
IDBObserverChanges::IDBObserverChanges(IDBDatabase* database, IDBTransaction* transaction, IDBAny* records)
IDBObserverChanges::IDBObserverChanges(IDBDatabase* database, const WebVector<WebIDBObservation>& observations, const WebVector<int32_t>& observationIndex)
: m_database(database)
, m_transaction(transaction)
, m_records(records)
{
extractChanges(observations, observationIndex);
}
void IDBObserverChanges::extractChanges(const WebVector<WebIDBObservation>& observations, const WebVector<int32_t>& observationIndex)
{
// TODO(dmurph): Avoid getting and setting repeated times.
for (const auto& idx : observationIndex)
m_records.add(observations[idx].objectStoreId, HeapVector<Member<IDBObservation>>()).storedValue->value.append(IDBObservation::create(observations[idx]));
}
DEFINE_TRACE(IDBObserverChanges)
......
......@@ -8,19 +8,20 @@
#include "bindings/core/v8/ScriptValue.h"
#include "bindings/core/v8/ScriptWrappable.h"
#include "modules/indexeddb/IDBDatabase.h"
#include "modules/indexeddb/IDBObservation.h"
#include "modules/indexeddb/IDBTransaction.h"
#include "platform/heap/Handle.h"
#include "public/platform/WebVector.h"
namespace blink {
class ScriptState;
class IDBObserverChangesRecord;
class IDBObserverChanges final : public GarbageCollected<IDBObserverChanges>, public ScriptWrappable {
class IDBObserverChanges final : public GarbageCollectedFinalized<IDBObserverChanges>, public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static IDBObserverChanges* create(IDBDatabase*, IDBTransaction*, IDBAny* records);
static IDBObserverChanges* create(IDBDatabase*, const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex);
DECLARE_TRACE();
......@@ -30,12 +31,14 @@ public:
ScriptValue records(ScriptState*);
private:
IDBObserverChanges(IDBDatabase*, IDBTransaction*, IDBAny* records);
IDBObserverChanges(IDBDatabase*, const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex);
void extractChanges(const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex);
Member<IDBDatabase> m_database;
Member<IDBTransaction> m_transaction;
// TODO(palakj) : change to appropriate type Map<String, sequence<IDBObserverChangesRecord>>.
Member<IDBAny> m_records;
// Map objectStoreId to IDBObservation list.
HeapHashMap<int64_t, HeapVector<Member<IDBObservation>>> m_records;
};
} // namespace blink
......
......@@ -11,9 +11,7 @@
readonly attribute IDBDatabase database;
// Transaction contains the same object stores as the transaction on which IDBTransaction.observe was called.
readonly attribute IDBTransaction transaction;
// This is the javascript Map<String, sequence<IDBObserverChangesRecord>>,
// where the key is the object store name.
// TODO(palakj) : Replace 'any' with maplike<DOMString, sequence<IDBObserverChangesRecord>>.
// Map object from String (object store name) to Array of IDBObservation.
[CallWith=ScriptState] readonly attribute any records;
};
......
......@@ -10,4 +10,5 @@
boolean transaction = false;
boolean values = false;
boolean noRecords = false;
sequence<IDBObservationType> operationTypes = [];
};
......@@ -34,7 +34,7 @@ public:
// Gmock does not support movable type, so cannot use MOCK_METHOD for addObserver. Issue: https://github.com/google/googletest/issues/395.
int32_t addObserver(std::unique_ptr<WebIDBObserver>, long long transactionId) { return -1; }
MOCK_CONST_METHOD1(containsObserverId, bool(int32_t id));
MOCK_METHOD1(removeObservers, void(const std::vector<int32_t>& observerIdsToRemove));
MOCK_METHOD1(removeObservers, void(const WebVector<int32_t>& observerIdsToRemove));
MOCK_METHOD6(get, void(long long transactionId, long long objectStoreId, long long indexId, const WebIDBKeyRange&, bool keyOnly, WebIDBCallbacks*));
MOCK_METHOD7(getAll, void(long long transactionId, long long objectStoreId, long long indexId, const WebIDBKeyRange&, long long maxCount, bool keyOnly, WebIDBCallbacks*));
MOCK_METHOD9(put, void(long long transactionId, long long objectStoreId, const WebData& value, const WebVector<WebBlobInfo>&, const WebIDBKey&, WebIDBPutMode, WebIDBCallbacks*, const WebVector<long long>& indexIds, const WebVector<WebIndexKeys>&));
......
......@@ -4,7 +4,6 @@
#include "modules/indexeddb/WebIDBObserverImpl.h"
#include "modules/indexeddb/IDBObserver.h"
#include "wtf/PtrUtil.h"
namespace blink {
......@@ -34,4 +33,9 @@ void WebIDBObserverImpl::setId(int32_t id)
m_id = id;
}
void WebIDBObserverImpl::onChange(const WebVector<WebIDBObservation>& observations, const WebVector<int32_t>& observationIndex)
{
m_observer->onChange(m_id, observations, std::move(observationIndex));
}
} // namespace blink
......@@ -5,12 +5,14 @@
#ifndef WebIDBObserverImpl_h
#define WebIDBObserverImpl_h
#include "modules/indexeddb/IDBObserver.h"
#include "platform/heap/Persistent.h"
#include "public/platform/modules/indexeddb/WebIDBObserver.h"
namespace blink {
class IDBObserver;
struct WebIDBObservation;
class WebIDBObserverImpl final : public WebIDBObserver {
USING_FAST_MALLOC(WebIDBObserverImpl);
......@@ -22,6 +24,12 @@ public:
void setId(int32_t);
bool transaction() const { return m_observer->transaction(); }
bool noRecords() const { return m_observer->noRecords(); }
bool values() const { return m_observer->values(); }
const std::bitset<WebIDBOperationTypeCount>& operationTypes() const { return m_observer->operationTypes(); }
void onChange(const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex);
private:
enum { kInvalidObserverId = -1 };
......
......@@ -152,6 +152,7 @@
"platform/WebPrerender.h",
"platform/WebPrerenderingSupport.h",
"platform/WebPrescientNetworking.h",
"platform/WebPrivateOwnPtr.h",
"platform/WebPrivatePtr.h",
"platform/WebPublicSuffixList.h",
"platform/WebRTCCertificate.h",
......@@ -231,6 +232,7 @@
"platform/modules/background_sync/WebSyncRegistration.h",
"platform/modules/bluetooth/WebBluetooth.h",
"platform/modules/bluetooth/WebBluetoothDevice.h",
"platform/modules/bluetooth/WebBluetoothError.h",
"platform/modules/bluetooth/WebBluetoothRemoteGATTCharacteristic.h",
"platform/modules/bluetooth/WebBluetoothRemoteGATTCharacteristicInit.h",
"platform/modules/bluetooth/WebBluetoothRemoteGATTService.h",
......@@ -250,6 +252,7 @@
"platform/modules/indexeddb/WebIDBKeyPath.h",
"platform/modules/indexeddb/WebIDBKeyRange.h",
"platform/modules/indexeddb/WebIDBMetadata.h",
"platform/modules/indexeddb/WebIDBObservation.h",
"platform/modules/indexeddb/WebIDBObserver.h",
"platform/modules/indexeddb/WebIDBTypes.h",
"platform/modules/indexeddb/WebIDBValue.h",
......
......@@ -32,8 +32,6 @@
#include "public/platform/modules/indexeddb/WebIDBMetadata.h"
#include "public/platform/modules/indexeddb/WebIDBObserver.h"
#include "public/platform/modules/indexeddb/WebIDBTypes.h"
#include <set>
#include <vector>
namespace blink {
......@@ -66,8 +64,7 @@ public:
typedef WebVector<WebIDBKey> WebIndexKeys;
virtual int32_t addObserver(std::unique_ptr<WebIDBObserver>, long long transactionId) = 0;
virtual bool containsObserverId(int32_t id) const = 0;
virtual void removeObservers(const std::vector<int32_t>& observerIdsToRemove) = 0;
virtual void removeObservers(const WebVector<int32_t>& observerIdsToRemove) = 0;
virtual void get(long long transactionId, long long objectStoreId, long long indexId, const WebIDBKeyRange&, bool keyOnly, WebIDBCallbacks*) = 0;
virtual void getAll(long long transactionId, long long objectStoreId, long long indexId, const WebIDBKeyRange&, long long maxCount, bool keyOnly, WebIDBCallbacks*) = 0;
virtual void put(long long transactionId, long long objectStoreId, const WebData& value, const WebVector<WebBlobInfo>&, const WebIDBKey&, WebIDBPutMode, WebIDBCallbacks*, const WebVector<long long>& indexIds, const WebVector<WebIndexKeys>&) = 0;
......
......@@ -38,6 +38,7 @@ class WebIDBKeyRange {
public:
~WebIDBKeyRange() { reset(); }
WebIDBKeyRange() {}
WebIDBKeyRange(const WebIDBKeyRange& keyRange) { assign(keyRange); }
WebIDBKeyRange(const WebIDBKey& lower, const WebIDBKey& upper, bool lowerOpen, bool upperOpen) { assign(lower, upper, lowerOpen, upperOpen); }
......@@ -48,6 +49,13 @@ public:
BLINK_EXPORT void assign(const WebIDBKeyRange&);
BLINK_EXPORT void assign(const WebIDBKey& lower, const WebIDBKey& upper, bool lowerOpen, bool upperOpen);
WebIDBKeyRange& operator=(const WebIDBKeyRange& e)
{
assign(e);
return *this;
}
// FIXME: when compiling core or modules, use inline for reset.
// when compiling WebIDBKeyRange.cpp, don't use inline to avoid redefinition.
#if !BLINK_WEB_IMPLEMENTATION && BLINK_IMPLEMENTATION && defined(COMPONENT_BUILD)
......
// Copyright 2016 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.
#ifndef WebIDBObservation_h
#define WebIDBObservation_h
#include "public/platform/modules/indexeddb/WebIDBKeyRange.h"
#include "public/platform/modules/indexeddb/WebIDBTypes.h"
#include "public/platform/modules/indexeddb/WebIDBValue.h"
namespace blink {
struct WebIDBObservation {
int64_t objectStoreId;
WebIDBOperationType type;
WebIDBKeyRange keyRange;
WebIDBValue value;
};
} // namespace blink
#endif // WebIDBObservation_h
......@@ -7,11 +7,23 @@
#ifndef WebIDBObserver_h
#define WebIDBObserver_h
#include "public/platform/WebVector.h"
#include "public/platform/modules/indexeddb/WebIDBTypes.h"
#include <bitset>
namespace blink {
struct WebIDBObservation;
class WebIDBObserver {
public:
virtual ~WebIDBObserver() {}
virtual bool transaction() const = 0;
virtual bool noRecords() const = 0;
virtual bool values() const = 0;
virtual const std::bitset<WebIDBOperationTypeCount>& operationTypes() const = 0;
virtual void onChange(const WebVector<WebIDBObservation>&, const WebVector<int32_t>& observationIndex) = 0;
};
} // namespace blink
......
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