Bug 1593358 - Wait to move extension cards while list is active r=rpl,Gijs

Differential Revision: https://phabricator.services.mozilla.com/D51700

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Striemer 2019-11-22 15:35:23 +00:00
Родитель fc6c868f4a
Коммит 3ad260c258
6 изменённых файлов: 471 добавлений и 122 удалений

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

@ -122,6 +122,13 @@ const PRIVATE_BROWSING_PERMS = {
origins: [],
};
function shouldSkipAnimations() {
return (
document.body.hasAttribute("skip-animations") ||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
);
}
const AddonCardListenerHandler = {
ADDON_EVENTS: new Set([
"onDisabled",
@ -2430,6 +2437,8 @@ class AddonCard extends HTMLElement {
switch (action) {
case "toggle-disabled":
this.recordActionEvent(addon.userDisabled ? "enable" : "disable");
// Keep the checked state the same until the add-on's state changes.
e.target.checked = !addon.userDisabled;
if (addon.userDisabled) {
if (shouldShowPermissionsPrompt(addon)) {
await showPermissionsPrompt(addon);
@ -2439,10 +2448,6 @@ class AddonCard extends HTMLElement {
} else {
await addon.disable();
}
if (e.mozInputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
// Refocus the button, since the card might've moved and lost focus.
e.target.focus();
}
break;
case "ask-to-activate":
if (hasPermission(addon, "ask-to-activate")) {
@ -3129,6 +3134,8 @@ class AddonList extends HTMLElement {
super();
this.sections = [];
this.pendingUninstallAddons = new Set();
this._addonsToUpdate = new Set();
this._userFocusListenersAdded = false;
}
async connectedCallback() {
@ -3321,9 +3328,7 @@ class AddonList extends HTMLElement {
return;
}
let insertSection = this.sections.findIndex(({ filterFn }) =>
filterFn(addon)
);
let insertSection = this._addonSectionIndex(addon);
// Don't add the add-on if it doesn't go in a section.
if (insertSection == -1) {
@ -3352,24 +3357,144 @@ class AddonList extends HTMLElement {
}
updateAddon(addon) {
if (!this.getCard(addon)) {
// Try to add the add-on right away.
this.addAddon(addon);
} else if (this._addonSectionIndex(addon) == -1) {
// Try to remove the add-on right away.
this._updateAddon(addon);
} else if (this.isUserFocused) {
// Queue up a change for when the focus is cleared.
this.updateLater(addon);
} else {
// Not currently focused, make the change now.
this.withCardAnimation(() => this._updateAddon(addon));
}
}
updateLater(addon) {
this._addonsToUpdate.add(addon);
this._addUserFocusListeners();
}
_addUserFocusListeners() {
if (this._userFocusListenersAdded) {
return;
}
this._userFocusListenersAdded = true;
this.addEventListener("mouseleave", this);
this.addEventListener("hidden", this, true);
this.addEventListener("focusout", this);
}
_removeUserFocusListeners() {
if (!this._userFocusListenersAdded) {
return;
}
this.removeEventListener("mouseleave", this);
this.removeEventListener("hidden", this, true);
this.removeEventListener("focusout", this);
this._userFocusListenersAdded = false;
}
get hasMenuOpen() {
return !!this.querySelector("panel-list[open]");
}
get isUserFocused() {
return this.matches(":hover, :focus-within") || this.hasMenuOpen;
}
update() {
if (this._addonsToUpdate.size) {
this.withCardAnimation(() => {
for (let addon of this._addonsToUpdate) {
this._updateAddon(addon);
}
this._addonsToUpdate = new Set();
});
}
}
_getChildCoords() {
let results = new Map();
for (let child of this.querySelectorAll("addon-card")) {
results.set(child, child.getBoundingClientRect());
}
return results;
}
withCardAnimation(changeFn) {
if (shouldSkipAnimations()) {
changeFn();
return;
}
let origChildCoords = this._getChildCoords();
changeFn();
let newChildCoords = this._getChildCoords();
let cards = this.querySelectorAll("addon-card");
let transitionCards = [];
for (let card of cards) {
let orig = origChildCoords.get(card);
let moved = newChildCoords.get(card);
let changeY = moved.y - (orig || moved).y;
let cardEl = card.firstElementChild;
if (changeY != 0) {
cardEl.style.transform = `translateY(${changeY * -1}px)`;
transitionCards.push(card);
}
}
requestAnimationFrame(() => {
for (let card of transitionCards) {
card.firstElementChild.style.transition = "transform 125ms";
}
requestAnimationFrame(() => {
for (let card of transitionCards) {
let cardEl = card.firstElementChild;
cardEl.style.transform = "";
cardEl.addEventListener("transitionend", function handler(e) {
if (e.target == cardEl && e.propertyName == "transform") {
cardEl.style.transition = "";
cardEl.removeEventListener("transitionend", handler);
}
});
}
});
});
}
_addonSectionIndex(addon) {
return this.sections.findIndex(s => s.filterFn(addon));
}
_updateAddon(addon) {
let card = this.getCard(addon);
if (card) {
let sectionIndex = this.sections.findIndex(s => s.filterFn(addon));
let sectionIndex = this._addonSectionIndex(addon);
if (sectionIndex != -1) {
// Move the card, if needed. This will allow an animation between
// page sections and provides clearer events for testing.
if (card.parentNode.getAttribute("section") != sectionIndex) {
let { activeElement } = document;
let refocus = card.contains(activeElement);
let oldSection = card.parentNode;
this.insertCardInto(card, sectionIndex);
this.updateSectionIfEmpty(oldSection);
if (refocus) {
activeElement.focus();
}
this.sendEvent("move", { id: addon.id });
}
} else {
this.removeAddon(addon);
}
} else {
// Add the add-on, this will do nothing if it shouldn't be in the list.
this.addAddon(addon);
}
}
@ -3468,6 +3593,13 @@ class AddonList extends HTMLElement {
this.removePendingUninstallBar(addon);
this.removeAddon(addon);
}
handleEvent(e) {
if (!this.isUserFocused || (e.type == "mouseleave" && !this.hasMenuOpen)) {
this._removeUserFocusListeners();
this.update();
}
}
}
customElements.define("addon-list", AddonList);

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

@ -47,6 +47,7 @@ generated-files =
[browser_CTP_plugins.js]
tags = blocklist
[browser_about_debugging_link.js]
[browser_addon_list_reordering.js]
[browser_bug523784.js]
skip-if = (!debug && os == 'win') #Bug 1489496
[browser_bug567137.js]

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

@ -0,0 +1,189 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { AddonTestUtils } = ChromeUtils.import(
"resource://testing-common/AddonTestUtils.jsm"
);
AddonTestUtils.initMochitest(this);
function assertInSection(card, sectionName, msg) {
let section = card.closest("section");
let heading = section.querySelector(".list-section-heading");
is(
card.ownerDocument.l10n.getAttributes(heading).id,
`extension-${sectionName}-heading`,
msg
);
}
function waitForAnimationFrame(win) {
return new Promise(resolve => win.requestAnimationFrame(resolve));
}
async function clickEnableToggle(card) {
let isDisabled = card.addon.userDisabled;
let addonEvent = isDisabled ? "onEnabled" : "onDisabled";
let addonStateChanged = AddonTestUtils.promiseAddonEvent(addonEvent);
let win = card.ownerGlobal;
let button = card.querySelector(".extension-enable-button");
// Centre the button since "start" could be behind the sticky header.
button.scrollIntoView({ block: "center" });
EventUtils.synthesizeMouseAtCenter(button, { type: "mousemove" }, win);
EventUtils.synthesizeMouseAtCenter(button, {}, win);
await addonStateChanged;
await waitForAnimationFrame(win);
}
function mouseOver(el) {
let win = el.ownerGlobal;
el.scrollIntoView({ block: "center" });
EventUtils.synthesizeMouseAtCenter(el, { type: "mousemove" }, win);
return waitForAnimationFrame(win);
}
function mouseOutOfList(win) {
return mouseOver(win.document.querySelector(".header-name"));
}
function pressKey(win, key) {
EventUtils.synthesizeKey(key, {}, win);
return waitForAnimationFrame(win);
}
function waitForTransitionEnd(...els) {
return Promise.all(
els.map(el =>
BrowserTestUtils.waitForEvent(el, "transitionend", false, e => {
let cardEl = el.firstElementChild;
return e.target == cardEl && e.propertyName == "transform";
})
)
);
}
add_task(async function testReordering() {
let addonIds = [
"one@mochi.test",
"two@mochi.test",
"three@mochi.test",
"four@mochi.test",
"five@mochi.test",
];
let extensions = addonIds.map(id =>
ExtensionTestUtils.loadExtension({
manifest: {
name: id,
applications: { gecko: { id } },
},
useAddonManager: "temporary",
})
);
await Promise.all(extensions.map(ext => ext.startup()));
let win = await loadInitialView("extension", { withAnimations: true });
let cardOne = getAddonCard(win, "one@mochi.test");
ok(!cardOne.addon.userDisabled, "extension one is enabled");
assertInSection(cardOne, "enabled", "cardOne is initially in Enabled");
await clickEnableToggle(cardOne);
ok(cardOne.addon.userDisabled, "extension one is now disabled");
assertInSection(cardOne, "enabled", "cardOne is still in Enabled");
let cardThree = getAddonCard(win, "three@mochi.test");
ok(!cardThree.addon.userDisabled, "extension three is enabled");
assertInSection(cardThree, "enabled", "cardThree is initially in Enabled");
await clickEnableToggle(cardThree);
ok(cardThree.addon.userDisabled, "extension three is now disabled");
assertInSection(cardThree, "enabled", "cardThree is still in Enabled");
let transitionsEnded = waitForTransitionEnd(cardOne, cardThree);
await mouseOutOfList(win);
await transitionsEnded;
assertInSection(cardOne, "disabled", "cardOne has moved to disabled");
assertInSection(cardThree, "disabled", "cardThree has moved to disabled");
await clickEnableToggle(cardThree);
await clickEnableToggle(cardOne);
assertInSection(cardOne, "disabled", "cardOne is still in disabled");
assertInSection(cardThree, "disabled", "cardThree is still in disabled");
info("Opening a more options menu");
let panel = cardThree.querySelector("panel-list");
EventUtils.synthesizeMouseAtCenter(
cardThree.querySelector('[action="more-options"]'),
{},
win
);
await BrowserTestUtils.waitForEvent(panel, "shown");
await mouseOutOfList(win);
assertInSection(cardOne, "disabled", "cardOne stays in disabled, menu open");
assertInSection(cardThree, "disabled", "cardThree stays in disabled");
transitionsEnded = waitForTransitionEnd(cardOne, cardThree);
EventUtils.synthesizeMouseAtCenter(
win.document.querySelector(".header-name"),
{},
win
);
await transitionsEnded;
assertInSection(cardOne, "enabled", "cardOne is now in enabled");
assertInSection(cardThree, "enabled", "cardThree is now in enabled");
let cardOneToggle = cardOne.querySelector(".extension-enable-button");
cardOneToggle.scrollIntoView({ block: "center" });
cardOneToggle.focus();
await pressKey(win, " ");
await waitForAnimationFrame(win);
let cardThreeToggle = cardThree.querySelector(".extension-enable-button");
let addonList = win.document.querySelector("addon-list");
// Tab down to cardThreeToggle.
while (
addonList.contains(win.document.activeElement) &&
win.document.activeElement !== cardThreeToggle
) {
await pressKey(win, "VK_TAB");
}
await pressKey(win, " ");
assertInSection(cardOne, "enabled", "cardOne is still in enabled");
assertInSection(cardThree, "enabled", "cardThree is still in enabled");
transitionsEnded = waitForTransitionEnd(cardOne, cardThree);
win.document.querySelector('[action="page-options"]').focus();
await transitionsEnded;
assertInSection(
cardOne,
"disabled",
"cardOne is now in the disabled section"
);
assertInSection(
cardThree,
"disabled",
"cardThree is now in the disabled section"
);
// Ensure an uninstalled extension is removed right away.
// Hover a card in the middle of the list.
await mouseOver(getAddonCard(win, "two@mochi.test"));
await cardOne.addon.uninstall(true);
ok(!cardOne.parentNode, "cardOne has been removed from the document");
await closeView(win);
await Promise.all(extensions.map(ext => ext.unload()));
});

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

@ -15,9 +15,6 @@ var GMPScope = ChromeUtils.import(
const TEST_DATE = new Date(2013, 0, 1, 12);
var gManagerWindow;
var gCategoryUtilities;
var gMockAddons = [];
for (let plugin of GMPScope.GMP_PLUGINS) {
@ -53,35 +50,20 @@ MockGMPInstallManager.prototype = {
},
};
function openDetailsView(aId) {
let view = get_current_view(gManagerWindow);
Assert.equal(
view.id,
"html-view",
"Should be in the list view to use this function"
);
let item = get_addon_element(gManagerWindow, aId);
function openDetailsView(win, id) {
let item = getAddonCard(win, id);
Assert.ok(item, "Should have got add-on element.");
is_element_visible(item, "Add-on element should be visible.");
item.scrollIntoView();
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, item.ownerGlobal);
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, item.ownerGlobal);
return new Promise(resolve => {
wait_for_view_load(gManagerWindow, resolve);
});
let loaded = waitForViewLoad(win);
EventUtils.synthesizeMouseAtCenter(item, {}, item.ownerGlobal);
return loaded;
}
async function initializeState() {
add_task(async function initializeState() {
gPrefs.setBoolPref(GMPScope.GMPPrefs.KEY_LOGGING_DUMP, true);
gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_LOGGING_LEVEL, 0);
gManagerWindow = await open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
registerCleanupFunction(async function() {
for (let addon of gMockAddons) {
gPrefs.clearUserPref(
@ -142,14 +124,16 @@ async function initializeState() {
}
await GMPScope.GMPProvider.shutdown();
GMPScope.GMPProvider.startup();
}
});
async function testNotInstalledDisabled() {
Assert.ok(gCategoryUtilities.isTypeVisible("plugin"), "Plugin tab visible.");
await gCategoryUtilities.openType("plugin");
add_task(async function testNotInstalledDisabled() {
let win = await loadInitialView("extension");
Assert.ok(isCategoryVisible(win, "plugin"), "Plugin tab visible.");
await switchView(win, "plugin");
for (let addon of gMockAddons) {
let addonCard = get_addon_element(gManagerWindow, addon.id);
let addonCard = getAddonCard(win, addon.id);
Assert.ok(addonCard, "Got add-on element:" + addon.id);
is(
@ -161,18 +145,20 @@ async function testNotInstalledDisabled() {
let cardMessage = addonCard.querySelector("message-bar.addon-card-message");
is_element_hidden(cardMessage, "Warning notification is hidden");
}
}
async function testNotInstalledDisabledDetails() {
await closeView(win);
});
add_task(async function testNotInstalledDisabledDetails() {
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
await openDetailsView(addon.id);
let doc = gManagerWindow.document;
let addonCard = get_addon_element(gManagerWindow, addon.id);
await openDetailsView(win, addon.id);
let addonCard = getAddonCard(win, addon.id);
ok(addonCard, "Got add-on element: " + addon.id);
is(
doc.l10n.getAttributes(addonCard.addonNameEl).id,
win.document.l10n.getAttributes(addonCard.addonNameEl).id,
"addon-name-disabled",
"The addon name should include a disabled postfix"
);
@ -182,17 +168,21 @@ async function testNotInstalledDisabledDetails() {
let cardMessage = addonCard.querySelector("message-bar.addon-card-message");
is_element_hidden(cardMessage, "Warning notification is hidden");
await gCategoryUtilities.openType("plugin");
}
await switchView(win, "plugin");
}
async function testNotInstalled() {
await closeView(win);
});
add_task(async function testNotInstalled() {
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
gPrefs.setBoolPref(
getKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id),
true
);
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
Assert.ok(item, "Got add-on element:" + addon.id);
let warningMessageBar = await BrowserTestUtils.waitForCondition(() => {
@ -214,13 +204,17 @@ async function testNotInstalled() {
);
pluginOptions.querySelector("panel-list").open = false;
}
}
async function testNotInstalledDetails() {
await closeView(win);
});
add_task(async function testNotInstalledDetails() {
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
await openDetailsView(addon.id);
await openDetailsView(win, addon.id);
const addonCard = get_addon_element(gManagerWindow, addon.id);
const addonCard = getAddonCard(win, addon.id);
let el = addonCard.querySelector("[action=update-check]");
is_element_visible(el, "Check for Updates action is visible");
@ -231,11 +225,15 @@ async function testNotInstalledDetails() {
}, "Wait for the addon card message to be updated");
is_element_visible(warningMessageBar, "Warning notification is visible");
await gCategoryUtilities.openType("plugin");
}
await switchView(win, "plugin");
}
async function testInstalled() {
await closeView(win);
});
add_task(async function testInstalled() {
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
gPrefs.setIntPref(
getKey(GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, addon.id),
@ -250,7 +248,7 @@ async function testInstalled() {
"1.2.3.4"
);
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
Assert.ok(item, "Got add-on element.");
is(item.parentNode.getAttribute("section"), "0", "Should be enabled");
@ -266,13 +264,17 @@ async function testInstalled() {
);
pluginOptions.querySelector("panel-list").open = false;
}
}
async function testInstalledDetails() {
await closeView(win);
});
add_task(async function testInstalledDetails() {
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
await openDetailsView(addon.id);
await openDetailsView(win, addon.id);
let card = get_addon_element(gManagerWindow, addon.id);
let card = getAddonCard(win, addon.id);
ok(card, "Got add-on element:" + addon.id);
is_element_visible(
@ -280,14 +282,18 @@ async function testInstalledDetails() {
"Find updates link is visible"
);
await gCategoryUtilities.openType("plugin");
}
await switchView(win, "plugin");
}
async function testInstalledGlobalEmeDisabled() {
await closeView(win);
});
add_task(async function testInstalledGlobalEmeDisabled() {
let win = await loadInitialView("plugin");
gPrefs.setBoolPref(GMPScope.GMPPrefs.KEY_EME_ENABLED, false);
for (let addon of gMockAddons) {
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
if (addon.isEME) {
is(item.parentNode.getAttribute("section"), "1", "Should be disabled");
// Open the options menu (needed to check the disabled buttons).
@ -305,10 +311,12 @@ async function testInstalledGlobalEmeDisabled() {
Assert.ok(item, "Got add-on element.");
}
}
gPrefs.setBoolPref(GMPScope.GMPPrefs.KEY_EME_ENABLED, true);
}
async function testPreferencesButton() {
gPrefs.setBoolPref(GMPScope.GMPPrefs.KEY_EME_ENABLED, true);
await closeView(win);
});
add_task(async function testPreferencesButton() {
let prefValues = [
{ enabled: false, version: "" },
{ enabled: false, version: "1.2.3.4" },
@ -317,15 +325,12 @@ async function testPreferencesButton() {
];
for (let preferences of prefValues) {
dump(
info(
"Testing preferences button with pref settings: " +
JSON.stringify(preferences) +
"\n"
JSON.stringify(preferences)
);
for (let addon of gMockAddons) {
await close_manager(gManagerWindow);
gManagerWindow = await open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
let win = await loadInitialView("plugin");
gPrefs.setCharPref(
getKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
preferences.version
@ -335,8 +340,7 @@ async function testPreferencesButton() {
preferences.enabled
);
await gCategoryUtilities.openType("plugin");
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
// Open the options menu (needed to check the more options action is enabled).
const pluginOptions = item.querySelector("plugin-options");
@ -350,12 +354,17 @@ async function testPreferencesButton() {
);
moreOptions.click();
await wait_for_view_load(gManagerWindow);
}
}
}
await waitForViewLoad(win);
async function testUpdateButton() {
item = getAddonCard(win, addon.id);
ok(item, "The right view is loaded");
await closeView(win);
}
}
});
add_task(async function testUpdateButton() {
gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK);
let originalInstallManager = GMPScope.GMPInstallManager;
@ -366,20 +375,24 @@ async function testUpdateButton() {
configurable: true,
});
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
await gCategoryUtilities.openType("plugin");
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
gInstalledAddonId = "";
gInstallDeferred = Promise.defer();
let loaded = waitForViewLoad(win);
item.querySelector("[action=expand]").click();
await wait_for_view_load(gManagerWindow);
let detail = get_addon_element(gManagerWindow, addon.id);
await loaded;
let detail = getAddonCard(win, addon.id);
detail.querySelector("[action=update-check]").click();
await gInstallDeferred.promise;
Assert.equal(gInstalledAddonId, addon.id);
await switchView(win, "plugin");
}
Object.defineProperty(GMPScope, "GMPInstallManager", {
value: originalInstallManager,
@ -387,9 +400,11 @@ async function testUpdateButton() {
enumerable: true,
configurable: true,
});
}
async function testEmeSupport() {
await closeView(win);
});
add_task(async function testEmeSupport() {
for (let addon of gMockAddons) {
gPrefs.clearUserPref(
getKey(GMPScope.GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, addon.id)
@ -398,9 +413,10 @@ async function testEmeSupport() {
await GMPScope.GMPProvider.shutdown();
GMPScope.GMPProvider.startup();
let win = await loadInitialView("plugin");
for (let addon of gMockAddons) {
await gCategoryUtilities.openType("plugin");
let item = get_addon_element(gManagerWindow, addon.id);
let item = getAddonCard(win, addon.id);
if (addon.id == GMPScope.EME_ADOBE_ID) {
if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
Assert.ok(item, "Adobe EME supported, found add-on element.");
@ -428,6 +444,8 @@ async function testEmeSupport() {
}
}
await closeView(win);
for (let addon of gMockAddons) {
gPrefs.setBoolPref(
getKey(GMPScope.GMPPrefs.KEY_PLUGIN_VISIBLE, addon.id),
@ -440,27 +458,4 @@ async function testEmeSupport() {
}
await GMPScope.GMPProvider.shutdown();
GMPScope.GMPProvider.startup();
}
async function testCleanupState() {
await SpecialPowers.popPrefEnv();
await close_manager(gManagerWindow);
}
// This function run the sequence of all the gmpProvider tests
// under the same initializeStateOptions (which will enable or disable
// the HTML about:addons views).
add_task(async function test_gmpProvider(initializeStateOptions) {
await initializeState();
await testNotInstalledDisabled();
await testNotInstalledDisabledDetails();
await testNotInstalled();
await testNotInstalledDetails();
await testInstalled();
await testInstalledDetails();
await testInstalledGlobalEmeDisabled();
await testPreferencesButton();
await testUpdateButton();
await testEmeSupport();
await testCleanupState();
});

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

@ -366,10 +366,11 @@ add_task(async function testMouseSupport() {
});
add_task(async function testKeyboardSupport() {
let id = "test@mochi.test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
name: "Test extension",
applications: { gecko: { id: "test@mochi.test" } },
applications: { gecko: { id } },
},
useAddonManager: "temporary",
});
@ -424,19 +425,32 @@ add_task(async function testKeyboardSupport() {
tab({ shiftKey: true });
isFocused(disableButton, "The disable toggle is focused");
is(card.parentNode, enabledSection, "The card is in the enabled section");
let disabled = BrowserTestUtils.waitForEvent(list, "move");
space();
await disabled;
// Wait for the add-on state to change.
let [disabledAddon] = await AddonTestUtils.promiseAddonEvent("onDisabled");
is(disabledAddon.id, id, "The right add-on was disabled");
is(
card.parentNode,
enabledSection,
"The card is still in the enabled section"
);
isFocused(disableButton, "The disable button is still focused");
let moved = BrowserTestUtils.waitForEvent(list, "move");
// Click outside the list to clear any focus.
EventUtils.synthesizeMouseAtCenter(
doc.querySelector(".header-name"),
{},
win
);
await moved;
is(
card.parentNode,
disabledSection,
"The card is now in the disabled section"
"The card moved when keyboard focus left the list"
);
isFocused(disableButton, "The disable button is still focused");
// Remove the add-on.
tab();
isFocused(moreOptionsButton, "The more options button is focused again");
moreOptionsButton.focus();
shown = BrowserTestUtils.waitForEvent(moreOptionsMenu, "shown");
space();
is(moreOptionsMenu.open, true, "The menu is open");
@ -928,6 +942,10 @@ add_task(async function testDisabledDimming() {
let win = await loadInitialView("extension");
let doc = win.document;
let pageHeader = doc.querySelector("addon-page-header");
// Ensure there's no focus on the list.
EventUtils.synthesizeMouseAtCenter(pageHeader, {}, win);
const checkOpacity = (card, expected, msg) => {
let { opacity } = card.ownerGlobal.getComputedStyle(card.firstElementChild);
@ -944,18 +962,25 @@ add_task(async function testDisabledDimming() {
checkOpacity(card, "1", "The opacity is 1 when enabled");
// Disable the add-on, check again.
let list = doc.querySelector("addon-list");
let moved = BrowserTestUtils.waitForEvent(list, "move");
await addon.disable();
await moved;
let disabledSection = getSection(doc, "disabled");
is(card.parentNode, disabledSection, "The card is in the disabled section");
checkOpacity(card, "0.6", "The opacity is dimmed when disabled");
// Click on the menu button, this should un-dim the card.
let transitionEnded = waitForTransition(card);
card.panel.open = true;
let moreOptionsButton = card.querySelector(".more-options-button");
EventUtils.synthesizeMouseAtCenter(moreOptionsButton, {}, win);
await transitionEnded;
checkOpacity(card, "1", "The opacity is 1 when the menu is open");
// Close the menu, opacity should return.
transitionEnded = waitForTransition(card);
card.panel.open = false;
EventUtils.synthesizeMouseAtCenter(pageHeader, {}, win);
await transitionEnded;
checkOpacity(card, "0.6", "The card is dimmed again");

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

@ -1637,7 +1637,7 @@ function assertAboutAddonsTelemetryEvents(events, filters = {}) {
}
/* HTML view helpers */
async function loadInitialView(type) {
async function loadInitialView(type, opts) {
// Force the first page load to be the view we want.
let viewId = type == "discover" ? "discover/" : `list/${type}`;
Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, `addons://${viewId}`);
@ -1646,6 +1646,9 @@ async function loadInitialView(type) {
let browser = managerWindow.document.getElementById("html-view-browser");
let win = browser.contentWindow;
if (!opts || !opts.withAnimations) {
win.document.body.setAttribute("skip-animations", "");
}
win.managerWindow = managerWindow;
return win;
}
@ -1662,6 +1665,10 @@ function switchView(win, type) {
return new CategoryUtilities(win.managerWindow).openType(type);
}
function isCategoryVisible(win, type) {
return new CategoryUtilities(win.managerWindow).isTypeVisible(type);
}
function mockPromptService() {
let { prompt } = Services;
let promptService = {