mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 19:33:18 +00:00
Bug 1694515 - Part 2: Add BackgroundTasksUtils module for locking profiles and reading prefs. r=bytesized,mossop
Differential Revision: https://phabricator.services.mozilla.com/D107711
This commit is contained in:
parent
0b77649773
commit
d60f45d4c0
@ -263,6 +263,13 @@ if (AppConstants.platform == "android") {
|
||||
});
|
||||
}
|
||||
|
||||
if (AppConstants.MOZ_BACKGROUNDTASKS) {
|
||||
// These utilities are for background tasks, not regular headed browsing.
|
||||
whitelist.push({
|
||||
file: "resource://gre/modules/BackgroundTasksUtils.jsm",
|
||||
});
|
||||
}
|
||||
|
||||
whitelist = new Set(
|
||||
whitelist
|
||||
.filter(
|
||||
|
157
toolkit/components/backgroundtasks/BackgroundTasksUtils.jsm
Normal file
157
toolkit/components/backgroundtasks/BackgroundTasksUtils.jsm
Normal file
@ -0,0 +1,157 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BackgroundTasksUtils"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {})
|
||||
.ConsoleAPI;
|
||||
let consoleOptions = {
|
||||
// tip: set maxLogLevel to "debug" and use log.debug() to create detailed
|
||||
// messages during development. See LOG_LEVELS in Console.jsm for details.
|
||||
maxLogLevel: "error",
|
||||
maxLogLevelPref: "toolkit.backgroundtasks.loglevel",
|
||||
prefix: "BackgroundTasksUtils",
|
||||
};
|
||||
return new ConsoleAPI(consoleOptions);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"ProfileService",
|
||||
"@mozilla.org/toolkit/profile-service;1",
|
||||
"nsIToolkitProfileService"
|
||||
);
|
||||
|
||||
var BackgroundTasksUtils = {
|
||||
_throwIfNotLocked(lock) {
|
||||
if (!(lock instanceof Ci.nsIProfileLock)) {
|
||||
throw new Error("Passed lock was not an instance of nsIProfileLock");
|
||||
}
|
||||
|
||||
try {
|
||||
// In release builds, `.directory` throws NS_ERROR_NOT_INITIALIZED when
|
||||
// unlocked. In debug builds, `.directory` when the profile is not locked
|
||||
// will crash via `NS_ERROR`.
|
||||
if (lock.directory) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (
|
||||
!(
|
||||
e instanceof Ci.nsIException &&
|
||||
e.result == Cr.NS_ERROR_NOT_INITIALIZED
|
||||
)
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw new Error("Profile is not locked");
|
||||
},
|
||||
|
||||
/**
|
||||
* Locks the given profile and provides the path to it to the callback.
|
||||
* The callback should return a promise and once settled the profile is
|
||||
* unlocked and then the promise returned back to the caller of this function.
|
||||
*
|
||||
* @template T
|
||||
* @param {(lock: nsIProfileLock) => Promise<T>} callback
|
||||
* @param {nsIToolkitProfile} [profile] defaults to default profile
|
||||
* @return {Promise<T>}
|
||||
*/
|
||||
async withProfileLock(callback, profile = ProfileService.defaultProfile) {
|
||||
if (!profile) {
|
||||
throw new Error("No default profile exists");
|
||||
}
|
||||
|
||||
let lock;
|
||||
try {
|
||||
lock = profile.lock({});
|
||||
log.info(`withProfileLock: locked profile at ${lock.directory.path}`);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to lock profile: ${e}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// We must await to ensure any logging is displayed after the callback resolves.
|
||||
return await callback(lock);
|
||||
} finally {
|
||||
try {
|
||||
log.info(
|
||||
`withProfileLock: unlocking profile at ${lock.directory.path}`
|
||||
);
|
||||
lock.unlock();
|
||||
log.info(`withProfileLock: unlocked profile`);
|
||||
} catch (e) {
|
||||
log.warn(`withProfileLock: error unlocking profile`, e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads the preferences from "prefs.js" out of a profile, optionally
|
||||
* returning only names satisfying a given predicate.
|
||||
*
|
||||
* If no `lock` is given, the default profile is locked and the preferences
|
||||
* read from it. If `lock` is given, read from the given lock's directory.
|
||||
*
|
||||
* @param {(name: string) => boolean} [predicate] a predicate to filter
|
||||
* preferences by; if not given, all preferences are accepted.
|
||||
* @param {nsIProfileLock} [lock] optional lock to use
|
||||
* @returns {object} with keys that are string preference names and values
|
||||
* that are string|number|boolean preference values.
|
||||
*/
|
||||
async readPreferences(predicate = null, lock = null) {
|
||||
if (!lock) {
|
||||
return this.withProfileLock(profileLock =>
|
||||
this.readPreferences(predicate, profileLock)
|
||||
);
|
||||
}
|
||||
|
||||
this._throwIfNotLocked(lock);
|
||||
log.info(`readPreferences: profile is locked`);
|
||||
|
||||
let prefs = {};
|
||||
let addPref = (kind, name, value, sticky, locked) => {
|
||||
if (predicate && !predicate(name)) {
|
||||
return;
|
||||
}
|
||||
prefs[name] = value;
|
||||
};
|
||||
|
||||
// We ignore any "user.js" file, since usage is low and doing otherwise
|
||||
// requires implementing a bit more of `nsIPrefsService` than feels safe.
|
||||
let prefsFile = lock.directory.clone();
|
||||
prefsFile.append("prefs.js");
|
||||
log.info(`readPreferences: will parse prefs ${prefsFile.path}`);
|
||||
|
||||
let data = await IOUtils.read(prefsFile.path);
|
||||
log.debug(
|
||||
`readPreferences: parsing prefs from buffer of length ${data.length}`
|
||||
);
|
||||
|
||||
Services.prefs.parsePrefsFromBuffer(
|
||||
data,
|
||||
{
|
||||
onStringPref: addPref,
|
||||
onIntPref: addPref,
|
||||
onBoolPref: addPref,
|
||||
onError(message) {
|
||||
// Firefox itself manages "prefs.js", so errors should be infrequent.
|
||||
log.error(message);
|
||||
},
|
||||
},
|
||||
prefsFile.path
|
||||
);
|
||||
|
||||
log.debug(`readPreferences: parsed prefs from buffer`, prefs);
|
||||
return prefs;
|
||||
},
|
||||
};
|
@ -34,6 +34,7 @@ XPIDL_MODULE = "toolkit_backgroundtasks"
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"BackgroundTasksManager.jsm",
|
||||
"BackgroundTasksUtils.jsm",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.backgroundtasks += [
|
||||
|
@ -0,0 +1,99 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* vim: sw=4 ts=4 sts=4 et
|
||||
* 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/. */
|
||||
|
||||
const { BackgroundTasksUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/BackgroundTasksUtils.jsm"
|
||||
);
|
||||
|
||||
setupProfileService();
|
||||
|
||||
add_task(async function test_withProfileLock() {
|
||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||
Ci.nsIToolkitProfileService
|
||||
);
|
||||
|
||||
let profilePath = do_get_profile();
|
||||
profilePath.append(`test_withProfileLock`);
|
||||
let profile = profileService.createUniqueProfile(
|
||||
profilePath,
|
||||
"test_withProfileLock"
|
||||
);
|
||||
|
||||
await BackgroundTasksUtils.withProfileLock(async lock => {
|
||||
BackgroundTasksUtils._throwIfNotLocked(lock);
|
||||
}, profile);
|
||||
|
||||
// In debug builds, an unlocked lock crashes via `NS_ERROR` when
|
||||
// `.directory` is invoked. There's no way to check that the lock
|
||||
// is unlocked, so we can't realistically test this scenario in
|
||||
// debug builds.
|
||||
if (!AppConstants.DEBUG) {
|
||||
await Assert.rejects(
|
||||
BackgroundTasksUtils.withProfileLock(async lock => {
|
||||
lock.unlock();
|
||||
BackgroundTasksUtils._throwIfNotLocked(lock);
|
||||
}, profile),
|
||||
/Profile is not locked/
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_readPreferences() {
|
||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||
Ci.nsIToolkitProfileService
|
||||
);
|
||||
|
||||
let profilePath = do_get_profile();
|
||||
profilePath.append(`test_readPreferences`);
|
||||
let profile = profileService.createUniqueProfile(
|
||||
profilePath,
|
||||
"test_readPreferences"
|
||||
);
|
||||
|
||||
// Before we write any preferences, we fail to read.
|
||||
await Assert.rejects(
|
||||
BackgroundTasksUtils.withProfileLock(
|
||||
lock => BackgroundTasksUtils.readPreferences(null, lock),
|
||||
profile
|
||||
),
|
||||
/NotFoundError/
|
||||
);
|
||||
|
||||
let s = `user_pref("testPref.bool1", true);
|
||||
user_pref("testPref.bool2", false);
|
||||
user_pref("testPref.int1", 23);
|
||||
user_pref("testPref.int2", -1236);
|
||||
`;
|
||||
|
||||
let prefsFile = profile.rootDir.clone();
|
||||
prefsFile.append("prefs.js");
|
||||
await IOUtils.writeUTF8(prefsFile.path, s);
|
||||
|
||||
// Now we can read all the prefs.
|
||||
let prefs = await BackgroundTasksUtils.withProfileLock(
|
||||
lock => BackgroundTasksUtils.readPreferences(null, lock),
|
||||
profile
|
||||
);
|
||||
let expected = {
|
||||
"testPref.bool1": true,
|
||||
"testPref.bool2": false,
|
||||
"testPref.int1": 23,
|
||||
"testPref.int2": -1236,
|
||||
};
|
||||
Assert.deepEqual(prefs, expected, "Prefs read are correct");
|
||||
|
||||
// And we can filter the read as well.
|
||||
prefs = await BackgroundTasksUtils.withProfileLock(
|
||||
lock =>
|
||||
BackgroundTasksUtils.readPreferences(name => name.endsWith("1"), lock),
|
||||
profile
|
||||
);
|
||||
expected = {
|
||||
"testPref.bool1": true,
|
||||
"testPref.int1": 23,
|
||||
};
|
||||
Assert.deepEqual(prefs, expected, "Filtered prefs read are correct");
|
||||
});
|
@ -15,5 +15,6 @@ support-files =
|
||||
[test_backgroundtask_shouldprocessupdates.js]
|
||||
[test_backgroundtask_specific_pref.js]
|
||||
[test_backgroundtask_update_sync_manager.js]
|
||||
[test_backgroundtasksutils.js]
|
||||
[test_manifest_with_backgroundtask.js]
|
||||
[test_manifest_without_backgroundtask.js]
|
||||
|
Loading…
Reference in New Issue
Block a user