Backed out 11 changesets (bug 1059079, bug 1015518, bug 900551, bug 846200) for Gaia UI test failures on a CLOSED TREE.

Backed out changeset d85b4e48b3b4 (bug 1015518)
Backed out changeset 663b73ba69ec (bug 1015518)
Backed out changeset 5cf1cb5fa022 (bug 900551)
Backed out changeset b953dd5bfdaa (bug 900551)
Backed out changeset a2b6d7c84100 (bug 900551)
Backed out changeset ceb79fe83d15 (bug 900551)
Backed out changeset f6acf344fbf0 (bug 900551)
Backed out changeset fa269ea53937 (bug 846200)
Backed out changeset b89c84a850f9 (bug 846200)
Backed out changeset b7a7dfbe4e3f (bug 846200)
Backed out changeset c6f54d821c11 (bug 1059079)
This commit is contained in:
Ryan VanderMeulen 2014-08-28 12:49:49 -04:00
Родитель 86c3d649db
Коммит a408d636f2
41 изменённых файлов: 626 добавлений и 1680 удалений

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

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

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

@ -30,6 +30,20 @@ function debug(aMsg) {
//dump("-*-*- PermissionsInstaller.jsm : " + aMsg + "\n"); //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 = { this.PermissionsInstaller = {
/** /**
* Install permissisions or remove deprecated permissions upon re-install. * Install permissisions or remove deprecated permissions upon re-install.

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

@ -12,8 +12,7 @@ this.EXPORTED_SYMBOLS = [
"PermissionsReverseTable", "PermissionsReverseTable",
"expandPermissions", "expandPermissions",
"appendAccessToPermName", "appendAccessToPermName",
"isExplicitInPermissionsTable", "isExplicitInPermissionsTable"
"AllPossiblePermissions"
]; ];
// Permission access flags // Permission access flags
@ -150,16 +149,7 @@ this.PermissionsTable = { geolocation: {
privileged: DENY_ACTION, privileged: DENY_ACTION,
certified: ALLOW_ACTION, certified: ALLOW_ACTION,
access: ["read", "write"], access: ["read", "write"],
additional: ["indexedDB-chrome-settings", "settings-api"] additional: ["indexedDB-chrome-settings"]
},
// 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"]
}, },
permissions: { permissions: {
app: DENY_ACTION, app: DENY_ACTION,
@ -403,13 +393,6 @@ this.PermissionsTable = { geolocation: {
privileged: PROMPT_ACTION, privileged: PROMPT_ACTION,
certified: ALLOW_ACTION, certified: ALLOW_ACTION,
substitute: ["firefox-accounts"] substitute: ["firefox-accounts"]
},
"settings:wallpaper.image": {
app: DENY_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write"],
additional: ["settings-api"]
} }
}; };
@ -518,10 +501,7 @@ this.expandPermissions = function expandPermissions(aPermName, aAccess) {
return expandedPermNames; return expandedPermNames;
}; };
this.PermissionsReverseTable = {}; this.PermissionsReverseTable = (function () {
this.AllPossiblePermissions = [];
(function () {
// PermissionsTable as it is works well for direct searches, but not // PermissionsTable as it is works well for direct searches, but not
// so well for reverse ones (that is, if I get something like // so well for reverse ones (that is, if I get something like
// device-storage:music-read or indexedDB-chrome-settings-read how // device-storage:music-read or indexedDB-chrome-settings-read how
@ -529,9 +509,8 @@ this.AllPossiblePermissions = [];
// born. The idea is that // born. The idea is that
// reverseTable[device-storage:music-read] should return // reverseTable[device-storage:music-read] should return
// device-storage:music // device-storage:music
// let reverseTable = {};
// We also need a list of all the possible permissions for things like the
// settingsmanager, so construct that while we're at it.
for (let permName in PermissionsTable) { for (let permName in PermissionsTable) {
let permAliases; let permAliases;
if (PermissionsTable[permName].access) { if (PermissionsTable[permName].access) {
@ -540,12 +519,12 @@ this.AllPossiblePermissions = [];
permAliases = expandPermissions(permName); permAliases = expandPermissions(permName);
} }
for (let i = 0; i < permAliases.length; i++) { for (let i = 0; i < permAliases.length; i++) {
PermissionsReverseTable[permAliases[i]] = permName; reverseTable[permAliases[i]] = permName;
AllPossiblePermissions.push(permAliases[i]);
} }
} }
AllPossiblePermissions =
AllPossiblePermissions.concat(["offline-app", "pin-app"]); return reverseTable;
})(); })();
this.isExplicitInPermissionsTable = function(aPermName, aIntStatus) { this.isExplicitInPermissionsTable = function(aPermName, aIntStatus) {

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

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

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

@ -726,7 +726,7 @@ function startBluetoothTestBase(aPermissions, aTestCaseMain) {
} }
function startBluetoothTest(aReenable, aTestCaseMain) { function startBluetoothTest(aReenable, aTestCaseMain) {
startBluetoothTestBase(["settings-read", "settings-write", "settings-api-read", "settings-api-write"], function() { startBluetoothTestBase(["settings-read", "settings-write"], function() {
let origEnabled, needEnable; let origEnabled, needEnable;
return getBluetoothEnabled() return getBluetoothEnabled()

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

@ -4,13 +4,9 @@ browser = false
qemu = true qemu = true
[test_navigate_to_default_url.py] [test_navigate_to_default_url.py]
disabled = https://bugzilla.mozilla.org/show_bug.cgi?id=1058158
[test_dom_BluetoothManager_enabled.js] [test_dom_BluetoothManager_enabled.js]
[test_dom_BluetoothManager_adapteradded.js] [test_dom_BluetoothManager_adapteradded.js]
[test_dom_BluetoothAdapter_setters.js] [test_dom_BluetoothAdapter_setters.js]
[test_dom_BluetoothAdapter_getters.js] [test_dom_BluetoothAdapter_getters.js]
[test_dom_BluetoothAdapter_discovery.js] [test_dom_BluetoothAdapter_discovery.js]
[test_dom_BluetoothAdapter_pair.js] [test_dom_BluetoothAdapter_pair.js]
disabled = https://bugzilla.mozilla.org/show_bug.cgi?id=1058158

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

@ -58,7 +58,7 @@ function test(aEnabled) {
return deferred.promise; return deferred.promise;
} }
startBluetoothTestBase(["settings-read", "settings-write", "settings-api-read", "settings-api-write"], startBluetoothTestBase(["settings-read", "settings-write"],
function testCaseMain() { function testCaseMain() {
return getBluetoothEnabled() return getBluetoothEnabled()
.then(function(aEnabled) { .then(function(aEnabled) {

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

@ -39,7 +39,7 @@ function waitForManagerAttributeChanged() {
return deferred.promise; return deferred.promise;
} }
startBluetoothTestBase(["settings-read", "settings-write", "settings-api-read", "settings-api-write"], startBluetoothTestBase(["settings-read", "settings-write"],
function testCaseMain() { function testCaseMain() {
let adapters = bluetoothManager.getAdapters(); let adapters = bluetoothManager.getAdapters();
ok(Array.isArray(adapters), "Can not got the array of adapters"); ok(Array.isArray(adapters), "Can not got the array of adapters");

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

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

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

@ -6,8 +6,6 @@ MARIONETTE_TIMEOUT = 10000;
SpecialPowers.addPermission("fmradio", true, document); SpecialPowers.addPermission("fmradio", true, document);
SpecialPowers.addPermission("settings-read", true, document); SpecialPowers.addPermission("settings-read", true, document);
SpecialPowers.addPermission("settings-write", true, document); SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-api-read", true, document);
SpecialPowers.addPermission("settings-api-write", true, document);
let FMRadio = window.navigator.mozFMRadio; let FMRadio = window.navigator.mozFMRadio;
let mozSettings = window.navigator.mozSettings; let mozSettings = window.navigator.mozSettings;

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

@ -26,6 +26,7 @@ const BrowserElementIsPreloaded = true;
Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SettingsDB.jsm"); Cu.import("resource://gre/modules/SettingsDB.jsm");
Cu.import("resource://gre/modules/SettingsQueue.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]); Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]);

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

@ -151,4 +151,4 @@ startDSDSTestCommon(function() {
.then(testEnableDataRoamingWhileRoaming) .then(testEnableDataRoamingWhileRoaming)
.then(testDisableData) .then(testDisableData)
.then(restoreTestEnvironment); .then(restoreTestEnvironment);
}, ["settings-read", "settings-write", "settings-api-read", "settings-api-write"]); }, ["settings-read", "settings-write"]);

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

@ -108,4 +108,4 @@ startTestCommon(function() {
.then(() => setEmulatorRoamingAndWait(false)) .then(() => setEmulatorRoamingAndWait(false))
.then(() => setDataRoamingEnabled(false)); .then(() => setDataRoamingEnabled(false));
}, ["settings-read", "settings-write", "settings-api-read", "settings-api-write"]); }, ["settings-read", "settings-write"]);

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

@ -125,4 +125,4 @@ startTestCommon(function() {
return setDataApnSettings(origApnSettings); return setDataApnSettings(origApnSettings);
} }
}); });
}, ["settings-read", "settings-write", "settings-api-read", "settings-api-write"]); }, ["settings-read", "settings-write"]);

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

@ -38,4 +38,4 @@ startTestCommon(function() {
.then(() => setDataEnabled(false)) .then(() => setDataEnabled(false))
.then(() => setRadioEnabledAndWait(true)); .then(() => setRadioEnabledAndWait(true));
}, ["settings-read", "settings-write", "settings-api-read", "settings-api-write"]); }, ["settings-read", "settings-write"]);

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

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

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

@ -0,0 +1,126 @@
/* 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);
// Broadcast when SettingChangeNotifier finishes init process.
ppmm.broadcastAsyncMessage("Settings:Notifier:Init:OK");
},
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"; "use strict";
const DEBUG = false; const DEBUG = false;
function debug(s) { dump("-*- SettingsManager: " + s + "\n"); } function debug(s) {
if (DEBUG) dump("-*- SettingsManager: " + s + "\n");
}
const Cc = Components.classes; const Cc = Components.classes;
const Ci = Components.interfaces; const Ci = Components.interfaces;
const Cu = Components.utils; 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/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.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", XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1", "@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender"); "nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "mrm", XPCOMUtils.defineLazyServiceGetter(this, "mrm",
"@mozilla.org/memory-reporter-manager;1", "@mozilla.org/memory-reporter-manager;1",
"nsIMemoryReporterManager"); "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) { function SettingsLock(aSettingsManager) {
if (DEBUG) debug("settings lock init");
this._open = true; this._open = true;
this._isBusy = false;
this._requests = new Queue();
this._settingsManager = aSettingsManager; this._settingsManager = aSettingsManager;
this._id = uuidgen.generateUUID().toString(); this._transaction = null;
let closeHelper = function() { let closeHelper = function() {
if (DEBUG) debug("closing lock " + this._id); if (DEBUG) debug("closing lock");
this._open = false; this._open = false;
this.runOrFinalizeQueries();
}.bind(this); }.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); Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
} }
SettingsLock.prototype = { 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() { get closed() {
return !this._open; return !this._open;
}, },
@ -91,140 +49,230 @@ SettingsLock.prototype = {
return Cu.cloneInto(obj, this._settingsManager._window); return Cu.cloneInto(obj, this._settingsManager._window);
}, },
sendMessage: function(aMessageName, aData) { process: function process() {
cpmm.sendAsyncMessage(aMessageName, let lock = this;
aData, let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);
undefined,
this._settingsManager._window.document.nodePrincipal);
},
runOrFinalizeQueries: function() { while (!lock._requests.isEmpty()) {
if (!this._requests || Object.keys(this._requests).length == 0) { let info = lock._requests.dequeue();
this.sendMessage("Settings:Finalize", {lockID: this._id}); 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 { } 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) { createTransactionAndProcess: function() {
let msg = aMessage.data; if (DEBUG) debug("database opened, creating transaction");
// 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 (DEBUG) 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) {
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";
let req = this.getRequest(msg.requestID); this._transaction =
if (!req) { manager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType);
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) { get: function get(aName) {
if (DEBUG) debug("get (" + this._id + "): " + aName);
if (!this._open) { if (!this._open) {
dump("Settings lock not open!\n"); dump("Settings lock not open!\n");
throw Components.results.NS_ERROR_ABORT; throw Components.results.NS_ERROR_ABORT;
} }
let req = this.createRequest();
let reqID = this.getRequestId({request: req}); if (this._settingsManager.hasReadPrivileges) {
this.sendMessage("Settings:Get", {requestID: reqID, let req = Services.DOMRequest.createRequest(this._settingsManager._window);
lockID: this._id, this._requests.enqueue({ request: req, intent:"get", name: aName });
name: aName}); this.maybeProcess();
return req; 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) { set: function set(aSettings) {
if (DEBUG) debug("send: " + JSON.stringify(aSettings));
if (!this._open) { if (!this._open) {
throw "Settings lock not open"; throw "Settings lock not open";
} }
let req = this.createRequest();
let reqID = this.getRequestId({request: req}); if (this._settingsManager.hasWritePrivileges) {
this.sendMessage("Settings:Set", {requestID: reqID, let req = Services.DOMRequest.createRequest(this._settingsManager._window);
lockID: this._id, if (DEBUG) debug("send: " + JSON.stringify(aSettings));
settings: aSettings}); let settings = this._serializePreservingBinaries(aSettings);
this._requests.enqueue({request: req, intent: "set", settings: settings});
this.maybeProcess();
return req; return req;
} else {
if (DEBUG) debug("set not allowed");
throw "No permission to call set";
}
}, },
clear: function clear() { clear: function clear() {
if (DEBUG) if (DEBUG) debug("clear");
if (!this._open) { if (!this._open) {
throw "Settings lock not open"; throw "Settings lock not open";
} }
let req = this.createRequest();
let reqID = this.getRequestId({request: req}); if (this._settingsManager.hasWritePrivileges) {
this.sendMessage("Settings:Clear", {requestID: reqID, let req = Services.DOMRequest.createRequest(this._settingsManager._window);
lockID: this._id}); this._requests.enqueue({ request: req, intent: "clear"});
this.maybeProcess();
return req; return req;
} else {
if (DEBUG) debug("clear not allowed");
throw "No permission to call clear";
}
}, },
classID: Components.ID("{60c9357c-3ae0-4222-8f55-da01428470d5}"), classID: Components.ID("{60c9357c-3ae0-4222-8f55-da01428470d5}"),
contractID: "@mozilla.org/settingsLock;1", contractID: "@mozilla.org/settingsLock;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
Ci.nsIObserver,
Ci.nsISupportsWeakReference])
}; };
function SettingsManager() { function SettingsManager() {
this._settingsDB = new SettingsDB();
this._settingsDB.init();
} }
SettingsManager.prototype = { SettingsManager.prototype = {
_callbacks: null, _callbacks: null,
_isRegistered: false,
_perms: [],
_wrap: function _wrap(obj) { _wrap: function _wrap(obj) {
return Cu.cloneInto(obj, this._window); return Cu.cloneInto(obj, this._window);
@ -232,7 +280,6 @@ SettingsManager.prototype = {
set onsettingchange(aHandler) { set onsettingchange(aHandler) {
this.__DOM_IMPL__.setEventHandler("onsettingchange", aHandler); this.__DOM_IMPL__.setEventHandler("onsettingchange", aHandler);
this.checkMessageRegistration();
}, },
get onsettingchange() { get onsettingchange() {
@ -240,8 +287,12 @@ SettingsManager.prototype = {
}, },
createLock: function() { createLock: function() {
if (DEBUG) debug("creating lock"); if (DEBUG) debug("get lock!");
let lock = new SettingsLock(this); 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; return lock;
}, },
@ -268,37 +319,19 @@ SettingsManager.prototype = {
if (DEBUG) debug("no observers stored!"); if (DEBUG) debug("no observers stored!");
} }
break; break;
case "Settings:Notifier:Init:OK":
// If SettingManager receives this message means SettingChangeNotifier
// might not receive the Settings:RegisterForMessage message. We should
// send it again after SettingChangeNotifier is ready.
if (this.hasReadPrivileges) {
cpmm.sendAsyncMessage("Settings:RegisterForMessages");
}
break;
default: default:
if (DEBUG) debug("Wrong message: " + aMessage.name); if (DEBUG) debug("Wrong message: " + aMessage.name);
} }
}, },
// 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) { addObserver: function addObserver(aName, aCallback) {
if (DEBUG) debug("addObserver " + aName); if (DEBUG) debug("addObserver " + aName);
if (!this._callbacks) { if (!this._callbacks) {
@ -309,35 +342,44 @@ SettingsManager.prototype = {
} else { } else {
this._callbacks[aName].push(aCallback); this._callbacks[aName].push(aCallback);
} }
this.checkMessageRegistration();
}, },
removeObserver: function removeObserver(aName, aCallback) { removeObserver: function removeObserver(aName, aCallback) {
if (DEBUG) debug("deleteObserver " + aName); if (DEBUG) debug("deleteObserver " + aName);
if (this._callbacks && this._callbacks[aName]) { if (this._callbacks && this._callbacks[aName]) {
let index = this._callbacks[aName].indexOf(aCallback); let index = this._callbacks[aName].indexOf(aCallback)
if (index != -1) { if (index != -1) {
this._callbacks[aName].splice(index, 1); this._callbacks[aName].splice(index, 1)
if (this._callbacks[aName].length == 0) {
delete this._callbacks[aName];
}
} else { } else {
if (DEBUG) debug("Callback not found for: " + aName); if (DEBUG) debug("Callback not found for: " + aName);
} }
} else { } else {
if (DEBUG) debug("No observers stored for " + aName); if (DEBUG) debug("No observers stored for " + aName);
} }
this.checkMessageRegistration();
}, },
init: function(aWindow) { init: function(aWindow) {
if (DEBUG) debug("SettingsManager init");
mrm.registerStrongReporter(this); mrm.registerStrongReporter(this);
cpmm.addMessageListener("Settings:Change:Return:OK", this); cpmm.addMessageListener("Settings:Change:Return:OK", this);
cpmm.addMessageListener("Settings:Notifier:Init:OK", this);
this._window = aWindow; this._window = aWindow;
Services.obs.addObserver(this, "inner-window-destroyed", false); Services.obs.addObserver(this, "inner-window-destroyed", false);
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
this.innerWindowID = util.currentInnerWindowID; 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) { observe: function(aSubject, aTopic, aData) {
@ -351,7 +393,7 @@ SettingsManager.prototype = {
}, },
collectReports: function(aCallback, aData, aAnonymize) { collectReports: function(aCallback, aData, aAnonymize) {
for (let topic in this._callbacks) { for (var topic in this._callbacks) {
let length = this._callbacks[topic].length; let length = this._callbacks[topic].length;
if (length == 0) { if (length == 0) {
continue; continue;
@ -377,10 +419,12 @@ SettingsManager.prototype = {
cleanup: function() { cleanup: function() {
Services.obs.removeObserver(this, "inner-window-destroyed"); Services.obs.removeObserver(this, "inner-window-destroyed");
cpmm.removeMessageListener("Settings:Change:Return:OK", this); cpmm.removeMessageListener("Settings:Change:Return:OK", this);
cpmm.removeMessageListener("Settings:Notifier:Init:OK", this);
mrm.unregisterStrongReporter(this); mrm.unregisterStrongReporter(this);
this._requests = null; this._requests = null;
this._window = null; this._window = null;
this._innerWindowID = null; this._innerWindowID = null;
this._settingsDB.close();
}, },
classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"), classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),
@ -391,4 +435,4 @@ SettingsManager.prototype = {
Ci.nsIMemoryReporter]), Ci.nsIMemoryReporter]),
}; };
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock]); this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsManager, SettingsLock])

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

@ -1,920 +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) {
try {
transaction.abort();
} catch (e) {
if (e.name == "InvalidStateError") {
if (DEBUG) debug("Current transaction for " + this.lockID + " closed, trying to create new one.");
} else {
throw e;
}
}
}
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" "use strict"
/* static functions */ /* static functions */
const DEBUG = false; let DEBUG = 0;
function debug(s) { let debug;
dump("-*- SettingsService: " + s + "\n"); if (DEBUG)
} debug = function (s) { dump("-*- SettingsService: " + s + "\n"); }
else
debug = function (s) {}
const Ci = Components.interfaces; const Ci = Components.interfaces;
const Cu = Components.utils; 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/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.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; 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 SETTINGSSERVICELOCK_CID = Components.ID("{d7a395a0-e292-11e1-834e-1761d57f5f99}");
const nsISettingsServiceLock = Ci.nsISettingsServiceLock; const nsISettingsServiceLock = Ci.nsISettingsServiceLock;
function makeSettingsServiceRequest(aCallback, aName, aValue) { function SettingsServiceLock(aSettingsService, aTransactionCallback)
return { {
callback: aCallback,
name: aName,
value: aValue
};
};
function SettingsServiceLock(aSettingsService, aTransactionCallback) {
if (DEBUG) debug("settingsServiceLock constr!"); if (DEBUG) debug("settingsServiceLock constr!");
this._open = true; this._open = true;
this._busy = false;
this._requests = new Queue();
this._settingsService = aSettingsService; this._settingsService = aSettingsService;
this._id = uuidgen.generateUUID().toString(); this._transaction = null;
this._transactionCallback = aTransactionCallback; 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 = { 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) {
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 (DEBUG) 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 (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) { callHandle: function callHandle(aCallback, aName, aValue) {
try { 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, classID : SETTINGSSERVICELOCK_CID,
QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock]) QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock])
}; };
@ -214,18 +222,34 @@ const SETTINGSSERVICE_CID = Components.ID("{f656f0c0-f776-11e1-a21f-08002
function SettingsService() function SettingsService()
{ {
if (DEBUG) debug("settingsService Constructor"); debug("settingsService Constructor");
this._locks = new Queue();
this._settingsDB = new SettingsDB();
this._settingsDB.init();
} }
SettingsService.prototype = { 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) { createLock: function createLock(aCallback) {
var lock = new SettingsServiceLock(this, 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; return lock;
}, },
classID : SETTINGSSERVICE_CID, classID : SETTINGSSERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService]) 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'] MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
EXTRA_JS_MODULES += [ EXTRA_JS_MODULES += [
'SettingsChangeNotifier.jsm',
'SettingsDB.jsm', 'SettingsDB.jsm',
'SettingsRequestManager.jsm' 'SettingsQueue.jsm',
] ]
MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest.ini']

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

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

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

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id={678695}
"use strict"; "use strict";
if (SpecialPowers.isMainProcess()) { 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-read", true, document);
SpecialPowers.addPermission("settings-write", true, document); SpecialPowers.addPermission("settings-write", true, document);
SpecialPowers.addPermission("settings-clear", true, document);
function onUnwantedSuccess() { function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here"); ok(false, "onUnwantedSuccess: shouldn't get here");
@ -327,11 +324,8 @@ var steps = [
req = lock.clear(); req = lock.clear();
req.onsuccess = function () { req.onsuccess = function () {
ok(true, "Deleted the database"); ok(true, "Deleted the database");
next();
}; };
}, req.onerror = onFailure;
function () {
var lock = mozSettings.createLock();
req2 = lock.set(wifi); req2 = lock.set(wifi);
req2.onsuccess = function () { req2.onsuccess = function () {
ok(true, "set done"); ok(true, "set done");
@ -340,23 +334,23 @@ var steps = [
ok(true, "Get all settings"); ok(true, "Get all settings");
var lock2 = mozSettings.createLock(); var lock2 = mozSettings.createLock();
req3 = lock2.get("*"); req = lock2.get("*");
req3.onsuccess = function () { req.onsuccess = function () {
is(Object.keys(req3.result).length, 1, "length 1"); is(Object.keys(req.result).length, 1, "length 1");
check(req3.result, wifi); check(wifi, req.result);
ok(true, JSON.stringify(req3.result)); ok(true, JSON.stringify(req.result));
ok(true, "Get all settings Done"); ok(true, "Get all settings Done");
}; };
req3.onerror = onFailure; req.onerror = onFailure;
req4 = lock2.get("net3g.apn"); req2 = lock2.get("net3g.apn");
req4.onsuccess = function () { req2.onsuccess = function () {
is(Object.keys(req4.result).length, 1, "length 1"); is(Object.keys(req2.result).length, 1, "length 1");
check(wifi, req4.result); check(wifi, req2.result);
ok(true, "Get net3g.apn Done"); ok(true, "Get net3g.apn Done");
next(); next();
}; };
req4.onerror = onFailure; req2.onerror = onFailure;
}, },
function () { function () {
ok(true, "Change wifi1"); ok(true, "Change wifi1");
@ -473,8 +467,8 @@ var steps = [
req5.onerror = onFailure; req5.onerror = onFailure;
} }
var lock6 = mozSettings.createLock(); var lock5 = mozSettings.createLock();
req6 = lock6.clear(); req6 = lock5.clear();
req6.onsuccess = function () { req6.onsuccess = function () {
ok(true, "Deleted the database"); ok(true, "Deleted the database");
next(); next();
@ -510,15 +504,13 @@ var steps = [
} }
req.onerror = onFailure; req.onerror = onFailure;
var lock2 = mozSettings.createLock(); req2 = lock.clear();
req2 = lock2.clear();
req2.onsuccess = function () { req2.onsuccess = function () {
ok(true, "Deleted the database"); ok(true, "Deleted the database");
}; };
req2.onerror = onFailure; req2.onerror = onFailure;
var lock3 = mozSettings.createLock(); req3 = lock.set(wifi);
req3 = lock3.set(wifi);
req3.onsuccess = function () { req3.onsuccess = function () {
ok(true, "set done"); ok(true, "set done");
next(); next();
@ -769,34 +761,6 @@ var steps = [
}; };
req.onerror = onFailure; 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, "Clear DB");
var lock = mozSettings.createLock();
req = lock.clear();
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
};
req.onerror = onFailure;
},
function () { function () {
ok(true, "all done!\n"); ok(true, "all done!\n");
SimpleTest.finish(); SimpleTest.finish();

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

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=821630
"use strict"; "use strict";
if (SpecialPowers.isMainProcess()) { 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-read", true, document);
SpecialPowers.addPermission("settings-write", 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() { function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here"); ok(false, "onUnwantedSuccess: shouldn't get here");

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

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=806374
"use strict"; "use strict";
if (SpecialPowers.isMainProcess()) { 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-read", true, document);
SpecialPowers.addPermission("settings-write", 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() { function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here"); 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.settingName, "a", "Name should be a.");
is(e.settingValue, true, "Value should be true."); 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> </script>
</pre> </pre>
</body> </body>
</html> </html>

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

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

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

@ -22,14 +22,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=678695
"use strict"; "use strict";
if (SpecialPowers.isMainProcess()) { 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-write", true, document);
SpecialPowers.addPermission("settings-read", 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}; var screenBright = {"screen.brightness": 0.7};
@ -92,12 +89,8 @@ var steps = [
req = lock.clear(); req = lock.clear();
req.onsuccess = function () { req.onsuccess = function () {
ok(true, "Deleted the database"); ok(true, "Deleted the database");
next();
}; };
req.onerror = onFailure; req.onerror = onFailure;
},
function () {
var lock = mozSettings.createLock();
req2 = lock.set(screenBright); req2 = lock.set(screenBright);
req2.onsuccess = function () { req2.onsuccess = function () {
ok(true, "set done"); ok(true, "set done");
@ -274,19 +267,16 @@ var steps = [
req = lock.clear(); req = lock.clear();
req.onsuccess = function () { req.onsuccess = function () {
ok(true, "Deleted the database"); ok(true, "Deleted the database");
next(); navigator.mozSettings.onsettingchange = onComplexSettingschangeWithNext;
}; };
req.onerror = onFailure; req.onerror = onFailure;
},
function () {
var lock = mozSettings.createLock();
navigator.mozSettings.onsettingchange = onComplexSettingschangeWithNext;
req2 = navigator.mozSettings.createLock().set({'test.key': cset}); req2 = navigator.mozSettings.createLock().set({'test.key': cset});
req2.onsuccess = function () { req2.onsuccess = function () {
ok(true, "set done"); ok(true, "set done");
} }
req2.onerror = onFailure; req2.onerror = onFailure;
}, },
function () { function () {
ok(true, "all done!\n"); ok(true, "all done!\n");
SimpleTest.finish(); 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 Cc = Components.classes;
const Ci = Components.interfaces; 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/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
@ -84,6 +80,8 @@ let tests = [
const TEST_OBSERVER_VALUE = true; const TEST_OBSERVER_VALUE = true;
const TEST_OBSERVER_MESSAGE = "test.observer.message"; const TEST_OBSERVER_MESSAGE = "test.observer.message";
let observerCount = 2;
function observer(subject, topic, data) { function observer(subject, topic, data) {
if (topic !== MOZSETTINGS_CHANGED) { if (topic !== MOZSETTINGS_CHANGED) {
@ -92,6 +90,7 @@ let tests = [
} }
data = JSON.parse(data); data = JSON.parse(data);
function checkProp(name, type, value) { function checkProp(name, type, value) {
ok(name in data, "data." + name + " is present"); ok(name in data, "data." + name + " is present");
is(typeof data[name], type, "data." + name + " is " + type); is(typeof data[name], type, "data." + name + " is " + type);
@ -100,16 +99,24 @@ let tests = [
checkProp("key", "string", TEST_OBSERVER_KEY); checkProp("key", "string", TEST_OBSERVER_KEY);
checkProp("value", "boolean", TEST_OBSERVER_VALUE); 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); Services.obs.removeObserver(this, MOZSETTINGS_CHANGED);
next(); next();
} }
}
Services.obs.addObserver(observer, MOZSETTINGS_CHANGED, false); Services.obs.addObserver(observer, MOZSETTINGS_CHANGED, false);
let lock = SettingsService.createLock(); 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);
} }
]; ];

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

@ -93,20 +93,13 @@ function getSettings(aKey, aAllowError) {
function setSettings(aKey, aValue, aAllowError) { function setSettings(aKey, aValue, aAllowError) {
let settings = {}; let settings = {};
settings[aKey] = aValue; settings[aKey] = aValue;
let lock = window.navigator.mozSettings.createLock(); let request = window.navigator.mozSettings.createLock().set(settings);
let request = lock.set(settings); return wrapDomRequestAsPromise(request)
let deferred = Promise.defer(); .then(function resolve() {
lock.onsettingstransactionsuccess = function () {
log("setSettings(" + JSON.stringify(settings) + ") - success"); log("setSettings(" + JSON.stringify(settings) + ") - success");
deferred.resolve(); }, function reject() {
};
lock.onsettingstransactionfailure = function () {
ok(aAllowError, "setSettings(" + JSON.stringify(settings) + ") - error"); ok(aAllowError, "setSettings(" + JSON.stringify(settings) + ") - error");
// We resolve even though we've thrown an error, since the ok() });
// will do that.
deferred.resolve();
};
return deferred.promise;
} }
/** /**

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

@ -700,8 +700,6 @@ var interfaceNamesInGlobalScope =
{name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"}, {name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"},
// IMPORTANT: Do not change this list without review from a DOM peer! // IMPORTANT: Do not change this list without review from a DOM peer!
"MozSettingsEvent", "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! // IMPORTANT: Do not change this list without review from a DOM peer!
"MozSmsEvent", "MozSmsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer! // IMPORTANT: Do not change this list without review from a DOM peer!

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

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

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

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

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

@ -155,19 +155,15 @@ let gTestSuite = (function() {
* @return A deferred promise. * @return A deferred promise.
*/ */
function setSettings(aSettings) { function setSettings(aSettings) {
let lock = window.navigator.mozSettings.createLock(); let request = navigator.mozSettings.createLock().set(aSettings);
let request = lock.set(aSettings);
let deferred = Promise.defer(); return wrapDomRequestAsPromise(request)
lock.onsettingstransactionsuccess = function () { .then(function resolve() {
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")"); ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.resolve(); }, function reject(aEvent) {
};
lock.onsettingstransactionfailure = function (aEvent) {
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")"); ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.reject();
throw aEvent.target.error; throw aEvent.target.error;
}; });
return deferred.promise;
} }
/** /**
@ -542,8 +538,6 @@ let gTestSuite = (function() {
let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document }, let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document },
{ 'type': 'settings-write', 'allow': 1, 'context': window.document }, { 'type': 'settings-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-read', 'allow': 1, 'context': window.document }, { 'type': 'settings-read', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-read', 'allow': 1, 'context': window.document },
{ 'type': 'mobileconnection', 'allow': 1, 'context': window.document }]; { 'type': 'mobileconnection', 'allow': 1, 'context': window.document }];
SpecialPowers.pushPermissions(permissions, function() { SpecialPowers.pushPermissions(permissions, function() {

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

@ -4,4 +4,3 @@ browser = false
qemu = true qemu = true
[test_wifi_tethering_enabled.js] [test_wifi_tethering_enabled.js]
disabled = https://bugzilla.mozilla.org/show_bug.cgi?id=1058158

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

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

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

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

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

@ -209,8 +209,6 @@ let gTestSuite = (function() {
let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document }, let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document },
{ 'type': 'settings-write', 'allow': 1, 'context': window.document }, { 'type': 'settings-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-read', 'allow': 1, 'context': window.document }, { 'type': 'settings-read', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-read', 'allow': 1, 'context': window.document },
{ 'type': 'mobileconnection', 'allow': 1, 'context': window.document }]; { 'type': 'mobileconnection', 'allow': 1, 'context': window.document }];
SpecialPowers.pushPermissions(permissions, function() { SpecialPowers.pushPermissions(permissions, function() {
@ -584,20 +582,14 @@ let gTestSuite = (function() {
* *
* @return A deferred promise. * @return A deferred promise.
*/ */
function setSettings(aSettings) { function setSettings(aSettings, aAllowError) {
let lock = window.navigator.mozSettings.createLock(); let request = window.navigator.mozSettings.createLock().set(aSettings);
let request = lock.set(aSettings); return wrapDomRequestAsPromise(request)
let deferred = Promise.defer(); .then(function resolve() {
lock.onsettingstransactionsuccess = function () {
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")"); ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.resolve(); }, function reject() {
}; ok(aAllowError, "setSettings(" + JSON.stringify(aSettings) + ")");
lock.onsettingstransactionfailure = function (aEvent) { });
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.reject();
throw aEvent.target.error;
};
return deferred.promise;
} }
/** /**

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

@ -208,11 +208,11 @@ waitFor(
log('waiting for mozbrowserloadend'); log('waiting for mozbrowserloadend');
window.addEventListener('mozbrowserloadend', function loaded(aEvent) { window.addEventListener('mozbrowserloadend', function loaded(aEvent) {
log('received mozbrowserloadend for ' + aEvent.target.src); log('received mozbrowserloadend for ' + aEvent.target.src);
if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1 || aEvent.target.src.indexOf('verticalhome') != -1) { if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1) {
window.removeEventListener('mozbrowserloadend', loaded); window.removeEventListener('mozbrowserloadend', loaded);
marionetteScriptFinished(); marionetteScriptFinished();
} }
});""", script_timeout=300000) });""", script_timeout=120000)
print '...done' print '...done'
def _get_telnet_response(self, command=None): def _get_telnet_response(self, command=None):