255 lines
7.9 KiB
JavaScript
255 lines
7.9 KiB
JavaScript
/* -*- 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"
|
|
);
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyGetter(lazy, "log", () => {
|
|
let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
|
|
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(
|
|
lazy,
|
|
"ProfileService",
|
|
"@mozilla.org/toolkit/profile-service;1",
|
|
"nsIToolkitProfileService"
|
|
);
|
|
|
|
class CannotLockProfileError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "CannotLockProfileError";
|
|
}
|
|
}
|
|
|
|
var BackgroundTasksUtils = {
|
|
// Manage our own default profile that can be overridden for testing. It's
|
|
// easier to do this here rather than using the profile service itself.
|
|
_defaultProfileInitialized: false,
|
|
_defaultProfile: null,
|
|
|
|
getDefaultProfile() {
|
|
if (!this._defaultProfileInitialized) {
|
|
this._defaultProfileInitialized = true;
|
|
// This is all test-only.
|
|
const env = Cc["@mozilla.org/process/environment;1"].getService(
|
|
Ci.nsIEnvironment
|
|
);
|
|
let defaultProfilePath = env.get(
|
|
"MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH"
|
|
);
|
|
let noDefaultProfile = env.get("MOZ_BACKGROUNDTASKS_NO_DEFAULT_PROFILE");
|
|
if (defaultProfilePath) {
|
|
lazy.log.info(
|
|
`getDefaultProfile: using default profile path ${defaultProfilePath}`
|
|
);
|
|
var tmpd = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
|
tmpd.initWithPath(defaultProfilePath);
|
|
// Sadly this writes to `profiles.ini`, but there's little to be done.
|
|
this._defaultProfile = lazy.ProfileService.createProfile(
|
|
tmpd,
|
|
`MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH-${Date.now()}`
|
|
);
|
|
} else if (noDefaultProfile) {
|
|
lazy.log.info(`getDefaultProfile: setting default profile to null`);
|
|
this._defaultProfile = null;
|
|
} else {
|
|
try {
|
|
lazy.log.info(
|
|
`getDefaultProfile: using ProfileService.defaultProfile`
|
|
);
|
|
this._defaultProfile = lazy.ProfileService.defaultProfile;
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
return this._defaultProfile;
|
|
},
|
|
|
|
hasDefaultProfile() {
|
|
return this.getDefaultProfile() != null;
|
|
},
|
|
|
|
currentProfileIsDefaultProfile() {
|
|
let defaultProfile = this.getDefaultProfile();
|
|
let currentProfile = lazy.ProfileService.currentProfile;
|
|
// This comparison needs to accommodate null on both sides.
|
|
let isDefaultProfile = defaultProfile && currentProfile == defaultProfile;
|
|
return isDefaultProfile;
|
|
},
|
|
|
|
_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 = this.getDefaultProfile()) {
|
|
if (!profile) {
|
|
throw new Error("No default profile exists");
|
|
}
|
|
|
|
let lock;
|
|
try {
|
|
lock = profile.lock({});
|
|
lazy.log.info(
|
|
`withProfileLock: locked profile at ${lock.directory.path}`
|
|
);
|
|
} catch (e) {
|
|
throw new CannotLockProfileError(`Cannot lock profile: ${e}`);
|
|
}
|
|
|
|
try {
|
|
// We must await to ensure any logging is displayed after the callback resolves.
|
|
return await callback(lock);
|
|
} finally {
|
|
try {
|
|
lazy.log.info(
|
|
`withProfileLock: unlocking profile at ${lock.directory.path}`
|
|
);
|
|
lock.unlock();
|
|
lazy.log.info(`withProfileLock: unlocked profile`);
|
|
} catch (e) {
|
|
lazy.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);
|
|
lazy.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");
|
|
lazy.log.info(`readPreferences: will parse prefs ${prefsFile.path}`);
|
|
|
|
let data = await IOUtils.read(prefsFile.path);
|
|
lazy.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.
|
|
lazy.log.error(message);
|
|
},
|
|
},
|
|
prefsFile.path
|
|
);
|
|
|
|
lazy.log.debug(`readPreferences: parsed prefs from buffer`, prefs);
|
|
return prefs;
|
|
},
|
|
|
|
/**
|
|
* Reads the Telemetry Client ID out of a profile.
|
|
*
|
|
* 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 {nsIProfileLock} [lock] optional lock to use
|
|
* @returns {string}
|
|
*/
|
|
async readTelemetryClientID(lock = null) {
|
|
if (!lock) {
|
|
return this.withProfileLock(profileLock =>
|
|
this.readTelemetryClientID(profileLock)
|
|
);
|
|
}
|
|
|
|
this._throwIfNotLocked(lock);
|
|
|
|
let stateFile = lock.directory.clone();
|
|
stateFile.append("datareporting");
|
|
stateFile.append("state.json");
|
|
|
|
lazy.log.info(
|
|
`readPreferences: will read Telemetry client ID from ${stateFile.path}`
|
|
);
|
|
|
|
// This JSON is always UTF-8.
|
|
let data = await IOUtils.readUTF8(stateFile.path);
|
|
let state = JSON.parse(data);
|
|
|
|
return state.clientID;
|
|
},
|
|
};
|