1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
9 if (DEBUG) dump("-*- SettingsManager: " + s + "\n");
12 const Cc = Components.classes;
13 const Ci = Components.interfaces;
14 const Cu = Components.utils;
16 Cu.import("resource://gre/modules/SettingsQueue.jsm");
17 Cu.import("resource://gre/modules/SettingsDB.jsm");
18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
19 Cu.import("resource://gre/modules/Services.jsm");
20 Cu.import("resource://gre/modules/ObjectWrapper.jsm")
22 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
23 "@mozilla.org/childprocessmessagemanager;1",
26 const nsIClassInfo = Ci.nsIClassInfo;
27 const SETTINGSLOCK_CONTRACTID = "@mozilla.org/settingsLock;1";
28 const SETTINGSLOCK_CID = Components.ID("{60c9357c-3ae0-4222-8f55-da01428470d5}");
29 const nsIDOMSettingsLock = Ci.nsIDOMSettingsLock;
31 function SettingsLock(aSettingsManager)
35 this._requests = new Queue();
36 this._settingsManager = aSettingsManager;
37 this._transaction = null;
40 SettingsLock.prototype = {
46 _wrap: function _wrap(obj) {
47 return ObjectWrapper.wrap(obj, this._settingsManager._window);
50 process: function process() {
53 let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);
55 while (!lock._requests.isEmpty()) {
56 let info = lock._requests.dequeue();
57 if (DEBUG) debug("info: " + info.intent);
58 let request = info.request;
59 switch (info.intent) {
61 let clearReq = store.clear();
62 clearReq.onsuccess = function() {
64 Services.DOMRequest.fireSuccess(request, 0);
67 clearReq.onerror = function() {
68 Services.DOMRequest.fireError(request, 0)
72 let keys = Object.getOwnPropertyNames(info.settings);
73 for (let i = 0; i < keys.length; i++) {
75 let last = i === keys.length - 1;
76 if (DEBUG) debug("key: " + key + ", val: " + JSON.stringify(info.settings[key]) + ", type: " + typeof(info.settings[key]));
78 let checkKeyRequest = store.get(key);
80 checkKeyRequest.onsuccess = function (event) {
82 let userValue = info.settings[key];
83 if (event.target.result) {
84 defaultValue = event.target.result.defaultValue;
87 if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + key + " is not in the database.\n");
90 let obj = {settingName: key, defaultValue: defaultValue, userValue: userValue};
91 if (DEBUG) debug("store1: " + JSON.stringify(obj));
92 let setReq = store.put(obj);
94 setReq.onsuccess = function() {
96 cpmm.sendAsyncMessage("Settings:Changed", { key: key, value: userValue });
97 if (last && !request.error) {
99 Services.DOMRequest.fireSuccess(request, 0);
101 if (!lock._requests.isEmpty()) {
107 setReq.onerror = function() {
108 if (!request.error) {
109 Services.DOMRequest.fireError(request, setReq.error.name)
113 checkKeyRequest.onerror = function(event) {
114 if (!request.error) {
115 Services.DOMRequest.fireError(request, checkKeyRequest.error.name)
121 let getReq = (info.name === "*") ? store.mozGetAll()
122 : store.mozGetAll(info.name);
124 getReq.onsuccess = function(event) {
125 if (DEBUG) debug("Request for '" + info.name + "' successful. " +
126 "Record count: " + event.target.result.length);
128 if (event.target.result.length == 0) {
129 if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + info.name + " is not in the database.\n");
134 for (var i in event.target.result) {
135 let result = event.target.result[i];
136 var name = result.settingName;
137 if (DEBUG) debug("VAL: " + result.userValue +", " + result.defaultValue + "\n");
138 var value = result.userValue !== undefined ? result.userValue : result.defaultValue;
139 results[name] = this._wrap(value);
143 Services.DOMRequest.fireSuccess(request, this._wrap(results));
147 getReq.onerror = function() {
148 Services.DOMRequest.fireError(request, 0)
156 createTransactionAndProcess: function() {
157 if (this._settingsManager._settingsDB._db) {
159 while (lock = this._settingsManager._locks.dequeue()) {
160 if (!lock._transaction) {
161 let transactionType = this._settingsManager.hasWritePrivileges ? "readwrite" : "readonly";
162 lock._transaction = lock._settingsManager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType);
167 this._settingsManager._locks.enqueue(lock);
170 if (!this._requests.isEmpty() && !this._isBusy) {
176 get: function get(aName) {
178 dump("Settings lock not open!\n");
179 throw Components.results.NS_ERROR_ABORT;
182 if (this._settingsManager.hasReadPrivileges) {
183 let req = Services.DOMRequest.createRequest(this._settingsManager._window);
184 this._requests.enqueue({ request: req, intent:"get", name: aName });
185 this.createTransactionAndProcess();
188 if (DEBUG) debug("get not allowed");
189 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
193 _serializePreservingBinaries: function _serializePreservingBinaries(aObject) {
194 // We need to serialize settings objects, otherwise they can change between
195 // the set() call and the enqueued request being processed. We can't simply
196 // parse(stringify(obj)) because that breaks things like Blobs, Files and
197 // Dates, so we use stringify's replacer and parse's reviver parameters to
198 // preserve binaries.
199 let binaries = Object.create(null);
200 let stringified = JSON.stringify(aObject, function(key, value) {
201 let kind = ObjectWrapper.getObjectKind(value);
202 if (kind == "file" || kind == "blob" || kind == "date") {
203 let uuid = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
204 .generateUUID().toString();
205 binaries[uuid] = value;
210 return JSON.parse(stringified, function(key, value) {
211 if (value in binaries) {
212 return binaries[value];
218 set: function set(aSettings) {
220 dump("Settings lock not open!\n");
221 throw Components.results.NS_ERROR_ABORT;
224 if (this._settingsManager.hasWritePrivileges) {
225 let req = Services.DOMRequest.createRequest(this._settingsManager._window);
226 if (DEBUG) debug("send: " + JSON.stringify(aSettings));
227 let settings = this._serializePreservingBinaries(aSettings);
228 this._requests.enqueue({request: req, intent: "set", settings: settings});
229 this.createTransactionAndProcess();
232 if (DEBUG) debug("set not allowed");
233 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
237 clear: function clear() {
239 dump("Settings lock not open!\n");
240 throw Components.results.NS_ERROR_ABORT;
243 if (this._settingsManager.hasWritePrivileges) {
244 let req = Services.DOMRequest.createRequest(this._settingsManager._window);
245 this._requests.enqueue({ request: req, intent: "clear"});
246 this.createTransactionAndProcess();
249 if (DEBUG) debug("clear not allowed");
250 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
254 classID : SETTINGSLOCK_CID,
255 QueryInterface : XPCOMUtils.generateQI([nsIDOMSettingsLock]),
257 classInfo : XPCOMUtils.generateCI({classID: SETTINGSLOCK_CID,
258 contractID: SETTINGSLOCK_CONTRACTID,
259 classDescription: "SettingsLock",
260 interfaces: [nsIDOMSettingsLock],
261 flags: nsIClassInfo.DOM_OBJECT})
264 const SETTINGSMANAGER_CONTRACTID = "@mozilla.org/settingsManager;1";
265 const SETTINGSMANAGER_CID = Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}");
266 const nsIDOMSettingsManager = Ci.nsIDOMSettingsManager;
270 function SettingsManager()
272 this._locks = new Queue();
273 if (!("indexedDB" in myGlobal)) {
274 let idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
275 idbManager.initWindowless(myGlobal);
277 this._settingsDB = new SettingsDB();
278 this._settingsDB.init(myGlobal);
281 SettingsManager.prototype = {
282 _onsettingchange: null,
285 _wrap: function _wrap(obj) {
286 return ObjectWrapper.wrap(obj, this._window);
289 nextTick: function nextTick(aCallback, thisObj) {
291 aCallback = aCallback.bind(thisObj);
293 Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
296 set onsettingchange(aCallback) {
297 if (this.hasReadPrivileges) {
298 if (!this._onsettingchange) {
299 cpmm.sendAsyncMessage("Settings:RegisterForMessages");
301 this._onsettingchange = aCallback;
303 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
307 get onsettingchange() {
308 return this._onsettingchange;
311 createLock: function() {
312 if (DEBUG) debug("get lock!");
313 var lock = new SettingsLock(this);
314 this._locks.enqueue(lock);
315 this._settingsDB.ensureDB(
316 function() { lock.createTransactionAndProcess(); },
317 function() { dump("Cannot open Settings DB. Trying to open an old version?\n"); },
319 this.nextTick(function() { this._open = false; }, lock);
323 receiveMessage: function(aMessage) {
324 if (DEBUG) debug("Settings::receiveMessage: " + aMessage.name);
325 let msg = aMessage.json;
327 switch (aMessage.name) {
328 case "Settings:Change:Return:OK":
329 if (this._onsettingchange || this._callbacks) {
330 if (DEBUG) debug('data:' + msg.key + ':' + msg.value + '\n');
332 if (this._onsettingchange) {
333 let event = new this._window.MozSettingsEvent("settingchanged", this._wrap({
334 settingName: msg.key,
335 settingValue: msg.value
337 this._onsettingchange.handleEvent(event);
339 if (this._callbacks && this._callbacks[msg.key]) {
340 if (DEBUG) debug("observe callback called! " + msg.key + " " + this._callbacks[msg.key].length);
341 this._callbacks[msg.key].forEach(function(cb) {
342 cb(this._wrap({settingName: msg.key, settingValue: msg.value}));
346 if (DEBUG) debug("no observers stored!");
350 if (DEBUG) debug("Wrong message: " + aMessage.name);
354 addObserver: function addObserver(aName, aCallback) {
355 if (DEBUG) debug("addObserver " + aName);
356 if (!this._callbacks) {
357 cpmm.sendAsyncMessage("Settings:RegisterForMessages");
358 this._callbacks = {};
360 if (!this._callbacks[aName]) {
361 this._callbacks[aName] = [aCallback];
363 this._callbacks[aName].push(aCallback);
367 removeObserver: function removeObserver(aName, aCallback) {
368 if (DEBUG) debug("deleteObserver " + aName);
369 if (this._callbacks && this._callbacks[aName]) {
370 let index = this._callbacks[aName].indexOf(aCallback)
372 this._callbacks[aName].splice(index, 1)
374 if (DEBUG) debug("Callback not found for: " + aName);
377 if (DEBUG) debug("No observers stored for " + aName);
381 init: function(aWindow) {
382 cpmm.addMessageListener("Settings:Change:Return:OK", this);
383 this._window = aWindow;
384 Services.obs.addObserver(this, "inner-window-destroyed", false);
385 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
386 this.innerWindowID = util.currentInnerWindowID;
388 let readPerm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "settings-read");
389 let writePerm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "settings-write");
390 this.hasReadPrivileges = readPerm == Ci.nsIPermissionManager.ALLOW_ACTION;
391 this.hasWritePrivileges = writePerm == Ci.nsIPermissionManager.ALLOW_ACTION;
393 if (!this.hasReadPrivileges && !this.hasWritePrivileges) {
394 Cu.reportError("NO SETTINGS PERMISSION FOR: " + aWindow.document.nodePrincipal.origin + "\n");
399 observe: function(aSubject, aTopic, aData) {
400 if (DEBUG) debug("Topic: " + aTopic);
401 if (aTopic == "inner-window-destroyed") {
402 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
403 if (wId == this.innerWindowID) {
404 Services.obs.removeObserver(this, "inner-window-destroyed");
405 cpmm.removeMessageListener("Settings:Change:Return:OK", this);
406 this._requests = null;
408 this._innerWindowID = null;
409 this._onsettingchange = null;
410 this._settingsDB.close();
415 classID : SETTINGSMANAGER_CID,
416 QueryInterface : XPCOMUtils.generateQI([nsIDOMSettingsManager, Ci.nsIDOMGlobalPropertyInitializer]),
418 classInfo : XPCOMUtils.generateCI({classID: SETTINGSMANAGER_CID,
419 contractID: SETTINGSMANAGER_CONTRACTID,
420 classDescription: "SettingsManager",
421 interfaces: [nsIDOMSettingsManager],
422 flags: nsIClassInfo.DOM_OBJECT})
425 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock])