mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
Bug 871445 - patch 3 - DataStore: getChanges + revisionID, r=ehsan, sr=mounir, r=bent
This commit is contained in:
parent
771c5cedf1
commit
214ea52208
@ -112,9 +112,17 @@ IndexedDBHelper.prototype = {
|
||||
newTxn: function newTxn(txn_type, store_name, callback, successCb, failureCb) {
|
||||
this.ensureDB(function () {
|
||||
if (DEBUG) debug("Starting new transaction" + txn_type);
|
||||
let txn = this._db.transaction(this.dbStoreNames, txn_type);
|
||||
let txn = this._db.transaction(Array.isArray(store_name) ? store_name : this.dbStoreNames, txn_type);
|
||||
if (DEBUG) debug("Retrieving object store", this.dbName);
|
||||
let store = txn.objectStore(store_name);
|
||||
let stores;
|
||||
if (Array.isArray(store_name)) {
|
||||
stores = [];
|
||||
for (let i = 0; i < store_name.length; ++i) {
|
||||
stores.push(txn.objectStore(store_name[i]));
|
||||
}
|
||||
} else {
|
||||
stores = txn.objectStore(store_name);
|
||||
}
|
||||
|
||||
txn.oncomplete = function (event) {
|
||||
if (DEBUG) debug("Transaction complete. Returning to callback.");
|
||||
@ -137,7 +145,7 @@ IndexedDBHelper.prototype = {
|
||||
}
|
||||
}
|
||||
};
|
||||
callback(txn, store);
|
||||
callback(txn, stores);
|
||||
}.bind(this), failureCb);
|
||||
},
|
||||
|
||||
|
@ -14,10 +14,20 @@ function debug(s) {
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const REVISION_ADDED = "added";
|
||||
const REVISION_UPDATED = "updated";
|
||||
const REVISION_REMOVED = "removed";
|
||||
const REVISION_VOID = "void";
|
||||
|
||||
Cu.import("resource://gre/modules/DataStoreDB.jsm");
|
||||
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
/* Helper function */
|
||||
function createDOMError(aWindow, aEvent) {
|
||||
return new aWindow.DOMError(aEvent.target.error.name);
|
||||
}
|
||||
|
||||
/* DataStore object */
|
||||
|
||||
function DataStore(aAppId, aName, aOwner, aReadOnly, aGlobalScope) {
|
||||
@ -35,6 +45,7 @@ DataStore.prototype = {
|
||||
name: null,
|
||||
owner: null,
|
||||
readOnly: null,
|
||||
revisionId: null,
|
||||
|
||||
newDBPromise: function(aWindow, aTxnType, aFunction) {
|
||||
let db = this.db;
|
||||
@ -42,19 +53,19 @@ DataStore.prototype = {
|
||||
debug("DBPromise started");
|
||||
db.txn(
|
||||
aTxnType,
|
||||
function(aTxn, aStore) {
|
||||
function(aTxn, aStore, aRevisionStore) {
|
||||
debug("DBPromise success");
|
||||
aFunction(aResolve, aReject, aTxn, aStore);
|
||||
aFunction(aResolve, aReject, aTxn, aStore, aRevisionStore);
|
||||
},
|
||||
function() {
|
||||
function(aEvent) {
|
||||
debug("DBPromise error");
|
||||
aReject(new aWindow.DOMError("InvalidStateError"));
|
||||
aReject(createDOMError(aWindow, aEvent));
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
getInternal: function(aWindow, aResolve, aReject, aStore, aId) {
|
||||
getInternal: function(aWindow, aResolve, aStore, aId) {
|
||||
debug("GetInternal " + aId);
|
||||
|
||||
let request = aStore.get(aId);
|
||||
@ -62,71 +73,127 @@ DataStore.prototype = {
|
||||
debug("GetInternal success. Record: " + aEvent.target.result);
|
||||
aResolve(ObjectWrapper.wrap(aEvent.target.result, aWindow));
|
||||
};
|
||||
|
||||
request.onerror = function(aEvent) {
|
||||
debug("GetInternal error");
|
||||
aReject(new aWindow.DOMError(aEvent.target.error.name));
|
||||
};
|
||||
},
|
||||
|
||||
updateInternal: function(aWindow, aResolve, aReject, aStore, aId, aObj) {
|
||||
updateInternal: function(aResolve, aStore, aRevisionStore, aId, aObj) {
|
||||
debug("UpdateInternal " + aId);
|
||||
|
||||
let self = this;
|
||||
let request = aStore.put(aObj, aId);
|
||||
request.onsuccess = function(aEvent) {
|
||||
debug("UpdateInternal success");
|
||||
// No wrap here because the result is always a int.
|
||||
aResolve(aEvent.target.result);
|
||||
};
|
||||
request.onerror = function(aEvent) {
|
||||
debug("UpdateInternal error");
|
||||
aReject(new aWindow.DOMError(aEvent.target.error.name));
|
||||
|
||||
self.addRevision(aRevisionStore, aId, REVISION_UPDATED,
|
||||
function() {
|
||||
debug("UpdateInternal - revisionId increased");
|
||||
// No wrap here because the result is always a int.
|
||||
aResolve(aEvent.target.result);
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
addInternal: function(aWindow, aResolve, aReject, aStore, aObj) {
|
||||
addInternal: function(aResolve, aStore, aRevisionStore, aObj) {
|
||||
debug("AddInternal");
|
||||
|
||||
let self = this;
|
||||
let request = aStore.put(aObj);
|
||||
request.onsuccess = function(aEvent) {
|
||||
debug("Request successful. Id: " + aEvent.target.result);
|
||||
// No wrap here because the result is always a int.
|
||||
aResolve(aEvent.target.result);
|
||||
};
|
||||
request.onerror = function(aEvent) {
|
||||
debug("AddInternal error");
|
||||
aReject(new aWindow.DOMError(aEvent.target.error.name));
|
||||
self.addRevision(aRevisionStore, aEvent.target.result, REVISION_ADDED,
|
||||
function() {
|
||||
debug("AddInternal - revisionId increased");
|
||||
// No wrap here because the result is always a int.
|
||||
aResolve(aEvent.target.result);
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
removeInternal: function(aResolve, aReject, aStore, aId) {
|
||||
removeInternal: function(aResolve, aStore, aRevisionStore, aId) {
|
||||
debug("RemoveInternal");
|
||||
|
||||
let request = aStore.delete(aId);
|
||||
request.onsuccess = function() {
|
||||
debug("RemoveInternal success");
|
||||
aResolve();
|
||||
};
|
||||
request.onerror = function(aEvent) {
|
||||
debug("RemoveInternal error");
|
||||
aReject(new aWindow.DOMError(aEvent.target.error.name));
|
||||
let self = this;
|
||||
let request = aStore.get(aId);
|
||||
request.onsuccess = function(aEvent) {
|
||||
debug("RemoveInternal success. Record: " + aEvent.target.result);
|
||||
if (aEvent.target.result === undefined) {
|
||||
aResolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let deleteRequest = aStore.delete(aId);
|
||||
deleteRequest.onsuccess = function() {
|
||||
debug("RemoveInternal success");
|
||||
self.addRevision(aRevisionStore, aId, REVISION_REMOVED,
|
||||
function() {
|
||||
aResolve(true);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
clearInternal: function(aResolve, aReject, aStore) {
|
||||
clearInternal: function(aResolve, aStore, aRevisionStore) {
|
||||
debug("ClearInternal");
|
||||
|
||||
let self = this;
|
||||
let request = aStore.clear();
|
||||
request.onsuccess = function() {
|
||||
debug("ClearInternal success");
|
||||
aResolve();
|
||||
};
|
||||
request.onerror = function(aEvent) {
|
||||
debug("ClearInternal error");
|
||||
aReject(new aWindow.DOMError(aEvent.target.error.name));
|
||||
self.db.clearRevisions(aRevisionStore,
|
||||
function() {
|
||||
debug("Revisions cleared");
|
||||
|
||||
self.addRevision(aRevisionStore, 0, REVISION_VOID,
|
||||
function() {
|
||||
debug("ClearInternal - revisionId increased");
|
||||
aResolve();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
addRevision: function(aRevisionStore, aId, aType, aSuccessCb) {
|
||||
let self = this;
|
||||
this.db.addRevision(aRevisionStore, aId, aType,
|
||||
function(aRevisionId) {
|
||||
self.revisionId = aRevisionId;
|
||||
aSuccessCb();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
retrieveRevisionId: function(aSuccessCb) {
|
||||
if (this.revisionId != null) {
|
||||
aSuccessCb();
|
||||
return;
|
||||
}
|
||||
|
||||
let self = this;
|
||||
this.db.revisionTxn(
|
||||
'readwrite',
|
||||
function(aTxn, aRevisionStore) {
|
||||
debug("RetrieveRevisionId transaction success");
|
||||
|
||||
let request = aRevisionStore.openCursor(null, 'prev');
|
||||
request.onsuccess = function(aEvent) {
|
||||
let cursor = aEvent.target.result;
|
||||
if (!cursor) {
|
||||
// If the revision doesn't exist, let's create the first one.
|
||||
self.addRevision(aRevisionStore, 0, REVISION_VOID, aSuccessCb);
|
||||
return;
|
||||
}
|
||||
|
||||
self.revisionId = cursor.value.revisionId;
|
||||
aSuccessCb();
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
throwInvalidArg: function(aWindow) {
|
||||
return aWindow.Promise.reject(
|
||||
new aWindow.DOMError("SyntaxError", "Non-numeric or invalid id"));
|
||||
@ -163,8 +230,8 @@ DataStore.prototype = {
|
||||
|
||||
// Promise<Object>
|
||||
return self.newDBPromise(aWindow, "readonly",
|
||||
function(aResolve, aReject, aTxn, aStore) {
|
||||
self.getInternal(aWindow, aResolve, aReject, aStore, aId);
|
||||
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
|
||||
self.getInternal(aWindow, aResolve, aStore, aId);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -181,8 +248,8 @@ DataStore.prototype = {
|
||||
|
||||
// Promise<void>
|
||||
return self.newDBPromise(aWindow, "readwrite",
|
||||
function(aResolve, aReject, aTxn, aStore) {
|
||||
self.updateInternal(aWindow, aResolve, aReject, aStore, aId, aObj);
|
||||
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
|
||||
self.updateInternal(aResolve, aStore, aRevisionStore, aId, aObj);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -194,8 +261,8 @@ DataStore.prototype = {
|
||||
|
||||
// Promise<int>
|
||||
return self.newDBPromise(aWindow, "readwrite",
|
||||
function(aResolve, aReject, aTxn, aStore) {
|
||||
self.addInternal(aWindow, aResolve, aReject, aStore, aObj);
|
||||
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
|
||||
self.addInternal(aResolve, aStore, aRevisionStore, aObj);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -212,8 +279,8 @@ DataStore.prototype = {
|
||||
|
||||
// Promise<void>
|
||||
return self.newDBPromise(aWindow, "readwrite",
|
||||
function(aResolve, aReject, aTxn, aStore) {
|
||||
self.removeInternal(aResolve, aReject, aStore, aId);
|
||||
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
|
||||
self.removeInternal(aResolve, aStore, aRevisionStore, aId);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -225,16 +292,114 @@ DataStore.prototype = {
|
||||
|
||||
// Promise<void>
|
||||
return self.newDBPromise(aWindow, "readwrite",
|
||||
function(aResolve, aReject, aTxn, aStore) {
|
||||
self.clearInternal(aResolve, aReject, aStore);
|
||||
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
|
||||
self.clearInternal(aResolve, aStore, aRevisionStore);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
get revisionId() {
|
||||
return self.revisionId;
|
||||
},
|
||||
|
||||
getChanges: function(aRevisionId) {
|
||||
debug("GetChanges: " + aRevisionId);
|
||||
|
||||
if (aRevisionId === null || aRevisionId === undefined) {
|
||||
return aWindow.Promise.reject(
|
||||
new aWindow.DOMError("SyntaxError", "Invalid revisionId"));
|
||||
}
|
||||
|
||||
// Promise<DataStoreChanges>
|
||||
return new aWindow.Promise(function(aResolve, aReject) {
|
||||
debug("GetChanges promise started");
|
||||
self.db.revisionTxn(
|
||||
'readonly',
|
||||
function(aTxn, aStore) {
|
||||
debug("GetChanges transaction success");
|
||||
|
||||
let request = self.db.getInternalRevisionId(
|
||||
aRevisionId,
|
||||
aStore,
|
||||
function(aInternalRevisionId) {
|
||||
if (aInternalRevisionId == undefined) {
|
||||
aResolve(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// This object is the return value of this promise.
|
||||
// Initially we use maps, and then we convert them in array.
|
||||
let changes = {
|
||||
revisionId: '',
|
||||
addedIds: {},
|
||||
updatedIds: {},
|
||||
removedIds: {}
|
||||
};
|
||||
|
||||
let request = aStore.mozGetAll(aWindow.IDBKeyRange.lowerBound(aInternalRevisionId, true));
|
||||
request.onsuccess = function(aEvent) {
|
||||
for (let i = 0; i < aEvent.target.result.length; ++i) {
|
||||
let data = aEvent.target.result[i];
|
||||
|
||||
switch (data.operation) {
|
||||
case REVISION_ADDED:
|
||||
changes.addedIds[data.objectId] = true;
|
||||
break;
|
||||
|
||||
case REVISION_UPDATED:
|
||||
// We don't consider an update if this object has been added
|
||||
// or if it has been already modified by a previous
|
||||
// operation.
|
||||
if (!(data.objectId in changes.addedIds) &&
|
||||
!(data.objectId in changes.updatedIds)) {
|
||||
changes.updatedIds[data.objectId] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case REVISION_REMOVED:
|
||||
let id = data.objectId;
|
||||
|
||||
// If the object has been added in this range of revisions
|
||||
// we can ignore it and remove it from the list.
|
||||
if (id in changes.addedIds) {
|
||||
delete changes.addedIds[id];
|
||||
} else {
|
||||
changes.removedIds[id] = true;
|
||||
}
|
||||
|
||||
if (id in changes.updatedIds) {
|
||||
delete changes.updatedIds[id];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The last revisionId.
|
||||
if (aEvent.target.result.length) {
|
||||
changes.revisionId = aEvent.target.result[aEvent.target.result.length - 1].revisionId;
|
||||
}
|
||||
|
||||
// From maps to arrays.
|
||||
changes.addedIds = Object.keys(changes.addedIds).map(function(aKey) { return parseInt(aKey, 10); });
|
||||
changes.updatedIds = Object.keys(changes.updatedIds).map(function(aKey) { return parseInt(aKey, 10); });
|
||||
changes.removedIds = Object.keys(changes.removedIds).map(function(aKey) { return parseInt(aKey, 10); });
|
||||
|
||||
let wrappedObject = ObjectWrapper.wrap(changes, aWindow);
|
||||
aResolve(wrappedObject);
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
function(aEvent) {
|
||||
debug("GetChanges transaction failed");
|
||||
aReject(createDOMError(aWindow, aEvent));
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/* TODO:
|
||||
readonly attribute DOMString revisionId
|
||||
attribute EventHandler onchange;
|
||||
Promise<DataStoreChanges> getChanges(DOMString revisionId)
|
||||
getAll(), getLength()
|
||||
*/
|
||||
|
||||
@ -246,7 +411,9 @@ DataStore.prototype = {
|
||||
update: 'r',
|
||||
add: 'r',
|
||||
remove: 'r',
|
||||
clear: 'r'
|
||||
clear: 'r',
|
||||
revisionId: 'r',
|
||||
getChanges: 'r'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -14,8 +14,15 @@ function debug(s) {
|
||||
|
||||
const DATASTOREDB_VERSION = 1;
|
||||
const DATASTOREDB_OBJECTSTORE_NAME = 'DataStoreDB';
|
||||
const DATASTOREDB_REVISION = 'revision';
|
||||
const DATASTOREDB_REVISION_INDEX = 'revisionIndex';
|
||||
|
||||
Cu.import('resource://gre/modules/IndexedDBHelper.jsm');
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
|
||||
this.DataStoreDB = function DataStoreDB() {}
|
||||
|
||||
@ -26,25 +33,68 @@ DataStoreDB.prototype = {
|
||||
upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) {
|
||||
debug('updateSchema');
|
||||
aDb.createObjectStore(DATASTOREDB_OBJECTSTORE_NAME, { autoIncrement: true });
|
||||
let store = aDb.createObjectStore(DATASTOREDB_REVISION,
|
||||
{ autoIncrement: true,
|
||||
keyPath: 'internalRevisionId' });
|
||||
store.createIndex(DATASTOREDB_REVISION_INDEX, 'revisionId', { unique: true });
|
||||
},
|
||||
|
||||
init: function(aOrigin, aName) {
|
||||
let dbName = aOrigin + '_' + aName;
|
||||
this.initDBHelper(dbName, DATASTOREDB_VERSION,
|
||||
[DATASTOREDB_OBJECTSTORE_NAME]);
|
||||
[DATASTOREDB_OBJECTSTORE_NAME, DATASTOREDB_REVISION]);
|
||||
},
|
||||
|
||||
txn: function(aType, aCallback, aErrorCb) {
|
||||
debug('Transaction request');
|
||||
this.newTxn(
|
||||
aType,
|
||||
DATASTOREDB_OBJECTSTORE_NAME,
|
||||
aType == 'readonly'
|
||||
? [ DATASTOREDB_OBJECTSTORE_NAME ] : [ DATASTOREDB_OBJECTSTORE_NAME, DATASTOREDB_REVISION ],
|
||||
function(aTxn, aStores) {
|
||||
aType == 'readonly' ? aCallback(aTxn, aStores[0], null) : aCallback(aTxn, aStores[0], aStores[1]);
|
||||
},
|
||||
function() {},
|
||||
aErrorCb
|
||||
);
|
||||
},
|
||||
|
||||
revisionTxn: function(aType, aCallback, aErrorCb) {
|
||||
debug("Transaction request");
|
||||
this.newTxn(
|
||||
aType,
|
||||
DATASTOREDB_REVISION,
|
||||
aCallback,
|
||||
function() {},
|
||||
aErrorCb
|
||||
);
|
||||
},
|
||||
|
||||
addRevision: function(aStore, aId, aType, aSuccessCb) {
|
||||
debug("AddRevision: " + aId + " - " + aType);
|
||||
let revisionId = uuidgen.generateUUID().toString();
|
||||
let request = aStore.put({ revisionId: revisionId, objectId: aId, operation: aType });
|
||||
request.onsuccess = function() {
|
||||
aSuccessCb(revisionId);
|
||||
}
|
||||
},
|
||||
|
||||
getInternalRevisionId: function(aRevisionId, aStore, aSuccessCb) {
|
||||
debug('GetInternalRevisionId');
|
||||
let request = aStore.index(DATASTOREDB_REVISION_INDEX).getKey(aRevisionId);
|
||||
request.onsuccess = function(aEvent) {
|
||||
aSuccessCb(aEvent.target.result);
|
||||
}
|
||||
},
|
||||
|
||||
clearRevisions: function(aStore, aSuccessCb) {
|
||||
debug("ClearRevisions");
|
||||
let request = aStore.clear();
|
||||
request.onsuccess = function() {
|
||||
aSuccessCb();
|
||||
}
|
||||
},
|
||||
|
||||
delete: function() {
|
||||
debug('delete');
|
||||
this.close();
|
||||
|
@ -61,16 +61,38 @@ DataStoreService.prototype = {
|
||||
debug('getDataStores - aName: ' + aName);
|
||||
let self = this;
|
||||
return new aWindow.Promise(function(resolve, reject) {
|
||||
let results = [];
|
||||
let matchingStores = [];
|
||||
|
||||
if (aName in self.stores) {
|
||||
for (let appId in self.stores[aName]) {
|
||||
let obj = self.stores[aName][appId].exposeObject(aWindow);
|
||||
results.push(obj);
|
||||
matchingStores.push(self.stores[aName][appId]);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(results);
|
||||
let callbackPending = matchingStores.length;
|
||||
let results = [];
|
||||
|
||||
if (!callbackPending) {
|
||||
resolve(results);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < matchingStores.length; ++i) {
|
||||
let obj = matchingStores[i].exposeObject(aWindow);
|
||||
results.push(obj);
|
||||
|
||||
matchingStores[i].retrieveRevisionId(
|
||||
function() {
|
||||
--callbackPending;
|
||||
if (!callbackPending) {
|
||||
resolve(results);
|
||||
}
|
||||
},
|
||||
function() {
|
||||
reject();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -15,6 +15,7 @@ MOCHITEST_FILES = \
|
||||
test_app_install.html \
|
||||
test_readonly.html \
|
||||
test_basic.html \
|
||||
test_revision.html \
|
||||
file_app.sjs \
|
||||
file_app.template.webapp \
|
||||
$(NULL)
|
||||
|
235
dom/datastore/tests/test_revision.html
Normal file
235
dom/datastore/tests/test_revision.html
Normal file
@ -0,0 +1,235 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for DataStore - basic operation on a readonly db</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
var gBaseURL = 'http://test/tests/dom/datastore/tests/';
|
||||
var gHostedManifestURL = gBaseURL + 'file_app.sjs';
|
||||
var gApp;
|
||||
var gStore;
|
||||
var gPreviousRevisionId = '';
|
||||
|
||||
function cbError() {
|
||||
ok(false, "Error callback invoked");
|
||||
finish();
|
||||
}
|
||||
|
||||
function installApp() {
|
||||
var request = navigator.mozApps.install(gHostedManifestURL);
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = function() {
|
||||
gApp = request.result;
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function testGetDataStores() {
|
||||
navigator.getDataStores('foo').then(function(stores) {
|
||||
is(stores.length, 1, "getDataStores('foo') returns 1 element");
|
||||
is(stores[0].name, 'foo', 'The dataStore.name is foo');
|
||||
is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
|
||||
|
||||
gStore = stores[0];
|
||||
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function testStoreAdd(value, expectedId) {
|
||||
return gStore.add(value).then(function(id) {
|
||||
is(id, expectedId, "store.add() is called");
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function testStoreUpdate(id, value) {
|
||||
return gStore.update(id, value).then(function(retId) {
|
||||
is(id, retId, "store.update() is called with the right id");
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function testStoreRemove(id, expectedSuccess) {
|
||||
return gStore.remove(id).then(function(success) {
|
||||
is(success, expectedSuccess, "store.remove() returns the right value");
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function testStoreRevisionId() {
|
||||
is(/[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}/.test(gStore.revisionId), true, "store.revisionId returns something");
|
||||
runTest();
|
||||
}
|
||||
|
||||
function testStoreWrongRevisions(id) {
|
||||
return gStore.getChanges(id).then(
|
||||
function(what) {
|
||||
is(what, undefined, "Wrong revisionId == undefined object");
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function testStoreRevisions(id, changes) {
|
||||
return gStore.getChanges(id).then(function(what) {
|
||||
is(JSON.stringify(changes.addedIds),
|
||||
JSON.stringify(what.addedIds), "store.revisions - addedIds: " +
|
||||
JSON.stringify(what.addedIds) + " | " + JSON.stringify(changes.addedIds));
|
||||
is(JSON.stringify(changes.updatedIds),
|
||||
JSON.stringify(what.updatedIds), "store.revisions - updatedIds: " +
|
||||
JSON.stringify(what.updatedIds) + " | " + JSON.stringify(changes.updatedIds));
|
||||
is(JSON.stringify(changes.removedIds),
|
||||
JSON.stringify(what.removedIds), "store.revisions - removedIds: " +
|
||||
JSON.stringify(what.removedIds) + " | " + JSON.stringify(changes.removedIds));
|
||||
runTest();
|
||||
}, cbError);
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
// Uninstall the app.
|
||||
request = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
request.onerror = cbError;
|
||||
request.onsuccess = function() {
|
||||
// All done.
|
||||
ok(true, "All done");
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function testStoreRevisionIdChanged() {
|
||||
isnot(gStore.revisionId, gPreviousRevisionId, "Revision changed");
|
||||
gPreviousRevisionId = gStore.revisionId;
|
||||
runTest();
|
||||
}
|
||||
|
||||
function testStoreRevisionIdNotChanged() {
|
||||
is(gStore.revisionId, gPreviousRevisionId, "Revision changed");
|
||||
runTest();
|
||||
}
|
||||
|
||||
var revisions = [];
|
||||
|
||||
var tests = [
|
||||
// Permissions
|
||||
function() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "browser", "allow": 1, "context": document },
|
||||
{ "type": "embed-apps", "allow": 1, "context": document },
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
|
||||
},
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
|
||||
},
|
||||
|
||||
// No confirmation needed when an app is installed
|
||||
function() {
|
||||
SpecialPowers.autoConfirmAppInstall(runTest);
|
||||
},
|
||||
|
||||
// Installing the app
|
||||
installApp,
|
||||
|
||||
// Test for GetDataStore
|
||||
testGetDataStores,
|
||||
|
||||
// The first revision is not empty
|
||||
testStoreRevisionIdChanged,
|
||||
|
||||
// wrong revision ID
|
||||
function() { testStoreWrongRevisions('foobar'); },
|
||||
|
||||
// Add
|
||||
function() { testStoreAdd({ number: 42 }, 1); },
|
||||
function() { revisions.push(gStore.revisionId); testStoreRevisionId(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
// Add
|
||||
function() { testStoreAdd({ number: 42 }, 2); },
|
||||
function() { revisions.push(gStore.revisionId); runTest(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [2], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[1], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
// Add
|
||||
function() { testStoreAdd({ number: 42 }, 3); },
|
||||
function() { revisions.push(gStore.revisionId); runTest(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
// Update
|
||||
function() { testStoreUpdate(3, { number: 43 }); },
|
||||
function() { revisions.push(gStore.revisionId); runTest(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [3], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
// Update
|
||||
function() { testStoreUpdate(3, { number: 42 }); },
|
||||
function() { revisions.push(gStore.revisionId); runTest(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [3], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [3], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[4], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
// Remove
|
||||
function() { testStoreRemove(3, true); },
|
||||
function() { revisions.push(gStore.revisionId); runTest(); },
|
||||
testStoreRevisionIdChanged,
|
||||
function() { testStoreRevisions(revisions[0], { addedIds: [2], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[1], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [], removedIds: [3] }); },
|
||||
function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [], removedIds: [3] }); },
|
||||
function() { testStoreRevisions(revisions[4], { addedIds: [], updatedIds: [], removedIds: [3] }); },
|
||||
function() { testStoreRevisions(revisions[5], { addedIds: [], updatedIds: [], removedIds: [] }); },
|
||||
|
||||
function() { testStoreRemove(3, false); },
|
||||
testStoreRevisionIdNotChanged,
|
||||
|
||||
// Remove
|
||||
function() { testStoreRemove(42, false); },
|
||||
testStoreRevisionIdNotChanged,
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
runTest();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user