Commit ddf9d5a8 authored by jsbell@chromium.org's avatar jsbell@chromium.org

IndexedDB: Have IDBCursor and IDBRequest explicitly break ref cycles

Until (1) a transaction ends or (2) a cursor hits the end of its
range, an IDBRequest holds on to an IDBCursor as its result. Per spec,
calling continue() or advance() on the cursor re-uses the same
IDBRequest, requiring a reference cycle.

Previously, the cycle was broken explicitly on either of those two
conditions, but until that time a cursor-request pair would "leak",
holding on to potentially large script value results.

This patch makes both classes RefCountedBase::deref() and check
to see if they have a partner object and both refcounts are 1. If
so, the cycle is broken.

Special case cruft for condition #1 is removed to simplify the
code - just rely on GC to reclaim the objects if necessary.

R=alecflett@chromium.org,dgrogan@chromium.org
BUG=225860

Review URL: https://chromiumcodereview.appspot.com/23653024

git-svn-id: svn://svn.chromium.org/blink/trunk@157382 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent d77994ff
Verify that that cursors weakly hold request, and work if request is GC'd
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
dbname = "cursor-request-cycle.html"
indexedDB.deleteDatabase(dbname)
indexedDB.open(dbname)
prepareDatabase():
db = event.target.result
store = db.createObjectStore('store')
onOpen():
db = event.target.result
tx = db.transaction('store')
store = tx.objectStore('store')
cursorRequest = store.openCursor()
otherRequest = store.get(0)
openCursorRequest():
cursor = cursorRequest.result
PASS cursor is non-null.
PASS cursor.key is "key1"
PASS cursor.value is "value1"
otherRequestSuccess():
PASS afterCount is beforeCount
cursor.continue()
finalRequest = store.get(0)
finalRequestSuccess():
PASS cursor.key is "key2"
PASS cursor.value is "value2"
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<script src="../../fast/js/resources/js-test-pre.js"></script>
<script src="resources/shared.js"></script>
<script>
description("Verify that that cursors weakly hold request, and work if request is GC'd");
indexedDBTest(prepareDatabase, onOpen);
function prepareDatabase(evt)
{
preamble(evt);
evalAndLog("db = event.target.result");
evalAndLog("store = db.createObjectStore('store')");
store.put("value1", "key1");
store.put("value2", "key2");
}
function onOpen(evt)
{
preamble(evt);
evalAndLog("db = event.target.result");
evalAndLog("tx = db.transaction('store')");
evalAndLog("store = tx.objectStore('store')");
evalAndLog("cursorRequest = store.openCursor()");
cursorRequest.onsuccess = function openCursorRequest(evt) {
preamble(evt);
evalAndLog("cursor = cursorRequest.result");
shouldBeNonNull("cursor");
shouldBeEqualToString("cursor.key", "key1");
shouldBeEqualToString("cursor.value", "value1");
};
evalAndLog("otherRequest = store.get(0)");
otherRequest.onsuccess = function otherRequestSuccess(evt) {
preamble(evt);
gc();
gc(); // FIXME: Why is this second call necessary?
beforeCount = window.internals.numberOfLiveNodes();
cursorRequest.canary = document.createElement('canary');
cursorRequest = null;
gc();
gc(); // FIXME: Why is this second call necessary?
afterCount = window.internals.numberOfLiveNodes();
shouldBe("afterCount", "beforeCount");
// The following call should generate a scratch request, invisible to script:
evalAndLog("cursor.continue()");
evalAndLog("finalRequest = store.get(0)");
finalRequest.onsuccess = function finalRequestSuccess(evt) {
preamble(evt);
shouldBeEqualToString("cursor.key", "key2");
shouldBeEqualToString("cursor.value", "value2");
};
};
tx.oncomplete = finishJSTest;
}
</script>
<script src="../../fast/js/resources/js-test-post.js"></script>
...@@ -62,52 +62,52 @@ IDBAny::~IDBAny() ...@@ -62,52 +62,52 @@ IDBAny::~IDBAny()
{ {
} }
PassRefPtr<DOMStringList> IDBAny::domStringList() DOMStringList* IDBAny::domStringList()
{ {
ASSERT(m_type == DOMStringListType); ASSERT(m_type == DOMStringListType);
return m_domStringList; return m_domStringList.get();
} }
PassRefPtr<IDBCursor> IDBAny::idbCursor() IDBCursor* IDBAny::idbCursor()
{ {
ASSERT(m_type == IDBCursorType); ASSERT(m_type == IDBCursorType);
return m_idbCursor; return m_idbCursor.get();
} }
PassRefPtr<IDBCursorWithValue> IDBAny::idbCursorWithValue() IDBCursorWithValue* IDBAny::idbCursorWithValue()
{ {
ASSERT(m_type == IDBCursorWithValueType); ASSERT(m_type == IDBCursorWithValueType);
return m_idbCursorWithValue; return m_idbCursorWithValue.get();
} }
PassRefPtr<IDBDatabase> IDBAny::idbDatabase() IDBDatabase* IDBAny::idbDatabase()
{ {
ASSERT(m_type == IDBDatabaseType); ASSERT(m_type == IDBDatabaseType);
return m_idbDatabase; return m_idbDatabase.get();
} }
PassRefPtr<IDBFactory> IDBAny::idbFactory() IDBFactory* IDBAny::idbFactory()
{ {
ASSERT(m_type == IDBFactoryType); ASSERT(m_type == IDBFactoryType);
return m_idbFactory; return m_idbFactory.get();
} }
PassRefPtr<IDBIndex> IDBAny::idbIndex() IDBIndex* IDBAny::idbIndex()
{ {
ASSERT(m_type == IDBIndexType); ASSERT(m_type == IDBIndexType);
return m_idbIndex; return m_idbIndex.get();
} }
PassRefPtr<IDBObjectStore> IDBAny::idbObjectStore() IDBObjectStore* IDBAny::idbObjectStore()
{ {
ASSERT(m_type == IDBObjectStoreType); ASSERT(m_type == IDBObjectStoreType);
return m_idbObjectStore; return m_idbObjectStore.get();
} }
PassRefPtr<IDBTransaction> IDBAny::idbTransaction() IDBTransaction* IDBAny::idbTransaction()
{ {
ASSERT(m_type == IDBTransactionType); ASSERT(m_type == IDBTransactionType);
return m_idbTransaction; return m_idbTransaction.get();
} }
const ScriptValue& IDBAny::scriptValue() const ScriptValue& IDBAny::scriptValue()
......
...@@ -91,14 +91,14 @@ public: ...@@ -91,14 +91,14 @@ public:
Type type() const { return m_type; } Type type() const { return m_type; }
// Use type() to figure out which one of these you're allowed to call. // Use type() to figure out which one of these you're allowed to call.
PassRefPtr<DOMStringList> domStringList(); DOMStringList* domStringList();
PassRefPtr<IDBCursor> idbCursor(); IDBCursor* idbCursor();
PassRefPtr<IDBCursorWithValue> idbCursorWithValue(); IDBCursorWithValue* idbCursorWithValue();
PassRefPtr<IDBDatabase> idbDatabase(); IDBDatabase* idbDatabase();
PassRefPtr<IDBFactory> idbFactory(); IDBFactory* idbFactory();
PassRefPtr<IDBIndex> idbIndex(); IDBIndex* idbIndex();
PassRefPtr<IDBObjectStore> idbObjectStore(); IDBObjectStore* idbObjectStore();
PassRefPtr<IDBTransaction> idbTransaction(); IDBTransaction* idbTransaction();
const ScriptValue& scriptValue(); const ScriptValue& scriptValue();
int64_t integer(); int64_t integer();
const String& string(); const String& string();
......
...@@ -41,9 +41,11 @@ namespace WebCore { ...@@ -41,9 +41,11 @@ namespace WebCore {
class DOMError; class DOMError;
class IDBCursorBackendInterface; class IDBCursorBackendInterface;
class IDBCallbacks : public RefCounted<IDBCallbacks> { class IDBCallbacks : public WTF::RefCountedBase {
public: public:
virtual ~IDBCallbacks() { } virtual ~IDBCallbacks() { }
virtual void deref() = 0;
virtual void onError(PassRefPtr<DOMError>) = 0; virtual void onError(PassRefPtr<DOMError>) = 0;
// From IDBFactory.webkitGetDatabaseNames() // From IDBFactory.webkitGetDatabaseNames()
......
...@@ -80,7 +80,6 @@ IDBCursor::IDBCursor(PassRefPtr<IDBCursorBackendInterface> backend, IndexedDB::C ...@@ -80,7 +80,6 @@ IDBCursor::IDBCursor(PassRefPtr<IDBCursorBackendInterface> backend, IndexedDB::C
, m_direction(direction) , m_direction(direction)
, m_source(source) , m_source(source)
, m_transaction(transaction) , m_transaction(transaction)
, m_transactionNotifier(transaction, this)
, m_gotValue(false) , m_gotValue(false)
, m_keyDirty(true) , m_keyDirty(true)
, m_primaryKeyDirty(true) , m_primaryKeyDirty(true)
...@@ -274,14 +273,23 @@ void IDBCursor::close() ...@@ -274,14 +273,23 @@ void IDBCursor::close()
{ {
// The notifier may be the last reference to this cursor. // The notifier may be the last reference to this cursor.
RefPtr<IDBCursor> protect(this); RefPtr<IDBCursor> protect(this);
m_transactionNotifier.cursorFinished(); m_request.clear();
if (m_request) {
m_request->finishCursor();
m_request.clear();
}
m_backend.clear(); m_backend.clear();
} }
void IDBCursor::checkForReferenceCycle()
{
// If this cursor and its request have the only references
// to each other, then explicitly break the cycle.
if (!m_request || m_request->getResultCursor() != this)
return;
if (!hasOneRef() || !m_request->hasOneRef())
return;
m_request.clear();
}
ScriptValue IDBCursor::key(ScriptExecutionContext* context) ScriptValue IDBCursor::key(ScriptExecutionContext* context)
{ {
m_keyDirty = false; m_keyDirty = false;
......
...@@ -46,7 +46,7 @@ class IDBRequest; ...@@ -46,7 +46,7 @@ class IDBRequest;
class ScriptExecutionContext; class ScriptExecutionContext;
class SharedBuffer; class SharedBuffer;
class IDBCursor : public ScriptWrappable, public RefCounted<IDBCursor> { class IDBCursor : public ScriptWrappable, public WTF::RefCountedBase {
public: public:
static const AtomicString& directionNext(); static const AtomicString& directionNext();
static const AtomicString& directionNextUnique(); static const AtomicString& directionNextUnique();
...@@ -80,6 +80,15 @@ public: ...@@ -80,6 +80,15 @@ public:
void close(); void close();
void setValueReady(PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> value); void setValueReady(PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> value);
PassRefPtr<IDBKey> idbPrimaryKey() { return m_primaryKey; } PassRefPtr<IDBKey> idbPrimaryKey() { return m_primaryKey; }
IDBRequest* request() { return m_request.get(); }
void deref()
{
if (derefBase())
delete this;
else if (hasOneRef())
checkForReferenceCycle();
}
protected: protected:
IDBCursor(PassRefPtr<IDBCursorBackendInterface>, IndexedDB::CursorDirection, IDBRequest*, IDBAny* source, IDBTransaction*); IDBCursor(PassRefPtr<IDBCursorBackendInterface>, IndexedDB::CursorDirection, IDBRequest*, IDBAny* source, IDBTransaction*);
...@@ -88,6 +97,7 @@ protected: ...@@ -88,6 +97,7 @@ protected:
private: private:
PassRefPtr<IDBObjectStore> effectiveObjectStore(); PassRefPtr<IDBObjectStore> effectiveObjectStore();
void checkForReferenceCycle();
bool isDeleted() const; bool isDeleted() const;
RefPtr<IDBCursorBackendInterface> m_backend; RefPtr<IDBCursorBackendInterface> m_backend;
...@@ -95,7 +105,6 @@ private: ...@@ -95,7 +105,6 @@ private:
const IndexedDB::CursorDirection m_direction; const IndexedDB::CursorDirection m_direction;
RefPtr<IDBAny> m_source; RefPtr<IDBAny> m_source;
RefPtr<IDBTransaction> m_transaction; RefPtr<IDBTransaction> m_transaction;
IDBTransaction::OpenCursorNotifier m_transactionNotifier;
bool m_gotValue; bool m_gotValue;
bool m_keyDirty; bool m_keyDirty;
bool m_primaryKeyDirty; bool m_primaryKeyDirty;
......
...@@ -79,7 +79,6 @@ IDBRequest::IDBRequest(ScriptExecutionContext* context, PassRefPtr<IDBAny> sourc ...@@ -79,7 +79,6 @@ IDBRequest::IDBRequest(ScriptExecutionContext* context, PassRefPtr<IDBAny> sourc
, m_hasPendingActivity(true) , m_hasPendingActivity(true)
, m_cursorType(IndexedDB::CursorKeyAndValue) , m_cursorType(IndexedDB::CursorKeyAndValue)
, m_cursorDirection(IndexedDB::CursorNext) , m_cursorDirection(IndexedDB::CursorNext)
, m_cursorFinished(false)
, m_pendingCursor(0) , m_pendingCursor(0)
, m_didFireUpgradeNeededEvent(false) , m_didFireUpgradeNeededEvent(false)
, m_preventPropagation(false) , m_preventPropagation(false)
...@@ -184,6 +183,7 @@ void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor) ...@@ -184,6 +183,7 @@ void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor)
ASSERT(!m_pendingCursor); ASSERT(!m_pendingCursor);
ASSERT(cursor == getResultCursor()); ASSERT(cursor == getResultCursor());
m_hasPendingActivity = true;
m_pendingCursor = cursor; m_pendingCursor = cursor;
m_result.clear(); m_result.clear();
m_readyState = PENDING; m_readyState = PENDING;
...@@ -191,7 +191,7 @@ void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor) ...@@ -191,7 +191,7 @@ void IDBRequest::setPendingCursor(PassRefPtr<IDBCursor> cursor)
m_transaction->registerRequest(this); m_transaction->registerRequest(this);
} }
PassRefPtr<IDBCursor> IDBRequest::getResultCursor() IDBCursor* IDBRequest::getResultCursor()
{ {
if (!m_result) if (!m_result)
return 0; return 0;
...@@ -217,11 +217,18 @@ void IDBRequest::setResultCursor(PassRefPtr<IDBCursor> cursor, PassRefPtr<IDBKey ...@@ -217,11 +217,18 @@ void IDBRequest::setResultCursor(PassRefPtr<IDBCursor> cursor, PassRefPtr<IDBKey
m_result = IDBAny::create(IDBCursorWithValue::fromCursor(cursor)); m_result = IDBAny::create(IDBCursorWithValue::fromCursor(cursor));
} }
void IDBRequest::finishCursor() void IDBRequest::checkForReferenceCycle()
{ {
m_cursorFinished = true; // If this request and its cursor have the only references
if (m_readyState != PENDING) // to each other, then explicitly break the cycle.
m_hasPendingActivity = false; IDBCursor* cursor = getResultCursor();
if (!cursor || cursor->request() != this)
return;
if (!hasOneRef() || !cursor->hasOneRef())
return;
m_result.clear();
} }
bool IDBRequest::shouldEnqueueEvent() const bool IDBRequest::shouldEnqueueEvent() const
...@@ -496,7 +503,7 @@ bool IDBRequest::dispatchEvent(PassRefPtr<Event> event) ...@@ -496,7 +503,7 @@ bool IDBRequest::dispatchEvent(PassRefPtr<Event> event)
if (cursorToNotify) if (cursorToNotify)
cursorToNotify->postSuccessHandlerCallback(); cursorToNotify->postSuccessHandlerCallback();
if (m_readyState == DONE && (!cursorToNotify || m_cursorFinished) && event->type() != eventNames().upgradeneededEvent) if (m_readyState == DONE && event->type() != eventNames().upgradeneededEvent)
m_hasPendingActivity = false; m_hasPendingActivity = false;
return dontPreventDefault; return dontPreventDefault;
......
...@@ -75,7 +75,6 @@ public: ...@@ -75,7 +75,6 @@ public:
void markEarlyDeath(); void markEarlyDeath();
void setCursorDetails(IndexedDB::CursorType, IndexedDB::CursorDirection); void setCursorDetails(IndexedDB::CursorType, IndexedDB::CursorDirection);
void setPendingCursor(PassRefPtr<IDBCursor>); void setPendingCursor(PassRefPtr<IDBCursor>);
void finishCursor();
void abort(); void abort();
// IDBCallbacks // IDBCallbacks
...@@ -103,12 +102,21 @@ public: ...@@ -103,12 +102,21 @@ public:
void transactionDidFinishAndDispatch(); void transactionDidFinishAndDispatch();
using RefCounted<IDBCallbacks>::ref; using IDBCallbacks::ref;
using RefCounted<IDBCallbacks>::deref; using IDBCallbacks::deref;
virtual void deref() OVERRIDE
{
if (derefBase())
delete this;
else if (hasOneRef())
checkForReferenceCycle();
}
IDBDatabaseBackendInterface::TaskType taskType() { return m_taskType; } IDBDatabaseBackendInterface::TaskType taskType() { return m_taskType; }
DOMRequestState* requestState() { return &m_requestState; } DOMRequestState* requestState() { return &m_requestState; }
IDBCursor* getResultCursor();
protected: protected:
IDBRequest(ScriptExecutionContext*, PassRefPtr<IDBAny> source, IDBDatabaseBackendInterface::TaskType, IDBTransaction*); IDBRequest(ScriptExecutionContext*, PassRefPtr<IDBAny> source, IDBDatabaseBackendInterface::TaskType, IDBTransaction*);
...@@ -131,8 +139,9 @@ private: ...@@ -131,8 +139,9 @@ private:
virtual EventTargetData* eventTargetData(); virtual EventTargetData* eventTargetData();
virtual EventTargetData* ensureEventTargetData(); virtual EventTargetData* ensureEventTargetData();
PassRefPtr<IDBCursor> getResultCursor();
void setResultCursor(PassRefPtr<IDBCursor>, PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> value); void setResultCursor(PassRefPtr<IDBCursor>, PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> value);
void checkForReferenceCycle();
RefPtr<IDBAny> m_source; RefPtr<IDBAny> m_source;
const IDBDatabaseBackendInterface::TaskType m_taskType; const IDBDatabaseBackendInterface::TaskType m_taskType;
...@@ -143,7 +152,6 @@ private: ...@@ -143,7 +152,6 @@ private:
// Only used if the result type will be a cursor. // Only used if the result type will be a cursor.
IndexedDB::CursorType m_cursorType; IndexedDB::CursorType m_cursorType;
IndexedDB::CursorDirection m_cursorDirection; IndexedDB::CursorDirection m_cursorDirection;
bool m_cursorFinished;
RefPtr<IDBCursor> m_pendingCursor; RefPtr<IDBCursor> m_pendingCursor;
RefPtr<IDBKey> m_cursorKey; RefPtr<IDBKey> m_cursorKey;
RefPtr<IDBKey> m_cursorPrimaryKey; RefPtr<IDBKey> m_cursorPrimaryKey;
......
...@@ -222,46 +222,6 @@ void IDBTransaction::abort(ExceptionState& es) ...@@ -222,46 +222,6 @@ void IDBTransaction::abort(ExceptionState& es)
backendDB()->abort(m_id); backendDB()->abort(m_id);
} }
IDBTransaction::OpenCursorNotifier::OpenCursorNotifier(PassRefPtr<IDBTransaction> transaction, IDBCursor* cursor)
: m_transaction(transaction),
m_cursor(cursor)
{
m_transaction->registerOpenCursor(m_cursor);
}
IDBTransaction::OpenCursorNotifier::~OpenCursorNotifier()
{
if (m_cursor)
m_transaction->unregisterOpenCursor(m_cursor);
}
void IDBTransaction::OpenCursorNotifier::cursorFinished()
{
if (m_cursor) {
m_transaction->unregisterOpenCursor(m_cursor);
m_cursor = 0;
m_transaction.clear();
}
}
void IDBTransaction::registerOpenCursor(IDBCursor* cursor)
{
m_openCursors.add(cursor);
}
void IDBTransaction::unregisterOpenCursor(IDBCursor* cursor)
{
m_openCursors.remove(cursor);
}
void IDBTransaction::closeOpenCursors()
{
HashSet<IDBCursor*> cursors;
cursors.swap(m_openCursors);
for (HashSet<IDBCursor*>::iterator i = cursors.begin(); i != cursors.end(); ++i)
(*i)->close();
}
void IDBTransaction::registerRequest(IDBRequest* request) void IDBTransaction::registerRequest(IDBRequest* request)
{ {
ASSERT(request); ASSERT(request);
...@@ -303,7 +263,6 @@ void IDBTransaction::onAbort(PassRefPtr<DOMError> prpError) ...@@ -303,7 +263,6 @@ void IDBTransaction::onAbort(PassRefPtr<DOMError> prpError)
m_database->close(); m_database->close();
} }
m_objectStoreCleanupMap.clear(); m_objectStoreCleanupMap.clear();
closeOpenCursors();
// Enqueue events before notifying database, as database may close which enqueues more events and order matters. // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
enqueueEvent(Event::createBubble(eventNames().abortEvent)); enqueueEvent(Event::createBubble(eventNames().abortEvent));
...@@ -316,7 +275,6 @@ void IDBTransaction::onComplete() ...@@ -316,7 +275,6 @@ void IDBTransaction::onComplete()
ASSERT(m_state != Finished); ASSERT(m_state != Finished);
m_state = Finishing; m_state = Finishing;
m_objectStoreCleanupMap.clear(); m_objectStoreCleanupMap.clear();
closeOpenCursors();
// Enqueue events before notifying database, as database may close which enqueues more events and order matters. // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
enqueueEvent(Event::create(eventNames().completeEvent)); enqueueEvent(Event::create(eventNames().completeEvent));
......
...@@ -79,16 +79,6 @@ public: ...@@ -79,16 +79,6 @@ public:
PassRefPtr<IDBObjectStore> objectStore(const String& name, ExceptionState&); PassRefPtr<IDBObjectStore> objectStore(const String& name, ExceptionState&);
void abort(ExceptionState&); void abort(ExceptionState&);
class OpenCursorNotifier {
public:
OpenCursorNotifier(PassRefPtr<IDBTransaction>, IDBCursor*);
~OpenCursorNotifier();
void cursorFinished();
private:
RefPtr<IDBTransaction> m_transaction;
IDBCursor* m_cursor;
};
void registerRequest(IDBRequest*); void registerRequest(IDBRequest*);
void unregisterRequest(IDBRequest*); void unregisterRequest(IDBRequest*);
void objectStoreCreated(const String&, PassRefPtr<IDBObjectStore>); void objectStoreCreated(const String&, PassRefPtr<IDBObjectStore>);
...@@ -122,10 +112,6 @@ private: ...@@ -122,10 +112,6 @@ private:
IDBTransaction(ScriptExecutionContext*, int64_t, const Vector<String>&, IndexedDB::TransactionMode, IDBDatabase*, IDBOpenDBRequest*, const IDBDatabaseMetadata&); IDBTransaction(ScriptExecutionContext*, int64_t, const Vector<String>&, IndexedDB::TransactionMode, IDBDatabase*, IDBOpenDBRequest*, const IDBDatabaseMetadata&);
void enqueueEvent(PassRefPtr<Event>); void enqueueEvent(PassRefPtr<Event>);
void closeOpenCursors();
void registerOpenCursor(IDBCursor*);
void unregisterOpenCursor(IDBCursor*);
// EventTarget // EventTarget
virtual void refEventTarget() { ref(); } virtual void refEventTarget() { ref(); }
...@@ -162,8 +148,6 @@ private: ...@@ -162,8 +148,6 @@ private:
IDBObjectStoreMetadataMap m_objectStoreCleanupMap; IDBObjectStoreMetadataMap m_objectStoreCleanupMap;
IDBDatabaseMetadata m_previousMetadata; IDBDatabaseMetadata m_previousMetadata;
HashSet<IDBCursor*> m_openCursors;
EventTargetData m_eventTargetData; EventTargetData m_eventTargetData;
}; };
......
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