/* 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/. */ "use strict" /* static functions */ let DEBUG = 0; if (DEBUG) debug = function (s) { dump("-*- SettingsManager: " + s + "\n"); } else debug = function (s) {} function Queue() { this._queue = []; this._index = 0; } Queue.prototype = { getLength: function() { return (this._queue.length - this._index); }, isEmpty: function() { return (this._queue.length == 0); }, enqueue: function(item) { this._queue.push(item); }, dequeue: function() { if(this.isEmpty()) return undefined; var item = this._queue[this._index]; if (++this._index * 2 >= this._queue.length){ this._queue = this._queue.slice(this._index); this._index = 0; } return item; } } const DB_NAME = "settings"; const DB_VERSION = 1; const STORE_NAME = "settings"; function SettingsDB() {} SettingsDB.prototype = { db: null, close: function close() { if (this.db) this.db.close(); }, /** * Prepare the database. This may include opening the database and upgrading * it to the latest schema version. * * @return (via callback) a database ready for use. */ ensureDB: function ensureDB(aSuccessCb, aFailureCb, aGlobal) { if (this.db) { debug("ensureDB: already have a database, returning early."); return; } let self = this; debug("try to open database:" + DB_NAME + " " + DB_VERSION + " " + this._indexedDB); let req = aGlobal.mozIndexedDB.open(DB_NAME, DB_VERSION); req.onsuccess = function (event) { debug("Opened database:", DB_NAME, DB_VERSION); self.db = event.target.result; self.db.onversionchange = function(event) { debug("WARNING: DB modified from a different window."); } aSuccessCb(); }; req.onupgradeneeded = function (aEvent) { debug("Database needs upgrade:" + DB_NAME + aEvent.oldVersion + aEvent.newVersion); debug("Correct new database version:" + aEvent.newVersion == DB_VERSION); let db = aEvent.target.result; switch (aEvent.oldVersion) { case 0: debug("New database"); self.createSchema(db); break; default: debug("No idea what to do with old database version:" + aEvent.oldVersion); aFailureCb(aEvent.target.errorMessage); break; } }; req.onerror = function (aEvent) { debug("Failed to open database:", DB_NAME); aFailureCb(aEvent.target.errorMessage); }; req.onblocked = function (aEvent) { debug("Opening database request is blocked."); }; }, createSchema: function createSchema(aDb) { let objectStore = aDb.createObjectStore(STORE_NAME, {keyPath: "settingName"}); objectStore.createIndex("settingValue", "settingValue", { unique: false }); debug("Created object stores and indexes"); } } const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() { return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); }); const nsIClassInfo = Ci.nsIClassInfo; const SETTINGSLOCK_CONTRACTID = "@mozilla.org/settingsLock;1"; const SETTINGSLOCK_CID = Components.ID("{ef95ddd0-6308-11e1-b86c-0800200c9a66}"); const nsIDOMSettingsLock = Ci.nsIDOMSettingsLock; function SettingsLock(aSettingsManager) { this._open = true; this._requests = new Queue(); this._settingsManager = aSettingsManager; this._transaction = null; } SettingsLock.prototype = { process: function process() { let lock = this; lock._open = false; let store = lock._transaction.objectStore(STORE_NAME); while (!lock._requests.isEmpty()) { let info = lock._requests.dequeue(); debug("info:" + info.intent); let request = info.request; switch (info.intent) { case "clear": let req = store.clear(); req.onsuccess = function() { this._open = true; Services.DOMRequest.fireSuccess(request, 0); this._open = false; }.bind(lock); req.onerror = function() { Services.DOMRequest.fireError(request, 0) }; break; case "set": for (let key in info.settings) { debug("key: " + key + ", val: " + JSON.stringify(info.settings[key]) + ", type: " + typeof(info.settings[key])); if(typeof(info.settings[key]) != 'object') { req = store.put({settingName: key, settingValue: info.settings[key]}); } else { //Workaround for cloning issues let obj = JSON.parse(JSON.stringify(info.settings[key])); req = store.put({settingName: key, settingValue: obj}); } req.onsuccess = function() { lock._open = true; Services.DOMRequest.fireSuccess(request, 0); Services.obs.notifyObservers(lock, "mozsettings-changed", JSON.stringify({ key: key, value: info.settings[key] })); lock._open = false; }; req.onerror = function() { Services.DOMRequest.fireError(request, 0) }; } break; case "get": if (info.name == "*") { req = store.getAll(); } else { req = store.getAll(info.name); } req.onsuccess = function(event) { debug("Request successful. Record count:" + event.target.result.length); debug("result: " + JSON.stringify(event.target.result)); var result = {}; for (var i in event.target.result) result[event.target.result[i].settingName] = event.target.result[i].settingValue; this._open = true; Services.DOMRequest.fireSuccess(request, result); this._open = false; }.bind(lock); req.onerror = function() { Services.DOMRequest.fireError(request, 0)}; break; } } if (!lock._requests.isEmpty()) throw Components.results.NS_ERROR_ABORT; lock._open = true; }, createTransactionAndProcess: function() { if (this._settingsManager._settingsDB.db) { var lock; while (lock = this._settingsManager._locks.dequeue()) { if (!lock._transaction) { lock._transaction = lock._settingsManager._settingsDB.db.transaction(STORE_NAME, "readwrite"); } lock.process(); } if (!this._requests.isEmpty()) this.process(); } }, get: function get(aName) { if (!this._open) throw Components.results.NS_ERROR_ABORT; if (this._settingsManager.hasReadPrivileges || this._settingsManager.hasReadWritePrivileges) { let req = Services.DOMRequest.createRequest(this._settingsManager._window); this._requests.enqueue({ request: req, intent:"get", name: aName }); this.createTransactionAndProcess(); return req; } else { debug("get not allowed"); throw Components.results.NS_ERROR_NOT_IMPLEMENTED; } }, set: function set(aSettings) { if (!this._open) throw Components.results.NS_ERROR_ABORT; if (this._settingsManager.hasReadWritePrivileges) { let req = Services.DOMRequest.createRequest(this._settingsManager._window); debug("send: " + JSON.stringify(aSettings)); this._requests.enqueue({request: req, intent: "set", settings: aSettings}); this.createTransactionAndProcess(); return req; } else { debug("set not allowed"); throw Components.results.NS_ERROR_NOT_IMPLEMENTED; } }, clear: function clear() { if (!this._open) throw Components.results.NS_ERROR_ABORT; if (this._settingsManager.hasReadWritePrivileges) { let req = Services.DOMRequest.createRequest(this._settingsManager._window); this._requests.enqueue({ request: req, intent: "clear"}); this.createTransactionAndProcess(); return req; } else { debug("clear not allowed"); throw Components.results.NS_ERROR_NOT_IMPLEMENTED; } }, classID : SETTINGSLOCK_CID, QueryInterface : XPCOMUtils.generateQI([nsIDOMSettingsLock]), classInfo : XPCOMUtils.generateCI({classID: SETTINGSLOCK_CID, contractID: SETTINGSLOCK_CONTRACTID, classDescription: "SettingsLock", interfaces: [nsIDOMSettingsLock], flags: nsIClassInfo.DOM_OBJECT}) }; const SETTINGSMANAGER_CONTRACTID = "@mozilla.org/settingsManager;1"; const SETTINGSMANAGER_CID = Components.ID("{5609d0a0-52a9-11e1-b86c-0800200c9a66}"); const nsIDOMSettingsManager = Ci.nsIDOMSettingsManager; let myGlobal = this; function SettingsManager() { this._locks = new Queue(); var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager); idbManager.initWindowless(myGlobal); this._settingsDB = new SettingsDB(); } SettingsManager.prototype = { _onsettingchange: null, nextTick: function nextTick(aCallback, thisObj) { if (thisObj) aCallback = aCallback.bind(thisObj); Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL); }, set onsettingchange(aCallback) { if (this.hasReadPrivileges || this.hasReadWritePrivileges) this._onsettingchange = aCallback; else throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, get onsettingchange() { return this._onsettingchange; }, getLock: function() { debug("get lock!"); var lock = new SettingsLock(this); this._locks.enqueue(lock); this._settingsDB.ensureDB( function() { lock.createTransactionAndProcess(); }, function() { dump("ensureDB error cb!\n"); }, myGlobal ); this.nextTick(function() { this._open = false; }, lock); return lock; }, init: function(aWindow) { // Set navigator.mozSettings to null. if (!Services.prefs.getBoolPref("dom.mozSettings.enabled")) return null; this._window = aWindow; Services.obs.addObserver(this, "inner-window-destroyed", false); Services.obs.addObserver(this, "mozsettings-changed", false); let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); this.innerWindowID = util.currentInnerWindowID; let principal = aWindow.document.nodePrincipal; let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); let readPerm = principal == secMan.getSystemPrincipal() ? Ci.nsIPermissionManager.ALLOW_ACTION : Services.perms.testExactPermission(principal.URI, "websettings-read"); let readwritePerm = principal == secMan.getSystemPrincipal() ? Ci.nsIPermissionManager.ALLOW_ACTION : Services.perms.testExactPermission(principal.URI, "websettings-readwrite"); this.hasReadPrivileges = readPerm == Ci.nsIPermissionManager.ALLOW_ACTION; this.hasReadWritePrivileges = readwritePerm == Ci.nsIPermissionManager.ALLOW_ACTION; debug("has read privileges :" + this.hasReadPrivileges + ", has read-write privileges: " + this.hasReadWritePrivileges); }, observe: function(aSubject, aTopic, aData) { debug("Topic: " + aTopic); if (aTopic == "inner-window-destroyed") { let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; if (wId == this.innerWindowID) { Services.obs.removeObserver(this, "inner-window-destroyed"); Services.obs.removeObserver(this, "mozsettings-changed"); this._requests = null; this._window = null; this._innerWindowID = null; this._onsettingchange = null; this._settingsDB.close(); } } else if (aTopic == "mozsettings-changed") { dump('\r\r\r11111111111111111111111111111r\n'); dump(this._window + '\n'); if (!this._onsettingchange) return; let data = JSON.parse(aData); debug(data + ':' + data.key + ':' + data.value + '\n'); let event = new this._window.MozSettingsEvent("settingchanged", { settingName: data.key, settingValue: data.value }); this._onsettingchange.handleEvent(event); dump('\r\r\r2222222222222222222222222222222222\n'); } }, classID : SETTINGSMANAGER_CID, QueryInterface : XPCOMUtils.generateQI([nsIDOMSettingsManager, Ci.nsIDOMGlobalPropertyInitializer]), classInfo : XPCOMUtils.generateCI({classID: SETTINGSMANAGER_CID, contractID: SETTINGSMANAGER_CONTRACTID, classDescription: "SettingsManager", interfaces: [nsIDOMSettingsManager], flags: nsIClassInfo.DOM_OBJECT}) } const NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock])