dom/datastore/DataStoreImpl.jsm

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

mercurial