зеркало из https://github.com/mozilla/gecko-dev.git
425 строки
14 KiB
JavaScript
425 строки
14 KiB
JavaScript
/* 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";
|
|
|
|
const DEBUG = false;
|
|
function debug(s) {
|
|
if (DEBUG) dump("-*- SettingsManager: " + s + "\n");
|
|
}
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
|
|
Cu.import("resource://gre/modules/SettingsQueue.jsm");
|
|
Cu.import("resource://gre/modules/SettingsDB.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
|
"nsIMessageSender");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "mrm",
|
|
"@mozilla.org/memory-reporter-manager;1",
|
|
"nsIMemoryReporterManager");
|
|
|
|
function SettingsLock(aSettingsManager) {
|
|
this._open = true;
|
|
this._isBusy = false;
|
|
this._requests = new Queue();
|
|
this._settingsManager = aSettingsManager;
|
|
this._transaction = null;
|
|
|
|
let closeHelper = function() {
|
|
if (DEBUG) debug("closing lock");
|
|
this._open = false;
|
|
}.bind(this);
|
|
|
|
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
|
|
}
|
|
|
|
SettingsLock.prototype = {
|
|
get closed() {
|
|
return !this._open;
|
|
},
|
|
|
|
_wrap: function _wrap(obj) {
|
|
return Cu.cloneInto(obj, this._settingsManager._window);
|
|
},
|
|
|
|
process: function process() {
|
|
let lock = this;
|
|
let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);
|
|
|
|
while (!lock._requests.isEmpty()) {
|
|
let info = lock._requests.dequeue();
|
|
if (DEBUG) debug("info: " + info.intent);
|
|
let request = info.request;
|
|
switch (info.intent) {
|
|
case "clear":
|
|
let clearReq = store.clear();
|
|
clearReq.onsuccess = function() {
|
|
this._open = true;
|
|
Services.DOMRequest.fireSuccess(request, 0);
|
|
this._open = false;
|
|
}.bind(lock);
|
|
clearReq.onerror = function() {
|
|
Services.DOMRequest.fireError(request, 0)
|
|
};
|
|
break;
|
|
case "set":
|
|
let keys = Object.getOwnPropertyNames(info.settings);
|
|
if (keys.length) {
|
|
lock._isBusy = true;
|
|
}
|
|
for (let i = 0; i < keys.length; i++) {
|
|
let key = keys[i];
|
|
let last = i === keys.length - 1;
|
|
if (DEBUG) debug("key: " + key + ", val: " + JSON.stringify(info.settings[key]) + ", type: " + typeof(info.settings[key]));
|
|
let checkKeyRequest = store.get(key);
|
|
|
|
checkKeyRequest.onsuccess = function (event) {
|
|
let defaultValue;
|
|
let userValue = info.settings[key];
|
|
if (event.target.result) {
|
|
defaultValue = event.target.result.defaultValue;
|
|
} else {
|
|
defaultValue = null;
|
|
if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + key + " is not in the database.\n");
|
|
}
|
|
|
|
let obj = {settingName: key, defaultValue: defaultValue, userValue: userValue};
|
|
if (DEBUG) debug("store1: " + JSON.stringify(obj));
|
|
let setReq = store.put(obj);
|
|
|
|
setReq.onsuccess = function() {
|
|
cpmm.sendAsyncMessage("Settings:Changed", { key: key, value: userValue });
|
|
if (last && !request.error) {
|
|
lock._open = true;
|
|
Services.DOMRequest.fireSuccess(request, 0);
|
|
lock._open = false;
|
|
}
|
|
};
|
|
|
|
setReq.onerror = function() {
|
|
if (!request.error) {
|
|
Services.DOMRequest.fireError(request, setReq.error.name)
|
|
}
|
|
};
|
|
|
|
if (last) {
|
|
lock._isBusy = false;
|
|
if (!lock._requests.isEmpty()) {
|
|
lock.process();
|
|
}
|
|
}
|
|
};
|
|
checkKeyRequest.onerror = function(event) {
|
|
if (!request.error) {
|
|
Services.DOMRequest.fireError(request, checkKeyRequest.error.name)
|
|
}
|
|
};
|
|
}
|
|
// Don't break here, instead return. Once the previous requests have
|
|
// finished this loop will start again.
|
|
return;
|
|
case "get":
|
|
let getReq = (info.name === "*") ? store.mozGetAll()
|
|
: store.mozGetAll(info.name);
|
|
|
|
getReq.onsuccess = function(event) {
|
|
if (DEBUG) debug("Request for '" + info.name + "' successful. " +
|
|
"Record count: " + event.target.result.length);
|
|
|
|
if (event.target.result.length == 0) {
|
|
if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + info.name + " is not in the database.\n");
|
|
}
|
|
|
|
let results = {};
|
|
|
|
for (var i in event.target.result) {
|
|
let result = event.target.result[i];
|
|
var name = result.settingName;
|
|
if (DEBUG) debug("VAL: " + result.userValue +", " + result.defaultValue + "\n");
|
|
var value = result.userValue !== undefined ? result.userValue : result.defaultValue;
|
|
results[name] = this._wrap(value);
|
|
}
|
|
|
|
this._open = true;
|
|
Services.DOMRequest.fireSuccess(request, this._wrap(results));
|
|
this._open = false;
|
|
}.bind(lock);
|
|
|
|
getReq.onerror = function() {
|
|
Services.DOMRequest.fireError(request, 0)
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
createTransactionAndProcess: function() {
|
|
if (DEBUG) debug("database opened, creating transaction");
|
|
|
|
let manager = this._settingsManager;
|
|
let transactionType = manager.hasWritePrivileges ? "readwrite" : "readonly";
|
|
|
|
this._transaction =
|
|
manager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType);
|
|
|
|
this.process();
|
|
},
|
|
|
|
maybeProcess: function() {
|
|
if (this._transaction && !this._isBusy) {
|
|
this.process();
|
|
}
|
|
},
|
|
|
|
get: function get(aName) {
|
|
if (!this._open) {
|
|
dump("Settings lock not open!\n");
|
|
throw Components.results.NS_ERROR_ABORT;
|
|
}
|
|
|
|
if (this._settingsManager.hasReadPrivileges) {
|
|
let req = Services.DOMRequest.createRequest(this._settingsManager._window);
|
|
this._requests.enqueue({ request: req, intent:"get", name: aName });
|
|
this.maybeProcess();
|
|
return req;
|
|
} else {
|
|
if (DEBUG) debug("get not allowed");
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
},
|
|
|
|
_serializePreservingBinaries: function _serializePreservingBinaries(aObject) {
|
|
// We need to serialize settings objects, otherwise they can change between
|
|
// the set() call and the enqueued request being processed. We can't simply
|
|
// parse(stringify(obj)) because that breaks things like Blobs, Files and
|
|
// Dates, so we use stringify's replacer and parse's reviver parameters to
|
|
// preserve binaries.
|
|
let manager = this._settingsManager;
|
|
let binaries = Object.create(null);
|
|
let stringified = JSON.stringify(aObject, function(key, value) {
|
|
value = manager._settingsDB.prepareValue(value);
|
|
let kind = ObjectWrapper.getObjectKind(value);
|
|
if (kind == "file" || kind == "blob" || kind == "date") {
|
|
let uuid = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
|
|
.generateUUID().toString();
|
|
binaries[uuid] = value;
|
|
return uuid;
|
|
}
|
|
return value;
|
|
});
|
|
return JSON.parse(stringified, function(key, value) {
|
|
if (value in binaries) {
|
|
return binaries[value];
|
|
}
|
|
return value;
|
|
});
|
|
},
|
|
|
|
set: function set(aSettings) {
|
|
if (!this._open) {
|
|
throw "Settings lock not open";
|
|
}
|
|
|
|
if (this._settingsManager.hasWritePrivileges) {
|
|
let req = Services.DOMRequest.createRequest(this._settingsManager._window);
|
|
if (DEBUG) debug("send: " + JSON.stringify(aSettings));
|
|
let settings = this._serializePreservingBinaries(aSettings);
|
|
this._requests.enqueue({request: req, intent: "set", settings: settings});
|
|
this.maybeProcess();
|
|
return req;
|
|
} else {
|
|
if (DEBUG) debug("set not allowed");
|
|
throw "No permission to call set";
|
|
}
|
|
},
|
|
|
|
clear: function clear() {
|
|
if (!this._open) {
|
|
throw "Settings lock not open";
|
|
}
|
|
|
|
if (this._settingsManager.hasWritePrivileges) {
|
|
let req = Services.DOMRequest.createRequest(this._settingsManager._window);
|
|
this._requests.enqueue({ request: req, intent: "clear"});
|
|
this.maybeProcess();
|
|
return req;
|
|
} else {
|
|
if (DEBUG) debug("clear not allowed");
|
|
throw "No permission to call clear";
|
|
}
|
|
},
|
|
|
|
classID: Components.ID("{60c9357c-3ae0-4222-8f55-da01428470d5}"),
|
|
contractID: "@mozilla.org/settingsLock;1",
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
|
};
|
|
|
|
function SettingsManager() {
|
|
this._settingsDB = new SettingsDB();
|
|
this._settingsDB.init();
|
|
}
|
|
|
|
SettingsManager.prototype = {
|
|
_callbacks: null,
|
|
|
|
_wrap: function _wrap(obj) {
|
|
return Cu.cloneInto(obj, this._window);
|
|
},
|
|
|
|
set onsettingchange(aHandler) {
|
|
this.__DOM_IMPL__.setEventHandler("onsettingchange", aHandler);
|
|
},
|
|
|
|
get onsettingchange() {
|
|
return this.__DOM_IMPL__.getEventHandler("onsettingchange");
|
|
},
|
|
|
|
createLock: function() {
|
|
if (DEBUG) debug("get lock!");
|
|
var lock = new SettingsLock(this);
|
|
this._settingsDB.ensureDB(
|
|
function() { lock.createTransactionAndProcess(); },
|
|
function() { dump("Cannot open Settings DB. Trying to open an old version?\n"); }
|
|
);
|
|
return lock;
|
|
},
|
|
|
|
receiveMessage: function(aMessage) {
|
|
if (DEBUG) debug("Settings::receiveMessage: " + aMessage.name);
|
|
let msg = aMessage.json;
|
|
|
|
switch (aMessage.name) {
|
|
case "Settings:Change:Return:OK":
|
|
if (DEBUG) debug('data:' + msg.key + ':' + msg.value + '\n');
|
|
|
|
let event = new this._window.MozSettingsEvent("settingchange", this._wrap({
|
|
settingName: msg.key,
|
|
settingValue: msg.value
|
|
}));
|
|
this.__DOM_IMPL__.dispatchEvent(event);
|
|
|
|
if (this._callbacks && this._callbacks[msg.key]) {
|
|
if (DEBUG) debug("observe callback called! " + msg.key + " " + this._callbacks[msg.key].length);
|
|
this._callbacks[msg.key].forEach(function(cb) {
|
|
cb(this._wrap({settingName: msg.key, settingValue: msg.value}));
|
|
}.bind(this));
|
|
} else {
|
|
if (DEBUG) debug("no observers stored!");
|
|
}
|
|
break;
|
|
default:
|
|
if (DEBUG) debug("Wrong message: " + aMessage.name);
|
|
}
|
|
},
|
|
|
|
addObserver: function addObserver(aName, aCallback) {
|
|
if (DEBUG) debug("addObserver " + aName);
|
|
if (!this._callbacks) {
|
|
cpmm.sendAsyncMessage("Settings:RegisterForMessages");
|
|
this._callbacks = {};
|
|
}
|
|
if (!this._callbacks[aName]) {
|
|
this._callbacks[aName] = [aCallback];
|
|
} else {
|
|
this._callbacks[aName].push(aCallback);
|
|
}
|
|
},
|
|
|
|
removeObserver: function removeObserver(aName, aCallback) {
|
|
if (DEBUG) debug("deleteObserver " + aName);
|
|
if (this._callbacks && this._callbacks[aName]) {
|
|
let index = this._callbacks[aName].indexOf(aCallback)
|
|
if (index != -1) {
|
|
this._callbacks[aName].splice(index, 1)
|
|
} else {
|
|
if (DEBUG) debug("Callback not found for: " + aName);
|
|
}
|
|
} else {
|
|
if (DEBUG) debug("No observers stored for " + aName);
|
|
}
|
|
},
|
|
|
|
init: function(aWindow) {
|
|
mrm.registerStrongReporter(this);
|
|
cpmm.addMessageListener("Settings:Change:Return:OK", this);
|
|
this._window = aWindow;
|
|
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
|
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
|
this.innerWindowID = util.currentInnerWindowID;
|
|
|
|
let readPerm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "settings-read");
|
|
let writePerm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "settings-write");
|
|
this.hasReadPrivileges = readPerm == Ci.nsIPermissionManager.ALLOW_ACTION;
|
|
this.hasWritePrivileges = writePerm == Ci.nsIPermissionManager.ALLOW_ACTION;
|
|
|
|
if (this.hasReadPrivileges) {
|
|
cpmm.sendAsyncMessage("Settings:RegisterForMessages");
|
|
}
|
|
|
|
if (!this.hasReadPrivileges && !this.hasWritePrivileges) {
|
|
dump("No settings permission for: " + aWindow.document.nodePrincipal.origin + "\n");
|
|
Cu.reportError("No settings permission for: " + aWindow.document.nodePrincipal.origin);
|
|
}
|
|
},
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
if (DEBUG) debug("Topic: " + aTopic);
|
|
if (aTopic == "inner-window-destroyed") {
|
|
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
if (wId == this.innerWindowID) {
|
|
this.cleanup();
|
|
}
|
|
}
|
|
},
|
|
|
|
collectReports: function(aCallback, aData) {
|
|
for (var topic in this._callbacks) {
|
|
let length = this._callbacks[topic].length;
|
|
if (length == 0) {
|
|
continue;
|
|
}
|
|
|
|
let path;
|
|
if (length < 20) {
|
|
path = "settings-observers";
|
|
} else {
|
|
path = "settings-observers-suspect/referent(topic=" + topic + ")";
|
|
}
|
|
|
|
aCallback.callback("", path,
|
|
Ci.nsIMemoryReporter.KIND_OTHER,
|
|
Ci.nsIMemoryReporter.UNITS_COUNT,
|
|
this._callbacks[topic].length,
|
|
"The number of settings observers for this topic.",
|
|
aData);
|
|
}
|
|
},
|
|
|
|
cleanup: function() {
|
|
Services.obs.removeObserver(this, "inner-window-destroyed");
|
|
cpmm.removeMessageListener("Settings:Change:Return:OK", this);
|
|
mrm.unregisterStrongReporter(this);
|
|
this._requests = null;
|
|
this._window = null;
|
|
this._innerWindowID = null;
|
|
this._settingsDB.close();
|
|
},
|
|
|
|
classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),
|
|
contractID: "@mozilla.org/settingsManager;1",
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
|
|
Ci.nsIDOMGlobalPropertyInitializer,
|
|
Ci.nsIObserver,
|
|
Ci.nsIMemoryReporter]),
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock])
|