Backed out changeset 3fe07c50c854 (bug 946316) for bustage. a=backout
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 22 Jan 2014 17:43:39 -0500
changeset 175955 edd34e3ec168340308e1f820a6c6eed76b470877
parent 175954 343a400dfc4a6c40e84ab7b0ce177e84de7bd3d0
child 175956 55c535ceba243d828af18c4985e7a615028d2de9
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs946316
milestone28.0a2
backs out3fe07c50c854223ecb154810f5046310b3e15827
Backed out changeset 3fe07c50c854 (bug 946316) for bustage. a=backout CLOSED TREE
dom/datastore/DataStore.jsm
dom/datastore/DataStoreCursor.jsm
dom/datastore/tests/file_arrays.html
dom/datastore/tests/file_basic.html
dom/datastore/tests/file_keys.html
dom/datastore/tests/file_sync.html
dom/datastore/tests/mochitest.ini
dom/datastore/tests/test_keys.html
dom/webidl/DataStore.webidl
dom/webidl/DataStoreChangeEvent.webidl
--- a/dom/datastore/DataStore.jsm
+++ b/dom/datastore/DataStore.jsm
@@ -44,24 +44,34 @@ function throwInvalidArg(aWindow) {
     new aWindow.DOMError("SyntaxError", "Non-numeric or invalid id"));
 }
 
 function throwReadOnly(aWindow) {
   return aWindow.Promise.reject(
     new aWindow.DOMError("ReadOnlyError", "DataStore in readonly mode"));
 }
 
-function validateId(aId) {
-  // If string, it cannot be empty.
-  if (typeof(aId) == 'string') {
-    return aId.length;
+function parseIds(aId) {
+  function parseId(aId) {
+    aId = parseInt(aId);
+    return (isNaN(aId) || aId <= 0) ? null : aId;
   }
 
-  aId = parseInt(aId);
-  return (!isNaN(aId) && aId > 0);
+  if (!Array.isArray(aId)) {
+    return parseId(aId);
+  }
+
+  for (let i = 0; i < aId.length; ++i) {
+    aId[i] = parseId(aId[i]);
+    if (aId[i] === null) {
+      return null;
+    }
+  }
+
+  return aId;
 }
 
 /* DataStore object */
 this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
   debug("DataStore created");
   this.init(aWindow, aName, aOwner, aReadOnly);
 }
 
@@ -350,39 +360,39 @@ this.DataStore.prototype = {
   get owner() {
     return this._owner;
   },
 
   get readOnly() {
     return this._readOnly;
   },
 
-  get: function() {
-    let ids = Array.prototype.slice.call(arguments);
-    for (let i = 0; i < ids.length; ++i) {
-      if (!validateId(ids[i])) {
-        return throwInvalidArg(this._window);
-      }
+  get: function(aId) {
+    aId = parseIds(aId);
+    if (aId === null) {
+      return throwInvalidArg(this._window);
     }
 
     let self = this;
 
     // Promise<Object>
     return this.newDBPromise("readonly",
       function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-               self.getInternal(aStore, ids,
+               self.getInternal(aStore,
+                                Array.isArray(aId) ?  aId : [ aId ],
                                 function(aResults) {
-          aResolve(ids.length > 1 ? aResults : aResults[0]);
+          aResolve(Array.isArray(aId) ? aResults : aResults[0]);
         });
       }
     );
   },
 
   put: function(aObj, aId) {
-    if (!validateId(aId)) {
+    aId = parseInt(aId);
+    if (isNaN(aId) || aId <= 0) {
       return throwInvalidArg(this._window);
     }
 
     if (this._readOnly) {
       return throwReadOnly(this._window);
     }
 
     let self = this;
@@ -392,17 +402,18 @@ this.DataStore.prototype = {
       function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
         self.putInternal(aResolve, aStore, aRevisionStore, aObj, aId);
       }
     );
   },
 
   add: function(aObj, aId) {
     if (aId) {
-      if (!validateId(aId)) {
+      aId = parseInt(aId);
+      if (isNaN(aId) || aId <= 0) {
         return throwInvalidArg(this._window);
       }
     }
 
     if (this._readOnly) {
       return throwReadOnly(this._window);
     }
 
@@ -412,17 +423,18 @@ this.DataStore.prototype = {
     return this.newDBPromise("readwrite",
       function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
         self.addInternal(aResolve, aStore, aRevisionStore, aObj, aId);
       }
     );
   },
 
   remove: function(aId) {
-    if (!validateId(aId)) {
+    aId = parseInt(aId);
+    if (isNaN(aId) || aId <= 0) {
       return throwInvalidArg(this._window);
     }
 
     if (this._readOnly) {
       return throwReadOnly(this._window);
     }
 
     let self = this;
--- a/dom/datastore/DataStoreCursor.jsm
+++ b/dom/datastore/DataStoreCursor.jsm
@@ -29,37 +29,37 @@ const REVISION_SKIP = 'skip'
 
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 /**
  * legend:
  * - RID = revision ID
  * - R = revision object (with the internalRevisionId that is a number)
- * - X = current object ID.
+ * - X = current object ID. Default value is 0
+ * - MX = max known object ID
  * - L = the list of revisions that we have to send
  *
  * State: init: do you have RID ?
  *   YES: state->initRevision; loop
- *   NO: get R; X=0; state->sendAll; send a 'clear'
+ *   NO: get R; get MX; state->sendAll; send a 'clear'
  *
  * State: initRevision. Get R from RID. Done?
  *   YES: state->revisionCheck; loop
  *   NO: RID = null; state->init; loop
  *
  * State: revisionCheck: get all the revisions between R and NOW. Done?
  *   YES and R == NOW: state->done; loop
  *   YES and R != NOW: Store this revisions in L; state->revisionSend; loop
- *   NO: R = NOW; X=0; state->sendAll; send a 'clear'
+ *   NO: R = NOW; get MX; state->sendAll; send a 'clear';
  *
- * State: sendAll: is R still the last revision?
- *   YES get the first object with id > X. Done?
- *     YES: X = object.id; send 'add'
- *     NO: state->revisionCheck; loop
- *   NO: R = NOW; X=0; send a 'clear'
+ * State: sendAll: get the first object with id > X. Done?
+ *   YES and object.id > MX: state->revisionCheck; loop
+ *   YES and object.id <= MX: X = object.id; send 'add'
+ *   NO: state->revisionCheck; loop
  *
  * State: revisionSend: do you have something from L to send?
  *   YES and L[0] == 'removed': R=L[0]; send 'remove' with ID
  *   YES and L[0] == 'added': R=L[0]; get the object; found?
  *     NO: loop
  *     YES: send 'add' with ID and object
  *   YES and L[0] == 'updated': R=L[0]; get the object; found?
  *     NO: loop
@@ -88,16 +88,17 @@ this.DataStoreCursor.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
 
   _window: null,
   _dataStore: null,
   _revisionId: null,
   _revision: null,
   _revisionsList: null,
   _objectId: 0,
+  _maxObjectId: 0,
 
   _state: STATE_INIT,
 
   init: function(aWindow, aDataStore, aRevisionId) {
     debug('DataStoreCursor init');
 
     this._window = aWindow;
     this._dataStore = aDataStore;
@@ -144,34 +145,36 @@ this.DataStoreCursor.prototype = {
       this.stateMachine(aStore, aRevisionStore, aResolve, aReject);
       return;
     }
 
     let self = this;
     let request = aRevisionStore.openCursor(null, 'prev');
     request.onsuccess = function(aEvent) {
       self._revision = aEvent.target.result.value;
-      self._objectId = 0;
-      self._state = STATE_SEND_ALL;
-      aResolve(ObjectWrapper.wrap({ operation: 'clear' }, self._window));
+      self.getMaxObjectId(aStore,
+        function() {
+          self._state = STATE_SEND_ALL;
+          aResolve(ObjectWrapper.wrap({ operation: 'clear' }, self._window));
+        }
+      );
     }
   },
 
   stateMachineRevisionInit: function(aStore, aRevisionStore, aResolve, aReject) {
     debug('StateMachineRevisionInit');
 
     let self = this;
     let request = this._dataStore._db.getInternalRevisionId(
       self._revisionId,
       aRevisionStore,
       function(aInternalRevisionId) {
         // This revision doesn't exist.
         if (aInternalRevisionId == undefined) {
           self._revisionId = null;
-          self._objectId = 0;
           self._state = STATE_INIT;
           self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
           return;
         }
 
         self._revision = { revisionId: self._revisionId,
                            internalRevisionId: aInternalRevisionId };
         self._state = STATE_REVISION_CHECK;
@@ -230,20 +233,23 @@ this.DataStoreCursor.prototype = {
             break;
 
           case REVISION_VOID:
             if (i != 0) {
               dump('Internal error: Revision "' + REVISION_VOID + '" should not be found!!!\n');
               return;
             }
 
-            self._revisionId = null;
-            self._objectId = 0;
-            self._state = STATE_INIT;
-            self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
+            self.getMaxObjectId(aStore,
+              function() {
+                self._revisionId = null;
+                self._state = STATE_INIT;
+                self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
+              }
+            );
             return;
         }
       }
 
       // From changes to a map of internalRevisionId.
       let revisions = {};
       function addRevisions(obj) {
         for (let key in obj) {
@@ -281,38 +287,28 @@ this.DataStoreCursor.prototype = {
       self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
     };
   },
 
   stateMachineSendAll: function(aStore, aRevisionStore, aResolve, aReject) {
     debug('StateMachineSendAll');
 
     let self = this;
-    let request = aRevisionStore.openCursor(null, 'prev');
+    let request = aStore.openCursor(self._window.IDBKeyRange.lowerBound(this._objectId, true));
     request.onsuccess = function(aEvent) {
-      if (self._revision.revisionId != aEvent.target.result.value.revisionId) {
-        self._revision = aEvent.target.result.value;
-        self._objectId = 0;
-        aResolve(ObjectWrapper.wrap({ operation: 'clear' }, self._window));
+      let cursor = aEvent.target.result;
+      if (!cursor || cursor.key > self._maxObjectId) {
+        self._state = STATE_REVISION_CHECK;
+        self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
         return;
       }
 
-      let request = aStore.openCursor(self._window.IDBKeyRange.lowerBound(self._objectId, true));
-      request.onsuccess = function(aEvent) {
-        let cursor = aEvent.target.result;
-        if (!cursor) {
-          self._state = STATE_REVISION_CHECK;
-          self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
-          return;
-        }
-
-        self._objectId = cursor.key;
-        aResolve(ObjectWrapper.wrap({ operation: 'add', id: self._objectId,
-                                      data: cursor.value }, self._window));
-      };
+      self._objectId = cursor.key;
+      aResolve(ObjectWrapper.wrap({ operation: 'add', id: self._objectId,
+                                    data: cursor.value }, self._window));
     };
   },
 
   stateMachineRevisionSend: function(aStore, aRevisionStore, aResolve, aReject) {
     debug('StateMachineRevisionSend');
 
     if (!this._revisionsList.length) {
       this._state = STATE_REVISION_CHECK;
@@ -376,16 +372,27 @@ this.DataStoreCursor.prototype = {
   },
 
   stateMachineDone: function(aStore, aRevisionStore, aResolve, aReject) {
     this.close();
     aResolve(ObjectWrapper.wrap({ revisionId: this._revision.revisionId,
                                   operation: 'done' }, this._window));
   },
 
+  getMaxObjectId: function(aStore, aCallback) {
+    let self = this;
+    let request = aStore.openCursor(null, 'prev');
+    request.onsuccess = function(aEvent) {
+      if (aEvent.target.result) {
+        self._maxObjectId = aEvent.target.result.key;
+      }
+      aCallback();
+    }
+  },
+
   // public interface
 
   get store() {
     return this._dataStore.exposedObject;
   },
 
   next: function() {
     debug('Next');
--- a/dom/datastore/tests/file_arrays.html
+++ b/dom/datastore/tests/file_arrays.html
@@ -71,17 +71,17 @@
   }
 
   function testStoreGet() {
     var objects = [];
     for (var i = 1; i <= itemNumber; ++i) {
       objects.push(i);
     }
 
-    gStore.get.apply(gStore, objects).then(function(data) {
+    gStore.get(objects).then(function(data) {
        is(data.length, objects.length, "Get - Data matches");
        for (var i = 0; i < data.length; ++i) {
          is(data[i], objects[i] - 1, "Get - Data matches: " + i + " " + data[i] + " == " + objects[i]);
        }
        runTest();
     }, cbError);
   }
 
--- a/dom/datastore/tests/file_basic.html
+++ b/dom/datastore/tests/file_basic.html
@@ -96,16 +96,17 @@
     // Unknown ID
     function() { testStoreGet(42, undefined); },
     function() { testStoreGet(42, undefined); }, // twice
 
     // Add + Get - number
     function() { testStoreAdd(42).then(function(id) {
                    gId = id; runTest(); }, cbError); },
     function() { testStoreGet(gId, 42); },
+    function() { testStoreGet(gId + "", 42); },
 
     // Add + Get - boolean
     function() { testStoreAdd(true).then(function(id) {
                    gId = id; runTest(); }, cbError); },
     function() { testStoreGet(gId, true); },
 
     // Add + Get - string
     function() { testStoreAdd("hello world").then(function(id) {
deleted file mode 100644
--- a/dom/datastore/tests/file_keys.html
+++ /dev/null
@@ -1,161 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for DataStore - string or unsigned long keys</title>
-</head>
-<body>
-<div id="container"></div>
-  <script type="application/javascript;version=1.7">
-
-  var gStore;
-  var gEvent;
-  var gChangeId;
-
-  function is(a, b, msg) {
-    alert((a === b ? 'OK' : 'KO') + ' ' + msg)
-  }
-
-  function ok(a, msg) {
-    alert((a ? 'OK' : 'KO')+ ' ' + msg)
-  }
-
-  function cbError() {
-    alert('KO error');
-  }
-
-  function finish() {
-    alert('DONE');
-  }
-
-  function testGetDataStores() {
-    navigator.getDataStores('foo').then(function(stores) {
-      gStore = stores[0];
-      runTest();
-    }, cbError);
-  }
-
-  function testAdd_noKey(key) {
-    gEvent = 'added';
-    gChangeId = key;
-
-    gStore.add({ a: 42 }).then(function(id) {
-      is(id, key, "Id must be " + key + " received: " + id);
-    });
-  }
-
-  function testAdd_withKey(key) {
-    gEvent = 'added';
-    gChangeId = key;
-
-    gStore.add({ a: 42 }, key).then(function(id) {
-      is(id, key, "Id must be " + key + " received: " + id);
-    });
-  }
-
-  function testPut(key) {
-    gEvent = 'updated';
-    gChangeId = key;
-
-    gStore.put({ a: 42 }, key).then(function(id) {
-      is(id, key, "Id must be " + key + " received: " + id);
-    });
-  }
-
-  function testGet(key) {
-    gStore.get(key).then(function(value) {
-      ok(value, "Object received!");
-      is(value.a, 42, "Object received with right value!");
-      runTest();
-    });
-  }
-
-  function testArrayGet(key) {
-    gStore.get.apply(gStore, key).then(function(values) {
-      is(values.length, key.length, "Object received!");
-      for (var i = 0; i < values.length; ++i) {
-        is(values[i].a, 42, "Object received with right value!");
-      }
-
-      runTest();
-    });
-  }
-
-  function testRemove(key, success) {
-    gEvent = 'removed';
-    gChangeId = key;
-
-    gStore.remove(key).then(function(value) {
-      is(value, success, "Status must be " + success + " received: " + value);
-      if (value == false) {
-        runTest();
-        return;
-      }
-    });
-  }
-
-  function eventListener() {
-    gStore.onchange = function(e) {
-      is(e.operation, gEvent, "Operation matches: " + e.operation + " " + gEvent);
-      ok(e.id === gChangeId, "Operation id matches");
-      runTest();
-    };
-
-    runTest();
-  }
-
-  var tests = [
-    // Test for GetDataStore
-    testGetDataStores,
-
-    // Event listener
-    eventListener,
-
-    // add
-    function() { testAdd_noKey(1); },
-    function() { testAdd_withKey(123); },
-    function() { testAdd_noKey(124); },
-    function() { testAdd_withKey('foobar'); },
-    function() { testAdd_noKey(125); },
-    function() { testAdd_withKey('125'); },
-    function() { testAdd_withKey('126'); },
-    function() { testAdd_noKey(126); },
-
-    // put
-    function() { testPut(42); },
-    function() { testPut('42'); },
-
-    // get
-    function() { testGet('42'); },
-    function() { testGet(42); },
-    function() { testGet(1); },
-    function() { testGet(123); },
-    function() { testGet(124); },
-    function() { testGet('foobar'); },
-    function() { testGet(125); },
-    function() { testGet('125'); },
-    function() { testGet('126'); },
-    function() { testGet(126); },
-    function() { testArrayGet(['42', 42, 1, 123, 124, 'foobar', 125, '125', '126', 126]); },
-
-    // remove
-    function() { testRemove(42, true); },
-    function() { testRemove('42', true); },
-    function() { testRemove('43', false); },
-    function() { testRemove(43, false); },
-  ];
-
-  function runTest() {
-    if (!tests.length) {
-      finish();
-      return;
-    }
-
-    var test = tests.shift();
-    test();
-  }
-
-  runTest();
-  </script>
-</body>
-</html>
--- a/dom/datastore/tests/file_sync.html
+++ b/dom/datastore/tests/file_sync.html
@@ -46,19 +46,16 @@
   }
 
   function testBasicInterface() {
     var cursor = gStore.sync();
     ok(cursor, "Cursor is created");
     is(cursor.store, gStore, "Cursor.store is the store");
 
     ok("next" in cursor, "Cursor.next exists");
-    ok("close" in cursor, "Cursor.close exists");
-
-    cursor.close();
 
     runTest();
   }
 
   function testCursor(cursor, steps) {
     if (!steps.length) {
       runTest();
       return;
@@ -131,319 +128,241 @@
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     // Test add from scratch
     function() {
       gExpectedEvents = true;
 
-      gStore.add(1).then(function(id) {
+      gStore.add(1,2).then(function(id) {
         gRevisions.push(gStore.revisionId);
-        ok(true, "Item: " + id + " added");
+        ok(true, "Iteme: " + id + " added");
       });
     },
 
     function() {
-      gStore.add(2,"foobar").then(function(id) {
+      gStore.add(2,3).then(function(id) {
         gRevisions.push(gStore.revisionId);
-        ok(true, "Item: " + id + " added");
-      });
-    },
-
-    function() {
-      gStore.add(3,3).then(function(id) {
-        gRevisions.push(gStore.revisionId);
-        ok(true, "Item: " + id + " added");
+        ok(true, "Iteme: " + id + " added");
       });
     },
 
     function() {
       gExpectedEvents = false;
       var cursor = gStore.sync();
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 1 },
-                    { operation: 'add', id: 3, data: 3 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 1 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync('wrong revision ID');
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 1 },
-                    { operation: 'add', id: 3, data: 3 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 1 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[0]);
-      var steps = [ { operation: 'add', id: 1, data: 1 },
-                    { operation: 'add', id: 'foobar', data: 2 },
-                    { operation: 'add', id: 3, data: 3 },
+      var steps = [ { operation: 'add', id: 2, data: 1 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[1]);
-      var steps = [ { operation: 'add', id: 'foobar', data: 2 },
-                    { operation: 'add', id: 3, data: 3 },
+      var steps = [ { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[2]);
-      var steps = [ { operation: 'add', id: 3, data: 3 },
-                    { operation: 'done' }];
-      testCursor(cursor, steps);
-    },
-
-    function() {
-      var cursor = gStore.sync(gRevisions[3]);
       var steps = [ { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     // Test after an update
     function() {
       gExpectedEvents = true;
-      gStore.put(123, 1).then(function() {
+      gStore.put(3, 2).then(function() {
         gRevisions.push(gStore.revisionId);
       });
     },
 
     function() {
       gExpectedEvents = false;
       var cursor = gStore.sync();
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 3, data: 3 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 3 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync('wrong revision ID');
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 3, data: 3 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 3 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[0]);
-      var steps = [ { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 'foobar', data: 2 },
-                    { operation: 'add', id: 3, data: 3 },
+      var steps = [ { operation: 'add', id: 2, data: 3 },
+                    { operation: 'add', id: 3, data: 2 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[1]);
-      var steps = [ { operation: 'add', id: 'foobar', data: 2 },
-                    { operation: 'add', id: 3, data: 3 },
-                    { operation: 'update', id: 1, data: 123 },
+      var steps = [ { operation: 'add', id: 3, data: 2 },
+                    { operation: 'update', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[2]);
-      var steps = [ { operation: 'add', id: 3, data: 3 },
-                    { operation: 'update', id: 1, data: 123 },
+      var steps = [ { operation: 'update', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[3]);
-      var steps = [ { operation: 'update', id: 1, data: 123 },
-                    { operation: 'done' }];
-      testCursor(cursor, steps);
-    },
-
-    function() {
-      var cursor = gStore.sync(gRevisions[4]);
       var steps = [ { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     // Test after a remove
     function() {
       gExpectedEvents = true;
       gStore.remove(3).then(function() {
         gRevisions.push(gStore.revisionId);
       });
     },
 
     function() {
       gExpectedEvents = false;
       var cursor = gStore.sync();
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync('wrong revision ID');
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+                    { operation: 'add', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[0]);
-      var steps = [ { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 'foobar', data: 2 },
+      var steps = [ { operation: 'add', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[1]);
-      var steps = [ { operation: 'add', id: 'foobar', data: 2 },
-                    { operation: 'update', id: 1, data: 123 },
+      var steps = [ { operation: 'update', id: 2, data: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[2]);
-      var steps = [ { operation: 'update', id: 1, data: 123 },
+      var steps = [ { operation: 'update', id: 2, data: 3 },
+                    { operation: 'remove', id: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[3]);
-      var steps = [ { operation: 'update', id: 1, data: 123 },
-                    { operation: 'remove', id: 3 },
+      var steps = [ { operation: 'remove', id: 3 },
                     { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     function() {
       var cursor = gStore.sync(gRevisions[4]);
-      var steps = [ { operation: 'remove', id: 3 },
-                    { operation: 'done' }];
-      testCursor(cursor, steps);
-    },
-
-    function() {
-      var cursor = gStore.sync(gRevisions[5]);
       var steps = [ { operation: 'done' }];
       testCursor(cursor, steps);
     },
 
     // New events when the cursor is active
     function() {
       gCursor = gStore.sync();
       var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 'foobar', data: 2 } ];
+                    { operation: 'add', id: 2, data: 3 } ];
       testCursor(gCursor, steps);
     },
 
     function() {
-      gStore.add(42, 2).then(function(id) {
+      gStore.add(42).then(function(id) {
         ok(true, "Item: " + id + " added");
         gRevisions.push(gStore.revisionId);
         runTest();
       });
     },
 
+    // New events when the cursor is active
+    function() {
+      var steps = [ { operation: 'add', id: 4, data: 42 } ];
+      testCursor(gCursor, steps);
+    },
+
+    function() {
+      gStore.put(42, 2).then(function(id) {
+        gRevisions.push(gStore.revisionId);
+        runTest();
+      });
+    },
+
     function() {
-      var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 2, data: 42 },
-		    { operation: 'add', id: 'foobar', data: 2 } ]
-      testCursor(gCursor, steps);
-    },
-
-    function() {
-      gStore.put(43, 2).then(function(id) {
-        gRevisions.push(gStore.revisionId);
-        runTest();
-      });
-    },
-
-    function() {
-      var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 2, data: 43 },
-		    { operation: 'add', id: 'foobar', data: 2 } ]
+      var steps = [ { operation: 'update', id: 2, data: 42 } ];
       testCursor(gCursor, steps);
     },
 
     function() {
       gStore.remove(2).then(function(id) {
         gRevisions.push(gStore.revisionId);
         runTest();
       });
     },
 
     function() {
-      var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-		    { operation: 'add', id: 'foobar', data: 2 } ]
+      var steps = [ { operation: 'remove', id: 2 } ];
       testCursor(gCursor, steps);
     },
 
     function() {
       gStore.add(42).then(function(id) {
         ok(true, "Item: " + id + " added");
         gRevisions.push(gStore.revisionId);
         runTest();
       });
     },
 
     function() {
-      var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 1, data: 123 },
-                    { operation: 'add', id: 4, data: 42 },
-		    { operation: 'add', id: 'foobar', data: 2 } ]
-      testCursor(gCursor, steps);
-    },
-
-    function() {
-      gStore.clear().then(function() {
-        gRevisions.push(gStore.revisionId);
-        runTest();
-      });
-    },
-
-    function() {
-      var steps = [ { operation: 'clear' } ];
-      testCursor(gCursor, steps);
-    },
-
-    function() {
-      gStore.add(42).then(function(id) {
-        ok(true, "Item: " + id + " added");
-        gRevisions.push(gStore.revisionId);
-        runTest();
-      });
-    },
-
-    function() {
-      var steps = [ { operation: 'clear', },
-                    { operation: 'add', id: 5, data: 42 } ];
+      var steps = [ { operation: 'add', id: 5, data: 42 } ];
       testCursor(gCursor, steps);
     },
 
     function() {
       gStore.clear().then(function() {
         gRevisions.push(gStore.revisionId);
         runTest();
       });
@@ -455,17 +374,17 @@
         gRevisions.push(gStore.revisionId);
         runTest();
       });
     },
 
     function() {
       var steps = [ { operation: 'clear' },
                     { operation: 'add', id: 6, data: 42 },
-                    { operation: 'done'} ];
+                    { operation: 'done' } ];
       testCursor(gCursor, steps);
     },
 
     function() {
       gExpectedEvents = true;
       gStore.add(42).then(function(id) {
       });
     }
--- a/dom/datastore/tests/mochitest.ini
+++ b/dom/datastore/tests/mochitest.ini
@@ -7,20 +7,18 @@ support-files =
   file_changes2.html
   file_app.sjs
   file_app.template.webapp
   file_app2.template.webapp
   file_arrays.html
   file_sync.html
   file_bug924104.html
   file_certifiedApp.html
-  file_keys.html
 
 [test_app_install.html]
 [test_readonly.html]
 [test_basic.html]
 [test_changes.html]
 [test_arrays.html]
 [test_oop.html]
 [test_sync.html]
 [test_bug924104.html]
 [test_certifiedApp.html]
-[test_keys.html]
deleted file mode 100644
--- a/dom/datastore/tests/test_keys.html
+++ /dev/null
@@ -1,128 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for DataStore - string or unsigned long keys</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<div id="container"></div>
-  <script type="application/javascript;version=1.7">
-
-  var gHostedManifestURL = 'http://test/tests/dom/datastore/tests/file_app.sjs?testToken=file_keys.html';
-  var gApp;
-
-  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 uninstallApp() {
-    // Uninstall the app.
-    var request = navigator.mozApps.mgmt.uninstall(gApp);
-    request.onerror = cbError;
-    request.onsuccess = function() {
-      // All done.
-      info("All done");
-      runTest();
-    }
-  }
-
-  function testApp() {
-    var ifr = document.createElement('iframe');
-    ifr.setAttribute('mozbrowser', 'true');
-    ifr.setAttribute('mozapp', gApp.manifestURL);
-    ifr.setAttribute('src', gApp.manifest.launch_path);
-    var domParent = document.getElementById('container');
-
-    // Set us up to listen for messages from the app.
-    var listener = function(e) {
-      var message = e.detail.message;
-      if (/^OK/.exec(message)) {
-        ok(true, "Message from app: " + message);
-      } else if (/KO/.exec(message)) {
-        ok(false, "Message from app: " + message);
-      } else if (/DONE/.exec(message)) {
-        ok(true, "Messaging from app complete");
-        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
-        domParent.removeChild(ifr);
-        runTest();
-      }
-    }
-
-    // This event is triggered when the app calls "alert".
-    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
-    domParent.appendChild(ifr);
-  }
-
-  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],
-                                         ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true],
-                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.setAllAppsLaunchable(true);
-      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
-      runTest();
-    },
-
-    // No confirmation needed when an app is installed
-    function() {
-      SpecialPowers.autoConfirmAppInstall(runTest);
-    },
-
-    // Installing the app
-    installApp,
-
-    // Run tests in app
-    testApp,
-
-    // Uninstall the app
-    uninstallApp
-  ];
-
-  function runTest() {
-    if (!tests.length) {
-      finish();
-      return;
-    }
-
-    var test = tests.shift();
-    test();
-  }
-
-  function finish() {
-    SimpleTest.finish();
-  }
-
-  if (SpecialPowers.isMainProcess()) {
-    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
-  }
-
-  SimpleTest.waitForExplicitFinish();
-  runTest();
-  </script>
-</body>
-</html>
--- a/dom/webidl/DataStore.webidl
+++ b/dom/webidl/DataStore.webidl
@@ -1,40 +1,41 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-typedef (DOMString or unsigned long) DataStoreKey;
-
 [Pref="dom.datastore.enabled",
  JSImplementation="@mozilla.org/dom/datastore;1"]
 interface DataStore : EventTarget {
   // Returns the label of the DataSource.
   readonly attribute DOMString name;
 
   // Returns the origin of the DataSource (e.g., 'facebook.com').
   // This value is the manifest URL of the owner app.
   readonly attribute DOMString owner;
 
   // is readOnly a F(current_app, datastore) function? yes
   readonly attribute boolean readOnly;
 
   // Promise<any>
-  Promise get(DataStoreKey... id);
+  Promise get(unsigned long id);
+
+  // Promise<any>
+  Promise get(sequence<unsigned long> id);
 
   // Promise<void>
-  Promise put(any obj, DataStoreKey id);
+  Promise put(any obj, unsigned long id);
 
-  // Promise<DataStoreKey>
-  Promise add(any obj, optional DataStoreKey id);
+  // Promise<unsigned long>
+  Promise add(any obj, optional unsigned long id);
 
   // Promise<boolean>
-  Promise remove(DataStoreKey id);
+  Promise remove(unsigned long id);
 
   // Promise<void>
   Promise clear();
 
   readonly attribute DOMString revisionId;
 
   attribute EventHandler onchange;
 
@@ -64,11 +65,11 @@ enum DataStoreOperation {
   "clear",
   "done"
 };
 
 dictionary DataStoreTask {
   DOMString revisionId;
 
   DataStoreOperation operation;
-  DataStoreKey id;
+  unsigned long id;
   any data;
 };
--- a/dom/webidl/DataStoreChangeEvent.webidl
+++ b/dom/webidl/DataStoreChangeEvent.webidl
@@ -1,19 +1,19 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 dictionary DataStoreChangeEventInit : EventInit {
   DOMString revisionId = "";
-  DataStoreKey id = 0;
+  unsigned long id = 0;
   DOMString operation = "";
 };
 
 [Pref="dom.datastore.enabled",
  Constructor(DOMString type, optional DataStoreChangeEventInit eventInitDict)]
 interface DataStoreChangeEvent : Event {
   readonly attribute DOMString revisionId;
-  readonly attribute DataStoreKey id;
+  readonly attribute unsigned long id;
   readonly attribute DOMString operation;
 };