dom/datastore/DataStoreImpl.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 'use strict'
michael@0 8
michael@0 9 this.EXPORTED_SYMBOLS = ["DataStore"];
michael@0 10
michael@0 11 function debug(s) {
michael@0 12 //dump('DEBUG DataStore: ' + s + '\n');
michael@0 13 }
michael@0 14
michael@0 15 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
michael@0 16
michael@0 17 const REVISION_ADDED = "added";
michael@0 18 const REVISION_UPDATED = "updated";
michael@0 19 const REVISION_REMOVED = "removed";
michael@0 20 const REVISION_VOID = "void";
michael@0 21
michael@0 22 // This value has to be tuned a bit. Currently it's just a guess
michael@0 23 // and yet we don't know if it's too low or too high.
michael@0 24 const MAX_REQUESTS = 25;
michael@0 25
michael@0 26 Cu.import("resource://gre/modules/DataStoreCursorImpl.jsm");
michael@0 27 Cu.import("resource://gre/modules/DataStoreDB.jsm");
michael@0 28 Cu.import('resource://gre/modules/Services.jsm');
michael@0 29 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
michael@0 30 Cu.importGlobalProperties(["indexedDB"]);
michael@0 31
michael@0 32 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
michael@0 33 "@mozilla.org/childprocessmessagemanager;1",
michael@0 34 "nsIMessageSender");
michael@0 35
michael@0 36 /* Helper functions */
michael@0 37 function createDOMError(aWindow, aEvent) {
michael@0 38 return new aWindow.DOMError(aEvent);
michael@0 39 }
michael@0 40
michael@0 41 function throwInvalidArg(aWindow) {
michael@0 42 return aWindow.Promise.reject(
michael@0 43 new aWindow.DOMError("SyntaxError", "Non-numeric or invalid id"));
michael@0 44 }
michael@0 45
michael@0 46 function throwReadOnly(aWindow) {
michael@0 47 return aWindow.Promise.reject(
michael@0 48 new aWindow.DOMError("ReadOnlyError", "DataStore in readonly mode"));
michael@0 49 }
michael@0 50
michael@0 51 function validateId(aId) {
michael@0 52 // If string, it cannot be empty.
michael@0 53 if (typeof(aId) == 'string') {
michael@0 54 return aId.length;
michael@0 55 }
michael@0 56
michael@0 57 aId = parseInt(aId);
michael@0 58 return (!isNaN(aId) && aId > 0);
michael@0 59 }
michael@0 60
michael@0 61 /* DataStore object */
michael@0 62 this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
michael@0 63 debug("DataStore created");
michael@0 64 this.init(aWindow, aName, aOwner, aReadOnly);
michael@0 65 }
michael@0 66
michael@0 67 this.DataStore.prototype = {
michael@0 68 classDescription: "DataStore XPCOM Component",
michael@0 69 classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"),
michael@0 70 contractID: "@mozilla.org/dom/datastore-impl;1",
michael@0 71 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
michael@0 72 Components.interfaces.nsIObserver]),
michael@0 73
michael@0 74 callbacks: [],
michael@0 75
michael@0 76 _window: null,
michael@0 77 _name: null,
michael@0 78 _owner: null,
michael@0 79 _readOnly: null,
michael@0 80 _revisionId: null,
michael@0 81 _exposedObject: null,
michael@0 82 _cursor: null,
michael@0 83 _shuttingdown: false,
michael@0 84 _eventTarget: null,
michael@0 85
michael@0 86 init: function(aWindow, aName, aOwner, aReadOnly) {
michael@0 87 debug("DataStore init");
michael@0 88
michael@0 89 this._window = aWindow;
michael@0 90 this._name = aName;
michael@0 91 this._owner = aOwner;
michael@0 92 this._readOnly = aReadOnly;
michael@0 93
michael@0 94 this._db = new DataStoreDB();
michael@0 95 this._db.init(aOwner, aName);
michael@0 96
michael@0 97 Services.obs.addObserver(this, "inner-window-destroyed", false);
michael@0 98
michael@0 99 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 100 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 101 this._innerWindowID = util.currentInnerWindowID;
michael@0 102
michael@0 103 cpmm.addMessageListener("DataStore:Changed:Return:OK", this);
michael@0 104 cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
michael@0 105 { store: this._name, owner: this._owner });
michael@0 106 },
michael@0 107
michael@0 108 observe: function(aSubject, aTopic, aData) {
michael@0 109 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
michael@0 110 if (wId == this._innerWindowID) {
michael@0 111 Services.obs.removeObserver(this, "inner-window-destroyed");
michael@0 112
michael@0 113 cpmm.removeMessageListener("DataStore:Changed:Return:OK", this);
michael@0 114 cpmm.sendAsyncMessage("DataStore:UnregisterForMessages");
michael@0 115 this._shuttingdown = true;
michael@0 116 this._db.close();
michael@0 117 }
michael@0 118 },
michael@0 119
michael@0 120 setEventTarget: function(aEventTarget) {
michael@0 121 this._eventTarget = aEventTarget;
michael@0 122 },
michael@0 123
michael@0 124 newDBPromise: function(aTxnType, aFunction) {
michael@0 125 let self = this;
michael@0 126 return new this._window.Promise(function(aResolve, aReject) {
michael@0 127 debug("DBPromise started");
michael@0 128 self._db.txn(
michael@0 129 aTxnType,
michael@0 130 function(aTxn, aStore, aRevisionStore) {
michael@0 131 debug("DBPromise success");
michael@0 132 aFunction(aResolve, aReject, aTxn, aStore, aRevisionStore);
michael@0 133 },
michael@0 134 function(aEvent) {
michael@0 135 debug("DBPromise error");
michael@0 136 aReject(createDOMError(self._window, aEvent));
michael@0 137 }
michael@0 138 );
michael@0 139 });
michael@0 140 },
michael@0 141
michael@0 142 checkRevision: function(aReject, aRevisionStore, aRevisionId, aCallback) {
michael@0 143 if (!aRevisionId) {
michael@0 144 aCallback();
michael@0 145 return;
michael@0 146 }
michael@0 147
michael@0 148 let self = this;
michael@0 149
michael@0 150 let request = aRevisionStore.openCursor(null, 'prev');
michael@0 151 request.onsuccess = function(aEvent) {
michael@0 152 let cursor = aEvent.target.result;
michael@0 153 if (!cursor) {
michael@0 154 dump("This cannot really happen.");
michael@0 155 return;
michael@0 156 }
michael@0 157
michael@0 158 if (cursor.value.revisionId != aRevisionId) {
michael@0 159 aReject(new self._window.DOMError("ConstraintError",
michael@0 160 "RevisionId is not up-to-date"));
michael@0 161 return;
michael@0 162 }
michael@0 163
michael@0 164 aCallback();
michael@0 165 }
michael@0 166 },
michael@0 167
michael@0 168 getInternal: function(aStore, aIds, aCallback) {
michael@0 169 debug("GetInternal: " + aIds.toSource());
michael@0 170
michael@0 171 // Creation of the results array.
michael@0 172 let results = new Array(aIds.length);
michael@0 173
michael@0 174 // We're going to create this amount of requests.
michael@0 175 let pendingIds = aIds.length;
michael@0 176 let indexPos = 0;
michael@0 177
michael@0 178 let self = this;
michael@0 179
michael@0 180 function getInternalSuccess(aEvent, aPos) {
michael@0 181 debug("GetInternal success. Record: " + aEvent.target.result);
michael@0 182 results[aPos] = Cu.cloneInto(aEvent.target.result, self._window);
michael@0 183 if (!--pendingIds) {
michael@0 184 aCallback(results);
michael@0 185 return;
michael@0 186 }
michael@0 187
michael@0 188 if (indexPos < aIds.length) {
michael@0 189 // Just MAX_REQUESTS requests at the same time.
michael@0 190 let count = 0;
michael@0 191 while (indexPos < aIds.length && ++count < MAX_REQUESTS) {
michael@0 192 getInternalRequest();
michael@0 193 }
michael@0 194 }
michael@0 195 }
michael@0 196
michael@0 197 function getInternalRequest() {
michael@0 198 let currentPos = indexPos++;
michael@0 199 let request = aStore.get(aIds[currentPos]);
michael@0 200 request.onsuccess = function(aEvent) {
michael@0 201 getInternalSuccess(aEvent, currentPos);
michael@0 202 }
michael@0 203 }
michael@0 204
michael@0 205 getInternalRequest();
michael@0 206 },
michael@0 207
michael@0 208 putInternal: function(aResolve, aStore, aRevisionStore, aObj, aId) {
michael@0 209 debug("putInternal " + aId);
michael@0 210
michael@0 211 let self = this;
michael@0 212 let request = aStore.put(aObj, aId);
michael@0 213 request.onsuccess = function(aEvent) {
michael@0 214 debug("putInternal success");
michael@0 215
michael@0 216 self.addRevision(aRevisionStore, aId, REVISION_UPDATED,
michael@0 217 function() {
michael@0 218 debug("putInternal - revisionId increased");
michael@0 219 // No wrap here because the result is always a int.
michael@0 220 aResolve(aEvent.target.result);
michael@0 221 }
michael@0 222 );
michael@0 223 };
michael@0 224 },
michael@0 225
michael@0 226 addInternal: function(aResolve, aStore, aRevisionStore, aObj, aId) {
michael@0 227 debug("AddInternal");
michael@0 228
michael@0 229 let self = this;
michael@0 230 let request = aStore.add(aObj, aId);
michael@0 231 request.onsuccess = function(aEvent) {
michael@0 232 debug("Request successful. Id: " + aEvent.target.result);
michael@0 233 self.addRevision(aRevisionStore, aEvent.target.result, REVISION_ADDED,
michael@0 234 function() {
michael@0 235 debug("AddInternal - revisionId increased");
michael@0 236 // No wrap here because the result is always a int.
michael@0 237 aResolve(aEvent.target.result);
michael@0 238 }
michael@0 239 );
michael@0 240 };
michael@0 241 },
michael@0 242
michael@0 243 removeInternal: function(aResolve, aStore, aRevisionStore, aId) {
michael@0 244 debug("RemoveInternal");
michael@0 245
michael@0 246 let self = this;
michael@0 247 let request = aStore.get(aId);
michael@0 248 request.onsuccess = function(aEvent) {
michael@0 249 debug("RemoveInternal success. Record: " + aEvent.target.result);
michael@0 250 if (aEvent.target.result === undefined) {
michael@0 251 aResolve(false);
michael@0 252 return;
michael@0 253 }
michael@0 254
michael@0 255 let deleteRequest = aStore.delete(aId);
michael@0 256 deleteRequest.onsuccess = function() {
michael@0 257 debug("RemoveInternal success");
michael@0 258 self.addRevision(aRevisionStore, aId, REVISION_REMOVED,
michael@0 259 function() {
michael@0 260 aResolve(true);
michael@0 261 }
michael@0 262 );
michael@0 263 };
michael@0 264 };
michael@0 265 },
michael@0 266
michael@0 267 clearInternal: function(aResolve, aStore, aRevisionStore) {
michael@0 268 debug("ClearInternal");
michael@0 269
michael@0 270 let self = this;
michael@0 271 let request = aStore.clear();
michael@0 272 request.onsuccess = function() {
michael@0 273 debug("ClearInternal success");
michael@0 274 self._db.clearRevisions(aRevisionStore,
michael@0 275 function() {
michael@0 276 debug("Revisions cleared");
michael@0 277
michael@0 278 self.addRevision(aRevisionStore, 0, REVISION_VOID,
michael@0 279 function() {
michael@0 280 debug("ClearInternal - revisionId increased");
michael@0 281 aResolve();
michael@0 282 }
michael@0 283 );
michael@0 284 }
michael@0 285 );
michael@0 286 };
michael@0 287 },
michael@0 288
michael@0 289 getLengthInternal: function(aResolve, aStore) {
michael@0 290 debug("GetLengthInternal");
michael@0 291
michael@0 292 let request = aStore.count();
michael@0 293 request.onsuccess = function(aEvent) {
michael@0 294 debug("GetLengthInternal success: " + aEvent.target.result);
michael@0 295 // No wrap here because the result is always a int.
michael@0 296 aResolve(aEvent.target.result);
michael@0 297 };
michael@0 298 },
michael@0 299
michael@0 300 addRevision: function(aRevisionStore, aId, aType, aSuccessCb) {
michael@0 301 let self = this;
michael@0 302 this._db.addRevision(aRevisionStore, aId, aType,
michael@0 303 function(aRevisionId) {
michael@0 304 self._revisionId = aRevisionId;
michael@0 305 self.sendNotification(aId, aType, aRevisionId);
michael@0 306 aSuccessCb();
michael@0 307 }
michael@0 308 );
michael@0 309 },
michael@0 310
michael@0 311 retrieveRevisionId: function(aSuccessCb) {
michael@0 312 let self = this;
michael@0 313 this._db.revisionTxn(
michael@0 314 'readonly',
michael@0 315 function(aTxn, aRevisionStore) {
michael@0 316 debug("RetrieveRevisionId transaction success");
michael@0 317
michael@0 318 let request = aRevisionStore.openCursor(null, 'prev');
michael@0 319 request.onsuccess = function(aEvent) {
michael@0 320 let cursor = aEvent.target.result;
michael@0 321 if (cursor) {
michael@0 322 self._revisionId = cursor.value.revisionId;
michael@0 323 }
michael@0 324
michael@0 325 aSuccessCb(self._revisionId);
michael@0 326 };
michael@0 327 }
michael@0 328 );
michael@0 329 },
michael@0 330
michael@0 331 sendNotification: function(aId, aOperation, aRevisionId) {
michael@0 332 debug("SendNotification");
michael@0 333 if (aOperation == REVISION_VOID) {
michael@0 334 aOperation = "cleared";
michael@0 335 }
michael@0 336
michael@0 337 cpmm.sendAsyncMessage("DataStore:Changed",
michael@0 338 { store: this.name, owner: this._owner,
michael@0 339 message: { revisionId: aRevisionId, id: aId,
michael@0 340 operation: aOperation, owner: this._owner } } );
michael@0 341 },
michael@0 342
michael@0 343 receiveMessage: function(aMessage) {
michael@0 344 debug("receiveMessage");
michael@0 345
michael@0 346 if (aMessage.name != "DataStore:Changed:Return:OK") {
michael@0 347 debug("Wrong message: " + aMessage.name);
michael@0 348 return;
michael@0 349 }
michael@0 350
michael@0 351 // If this message is not for this DataStore, let's ignore it.
michael@0 352 if (aMessage.data.owner != this._owner ||
michael@0 353 aMessage.data.store != this._name) {
michael@0 354 return;
michael@0 355 }
michael@0 356
michael@0 357 let self = this;
michael@0 358
michael@0 359 this.retrieveRevisionId(
michael@0 360 function() {
michael@0 361 // If the window has been destroyed we don't emit the events.
michael@0 362 if (self._shuttingdown) {
michael@0 363 return;
michael@0 364 }
michael@0 365
michael@0 366 // If we have an active cursor we don't emit events.
michael@0 367 if (self._cursor) {
michael@0 368 return;
michael@0 369 }
michael@0 370
michael@0 371 let event = new self._window.DataStoreChangeEvent('change',
michael@0 372 aMessage.data.message);
michael@0 373 self._eventTarget.dispatchEvent(event);
michael@0 374 }
michael@0 375 );
michael@0 376 },
michael@0 377
michael@0 378 get exposedObject() {
michael@0 379 debug("get exposedObject");
michael@0 380 return this._exposedObject;
michael@0 381 },
michael@0 382
michael@0 383 set exposedObject(aObject) {
michael@0 384 debug("set exposedObject");
michael@0 385 this._exposedObject = aObject;
michael@0 386 },
michael@0 387
michael@0 388 syncTerminated: function(aCursor) {
michael@0 389 // This checks is to avoid that an invalid cursor stops a sync.
michael@0 390 if (this._cursor == aCursor) {
michael@0 391 this._cursor = null;
michael@0 392 }
michael@0 393 },
michael@0 394
michael@0 395 // Public interface :
michael@0 396
michael@0 397 get name() {
michael@0 398 return this._name;
michael@0 399 },
michael@0 400
michael@0 401 get owner() {
michael@0 402 return this._owner;
michael@0 403 },
michael@0 404
michael@0 405 get readOnly() {
michael@0 406 return this._readOnly;
michael@0 407 },
michael@0 408
michael@0 409 get: function() {
michael@0 410 let ids = Array.prototype.slice.call(arguments);
michael@0 411 for (let i = 0; i < ids.length; ++i) {
michael@0 412 if (!validateId(ids[i])) {
michael@0 413 return throwInvalidArg(this._window);
michael@0 414 }
michael@0 415 }
michael@0 416
michael@0 417 let self = this;
michael@0 418
michael@0 419 // Promise<Object>
michael@0 420 return this.newDBPromise("readonly",
michael@0 421 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 422 self.getInternal(aStore, ids,
michael@0 423 function(aResults) {
michael@0 424 aResolve(ids.length > 1 ? aResults : aResults[0]);
michael@0 425 });
michael@0 426 }
michael@0 427 );
michael@0 428 },
michael@0 429
michael@0 430 put: function(aObj, aId, aRevisionId) {
michael@0 431 if (!validateId(aId)) {
michael@0 432 return throwInvalidArg(this._window);
michael@0 433 }
michael@0 434
michael@0 435 if (this._readOnly) {
michael@0 436 return throwReadOnly(this._window);
michael@0 437 }
michael@0 438
michael@0 439 let self = this;
michael@0 440
michael@0 441 // Promise<void>
michael@0 442 return this.newDBPromise("readwrite",
michael@0 443 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 444 self.checkRevision(aReject, aRevisionStore, aRevisionId, function() {
michael@0 445 self.putInternal(aResolve, aStore, aRevisionStore, aObj, aId);
michael@0 446 });
michael@0 447 }
michael@0 448 );
michael@0 449 },
michael@0 450
michael@0 451 add: function(aObj, aId, aRevisionId) {
michael@0 452 if (aId) {
michael@0 453 if (!validateId(aId)) {
michael@0 454 return throwInvalidArg(this._window);
michael@0 455 }
michael@0 456 }
michael@0 457
michael@0 458 if (this._readOnly) {
michael@0 459 return throwReadOnly(this._window);
michael@0 460 }
michael@0 461
michael@0 462 let self = this;
michael@0 463
michael@0 464 // Promise<int>
michael@0 465 return this.newDBPromise("readwrite",
michael@0 466 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 467 self.checkRevision(aReject, aRevisionStore, aRevisionId, function() {
michael@0 468 self.addInternal(aResolve, aStore, aRevisionStore, aObj, aId);
michael@0 469 });
michael@0 470 }
michael@0 471 );
michael@0 472 },
michael@0 473
michael@0 474 remove: function(aId, aRevisionId) {
michael@0 475 if (!validateId(aId)) {
michael@0 476 return throwInvalidArg(this._window);
michael@0 477 }
michael@0 478
michael@0 479 if (this._readOnly) {
michael@0 480 return throwReadOnly(this._window);
michael@0 481 }
michael@0 482
michael@0 483 let self = this;
michael@0 484
michael@0 485 // Promise<void>
michael@0 486 return this.newDBPromise("readwrite",
michael@0 487 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 488 self.checkRevision(aReject, aRevisionStore, aRevisionId, function() {
michael@0 489 self.removeInternal(aResolve, aStore, aRevisionStore, aId);
michael@0 490 });
michael@0 491 }
michael@0 492 );
michael@0 493 },
michael@0 494
michael@0 495 clear: function(aRevisionId) {
michael@0 496 if (this._readOnly) {
michael@0 497 return throwReadOnly(this._window);
michael@0 498 }
michael@0 499
michael@0 500 let self = this;
michael@0 501
michael@0 502 // Promise<void>
michael@0 503 return this.newDBPromise("readwrite",
michael@0 504 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 505 self.checkRevision(aReject, aRevisionStore, aRevisionId, function() {
michael@0 506 self.clearInternal(aResolve, aStore, aRevisionStore);
michael@0 507 });
michael@0 508 }
michael@0 509 );
michael@0 510 },
michael@0 511
michael@0 512 get revisionId() {
michael@0 513 return this._revisionId;
michael@0 514 },
michael@0 515
michael@0 516 getLength: function() {
michael@0 517 let self = this;
michael@0 518
michael@0 519 // Promise<int>
michael@0 520 return this.newDBPromise("readonly",
michael@0 521 function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
michael@0 522 self.getLengthInternal(aResolve, aStore);
michael@0 523 }
michael@0 524 );
michael@0 525 },
michael@0 526
michael@0 527 sync: function(aRevisionId) {
michael@0 528 debug("Sync");
michael@0 529 this._cursor = new DataStoreCursor(this._window, this, aRevisionId);
michael@0 530
michael@0 531 let cursorImpl = this._window.DataStoreCursorImpl.
michael@0 532 _create(this._window, this._cursor);
michael@0 533
michael@0 534 let exposedCursor = new this._window.DataStoreCursor();
michael@0 535 exposedCursor.setDataStoreCursorImpl(cursorImpl);
michael@0 536 return exposedCursor;
michael@0 537 }
michael@0 538 };

mercurial