зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1356826: Part 7 - Scan for extension sideloads after final UI startup. r=aswan,rhelmer
MozReview-Commit-ID: 1syn9GD2DEb --HG-- extra : rebase_source : d681fa6c0715790664e090580175cfe33bca3425
This commit is contained in:
Родитель
01a97bf1e9
Коммит
05591ed101
|
@ -1,155 +1,89 @@
|
||||||
const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||||
|
|
||||||
// MockAddon mimics the AddonInternal interface and MockProvider implements
|
const {AddonTestUtils} = Cu.import("resource://testing-common/AddonTestUtils.jsm", {});
|
||||||
// just enough of the AddonManager provider interface to make it look like
|
|
||||||
// we have sideloaded webextensions so the sideloading flow can be tested.
|
|
||||||
|
|
||||||
// MockAddon -> callback
|
AddonTestUtils.initMochitest(this);
|
||||||
let setCallbacks = new Map();
|
|
||||||
|
|
||||||
class MockAddon {
|
async function createWebExtension(details) {
|
||||||
constructor(props) {
|
let options = {
|
||||||
this._userDisabled = false;
|
manifest: {
|
||||||
this.pendingOperations = 0;
|
applications: {gecko: {id: details.id}},
|
||||||
this.type = "extension";
|
|
||||||
|
|
||||||
for (let name in props) {
|
name: details.name,
|
||||||
if (name == "userDisabled") {
|
|
||||||
this._userDisabled = props[name];
|
permissions: details.permissions,
|
||||||
}
|
},
|
||||||
this[name] = props[name];
|
};
|
||||||
}
|
|
||||||
|
if (details.iconURL) {
|
||||||
|
options.manifest.icons = {"64": details.iconURL};
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsSeen() {
|
let xpi = AddonTestUtils.createTempWebExtensionFile(options);
|
||||||
this.seen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get userDisabled() {
|
await AddonTestUtils.manuallyInstall(xpi);
|
||||||
return this._userDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
set userDisabled(val) {
|
|
||||||
this._userDisabled = val;
|
|
||||||
AddonManagerPrivate.callAddonListeners(val ? "onDisabled" : "onEnabled", this);
|
|
||||||
let fn = setCallbacks.get(this);
|
|
||||||
if (fn) {
|
|
||||||
setCallbacks.delete(this);
|
|
||||||
fn(val);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
get permissions() {
|
|
||||||
return this._userDisabled ? AddonManager.PERM_CAN_ENABLE : AddonManager.PERM_CAN_DISABLE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockProvider {
|
async function createXULExtension(details) {
|
||||||
constructor(...addons) {
|
let xpi = AddonTestUtils.createTempXPIFile({
|
||||||
this.addons = new Set(addons);
|
"install.rdf": {
|
||||||
}
|
id: details.id,
|
||||||
|
name: details.name,
|
||||||
startup() { }
|
version: "0.1",
|
||||||
shutdown() { }
|
targetApplications: [{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
getAddonByID(id, callback) {
|
minVersion: "0",
|
||||||
for (let addon of this.addons) {
|
maxVersion: "*",
|
||||||
if (addon.id == id) {
|
}],
|
||||||
callback(addon);
|
},
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddonsByTypes(types, callback) {
|
|
||||||
let addons = [];
|
|
||||||
if (!types || types.includes("extension")) {
|
|
||||||
addons = [...this.addons];
|
|
||||||
}
|
|
||||||
callback(addons);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function promiseSetDisabled(addon) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setCallbacks.set(addon, resolve);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await AddonTestUtils.manuallyInstall(xpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cleanup;
|
let cleanup;
|
||||||
|
|
||||||
add_task(function* () {
|
add_task(function* test_sideloading() {
|
||||||
// ICON_URL wouldn't ever appear as an actual webextension icon, but
|
|
||||||
// we're just mocking out the addon here, so all we care about is that
|
|
||||||
// that it propagates correctly to the popup.
|
|
||||||
const ICON_URL = "chrome://mozapps/skin/extensions/category-extensions.svg";
|
|
||||||
const DEFAULT_ICON_URL = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
const DEFAULT_ICON_URL = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||||
|
|
||||||
|
yield SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
["xpinstall.signatures.required", false],
|
||||||
|
["extensions.autoDisableScopes", 15],
|
||||||
|
["extensions.ui.ignoreUnsigned", true],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const ID1 = "addon1@tests.mozilla.org";
|
const ID1 = "addon1@tests.mozilla.org";
|
||||||
let mock1 = new MockAddon({
|
yield createWebExtension({
|
||||||
id: ID1,
|
id: ID1,
|
||||||
name: "Test 1",
|
name: "Test 1",
|
||||||
userDisabled: true,
|
userDisabled: true,
|
||||||
seen: false,
|
permissions: ["history", "https://*/*"],
|
||||||
userPermissions: {
|
iconURL: "foo-icon.png",
|
||||||
permissions: ["history"],
|
|
||||||
origins: ["https://*/*"],
|
|
||||||
},
|
|
||||||
iconURL: ICON_URL,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ID2 = "addon2@tests.mozilla.org";
|
const ID2 = "addon2@tests.mozilla.org";
|
||||||
let mock2 = new MockAddon({
|
yield createXULExtension({
|
||||||
id: ID2,
|
id: ID2,
|
||||||
name: "Test 2",
|
name: "Test 2",
|
||||||
userDisabled: true,
|
|
||||||
seen: false,
|
|
||||||
userPermissions: {
|
|
||||||
permissions: [],
|
|
||||||
origins: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ID3 = "addon3@tests.mozilla.org";
|
const ID3 = "addon3@tests.mozilla.org";
|
||||||
let mock3 = new MockAddon({
|
yield createWebExtension({
|
||||||
id: ID3,
|
id: ID3,
|
||||||
name: "Test 3",
|
name: "Test 3",
|
||||||
isWebExtension: true,
|
permissions: ["<all_urls>"],
|
||||||
userDisabled: true,
|
|
||||||
seen: false,
|
|
||||||
userPermissions: {
|
|
||||||
permissions: [],
|
|
||||||
origins: ["<all_urls>"],
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ID4 = "addon4@tests.mozilla.org";
|
const ID4 = "addon4@tests.mozilla.org";
|
||||||
let mock4 = new MockAddon({
|
yield createWebExtension({
|
||||||
id: ID4,
|
id: ID4,
|
||||||
name: "Test 4",
|
name: "Test 4",
|
||||||
isWebExtension: true,
|
permissions: ["<all_urls>"],
|
||||||
userDisabled: true,
|
|
||||||
seen: false,
|
|
||||||
userPermissions: {
|
|
||||||
permissions: [],
|
|
||||||
origins: ["<all_urls>"],
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let provider = new MockProvider(mock1, mock2, mock3, mock4);
|
|
||||||
AddonManagerPrivate.registerProvider(provider, [{
|
|
||||||
id: "extension",
|
|
||||||
name: "Extensions",
|
|
||||||
uiPriority: 4000,
|
|
||||||
flags: AddonManager.TYPE_UI_VIEW_LIST |
|
|
||||||
AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL,
|
|
||||||
}]);
|
|
||||||
|
|
||||||
testCleanup = async function() {
|
testCleanup = async function() {
|
||||||
AddonManagerPrivate.unregisterProvider(provider);
|
|
||||||
|
|
||||||
// clear out ExtensionsUI state about sideloaded extensions so
|
// clear out ExtensionsUI state about sideloaded extensions so
|
||||||
// subsequent tests don't get confused.
|
// subsequent tests don't get confused.
|
||||||
ExtensionsUI.sideloaded.clear();
|
ExtensionsUI.sideloaded.clear();
|
||||||
|
@ -203,17 +137,13 @@ add_task(function* () {
|
||||||
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
|
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
|
||||||
|
|
||||||
// Check the contents of the notification, then choose "Cancel"
|
// Check the contents of the notification, then choose "Cancel"
|
||||||
checkNotification(panel, ICON_URL, [
|
checkNotification(panel, /\/foo-icon\.png$/, [
|
||||||
["webextPerms.hostDescription.allUrls"],
|
["webextPerms.hostDescription.allUrls"],
|
||||||
["webextPerms.description.history"],
|
["webextPerms.description.history"],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let disablePromise = promiseSetDisabled(mock1);
|
|
||||||
panel.secondaryButton.click();
|
panel.secondaryButton.click();
|
||||||
|
|
||||||
let value = yield disablePromise;
|
|
||||||
is(value, true, "Addon should remain disabled");
|
|
||||||
|
|
||||||
let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
|
let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
|
||||||
ok(addon1.seen, "Addon should be marked as seen");
|
ok(addon1.seen, "Addon should be marked as seen");
|
||||||
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
|
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
|
||||||
|
@ -245,12 +175,8 @@ add_task(function* () {
|
||||||
checkNotification(panel, DEFAULT_ICON_URL, []);
|
checkNotification(panel, DEFAULT_ICON_URL, []);
|
||||||
|
|
||||||
// This time accept the install.
|
// This time accept the install.
|
||||||
disablePromise = promiseSetDisabled(mock2);
|
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
|
|
||||||
value = yield disablePromise;
|
|
||||||
is(value, false, "Addon should be set to enabled");
|
|
||||||
|
|
||||||
[addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
|
[addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
|
||||||
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
|
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
|
||||||
is(addon2.userDisabled, false, "Addon 2 should now be enabled");
|
is(addon2.userDisabled, false, "Addon 2 should now be enabled");
|
||||||
|
@ -288,10 +214,7 @@ add_task(function* () {
|
||||||
checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
|
checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
|
||||||
|
|
||||||
// Accept the permissions
|
// Accept the permissions
|
||||||
disablePromise = promiseSetDisabled(mock3);
|
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
value = yield disablePromise;
|
|
||||||
is(value, false, "userDisabled should be set on addon 3");
|
|
||||||
|
|
||||||
addon3 = yield AddonManager.getAddonByID(ID3);
|
addon3 = yield AddonManager.getAddonByID(ID3);
|
||||||
is(addon3.userDisabled, false, "Addon 3 should be enabled");
|
is(addon3.userDisabled, false, "Addon 3 should be enabled");
|
||||||
|
@ -316,10 +239,7 @@ add_task(function* () {
|
||||||
checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
|
checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
|
||||||
|
|
||||||
// Accept the permissions
|
// Accept the permissions
|
||||||
disablePromise = promiseSetDisabled(mock4);
|
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
value = yield disablePromise;
|
|
||||||
is(value, false, "userDisabled should be set on addon 4");
|
|
||||||
|
|
||||||
addon4 = yield AddonManager.getAddonByID(ID4);
|
addon4 = yield AddonManager.getAddonByID(ID4);
|
||||||
is(addon4.userDisabled, false, "Addon 4 should be enabled");
|
is(addon4.userDisabled, false, "Addon 4 should be enabled");
|
||||||
|
@ -329,5 +249,11 @@ add_task(function* () {
|
||||||
|
|
||||||
isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
|
isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
|
||||||
|
|
||||||
|
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
for (let addon of [addon1, addon2, addon3, addon4]) {
|
||||||
|
addon.uninstall();
|
||||||
|
}
|
||||||
|
|
||||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||||
});
|
});
|
||||||
|
|
|
@ -218,7 +218,7 @@ function checkNotification(panel, checkIcon, permissions) {
|
||||||
let header = document.getElementById("addon-webext-perm-intro");
|
let header = document.getElementById("addon-webext-perm-intro");
|
||||||
|
|
||||||
if (checkIcon instanceof RegExp) {
|
if (checkIcon instanceof RegExp) {
|
||||||
ok(checkIcon.test(icon), "Notification icon is correct");
|
ok(checkIcon.test(icon), `Notification icon is correct ${JSON.stringify(icon)} ~= ${checkIcon}`);
|
||||||
} else if (typeof checkIcon == "function") {
|
} else if (typeof checkIcon == "function") {
|
||||||
ok(checkIcon(icon), "Notification icon is correct");
|
ok(checkIcon(icon), "Notification icon is correct");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/EventEmitter.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||||
"resource://gre/modules/AddonManager.jsm");
|
"resource://gre/modules/AddonManager.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
|
||||||
|
"resource://gre/modules/AddonManager.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||||
"resource://gre/modules/PluralForm.jsm");
|
"resource://gre/modules/PluralForm.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||||
|
@ -48,50 +50,50 @@ this.ExtensionsUI = {
|
||||||
this._checkForSideloaded();
|
this._checkForSideloaded();
|
||||||
},
|
},
|
||||||
|
|
||||||
_checkForSideloaded() {
|
async _checkForSideloaded() {
|
||||||
AddonManager.getAllAddons(addons => {
|
let sideloaded = await AddonManagerPrivate.getNewSideloads();
|
||||||
// Check for any side-loaded addons that the user is allowed
|
|
||||||
// to enable.
|
|
||||||
let sideloaded = addons.filter(
|
|
||||||
addon => addon.seen === false && (addon.permissions & AddonManager.PERM_CAN_ENABLE));
|
|
||||||
|
|
||||||
if (!sideloaded.length) {
|
if (!sideloaded.length) {
|
||||||
return;
|
// No new side-loads. We're done.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ordering shouldn't matter, but tests depend on notifications
|
||||||
|
// happening in a specific order.
|
||||||
|
sideloaded.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
|
||||||
|
if (WEBEXT_PERMISSION_PROMPTS) {
|
||||||
|
if (!this.sideloadListener) {
|
||||||
|
this.sideloadListener = {
|
||||||
|
onEnabled: addon => {
|
||||||
|
if (!this.sideloaded.has(addon)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sideloaded.delete(addon);
|
||||||
|
this.emit("change");
|
||||||
|
|
||||||
|
if (this.sideloaded.size == 0) {
|
||||||
|
AddonManager.removeAddonListener(this.sideloadListener);
|
||||||
|
this.sideloadListener = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddonManager.addAddonListener(this.sideloadListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WEBEXT_PERMISSION_PROMPTS) {
|
for (let addon of sideloaded) {
|
||||||
if (!this.sideloadListener) {
|
this.sideloaded.add(addon);
|
||||||
this.sideloadListener = {
|
|
||||||
onEnabled: addon => {
|
|
||||||
if (!this.sideloaded.has(addon)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sideloaded.delete(addon);
|
|
||||||
this.emit("change");
|
|
||||||
|
|
||||||
if (this.sideloaded.size == 0) {
|
|
||||||
AddonManager.removeAddonListener(this.sideloadListener);
|
|
||||||
this.sideloadListener = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AddonManager.addAddonListener(this.sideloadListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let addon of sideloaded) {
|
|
||||||
this.sideloaded.add(addon);
|
|
||||||
}
|
|
||||||
this.emit("change");
|
|
||||||
} else {
|
|
||||||
// This and all the accompanying about:newaddon code can eventually
|
|
||||||
// be removed. See bug 1331521.
|
|
||||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
|
||||||
for (let addon of sideloaded) {
|
|
||||||
win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
this.emit("change");
|
||||||
|
} else {
|
||||||
|
// This and all the accompanying about:newaddon code can eventually
|
||||||
|
// be removed. See bug 1331521.
|
||||||
|
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||||
|
for (let addon of sideloaded) {
|
||||||
|
win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showAddonsManager(browser, strings, icon, histkey) {
|
showAddonsManager(browser, strings, icon, histkey) {
|
||||||
|
@ -148,6 +150,11 @@ this.ExtensionsUI = {
|
||||||
}
|
}
|
||||||
|
|
||||||
info.unsigned = info.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING;
|
info.unsigned = info.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING;
|
||||||
|
if (info.unsigned && Cu.isInAutomation &&
|
||||||
|
Services.prefs.getBoolPref("extensions.ui.ignoreUnsigned", false)) {
|
||||||
|
info.unsigned = false;
|
||||||
|
}
|
||||||
|
|
||||||
let strings = this._buildStrings(info);
|
let strings = this._buildStrings(info);
|
||||||
|
|
||||||
// If this is an update with no promptable permissions, just apply it
|
// If this is an update with no promptable permissions, just apply it
|
||||||
|
|
|
@ -3086,6 +3086,17 @@ this.AddonManagerPrivate = {
|
||||||
.addonIsActive(addonId);
|
.addonIsActive(addonId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an array of add-ons which were side-loaded prior to the last
|
||||||
|
* startup, and are currently disabled.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array<Addon>>}
|
||||||
|
*/
|
||||||
|
getNewSideloads() {
|
||||||
|
return AddonManagerInternal._getProviderByName("XPIProvider")
|
||||||
|
.getNewSideloads();
|
||||||
|
},
|
||||||
|
|
||||||
get browserUpdated() {
|
get browserUpdated() {
|
||||||
return gBrowserUpdated;
|
return gBrowserUpdated;
|
||||||
},
|
},
|
||||||
|
|
|
@ -218,6 +218,9 @@ var AddonTestUtils = {
|
||||||
// Get the profile directory for tests to use.
|
// Get the profile directory for tests to use.
|
||||||
this.profileDir = testScope.do_get_profile();
|
this.profileDir = testScope.do_get_profile();
|
||||||
|
|
||||||
|
this.profileExtensions = this.profileDir.clone();
|
||||||
|
this.profileExtensions.append("extensions");
|
||||||
|
|
||||||
this.extensionsINI = this.profileDir.clone();
|
this.extensionsINI = this.profileDir.clone();
|
||||||
this.extensionsINI.append("extensions.ini");
|
this.extensionsINI.append("extensions.ini");
|
||||||
|
|
||||||
|
@ -287,10 +290,7 @@ var AddonTestUtils = {
|
||||||
}
|
}
|
||||||
|
|
||||||
testScope.do_register_cleanup(() => {
|
testScope.do_register_cleanup(() => {
|
||||||
for (let file of this.tempXPIs) {
|
this.cleanupTempXPIs();
|
||||||
if (file.exists())
|
|
||||||
file.remove(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the temporary directory is empty
|
// Check that the temporary directory is empty
|
||||||
var dirEntries = this.tempDir.directoryEntries
|
var dirEntries = this.tempDir.directoryEntries
|
||||||
|
@ -342,6 +342,37 @@ var AddonTestUtils = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initMochitest(testScope) {
|
||||||
|
this.profileDir = FileUtils.getDir("ProfD", []);
|
||||||
|
|
||||||
|
this.profileExtensions = FileUtils.getDir("ProfD", ["extensions"]);
|
||||||
|
|
||||||
|
this.tempDir = FileUtils.getDir("TmpD", []);
|
||||||
|
this.tempDir.append("addons-mochitest");
|
||||||
|
this.tempDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||||
|
|
||||||
|
testScope.registerCleanupFunction(() => {
|
||||||
|
this.cleanupTempXPIs();
|
||||||
|
try {
|
||||||
|
this.tempDir.remove(true);
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanupTempXPIs() {
|
||||||
|
for (let file of this.tempXPIs.splice(0)) {
|
||||||
|
if (file.exists()) {
|
||||||
|
try {
|
||||||
|
file.remove(false);
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to spin the event loop until a promise resolves or rejects
|
* Helper to spin the event loop until a promise resolves or rejects
|
||||||
*
|
*
|
||||||
|
@ -412,6 +443,10 @@ var AddonTestUtils = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getIDFromExtension(file) {
|
||||||
|
return this.getIDFromManifest(this.getManifestURI(file));
|
||||||
|
},
|
||||||
|
|
||||||
getIDFromManifest: Task.async(function*(manifestURI) {
|
getIDFromManifest: Task.async(function*(manifestURI) {
|
||||||
let body = yield fetch(manifestURI.spec);
|
let body = yield fetch(manifestURI.spec);
|
||||||
|
|
||||||
|
@ -852,9 +887,9 @@ var AddonTestUtils = {
|
||||||
*
|
*
|
||||||
* @param {nsIFile} xpiFile
|
* @param {nsIFile} xpiFile
|
||||||
* The XPI file to install.
|
* The XPI file to install.
|
||||||
* @param {nsIFile} installLocation
|
* @param {nsIFile} [installLocation = this.profileExtensions]
|
||||||
* The install location (an nsIFile) to install into.
|
* The install location (an nsIFile) to install into.
|
||||||
* @param {string} id
|
* @param {string} [id]
|
||||||
* The ID to install as.
|
* The ID to install as.
|
||||||
* @param {boolean} [unpacked = this.testUnpacked]
|
* @param {boolean} [unpacked = this.testUnpacked]
|
||||||
* If true, install as an unpacked directory, rather than a
|
* If true, install as an unpacked directory, rather than a
|
||||||
|
@ -863,7 +898,11 @@ var AddonTestUtils = {
|
||||||
* A file pointing to the installed location of the XPI file or
|
* A file pointing to the installed location of the XPI file or
|
||||||
* unpacked directory.
|
* unpacked directory.
|
||||||
*/
|
*/
|
||||||
manuallyInstall(xpiFile, installLocation, id, unpacked = this.testUnpacked) {
|
async manuallyInstall(xpiFile, installLocation = this.profileExtensions, id = null, unpacked = this.testUnpacked) {
|
||||||
|
if (id == null) {
|
||||||
|
id = await this.getIDFromExtension(xpiFile);
|
||||||
|
}
|
||||||
|
|
||||||
if (unpacked) {
|
if (unpacked) {
|
||||||
let dir = installLocation.clone();
|
let dir = installLocation.clone();
|
||||||
dir.append(id);
|
dir.append(id);
|
||||||
|
|
|
@ -2241,6 +2241,14 @@ this.XPIStates = {
|
||||||
// Map(location name -> Map(add-on ID -> XPIState))
|
// Map(location name -> Map(add-on ID -> XPIState))
|
||||||
db: null,
|
db: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Map<string, XPIState>} sideLoadedAddons
|
||||||
|
* A map of new add-ons detected during install location
|
||||||
|
* directory scans. Keys are add-on IDs, values are XPIState
|
||||||
|
* objects corresponding to those add-ons.
|
||||||
|
*/
|
||||||
|
sideLoadedAddons: new Map(),
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2321,6 +2329,7 @@ this.XPIStates = {
|
||||||
let xpiState = new XPIState({d: file.persistentDescriptor});
|
let xpiState = new XPIState({d: file.persistentDescriptor});
|
||||||
changed = xpiState.getModTime(file, id) || changed;
|
changed = xpiState.getModTime(file, id) || changed;
|
||||||
foundAddons.set(id, xpiState);
|
foundAddons.set(id, xpiState);
|
||||||
|
this.sideLoadedAddons.set(id, xpiState);
|
||||||
} else {
|
} else {
|
||||||
let xpiState = new XPIState(locState[id]);
|
let xpiState = new XPIState(locState[id]);
|
||||||
// We found this add-on in the file system
|
// We found this add-on in the file system
|
||||||
|
@ -3867,12 +3876,7 @@ this.XPIProvider = {
|
||||||
// If the application crashed before completing any pending operations then
|
// If the application crashed before completing any pending operations then
|
||||||
// we should perform them now.
|
// we should perform them now.
|
||||||
if (extensionListChanged || hasPendingChanges) {
|
if (extensionListChanged || hasPendingChanges) {
|
||||||
logger.debug("Updating database with changes to installed add-ons");
|
this._updateActiveAddons();
|
||||||
XPIDatabase.updateActiveAddons();
|
|
||||||
Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
|
|
||||||
!XPIDatabase.writeAddonsList());
|
|
||||||
Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
|
|
||||||
JSON.stringify(this.bootstrappedAddons));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3893,6 +3897,38 @@ this.XPIProvider = {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_updateActiveAddons() {
|
||||||
|
logger.debug("Updating database with changes to installed add-ons");
|
||||||
|
XPIDatabase.updateActiveAddons();
|
||||||
|
Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
|
||||||
|
!XPIDatabase.writeAddonsList());
|
||||||
|
Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
|
||||||
|
JSON.stringify(this.bootstrappedAddons));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an array of add-ons which were placed in a known install location
|
||||||
|
* prior to startup of the current session, were detected by a directory scan
|
||||||
|
* of those locations, and are currently disabled.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array<Addon>>}
|
||||||
|
*/
|
||||||
|
async getNewSideloads() {
|
||||||
|
if (XPIStates.getInstallState(false)) {
|
||||||
|
// We detected changes. Update the database to account for them.
|
||||||
|
await XPIDatabase.asyncLoadDB(false);
|
||||||
|
XPIDatabaseReconcile.processFileChanges({}, false);
|
||||||
|
this._updateActiveAddons();
|
||||||
|
}
|
||||||
|
|
||||||
|
let addons = await Promise.all(
|
||||||
|
Array.from(XPIStates.sideLoadedAddons.keys(),
|
||||||
|
id => AddonManager.getAddonByID(id)));
|
||||||
|
|
||||||
|
return addons.filter(addon => (addon.seen === false &&
|
||||||
|
addon.permissions & AddonManager.PERM_CAN_ENABLE));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to test whether this provider supports installing a particular
|
* Called to test whether this provider supports installing a particular
|
||||||
* mimetype.
|
* mimetype.
|
||||||
|
|
|
@ -1708,7 +1708,8 @@ this.XPIDatabaseReconcile = {
|
||||||
|
|
||||||
// If we don't have an old app version then this is a new profile in
|
// If we don't have an old app version then this is a new profile in
|
||||||
// which case just mark any sideloaded add-ons as already seen.
|
// which case just mark any sideloaded add-ons as already seen.
|
||||||
aNewAddon.seen = !aOldAppVersion;
|
aNewAddon.seen = (aInstallLocation.name != KEY_APP_PROFILE &&
|
||||||
|
!aOldAppVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ const {
|
||||||
createTempWebExtensionFile,
|
createTempWebExtensionFile,
|
||||||
createUpdateRDF,
|
createUpdateRDF,
|
||||||
getFileForAddon,
|
getFileForAddon,
|
||||||
manuallyInstall,
|
|
||||||
manuallyUninstall,
|
manuallyUninstall,
|
||||||
promiseAddonEvent,
|
promiseAddonEvent,
|
||||||
promiseCompleteAllInstalls,
|
promiseCompleteAllInstalls,
|
||||||
|
@ -90,6 +89,11 @@ const {
|
||||||
writeFilesToZip
|
writeFilesToZip
|
||||||
} = AddonTestUtils;
|
} = AddonTestUtils;
|
||||||
|
|
||||||
|
function manuallyInstall(...args) {
|
||||||
|
return AddonTestUtils.awaitPromise(
|
||||||
|
AddonTestUtils.manuallyInstall(...args));
|
||||||
|
}
|
||||||
|
|
||||||
// WebExtension wrapper for ease of testing
|
// WebExtension wrapper for ease of testing
|
||||||
ExtensionTestUtils.init(this);
|
ExtensionTestUtils.init(this);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
|
||||||
"AddonScreenshot", "AddonType", "startup", "shutdown",
|
"AddonScreenshot", "AddonType", "startup", "shutdown",
|
||||||
"addonIsActive", "registerProvider", "unregisterProvider",
|
"addonIsActive", "registerProvider", "unregisterProvider",
|
||||||
"addStartupChange", "removeStartupChange",
|
"addStartupChange", "removeStartupChange",
|
||||||
|
"getNewSideloads",
|
||||||
"recordTimestamp", "recordSimpleMeasure",
|
"recordTimestamp", "recordSimpleMeasure",
|
||||||
"recordException", "getSimpleMeasures", "simpleTimer",
|
"recordException", "getSimpleMeasures", "simpleTimer",
|
||||||
"setTelemetryDetails", "getTelemetryDetails",
|
"setTelemetryDetails", "getTelemetryDetails",
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
|
||||||
|
|
||||||
|
async function createWebExtension(details) {
|
||||||
|
let options = {
|
||||||
|
manifest: {
|
||||||
|
applications: {gecko: {id: details.id}},
|
||||||
|
|
||||||
|
name: details.name,
|
||||||
|
|
||||||
|
permissions: details.permissions,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (details.iconURL) {
|
||||||
|
options.manifest.icons = {"64": details.iconURL};
|
||||||
|
}
|
||||||
|
|
||||||
|
let xpi = AddonTestUtils.createTempWebExtensionFile(options);
|
||||||
|
|
||||||
|
await AddonTestUtils.manuallyInstall(xpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createXULExtension(details) {
|
||||||
|
let xpi = AddonTestUtils.createTempXPIFile({
|
||||||
|
"install.rdf": {
|
||||||
|
id: details.id,
|
||||||
|
name: details.name,
|
||||||
|
version: "0.1",
|
||||||
|
targetApplications: [{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "0",
|
||||||
|
maxVersion: "*",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await AddonTestUtils.manuallyInstall(xpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_sideloading() {
|
||||||
|
Services.prefs.setIntPref("extensions.autoDisableScopes", 15);
|
||||||
|
Services.prefs.setIntPref("extensions.startupScanScopes", 0);
|
||||||
|
|
||||||
|
const ID1 = "addon1@tests.mozilla.org";
|
||||||
|
await createWebExtension({
|
||||||
|
id: ID1,
|
||||||
|
name: "Test 1",
|
||||||
|
userDisabled: true,
|
||||||
|
permissions: ["history", "https://*/*"],
|
||||||
|
iconURL: "foo-icon.png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ID2 = "addon2@tests.mozilla.org";
|
||||||
|
await createXULExtension({
|
||||||
|
id: ID2,
|
||||||
|
name: "Test 2",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ID3 = "addon3@tests.mozilla.org";
|
||||||
|
await createWebExtension({
|
||||||
|
id: ID3,
|
||||||
|
name: "Test 3",
|
||||||
|
permissions: ["<all_urls>"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const ID4 = "addon4@tests.mozilla.org";
|
||||||
|
await createWebExtension({
|
||||||
|
id: ID4,
|
||||||
|
name: "Test 4",
|
||||||
|
permissions: ["<all_urls>"],
|
||||||
|
});
|
||||||
|
|
||||||
|
await promiseStartupManager();
|
||||||
|
|
||||||
|
let sideloaded = await AddonManagerPrivate.getNewSideloads();
|
||||||
|
|
||||||
|
sideloaded.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
|
||||||
|
deepEqual(sideloaded.map(a => a.id),
|
||||||
|
[ID1, ID2, ID3, ID4],
|
||||||
|
"Got the correct sideload add-ons");
|
||||||
|
|
||||||
|
deepEqual(sideloaded.map(a => a.userDisabled),
|
||||||
|
[true, true, true, true],
|
||||||
|
"All sideloaded add-ons are disabled");
|
||||||
|
});
|
|
@ -334,6 +334,7 @@ run-sequentially = Uses global XCurProcD dir.
|
||||||
[test_overrideblocklist.js]
|
[test_overrideblocklist.js]
|
||||||
run-sequentially = Uses global XCurProcD dir.
|
run-sequentially = Uses global XCurProcD dir.
|
||||||
tags = blocklist
|
tags = blocklist
|
||||||
|
[test_sideloads.js]
|
||||||
[test_sourceURI.js]
|
[test_sourceURI.js]
|
||||||
[test_webextension_icons.js]
|
[test_webextension_icons.js]
|
||||||
skip-if = appname == "thunderbird"
|
skip-if = appname == "thunderbird"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче