Bug 871445 - patch 9 - DataStore: child<->parent communication, r=ehsan

--HG--
rename : dom/datastore/tests/test_basic.html => dom/datastore/tests/test_oop.html
This commit is contained in:
Andrea Marchesini 2013-10-02 13:27:15 -04:00
parent bc8f1080be
commit 263e781a73
5 changed files with 537 additions and 392 deletions

View File

@ -6,7 +6,7 @@
'use strict' 'use strict'
this.EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"]; this.EXPORTED_SYMBOLS = ["DataStore"];
function debug(s) { function debug(s) {
// dump('DEBUG DataStore: ' + s + '\n'); // dump('DEBUG DataStore: ' + s + '\n');
@ -67,13 +67,13 @@ function parseIds(aId) {
return aId; return aId;
} }
/* Exposed DataStore object */ /* DataStore object */
function ExposedDataStore(aWindow, aDataStore, aReadOnly) { this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
debug("ExposedDataStore created"); debug("DataStore created");
this.init(aWindow, aDataStore, aReadOnly); this.init(aWindow, aName, aOwner, aReadOnly);
} }
ExposedDataStore.prototype = { this.DataStore.prototype = {
classDescription: "DataStore XPCOM Component", classDescription: "DataStore XPCOM Component",
classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"), classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"),
contractID: "@mozilla.org/dom/datastore;1", contractID: "@mozilla.org/dom/datastore;1",
@ -81,286 +81,46 @@ ExposedDataStore.prototype = {
callbacks: [], callbacks: [],
init: function(aWindow, aDataStore, aReadOnly) { _window: null,
debug("ExposedDataStore init"); _name: null,
_owner: null,
_readOnly: null,
_revisionId: null,
this.window = aWindow; init: function(aWindow, aName, aOwner, aReadOnly) {
this.dataStore = aDataStore; debug("DataStore init");
this.isReadOnly = aReadOnly;
},
receiveMessage: function(aMessage) { this._window = aWindow;
debug("receiveMessage"); this._name = aName;
this._owner = aOwner;
this._readOnly = aReadOnly;
if (aMessage.name != "DataStore:Changed:Return:OK") { this._db = new DataStoreDB();
debug("Wrong message: " + aMessage.name); this._db.init(aOwner, aName);
return;
}
let self = this; let self = this;
Services.obs.addObserver(function(aSubject, aTopic, aData) {
this.dataStore.retrieveRevisionId( let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
function() { if (wId == self._innerWindowID) {
let event = new self.window.DataStoreChangeEvent('change', aMessage.data); cpmm.removeMessageListener("DataStore:Changed:Return:OK", self);
self.__DOM_IMPL__.dispatchEvent(event); self._db.delete();
},
// Forcing the reading of the revisionId
true
);
},
// Public interface :
get name() {
return this.dataStore.name;
},
get owner() {
return this.dataStore.owner;
},
get readOnly() {
return this.isReadOnly;
},
get: function(aId) {
aId = parseIds(aId);
if (aId === null) {
return throwInvalidArg(this.window);
} }
}, "inner-window-destroyed", false);
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
this._innerWindowID = util.currentInnerWindowID;
cpmm.addMessageListener("DataStore:Changed:Return:OK", this);
cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
{ store: this._name, owner: this._owner });
},
newDBPromise: function(aTxnType, aFunction) {
let self = this; let self = this;
return new this._window.Promise(function(aResolve, aReject) {
// Promise<Object>
return this.dataStore.newDBPromise(this.window, "readonly",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.getInternal(aStore,
Array.isArray(aId) ? aId : [ aId ],
function(aResults) {
aResolve(Array.isArray(aId) ? aResults : aResults[0]);
});
}
);
},
update: function(aId, aObj) {
aId = parseInt(aId);
if (isNaN(aId) || aId <= 0) {
return throwInvalidArg(this.window);
}
if (this.isReadOnly) {
return throwReadOnly(this.window);
}
let self = this;
// Promise<void>
return this.dataStore.newDBPromise(this.window, "readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.updateInternal(aResolve, aStore, aRevisionStore, aId, aObj);
}
);
},
add: function(aObj) {
if (this.isReadOnly) {
return throwReadOnly(this.window);
}
let self = this;
// Promise<int>
return this.dataStore.newDBPromise(this.window, "readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.addInternal(aResolve, aStore, aRevisionStore, aObj);
}
);
},
remove: function(aId) {
aId = parseInt(aId);
if (isNaN(aId) || aId <= 0) {
return throwInvalidArg(this.window);
}
if (this.isReadOnly) {
return throwReadOnly(this.window);
}
let self = this;
// Promise<void>
return this.dataStore.newDBPromise(this.window, "readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.removeInternal(aResolve, aStore, aRevisionStore, aId);
}
);
},
clear: function() {
if (this.isReadOnly) {
return throwReadOnly(this.window);
}
let self = this;
// Promise<void>
return this.dataStore.newDBPromise(this.window, "readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.clearInternal(aResolve, aStore, aRevisionStore);
}
);
},
get revisionId() {
return this.dataStore.revisionId;
},
getChanges: function(aRevisionId) {
debug("GetChanges: " + aRevisionId);
if (aRevisionId === null || aRevisionId === undefined) {
return this.window.Promise.reject(
new this.window.DOMError("SyntaxError", "Invalid revisionId"));
}
let self = this;
// Promise<DataStoreChanges>
return new this.window.Promise(function(aResolve, aReject) {
debug("GetChanges promise started");
self.dataStore.db.revisionTxn(
'readonly',
function(aTxn, aStore) {
debug("GetChanges transaction success");
let request = self.dataStore.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(self.window.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, self.window);
aResolve(wrappedObject);
};
}
);
},
function(aEvent) {
debug("GetChanges transaction failed");
aReject(createDOMError(self.window, aEvent));
}
);
});
},
getLength: function() {
let self = this;
// Promise<int>
return this.dataStore.newDBPromise(this.window, "readonly",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.dataStore.getLengthInternal(aResolve, aStore);
}
);
},
set onchange(aCallback) {
debug("Set OnChange");
this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
},
get onchange() {
debug("Get OnChange");
return this.__DOM_IMPL__.getEventHandler("onchange");
}
};
/* DataStore object */
function DataStore(aAppId, aName, aOwner, aReadOnly, aGlobalScope) {
this.appId = aAppId;
this.name = aName;
this.owner = aOwner;
this.readOnly = aReadOnly;
this.db = new DataStoreDB();
this.db.init(aOwner, aName, aGlobalScope);
}
DataStore.prototype = {
appId: null,
name: null,
owner: null,
readOnly: null,
revisionId: null,
newDBPromise: function(aWindow, aTxnType, aFunction) {
let db = this.db;
return new aWindow.Promise(function(aResolve, aReject) {
debug("DBPromise started"); debug("DBPromise started");
db.txn( self._db.txn(
aTxnType, aTxnType,
function(aTxn, aStore, aRevisionStore) { function(aTxn, aStore, aRevisionStore) {
debug("DBPromise success"); debug("DBPromise success");
@ -368,7 +128,7 @@ DataStore.prototype = {
}, },
function(aEvent) { function(aEvent) {
debug("DBPromise error"); debug("DBPromise error");
aReject(createDOMError(aWindow, aEvent)); aReject(createDOMError(self._window, aEvent));
} }
); );
}); });
@ -478,7 +238,7 @@ DataStore.prototype = {
let request = aStore.clear(); let request = aStore.clear();
request.onsuccess = function() { request.onsuccess = function() {
debug("ClearInternal success"); debug("ClearInternal success");
self.db.clearRevisions(aRevisionStore, self._db.clearRevisions(aRevisionStore,
function() { function() {
debug("Revisions cleared"); debug("Revisions cleared");
@ -506,23 +266,18 @@ DataStore.prototype = {
addRevision: function(aRevisionStore, aId, aType, aSuccessCb) { addRevision: function(aRevisionStore, aId, aType, aSuccessCb) {
let self = this; let self = this;
this.db.addRevision(aRevisionStore, aId, aType, this._db.addRevision(aRevisionStore, aId, aType,
function(aRevisionId) { function(aRevisionId) {
self.revisionId = aRevisionId; self._revisionId = aRevisionId;
self.sendNotification(aId, aType, aRevisionId); self.sendNotification(aId, aType, aRevisionId);
aSuccessCb(); aSuccessCb();
} }
); );
}, },
retrieveRevisionId: function(aSuccessCb, aForced) { retrieveRevisionId: function(aSuccessCb) {
if (this.revisionId != null && !aForced) {
aSuccessCb();
return;
}
let self = this; let self = this;
this.db.revisionTxn( this._db.revisionTxn(
'readwrite', 'readwrite',
function(aTxn, aRevisionStore) { function(aTxn, aRevisionStore) {
debug("RetrieveRevisionId transaction success"); debug("RetrieveRevisionId transaction success");
@ -534,48 +289,20 @@ DataStore.prototype = {
// If the revision doesn't exist, let's create the first one. // If the revision doesn't exist, let's create the first one.
self.addRevision(aRevisionStore, 0, REVISION_VOID, self.addRevision(aRevisionStore, 0, REVISION_VOID,
function(aRevisionId) { function(aRevisionId) {
self.revisionId = aRevisionId; self._revisionId = aRevisionId;
aSuccessCb(); aSuccessCb();
} }
); );
return; return;
} }
self.revisionId = cursor.value.revisionId; self._revisionId = cursor.value.revisionId;
aSuccessCb(); aSuccessCb();
}; };
} }
); );
}, },
exposeObject: function(aWindow, aReadOnly) {
debug("Exposing Object");
let exposedObject = new ExposedDataStore(aWindow, this, aReadOnly);
let object = aWindow.DataStore._create(aWindow, exposedObject);
Services.obs.addObserver(function(aSubject, aTopic, aData) {
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == exposedObject.innerWindowID) {
cpmm.removeMessageListener("DataStore:Changed:Return:OK", exposedObject);
}
}, "inner-window-destroyed", false);
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
exposedObject.innerWindowID = util.currentInnerWindowID;
cpmm.addMessageListener("DataStore:Changed:Return:OK", exposedObject);
cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
{ store: this.name, owner: this.owner });
return object;
},
delete: function() {
this.db.delete();
},
sendNotification: function(aId, aOperation, aRevisionId) { sendNotification: function(aId, aOperation, aRevisionId) {
debug("SendNotification"); debug("SendNotification");
if (aOperation != REVISION_VOID) { if (aOperation != REVISION_VOID) {
@ -584,16 +311,250 @@ DataStore.prototype = {
message: { revisionId: aRevisionId, id: aId, message: { revisionId: aRevisionId, id: aId,
operation: aOperation } } ); operation: aOperation } } );
} }
},
receiveMessage: function(aMessage) {
debug("receiveMessage");
if (aMessage.name != "DataStore:Changed:Return:OK") {
debug("Wrong message: " + aMessage.name);
return;
} }
let self = this;
this.retrieveRevisionId(
function() {
let event = new self._window.DataStoreChangeEvent('change', aMessage.data);
self.__DOM_IMPL__.dispatchEvent(event);
}
);
},
// Public interface :
get name() {
return this._name;
},
get owner() {
return this._owner;
},
get readOnly() {
return this._readOnly;
},
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,
Array.isArray(aId) ? aId : [ aId ],
function(aResults) {
aResolve(Array.isArray(aId) ? aResults : aResults[0]);
});
}
);
},
update: function(aId, aObj) {
aId = parseInt(aId);
if (isNaN(aId) || aId <= 0) {
return throwInvalidArg(this._window);
}
if (this._readOnly) {
return throwReadOnly(this._window);
}
let self = this;
// Promise<void>
return this.newDBPromise("readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.updateInternal(aResolve, aStore, aRevisionStore, aId, aObj);
}
);
},
add: function(aObj) {
if (this._readOnly) {
return throwReadOnly(this._window);
}
let self = this;
// Promise<int>
return this.newDBPromise("readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.addInternal(aResolve, aStore, aRevisionStore, aObj);
}
);
},
remove: function(aId) {
aId = parseInt(aId);
if (isNaN(aId) || aId <= 0) {
return throwInvalidArg(this._window);
}
if (this._readOnly) {
return throwReadOnly(this._window);
}
let self = this;
// Promise<void>
return this.newDBPromise("readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.removeInternal(aResolve, aStore, aRevisionStore, aId);
}
);
},
clear: function() {
if (this._readOnly) {
return throwReadOnly(this._window);
}
let self = this;
// Promise<void>
return this.newDBPromise("readwrite",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.clearInternal(aResolve, aStore, aRevisionStore);
}
);
},
get revisionId() {
return this._revisionId;
},
getChanges: function(aRevisionId) {
debug("GetChanges: " + aRevisionId);
if (aRevisionId === null || aRevisionId === undefined) {
return this._window.Promise.reject(
new this._window.DOMError("SyntaxError", "Invalid revisionId"));
}
let self = this;
// Promise<DataStoreChanges>
return new this._window.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: {}
}; };
/* DataStoreAccess */ let request = aStore.mozGetAll(self._window.IDBKeyRange.lowerBound(aInternalRevisionId, true));
request.onsuccess = function(aEvent) {
for (let i = 0; i < aEvent.target.result.length; ++i) {
let data = aEvent.target.result[i];
function DataStoreAccess(aAppId, aName, aOrigin, aReadOnly) { switch (data.operation) {
this.appId = aAppId; case REVISION_ADDED:
this.name = aName; changes.addedIds[data.objectId] = true;
this.origin = aOrigin; break;
this.readOnly = aReadOnly;
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;
} }
DataStoreAccess.prototype = {}; 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, self._window);
aResolve(wrappedObject);
};
}
);
},
function(aEvent) {
debug("GetChanges transaction failed");
aReject(createDOMError(self._window, aEvent));
}
);
});
},
getLength: function() {
let self = this;
// Promise<int>
return this.newDBPromise("readonly",
function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
self.getLengthInternal(aResolve, aStore);
}
);
},
set onchange(aCallback) {
debug("Set OnChange");
this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
},
get onchange() {
debug("Get OnChange");
return this.__DOM_IMPL__.getEventHandler("onchange");
}
};

View File

@ -15,8 +15,6 @@ function debug(s) {
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
const kFromDataStoreChangeNotifier = "fromDataStoreChangeNotifier";
XPCOMUtils.defineLazyServiceGetter(this, "ppmm", XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1", "@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster"); "nsIMessageBroadcaster");

View File

@ -17,6 +17,15 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DataStore.jsm'); Cu.import('resource://gre/modules/DataStore.jsm');
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
/* DataStoreService */ /* DataStoreService */
@ -32,6 +41,12 @@ function DataStoreService() {
} }
obs.addObserver(this, 'webapps-clear-data', false); obs.addObserver(this, 'webapps-clear-data', false);
let inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (inParent) {
ppmm.addMessageListener("DataStore:Get", this);
}
} }
DataStoreService.prototype = { DataStoreService.prototype = {
@ -49,13 +64,11 @@ DataStoreService.prototype = {
return; return;
} }
let store = new DataStore(aAppId, aName, aOwner, aReadOnly);
if (!(aName in this.stores)) { if (!(aName in this.stores)) {
this.stores[aName] = {}; this.stores[aName] = {};
} }
this.stores[aName][aAppId] = store; this.stores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
}, },
installAccessDataStore: function(aAppId, aName, aOwner, aReadOnly) { installAccessDataStore: function(aAppId, aName, aOwner, aReadOnly) {
@ -68,80 +81,69 @@ DataStoreService.prototype = {
return; return;
} }
let accessStore = new DataStoreAccess(aAppId, aName, aOwner, aReadOnly);
if (!(aName in this.accessStores)) { if (!(aName in this.accessStores)) {
this.accessStores[aName] = {}; this.accessStores[aName] = {};
} }
this.accessStores[aName][aAppId] = accessStore; this.accessStores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
}, },
getDataStores: function(aWindow, aName) { getDataStores: function(aWindow, aName) {
debug('getDataStores - aName: ' + aName); debug('getDataStores - aName: ' + aName);
let appId = aWindow.document.nodePrincipal.appId;
let self = this; // This method can be called in the child so we need to send a request to
// the parent and create DataStore object here.
return new aWindow.Promise(function(resolve, reject) { return new aWindow.Promise(function(resolve, reject) {
let matchingStores = []; new DataStoreServiceChild(aWindow, aName, resolve);
});
},
if (aName in self.stores) { receiveMessage: function(aMessage) {
if (appId in self.stores[aName]) { if (aMessage.name != 'DataStore:Get') {
matchingStores.push({ store: self.stores[aName][appId], return;
readonly: false });
} }
for (var i in self.stores[aName]) { let msg = aMessage.data;
// This is a security issue and it will be fixed by Bug 916091
let appId = msg.appId;
let results = [];
if (msg.name in this.stores) {
if (appId in this.stores[msg.name]) {
results.push({ store: this.stores[msg.name][appId],
readOnly: false });
}
for (var i in this.stores[msg.name]) {
if (i == appId) { if (i == appId) {
continue; continue;
} }
let access = self.getDataStoreAccess(self.stores[aName][i], appId); let access = this.getDataStoreAccess(msg.name, appId);
if (!access) { if (!access) {
continue; continue;
} }
let readOnly = self.stores[aName][i].readOnly || access.readOnly; let readOnly = this.stores[msg.name][i].readOnly || access.readOnly;
matchingStores.push({ store: self.stores[aName][i], results.push({ store: this.stores[msg.name][i],
readonly: readOnly }); readOnly: readOnly });
} }
} }
let callbackPending = matchingStores.length; msg.stores = results;
let results = []; aMessage.target.sendAsyncMessage("DataStore:Get:Return", msg);
if (!callbackPending) {
resolve(results);
return;
}
for (let i = 0; i < matchingStores.length; ++i) {
let obj = matchingStores[i].store.exposeObject(aWindow,
matchingStores[i].readonly);
results.push(obj);
matchingStores[i].store.retrieveRevisionId(
function() {
--callbackPending;
if (!callbackPending) {
resolve(results);
}
},
// if the revision is already known, we don't need to retrieve it
// again.
false
);
}
});
}, },
getDataStoreAccess: function(aStore, aAppId) { getDataStoreAccess: function(aName, aAppId) {
if (!(aStore.name in this.accessStores) || if (!(aName in this.accessStores) ||
!(aAppId in this.accessStores[aStore.name])) { !(aAppId in this.accessStores[aName])) {
return null; return null;
} }
return this.accessStores[aStore.name][aAppId]; return this.accessStores[aName][aAppId];
}, },
observe: function observe(aSubject, aTopic, aData) { observe: function observe(aSubject, aTopic, aData) {
@ -160,7 +162,6 @@ DataStoreService.prototype = {
for (let key in this.stores) { for (let key in this.stores) {
if (params.appId in this.stores[key]) { if (params.appId in this.stores[key]) {
this.stores[key][params.appId].delete();
delete this.stores[key][params.appId]; delete this.stores[key][params.appId];
} }
@ -181,4 +182,59 @@ DataStoreService.prototype = {
}) })
}; };
/* DataStoreServiceChild */
function DataStoreServiceChild(aWindow, aName, aResolve) {
debug("DataStoreServiceChild created");
this.init(aWindow, aName, aResolve);
}
DataStoreServiceChild.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
init: function(aWindow, aName, aResolve) {
this._window = aWindow;
this._name = aName;
this._resolve = aResolve;
this.initDOMRequestHelper(aWindow, [ "DataStore:Get:Return" ]);
// This is a security issue and it will be fixed by Bug 916091
cpmm.sendAsyncMessage("DataStore:Get",
{ name: aName, appId: aWindow.document.nodePrincipal.appId });
},
receiveMessage: function(aMessage) {
if (aMessage.name != 'DataStore:Get:Return') {
return;
}
let msg = aMessage.data;
let self = this;
let callbackPending = msg.stores.length;
let results = [];
if (!callbackPending) {
this._resolve(results);
return;
}
for (let i = 0; i < msg.stores.length; ++i) {
let obj = new DataStore(this._window, this._name,
msg.stores[i].owner, msg.stores[i].readOnly);
let exposedObj = this._window.DataStore._create(this._window, obj);
results.push(exposedObj);
obj.retrieveRevisionId(
function() {
--callbackPending;
if (!callbackPending) {
self._resolve(results);
}
}
);
}
}
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataStoreService]); this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataStoreService]);

View File

@ -28,6 +28,7 @@ MOCHITEST_FILES = \
file_app2.template.webapp \ file_app2.template.webapp \
test_arrays.html \ test_arrays.html \
file_arrays.html \ file_arrays.html \
test_oop.html \
$(NULL) $(NULL)
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,129 @@
<!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>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var gHostedManifestURL = 'http://test/tests/dom/datastore/tests/file_app.sjs?testToken=file_basic.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]]}, runTest);
},
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
},
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
},
function() {
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();
}
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
SimpleTest.waitForExplicitFinish();
runTest();
</script>
</body>
</html>