Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
requestLongerTimeout(3);
add_task(async function testCheckboxTogglesSelection() {
let tabOne = await setupTabWithOriginAndTitle(
"Checkbox toggle"
);
let { tabAboutProcesses, doc } = await openTabView();
let row = findTabRow(doc, "Checkbox toggle");
let checkbox = row.querySelector(".select-checkbox");
info("Clicking the checkbox selects the row");
clickRow(checkbox);
Assert.ok(checkbox.checked, "Checkbox is checked");
Assert.ok(row.hasAttribute("selected"), "Row is selected");
info("Clicking it again deselects the row");
clickRow(checkbox);
Assert.ok(!checkbox.checked, "Checkbox is unchecked");
Assert.ok(!row.hasAttribute("selected"), "Row is no longer selected");
info("Space on the focused checkbox toggles it too");
checkbox.focus();
EventUtils.synthesizeKey(" ", {}, doc.defaultView);
Assert.ok(row.hasAttribute("selected"), "Space selects the row");
EventUtils.synthesizeKey(" ", {}, doc.defaultView);
Assert.ok(!row.hasAttribute("selected"), "Space deselects it again");
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
});
add_task(async function testSelectAllCheckboxSelectsAndClearsAll() {
let tabOne = await setupTabWithOriginAndTitle(
"Select all 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Select all 2"
);
let { tabAboutProcesses, doc } = await openTabView();
let selectAll = doc.querySelector("#process-thead .select-checkbox");
Assert.ok(selectAll, "Header select-all checkbox is rendered");
info("Checking the header checkbox selects every row");
clickRow(selectAll);
for (let row of doc.querySelectorAll("tr.tab-row")) {
Assert.ok(
row.hasAttribute("selected"),
`${row.tabData.title} is selected after select-all`
);
}
Assert.ok(selectAll.checked, "Select-all checkbox reads as checked");
Assert.ok(!selectAll.indeterminate, "Not indeterminate when all selected");
info("Deselecting one row makes the header checkbox indeterminate");
clickCheckbox(doc, "Select all 1");
Assert.ok(!selectAll.checked, "No longer fully checked");
Assert.ok(selectAll.indeterminate, "Indeterminate with a partial selection");
info("Checking the header checkbox again selects every row again");
clickRow(selectAll);
for (let row of doc.querySelectorAll("tr.tab-row")) {
Assert.ok(row.hasAttribute("selected"), "Every row selected again");
}
info("Unchecking the header checkbox clears the whole selection");
clickRow(selectAll);
for (let row of doc.querySelectorAll("tr.tab-row")) {
Assert.ok(!row.hasAttribute("selected"), "Every row cleared");
}
Assert.ok(!selectAll.indeterminate, "Not indeterminate with none selected");
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
});
add_task(async function testSelectionSurvivesPollAndPrunesClosedTabs() {
let tabOne = await setupTabWithOriginAndTitle(
"Survives poll 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Survives poll 2"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Survives poll 1");
clickCheckbox(doc, "Survives poll 2");
info("Forcing a poll tick while two rows are selected");
await forceTabViewUpdate(tabAboutProcesses);
Assert.ok(
findTabRow(doc, "Survives poll 1").hasAttribute("selected"),
"Selection survives a poll tick"
);
Assert.ok(
findTabRow(doc, "Survives poll 2").hasAttribute("selected"),
"Both selected rows survive the poll"
);
info("Closing a selected tab out from under the selection");
let closedRow = findTabRow(doc, "Survives poll 1");
let closedIndex = closedRow.rowIndex;
await BrowserTestUtils.removeTab(tabOne);
await forceTabViewUpdate(tabAboutProcesses);
Assert.equal(
closedRow.rowIndex,
closedIndex,
"The closed tab's row holds its place while the checkbox clicks above freeze reordering"
);
Assert.ok(
closedRow.classList.contains("departed") && closedRow.inert,
"...dimmed and inert, so it can't be acted on"
);
Assert.ok(
findTabRow(doc, "Survives poll 2").hasAttribute("selected"),
"The still-open tab's selection is untouched by the other tab closing"
);
Assert.equal(
doc.querySelectorAll("tr.tab-row[selected]").length,
1,
"The closed tab no longer counts as selected"
);
info("Sorting ends the freeze");
let updated = promiseTabViewUpdated(doc);
doc.getElementById("column-name").click();
await updated;
Assert.ok(!closedRow.isConnected, "The closed tab's row is gone");
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabTwo);
});
add_task(async function testBulkActionBarShowsSelectionSummary() {
let tabOne = await setupTabWithOriginAndTitle(
"Bulk summary 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk summary 2"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Bulk summary 1");
clickCheckbox(doc, "Bulk summary 2");
let summaryRow = doc.querySelector("tr.bulk-summary-row");
Assert.ok(
doc.body.classList.contains("bulk-action-visible"),
"The visibility class is set while a selection exists"
);
Assert.equal(
doc.defaultView.getComputedStyle(summaryRow).display,
"grid",
"The summary row uses the same per-column grid as every other row"
);
let countCell = summaryRow.querySelector(".bulk-count");
await doc.l10n.translateFragment(summaryRow);
Assert.ok(
countCell.textContent.includes("2"),
"The bulk-action bar's count reflects the two selected tabs"
);
let memoryText = summaryRow.querySelector(".memory").textContent;
Assert.ok(
/^[0-9.]+(B|KB|MB|GB)$/.test(memoryText),
`Bulk-action memory "${memoryText}" is formatted as a number + unit, same as the totals row`
);
let cpuCell = summaryRow.querySelector(".cpu");
Assert.ok(
/^([0-9.]+%|idle|…|< 0\.01%)$/.test(cpuCell.textContent),
`Bulk-action CPU "${cpuCell.textContent}" is formatted as a percent or idle, same as the totals row`
);
Assert.equal(
doc.defaultView.getComputedStyle(cpuCell).backgroundImage,
"none",
"Bulk-action CPU cell has no CPU bar"
);
info("Deselecting both rows empties the selection and hides the bar");
clickCheckbox(doc, "Bulk summary 1");
clickCheckbox(doc, "Bulk summary 2");
Assert.ok(
!doc.body.classList.contains("bulk-action-visible"),
"The visibility class is cleared once the selection is empty"
);
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
});
add_task(async function testBulkActionsReflectLoadedAndDiscardedSelection() {
let tabOne = await setupTabWithOriginAndTitle(
"Bulk reload discarded"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk reload loaded"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Bulk reload discarded");
let unloadButton = doc.getElementById("bulk-unload-button");
let reloadButton = doc.getElementById("bulk-reload-button");
Assert.ok(
!unloadButton.hidden,
"Unload is shown when a loaded tab is selected"
);
findTabRow(doc, "Bulk reload discarded")
.querySelector(".unload-icon")
.click();
await BrowserTestUtils.waitForMutationCondition(
tabOne,
{ attributeFilter: ["discarded"] },
() => tabOne.hasAttribute("discarded"),
{ msg: "The selected tab becomes discarded" }
);
await forceTabViewUpdate(tabAboutProcesses);
clickCheckbox(doc, "Bulk reload loaded");
Assert.ok(
!unloadButton.hidden,
"Mixed selection offers Unload for loaded tabs"
);
clickCheckbox(doc, "Bulk reload loaded");
Assert.ok(unloadButton.hidden, "Discarded-only selection hides Unload");
clickCheckbox(doc, "Bulk reload loaded");
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let loadedTabReloaded = BrowserTestUtils.browserLoaded(tabTwo.linkedBrowser);
reloadButton.click();
await BrowserTestUtils.waitForMutationCondition(
tabOne,
{ attributeFilter: ["discarded"] },
() => !tabOne.hasAttribute("discarded"),
{ msg: "Bulk Reload restores the selected discarded tab" }
);
await loadedTabReloaded;
Assert.ok(
tabAboutProcesses.selected,
"Bulk Reload leaves the monitor selected"
);
await Services.fog.testFlushAllChildren();
assertOneEvent(
Glean.resourceMonitor.tabActionTaken,
{ action_type: "reload", action_source: "bulk_bar", tab_count: "2" },
"Bulk Reload records one action event"
);
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
Services.fog.testResetFOG();
});
add_task(async function testBulkActionBarCancelAndEscapeClearSelection() {
let tabOne = await setupTabWithOriginAndTitle(
"Bulk cancel 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk cancel 2"
);
let { tabAboutProcesses, doc } = await openTabView();
let rowOne = findTabRow(doc, "Bulk cancel 1");
let rowTwo = findTabRow(doc, "Bulk cancel 2");
clickRow(rowOne.querySelector(".select-checkbox"));
clickRow(rowTwo.querySelector(".select-checkbox"));
Assert.ok(
doc.body.classList.contains("bulk-action-visible"),
"Selection is active before cancelling"
);
doc.getElementById("bulk-cancel-button").click();
Assert.ok(
!doc.body.classList.contains("bulk-action-visible"),
"The cancel icon clears the selection"
);
Assert.ok(!rowOne.hasAttribute("selected"), "Row one is no longer selected");
Assert.ok(!rowTwo.hasAttribute("selected"), "Row two is no longer selected");
info("Re-selecting, then cancelling via Escape instead");
clickRow(rowOne.querySelector(".select-checkbox"));
clickRow(rowTwo.querySelector(".select-checkbox"));
Assert.ok(
doc.body.classList.contains("bulk-action-visible"),
"Selection is active again before the Escape test"
);
doc.getElementById("bulk-close-button").focus();
EventUtils.synthesizeKey("KEY_Escape", {}, doc.defaultView);
Assert.ok(
!doc.body.classList.contains("bulk-action-visible"),
"Escape also clears the selection, even with focus outside the rows"
);
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
});
add_task(async function testBulkActionBarUnloadButton() {
let tabOne = await setupTabWithOriginAndTitle(
"Bulk unload 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk unload 2"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Bulk unload 1");
clickCheckbox(doc, "Bulk unload 2");
let buttonsRow = doc.querySelector("tr.bulk-buttons-row");
pauseTabViewPolling(doc);
buttonsRow.querySelector("#bulk-unload-button").click();
await Promise.all(
[tabOne, tabTwo].map(tab =>
BrowserTestUtils.waitForMutationCondition(
tab,
{ attributeFilter: ["discarded"] },
() => tab.hasAttribute("discarded"),
{
msg: "Waiting for both selected tabs to be discarded by the bulk unload button",
}
)
)
);
Assert.ok(
!doc.body.classList.contains("bulk-action-visible"),
"Unload clears the selection eagerly, hiding the bar immediately"
);
await BrowserTestUtils.waitForMutationCondition(
doc.getElementById("process-tbody"),
{ subtree: true, attributeFilter: ["class"] },
() =>
findTabRow(doc, "Bulk unload 1")?.classList.contains("killed") &&
findTabRow(doc, "Bulk unload 2")?.classList.contains("killed"),
{
msg: "Waiting for the bulk unload's own eager refresh to dim both rows, with no test-side update nudge",
}
);
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
});
add_task(async function testBulkActionBarCloseButton() {
let tabOne = await setupTabWithOriginAndTitle(
"Bulk close 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk close 2"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Bulk close 1");
clickCheckbox(doc, "Bulk close 2");
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
let closed = Promise.all([
BrowserTestUtils.waitForTabClosing(tabOne),
BrowserTestUtils.waitForTabClosing(tabTwo),
]);
let buttonsRow = doc.querySelector("tr.bulk-buttons-row");
pauseTabViewPolling(doc);
buttonsRow.querySelector("#bulk-close-button").click();
await closed;
await Services.fog.testFlushAllChildren();
assertOneEvent(
Glean.resourceMonitor.tabActionTaken,
{ action_type: "close", action_source: "bulk_bar", tab_count: "2" },
"Bulk Close records one action event for both selected tabs"
);
Assert.ok(
!doc.body.classList.contains("bulk-action-visible"),
"Close clears the selection eagerly, hiding the bar immediately"
);
await BrowserTestUtils.waitForMutationCondition(
doc.getElementById("process-tbody"),
{ childList: true },
() => !findTabRow(doc, "Bulk close 1") && !findTabRow(doc, "Bulk close 2"),
{
msg: "Waiting for the bulk close's own eager refresh to remove both rows, with no test-side update nudge",
}
);
BrowserTestUtils.removeTab(tabAboutProcesses);
});
add_task(async function testBulkCloseHonorsCloseTabsWarning() {
const { sinon } = ChromeUtils.importESModule(
);
let tabOne = await setupTabWithOriginAndTitle(
"Bulk close warned 1"
);
let tabTwo = await setupTabWithOriginAndTitle(
"Bulk close warned 2"
);
let { tabAboutProcesses, doc } = await openTabView();
clickCheckbox(doc, "Bulk close warned 1");
clickCheckbox(doc, "Bulk close warned 2");
let warn = sinon.stub(gBrowser, "warnAboutClosingTabs").returns(false);
doc.querySelector("#bulk-close-button").click();
warn.restore();
Assert.ok(
warn.calledOnceWith(2, gBrowser.constructor.closingTabsEnum.MULTI_SELECTED),
"Bulk close asks the same multi-select close warning as the tab strip"
);
Assert.ok(
tabOne.parentNode && tabTwo.parentNode,
"Declining the warning keeps both tabs open"
);
BrowserTestUtils.removeTab(tabAboutProcesses);
BrowserTestUtils.removeTab(tabOne);
BrowserTestUtils.removeTab(tabTwo);
});
// A cross-window selection is grouped per tabbrowser, for every bulk action.
add_task(async function testBulkActionsSpanMultipleWindows() {
for (let { action, promiseDone } of [
{
action: "close",
promiseDone: tab => BrowserTestUtils.waitForTabClosing(tab),
},
{
action: "unload",
promiseDone: tab =>
BrowserTestUtils.waitForMutationCondition(
tab,
{ attributeFilter: ["discarded"] },
() => tab.hasAttribute("discarded")
),
},
]) {
let tabOne = await setupTabWithOriginAndTitle(
`Cross-window ${action} 1`
);
let secondWindow = await BrowserTestUtils.openNewBrowserWindow();
let tabTwo = await setupTabWithOriginAndTitle(
`Cross-window ${action} 2`,
secondWindow
);
// The second window can steal OS focus on Windows, leaving this one hidden.
await SimpleTest.promiseFocus(window);
let { tabAboutProcesses, doc } = await openTabView();
for (let n of [1, 2]) {
clickCheckbox(doc, `Cross-window ${action} ${n}`);
}
let done = Promise.all([promiseDone(tabOne), promiseDone(tabTwo)]);
doc.querySelector(`#bulk-${action}-button`).click();
await done;
BrowserTestUtils.removeTab(tabAboutProcesses);
if (tabOne.isConnected) {
BrowserTestUtils.removeTab(tabOne);
}
await BrowserTestUtils.closeWindow(secondWindow);
}
});