gecko-dev/dom/settings/SettingsService.js

348 lines
11 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 Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
/* static functions */
let DEBUG = false;
let VERBOSE = false;
try {
DEBUG =
Services.prefs.getBoolPref("dom.mozSettings.SettingsService.debug.enabled");
VERBOSE =
Services.prefs.getBoolPref("dom.mozSettings.SettingsService.verbose.enabled");
} catch (ex) { }
function debug(s) {
dump("-*- SettingsService: " + s + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "mrm",
"@mozilla.org/memory-reporter-manager;1",
"nsIMemoryReporterManager");
const nsIClassInfo = Ci.nsIClassInfo;
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
const SETTINGSSERVICELOCK_CONTRACTID = "@mozilla.org/settingsServiceLock;1";
const SETTINGSSERVICELOCK_CID = Components.ID("{d7a395a0-e292-11e1-834e-1761d57f5f99}");
const nsISettingsServiceLock = Ci.nsISettingsServiceLock;
function makeSettingsServiceRequest(aCallback, aName, aValue) {
return {
callback: aCallback,
name: aName,
value: aValue
};
};
function SettingsServiceLock(aSettingsService, aTransactionCallback) {
if (VERBOSE) debug("settingsServiceLock constr!");
this._open = true;
this._settingsService = aSettingsService;
this._id = uuidgen.generateUUID().toString();
this._transactionCallback = aTransactionCallback;
this._requests = {};
let closeHelper = function() {
if (VERBOSE) debug("closing lock " + this._id);
this._open = false;
this.runOrFinalizeQueries();
}.bind(this);
let msgs = ["Settings:Get:OK", "Settings:Get:KO",
"Settings:Clear:OK", "Settings:Clear:KO",
"Settings:Set:OK", "Settings:Set:KO",
"Settings:Finalize:OK", "Settings:Finalize:KO"];
for (let msg in msgs) {
cpmm.addMessageListener(msgs[msg], this);
}
let createLockPayload = {
lockID: this._id,
isServiceLock: true,
windowID: undefined,
lockStack: (new Error).stack
};
this.returnMessage("Settings:CreateLock", createLockPayload);
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
}
SettingsServiceLock.prototype = {
get closed() {
return !this._open;
},
returnMessage: function(aMessage, aData) {
SettingsRequestManager.receiveMessage({
name: aMessage,
data: aData,
target: undefined,
principal: Services.scriptSecurityManager.getSystemPrincipal()
});
},
runOrFinalizeQueries: function() {
if (!this._requests || Object.keys(this._requests).length == 0) {
this.returnMessage("Settings:Finalize", {lockID: this._id});
} else {
this.returnMessage("Settings:Run", {lockID: this._id});
}
},
receiveMessage: function(aMessage) {
let msg = aMessage.data;
// SettingsRequestManager broadcasts changes to all locks in the child. If
// our lock isn't being addressed, just return.
if(msg.lockID != this._id) {
return;
}
if (VERBOSE) debug("receiveMessage (" + this._id + "): " + aMessage.name);
// Finalizing a transaction does not return a request ID since we are
// supposed to fire callbacks.
if (!msg.requestID) {
switch (aMessage.name) {
case "Settings:Finalize:OK":
if (VERBOSE) debug("Lock finalize ok!");
this.callTransactionHandle();
break;
case "Settings:Finalize:KO":
if (DEBUG) debug("Lock finalize failed!");
this.callAbort();
break;
default:
if (DEBUG) debug("Message type " + aMessage.name + " is missing a requestID");
}
this._settingsService.unregisterLock(this._id);
return;
}
let req = this._requests[msg.requestID];
if (!req) {
if (DEBUG) debug("Matching request not found.");
return;
}
delete this._requests[msg.requestID];
switch (aMessage.name) {
case "Settings:Get:OK":
this._open = true;
let settings_names = Object.keys(msg.settings);
if (settings_names.length > 0) {
let name = settings_names[0];
if (DEBUG && settings_names.length > 1) {
debug("Warning: overloaded setting:" + name);
}
let result = msg.settings[name];
this.callHandle(req.callback, name, result);
} else {
this.callHandle(req.callback, req.name, null);
}
this._open = false;
break;
case "Settings:Set:OK":
this._open = true;
// We don't pass values back from sets in SettingsManager...
this.callHandle(req.callback, req.name, req.value);
this._open = false;
break;
case "Settings:Get:KO":
case "Settings:Set:KO":
if (DEBUG) debug("error:" + msg.errorMsg);
this.callError(req.callback, msg.error);
break;
default:
if (DEBUG) debug("Wrong message: " + aMessage.name);
}
this.runOrFinalizeQueries();
},
get: function get(aName, aCallback) {
if (VERBOSE) debug("get (" + this._id + "): " + aName);
if (!this._open) {
if (DEBUG) debug("Settings lock not open!\n");
throw Components.results.NS_ERROR_ABORT;
}
let reqID = uuidgen.generateUUID().toString();
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName);
this.returnMessage("Settings:Get", {requestID: reqID,
lockID: this._id,
name: aName});
},
set: function set(aName, aValue, aCallback) {
if (VERBOSE) debug("set: " + aName + " " + aValue);
if (!this._open) {
throw "Settings lock not open";
}
let reqID = uuidgen.generateUUID().toString();
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName, aValue);
let settings = {};
settings[aName] = aValue;
this.returnMessage("Settings:Set", {requestID: reqID,
lockID: this._id,
settings: settings});
},
callHandle: function callHandle(aCallback, aName, aValue) {
try {
aCallback && aCallback.handle ? aCallback.handle(aName, aValue) : null;
} catch (e) {
if (DEBUG) debug("settings 'handle' for " + aName + " callback threw an exception, dropping: " + e + "\n");
}
},
callAbort: function callAbort(aCallback, aMessage) {
try {
aCallback && aCallback.handleAbort ? aCallback.handleAbort(aMessage) : null;
} catch (e) {
if (DEBUG) debug("settings 'abort' callback threw an exception, dropping: " + e + "\n");
}
},
callError: function callError(aCallback, aMessage) {
try {
aCallback && aCallback.handleError ? aCallback.handleError(aMessage) : null;
} catch (e) {
if (DEBUG) debug("settings 'error' callback threw an exception, dropping: " + e + "\n");
}
},
callTransactionHandle: function callTransactionHandle() {
try {
this._transactionCallback && this._transactionCallback.handle ? this._transactionCallback.handle() : null;
} catch (e) {
if (DEBUG) debug("settings 'Transaction handle' callback threw an exception, dropping: " + e + "\n");
}
},
classID : SETTINGSSERVICELOCK_CID,
QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock])
};
const SETTINGSSERVICE_CID = Components.ID("{f656f0c0-f776-11e1-a21f-0800200c9a66}");
function SettingsService()
{
if (VERBOSE) debug("settingsService Constructor");
this._locks = [];
this._serviceLocks = {};
this._createdLocks = 0;
this._unregisteredLocks = 0;
this.init();
}
SettingsService.prototype = {
init: function() {
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
mrm.registerStrongReporter(this);
},
uninit: function() {
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
mrm.unregisterStrongReporter(this);
},
observe: function(aSubject, aTopic, aData) {
if (VERBOSE) debug("observe: " + aTopic);
if (aTopic === kXpcomShutdownObserverTopic) {
this.uninit();
}
},
receiveMessage: function(aMessage) {
if (VERBOSE) debug("Entering receiveMessage");
let lockID = aMessage.data.lockID;
if (!lockID) {
if (DEBUG) debug("No lock ID");
return;
}
if (!(lockID in this._serviceLocks)) {
if (DEBUG) debug("Received message for lock " + lockID + " but no lock");
return;
}
if (VERBOSE) debug("Delivering message");
this._serviceLocks[lockID].receiveMessage(aMessage);
},
createLock: function createLock(aCallback) {
if (VERBOSE) debug("Calling createLock");
var lock = new SettingsServiceLock(this, aCallback);
if (VERBOSE) debug("Created lock " + lock._id);
this.registerLock(lock);
return lock;
},
registerLock: function(aLock) {
if (VERBOSE) debug("Registering lock " + aLock._id);
this._locks.push(aLock._id);
this._serviceLocks[aLock._id] = aLock;
this._createdLocks++;
},
unregisterLock: function(aLockID) {
let lock_index = this._locks.indexOf(aLockID);
if (lock_index != -1) {
if (VERBOSE) debug("Unregistering lock " + aLockID);
this._locks.splice(lock_index, 1);
this._serviceLocks[aLockID] = null;
delete this._serviceLocks[aLockID];
this._unregisteredLocks++;
}
},
collectReports: function(aCallback, aData, aAnonymize) {
aCallback.callback("",
"settings-service-locks/alive",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._locks.length,
"The number of service locks that are currently alives.",
aData);
aCallback.callback("",
"settings-service-locks/created",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._createdLocks,
"The number of service locks that were created.",
aData);
aCallback.callback("",
"settings-service-locks/deleted",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._unregisteredLocks,
"The number of service locks that were deleted.",
aData);
},
classID : SETTINGSSERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService,
Ci.nsIObserver,
Ci.nsIMemoryReporter])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock]);