Backed out 82bed38ff50a (bug 900551, bug 1015518, bug 846200) due to marionette webapi test bustage on ICS Emulator

This commit is contained in:
Kyle Machulis 2014-08-19 22:33:36 -07:00
Родитель fbe4723bf6
Коммит e4ee574673
28 изменённых файлов: 599 добавлений и 1616 удалений

Просмотреть файл

@ -5,7 +5,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import('resource://gre/modules/ContactService.jsm');
Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm');
Cu.import('resource://gre/modules/AlarmService.jsm');
Cu.import('resource://gre/modules/ActivitiesService.jsm');

Просмотреть файл

@ -30,6 +30,20 @@ function debug(aMsg) {
//dump("-*-*- PermissionsInstaller.jsm : " + aMsg + "\n");
}
// An array carring all the possible (expanded) permission names.
let AllPossiblePermissions = [];
for (let permName in PermissionsTable) {
let expandedPermNames = [];
if (PermissionsTable[permName].access) {
expandedPermNames = expandPermissions(permName, READWRITE);
} else {
expandedPermNames = expandPermissions(permName);
}
AllPossiblePermissions = AllPossiblePermissions.concat(expandedPermNames);
AllPossiblePermissions =
AllPossiblePermissions.concat(["offline-app", "pin-app"]);
}
this.PermissionsInstaller = {
/**
* Install permissisions or remove deprecated permissions upon re-install.

Просмотреть файл

@ -12,8 +12,7 @@ this.EXPORTED_SYMBOLS = [
"PermissionsReverseTable",
"expandPermissions",
"appendAccessToPermName",
"isExplicitInPermissionsTable",
"AllPossiblePermissions"
"isExplicitInPermissionsTable"
];
// Permission access flags
@ -145,16 +144,7 @@ this.PermissionsTable = { geolocation: {
privileged: DENY_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write"],
additional: ["indexedDB-chrome-settings", "settings-api"]
},
// This exists purely for tests, no app
// should ever use it. It can only be
// handed out by SpecialPowers.
"settings-clear": {
app: DENY_ACTION,
privileged: DENY_ACTION,
certified: DENY_ACTION,
additional: ["indexedDB-chrome-settings", "settings-api"]
additional: ["indexedDB-chrome-settings"]
},
permissions: {
app: DENY_ACTION,
@ -393,13 +383,6 @@ this.PermissionsTable = { geolocation: {
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
substitute: ["firefox-accounts"]
},
"settings:wallpaper.image": {
app: DENY_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write"],
additional: ["settings-api"]
}
};
@ -508,10 +491,7 @@ this.expandPermissions = function expandPermissions(aPermName, aAccess) {
return expandedPermNames;
};
this.PermissionsReverseTable = {};
this.AllPossiblePermissions = [];
(function () {
this.PermissionsReverseTable = (function () {
// PermissionsTable as it is works well for direct searches, but not
// so well for reverse ones (that is, if I get something like
// device-storage:music-read or indexedDB-chrome-settings-read how
@ -519,9 +499,8 @@ this.AllPossiblePermissions = [];
// born. The idea is that
// reverseTable[device-storage:music-read] should return
// device-storage:music
//
// We also need a list of all the possible permissions for things like the
// settingsmanager, so construct that while we're at it.
let reverseTable = {};
for (let permName in PermissionsTable) {
let permAliases;
if (PermissionsTable[permName].access) {
@ -530,12 +509,12 @@ this.AllPossiblePermissions = [];
permAliases = expandPermissions(permName);
}
for (let i = 0; i < permAliases.length; i++) {
PermissionsReverseTable[permAliases[i]] = permName;
AllPossiblePermissions.push(permAliases[i]);
reverseTable[permAliases[i]] = permName;
}
}
AllPossiblePermissions =
AllPossiblePermissions.concat(["offline-app", "pin-app"]);
return reverseTable;
})();
this.isExplicitInPermissionsTable = function(aPermName, aIntStatus) {

Просмотреть файл

@ -2109,8 +2109,8 @@ Navigator::DoNewResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
}
if (name.EqualsLiteral("mozSettings")) {
bool hasPermission = CheckPermission("settings-api-read") ||
CheckPermission("settings-api-write");
bool hasPermission = CheckPermission("settings-read") ||
CheckPermission("settings-write");
if (!hasPermission) {
FillPropertyDescriptor(aDesc, aObject, JS::NullValue(), false);
return true;

Просмотреть файл

@ -277,10 +277,6 @@ const kEventConstructors = {
return new MozSettingsEvent(aName, aProps);
},
},
MozSettingsTransactionEvent: { create: function (aName, aProps) {
return new MozSettingsTransactionEvent(aName, aProps);
},
},
MozSmsEvent: { create: function (aName, aProps) {
return new MozSmsEvent(aName, aProps);
},

Просмотреть файл

@ -15,7 +15,6 @@ var gData = [
permission: "settings",
access: READWRITE,
expected: ["settings-read", "settings-write",
"settings-api-read", "settings-api-write",
"indexedDB-chrome-settings-read",
"indexedDB-chrome-settings-write"]
},

Просмотреть файл

@ -0,0 +1,123 @@
/* 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("-*- SettingsChangeNotifier: " + s + "\n");
}
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
this.EXPORTED_SYMBOLS = ["SettingsChangeNotifier"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kFromSettingsChangeNotifier = "fromSettingsChangeNotifier";
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
this.SettingsChangeNotifier = {
init: function() {
if (DEBUG) debug("init");
this.children = [];
this._messages = ["Settings:Changed", "Settings:RegisterForMessages", "child-process-shutdown"];
this._messages.forEach((function(msgName) {
ppmm.addMessageListener(msgName, this);
}).bind(this));
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
},
observe: function(aSubject, aTopic, aData) {
if (DEBUG) debug("observe");
switch (aTopic) {
case kXpcomShutdownObserverTopic:
this._messages.forEach((function(msgName) {
ppmm.removeMessageListener(msgName, this);
}).bind(this));
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
ppmm = null;
break;
case kMozSettingsChangedObserverTopic:
{
let setting = JSON.parse(aData);
// To avoid redundantly broadcasting settings-changed events that are
// just requested from content processes themselves, skip the observer
// messages that are notified from the internal SettingsChangeNotifier.
if (setting.message && setting.message === kFromSettingsChangeNotifier)
return;
this.broadcastMessage("Settings:Change:Return:OK",
{ key: setting.key, value: setting.value });
break;
}
default:
if (DEBUG) debug("Wrong observer topic: " + aTopic);
break;
}
},
broadcastMessage: function broadcastMessage(aMsgName, aContent) {
if (DEBUG) debug("Broadast");
this.children.forEach(function(msgMgr) {
msgMgr.sendAsyncMessage(aMsgName, aContent);
});
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage");
let msg = aMessage.data;
let mm = aMessage.target;
switch (aMessage.name) {
case "Settings:Changed":
if (!aMessage.target.assertPermission("settings-write")) {
Cu.reportError("Settings message " + msg.name +
" from a content process with no 'settings-write' privileges.");
return null;
}
this.broadcastMessage("Settings:Change:Return:OK",
{ key: msg.key, value: msg.value });
Services.obs.notifyObservers(this, kMozSettingsChangedObserverTopic,
JSON.stringify({
key: msg.key,
value: msg.value,
message: kFromSettingsChangeNotifier
}));
break;
case "Settings:RegisterForMessages":
if (!aMessage.target.assertPermission("settings-read")) {
Cu.reportError("Settings message " + msg.name +
" from a content process with no 'settings-read' privileges.");
return null;
}
if (DEBUG) debug("Register!");
if (this.children.indexOf(mm) == -1) {
this.children.push(mm);
}
break;
case "child-process-shutdown":
if (DEBUG) debug("Unregister");
let index;
if ((index = this.children.indexOf(mm)) != -1) {
if (DEBUG) debug("Unregister index: " + index);
this.children.splice(index, 1);
}
break;
default:
if (DEBUG) debug("Wrong message: " + aMessage.name);
}
}
}
SettingsChangeNotifier.init();

Просмотреть файл

@ -5,84 +5,42 @@
"use strict";
const DEBUG = false;
function debug(s) { dump("-*- SettingsManager: " + s + "\n"); }
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/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest",
"@mozilla.org/dom/dom-request-service;1",
"nsIDOMRequestService");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "mrm",
"@mozilla.org/memory-reporter-manager;1",
"nsIMemoryReporterManager");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
/**
* In order to make SettingsManager work with Privileged Apps, we need the lock
* to be OOP. However, the lock state needs to be managed on the child process,
* while the IDB functions now happen on the parent process so we don't have to
* expose IDB permissions at the child process level. We use the
* DOMRequestHelper mechanism to deal with DOMRequests/promises across the
* processes.
*
* However, due to the nature of the IDBTransaction lifetime, we need to relay
* to the parent when to finalize the transaction once the child is done with the
* lock. We keep a list of all open requests for a lock, and once the lock
* reaches the end of its receiveMessage function with no more queued requests,
* we consider it dead. At that point, we send a message to the parent to notify
* it to finalize the transaction.
*/
function SettingsLock(aSettingsManager) {
if (DEBUG) debug("settings lock init");
this._open = true;
this._isBusy = false;
this._requests = new Queue();
this._settingsManager = aSettingsManager;
this._id = uuidgen.generateUUID().toString();
this._transaction = null;
let closeHelper = function() {
if (DEBUG) debug("closing lock " + this._id);
if (DEBUG) debug("closing lock");
this._open = false;
this.runOrFinalizeQueries();
}.bind(this);
// DOMRequestIpcHelper.initHelper sets this._window
this.initDOMRequestHelper(this._settingsManager._window, ["Settings:Get:OK", "Settings:Get:KO",
"Settings:Clear:OK", "Settings:Clear:KO",
"Settings:Set:OK", "Settings:Set:KO",
"Settings:Finalize:OK", "Settings:Finalize:KO"]);
this.sendMessage("Settings:CreateLock", {lockID: this._id, isInternalLock: false});
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
}
SettingsLock.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
set onsettingstransactionsuccess(aHandler) {
this.__DOM_IMPL__.setEventHandler("onsettingstransactionsuccess", aHandler);
},
get onsettingstransactionsuccess() {
return this.__DOM_IMPL__.getEventHandler("onsettingstransactionsuccess");
},
set onsettingstransactionfailure(aHandler) {
this.__DOM_IMPL__.setEventHandler("onsettingstransactionfailure", aHandler);
},
get onsettingstransactionfailure() {
return this.__DOM_IMPL__.getEventHandler("onsettingstransactionfailure");
},
get closed() {
return !this._open;
},
@ -91,140 +49,230 @@ SettingsLock.prototype = {
return Cu.cloneInto(obj, this._settingsManager._window);
},
sendMessage: function(aMessageName, aData) {
cpmm.sendAsyncMessage(aMessageName,
aData,
undefined,
this._settingsManager._window.document.nodePrincipal);
},
process: function process() {
let lock = this;
let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);
runOrFinalizeQueries: function() {
if (!this._requests || Object.keys(this._requests).length == 0) {
this.sendMessage("Settings:Finalize", {lockID: this._id});
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 {
this.sendMessage("Settings:Run", {lockID: this._id});
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");
results[name] = result.userValue !== undefined ? result.userValue : result.defaultValue;
}
this._open = true;
Services.DOMRequest.fireSuccess(request, this._wrap(results));
this._open = false;
}.bind(lock);
getReq.onerror = function() {
Services.DOMRequest.fireError(request, 0)
};
break;
}
}
},
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;
}
createTransactionAndProcess: function() {
if (DEBUG) debug("database opened, creating transaction");
// Finalizing a transaction does not return a request ID since we are
// supposed to fire callbacks.
if (!msg.requestID) {
let event;
switch (aMessage.name) {
case "Settings:Finalize:OK":
if (DEBUG) debug("Lock finalize ok!");
event = new this._window.MozSettingsTransactionEvent("settingstransactionsuccess", {});
this.__DOM_IMPL__.dispatchEvent(event);
break;
case "Settings:Finalize:KO":
if (DEBUG) debug("Lock finalize failed!");
event = new this._window.MozSettingsTransactionEvent("settingstransactionfailure", {
error: msg.errorMsg
});
this.__DOM_IMPL__.dispatchEvent(event);
break;
default:
if (DEBUG) debug("Message type " + aMessage.name + " is missing a requestID");
}
return;
}
let manager = this._settingsManager;
let transactionType = manager.hasWritePrivileges ? "readwrite" : "readonly";
this._transaction =
manager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType);
let req = this.getRequest(msg.requestID);
if (!req) {
if (DEBUG) debug("Matching request not found.");
return;
this.process();
},
maybeProcess: function() {
if (this._transaction && !this._isBusy) {
this.process();
}
this.removeRequest(msg.requestID);
if (DEBUG) debug("receiveMessage: " + aMessage.name);
switch (aMessage.name) {
case "Settings:Get:OK":
for (let i in msg.settings) {
msg.settings[i] = this._wrap(msg.settings[i]);
}
this._open = true;
Services.DOMRequest.fireSuccess(req.request, this._wrap(msg.settings));
this._open = false;
break;
case "Settings:Set:OK":
case "Settings:Clear:OK":
this._open = true;
Services.DOMRequest.fireSuccess(req.request, 0);
this._open = false;
break;
case "Settings:Get:KO":
case "Settings:Set:KO":
case "Settings:Clear:KO":
if (DEBUG) debug("error:" + msg.errorMsg);
Services.DOMRequest.fireError(req.request, msg.errorMsg);
break;
default:
if (DEBUG) debug("Wrong message: " + aMessage.name);
}
this.runOrFinalizeQueries();
},
get: function get(aName) {
if (DEBUG) debug("get (" + this._id + "): " + aName);
if (!this._open) {
dump("Settings lock not open!\n");
throw Components.results.NS_ERROR_ABORT;
}
let req = this.createRequest();
let reqID = this.getRequestId({request: req});
this.sendMessage("Settings:Get", {requestID: reqID,
lockID: this._id,
name: aName});
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) {
function needsUUID(aValue) {
if (!aValue || !aValue.constructor) {
return false;
}
return (aValue.constructor.name == "Date") || (aValue instanceof Ci.nsIDOMFile) ||
(aValue instanceof Ci.nsIDOMBlob);
}
// 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);
if (needsUUID(value)) {
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 (DEBUG) debug("send: " + JSON.stringify(aSettings));
if (!this._open) {
throw "Settings lock not open";
}
let req = this.createRequest();
let reqID = this.getRequestId({request: req});
this.sendMessage("Settings:Set", {requestID: reqID,
lockID: this._id,
settings: aSettings});
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 (DEBUG) if (DEBUG) debug("clear");
if (!this._open) {
throw "Settings lock not open";
}
let req = this.createRequest();
let reqID = this.getRequestId({request: req});
this.sendMessage("Settings:Clear", {requestID: reqID,
lockID: this._id});
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,
Ci.nsIObserver,
Ci.nsISupportsWeakReference])
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
};
function SettingsManager() {
this._settingsDB = new SettingsDB();
this._settingsDB.init();
}
SettingsManager.prototype = {
_callbacks: null,
_isRegistered: false,
_perms: [],
_wrap: function _wrap(obj) {
return Cu.cloneInto(obj, this._window);
@ -232,7 +280,6 @@ SettingsManager.prototype = {
set onsettingchange(aHandler) {
this.__DOM_IMPL__.setEventHandler("onsettingchange", aHandler);
this.checkMessageRegistration();
},
get onsettingchange() {
@ -240,8 +287,12 @@ SettingsManager.prototype = {
},
createLock: function() {
if (DEBUG) debug("creating lock");
let lock = new SettingsLock(this);
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;
},
@ -273,35 +324,10 @@ SettingsManager.prototype = {
}
},
// If we have either observer callbacks or an event handler,
// register for messages from the main thread. Otherwise, if no one
// is listening, unregister to reduce parent load.
checkMessageRegistration: function checkRegistration() {
let handler = this.__DOM_IMPL__.getEventHandler("onsettingchange");
if (!this._isRegistered) {
if (DEBUG) debug("Registering for messages");
cpmm.sendAsyncMessage("Settings:RegisterForMessages",
undefined,
undefined,
this._window.document.nodePrincipal);
this._isRegistered = true;
} else {
if ((!this._callbacks || Object.keys(this._callbacks).length == 0) &&
!handler) {
if (DEBUG) debug("Unregistering for messages");
cpmm.sendAsyncMessage("Settings:UnregisterForMessages",
undefined,
undefined,
this._window.document.nodePrincipal);
this._isRegistered = false;
this._callbacks = null;
}
}
},
addObserver: function addObserver(aName, aCallback) {
if (DEBUG) debug("addObserver " + aName);
if (!this._callbacks) {
cpmm.sendAsyncMessage("Settings:RegisterForMessages");
this._callbacks = {};
}
if (!this._callbacks[aName]) {
@ -309,35 +335,43 @@ SettingsManager.prototype = {
} else {
this._callbacks[aName].push(aCallback);
}
this.checkMessageRegistration();
},
removeObserver: function removeObserver(aName, aCallback) {
if (DEBUG) debug("deleteObserver " + aName);
if (this._callbacks && this._callbacks[aName]) {
let index = this._callbacks[aName].indexOf(aCallback);
let index = this._callbacks[aName].indexOf(aCallback)
if (index != -1) {
this._callbacks[aName].splice(index, 1);
if (this._callbacks[aName].length == 0) {
delete this._callbacks[aName];
}
this._callbacks[aName].splice(index, 1)
} else {
if (DEBUG) debug("Callback not found for: " + aName);
}
} else {
if (DEBUG) debug("No observers stored for " + aName);
}
this.checkMessageRegistration();
},
init: function(aWindow) {
if (DEBUG) debug("SettingsManager init");
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) {
@ -351,7 +385,7 @@ SettingsManager.prototype = {
},
collectReports: function(aCallback, aData, aAnonymize) {
for (let topic in this._callbacks) {
for (var topic in this._callbacks) {
let length = this._callbacks[topic].length;
if (length == 0) {
continue;
@ -381,6 +415,7 @@ SettingsManager.prototype = {
this._requests = null;
this._window = null;
this._innerWindowID = null;
this._settingsDB.close();
},
classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),
@ -391,4 +426,4 @@ SettingsManager.prototype = {
Ci.nsIMemoryReporter]),
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock])

Просмотреть файл

@ -1,912 +0,0 @@
/* 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) { dump("-*- SettingsRequestManager: " + s + "\n"); }
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = ["SettingsRequestManager"];
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/PermissionsTable.jsm");
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kSettingsReadSuffix = "-read";
const kSettingsWriteSuffix = "-write";
const kSettingsClearPermission = "settings-clear";
const kAllSettingsReadPermission = "settings" + kSettingsReadSuffix;
const kAllSettingsWritePermission = "settings" + kSettingsWriteSuffix;
// Any application with settings permissions, be it for all settings
// or a single one, will need to be able to access the settings API.
// The settings-api permission allows an app to see the mozSettings
// API in order to create locks and queue tasks. Whether these tasks
// will be allowed depends on the exact permissions the app has.
const kSomeSettingsReadPermission = "settings-api" + kSettingsReadSuffix;
const kSomeSettingsWritePermission = "settings-api" + kSettingsWriteSuffix;
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
let SettingsPermissions = {
_mmPermissions: {},
addManager: function(aMessage) {
if (DEBUG) debug("Adding message manager permissions");
let mm = aMessage.target;
// In order for mochitests to work, we have to update permissions on every
// lock creation or observer addition. This still means we can cache
// permissions.
if (this._mmPermissions[mm]) {
if (DEBUG) debug("Manager already added, updating permissions");
}
let perms = [];
let principal;
let isSystemPrincipal = false;
if (aMessage.principal.origin == "[System Principal]") {
isSystemPrincipal = true;
} else {
let uri = Services.io.newURI(aMessage.principal.origin, null, null);
principal = Services.scriptSecurityManager.getAppCodebasePrincipal(uri,
aMessage.principal.appId,
aMessage.principal.isInBrowserElement);
}
for (let i in AllPossiblePermissions) {
let permName = AllPossiblePermissions[i];
// We only care about permissions starting with the word "settings"
if (permName.indexOf("settings") != 0) {
continue;
}
if (isSystemPrincipal || Services.perms.testExactPermissionFromPrincipal(principal, permName) == Ci.nsIPermissionManager.ALLOW_ACTION) {
perms.push(permName);
}
}
this._mmPermissions[mm] = perms;
},
removeManager: function(aMsgMgr) {
if (DEBUG) debug("Removing message manager permissions for " + aMsgMgr);
if (!this._mmPermissions[aMsgMgr]) {
if (DEBUG) debug("Manager not added!");
return;
}
delete this._mmPermissions[aMsgMgr];
},
checkPermission: function(aMsgMgr, aPerm) {
if (!this._mmPermissions[aMsgMgr]) {
if (DEBUG) debug("Manager not added!");
return false;
}
return (this._mmPermissions[aMsgMgr].indexOf(aPerm) != -1);
},
hasAllReadPermission: function(aMsgMgr) {
return this.checkPermission(aMsgMgr, kAllSettingsReadPermission);
},
hasAllWritePermission: function(aMsgMgr) {
return this.checkPermission(aMsgMgr, kAllSettingsWritePermission);
},
hasSomeReadPermission: function(aMsgMgr) {
return this.checkPermission(aMsgMgr, kSomeSettingsReadPermission);
},
hasSomeWritePermission: function(aMsgMgr) {
return this.checkPermission(aMsgMgr, kSomeSettingsWritePermission);
},
hasClearPermission: function(aMsgMgr) {
return this.checkPermission(aMsgMgr, kSettingsClearPermission);
},
assertSomeReadPermission: function(aMsgMgr) {
aMsgMgr.assertPermission(kSomeSettingsReadPermission);
},
hasReadPermission: function(aMsgMgr, aSettingsName) {
return this.hasAllReadPermission(aMsgMgr) || this.checkPermission(aMsgMgr, "settings:" + aSettingsName + kSettingsReadSuffix);
},
hasWritePermission: function(aMsgMgr, aSettingsName) {
return this.hasAllWritePermission(aMsgMgr) || this.checkPermission(aMsgMgr, "settings:" + aSettingsName + kSettingsWriteSuffix);
}
};
function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock) {
return {
// ID Shared with the object on the child side
lockID: aLockID,
// Is this a content lock or a settings service lock?
isServiceLock: aIsServiceLock,
// Tasks to be run once the lock is at the head of the queue
tasks: [],
// This is set to true once a transaction is ready to run, but is not at the
// head of the lock queue.
consumable: false,
// Holds values that are requested to be set until the lock lifetime ends,
// then commits them to the DB.
queuedSets: {},
// Internal transaction object
_transaction: undefined,
// Message manager that controls the lock
_mm: aMsgMgr,
// If true, it means a permissions check failed, so just fail everything now
_failed: false,
// If we're slated to run finalize, set this to make sure we don't
// somehow run other events afterward.
finalizing: false,
// Lets us know if we can use this lock for a clear command
canClear: true,
// Lets us know if this lock has been used to clear at any point.
hasCleared: false,
getObjectStore: function() {
if (DEBUG) debug("Getting transaction for " + this.lockID);
let store;
// Test for transaction validity via trying to get the
// datastore. If it doesn't work, assume the transaction is
// closed, create a new transaction and try again.
if (this._transaction) {
try {
store = this._transaction.objectStore(SETTINGSSTORE_NAME);
} catch (e) {
if (e.name == "InvalidStateError") {
if (DEBUG) debug("Current transaction for " + this.lockID + " closed, trying to create new one.");
} else {
throw e;
}
}
}
// Create one transaction with a global permission. This may be
// slightly slower on apps with full settings permissions, but
// it means we don't have to do our own transaction order
// bookkeeping.
if (!SettingsPermissions.hasSomeWritePermission(this._mm)) {
this._transaction = aDB._db.transaction(SETTINGSSTORE_NAME, "readonly");
} else {
this._transaction = aDB._db.transaction(SETTINGSSTORE_NAME, "readwrite");
}
this._transaction.oncomplete = function() {
if (DEBUG) debug("Transaction for lock " + this.lockID + " closed");
}.bind(this);
this._transaction.onabort = function () {
if (DEBUG) debug("Transaction for lock " + this.lockID + " aborted");
this._failed = true;
}.bind(this);
try {
store = this._transaction.objectStore(SETTINGSSTORE_NAME);
} catch (e) {
if (e.name == "InvalidStateError") {
if (DEBUG) debug("Cannot create objectstore on transaction for " + this.lockID);
return null;
} else {
throw e;
}
}
return store;
},
get objectStore() {
return this.getObjectStore();
}
};
}
let SettingsRequestManager = {
// Access to the settings DB
settingsDB: new SettingsDB(),
// Remote messages to listen for from child
messages: ["child-process-shutdown", "Settings:Get", "Settings:Set",
"Settings:Clear", "Settings:Run", "Settings:Finalize",
"Settings:CreateLock", "Settings:RegisterForMessages"],
// Map of LockID to SettingsLockInfo objects
lockInfo: {},
// Queue of LockIDs. The LockID on the front of the queue is the only lock
// that will have requests processed, all other locks will queue requests
// until they hit the front of the queue.
settingsLockQueue: [],
children: [],
init: function() {
if (DEBUG) debug("init");
this.settingsDB.init();
this.messages.forEach((function(msgName) {
ppmm.addMessageListener(msgName, this);
}).bind(this));
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
},
_serializePreservingBinaries: function _serializePreservingBinaries(aObject) {
function needsUUID(aValue) {
if (!aValue || !aValue.constructor) {
return false;
}
return (aValue.constructor.name == "Date") || (aValue instanceof Ci.nsIDOMFile) ||
(aValue instanceof Ci.nsIDOMBlob);
}
// 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 binaries = Object.create(null);
let stringified = JSON.stringify(aObject, function(key, value) {
value = this.settingsDB.prepareValue(value);
if (needsUUID(value)) {
let uuid = uuidgen.generateUUID().toString();
binaries[uuid] = value;
return uuid;
}
return value;
}.bind(this));
return JSON.parse(stringified, function(key, value) {
if (value in binaries) {
return binaries[value];
}
return value;
});
},
queueTask: function(aOperation, aData) {
if (DEBUG) debug("Queueing task: " + aOperation);
let defer = {};
if (aOperation == "set") {
aData.settings = this._serializePreservingBinaries(aData.settings);
}
this.lockInfo[aData.lockID].tasks.push({
operation: aOperation,
data: aData,
defer: defer
});
let promise = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
return promise;
},
// Due to the fact that we're skipping the database in some places
// by keeping a local "set" value cache, resolving some calls
// without a call to the database would mean we could potentially
// receive promise responses out of expected order if a get is
// called before a set. Therefore, we wrap our resolve in a null
// get, which means it will resolves afer the rest of the calls
// queued to the DB.
queueTaskReturn: function(aTask, aReturnValue) {
if (DEBUG) debug("Making task queuing transaction request.");
let data = aTask.data;
let lock = this.lockInfo[data.lockID];
let store = lock.objectStore;
if (!store) {
if (DEBUG) debug("Rejecting task queue on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
}
// Due to the fact that we're skipping the database, resolving
// this without a call to the database would mean we could
// potentially receive promise responses out of expected order if
// a get is called before a set. Therefore, we wrap our resolve in
// a null get, which means it will resolves afer the rest of the
// calls queued to the DB.
let getReq = store.get(0);
let defer = {};
let promiseWrapper = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
getReq.onsuccess = function(event) {
return defer.resolve(aReturnValue);
};
getReq.onerror = function() {
return defer.reject({task: aTask, error: getReq.error.name});
};
return promiseWrapper;
},
taskGet: function(aTask) {
if (DEBUG) debug("Running Get task on lock " + aTask.data.lockID);
// Check that we have permissions for getting the value
let data = aTask.data;
let lock = this.lockInfo[data.lockID];
if (lock._failed) {
if (DEBUG) debug("Lock failed. All subsequent requests will fail.");
return Promise.reject({task: aTask, error: "Lock failed, all requests now failing."});
}
if (lock.hasCleared) {
if (DEBUG) debug("Lock was used for a clear command. All subsequent requests will fail.");
return Promise.reject({task: aTask, error: "Lock was used for a clear command. All subsequent requests will fail."});
}
lock.canClear = false;
if (!SettingsPermissions.hasReadPermission(lock._mm, data.name)) {
if (DEBUG) debug("get not allowed for " + data.name);
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to get " + data.name});
}
// If the value was set during this transaction, use the cached value
if (data.name in lock.queuedSets) {
if (DEBUG) debug("Returning cached set value " + lock.queuedSets[data.name] + " for " + data.name);
let local_results = {};
local_results[data.name] = lock.queuedSets[data.name];
return this.queueTaskReturn(aTask, {task: aTask, results: local_results});
}
// Create/Get transaction and make request
if (DEBUG) debug("Making get transaction request for " + data.name);
let store = lock.objectStore;
if (!store) {
if (DEBUG) debug("Rejecting Get task on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
}
if (DEBUG) debug("Making get request for " + data.name);
let getReq = (data.name === "*") ? store.mozGetAll() : store.mozGetAll(data.name);
let defer = {};
let promiseWrapper = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
getReq.onsuccess = function(event) {
if (DEBUG) debug("Request for '" + data.name + "' successful. " +
"Record count: " + event.target.result.length);
if (event.target.result.length == 0) {
if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + data.name + " is not in the database.\n");
}
let results = {};
for (let i in event.target.result) {
let result = event.target.result[i];
let name = result.settingName;
if (DEBUG) debug(name + ": " + result.userValue +", " + result.defaultValue);
let value = result.userValue !== undefined ? result.userValue : result.defaultValue;
results[name] = value;
}
return defer.resolve({task: aTask, results: results});
};
getReq.onerror = function() {
return defer.reject({task: aTask, error: getReq.error.name});
};
return promiseWrapper;
},
taskSet: function(aTask) {
let data = aTask.data;
let lock = this.lockInfo[data.lockID];
let keys = Object.getOwnPropertyNames(data.settings);
if (lock._failed) {
if (DEBUG) debug("Lock failed. All subsequent requests will fail.");
return Promise.reject({task: aTask, error: "Lock failed a permissions check, all requests now failing."});
}
if (lock.hasCleared) {
if (DEBUG) debug("Lock was used for a clear command. All subsequent requests will fail.");
return Promise.reject({task: aTask, error: "Lock was used for a clear command. All other requests will fail."});
}
lock.canClear = false;
// If we have no keys, resolve
if (keys.length === 0) {
if (DEBUG) debug("No keys to change entered!");
return Promise.resolve({task: aTask});
}
for (let i = 0; i < keys.length; i++) {
if (!SettingsPermissions.hasWritePermission(lock._mm, keys[i])) {
if (DEBUG) debug("set not allowed on " + keys[i]);
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to set " + keys[i]});
}
}
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (DEBUG) debug("key: " + key + ", val: " + JSON.stringify(data.settings[key]) + ", type: " + typeof(data.settings[key]));
lock.queuedSets[key] = data.settings[key];
}
return this.queueTaskReturn(aTask, {task: aTask});
},
queueConsume: function() {
if (this.settingsLockQueue.length > 0 && this.lockInfo[this.settingsLockQueue[0]].consumable) {
Services.tm.currentThread.dispatch(SettingsRequestManager.consumeTasks.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
}
},
// Removes the current lock from the queue, and starts transactions for the
// next lock, assuming there is one.
removeCurrentLock: function() {
let lock = this.settingsLockQueue.shift();
delete this.lockInfo[lock.lockID];
this.queueConsume();
},
finalizeSets: function(aTask) {
let data = aTask.data;
if (DEBUG) debug("Finalizing tasks for lock " + data.lockID);
let lock = this.lockInfo[data.lockID];
lock.finalizing = true;
if (lock._failed) {
this.removeCurrentLock();
return Promise.reject({task: aTask, error: "Lock failed a permissions check, all requests now failing."});
}
// If we have cleared, there is no reason to continue finalizing
// this lock. Just resolve promise with task and move on.
if (lock.hasCleared) {
if (DEBUG) debug("Clear was called on lock, skipping finalize");
this.removeCurrentLock();
return Promise.resolve({task: aTask});
}
let keys = Object.getOwnPropertyNames(lock.queuedSets);
if (keys.length === 0) {
if (DEBUG) debug("Nothing to finalize. Exiting.");
this.removeCurrentLock();
return Promise.resolve({task: aTask});
}
let store = lock.objectStore;
if (!store) {
if (DEBUG) debug("Rejecting Set task on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
}
// Due to the fact there may have multiple set operations to clear, and
// they're all async, callbacks are gathered into promises, and the promises
// are processed with Promises.all().
let checkPromises = [];
let finalValues = {};
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (DEBUG) debug("key: " + key + ", val: " + lock.queuedSets[key] + ", type: " + typeof(lock.queuedSets[key]));
let checkDefer = {};
let checkPromise = new Promise(function(resolve, reject) {
checkDefer.resolve = resolve;
checkDefer.reject = reject;
});
// Get operation is used to fill in the default value, assuming there is
// one. For the moment, if a value doesn't exist in the settings DB, we
// allow the user to add it, and just pass back a null default value.
let checkKeyRequest = store.get(key);
checkKeyRequest.onsuccess = function (event) {
let userValue = lock.queuedSets[key];
let defaultValue;
if (!event.target.result) {
defaultValue = null;
if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + key + " is not in the database.\n");
} else {
defaultValue = event.target.result.defaultValue;
}
let obj = {settingName: key, defaultValue: defaultValue, userValue: userValue};
finalValues[key] = {defaultValue: defaultValue, userValue: userValue};
let setReq = store.put(obj);
setReq.onsuccess = function() {
if (DEBUG) debug("Set successful!");
if (DEBUG) debug("key: " + key + ", val: " + finalValues[key] + ", type: " + typeof(finalValues[key]));
return checkDefer.resolve({task: aTask});
};
setReq.onerror = function() {
return checkDefer.reject({task: aTask, error: setReq.error.name});
};
}.bind(this);
checkKeyRequest.onerror = function(event) {
return checkDefer.reject({task: aTask, error: checkKeyRequest.error.name});
};
checkPromises.push(checkPromise);
}
let defer = {};
let promiseWrapper = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
// Once all transactions are done, or any have failed, remove the lock and
// start processing the tasks from the next lock in the queue.
Promise.all(checkPromises).then(function() {
// If all commits were successful, notify observers
for (let i = 0; i < keys.length; i++) {
this.sendSettingsChange(keys[i], finalValues[keys[i]].userValue, lock.isServiceLock);
}
this.removeCurrentLock();
defer.resolve({task: aTask});
}.bind(this), function(ret) {
this.removeCurrentLock();
defer.reject({task: aTask, error: "Set transaction failure"});
}.bind(this));
return promiseWrapper;
},
// Clear is only expected to be called via tests, and if a lock
// calls clear, it should be the only thing the lock does. This
// allows us to not have to deal with the possibility of query
// integrity checking. Clear should never be called in the wild,
// even by certified apps, which is why it has its own permission
// (settings-clear).
taskClear: function(aTask) {
if (DEBUG) debug("Clearing");
let data = aTask.data;
let lock = this.lockInfo[data.lockID];
if (lock._failed) {
if (DEBUG) debug("Lock failed, all requests now failing.");
return Promise.reject({task: aTask, error: "Lock failed, all requests now failing."});
}
if (!lock.canClear) {
if (DEBUG) debug("Lock tried to clear after queuing other tasks. Failing.");
lock._failed = true;
return Promise.reject({task: aTask, error: "Cannot call clear after queuing other tasks, all requests now failing."});
}
if (!SettingsPermissions.hasClearPermission(lock._mm)) {
if (DEBUG) debug("clear not allowed");
lock._failed = true;
return Promise.reject({task: aTask, error: "No permission to clear DB"});
}
lock.hasCleared = true;
let store = lock.objectStore;
if (!store) {
if (DEBUG) debug("Rejecting Clear task on lock " + aTask.data.lockID);
return Promise.reject({task: aTask, error: "Cannot get object store"});
}
let defer = {};
let promiseWrapper = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
let clearReq = store.clear();
clearReq.onsuccess = function() {
return defer.resolve({task: aTask});
};
clearReq.onerror = function() {
return defer.reject({task: aTask});
};
return promiseWrapper;
},
ensureConnection : function() {
if (DEBUG) debug("Ensuring Connection");
let defer = {};
let promiseWrapper = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
this.settingsDB.ensureDB(
function() { defer.resolve(); },
function(error) {
if (DEBUG) debug("Cannot open Settings DB. Trying to open an old version?\n");
defer.reject(error);
}
);
return promiseWrapper;
},
runTasks: function(aLockID) {
if (DEBUG) debug("Running tasks for " + aLockID);
let lock = this.lockInfo[aLockID];
if (lock.finalizing) {
debug("TASK TRYING TO QUEUE AFTER FINALIZE CALLED. THIS IS BAD. Lock: " + aLockID);
return;
}
let currentTask = lock.tasks.shift();
let promises = [];
while (currentTask) {
if (DEBUG) debug("Running Operation " + currentTask.operation);
let p;
switch (currentTask.operation) {
case "get":
p = this.taskGet(currentTask);
break;
case "set":
p = this.taskSet(currentTask);
break;
case "clear":
p = this.taskClear(currentTask);
break;
case "finalize":
p = this.finalizeSets(currentTask);
break;
default:
if (DEBUG) debug("Invalid operation: " + currentTask.operation);
p.reject("Invalid operation: " + currentTask.operation);
}
p.then(function(ret) {
ret.task.defer.resolve(ret.results);
}.bind(currentTask), function(ret) {
ret.task.defer.reject(ret.error);
});
promises.push(p);
currentTask = lock.tasks.shift();
}
},
consumeTasks: function() {
if (this.settingsLockQueue.length == 0) {
if (DEBUG) debug("Nothing to run!");
return;
}
let lockID = this.settingsLockQueue[0];
if (DEBUG) debug("Consuming tasks for " + lockID);
let lock = this.lockInfo[lockID];
// If a process dies, we should clean up after it via the
// child-process-shutdown event. But just in case we don't, we want to make
// sure we never block on consuming.
if (!lock) {
if (DEBUG) debug("Lock not found");
this.queueConsume();
return;
}
if (!lock.consumable || lock.tasks.length === 0) {
if (DEBUG) debug("No more tasks to run or not yet consuamble.");
return;
}
lock.consumable = false;
this.ensureConnection().then(
function(task) {
this.runTasks(lockID);
}.bind(this), function(ret) {
dump("-*- SettingsRequestManager: SETTINGS DATABASE ERROR: Cannot make DB connection!\n");
});
},
observe: function(aSubject, aTopic, aData) {
if (DEBUG) debug("observe");
switch (aTopic) {
case kXpcomShutdownObserverTopic:
this.messages.forEach((function(msgName) {
ppmm.removeMessageListener(msgName, this);
}).bind(this));
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
ppmm = null;
break;
default:
if (DEBUG) debug("Wrong observer topic: " + aTopic);
break;
}
},
sendSettingsChange: function(aKey, aValue, aIsServiceLock) {
this.broadcastMessage("Settings:Change:Return:OK",
{ key: aKey, value: aValue });
Services.obs.notifyObservers(this, kMozSettingsChangedObserverTopic,
JSON.stringify({
key: aKey,
value: aValue,
isInternalChange: aIsServiceLock
}));
},
broadcastMessage: function broadcastMessage(aMsgName, aContent) {
if (DEBUG) debug("Broadcast");
this.children.forEach(function(msgMgr) {
if (SettingsPermissions.hasReadPermission(msgMgr, aContent.key)) {
msgMgr.sendAsyncMessage(aMsgName, aContent);
}
});
if (DEBUG) debug("Finished Broadcasting");
},
addObserver: function(aMsgMgr) {
if (DEBUG) debug("Add observer for" + aMsgMgr);
if (this.children.indexOf(aMsgMgr) == -1) {
this.children.push(aMsgMgr);
}
},
removeObserver: function(aMsgMgr) {
if (DEBUG) debug("Remove observer for" + aMsgMgr);
let index = this.children.indexOf(aMsgMgr);
if (index != -1) {
this.children.splice(index, 1);
}
},
removeMessageManager: function(aMsgMgr){
if (DEBUG) debug("Removing message manager " + aMsgMgr);
this.removeObserver(aMsgMgr);
SettingsPermissions.removeManager(aMsgMgr);
let closedLockIDs = [];
let lockIDs = Object.keys(this.lockInfo);
for (let i in lockIDs) {
if (this.lockInfo[lockIDs[i]]._mm == aMsgMgr) {
if (DEBUG) debug("Removing lock " + lockIDs[i] + " due to process close/crash");
closedLockIDs.push(lockIDs[i]);
}
}
for (let i in closedLockIDs) {
let transaction = this.lockInfo[closedLockIDs[i]]._transaction;
if (transaction) {
transaction.abort();
}
delete this.lockInfo[closedLockIDs[i]];
let index = this.settingsLockQueue.indexOf(closedLockIDs[i]);
if (index > -1) {
this.settingsLockQueue.splice(index, 1);
}
// If index is 0, the lock we just removed was at the head of
// the queue, so possibly queue the next lock if it's
// consumable.
if (index == 0) {
this.queueConsume();
}
}
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage " + aMessage.name);
let msg = aMessage.data;
let mm = aMessage.target;
function returnMessage(name, data) {
try {
mm.sendAsyncMessage(name, data);
} catch (e) {
if (DEBUG) debug("Return message failed, " + name);
}
}
// For all message types that expect a lockID, we check to make
// sure that we're accessing a lock that's part of our process. If
// not, consider it a security violation and kill the app. Killing
// based on creating a colliding lock ID happens as part of
// CreateLock check below.
switch (aMessage.name) {
case "Settings:Get":
case "Settings:Set":
case "Settings:Clear":
case "Settings:Run":
case "Settings:Finalize":
if (!msg.lockID ||
!this.lockInfo[msg.lockID] ||
mm != this.lockInfo[msg.lockID]._mm) {
Cu.reportError("Process trying to access settings lock from another process. Killing.");
// Kill the app by checking for a non-existent permission
aMessage.target.assertPermission("message-manager-mismatch-kill");
return;
}
default:
break;
}
switch (aMessage.name) {
case "child-process-shutdown":
if (DEBUG) debug("Child process shutdown received.");
this.removeMessageManager(mm);
break;
case "Settings:RegisterForMessages":
SettingsPermissions.addManager(aMessage);
if (!SettingsPermissions.hasSomeReadPermission(mm)) {
Cu.reportError("Settings message " + aMessage.name +
" from a content process with no 'settings-api-read' privileges.");
// Kill app after reporting error
SettingsPermissions.assertSomeReadPermission(mm);
return;
}
this.addObserver(mm);
break;
case "Settings:UnregisterForMessages":
this.removeObserver(mm);
break;
case "Settings:CreateLock":
if (DEBUG) debug("Received CreateLock for " + msg.lockID);
// If we try to create a lock ID that collides with one
// already in the system, consider it a security violation and
// kill.
if (msg.lockID in this.settingsLockQueue) {
Cu.reportError("Trying to queue a lock with the same ID as an already queued lock. Killing app.");
aMessage.target.assertPermission("lock-id-duplicate-kill");
return;
}
this.settingsLockQueue.push(msg.lockID);
SettingsPermissions.addManager(aMessage);
this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB, mm, msg.lockID, msg.isServiceLock);
break;
case "Settings:Get":
if (DEBUG) debug("Received getRequest");
this.queueTask("get", msg).then(function(settings) {
returnMessage("Settings:Get:OK", {
lockID: msg.lockID,
requestID: msg.requestID,
settings: settings
});
}, function(error) {
if (DEBUG) debug("getRequest FAILED " + msg.name);
returnMessage("Settings:Get:KO", {
lockID: msg.lockID,
requestID: msg.requestID,
errorMsg: error
});
});
break;
case "Settings:Set":
if (DEBUG) debug("Received Set Request");
this.queueTask("set", msg).then(function(settings) {
returnMessage("Settings:Set:OK", {
lockID: msg.lockID,
requestID: msg.requestID
});
}, function(error) {
returnMessage("Settings:Set:KO", {
lockID: msg.lockID,
requestID: msg.requestID,
errorMsg: error
});
});
break;
case "Settings:Clear":
if (DEBUG) debug("Received Clear Request");
this.queueTask("clear", msg).then(function() {
returnMessage("Settings:Clear:OK", {
lockID: msg.lockID,
requestID: msg.requestID
});
}, function(error) {
returnMessage("Settings:Clear:KO", {
lockID: msg.lockID,
requestID: msg.requestID,
errorMsg: error
});
});
break;
case "Settings:Finalize":
if (DEBUG) debug("Received Finalize");
this.queueTask("finalize", msg).then(function() {
returnMessage("Settings:Finalize:OK", {
lockID: msg.lockID
});
}, function(error) {
returnMessage("Settings:Finalize:KO", {
lockID: msg.lockID,
errorMsg: error
});
});
// YES THIS IS SUPPOSED TO FALL THROUGH. Finalize is considered a task
// running situation, but it also needs to queue a task.
case "Settings:Run":
if (DEBUG) debug("Received Run");
this.lockInfo[msg.lockID].consumable = true;
if (msg.lockID == this.settingsLockQueue[0]) {
// If a lock is currently at the head of the queue, run all tasks for
// it.
if (DEBUG) debug("Running tasks for " + msg.lockID);
this.queueConsume();
} else {
// If a lock isn't at the head of the queue, but requests to be run,
// simply mark it as consumable, which means it will automatically run
// once it comes to the head of the queue.
if (DEBUG) debug("Queuing tasks for " + msg.lockID + " while waiting for " + this.settingsLockQueue[0]);
}
break;
default:
if (DEBUG) debug("Wrong message: " + aMessage.name);
}
}
};
SettingsRequestManager.init();

Просмотреть файл

@ -5,24 +5,20 @@
"use strict"
/* static functions */
const DEBUG = false;
function debug(s) {
dump("-*- SettingsService: " + s + "\n");
}
let DEBUG = 0;
let debug;
if (DEBUG)
debug = function (s) { dump("-*- SettingsService: " + s + "\n"); }
else
debug = function (s) {}
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/SettingsRequestManager.jsm');
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
const nsIClassInfo = Ci.nsIClassInfo;
@ -30,149 +26,18 @@ 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) {
function SettingsServiceLock(aSettingsService, aTransactionCallback)
{
if (DEBUG) debug("settingsServiceLock constr!");
this._open = true;
this._busy = false;
this._requests = new Queue();
this._settingsService = aSettingsService;
this._id = uuidgen.generateUUID().toString();
this._transaction = null;
this._transactionCallback = aTransactionCallback;
this._requests = {};
let closeHelper = function() {
if (DEBUG) 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);
}
cpmm.sendAsyncMessage("Settings:CreateLock", {lockID: this._id, isServiceLock: true}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
}
SettingsServiceLock.prototype = {
get closed() {
return !this._open;
},
runOrFinalizeQueries: function() {
if (!this._requests || Object.keys(this._requests).length == 0) {
cpmm.sendAsyncMessage("Settings:Finalize", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
} else {
cpmm.sendAsyncMessage("Settings:Run", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
}
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: " + aMessage.name);
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;
}
// 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 (DEBUG) 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");
}
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 (DEBUG) debug("get (" + this._id + "): " + aName);
if (!this._open) {
dump("Settings lock not open!\n");
throw Components.results.NS_ERROR_ABORT;
}
let reqID = uuidgen.generateUUID().toString();
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName);
cpmm.sendAsyncMessage("Settings:Get", {requestID: reqID,
lockID: this._id,
name: aName},
undefined,
Services.scriptSecurityManager.getSystemPrincipal());
},
set: function set(aName, aValue, aCallback) {
if (DEBUG) 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;
cpmm.sendAsyncMessage("Settings:Set", {requestID: reqID,
lockID: this._id,
settings: settings},
undefined,
Services.scriptSecurityManager.getSystemPrincipal());
},
callHandle: function callHandle(aCallback, aName, aValue) {
try {
@ -206,6 +71,149 @@ SettingsServiceLock.prototype = {
}
},
process: function process() {
debug("process!");
let lock = this;
lock._open = false;
let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);
while (!lock._requests.isEmpty()) {
if (lock._isBusy) {
return;
}
let info = lock._requests.dequeue();
if (DEBUG) debug("info:" + info.intent);
let callback = info.callback;
let name = info.name;
switch (info.intent) {
case "set":
let value = info.value;
let message = info.message;
if(DEBUG && typeof(value) == 'object') {
debug("object name:" + name + ", val: " + JSON.stringify(value));
}
lock._isBusy = true;
let checkKeyRequest = store.get(name);
checkKeyRequest.onsuccess = function (event) {
let defaultValue;
if (event.target.result) {
defaultValue = event.target.result.defaultValue;
} else {
defaultValue = null;
if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + name + " is not in the database.\n");
}
let setReq = store.put({ settingName: name, defaultValue: defaultValue, userValue: value });
setReq.onsuccess = function() {
lock._isBusy = false;
lock._open = true;
lock.callHandle(callback, name, value);
Services.obs.notifyObservers(lock, "mozsettings-changed", JSON.stringify({
key: name,
value: value,
message: message
}));
lock._open = false;
lock.process();
};
setReq.onerror = function(event) {
lock._isBusy = false;
lock.callError(callback, event.target.errorMessage);
lock.process();
};
}
checkKeyRequest.onerror = function(event) {
lock._isBusy = false;
lock.callError(callback, event.target.errorMessage);
lock.process();
};
break;
case "get":
let getReq = store.mozGetAll(name);
getReq.onsuccess = function(event) {
if (DEBUG) {
debug("Request successful. Record count:" + event.target.result.length);
debug("result: " + JSON.stringify(event.target.result));
}
this._open = true;
if (callback) {
if (event.target.result[0]) {
if (event.target.result.length > 1) {
if (DEBUG) debug("Warning: overloaded setting:" + name);
}
let result = event.target.result[0];
let value = result.userValue !== undefined
? result.userValue
: result.defaultValue;
lock.callHandle(callback, name, value);
} else {
lock.callHandle(callback, name, null);
}
} else {
if (DEBUG) debug("no callback defined!");
}
this._open = false;
}.bind(lock);
getReq.onerror = function error(event) {
lock.callError(callback, event.target.errorMessage);
};
break;
}
}
lock._open = true;
},
createTransactionAndProcess: function() {
if (this._settingsService._settingsDB._db) {
let lock;
while (lock = this._settingsService._locks.dequeue()) {
if (!lock._transaction) {
lock._transaction = lock._settingsService._settingsDB._db.transaction(SETTINGSSTORE_NAME, "readwrite");
if (lock._transactionCallback) {
lock._transaction.oncomplete = lock.callTransactionHandle.bind(lock);
lock._transaction.onabort = function(event) {
let message = '';
if (event.target.error) {
message = event.target.error.name + ': ' + event.target.error.message;
}
this.callAbort(lock._transactionCallback.handleAbort, message);
};
}
}
if (!lock._isBusy) {
lock.process();
} else {
this._settingsService._locks.enqueue(lock);
return;
}
}
if (!this._requests.isEmpty() && !this._isBusy) {
this.process();
}
}
},
get: function get(aName, aCallback) {
if (DEBUG) debug("get: " + aName + ", " + aCallback);
this._requests.enqueue({ callback: aCallback, intent:"get", name: aName });
this.createTransactionAndProcess();
},
set: function set(aName, aValue, aCallback, aMessage) {
debug("set: " + aName + ": " + JSON.stringify(aValue));
if (aMessage === undefined)
aMessage = null;
this._requests.enqueue({ callback: aCallback,
intent: "set",
name: aName,
value: this._settingsService._settingsDB.prepareValue(aValue),
message: aMessage });
this.createTransactionAndProcess();
},
classID : SETTINGSSERVICELOCK_CID,
QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock])
};
@ -214,18 +222,34 @@ const SETTINGSSERVICE_CID = Components.ID("{f656f0c0-f776-11e1-a21f-08002
function SettingsService()
{
if (DEBUG) debug("settingsService Constructor");
debug("settingsService Constructor");
this._locks = new Queue();
this._settingsDB = new SettingsDB();
this._settingsDB.init();
}
SettingsService.prototype = {
nextTick: function nextTick(aCallback, thisObj) {
if (thisObj)
aCallback = aCallback.bind(thisObj);
Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
},
createLock: function createLock(aCallback) {
var lock = new SettingsServiceLock(this, aCallback);
this._locks.enqueue(lock);
this._settingsDB.ensureDB(
function() { lock.createTransactionAndProcess(); },
function() { dump("SettingsService failed to open DB!\n"); }
);
this.nextTick(function() { this._open = false; }, lock);
return lock;
},
classID : SETTINGSSERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService])
};
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock])

Просмотреть файл

@ -18,8 +18,9 @@ if CONFIG['MOZ_B2G']:
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
EXTRA_JS_MODULES += [
'SettingsChangeNotifier.jsm',
'SettingsDB.jsm',
'SettingsRequestManager.jsm'
'SettingsQueue.jsm',
]
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']

Просмотреть файл

@ -2,7 +2,6 @@
skip-if = (toolkit == 'gonk' && debug) || e10s #debug-only failure, bug 932878
[test_settings_basics.html]
[test_settings_permissions.html]
[test_settings_blobs.html]
[test_settings_data_uris.html]
[test_settings_events.html]

Просмотреть файл

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id={678695}
"use strict";
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here");
@ -327,11 +324,8 @@ var steps = [
req = lock.clear();
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
};
},
function () {
var lock = mozSettings.createLock();
req.onerror = onFailure;
req2 = lock.set(wifi);
req2.onsuccess = function () {
ok(true, "set done");
@ -340,23 +334,23 @@ var steps = [
ok(true, "Get all settings");
var lock2 = mozSettings.createLock();
req3 = lock2.get("*");
req3.onsuccess = function () {
is(Object.keys(req3.result).length, 1, "length 1");
check(req3.result, wifi);
ok(true, JSON.stringify(req3.result));
req = lock2.get("*");
req.onsuccess = function () {
is(Object.keys(req.result).length, 1, "length 1");
check(wifi, req.result);
ok(true, JSON.stringify(req.result));
ok(true, "Get all settings Done");
};
req3.onerror = onFailure;
req.onerror = onFailure;
req4 = lock2.get("net3g.apn");
req4.onsuccess = function () {
is(Object.keys(req4.result).length, 1, "length 1");
check(wifi, req4.result);
req2 = lock2.get("net3g.apn");
req2.onsuccess = function () {
is(Object.keys(req2.result).length, 1, "length 1");
check(wifi, req2.result);
ok(true, "Get net3g.apn Done");
next();
};
req4.onerror = onFailure;
req2.onerror = onFailure;
},
function () {
ok(true, "Change wifi1");
@ -473,8 +467,8 @@ var steps = [
req5.onerror = onFailure;
}
var lock6 = mozSettings.createLock();
req6 = lock6.clear();
var lock5 = mozSettings.createLock();
req6 = lock5.clear();
req6.onsuccess = function () {
ok(true, "Deleted the database");
next();
@ -505,20 +499,18 @@ var steps = [
req = lock.get("wifi.enabled");
req.onsuccess = function() {
check(this.result, wifiEnabled);
check(req.result, wifiEnabled);
ok(true, "Test2 locking result done");
}
req.onerror = onFailure;
var lock2 = mozSettings.createLock();
req2 = lock2.clear();
req2 = lock.clear();
req2.onsuccess = function () {
ok(true, "Deleted the database");
};
req2.onerror = onFailure;
var lock3 = mozSettings.createLock();
req3 = lock3.set(wifi);
req3 = lock.set(wifi);
req3.onsuccess = function () {
ok(true, "set done");
next();
@ -769,24 +761,6 @@ var steps = [
};
req.onerror = onFailure;
},
function () {
ok(true, "Call success callback when transaction commits");
var lock = mozSettings.createLock();
lock.onsettingstransactionsuccess = function () {
next();
};
req = lock.set({"setting-obj": {foo: {bar: 23}}});
req.onsuccess = function() {
req2 = lock.get("setting-obj");
req2.onsuccess = function(event) {
var result = event.target.result["setting-obj"];
ok(result, "Got valid result");
ok(typeof result == "object", "Result is object");
ok("foo" in result && "bar" in result.foo, "Result has properties");
ok(result.foo.bar == 23, "Result properties are set");
};
};
},
function () {
ok(true, "all done!\n");
SimpleTest.finish();

Просмотреть файл

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=821630
"use strict";
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here");

Просмотреть файл

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=806374
"use strict";
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here");

Просмотреть файл

@ -32,16 +32,8 @@ e = new MozSettingsEvent("settingchanged", {settingName: "a", settingValue: true
is(e.settingName, "a", "Name should be a.");
is(e.settingValue, true, "Value should be true.");
var e = new MozSettingsTransactionEvent("settingtransactionsuccess", {});
ok(e, "Should have settings event!");
is(e.error, "", "error should be null");
var e = new MozSettingsTransactionEvent("settingtransactionfailure", {error: "Test error."});
ok(e, "Should have settings event!");
is(e.error, "Test error.", "error should be 'Test error.'");
</script>
</pre>
</body>
</html>
</html>

Просмотреть файл

@ -32,10 +32,7 @@ function testPref() {
SpecialPowers.pushPermissions([
{type: "settings-read", allow: 0, context: document},
{type: "settings-write", allow: 0, context: document},
{type: "settings-api-read", allow: 0, context: document},
{type: "settings-api-write", allow: 0, context: document},
{type: "settings-clear", allow: 0, context: document}
{type: "settings-write", allow: 0, context: document}
], function() {
ise(frames[0].navigator.mozSettings, null, "navigator.mozSettings is null when the page doesn't have permissions");
testPref();

Просмотреть файл

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=678695
"use strict";
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
var screenBright = {"screen.brightness": 0.7};
@ -92,12 +89,8 @@ var steps = [
req = lock.clear();
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
};
req.onerror = onFailure;
},
function () {
var lock = mozSettings.createLock();
req2 = lock.set(screenBright);
req2.onsuccess = function () {
ok(true, "set done");
@ -274,19 +267,16 @@ var steps = [
req = lock.clear();
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
navigator.mozSettings.onsettingchange = onComplexSettingschangeWithNext;
};
req.onerror = onFailure;
},
function () {
var lock = mozSettings.createLock();
navigator.mozSettings.onsettingchange = onComplexSettingschangeWithNext;
req2 = navigator.mozSettings.createLock().set({'test.key': cset});
req2.onsuccess = function () {
ok(true, "set done");
}
req2.onerror = onFailure;
},
function () {
ok(true, "all done!\n");
SimpleTest.finish();

Просмотреть файл

@ -1,203 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id={678695}
-->
<head>
<title>Test for Bug {678695} Settings API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={900551}">Mozilla Bug {900551}</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
"use strict";
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
}
SpecialPowers.removePermission("settings-read", document);
SpecialPowers.removePermission("settings-write", document);
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
SpecialPowers.addPermission("settings:wallpaper.image-read", true, document);
SpecialPowers.addPermission("settings:wallpaper.image-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here");
}
function onFailure() {
ok(false, "in on Failure!");
}
const wifi = {"wifi.enabled": false}
const wallpaper = {"wallpaper.image": "test-image"};
var combination = {
"wifi.enabled": false,
"wallpaper.image": "test-image"
}
function equals(o1, o2) {
var k1 = Object.keys(o1).sort();
var k2 = Object.keys(o2).sort();
if (k1.length != k2.length) return false;
return k1.zip(k2, function(keyPair) {
if(typeof o1[keyPair[0]] == typeof o2[keyPair[1]] == "object"){
return equals(o1[keyPair[0]], o2[keyPair[1]])
} else {
return o1[keyPair[0]] == o2[keyPair[1]];
}
}).all();
};
function observer1(setting) {
is(setting.settingName, "screen.brightness", "Same settingName");
is(setting.settingValue, "0.7", "Same settingvalue");
};
function onsettingschangeWithNext(event) {
is(event.settingName, "screen.brightness", "Same settingName");
is(event.settingValue, "0.7", "Same settingvalue");
next();
};
function check(o1, o2) {
is(JSON.stringify(o1), JSON.stringify(o2), "same");
}
var req, req2, req3, req4, req5, req6;
var index = 0;
var mozSettings = navigator.mozSettings;
var steps = [
// Can't delete database here since that requires permissions we don't want
// to give the page.
function () {
ok(true, "Setting wallpaper");
var lock = mozSettings.createLock();
req = lock.set(wallpaper);
req.onsuccess = function () {
ok(true, "set done");
}
req.onerror = onFailure;
var lock2 = mozSettings.createLock();
req2 = lock2.get("wallpaper.image");
req2.onsuccess = function () {
is(Object.keys(req2.result).length, 1, "length 1");
check(wallpaper, req2.result);
ok(true, "Get wallpaper Done");
next();
};
req2.onerror = onFailure;
},
function () {
ok(true, "Get Wifi");
var lock = mozSettings.createLock();
req = lock.get("wifi.enabled");
req.onerror = function () {
ok(true, "get failed (expected)");
next();
}
req.onsuccess = onFailure;
},
function () {
ok(true, "Set Wifi");
var lock = mozSettings.createLock();
req = lock.set(wifi);
req.onerror = function () {
ok(true, "set failed (expected)");
next();
}
req.onsuccess = onFailure;
},
function () {
ok(true, "Set combination (1 valid 1 not valid)");
var lock = mozSettings.createLock();
req = lock.set(combination);
req.onerror = function () {
ok(true, "set failed (expected)");
next();
}
req.onsuccess = onFailure;
},
function () {
ok(true, "All requests on a failed lock should fail");
var lock = mozSettings.createLock();
lock.onsettingstransactionfailure = function (evt) {
ok(evt.error == "Lock failed a permissions check, all requests now failing.", "transaction failure on permissions error message correct.");
ok(true, "transaction failed (expected) ");
next();
};
lock.onsettingstransactionsuccess = onFailure;
req = lock.set(wifi);
req.onerror = function () {
ok(true, "set failed (expected)");
}
req.onsuccess = onFailure;
req2 = lock.get("wallpaper.image");
req2.onerror = function () {
ok(true, "get failed (expected)");
}
req2.onsuccess = onFailure;
},
function () {
ok(true, "Set combination (1 valid 1 not valid)");
var lock = mozSettings.createLock();
req = lock.set(combination);
req.onerror = function () {
ok(true, "set failed (expected)");
next();
}
req.onsuccess = onFailure;
},
function () {
ok(true, "Set combination (1 valid 1 not valid)");
var lock = mozSettings.createLock();
req = lock.set(combination);
req.onerror = function () {
ok(true, "set failed (expected)");
next();
}
req.onsuccess = onFailure;
},
function () {
ok(true, "all done!\n");
SimpleTest.finish();
}
];
function next() {
ok(true, "Begin!");
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
index += 1;
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(next);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -4,10 +4,6 @@ const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
}
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -84,6 +80,8 @@ let tests = [
const TEST_OBSERVER_VALUE = true;
const TEST_OBSERVER_MESSAGE = "test.observer.message";
let observerCount = 2;
function observer(subject, topic, data) {
if (topic !== MOZSETTINGS_CHANGED) {
@ -92,6 +90,7 @@ let tests = [
}
data = JSON.parse(data);
function checkProp(name, type, value) {
ok(name in data, "data." + name + " is present");
is(typeof data[name], type, "data." + name + " is " + type);
@ -100,16 +99,24 @@ let tests = [
checkProp("key", "string", TEST_OBSERVER_KEY);
checkProp("value", "boolean", TEST_OBSERVER_VALUE);
checkProp("isInternalChange", "boolean", true);
if (observerCount === 2) {
checkProp("message", "object", null);
} else {
checkProp("message", "string", TEST_OBSERVER_MESSAGE);
}
--observerCount;
if (observerCount === 0) {
Services.obs.removeObserver(this, MOZSETTINGS_CHANGED);
next();
}
}
Services.obs.addObserver(observer, MOZSETTINGS_CHANGED, false);
let lock = SettingsService.createLock();
lock.set(TEST_OBSERVER_KEY, TEST_OBSERVER_VALUE, null);
lock.set(TEST_OBSERVER_KEY, TEST_OBSERVER_VALUE, null, null);
lock.set(TEST_OBSERVER_KEY, TEST_OBSERVER_VALUE, null, TEST_OBSERVER_MESSAGE);
}
];

Просмотреть файл

@ -3314,14 +3314,16 @@ RadioInterface.prototype = {
* Set the setting value of "time.clock.automatic-update.available".
*/
setClockAutoUpdateAvailable: function(value) {
gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null);
gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
* Set the setting value of "time.timezone.automatic-update.available".
*/
setTimezoneAutoUpdateAvailable: function(value) {
gSettingsService.createLock().set(kSettingsTimezoneAutoUpdateAvailable, value, null);
gSettingsService.createLock().set(kSettingsTimezoneAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
@ -3497,7 +3499,7 @@ RadioInterface.prototype = {
switch (topic) {
case kMozSettingsChangedObserverTopic:
let setting = JSON.parse(data);
this.handleSettingsChange(setting.key, setting.value, setting.isInternalChange);
this.handleSettingsChange(setting.key, setting.value, setting.message);
break;
case kSysClockChangeObserverTopic:
let offset = parseInt(data, 10);
@ -3578,11 +3580,11 @@ RadioInterface.prototype = {
// ICC's mcc-mnc.
_lastKnownHomeNetwork: null,
handleSettingsChange: function(aName, aResult, aIsInternalSetting) {
handleSettingsChange: function(aName, aResult, aMessage) {
// Don't allow any content processes to modify the setting
// "time.clock.automatic-update.available" except for the chrome process.
if (aName === kSettingsClockAutoUpdateAvailable &&
!aIsInternalSetting) {
aMessage !== "fromInternalSetting") {
let isClockAutoUpdateAvailable = this._lastNitzMessage !== null ||
this._sntp.isAvailable();
if (aResult !== isClockAutoUpdateAvailable) {
@ -3598,7 +3600,7 @@ RadioInterface.prototype = {
// "time.timezone.automatic-update.available" except for the chrome
// process.
if (aName === kSettingsTimezoneAutoUpdateAvailable &&
!aIsInternalSetting) {
aMessage !== "fromInternalSetting") {
let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null;
if (aResult !== isTimezoneAutoUpdateAvailable) {
if (DEBUG) {

Просмотреть файл

@ -706,8 +706,6 @@ var interfaceNamesInGlobalScope =
{name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"},
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozSettingsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "MozSettingsTransactionEvent", permission: "settings-api-read"},
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozSmsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -26,16 +26,12 @@ resume_geolocationProvider(function() {
});
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
function test1() {
//This pushPermissions call is after pushPrefEnv call and pushPrefEnv calls follow after this
SpecialPowers.pushPermissions([{'type': 'settings-read', 'allow': true, 'context': document},
{'type': 'settings-write', 'allow': true, 'context': document},
{'type': 'settings-api-write', 'allow': true, 'context': document},
{'type': 'settings-api-read', 'allow': true, 'context': document}
], test2);
SpecialPowers.pushPermissions([{'type': 'settings-read', 'allow': true, 'context': document}, {'type': 'settings-write', 'allow': true, 'context': document}], test2);
}
function test2() {

Просмотреть файл

@ -26,16 +26,12 @@ resume_geolocationProvider(function() {
});
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
SpecialPowers.Cu.import("resource://gre/modules/SettingsChangeNotifier.jsm");
}
function test1() {
//This pushPermissions call is after pushPrefEnv call and pushPrefEnv calls follow after this
SpecialPowers.pushPermissions([{'type': 'settings-read', 'allow': true, 'context': document},
{'type': 'settings-write', 'allow': true, 'context': document},
{'type': 'settings-api-write', 'allow': true, 'context': document},
{'type': 'settings-api-read', 'allow': true, 'context': document}
], test2);
SpecialPowers.pushPermissions([{'type': 'settings-read', 'allow': true, 'context': document}, {'type': 'settings-write', 'allow': true, 'context': document}], test2);
}
var watchId;

Просмотреть файл

@ -1,17 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
[Constructor(DOMString type, optional MozSettingsTransactionEventInit eventInitDict),
CheckPermissions="settings-api-read settings-api-write"]
interface MozSettingsTransactionEvent : Event
{
readonly attribute DOMString? error;
};
dictionary MozSettingsTransactionEventInit : EventInit
{
DOMString error = "";
};

Просмотреть файл

@ -6,7 +6,7 @@
[JSImplementation="@mozilla.org/settingsLock;1",
Pref="dom.mozSettings.enabled"]
interface SettingsLock : EventTarget {
interface SettingsLock {
// Whether this lock is invalid
readonly attribute boolean closed;
@ -17,8 +17,6 @@ interface SettingsLock : EventTarget {
DOMRequest get(DOMString name);
DOMRequest clear();
attribute EventHandler onsettingstransactionsuccess;
attribute EventHandler onsettingstransactionfailure;
};
dictionary SettingChange {

Просмотреть файл

@ -658,7 +658,6 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'MozMmsEvent.webidl',
'MozOtaStatusEvent.webidl',
'MozSettingsEvent.webidl',
'MozSettingsTransactionEvent.webidl',
'MozSmsEvent.webidl',
'MozStkCommandEvent.webidl',
'PageTransitionEvent.webidl',

Просмотреть файл

@ -3511,7 +3511,8 @@ WifiWorker.prototype = {
handleError: function(aErrorMessage) {
self.requestDone();
}
});
},
"fromInternalSetting");
},
notifyTetheringOff: function notifyTetheringOff() {
@ -3531,7 +3532,8 @@ WifiWorker.prototype = {
handleError: function(aErrorMessage) {
self.requestDone();
}
});
},
"fromInternalSetting");
},
handleWifiEnabled: function(enabled) {
@ -3607,7 +3609,7 @@ WifiWorker.prototype = {
let setting = JSON.parse(data);
// To avoid WifiWorker setting the wifi again, don't need to deal with
// the "mozsettings-changed" event fired from internal setting.
if (setting.isInternalChange) {
if (setting.message && setting.message === "fromInternalSetting") {
return;
}