mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
2744d40536
7
CLOBBER
7
CLOBBER
@ -22,8 +22,7 @@
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 1155718: Merge Bluetooth v1/v2 files for all simple cases
|
||||
Bug 1038068: Check add-on signatures and refuse to install unsigned or broken add-ons
|
||||
|
||||
This patch set renames source files. This requires updating the
|
||||
build system's dependency information from scratch. The issue has
|
||||
been reported in bug 1154232.
|
||||
Not sure why this needs a clobber but tests perma-failed when they don't on
|
||||
try.
|
||||
|
@ -69,6 +69,9 @@ pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35
|
||||
// See the SCOPE constants in AddonManager.jsm for values to use here.
|
||||
pref("extensions.autoDisableScopes", 15);
|
||||
|
||||
// Don't require signed add-ons by default
|
||||
pref("xpinstall.signatures.required", false);
|
||||
|
||||
// Dictionary download preference
|
||||
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
|
||||
|
||||
|
@ -147,8 +147,8 @@ let gTests = [
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(tokenData.code, "code1");
|
||||
Assert.equal(tokenData.state, "state");
|
||||
Assert.equal(keys.kAr, "kAr");
|
||||
Assert.equal(keys.kBr, "kBr");
|
||||
Assert.deepEqual(keys.kAr, {k: "kAr"});
|
||||
Assert.deepEqual(keys.kBr, {k: "kBr"});
|
||||
resolve();
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,9 @@
|
||||
state: "state",
|
||||
code: "code1",
|
||||
closeWindow: "signin",
|
||||
keys: { kAr: 'kAr', kBr: 'kBr' },
|
||||
// Keys normally contain more information, but this is enough
|
||||
// to keep Loop's tests happy.
|
||||
keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -202,10 +202,10 @@ let LoopRoomsInternal = {
|
||||
* information.
|
||||
*/
|
||||
promiseEncryptRoomData: Task.async(function* (roomData) {
|
||||
// For now, disable encryption/context if context is disabled, or if
|
||||
// FxA is turned on.
|
||||
if (!MozLoopService.getLoopPref("contextInConverations.enabled") ||
|
||||
this.sessionType == LOOP_SESSION_TYPE.FXA) {
|
||||
// XXX We should only return unencrypted data whilst we're still working
|
||||
// on context. Once bug 1115340 is fixed, this function should no longer be
|
||||
// here.
|
||||
function getUnencryptedData() {
|
||||
var serverRoomData = extend({}, roomData);
|
||||
delete serverRoomData.decryptedContext;
|
||||
|
||||
@ -218,6 +218,11 @@ let LoopRoomsInternal = {
|
||||
};
|
||||
}
|
||||
|
||||
// For now, disable encryption/context if context is disabled
|
||||
if (!MozLoopService.getLoopPref("contextInConverations.enabled")) {
|
||||
return getUnencryptedData();
|
||||
}
|
||||
|
||||
var newRoomData = extend({}, roomData);
|
||||
|
||||
if (!newRoomData.context) {
|
||||
@ -227,7 +232,17 @@ let LoopRoomsInternal = {
|
||||
// First get the room key.
|
||||
let key = yield this.promiseGetOrCreateRoomKey(newRoomData);
|
||||
|
||||
newRoomData.context.wrappedKey = yield this.promiseEncryptedRoomKey(key);
|
||||
try {
|
||||
newRoomData.context.wrappedKey = yield this.promiseEncryptedRoomKey(key);
|
||||
}
|
||||
catch (ex) {
|
||||
// XXX Bug 1153788 should remove this, then we can remove the whole
|
||||
// try/catch.
|
||||
if (ex.message == "FxA re-register not implemented") {
|
||||
return getUnencryptedData();
|
||||
}
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
|
||||
// Now encrypt the actual data.
|
||||
newRoomData.context.value = yield loopCrypto.encryptBytes(key,
|
||||
@ -654,8 +669,7 @@ let LoopRoomsInternal = {
|
||||
};
|
||||
|
||||
// If we're not encrypting currently, then only send the roomName.
|
||||
if (!Services.prefs.getBoolPref("loop.contextInConverations.enabled") ||
|
||||
this.sessionType == LOOP_SESSION_TYPE.FXA) {
|
||||
if (!Services.prefs.getBoolPref("loop.contextInConverations.enabled")) {
|
||||
sendData = {
|
||||
roomName: newRoomName
|
||||
};
|
||||
|
@ -959,6 +959,9 @@ let MozLoopServiceInternal = {
|
||||
|
||||
gFxAOAuthClientPromise = this.promiseFxAOAuthParameters().then(
|
||||
parameters => {
|
||||
// Add the fact that we want keys to the parameters.
|
||||
parameters.keys = true;
|
||||
|
||||
try {
|
||||
gFxAOAuthClient = new FxAccountsOAuthClient({
|
||||
parameters: parameters,
|
||||
@ -1031,7 +1034,10 @@ let MozLoopServiceInternal = {
|
||||
* @param {Deferred} deferred used to resolve the gFxAOAuthClientPromise
|
||||
* @param {Object} result (with code and state)
|
||||
*/
|
||||
_fxAOAuthComplete: function(deferred, result) {
|
||||
_fxAOAuthComplete: function(deferred, result, keys) {
|
||||
if (keys.kBr) {
|
||||
Services.prefs.setCharPref("loop.key.fxa", keys.kBr.k);
|
||||
}
|
||||
gFxAOAuthClientPromise = null;
|
||||
// Note: The state was already verified in FxAccountsOAuthClient.
|
||||
deferred.resolve(result);
|
||||
@ -1331,8 +1337,14 @@ this.MozLoopService = {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.userProfile) {
|
||||
// We're an FxA user.
|
||||
// XXX Bug 1153788 will implement this for FxA.
|
||||
reject(new Error("unimplemented"));
|
||||
if (Services.prefs.prefHasUserValue("loop.key.fxa")) {
|
||||
resolve(MozLoopService.getLoopPref("key.fxa"));
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX If we don't have a key for FxA yet, then simply reject for now.
|
||||
// We'll create some sign-in/sign-out UX in bug 1153788.
|
||||
reject(new Error("FxA re-register not implemented"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,18 @@ loop.shared.mixins = (function() {
|
||||
rootObject.removeEventListener("resize", this.updateVideoContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the dimensions cache, e.g. for when the session is ended, and
|
||||
* before a new session, so that we always ensure we see an update when a
|
||||
* new session is started.
|
||||
*/
|
||||
resetDimensionsCache: function() {
|
||||
this._videoDimensionsCache = {
|
||||
local: {},
|
||||
remote: {}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Whenever the dimensions change of a video stream, this function is called
|
||||
* by `updateVideoDimensions` to store the new values and notifies the callee
|
||||
|
@ -383,6 +383,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
this.updateVideoContainer();
|
||||
}
|
||||
|
||||
if (nextState.roomState === ROOM_STATES.INIT ||
|
||||
nextState.roomState === ROOM_STATES.GATHER ||
|
||||
nextState.roomState === ROOM_STATES.READY) {
|
||||
this.resetDimensionsCache();
|
||||
}
|
||||
|
||||
// When screen sharing stops.
|
||||
if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
|
||||
// Remove the custom screenshare styles on the remote camera.
|
||||
|
@ -383,6 +383,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
this.updateVideoContainer();
|
||||
}
|
||||
|
||||
if (nextState.roomState === ROOM_STATES.INIT ||
|
||||
nextState.roomState === ROOM_STATES.GATHER ||
|
||||
nextState.roomState === ROOM_STATES.READY) {
|
||||
this.resetDimensionsCache();
|
||||
}
|
||||
|
||||
// When screen sharing stops.
|
||||
if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
|
||||
// Remove the custom screenshare styles on the remote camera.
|
||||
|
@ -7,7 +7,7 @@ support-files =
|
||||
google_service.sjs
|
||||
head.js
|
||||
loop_fxa.sjs
|
||||
../../../../base/content/test/general/browser_fxa_oauth.html
|
||||
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
|
||||
|
||||
[browser_CardDavImporter.js]
|
||||
[browser_fxa_login.js]
|
||||
|
@ -134,7 +134,7 @@ function params(request, response) {
|
||||
*/
|
||||
function oauth_authorization(request, response) {
|
||||
response.setStatusLine(request.httpVersion, 302, "Found");
|
||||
response.setHeader("Location", "browser_fxa_oauth.html");
|
||||
response.setHeader("Location", "browser_fxa_oauth_with_keys.html");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,6 +214,19 @@ describe("loop.standaloneRoomViews", function() {
|
||||
|
||||
sinon.assert.calledOnce(view.updateVideoContainer);
|
||||
});
|
||||
|
||||
it("should reset the video dimensions cache when the gather state is entered", function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
|
||||
|
||||
var view = mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.GATHER});
|
||||
|
||||
expect(view._videoDimensionsCache).eql({
|
||||
local: {},
|
||||
remote: {}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("#publishStream", function() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
/* global Services, Assert */
|
||||
|
||||
const kGuestKeyPref = "loop.key";
|
||||
const kFxAKeyPref = "loop.key.fxa";
|
||||
|
||||
do_register_cleanup(function() {
|
||||
Services.prefs.clearUserPref(kGuestKeyPref);
|
||||
@ -33,12 +34,25 @@ add_task(function* test_guestGetKey() {
|
||||
Assert.equal(key, kFakeKey, "should return existing key");
|
||||
});
|
||||
|
||||
add_task(function* test_fxaGetKey() {
|
||||
// Set the userProfile to look like we're logged into FxA.
|
||||
add_task(function* test_fxaGetKnownKey() {
|
||||
const kFakeKey = "75312468";
|
||||
// Set the userProfile to look like we're logged into FxA with a key stored.
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
|
||||
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
|
||||
Services.prefs.setCharPref(kFxAKeyPref, kFakeKey);
|
||||
|
||||
let key = yield MozLoopService.promiseProfileEncryptionKey();
|
||||
|
||||
Assert.equal(key, kFakeKey, "should return existing key");
|
||||
});
|
||||
|
||||
add_task(function* test_fxaGetKey() {
|
||||
// Set the userProfile to look like we're logged into FxA without a key stored.
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
|
||||
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
|
||||
Services.prefs.clearUserPref(kFxAKeyPref);
|
||||
|
||||
// Currently unimplemented, add a test when we implement the code.
|
||||
yield Assert.rejects(MozLoopService.promiseProfileEncryptionKey(),
|
||||
/unimplemented/, "should reject as unimplemented");
|
||||
/not implemented/, "should reject as unimplemented");
|
||||
});
|
||||
|
@ -69,20 +69,22 @@ addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-on
|
||||
addonInstallRestartButton=Restart Now
|
||||
addonInstallRestartButton.accesskey=R
|
||||
|
||||
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
|
||||
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonError-5):
|
||||
# #1 is the add-on name, #2 is the host name, #3 is the application name
|
||||
# #4 is the application version
|
||||
addonError-1=The add-on could not be downloaded because of a connection failure on #2.
|
||||
addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
|
||||
addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
|
||||
addonError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||
addonError-5=#3 has prevented this site from installing an unverified add-on.
|
||||
|
||||
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
|
||||
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonLocalError-5, addonErrorIncompatible, addonErrorBlocklisted):
|
||||
# #1 is the add-on name, #3 is the application name, #4 is the application version
|
||||
addonLocalError-1=This add-on could not be installed because of a filesystem error.
|
||||
addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
|
||||
addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
|
||||
addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||
addonLocalError-5=This add-on could not be installed because it has not been verified.
|
||||
addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
|
||||
addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
|
||||
|
||||
|
@ -50,7 +50,6 @@ UNIFIED_SOURCES += [
|
||||
'src/base/pickle.cc',
|
||||
'src/base/rand_util.cc',
|
||||
'src/base/revocable_store.cc',
|
||||
'src/base/scoped_temp_dir.cc',
|
||||
'src/base/string_piece.cc',
|
||||
'src/base/string_util.cc',
|
||||
'src/base/thread.cc',
|
||||
|
@ -139,12 +139,6 @@ void AppendToPath(std::wstring* path, const std::wstring& new_ending) {
|
||||
path->push_back(FilePath::kSeparators[0]);
|
||||
path->append(new_ending);
|
||||
}
|
||||
bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
|
||||
bool recursive) {
|
||||
return CopyDirectory(FilePath::FromWStringHack(from_path),
|
||||
FilePath::FromWStringHack(to_path),
|
||||
recursive);
|
||||
}
|
||||
bool CopyFile(const std::wstring& from_path, const std::wstring& to_path) {
|
||||
return CopyFile(FilePath::FromWStringHack(from_path),
|
||||
FilePath::FromWStringHack(to_path));
|
||||
@ -172,8 +166,8 @@ bool CreateTemporaryFileName(std::wstring* temp_file) {
|
||||
*temp_file = temp_file_path.ToWStringHack();
|
||||
return true;
|
||||
}
|
||||
bool Delete(const std::wstring& path, bool recursive) {
|
||||
return Delete(FilePath::FromWStringHack(path), recursive);
|
||||
bool Delete(const std::wstring& path) {
|
||||
return Delete(FilePath::FromWStringHack(path));
|
||||
}
|
||||
bool DirectoryExists(const std::wstring& path) {
|
||||
return DirectoryExists(FilePath::FromWStringHack(path));
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include <sys/stat.h>
|
||||
#elif defined(OS_POSIX)
|
||||
#include <sys/types.h>
|
||||
#include <fts.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
@ -87,34 +86,17 @@ void ReplaceExtension(std::wstring* file_name, const std::wstring& extension);
|
||||
|
||||
// Deletes the given path, whether it's a file or a directory.
|
||||
// If it's a directory, it's perfectly happy to delete all of the
|
||||
// directory's contents. Passing true to recursive deletes
|
||||
// subdirectories and their contents as well.
|
||||
// directory's contents.
|
||||
// Returns true if successful, false otherwise.
|
||||
//
|
||||
// WARNING: USING THIS WITH recursive==true IS EQUIVALENT
|
||||
// TO "rm -rf", SO USE WITH CAUTION.
|
||||
bool Delete(const FilePath& path, bool recursive);
|
||||
bool Delete(const FilePath& path);
|
||||
// Deprecated temporary compatibility function.
|
||||
bool Delete(const std::wstring& path, bool recursive);
|
||||
bool Delete(const std::wstring& path);
|
||||
|
||||
// Copies a single file. Use CopyDirectory to copy directories.
|
||||
bool CopyFile(const FilePath& from_path, const FilePath& to_path);
|
||||
// Deprecated temporary compatibility function.
|
||||
bool CopyFile(const std::wstring& from_path, const std::wstring& to_path);
|
||||
|
||||
// Copies the given path, and optionally all subdirectories and their contents
|
||||
// as well.
|
||||
// If there are files existing under to_path, always overwrite.
|
||||
// Returns true if successful, false otherwise.
|
||||
// Dont't use wildcards on the names, it may stop working without notice.
|
||||
//
|
||||
// If you only need to copy a file use CopyFile, it's faster.
|
||||
bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
|
||||
bool recursive);
|
||||
// Deprecated temporary compatibility function.
|
||||
bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
|
||||
bool recursive);
|
||||
|
||||
// Returns true if the given path exists on the local filesystem,
|
||||
// false otherwise.
|
||||
bool PathExists(const FilePath& path);
|
||||
|
@ -8,13 +8,10 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
#ifndef ANDROID
|
||||
#include <fts.h>
|
||||
#endif
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/errno.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#define _DARWIN_USE_64_BIT_INODE // Use 64-bit inode data structures
|
||||
#include <sys/stat.h>
|
||||
@ -53,7 +50,7 @@ bool AbsolutePath(FilePath* path) {
|
||||
// which works both with and without the recursive flag. I'm not sure we need
|
||||
// that functionality. If not, remove from file_util_win.cc, otherwise add it
|
||||
// here.
|
||||
bool Delete(const FilePath& path, bool recursive) {
|
||||
bool Delete(const FilePath& path) {
|
||||
const char* path_str = path.value().c_str();
|
||||
struct stat file_info;
|
||||
int test = stat(path_str, &file_info);
|
||||
@ -64,174 +61,8 @@ bool Delete(const FilePath& path, bool recursive) {
|
||||
}
|
||||
if (!S_ISDIR(file_info.st_mode))
|
||||
return (unlink(path_str) == 0);
|
||||
if (!recursive)
|
||||
return (rmdir(path_str) == 0);
|
||||
|
||||
#ifdef ANDROID
|
||||
// XXX Need ftsless impl for bionic
|
||||
return false;
|
||||
#else
|
||||
bool success = true;
|
||||
int ftsflags = FTS_PHYSICAL | FTS_NOSTAT;
|
||||
char top_dir[PATH_MAX];
|
||||
if (base::strlcpy(top_dir, path_str,
|
||||
arraysize(top_dir)) >= arraysize(top_dir)) {
|
||||
return false;
|
||||
}
|
||||
char* dir_list[2] = { top_dir, NULL };
|
||||
FTS* fts = fts_open(dir_list, ftsflags, NULL);
|
||||
if (fts) {
|
||||
FTSENT* fts_ent = fts_read(fts);
|
||||
while (success && fts_ent != NULL) {
|
||||
switch (fts_ent->fts_info) {
|
||||
case FTS_DNR:
|
||||
case FTS_ERR:
|
||||
// log error
|
||||
success = false;
|
||||
continue;
|
||||
break;
|
||||
case FTS_DP:
|
||||
success = (rmdir(fts_ent->fts_accpath) == 0);
|
||||
break;
|
||||
case FTS_D:
|
||||
break;
|
||||
case FTS_NSOK:
|
||||
case FTS_F:
|
||||
case FTS_SL:
|
||||
case FTS_SLNONE:
|
||||
success = (unlink(fts_ent->fts_accpath) == 0);
|
||||
break;
|
||||
default:
|
||||
DCHECK(false);
|
||||
break;
|
||||
}
|
||||
fts_ent = fts_read(fts);
|
||||
}
|
||||
fts_close(fts);
|
||||
}
|
||||
return success;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Move(const FilePath& from_path, const FilePath& to_path) {
|
||||
if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0)
|
||||
return true;
|
||||
|
||||
if (!CopyDirectory(from_path, to_path, true))
|
||||
return false;
|
||||
|
||||
Delete(from_path, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CopyDirectory(const FilePath& from_path,
|
||||
const FilePath& to_path,
|
||||
bool recursive) {
|
||||
// Some old callers of CopyDirectory want it to support wildcards.
|
||||
// After some discussion, we decided to fix those callers.
|
||||
// Break loudly here if anyone tries to do this.
|
||||
// TODO(evanm): remove this once we're sure it's ok.
|
||||
DCHECK(to_path.value().find('*') == std::string::npos);
|
||||
DCHECK(from_path.value().find('*') == std::string::npos);
|
||||
|
||||
char top_dir[PATH_MAX];
|
||||
if (base::strlcpy(top_dir, from_path.value().c_str(),
|
||||
arraysize(top_dir)) >= arraysize(top_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
// XXX Need ftsless impl for bionic
|
||||
return false;
|
||||
#else
|
||||
char* dir_list[] = { top_dir, NULL };
|
||||
FTS* fts = fts_open(dir_list, FTS_PHYSICAL | FTS_NOSTAT, NULL);
|
||||
if (!fts) {
|
||||
CHROMIUM_LOG(ERROR) << "fts_open failed: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int error = 0;
|
||||
FTSENT* ent;
|
||||
while (!error && (ent = fts_read(fts)) != NULL) {
|
||||
// ent->fts_path is the source path, including from_path, so paste
|
||||
// the suffix after from_path onto to_path to create the target_path.
|
||||
std::string suffix(&ent->fts_path[from_path.value().size()]);
|
||||
// Strip the leading '/' (if any).
|
||||
if (!suffix.empty()) {
|
||||
DCHECK_EQ('/', suffix[0]);
|
||||
suffix.erase(0, 1);
|
||||
}
|
||||
const FilePath target_path = to_path.Append(suffix);
|
||||
switch (ent->fts_info) {
|
||||
case FTS_D: // Preorder directory.
|
||||
// If we encounter a subdirectory in a non-recursive copy, prune it
|
||||
// from the traversal.
|
||||
if (!recursive && ent->fts_level > 0) {
|
||||
if (fts_set(fts, ent, FTS_SKIP) != 0)
|
||||
error = errno;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try creating the target dir, continuing on it if it exists already.
|
||||
// Rely on the user's umask to produce correct permissions.
|
||||
if (mkdir(target_path.value().c_str(), 0777) != 0) {
|
||||
if (errno != EEXIST)
|
||||
error = errno;
|
||||
}
|
||||
break;
|
||||
case FTS_F: // Regular file.
|
||||
case FTS_NSOK: // File, no stat info requested.
|
||||
errno = 0;
|
||||
if (!CopyFile(FilePath(ent->fts_path), target_path))
|
||||
error = errno ? errno : EINVAL;
|
||||
break;
|
||||
case FTS_DP: // Postorder directory.
|
||||
case FTS_DOT: // "." or ".."
|
||||
// Skip it.
|
||||
continue;
|
||||
case FTS_DC: // Directory causing a cycle.
|
||||
// Skip this branch.
|
||||
if (fts_set(fts, ent, FTS_SKIP) != 0)
|
||||
error = errno;
|
||||
break;
|
||||
case FTS_DNR: // Directory cannot be read.
|
||||
case FTS_ERR: // Error.
|
||||
case FTS_NS: // Stat failed.
|
||||
// Abort with the error.
|
||||
error = ent->fts_errno;
|
||||
break;
|
||||
case FTS_SL: // Symlink.
|
||||
case FTS_SLNONE: // Symlink with broken target.
|
||||
CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping symbolic link: " <<
|
||||
ent->fts_path;
|
||||
continue;
|
||||
case FTS_DEFAULT: // Some other sort of file.
|
||||
CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping file of unknown type: " <<
|
||||
ent->fts_path;
|
||||
continue;
|
||||
default:
|
||||
NOTREACHED();
|
||||
continue; // Hope for the best!
|
||||
}
|
||||
}
|
||||
// fts_read may have returned NULL and set errno to indicate an error.
|
||||
if (!error && errno != 0)
|
||||
error = errno;
|
||||
|
||||
if (!fts_close(fts)) {
|
||||
// If we already have an error, let's use that error instead of the error
|
||||
// fts_close set.
|
||||
if (!error)
|
||||
error = errno;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
CHROMIUM_LOG(ERROR) << "CopyDirectory(): " << strerror(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
return (rmdir(path_str) == 0);
|
||||
}
|
||||
|
||||
bool PathExists(const FilePath& path) {
|
||||
|
@ -27,14 +27,14 @@ bool AbsolutePath(FilePath* path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Delete(const FilePath& path, bool recursive) {
|
||||
bool Delete(const FilePath& path) {
|
||||
if (path.value().length() >= MAX_PATH)
|
||||
return false;
|
||||
|
||||
// If we're not recursing use DeleteFile; it should be faster. DeleteFile
|
||||
// Use DeleteFile; it should be faster. DeleteFile
|
||||
// fails if passed a directory though, which is why we fall through on
|
||||
// failure to the SHFileOperation.
|
||||
if (!recursive && DeleteFile(path.value().c_str()) != 0)
|
||||
if (DeleteFile(path.value().c_str()) != 0)
|
||||
return true;
|
||||
|
||||
// SHFILEOPSTRUCT wants the path to be terminated with two NULLs,
|
||||
@ -48,8 +48,7 @@ bool Delete(const FilePath& path, bool recursive) {
|
||||
file_operation.wFunc = FO_DELETE;
|
||||
file_operation.pFrom = double_terminated_path;
|
||||
file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION;
|
||||
if (!recursive)
|
||||
file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
|
||||
file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
|
||||
int err = SHFileOperation(&file_operation);
|
||||
// Some versions of Windows return ERROR_FILE_NOT_FOUND when
|
||||
// deleting an empty directory.
|
||||
@ -98,26 +97,6 @@ bool ShellCopy(const FilePath& from_path, const FilePath& to_path,
|
||||
return (SHFileOperation(&file_operation) == 0);
|
||||
}
|
||||
|
||||
bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
|
||||
bool recursive) {
|
||||
if (recursive)
|
||||
return ShellCopy(from_path, to_path, true);
|
||||
|
||||
// Instead of creating a new directory, we copy the old one to include the
|
||||
// security information of the folder as part of the copy.
|
||||
if (!PathExists(to_path)) {
|
||||
// Except that Vista fails to do that, and instead do a recursive copy if
|
||||
// the target directory doesn't exist.
|
||||
if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA)
|
||||
CreateDirectory(to_path);
|
||||
else
|
||||
ShellCopy(from_path, to_path, false);
|
||||
}
|
||||
|
||||
FilePath directory = from_path.Append(L"*.*");
|
||||
return ShellCopy(directory, to_path, false);
|
||||
}
|
||||
|
||||
bool PathExists(const FilePath& path) {
|
||||
return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES);
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/scoped_temp_dir.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
ScopedTempDir::ScopedTempDir() {
|
||||
}
|
||||
|
||||
ScopedTempDir::~ScopedTempDir() {
|
||||
if (!path_.empty() && !file_util::Delete(path_, true))
|
||||
CHROMIUM_LOG(ERROR) << "ScopedTempDir unable to delete " << path_.value();
|
||||
}
|
||||
|
||||
bool ScopedTempDir::CreateUniqueTempDir() {
|
||||
// This "scoped_dir" prefix is only used on Windows and serves as a template
|
||||
// for the unique name.
|
||||
if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"),
|
||||
&path_))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScopedTempDir::Set(const FilePath& path) {
|
||||
DCHECK(path_.empty());
|
||||
if (!file_util::DirectoryExists(path) &&
|
||||
!file_util::CreateDirectory(path)) {
|
||||
return false;
|
||||
}
|
||||
path_ = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
FilePath ScopedTempDir::Take() {
|
||||
FilePath ret = path_;
|
||||
path_ = FilePath();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ScopedTempDir::IsValid() const {
|
||||
return !path_.empty() && file_util::DirectoryExists(path_);
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_SCOPED_TEMP_DIR_H_
|
||||
#define BASE_SCOPED_TEMP_DIR_H_
|
||||
|
||||
// An object representing a temporary / scratch directory that should be cleaned
|
||||
// up (recursively) when this object goes out of scope. Note that since
|
||||
// deletion occurs during the destructor, no further error handling is possible
|
||||
// if the directory fails to be deleted. As a result, deletion is not
|
||||
// guaranteed by this class.
|
||||
|
||||
#include "base/file_path.h"
|
||||
|
||||
class ScopedTempDir {
|
||||
public:
|
||||
// No directory is owned/created initially.
|
||||
ScopedTempDir();
|
||||
|
||||
// Recursively delete path_
|
||||
~ScopedTempDir();
|
||||
|
||||
// Creates a unique directory in TempPath, and takes ownership of it.
|
||||
// See file_util::CreateNewTemporaryDirectory.
|
||||
bool CreateUniqueTempDir();
|
||||
|
||||
// Takes ownership of directory at |path|, creating it if necessary.
|
||||
// Don't call multiple times unless Take() has been called first.
|
||||
bool Set(const FilePath& path);
|
||||
|
||||
// Caller takes ownership of the temporary directory so it won't be destroyed
|
||||
// when this object goes out of scope.
|
||||
FilePath Take();
|
||||
|
||||
const FilePath& path() const { return path_; }
|
||||
|
||||
// Returns true if path_ is non-empty and exists.
|
||||
bool IsValid() const;
|
||||
|
||||
private:
|
||||
FilePath path_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedTempDir);
|
||||
};
|
||||
|
||||
#endif // BASE_SCOPED_TEMP_DIR_H_
|
@ -93,7 +93,7 @@ bool SharedMemory::Delete(const std::wstring& name) {
|
||||
|
||||
FilePath path(WideToUTF8(mem_filename));
|
||||
if (file_util::PathExists(path)) {
|
||||
return file_util::Delete(path, false);
|
||||
return file_util::Delete(path);
|
||||
}
|
||||
|
||||
// Doesn't exist, so success.
|
||||
@ -170,7 +170,7 @@ bool SharedMemory::CreateOrOpen(const std::wstring &name,
|
||||
// Deleting the file prevents anyone else from mapping it in
|
||||
// (making it private), and prevents the need for cleanup (once
|
||||
// the last fd is closed, it is truly freed).
|
||||
file_util::Delete(path, false);
|
||||
file_util::Delete(path);
|
||||
} else {
|
||||
std::wstring mem_filename;
|
||||
if (FilenameForMemoryName(name, &mem_filename) == false)
|
||||
|
@ -223,6 +223,8 @@ class RefTest(object):
|
||||
# And for about:newtab content fetch and pings.
|
||||
prefs['browser.newtabpage.directory.source'] = 'data:application/json,{"reftest":1}'
|
||||
prefs['browser.newtabpage.directory.ping'] = ''
|
||||
# Allow unsigned add-ons
|
||||
prefs['xpinstall.signatures.required'] = False
|
||||
|
||||
#Don't use auto-enabled e10s
|
||||
prefs['browser.tabs.remote.autostart.1'] = False
|
||||
|
@ -198,20 +198,15 @@
|
||||
<!ENTITY tab_queue_prompt_tip_text "you can change this later in Settings">
|
||||
<!ENTITY tab_queue_prompt_positive_action_button "Enable">
|
||||
<!ENTITY tab_queue_prompt_negative_action_button "Not now">
|
||||
<!-- Localization note (tab_queue_notification_text_plural) : The
|
||||
<!ENTITY tab_queue_notification_title "&brandShortName;">
|
||||
<!-- Localization note (tab_queue_notification_text_plural2) : The
|
||||
formatD is replaced with the number of tabs queued. The
|
||||
number of tabs queued is always more than one. We can't use
|
||||
Android plural forms, sadly. See Bug #753859. -->
|
||||
<!ENTITY tab_queue_notification_text_plural "&formatD; tabs queued">
|
||||
<!-- Localization note (tab_queue_notification_title_plural) : This is the
|
||||
title of a notification; we expect more than one tab queued. -->
|
||||
<!ENTITY tab_queue_notification_title_plural "Tabs Queued">
|
||||
<!-- Localization note (tab_queue_notification_title_singular) : This is the
|
||||
title of a notification; we expect only one tab queued. -->
|
||||
<!ENTITY tab_queue_notification_title_singular "Tab Queued">
|
||||
<!-- Localization note (tab_queue_notification_text_singular) : This is the
|
||||
<!ENTITY tab_queue_notification_text_plural2 "&formatD; tabs waiting">
|
||||
<!-- Localization note (tab_queue_notification_text_singular2) : This is the
|
||||
text of a notification; we expect only one tab queued. -->
|
||||
<!ENTITY tab_queue_notification_text_singular "1 tab queued">
|
||||
<!ENTITY tab_queue_notification_text_singular2 "1 tab waiting">
|
||||
|
||||
<!ENTITY pref_char_encoding "Character encoding">
|
||||
<!ENTITY pref_char_encoding_on "Show menu">
|
||||
|
@ -137,6 +137,8 @@ OnSharedPreferenceChangeListener
|
||||
public static final String PREFS_SUGGESTED_SITES = NON_PREF_PREFIX + "home_suggested_sites";
|
||||
public static final String PREFS_TAB_QUEUE = NON_PREF_PREFIX + "tab_queue";
|
||||
public static final String PREFS_CUSTOMIZE_SCREEN = NON_PREF_PREFIX + "customize_screen";
|
||||
public static final String PREFS_TAB_QUEUE_LAST_SITE = NON_PREF_PREFIX + "last_site";
|
||||
public static final String PREFS_TAB_QUEUE_LAST_TIME = NON_PREF_PREFIX + "last_time";
|
||||
|
||||
// These values are chosen to be distinct from other Activity constants.
|
||||
private static final int REQUEST_CODE_PREF_SCREEN = 5;
|
||||
|
35
mobile/android/base/resources/layout/tab_queue_toast.xml
Normal file
35
mobile/android/base/resources/layout/tab_queue_toast.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/toast"
|
||||
style="@style/Toast">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toast_message"
|
||||
style="@style/ToastMessage"
|
||||
|
||||
tools:text="Tab queued in firefox" />
|
||||
|
||||
<View
|
||||
android:id="@+id/toast_divider"
|
||||
style="@style/ToastDivider" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/toast_button"
|
||||
style="@style/ToastButton"
|
||||
android:drawableLeft="@drawable/switch_button_icon"
|
||||
|
||||
tools:text="Open" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
12
mobile/android/base/resources/values-w400dp/styles.xml
Normal file
12
mobile/android/base/resources/values-w400dp/styles.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Toast" parent="ToastBase">
|
||||
<item name="android:layout_width">400dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -247,10 +247,9 @@
|
||||
<string name="tab_queue_prompt_negative_action_button">&tab_queue_prompt_negative_action_button;</string>
|
||||
<string name="tab_queue_toast_message">&tab_queue_toast_message2;</string>
|
||||
<string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
|
||||
<string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular;</string>
|
||||
<string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural;</string>
|
||||
<string name="tab_queue_notification_title_singular">&tab_queue_notification_title_singular;</string>
|
||||
<string name="tab_queue_notification_title_plural">&tab_queue_notification_title_plural;</string>
|
||||
<string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
|
||||
<string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
|
||||
<string name="tab_queue_notification_title">&tab_queue_notification_title;</string>
|
||||
|
||||
<string name="pref_about_firefox">&pref_about_firefox;</string>
|
||||
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
|
||||
|
@ -21,6 +21,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -104,6 +105,45 @@ public class TabQueueHelper {
|
||||
return jsonArray.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a url from the file, if it exists.
|
||||
* If the url exists multiple times, all instances of it will be removed.
|
||||
* This should not be run on the UI thread.
|
||||
*
|
||||
* @param context
|
||||
* @param urlToRemove URL to remove
|
||||
* @param filename filename to remove URL from
|
||||
* @return the number of queued urls
|
||||
*/
|
||||
public static int removeURLFromFile(final Context context, final String urlToRemove, final String filename) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
|
||||
JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
|
||||
JSONArray newArray = new JSONArray();
|
||||
String url;
|
||||
|
||||
// Since JSONArray.remove was only added in API 19, we have to use two arrays in order to remove.
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
try {
|
||||
url = jsonArray.getString(i);
|
||||
} catch (JSONException e) {
|
||||
url = "";
|
||||
}
|
||||
if(!TextUtils.isEmpty(url) && !urlToRemove.equals(url)) {
|
||||
newArray.put(url);
|
||||
}
|
||||
}
|
||||
|
||||
profile.writeFile(filename, newArray.toString());
|
||||
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
prefs.edit().putInt(PREF_TAB_QUEUE_COUNT, newArray.length()).apply();
|
||||
|
||||
return newArray.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a notification showing the total number of tabs queue. If there is already a notification displayed, it
|
||||
* will be replaced.
|
||||
@ -119,19 +159,17 @@ public class TabQueueHelper {
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
String title, text;
|
||||
final String text;
|
||||
final Resources resources = context.getResources();
|
||||
if (tabsQueued == 1) {
|
||||
title = resources.getString(R.string.tab_queue_notification_title_singular);
|
||||
text = resources.getString(R.string.tab_queue_notification_text_singular);
|
||||
} else {
|
||||
title = resources.getString(R.string.tab_queue_notification_title_plural);
|
||||
text = resources.getString(R.string.tab_queue_notification_text_plural, tabsQueued);
|
||||
}
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_status_logo)
|
||||
.setContentTitle(title)
|
||||
.setContentTitle(resources.getString(R.string.tab_queue_notification_title))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
|
@ -5,6 +5,13 @@
|
||||
|
||||
package org.mozilla.gecko.tabqueue;
|
||||
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -14,6 +21,7 @@ import android.graphics.PixelFormat;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
@ -21,12 +29,6 @@ import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils;
|
||||
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -56,6 +58,7 @@ public class TabQueueService extends Service {
|
||||
private static final String LOGTAG = "Gecko" + TabQueueService.class.getSimpleName();
|
||||
|
||||
private static final long TOAST_TIMEOUT = 3000;
|
||||
private static final long TOAST_DOUBLE_TAP_TIMEOUT_MILLIS = 6000;
|
||||
|
||||
private WindowManager windowManager;
|
||||
private View toastLayout;
|
||||
@ -84,7 +87,7 @@ public class TabQueueService extends Service {
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
|
||||
LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
toastLayout = layoutInflater.inflate(R.layout.button_toast, null);
|
||||
toastLayout = layoutInflater.inflate(R.layout.tab_queue_toast, null);
|
||||
|
||||
final Resources resources = getResources();
|
||||
|
||||
@ -95,7 +98,7 @@ public class TabQueueService extends Service {
|
||||
openNowButton.setText(resources.getText(R.string.tab_queue_toast_action));
|
||||
|
||||
toastLayoutParams = new WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
|
||||
@ -108,6 +111,50 @@ public class TabQueueService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
// If this is a redelivery then lets bypass the entire double tap to open now code as that's a big can of worms,
|
||||
// we also don't expect redeliveries because of the short time window associated with this feature.
|
||||
if (flags != START_FLAG_REDELIVERY) {
|
||||
final Context applicationContext = getApplicationContext();
|
||||
final SharedPreferences sharedPreferences = GeckoSharedPrefs.forApp(applicationContext);
|
||||
|
||||
final String lastUrl = sharedPreferences.getString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, "");
|
||||
|
||||
final ContextUtils.SafeIntent safeIntent = new ContextUtils.SafeIntent(intent);
|
||||
final String intentUrl = safeIntent.getDataString();
|
||||
|
||||
final long lastRunTime = sharedPreferences.getLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, 0);
|
||||
final boolean isWithinDoubleTapTimeLimit = System.currentTimeMillis() - lastRunTime < TOAST_DOUBLE_TAP_TIMEOUT_MILLIS;
|
||||
|
||||
if (!TextUtils.isEmpty(lastUrl) && lastUrl.equals(intentUrl) && isWithinDoubleTapTimeLimit) {
|
||||
// Background thread because we could do some file IO if we have to remove a url from the list.
|
||||
tabQueueHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If there is a runnable around, that means that the previous process hasn't yet completed, so
|
||||
// we will need to prevent it from running and remove the view from the window manager.
|
||||
// If there is no runnable around then the url has already been added to the list, so we'll
|
||||
// need to remove it before proceeding or that url will open multiple times.
|
||||
if (stopServiceRunnable != null) {
|
||||
tabQueueHandler.removeCallbacks(stopServiceRunnable);
|
||||
stopSelfResult(stopServiceRunnable.getStartId());
|
||||
stopServiceRunnable = null;
|
||||
removeView();
|
||||
} else {
|
||||
TabQueueHelper.removeURLFromFile(applicationContext, intentUrl, TabQueueHelper.FILE_NAME);
|
||||
}
|
||||
openNow(safeIntent.getUnsafe());
|
||||
stopSelfResult(startId);
|
||||
}
|
||||
});
|
||||
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
sharedPreferences.edit().putString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, intentUrl)
|
||||
.putLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, System.currentTimeMillis())
|
||||
.apply();
|
||||
}
|
||||
|
||||
if (stopServiceRunnable != null) {
|
||||
// If we're already displaying a toast, keep displaying it but store the previous url.
|
||||
// The open button will refer to the most recently opened link.
|
||||
@ -130,14 +177,8 @@ public class TabQueueService extends Service {
|
||||
public void onClick(final View view) {
|
||||
tabQueueHandler.removeCallbacks(stopServiceRunnable);
|
||||
stopServiceRunnable = null;
|
||||
|
||||
|
||||
Intent forwardIntent = new Intent(intent);
|
||||
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
|
||||
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(forwardIntent);
|
||||
|
||||
removeView();
|
||||
openNow(intent);
|
||||
stopSelfResult(startId);
|
||||
}
|
||||
});
|
||||
@ -147,6 +188,17 @@ public class TabQueueService extends Service {
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void openNow(Intent intent) {
|
||||
Intent forwardIntent = new Intent(intent);
|
||||
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
|
||||
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(forwardIntent);
|
||||
|
||||
GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
|
||||
.remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void removeView() {
|
||||
windowManager.removeView(toastLayout);
|
||||
}
|
||||
@ -198,18 +250,22 @@ public class TabQueueService extends Service {
|
||||
this.startId = startId;
|
||||
}
|
||||
|
||||
public void run(final boolean shouldStopService) {
|
||||
public void run() {
|
||||
run(true);
|
||||
}
|
||||
|
||||
public void run(final boolean shouldRemoveView) {
|
||||
onRun();
|
||||
|
||||
if (shouldStopService) {
|
||||
if (shouldRemoveView) {
|
||||
removeView();
|
||||
}
|
||||
|
||||
stopSelfResult(startId);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
run(true);
|
||||
public int getStartId() {
|
||||
return startId;
|
||||
}
|
||||
|
||||
public abstract void onRun();
|
||||
|
@ -4108,6 +4108,8 @@ pref("browser.history.maxStateObjectSize", 655360);
|
||||
|
||||
// XPInstall prefs
|
||||
pref("xpinstall.whitelist.required", true);
|
||||
// Only Firefox requires add-on signatures
|
||||
pref("xpinstall.signatures.required", false);
|
||||
pref("extensions.alwaysUnpack", false);
|
||||
pref("extensions.minCompatiblePlatformVersion", "2.0");
|
||||
|
||||
|
@ -23,6 +23,9 @@
|
||||
// Trusted Hosted Apps Certificates
|
||||
#include "manifest-signing-root.inc"
|
||||
#include "manifest-signing-test-root.inc"
|
||||
// Add-on signing Certificates
|
||||
#include "addons-public.inc"
|
||||
#include "addons-stage.inc"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
@ -93,6 +96,16 @@ AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
|
||||
trustedDER.len = mozilla::ArrayLength(trustedAppTestRoot);
|
||||
break;
|
||||
|
||||
case nsIX509CertDB::AddonsPublicRoot:
|
||||
trustedDER.data = const_cast<uint8_t*>(addonsPublicRoot);
|
||||
trustedDER.len = mozilla::ArrayLength(addonsPublicRoot);
|
||||
break;
|
||||
|
||||
case nsIX509CertDB::AddonsStageRoot:
|
||||
trustedDER.data = const_cast<uint8_t*>(addonsStageRoot);
|
||||
trustedDER.len = mozilla::ArrayLength(addonsStageRoot);
|
||||
break;
|
||||
|
||||
default:
|
||||
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
|
||||
return SECFailure;
|
||||
|
BIN
security/apps/addons-public.crt
Normal file
BIN
security/apps/addons-public.crt
Normal file
Binary file not shown.
BIN
security/apps/addons-stage.crt
Normal file
BIN
security/apps/addons-stage.crt
Normal file
Binary file not shown.
@ -35,6 +35,8 @@ array_names = [
|
||||
'trustedAppPublicRoot',
|
||||
'trustedAppTestRoot',
|
||||
'xpcshellRoot',
|
||||
'addonsPublicRoot',
|
||||
'addonsStageRoot',
|
||||
]
|
||||
|
||||
for n in array_names:
|
||||
|
@ -34,6 +34,8 @@ headers_arrays_certs = [
|
||||
('manifest-signing-root.inc', 'trustedAppPublicRoot', 'trusted-app-public.der'),
|
||||
('manifest-signing-test-root.inc', 'trustedAppTestRoot', test_ssl_path + '/test_signed_manifest/trusted_ca1.der'),
|
||||
('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/trusted_ca1.der'),
|
||||
('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'),
|
||||
('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'),
|
||||
]
|
||||
|
||||
for header, array_name, cert in headers_arrays_certs:
|
||||
|
@ -39,7 +39,7 @@ interface nsIVerifySignedManifestCallback : nsISupports
|
||||
* This represents a service to access and manipulate
|
||||
* X.509 certificates stored in a database.
|
||||
*/
|
||||
[scriptable, uuid(8b01c2af-3a44-44d3-8ea5-51c2455e6c4b)]
|
||||
[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)]
|
||||
interface nsIX509CertDB : nsISupports {
|
||||
|
||||
/**
|
||||
@ -311,6 +311,8 @@ interface nsIX509CertDB : nsISupports {
|
||||
const AppTrustedRoot AppXPCShellRoot = 6;
|
||||
const AppTrustedRoot TrustedHostedAppPublicRoot = 7;
|
||||
const AppTrustedRoot TrustedHostedAppTestRoot = 8;
|
||||
const AppTrustedRoot AddonsPublicRoot = 9;
|
||||
const AppTrustedRoot AddonsStageRoot = 10;
|
||||
void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
|
||||
in nsIFile aJarFile,
|
||||
in nsIOpenSignedAppFileCallback callback);
|
||||
|
@ -76,6 +76,8 @@ user_pref("extensions.getAddons.cache.enabled", false);
|
||||
user_pref("extensions.installDistroAddons", false);
|
||||
// XPI extensions are required for test harnesses to load
|
||||
user_pref("extensions.defaultProviders.enabled", true);
|
||||
// Disable signature requirements where possible
|
||||
user_pref("xpinstall.signatures.required", false);
|
||||
|
||||
user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
|
||||
user_pref("geo.wifi.timeToWaitBeforeSending", 2000);
|
||||
|
@ -3446,50 +3446,59 @@
|
||||
"description": "PLACES: Days from last maintenance"
|
||||
},
|
||||
"UPDATE_CHECK_NO_UPDATE_EXTERNAL" : {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of no updates were found for a background update check (externally initiated)"
|
||||
},
|
||||
"UPDATE_CHECK_NO_UPDATE_NOTIFY" : {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of no updates were found for a background update check (timer initiated)"
|
||||
},
|
||||
"UPDATE_CHECK_CODE_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 50,
|
||||
"description": "Update: background update check result code except for no updates found (externally initiated)"
|
||||
},
|
||||
"UPDATE_CHECK_CODE_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 50,
|
||||
"description": "Update: background update check result code except for no updates found (timer initiated)"
|
||||
},
|
||||
"UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"keyed": true,
|
||||
"description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (externally initiated)"
|
||||
},
|
||||
"UPDATE_CHECK_EXTENDED_ERROR_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"keyed": true,
|
||||
"description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (timer initiated)"
|
||||
},
|
||||
"UPDATE_INVALID_LASTUPDATETIME_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that have a last update time greater than the current time (externally initiated)"
|
||||
},
|
||||
"UPDATE_INVALID_LASTUPDATETIME_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that have a last update time greater than the current time (timer initiated)"
|
||||
},
|
||||
"UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"n_buckets": 60,
|
||||
@ -3497,6 +3506,7 @@
|
||||
"description": "Update: interval in days since the last background update check (externally initiated)"
|
||||
},
|
||||
"UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"n_buckets": 30,
|
||||
@ -3504,214 +3514,253 @@
|
||||
"description": "Update: interval in days since the last background update check (timer initiated)"
|
||||
},
|
||||
"UPDATE_PING_COUNT_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems for this ping for comparison with other pings (externally initiated)"
|
||||
},
|
||||
"UPDATE_PING_COUNT_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems for this ping for comparison with other pings (timer initiated)"
|
||||
},
|
||||
"UPDATE_SERVICE_INSTALLED_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "Update: whether the service is installed (externally initiated)"
|
||||
},
|
||||
"UPDATE_SERVICE_INSTALLED_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "Update: whether the service is installed (timer initiated)"
|
||||
},
|
||||
"UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that manually uninstalled the service (externally initiated)"
|
||||
},
|
||||
"UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that manually uninstalled the service (timer initiated)"
|
||||
},
|
||||
"UPDATE_UNABLE_TO_APPLY_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that cannot apply updates (externally initiated)"
|
||||
},
|
||||
"UPDATE_UNABLE_TO_APPLY_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that cannot apply updates (timer initiated)"
|
||||
},
|
||||
"UPDATE_CANNOT_STAGE_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that cannot stage updates (externally initiated)"
|
||||
},
|
||||
"UPDATE_CANNOT_STAGE_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that cannot stage updates (timer initiated)"
|
||||
},
|
||||
"UPDATE_HAS_PREF_URL_OVERRIDE_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that have an app.update.url.override preference (externally initiated)"
|
||||
},
|
||||
"UPDATE_HAS_PREF_URL_OVERRIDE_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of systems that have an app.update.url.override preference (timer initiated)"
|
||||
},
|
||||
"UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: number of sequential update elevation request cancelations greater than 0 (externally initiated)"
|
||||
},
|
||||
"UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: number of sequential update elevation request cancelations greater than 0 (timer initiated)"
|
||||
},
|
||||
"UPDATE_PREF_SERVICE_ERRORS_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 30,
|
||||
"description": "Update: number of sequential update service errors greater than 0 (externally initiated)"
|
||||
},
|
||||
"UPDATE_PREF_SERVICE_ERRORS_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 30,
|
||||
"description": "Update: number of sequential update service errors greater than 0 (timer initiated)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_ENABLED_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_ENABLED_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
|
||||
},
|
||||
"UPDATE_DOWNLOAD_CODE_COMPLETE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 50,
|
||||
"description": "Update: complete patch download result code"
|
||||
},
|
||||
"UPDATE_DOWNLOAD_CODE_PARTIAL": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 50,
|
||||
"description": "Update: complete patch download result code"
|
||||
},
|
||||
"UPDATE_STATE_CODE_COMPLETE_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of a complete update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATE_CODE_PARTIAL_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of a partial patch update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATE_CODE_UNKNOWN_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of an unknown patch update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATE_CODE_COMPLETE_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of a complete patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_STATE_CODE_PARTIAL_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of a partial patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_STATE_CODE_UNKNOWN_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 20,
|
||||
"description": "Update: the state of an unknown patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed complete patch update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed partial patch update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed unknown patch update from update.status on startup"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed complete patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed partial patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 100,
|
||||
"description": "Update: the status error code for a failed unknown patch update from update.status after staging"
|
||||
},
|
||||
"UPDATE_WIZ_LAST_PAGE_CODE": {
|
||||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 30,
|
||||
|
@ -2672,6 +2672,8 @@ this.AddonManager = {
|
||||
ERROR_CORRUPT_FILE: -3,
|
||||
// An error occured trying to write to the filesystem.
|
||||
ERROR_FILE_ACCESS: -4,
|
||||
// The add-on must be signed and isn't.
|
||||
ERROR_SIGNEDSTATE_REQUIRED: -5,
|
||||
|
||||
// These must be kept in sync with AddonUpdateChecker.
|
||||
// No error was encountered.
|
||||
@ -2807,6 +2809,20 @@ this.AddonManager = {
|
||||
// add-ons that were pending being enabled the last time the application ran.
|
||||
STARTUP_CHANGE_ENABLED: "enabled",
|
||||
|
||||
// Constants for Addon.signedState. Any states that should cause an add-on
|
||||
// to be unusable in builds that require signing should have negative values.
|
||||
// Add-on is signed but signature verification has failed.
|
||||
SIGNEDSTATE_BROKEN: -2,
|
||||
// Add-on may be signed but by an certificate that doesn't chain to our
|
||||
// our trusted certificate.
|
||||
SIGNEDSTATE_UNKNOWN: -1,
|
||||
// Add-on is unsigned.
|
||||
SIGNEDSTATE_MISSING: 0,
|
||||
// Add-on is preliminarily reviewed.
|
||||
SIGNEDSTATE_PRELIMINARY: 1,
|
||||
// Add-on is fully reviewed.
|
||||
SIGNEDSTATE_SIGNED: 2,
|
||||
|
||||
// Constants for the Addon.userDisabled property
|
||||
// Indicates that the userDisabled state of this add-on is currently
|
||||
// ask-to-activate. That is, it can be conditionally enabled on a
|
||||
|
@ -78,6 +78,9 @@ const PREF_XPI_ENABLED = "xpinstall.enabled";
|
||||
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
|
||||
const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
|
||||
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
|
||||
// xpinstall.signatures.required only supported in dev builds
|
||||
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
|
||||
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";
|
||||
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
|
||||
const PREF_XPI_UNPACK = "extensions.alwaysUnpack";
|
||||
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
|
||||
@ -100,7 +103,6 @@ const STRING_TYPE_NAME = "type.%ID%.name";
|
||||
|
||||
const DIR_EXTENSIONS = "extensions";
|
||||
const DIR_STAGE = "staged";
|
||||
const DIR_XPI_STAGE = "staged-xpis";
|
||||
const DIR_TRASH = "trash";
|
||||
|
||||
const FILE_DATABASE = "extensions.json";
|
||||
@ -190,6 +192,19 @@ const RESTARTLESS_TYPES = new Set([
|
||||
"locale",
|
||||
]);
|
||||
|
||||
const SIGNED_TYPES = new Set([
|
||||
"extension",
|
||||
"experiment",
|
||||
]);
|
||||
|
||||
// Whether add-on signing is required.
|
||||
function mustSign(aType) {
|
||||
if (!SIGNED_TYPES.has(aType))
|
||||
return false;
|
||||
return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false);
|
||||
}
|
||||
|
||||
|
||||
// Keep track of where we are in startup for telemetry
|
||||
// event happened during XPIDatabase.startup()
|
||||
const XPI_STARTING = "XPIStarting";
|
||||
@ -630,6 +645,9 @@ function isUsableAddon(aAddon) {
|
||||
if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
|
||||
return true;
|
||||
|
||||
if (mustSign(aAddon.type) && aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING)
|
||||
return false;
|
||||
|
||||
if (aAddon.blocklistState == Blocklist.STATE_BLOCKED)
|
||||
return false;
|
||||
|
||||
@ -999,7 +1017,7 @@ function loadManifestFromRDF(aUri, aStream) {
|
||||
* @return an AddonInternal object
|
||||
* @throws if the directory does not contain a valid install manifest
|
||||
*/
|
||||
function loadManifestFromDir(aDir) {
|
||||
let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) {
|
||||
function getFileSize(aFile) {
|
||||
if (aFile.isSymlink())
|
||||
return 0;
|
||||
@ -1040,6 +1058,11 @@ function loadManifestFromDir(aDir) {
|
||||
addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
|
||||
"binary-component");
|
||||
|
||||
if (SIGNED_TYPES.has(addon.type))
|
||||
addon.signedState = yield verifyDirSignedState(aDir, addon.id);
|
||||
else
|
||||
addon.signedState = AddonManager.SIGNEDSTATE_MISSING;
|
||||
|
||||
addon.appDisabled = !isUsableAddon(addon);
|
||||
return addon;
|
||||
}
|
||||
@ -1047,7 +1070,7 @@ function loadManifestFromDir(aDir) {
|
||||
bis.close();
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Loads an AddonInternal object from an nsIZipReader for an add-on.
|
||||
@ -1057,7 +1080,7 @@ function loadManifestFromDir(aDir) {
|
||||
* @return an AddonInternal object
|
||||
* @throws if the XPI file does not contain a valid install manifest
|
||||
*/
|
||||
function loadManifestFromZipReader(aZipReader) {
|
||||
let loadManifestFromZipReader = Task.async(function* loadManifestFromZipReader(aZipReader) {
|
||||
let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
|
||||
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
|
||||
createInstance(Ci.nsIBufferedInputStream);
|
||||
@ -1083,6 +1106,11 @@ function loadManifestFromZipReader(aZipReader) {
|
||||
addon.hasBinaryComponents = false;
|
||||
}
|
||||
|
||||
if (SIGNED_TYPES.has(addon.type))
|
||||
addon.signedState = yield verifyZipSignedState(aZipReader.file, addon.id, addon.version);
|
||||
else
|
||||
addon.signedState = AddonManager.SIGNEDSTATE_MISSING;
|
||||
|
||||
addon.appDisabled = !isUsableAddon(addon);
|
||||
return addon;
|
||||
}
|
||||
@ -1090,7 +1118,7 @@ function loadManifestFromZipReader(aZipReader) {
|
||||
bis.close();
|
||||
zis.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Loads an AddonInternal object from an add-on in an XPI file.
|
||||
@ -1100,18 +1128,22 @@ function loadManifestFromZipReader(aZipReader) {
|
||||
* @return an AddonInternal object
|
||||
* @throws if the XPI file does not contain a valid install manifest
|
||||
*/
|
||||
function loadManifestFromZipFile(aXPIFile) {
|
||||
let loadManifestFromZipFile = Task.async(function* loadManifestFromZipFile(aXPIFile) {
|
||||
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
||||
createInstance(Ci.nsIZipReader);
|
||||
try {
|
||||
zipReader.open(aXPIFile);
|
||||
|
||||
return loadManifestFromZipReader(zipReader);
|
||||
// Can't return this promise because that will make us close the zip reader
|
||||
// before it has finished loading the manifest. Wait for the result and then
|
||||
// return.
|
||||
let manifest = yield loadManifestFromZipReader(zipReader);
|
||||
return manifest;
|
||||
}
|
||||
finally {
|
||||
zipReader.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function loadManifestFromFile(aFile) {
|
||||
if (aFile.isFile())
|
||||
@ -1120,6 +1152,32 @@ function loadManifestFromFile(aFile) {
|
||||
return loadManifestFromDir(aFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* A synchronous method for loading an add-on's manifest. This should only ever
|
||||
* be used during startup or a sync load of the add-ons DB
|
||||
*/
|
||||
function syncLoadManifestFromFile(aFile) {
|
||||
let success = undefined;
|
||||
let result = null;
|
||||
|
||||
loadManifestFromFile(aFile).then(val => {
|
||||
success = true;
|
||||
result = val;
|
||||
}, val => {
|
||||
success = false;
|
||||
result = val
|
||||
});
|
||||
|
||||
let thread = Services.tm.currentThread;
|
||||
|
||||
while (success === undefined)
|
||||
thread.processNextEvent(true);
|
||||
|
||||
if (!success)
|
||||
throw result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an nsIURI for a file within another file, either a directory or an XPI
|
||||
* file. If aFile is a directory then this will return a file: URI, if it is an
|
||||
@ -1226,6 +1284,81 @@ function verifyZipSigning(aZip, aCertificate) {
|
||||
return aZip.manifestEntriesCount == count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signedState for a given return code and certificate by verifying
|
||||
* it against the expected ID.
|
||||
*/
|
||||
function getSignedStatus(aRv, aCert, aExpectedID) {
|
||||
switch (aRv) {
|
||||
case Cr.NS_OK:
|
||||
if (aExpectedID != aCert.commonName)
|
||||
return AddonManager.SIGNEDSTATE_BROKEN;
|
||||
|
||||
return /preliminary/i.test(aCert.organizationalUnit)
|
||||
? AddonManager.SIGNEDSTATE_PRELIMINARY
|
||||
: AddonManager.SIGNEDSTATE_SIGNED;
|
||||
break;
|
||||
case Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED:
|
||||
return AddonManager.SIGNEDSTATE_MISSING;
|
||||
break;
|
||||
case Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID:
|
||||
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID:
|
||||
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING:
|
||||
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE:
|
||||
case Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY:
|
||||
case Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY:
|
||||
return AddonManager.SIGNEDSTATE_BROKEN;
|
||||
break;
|
||||
default:
|
||||
// Any other error indicates that either the add-on isn't signed or it
|
||||
// is signed by a signature that doesn't chain to the trusted root.
|
||||
return AddonManager.SIGNEDSTATE_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a zip file's contents are all correctly signed by an
|
||||
* AMO-issued certificate
|
||||
*
|
||||
* @param aFile
|
||||
* the xpi file to check
|
||||
* @param aExpectedID
|
||||
* the expected ID of the signature
|
||||
* @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
|
||||
*/
|
||||
function verifyZipSignedState(aFile, aExpectedID, aVersion) {
|
||||
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
||||
.getService(Ci.nsIX509CertDB);
|
||||
|
||||
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
|
||||
if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
|
||||
root = Ci.nsIX509CertDB.AddonsStageRoot;
|
||||
|
||||
return new Promise(resolve => {
|
||||
certDB.openSignedAppFileAsync(root, aFile, (aRv, aZipReader, aCert) => {
|
||||
if (aZipReader)
|
||||
aZipReader.close();
|
||||
resolve(getSignedStatus(aRv, aCert, aExpectedID));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a directory's contents are all correctly signed by an
|
||||
* AMO-issued certificate
|
||||
*
|
||||
* @param aDir
|
||||
* the directory to check
|
||||
* @param aExpectedID
|
||||
* the expected ID of the signature
|
||||
* @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
|
||||
*/
|
||||
function verifyDirSignedState(aDir, aExpectedID) {
|
||||
// TODO: Get the certificate for an unpacked add-on (bug 1038072)
|
||||
return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces %...% strings in an addon url (update and updateInfo) with
|
||||
* appropriate values.
|
||||
@ -2081,6 +2214,8 @@ this.XPIProvider = {
|
||||
|
||||
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
|
||||
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
|
||||
if (!REQUIRE_SIGNING)
|
||||
Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this, false);
|
||||
Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
|
||||
if (Cu.isModuleLoaded("resource:///modules/devtools/ToolboxProcess.jsm")) {
|
||||
// If BrowserToolboxProcess is already loaded, set the boolean to true
|
||||
@ -2424,95 +2559,8 @@ this.XPIProvider = {
|
||||
if (aLocation.locked)
|
||||
return;
|
||||
|
||||
let stagedXPIDir = aLocation.getXPIStagingDir();
|
||||
let stagingDir = aLocation.getStagingDir();
|
||||
|
||||
if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) {
|
||||
let entries = stagedXPIDir.directoryEntries
|
||||
.QueryInterface(Ci.nsIDirectoryEnumerator);
|
||||
while (entries.hasMoreElements()) {
|
||||
let stageDirEntry = entries.nextFile;
|
||||
|
||||
if (!stageDirEntry.isDirectory()) {
|
||||
logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the last added XPI file in the directory
|
||||
let stagedXPI = null;
|
||||
var xpiEntries = stageDirEntry.directoryEntries
|
||||
.QueryInterface(Ci.nsIDirectoryEnumerator);
|
||||
while (xpiEntries.hasMoreElements()) {
|
||||
let file = xpiEntries.nextFile;
|
||||
if (file.isDirectory())
|
||||
continue;
|
||||
|
||||
let extension = file.leafName;
|
||||
extension = extension.substring(extension.length - 4);
|
||||
|
||||
if (extension != ".xpi" && extension != ".jar")
|
||||
continue;
|
||||
|
||||
stagedXPI = file;
|
||||
}
|
||||
xpiEntries.close();
|
||||
|
||||
if (!stagedXPI)
|
||||
continue;
|
||||
|
||||
let addon = null;
|
||||
try {
|
||||
addon = loadManifestFromZipFile(stagedXPI);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Unable to read add-on manifest from " + stagedXPI.path, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name);
|
||||
|
||||
if (addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
|
||||
let targetDir = stagingDir.clone();
|
||||
targetDir.append(addon.id);
|
||||
try {
|
||||
targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to create staging directory for add-on " + addon.id, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ZipUtils.extractFiles(stagedXPI, targetDir);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
|
||||
aLocation.name, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to move staged XPI for add-on " + addon.id + " in " +
|
||||
aLocation.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.close();
|
||||
}
|
||||
|
||||
if (stagedXPIDir.exists()) {
|
||||
try {
|
||||
recursiveRemove(stagedXPIDir);
|
||||
}
|
||||
catch (e) {
|
||||
// Non-critical, just saves some perf on startup if we clean this up.
|
||||
logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
|
||||
return;
|
||||
@ -2593,7 +2641,7 @@ this.XPIProvider = {
|
||||
jsonfile.append(id + ".json");
|
||||
|
||||
try {
|
||||
aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry);
|
||||
aManifests[aLocation.name][id] = syncLoadManifestFromFile(stageDirEntry);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
|
||||
@ -2603,6 +2651,15 @@ this.XPIProvider = {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addon = aManifests[aLocation.name][id];
|
||||
|
||||
if ((addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) && mustSign(addon.type)) {
|
||||
logger.warn("Refusing to install staged add-on " + id + " with signed state " + addon.signedState);
|
||||
seenFiles.push(stageDirEntry.leafName);
|
||||
seenFiles.push(jsonfile.leafName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for a cached metadata for this add-on, it may contain updated
|
||||
// compatibility information
|
||||
if (jsonfile.exists()) {
|
||||
@ -2615,7 +2672,7 @@ this.XPIProvider = {
|
||||
try {
|
||||
fis.init(jsonfile, -1, 0, 0);
|
||||
let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
|
||||
aManifests[aLocation.name][id].importMetadata(metadata);
|
||||
addon.importMetadata(metadata);
|
||||
}
|
||||
catch (e) {
|
||||
// If some data can't be recovered from the cached metadata then it
|
||||
@ -2629,7 +2686,7 @@ this.XPIProvider = {
|
||||
}
|
||||
seenFiles.push(jsonfile.leafName);
|
||||
|
||||
existingAddonID = aManifests[aLocation.name][id].existingAddonID || id;
|
||||
existingAddonID = addon.existingAddonID || id;
|
||||
|
||||
var oldBootstrap = null;
|
||||
logger.debug("Processing install of " + id + " in " + aLocation.name);
|
||||
@ -2642,7 +2699,7 @@ this.XPIProvider = {
|
||||
|
||||
// We'll be replacing a currently active bootstrapped add-on so
|
||||
// call its uninstall method
|
||||
let newVersion = aManifests[aLocation.name][id].version;
|
||||
let newVersion = addon.version;
|
||||
let oldVersion = oldBootstrap.version;
|
||||
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
|
||||
BOOTSTRAP_REASONS.ADDON_UPGRADE :
|
||||
@ -2660,17 +2717,15 @@ this.XPIProvider = {
|
||||
}
|
||||
|
||||
try {
|
||||
var addonInstallLocation = aLocation.installAddon(id, stageDirEntry,
|
||||
existingAddonID);
|
||||
if (aManifests[aLocation.name][id])
|
||||
aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation;
|
||||
addon._sourceBundle = aLocation.installAddon(id, stageDirEntry,
|
||||
existingAddonID);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to install staged add-on " + id + " in " + aLocation.name,
|
||||
e);
|
||||
// Re-create the staged install
|
||||
AddonInstall.createStagedInstall(aLocation, stageDirEntry,
|
||||
aManifests[aLocation.name][id]);
|
||||
addon);
|
||||
// Make sure not to delete the cached manifest json file
|
||||
seenFiles.pop();
|
||||
|
||||
@ -2756,7 +2811,7 @@ this.XPIProvider = {
|
||||
|
||||
let addon;
|
||||
try {
|
||||
addon = loadManifestFromFile(entry);
|
||||
addon = syncLoadManifestFromFile(entry);
|
||||
}
|
||||
catch (e) {
|
||||
logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
|
||||
@ -2779,7 +2834,7 @@ this.XPIProvider = {
|
||||
if (existingEntry) {
|
||||
let existingAddon;
|
||||
try {
|
||||
existingAddon = loadManifestFromFile(existingEntry);
|
||||
existingAddon = syncLoadManifestFromFile(existingEntry);
|
||||
|
||||
if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
|
||||
continue;
|
||||
@ -2874,7 +2929,7 @@ this.XPIProvider = {
|
||||
// If not load it
|
||||
if (!newAddon) {
|
||||
let file = aInstallLocation.getLocationForID(aOldAddon.id);
|
||||
newAddon = loadManifestFromFile(file);
|
||||
newAddon = syncLoadManifestFromFile(file);
|
||||
applyBlocklistChanges(aOldAddon, newAddon);
|
||||
|
||||
// Carry over any pendingUninstall state to add-ons modified directly
|
||||
@ -2917,6 +2972,11 @@ this.XPIProvider = {
|
||||
// Remember add-ons that were changed during startup
|
||||
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
|
||||
newDBAddon.id);
|
||||
if (aOldAddon.active == newDBAddon.disabled) {
|
||||
let change = aOldAddon.active ? AddonManager.STARTUP_CHANGE_DISABLED
|
||||
: AddonManager.STARTUP_CHANGE_ENABLED;
|
||||
AddonManagerPrivate.addStartupChange(change, newDBAddon.id);
|
||||
}
|
||||
|
||||
// If this was the active theme and it is now disabled then enable the
|
||||
// default theme
|
||||
@ -3041,7 +3101,16 @@ this.XPIProvider = {
|
||||
let wasAppDisabled = aOldAddon.appDisabled;
|
||||
let wasUserDisabled = aOldAddon.userDisabled;
|
||||
let wasSoftDisabled = aOldAddon.softDisabled;
|
||||
let updateDB = false;
|
||||
|
||||
// If updating from a version of the app that didn't support signedState
|
||||
// then fetch that property now
|
||||
if (aOldAddon.signedState === undefined) {
|
||||
let file = aInstallLocation.getLocationForID(aOldAddon.id);
|
||||
let manifest = syncLoadManifestFromFile(file);
|
||||
aOldAddon.signedState = manifest.signedState;
|
||||
updateDB = true;
|
||||
}
|
||||
// This updates the addon's JSON cached data in place
|
||||
applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
|
||||
aOldPlatformVersion);
|
||||
@ -3050,7 +3119,7 @@ this.XPIProvider = {
|
||||
let isDisabled = aOldAddon.disabled;
|
||||
|
||||
// If either property has changed update the database.
|
||||
if (wasAppDisabled != aOldAddon.appDisabled ||
|
||||
if (updateDB || wasAppDisabled != aOldAddon.appDisabled ||
|
||||
wasUserDisabled != aOldAddon.userDisabled ||
|
||||
wasSoftDisabled != aOldAddon.softDisabled) {
|
||||
logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
|
||||
@ -3171,7 +3240,7 @@ this.XPIProvider = {
|
||||
if (!newAddon) {
|
||||
// Load the manifest from the add-on.
|
||||
let file = aInstallLocation.getLocationForID(aId);
|
||||
newAddon = loadManifestFromFile(file);
|
||||
newAddon = syncLoadManifestFromFile(file);
|
||||
}
|
||||
// The add-on in the manifest should match the add-on ID.
|
||||
if (newAddon.id != aId) {
|
||||
@ -4094,13 +4163,18 @@ this.XPIProvider = {
|
||||
if (aTopic == "nsPref:changed") {
|
||||
switch (aData) {
|
||||
case PREF_EM_MIN_COMPAT_APP_VERSION:
|
||||
case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
|
||||
this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
|
||||
null);
|
||||
this.updateAddonAppDisabledStates();
|
||||
break;
|
||||
case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
|
||||
this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
|
||||
null);
|
||||
this.updateAddonAppDisabledStates();
|
||||
break;
|
||||
case PREF_XPI_SIGNATURES_REQUIRED:
|
||||
this.updateAddonAppDisabledStates();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -4914,49 +4988,45 @@ AddonInstall.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let self = this;
|
||||
this.loadManifest(function initLocalInstall_loadManifest() {
|
||||
XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
|
||||
self.existingAddon = aAddon;
|
||||
if (aAddon)
|
||||
applyBlocklistChanges(aAddon, self.addon);
|
||||
self.addon.updateDate = Date.now();
|
||||
self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
|
||||
let self = this;
|
||||
this.loadManifest().then(() => {
|
||||
XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
|
||||
self.existingAddon = aAddon;
|
||||
if (aAddon)
|
||||
applyBlocklistChanges(aAddon, self.addon);
|
||||
self.addon.updateDate = Date.now();
|
||||
self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
|
||||
|
||||
if (!self.addon.isCompatible) {
|
||||
// TODO Should we send some event here?
|
||||
self.state = AddonManager.STATE_CHECKING;
|
||||
new UpdateChecker(self.addon, {
|
||||
onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
|
||||
self.state = AddonManager.STATE_DOWNLOADED;
|
||||
XPIProvider.installs.push(self);
|
||||
AddonManagerPrivate.callInstallListeners("onNewInstall",
|
||||
self.listeners,
|
||||
self.wrapper);
|
||||
if (!self.addon.isCompatible) {
|
||||
// TODO Should we send some event here?
|
||||
self.state = AddonManager.STATE_CHECKING;
|
||||
new UpdateChecker(self.addon, {
|
||||
onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
|
||||
self.state = AddonManager.STATE_DOWNLOADED;
|
||||
XPIProvider.installs.push(self);
|
||||
AddonManagerPrivate.callInstallListeners("onNewInstall",
|
||||
self.listeners,
|
||||
self.wrapper);
|
||||
|
||||
aCallback(self);
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
|
||||
}
|
||||
else {
|
||||
XPIProvider.installs.push(self);
|
||||
AddonManagerPrivate.callInstallListeners("onNewInstall",
|
||||
self.listeners,
|
||||
self.wrapper);
|
||||
aCallback(self);
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
|
||||
}
|
||||
else {
|
||||
XPIProvider.installs.push(self);
|
||||
AddonManagerPrivate.callInstallListeners("onNewInstall",
|
||||
self.listeners,
|
||||
self.wrapper);
|
||||
|
||||
aCallback(self);
|
||||
}
|
||||
});
|
||||
aCallback(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
logger.warn("Invalid XPI", e);
|
||||
}, ([error, message]) => {
|
||||
logger.warn("Invalid XPI", message);
|
||||
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
|
||||
this.error = AddonManager.ERROR_CORRUPT_FILE;
|
||||
this.error = error;
|
||||
aCallback(this);
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -5145,8 +5215,7 @@ AddonInstall.prototype = {
|
||||
* loaded. Because this loadMultipackageManifests is an internal API
|
||||
* we don't exception-wrap this callback
|
||||
*/
|
||||
_loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader,
|
||||
aCallback) {
|
||||
_loadMultipackageManifests: Task.async(function* AI_loadMultipackageManifests(aZipReader) {
|
||||
let files = [];
|
||||
let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])");
|
||||
while (entries.hasMore()) {
|
||||
@ -5179,7 +5248,7 @@ AddonInstall.prototype = {
|
||||
this.file = files.shift();
|
||||
this.ownsTempFile = true;
|
||||
try {
|
||||
addon = loadManifestFromZipFile(this.file);
|
||||
addon = yield loadManifestFromZipFile(this.file);
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
@ -5190,7 +5259,6 @@ AddonInstall.prototype = {
|
||||
|
||||
if (!addon) {
|
||||
// No valid add-on was found
|
||||
aCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5212,36 +5280,28 @@ AddonInstall.prototype = {
|
||||
// Create new AddonInstall instances for every remaining file
|
||||
if (files.length > 0) {
|
||||
this.linkedInstalls = [];
|
||||
let count = 0;
|
||||
let self = this;
|
||||
files.forEach(function(file) {
|
||||
AddonInstall.createInstall(function loadMultipackageManifests_createInstall(aInstall) {
|
||||
// Ignore bad add-ons (createInstall will have logged the error)
|
||||
if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
|
||||
// Manually remove the temporary file
|
||||
file.remove(true);
|
||||
}
|
||||
else {
|
||||
// Make the new install own its temporary file
|
||||
aInstall.ownsTempFile = true;
|
||||
for (let file of files) {
|
||||
let install = yield new Promise(resolve => AddonInstall.createInstall(resolve, file));
|
||||
|
||||
self.linkedInstalls.push(aInstall)
|
||||
// Ignore bad add-ons (createInstall will have logged the error)
|
||||
if (install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
|
||||
// Manually remove the temporary file
|
||||
file.remove(true);
|
||||
}
|
||||
else {
|
||||
// Make the new install own its temporary file
|
||||
install.ownsTempFile = true;
|
||||
|
||||
aInstall.sourceURI = self.sourceURI;
|
||||
aInstall.releaseNotesURI = self.releaseNotesURI;
|
||||
aInstall.updateAddonURIs();
|
||||
}
|
||||
self.linkedInstalls.push(install)
|
||||
|
||||
count++;
|
||||
if (count == files.length)
|
||||
aCallback();
|
||||
}, file);
|
||||
}, this);
|
||||
install.sourceURI = self.sourceURI;
|
||||
install.releaseNotesURI = self.releaseNotesURI;
|
||||
install.updateAddonURIs();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
aCallback();
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called after the add-on is a local file and the signature and install
|
||||
@ -5252,36 +5312,7 @@ AddonInstall.prototype = {
|
||||
* @throws if the add-on does not contain a valid install manifest or the
|
||||
* XPI is incorrectly signed
|
||||
*/
|
||||
loadManifest: function AI_loadManifest(aCallback) {
|
||||
aCallback = makeSafe(aCallback);
|
||||
let self = this;
|
||||
function addRepositoryData(aAddon) {
|
||||
// Try to load from the existing cache first
|
||||
AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
|
||||
if (aRepoAddon) {
|
||||
aAddon._repositoryAddon = aRepoAddon;
|
||||
self.name = self.name || aAddon._repositoryAddon.name;
|
||||
aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
|
||||
aAddon.appDisabled = !isUsableAddon(aAddon);
|
||||
aCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// It wasn't there so try to re-download it
|
||||
AddonRepository.cacheAddons([aAddon.id], function loadManifest_cacheAddons() {
|
||||
AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
|
||||
aAddon._repositoryAddon = aRepoAddon;
|
||||
self.name = self.name || aAddon._repositoryAddon.name;
|
||||
aAddon.compatibilityOverrides = aRepoAddon ?
|
||||
aRepoAddon.compatibilityOverrides :
|
||||
null;
|
||||
aAddon.appDisabled = !isUsableAddon(aAddon);
|
||||
aCallback();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadManifest: Task.async(function* AI_loadManifest() {
|
||||
let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
||||
createInstance(Ci.nsIZipReader);
|
||||
try {
|
||||
@ -5289,39 +5320,56 @@ AddonInstall.prototype = {
|
||||
}
|
||||
catch (e) {
|
||||
zipreader.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
let x509 = zipreader.getSigningCert(null);
|
||||
if (x509) {
|
||||
logger.debug("Verifying XPI signature");
|
||||
if (verifyZipSigning(zipreader, x509)) {
|
||||
this.certificate = x509;
|
||||
if (this.certificate.commonName.length > 0) {
|
||||
this.certName = this.certificate.commonName;
|
||||
} else {
|
||||
this.certName = this.certificate.organization;
|
||||
}
|
||||
} else {
|
||||
zipreader.close();
|
||||
throw new Error("XPI is incorrectly signed");
|
||||
}
|
||||
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
|
||||
}
|
||||
|
||||
try {
|
||||
this.addon = loadManifestFromZipReader(zipreader);
|
||||
// loadManifestFromZipReader performs the certificate verification for us
|
||||
this.addon = yield loadManifestFromZipReader(zipreader);
|
||||
}
|
||||
catch (e) {
|
||||
zipreader.close();
|
||||
throw e;
|
||||
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
|
||||
}
|
||||
|
||||
if (this.addon.type == "multipackage") {
|
||||
this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() {
|
||||
addRepositoryData(self.addon);
|
||||
});
|
||||
return;
|
||||
if (mustSign(this.addon.type)) {
|
||||
if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
// This add-on isn't properly signed by a signature that chains to the
|
||||
// trusted root.
|
||||
let state = this.addon.signedState;
|
||||
this.addon = null;
|
||||
zipreader.close();
|
||||
|
||||
if (state == AddonManager.SIGNEDSTATE_MISSING)
|
||||
return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
|
||||
"signature is required but missing"])
|
||||
|
||||
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
|
||||
"signature verification failed"])
|
||||
}
|
||||
}
|
||||
else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN) {
|
||||
// Check object signing certificate, if any
|
||||
let x509 = zipreader.getSigningCert(null);
|
||||
if (x509) {
|
||||
logger.debug("Verifying XPI signature");
|
||||
if (verifyZipSigning(zipreader, x509)) {
|
||||
this.certificate = x509;
|
||||
if (this.certificate.commonName.length > 0) {
|
||||
this.certName = this.certificate.commonName;
|
||||
} else {
|
||||
this.certName = this.certificate.organization;
|
||||
}
|
||||
} else {
|
||||
zipreader.close();
|
||||
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
|
||||
"XPI is incorrectly signed"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.addon.type == "multipackage")
|
||||
return this._loadMultipackageManifests(zipreader);
|
||||
|
||||
zipreader.close();
|
||||
|
||||
@ -5338,8 +5386,22 @@ AddonInstall.prototype = {
|
||||
//if (newIcon)
|
||||
// this.iconURL = newIcon;
|
||||
|
||||
addRepositoryData(this.addon);
|
||||
},
|
||||
// Try to load from the existing cache first
|
||||
let repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
|
||||
|
||||
// It wasn't there so try to re-download it
|
||||
if (!repoAddon) {
|
||||
yield new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve));
|
||||
repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
|
||||
}
|
||||
|
||||
this.addon._repositoryAddon = repoAddon;
|
||||
this.name = this.name || this.addon._repositoryAddon.name;
|
||||
this.addon.compatibilityOverrides = repoAddon ?
|
||||
repoAddon.compatibilityOverrides :
|
||||
null;
|
||||
this.addon.appDisabled = !isUsableAddon(this.addon);
|
||||
}),
|
||||
|
||||
observe: function AI_observe(aSubject, aTopic, aData) {
|
||||
// Network is going offline
|
||||
@ -5575,26 +5637,24 @@ AddonInstall.prototype = {
|
||||
") did not match provided hash (" + this.hash.data + ")");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let self = this;
|
||||
this.loadManifest(function onStopRequest_loadManifest() {
|
||||
if (self.addon.isCompatible) {
|
||||
self.downloadCompleted();
|
||||
}
|
||||
else {
|
||||
// TODO Should we send some event here (bug 557716)?
|
||||
self.state = AddonManager.STATE_CHECKING;
|
||||
new UpdateChecker(self.addon, {
|
||||
onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
|
||||
self.downloadCompleted();
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
this.loadManifest().then(() => {
|
||||
if (self.addon.isCompatible) {
|
||||
self.downloadCompleted();
|
||||
}
|
||||
else {
|
||||
// TODO Should we send some event here (bug 557716)?
|
||||
self.state = AddonManager.STATE_CHECKING;
|
||||
new UpdateChecker(self.addon, {
|
||||
onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
|
||||
self.downloadCompleted();
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
|
||||
}
|
||||
}, ([error, message]) => {
|
||||
this.downloadFailed(error, message);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (aRequest instanceof Ci.nsIHttpChannel)
|
||||
@ -6619,7 +6679,7 @@ function AddonWrapper(aAddon) {
|
||||
"providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
|
||||
"softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
|
||||
"strictCompatibility", "compatibilityOverrides", "updateURL",
|
||||
"getDataDirectory", "multiprocessCompatible"].forEach(function(aProp) {
|
||||
"getDataDirectory", "multiprocessCompatible", "signedState"].forEach(function(aProp) {
|
||||
this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
|
||||
}, this);
|
||||
|
||||
@ -7191,7 +7251,7 @@ DirectoryInstallLocation.prototype = {
|
||||
for (let entry of entries) {
|
||||
let id = entry.leafName;
|
||||
|
||||
if (id == DIR_STAGE || id == DIR_XPI_STAGE || id == DIR_TRASH)
|
||||
if (id == DIR_STAGE || id == DIR_TRASH)
|
||||
continue;
|
||||
|
||||
let directLoad = false;
|
||||
@ -7334,18 +7394,6 @@ DirectoryInstallLocation.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the directory used by old versions for staging XPI and JAR files ready
|
||||
* to be installed.
|
||||
*
|
||||
* @return an nsIFile
|
||||
*/
|
||||
getXPIStagingDir: function DirInstallLocation_getXPIStagingDir() {
|
||||
let dir = this._directory.clone();
|
||||
dir.append(DIR_XPI_STAGE);
|
||||
return dir;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a directory that is normally on the same filesystem as the rest of
|
||||
* the install location and can be used for temporarily storing files during
|
||||
@ -7722,6 +7770,19 @@ WinRegInstallLocation.prototype = {
|
||||
};
|
||||
#endif
|
||||
|
||||
// Make this a non-changable property so it can't be manipulated from other
|
||||
// code in the app.
|
||||
Object.defineProperty(this, "REQUIRE_SIGNING", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
#ifdef MOZ_REQUIRE_SIGNING
|
||||
value: true,
|
||||
#else
|
||||
value: false,
|
||||
#endif
|
||||
});
|
||||
|
||||
let addonTypes = [
|
||||
new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
|
||||
STRING_TYPE_NAME,
|
||||
|
@ -70,7 +70,7 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
|
||||
"skinnable", "size", "sourceURI", "releaseNotesURI",
|
||||
"softDisabled", "foreignInstall", "hasBinaryComponents",
|
||||
"strictCompatibility", "locales", "targetApplications",
|
||||
"targetPlatforms", "multiprocessCompatible"];
|
||||
"targetPlatforms", "multiprocessCompatible", "signedState"];
|
||||
|
||||
// Time to wait before async save of XPI JSON database, in milliseconds
|
||||
const ASYNC_SAVE_DELAY_MS = 20;
|
||||
|
@ -28,7 +28,7 @@ EXTRA_PP_JS_MODULES.addons += [
|
||||
|
||||
# This is used in multiple places, so is defined here to avoid it getting
|
||||
# out of sync.
|
||||
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16
|
||||
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 17
|
||||
|
||||
# Additional debugging info is exposed in debug builds
|
||||
if CONFIG['MOZ_EM_DEBUG']:
|
||||
|
29
toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js
vendored
Normal file
29
toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
// Test steps chain from pref observers on *_reason,
|
||||
// so always set that last
|
||||
function install(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
|
||||
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test Add-on</em:name>
|
||||
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||
<em:minVersion>2</em:minVersion>
|
||||
<em:maxVersion>5</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
@ -0,0 +1 @@
|
||||
This test file can be altered to break signing checks.
|
29
toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js
vendored
Normal file
29
toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const VERSION = 2;
|
||||
|
||||
// Test steps chain from pref observers on *_reason,
|
||||
// so always set that last
|
||||
function install(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
|
||||
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test@tests.mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test Add-on</em:name>
|
||||
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||
<em:minVersion>4</em:minVersion>
|
||||
<em:maxVersion>6</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
@ -0,0 +1 @@
|
||||
This test file can be altered to break signing checks.
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test Add-on</em:name>
|
||||
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||
<em:minVersion>2</em:minVersion>
|
||||
<em:maxVersion>5</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
@ -0,0 +1 @@
|
||||
This test file can be altered to break signing checks.
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>test@tests.mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test Add-on</em:name>
|
||||
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||
<em:minVersion>4</em:minVersion>
|
||||
<em:maxVersion>6</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
@ -0,0 +1 @@
|
||||
This test file can be altered to break signing checks.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -14,6 +14,7 @@ const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"
|
||||
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
|
||||
const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
|
||||
const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url";
|
||||
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
|
||||
|
||||
// Forcibly end the test if it runs longer than 15 minutes
|
||||
const TIMEOUT_MS = 900000;
|
||||
@ -1453,6 +1454,9 @@ Services.prefs.setCharPref("extensions.hotfix.id", "");
|
||||
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0");
|
||||
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0");
|
||||
|
||||
// Disable signature checks for most tests
|
||||
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
|
||||
|
||||
// Register a temporary directory for the tests.
|
||||
const gTmpD = gProfD.clone();
|
||||
gTmpD.append("temp");
|
||||
@ -1530,9 +1534,6 @@ do_register_cleanup(function addon_cleanup() {
|
||||
testDir.leafName = "staged";
|
||||
pathShouldntExist(testDir);
|
||||
|
||||
testDir.leafName = "staged-xpis";
|
||||
pathShouldntExist(testDir);
|
||||
|
||||
shutdownManager();
|
||||
|
||||
// Clear commonly set prefs.
|
||||
|
@ -197,35 +197,11 @@ function run_test() {
|
||||
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
|
||||
do_check_false(a5.hasBinaryComponents);
|
||||
|
||||
// addon6 should be installed and compatible and packed unless unpacking is
|
||||
// forced
|
||||
do_check_neq(a6, null);
|
||||
do_check_false(a6.userDisabled);
|
||||
do_check_false(a6.appDisabled);
|
||||
do_check_true(a6.isActive);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
|
||||
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
|
||||
do_check_eq(a6.getResourceURI("install.rdf").scheme, "file");
|
||||
else
|
||||
do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar");
|
||||
do_check_false(a6.hasBinaryComponents);
|
||||
|
||||
// addon7 should be installed and compatible and unpacked
|
||||
do_check_neq(a7, null);
|
||||
do_check_false(a7.userDisabled);
|
||||
do_check_false(a7.appDisabled);
|
||||
do_check_true(a7.isActive);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, a7.id));
|
||||
do_check_eq(a7.getResourceURI("install.rdf").scheme, "file");
|
||||
do_check_false(a7.hasBinaryComponents);
|
||||
|
||||
// addon8 should be installed and compatible and have binary components
|
||||
do_check_neq(a8, null);
|
||||
do_check_false(a8.userDisabled);
|
||||
do_check_false(a8.appDisabled);
|
||||
do_check_true(a8.isActive);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, a8.id));
|
||||
do_check_true(a8.hasBinaryComponents);
|
||||
// addon6, addon7 and addon8 will have been lost as they were staged in the
|
||||
// pre-Firefox 4.0 directory
|
||||
do_check_eq(a6, null);
|
||||
do_check_eq(a7, null);
|
||||
do_check_eq(a8, null);
|
||||
|
||||
// Theme 1 was previously enabled
|
||||
do_check_neq(t1, null);
|
||||
@ -243,8 +219,6 @@ function run_test() {
|
||||
do_check_false(isThemeInAddonsList(profileDir, t2.id));
|
||||
do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE));
|
||||
|
||||
do_check_false(stagedXPIs.exists());
|
||||
|
||||
do_execute_soon(do_test_finished);
|
||||
});
|
||||
}
|
||||
|
@ -196,25 +196,10 @@ function run_test() {
|
||||
do_check_false(a5.isActive);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
|
||||
|
||||
// addon6 should be installed and compatible and packed unless unpacking is
|
||||
// forced
|
||||
do_check_neq(a6, null);
|
||||
do_check_false(a6.userDisabled);
|
||||
do_check_false(a6.appDisabled);
|
||||
do_check_true(a6.isActive);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
|
||||
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
|
||||
do_check_eq(a6.getResourceURI("install.rdf").scheme, "file");
|
||||
else
|
||||
do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar");
|
||||
|
||||
// addon7 should be installed and compatible and unpacked
|
||||
do_check_neq(a7, null);
|
||||
do_check_false(a7.userDisabled);
|
||||
do_check_false(a7.appDisabled);
|
||||
do_check_true(a7.isActive);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, a7.id));
|
||||
do_check_eq(a7.getResourceURI("install.rdf").scheme, "file");
|
||||
// addon6 and addon7 will have been lost as they were staged in the
|
||||
// pre-Firefox 4.0 directory
|
||||
do_check_eq(a6, null);
|
||||
do_check_eq(a7, null);
|
||||
|
||||
// Theme 1 was previously disabled
|
||||
do_check_neq(t1, null);
|
||||
@ -232,8 +217,6 @@ function run_test() {
|
||||
do_check_false(isThemeInAddonsList(profileDir, t2.id));
|
||||
do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE));
|
||||
|
||||
do_check_false(stagedXPIs.exists());
|
||||
|
||||
do_execute_soon(do_test_finished);
|
||||
});
|
||||
}
|
||||
|
320
toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
Normal file
320
toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
Normal file
@ -0,0 +1,320 @@
|
||||
// Enable signature checks for these tests
|
||||
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
|
||||
// Disable update security
|
||||
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
|
||||
|
||||
const DATA = "data/signing_checks/";
|
||||
const ADDONS = {
|
||||
bootstrap: {
|
||||
unsigned: "unsigned_bootstrap_2.xpi",
|
||||
badid: "signed_bootstrap_badid_2.xpi",
|
||||
signed: "signed_bootstrap_2.xpi",
|
||||
},
|
||||
nonbootstrap: {
|
||||
unsigned: "unsigned_nonbootstrap_2.xpi",
|
||||
badid: "signed_nonbootstrap_badid_2.xpi",
|
||||
signed: "signed_nonbootstrap_2.xpi",
|
||||
}
|
||||
};
|
||||
const ID = "test@tests.mozilla.org";
|
||||
|
||||
const profileDir = gProfD.clone();
|
||||
profileDir.append("extensions");
|
||||
|
||||
// Deletes a file from the test add-on in the profile
|
||||
function breakAddon(file) {
|
||||
if (TEST_UNPACKED) {
|
||||
file.append("test.txt");
|
||||
file.remove(true);
|
||||
}
|
||||
else {
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
|
||||
createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(file, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
|
||||
zipW.removeEntry("test.txt", false);
|
||||
zipW.close();
|
||||
}
|
||||
}
|
||||
|
||||
function resetPrefs() {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.install_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.install_oldversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1);
|
||||
}
|
||||
|
||||
function getActiveVersion() {
|
||||
return Services.prefs.getIntPref("bootstraptest.active_version");
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
|
||||
|
||||
// Start and stop the manager to initialise everything in the profile before
|
||||
// actual testing
|
||||
startupManager();
|
||||
shutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Injecting into profile (bootstrap)
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.unsigned), profileDir, ID);
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
|
||||
breakAddon(getFileForAddon(profileDir, ID));
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.badid), profileDir, ID);
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
// Installs a signed add-on then modifies it in place breaking its signing
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
|
||||
|
||||
// Make it appear to come from the past so when we modify it later it is
|
||||
// detected during startup. Obviously malware can bypass this method of
|
||||
// detection but the periodic scan will catch that
|
||||
yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 600000);
|
||||
|
||||
startupManager();
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_false(addon.appDisabled);
|
||||
do_check_true(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
|
||||
do_check_eq(getActiveVersion(), 2);
|
||||
|
||||
yield promiseShutdownManager();
|
||||
do_check_eq(getActiveVersion(), 0);
|
||||
|
||||
breakAddon(getFileForAddon(profileDir, ID));
|
||||
resetPrefs();
|
||||
|
||||
startupManager();
|
||||
|
||||
addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
|
||||
let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
|
||||
do_check_eq(ids.length, 1);
|
||||
do_check_eq(ids[0], ID);
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
// Injecting into profile (non-bootstrap)
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.unsigned), profileDir, ID);
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
|
||||
breakAddon(getFileForAddon(profileDir, ID));
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.badid), profileDir, ID);
|
||||
|
||||
startupManager();
|
||||
|
||||
// Currently we leave the sideloaded add-on there but just don't run it
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
// Installs a signed add-on then modifies it in place breaking its signing
|
||||
add_task(function*() {
|
||||
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
|
||||
|
||||
// Make it appear to come from the past so when we modify it later it is
|
||||
// detected during startup. Obviously malware can bypass this method of
|
||||
// detection but the periodic scan will catch that
|
||||
yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 60000);
|
||||
|
||||
startupManager();
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_false(addon.appDisabled);
|
||||
do_check_true(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
|
||||
do_check_true(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
yield promiseShutdownManager();
|
||||
|
||||
breakAddon(getFileForAddon(profileDir, ID));
|
||||
|
||||
startupManager();
|
||||
|
||||
addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
|
||||
do_check_eq(ids.length, 1);
|
||||
do_check_eq(ids[0], ID);
|
||||
|
||||
addon.uninstall();
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
|
||||
do_check_false(getFileForAddon(profileDir, ID).exists());
|
||||
});
|
||||
|
||||
// Stage install then modify before startup (non-bootstrap)
|
||||
add_task(function*() {
|
||||
startupManager();
|
||||
yield promiseInstallAllFiles([do_get_file(DATA + ADDONS.nonbootstrap.signed)]);
|
||||
yield promiseShutdownManager();
|
||||
|
||||
let staged = profileDir.clone();
|
||||
staged.append("staged");
|
||||
staged.append(do_get_expected_addon_name(ID));
|
||||
do_check_true(staged.exists());
|
||||
|
||||
breakAddon(staged);
|
||||
startupManager();
|
||||
|
||||
// Should have refused to install the broken staged version
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_eq(addon, null);
|
||||
|
||||
let install = getFileForAddon(profileDir, ID);
|
||||
do_check_false(install.exists());
|
||||
|
||||
yield promiseShutdownManager();
|
||||
});
|
||||
|
||||
// Manufacture staged install (bootstrap)
|
||||
add_task(function*() {
|
||||
let stage = profileDir.clone();
|
||||
stage.append("staged");
|
||||
|
||||
let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), stage, ID);
|
||||
breakAddon(file);
|
||||
|
||||
startupManager();
|
||||
|
||||
// Should have refused to install the broken staged version
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_eq(addon, null);
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
|
||||
let install = getFileForAddon(profileDir, ID);
|
||||
do_check_false(install.exists());
|
||||
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
});
|
265
toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js
Normal file
265
toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js
Normal file
@ -0,0 +1,265 @@
|
||||
// Enable signature checks for these tests
|
||||
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
|
||||
// Disable update security
|
||||
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
|
||||
|
||||
const DATA = "data/signing_checks/";
|
||||
const ADDONS = {
|
||||
bootstrap: {
|
||||
unsigned: "unsigned_bootstrap_2.xpi",
|
||||
badid: "signed_bootstrap_badid_2.xpi",
|
||||
preliminary: "preliminary_bootstrap_2.xpi",
|
||||
signed: "signed_bootstrap_2.xpi",
|
||||
},
|
||||
};
|
||||
const WORKING = "signed_bootstrap_1.xpi";
|
||||
const ID = "test@tests.mozilla.org";
|
||||
|
||||
Components.utils.import("resource://testing-common/httpd.js");
|
||||
var gServer = new HttpServer();
|
||||
gServer.start(4444);
|
||||
|
||||
// Creates an add-on with a broken signature by changing an existing file
|
||||
function createBrokenAddonModify(file) {
|
||||
let brokenFile = gTmpD.clone();
|
||||
brokenFile.append("broken.xpi");
|
||||
file.copyTo(brokenFile.parent, brokenFile.leafName);
|
||||
|
||||
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(AM_Ci.nsIStringInputStream);
|
||||
stream.setData("FOOBAR", -1);
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
|
||||
createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
|
||||
zipW.removeEntry("test.txt", false);
|
||||
zipW.addEntryStream("test.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
|
||||
stream, false);
|
||||
zipW.close();
|
||||
|
||||
return brokenFile;
|
||||
}
|
||||
|
||||
// Creates an add-on with a broken signature by adding a new file
|
||||
function createBrokenAddonAdd(file) {
|
||||
let brokenFile = gTmpD.clone();
|
||||
brokenFile.append("broken.xpi");
|
||||
file.copyTo(brokenFile.parent, brokenFile.leafName);
|
||||
|
||||
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(AM_Ci.nsIStringInputStream);
|
||||
stream.setData("FOOBAR", -1);
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
|
||||
createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
|
||||
zipW.addEntryStream("test2.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
|
||||
stream, false);
|
||||
zipW.close();
|
||||
|
||||
return brokenFile;
|
||||
}
|
||||
|
||||
// Creates an add-on with a broken signature by removing an existing file
|
||||
function createBrokenAddonRemove(file) {
|
||||
let brokenFile = gTmpD.clone();
|
||||
brokenFile.append("broken.xpi");
|
||||
file.copyTo(brokenFile.parent, brokenFile.leafName);
|
||||
|
||||
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(AM_Ci.nsIStringInputStream);
|
||||
stream.setData("FOOBAR", -1);
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
|
||||
createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
|
||||
zipW.removeEntry("test.txt", false);
|
||||
zipW.close();
|
||||
|
||||
return brokenFile;
|
||||
}
|
||||
|
||||
function createInstall(url) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getInstallForURL(url, resolve, "application/x-xpinstall");
|
||||
});
|
||||
}
|
||||
|
||||
function serveUpdateRDF(leafName) {
|
||||
gServer.registerPathHandler("/update.rdf", function(request, response) {
|
||||
let updateData = {};
|
||||
updateData[ID] = [{
|
||||
version: "2.0",
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "4",
|
||||
maxVersion: "6",
|
||||
updateLink: "http://localhost:4444/" + leafName
|
||||
}]
|
||||
}];
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.write(createUpdateRDF(updateData));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function* test_install_broken(file, expectedError) {
|
||||
gServer.registerFile("/" + file.leafName, file);
|
||||
|
||||
let install = yield createInstall("http://localhost:4444/" + file.leafName);
|
||||
yield promiseCompleteAllInstalls([install]);
|
||||
|
||||
do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
|
||||
do_check_eq(install.error, expectedError);
|
||||
do_check_eq(install.addon, null);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, null);
|
||||
}
|
||||
|
||||
function* test_install_working(file, expectedSignedState) {
|
||||
gServer.registerFile("/" + file.leafName, file);
|
||||
|
||||
let install = yield createInstall("http://localhost:4444/" + file.leafName);
|
||||
yield promiseCompleteAllInstalls([install]);
|
||||
|
||||
do_check_eq(install.state, AddonManager.STATE_INSTALLED);
|
||||
do_check_neq(install.addon, null);
|
||||
do_check_eq(install.addon.signedState, expectedSignedState);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, null);
|
||||
|
||||
install.addon.uninstall();
|
||||
}
|
||||
|
||||
function* test_update_broken(file, expectedError) {
|
||||
// First install the older version
|
||||
yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, file);
|
||||
serveUpdateRDF(file.leafName);
|
||||
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
let install = yield promiseFindAddonUpdates(addon);
|
||||
yield promiseCompleteAllInstalls([install]);
|
||||
|
||||
do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
|
||||
do_check_eq(install.error, expectedError);
|
||||
do_check_eq(install.addon, null);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, null);
|
||||
gServer.registerPathHandler("/update.rdf", null);
|
||||
|
||||
addon.uninstall();
|
||||
}
|
||||
|
||||
function* test_update_working(file, expectedSignedState) {
|
||||
// First install the older version
|
||||
yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, file);
|
||||
serveUpdateRDF(file.leafName);
|
||||
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
let install = yield promiseFindAddonUpdates(addon);
|
||||
yield promiseCompleteAllInstalls([install]);
|
||||
|
||||
do_check_eq(install.state, AddonManager.STATE_INSTALLED);
|
||||
do_check_neq(install.addon, null);
|
||||
do_check_eq(install.addon.signedState, expectedSignedState);
|
||||
|
||||
gServer.registerFile("/" + file.leafName, null);
|
||||
gServer.registerPathHandler("/update.rdf", null);
|
||||
|
||||
install.addon.uninstall();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
|
||||
startupManager();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Try to install a broken add-on
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
// Try to install an add-on with an incorrect ID
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.badid);
|
||||
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
});
|
||||
|
||||
// Try to install an unsigned add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.unsigned);
|
||||
yield test_install_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED);
|
||||
});
|
||||
|
||||
// Try to install a preliminarily reviewed add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.preliminary);
|
||||
yield test_install_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY);
|
||||
});
|
||||
|
||||
// Try to install a signed add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.signed);
|
||||
yield test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED);
|
||||
});
|
||||
|
||||
// Try to update to a broken add-on
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed));
|
||||
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
file.remove(true);
|
||||
});
|
||||
|
||||
// Try to update to an add-on with an incorrect ID
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.badid);
|
||||
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
|
||||
});
|
||||
|
||||
// Try to update to an unsigned add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.unsigned);
|
||||
yield test_update_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED);
|
||||
});
|
||||
|
||||
// Try to update to a preliminarily reviewed add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.preliminary);
|
||||
yield test_update_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY);
|
||||
});
|
||||
|
||||
// Try to update to a signed add-on
|
||||
add_task(function*() {
|
||||
let file = do_get_file(DATA + ADDONS.bootstrap.signed);
|
||||
yield test_update_working(file, AddonManager.SIGNEDSTATE_SIGNED);
|
||||
});
|
164
toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js
Normal file
164
toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js
Normal file
@ -0,0 +1,164 @@
|
||||
// Enable signature checks for these tests
|
||||
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
|
||||
// Disable update security
|
||||
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
|
||||
|
||||
const DATA = "data/signing_checks/";
|
||||
const ADDONS = {
|
||||
bootstrap: {
|
||||
unsigned: "unsigned_bootstrap_2.xpi",
|
||||
badid: "signed_bootstrap_badid_2.xpi",
|
||||
signed: "signed_bootstrap_2.xpi",
|
||||
},
|
||||
nonbootstrap: {
|
||||
unsigned: "unsigned_nonbootstrap_2.xpi",
|
||||
badid: "signed_nonbootstrap_badid_2.xpi",
|
||||
signed: "signed_nonbootstrap_2.xpi",
|
||||
}
|
||||
};
|
||||
const ID = "test@tests.mozilla.org";
|
||||
|
||||
const profileDir = gProfD.clone();
|
||||
profileDir.append("extensions");
|
||||
|
||||
function resetPrefs() {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.install_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.install_oldversion", -1);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1);
|
||||
}
|
||||
|
||||
function getActiveVersion() {
|
||||
return Services.prefs.getIntPref("bootstraptest.active_version");
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
|
||||
|
||||
// Start and stop the manager to initialise everything in the profile before
|
||||
// actual testing
|
||||
startupManager();
|
||||
shutdownManager();
|
||||
resetPrefs();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Removes the signedState field from add-ons in the json database to make it
|
||||
// look like the database was written with an older version of the application
|
||||
function stripDB() {
|
||||
let jData = loadJSON(gExtensionsJSON);
|
||||
jData.schemaVersion--;
|
||||
|
||||
for (let addon of jData.addons)
|
||||
delete addon.signedState;
|
||||
|
||||
saveJSON(jData, gExtensionsJSON);
|
||||
}
|
||||
|
||||
function* test_breaking_migrate(addons, test, expectedSignedState) {
|
||||
// Startup as the old version
|
||||
gAppInfo.version = "4";
|
||||
startupManager(true);
|
||||
|
||||
// Install the signed add-on
|
||||
yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]);
|
||||
// Restart to let non-restartless add-ons install fully
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
stripDB();
|
||||
|
||||
// Now replace it with the version to test. Doing this so quickly shouldn't
|
||||
// trigger the file modification code to detect the change by itself.
|
||||
manuallyUninstall(profileDir, ID);
|
||||
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
|
||||
|
||||
// Update the application
|
||||
gAppInfo.version = "5";
|
||||
startupManager(true);
|
||||
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_true(addon.appDisabled);
|
||||
do_check_false(addon.isActive);
|
||||
do_check_eq(addon.signedState, expectedSignedState);
|
||||
|
||||
// Add-on shouldn't be active
|
||||
if (addons == ADDONS.bootstrap)
|
||||
do_check_eq(getActiveVersion(), -1);
|
||||
else
|
||||
do_check_false(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
// Should have flagged the change during startup
|
||||
let changes = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
|
||||
do_check_eq(changes.length, 1);
|
||||
do_check_eq(changes[0], ID);
|
||||
|
||||
addon.uninstall();
|
||||
// Restart to let non-restartless add-ons uninstall fully
|
||||
yield promiseRestartManager();
|
||||
yield shutdownManager();
|
||||
resetPrefs();
|
||||
}
|
||||
|
||||
function* test_working_migrate(addons, test, expectedSignedState) {
|
||||
// Startup as the old version
|
||||
gAppInfo.version = "4";
|
||||
startupManager(true);
|
||||
|
||||
// Install the signed add-on
|
||||
yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]);
|
||||
// Restart to let non-restartless add-ons install fully
|
||||
yield promiseRestartManager();
|
||||
yield promiseShutdownManager();
|
||||
resetPrefs();
|
||||
stripDB();
|
||||
|
||||
// Now replace it with the version to test. Doing this so quickly shouldn't
|
||||
// trigger the file modification code to detect the change by itself.
|
||||
manuallyUninstall(profileDir, ID);
|
||||
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
|
||||
|
||||
// Update the application
|
||||
gAppInfo.version = "5";
|
||||
startupManager(true);
|
||||
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
do_check_false(addon.appDisabled);
|
||||
do_check_true(addon.isActive);
|
||||
do_check_eq(addon.signedState, expectedSignedState);
|
||||
|
||||
if (addons == ADDONS.bootstrap)
|
||||
do_check_eq(getActiveVersion(), 2);
|
||||
else
|
||||
do_check_true(isExtensionInAddonsList(profileDir, ID));
|
||||
|
||||
addon.uninstall();
|
||||
// Restart to let non-restartless add-ons uninstall fully
|
||||
yield promiseRestartManager();
|
||||
yield shutdownManager();
|
||||
resetPrefs();
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
yield test_breaking_migrate(ADDONS.bootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING);
|
||||
yield test_breaking_migrate(ADDONS.nonbootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
yield test_breaking_migrate(ADDONS.bootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN);
|
||||
yield test_breaking_migrate(ADDONS.nonbootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN);
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
yield test_working_migrate(ADDONS.bootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED);
|
||||
yield test_working_migrate(ADDONS.nonbootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED);
|
||||
});
|
@ -22,6 +22,11 @@ skip-if = appname != "firefox"
|
||||
[test_provider_unsafe_access_shutdown.js]
|
||||
[test_provider_unsafe_access_startup.js]
|
||||
[test_shutdown.js]
|
||||
[test_signed_inject.js]
|
||||
skip-if = true
|
||||
[test_signed_install.js]
|
||||
run-sequentially = Uses hardcoded ports in xpi files.
|
||||
[test_signed_migrate.js]
|
||||
[test_XPIcancel.js]
|
||||
[test_XPIStates.js]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user