/* Any copyright is dedicated to the Public Domain. * https://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Tests that parent navigation buttons stay selected when on sub-panes, // enabling keyboard navigation from sub-panes (Bug 2038759). /** * Helper to get a nav button by its view attribute. */ function getNavButton(doc, viewName) { return doc.querySelector(`moz-page-nav-button[view="${viewName}"]`); } /** * Helper to assert that a nav button is selected and focusable. */ function assertNavButtonSelected(button, message) { ok(button, "Nav button exists"); ok(button.selected, `${message} - button has selected attribute`); is( button.buttonEl.getAttribute("tabindex"), "0", `${message} - button is focusable` ); } // Test that parent nav buttons stay selected for various sub-pane scenarios. add_task(async function test_parent_nav_selected_for_subpanes() { // Test single-level sub-pane: etp parent is privacy await openPreferencesViaOpenPreferencesAPI("etp", { leaveOpen: true }); let doc = gBrowser.contentDocument; let categories = doc.getElementById("categories"); let privacyButton = getNavButton(doc, "panePrivacy"); is( categories.currentView, "panePrivacy", "Privacy nav button selected when on ETP sub-pane" ); assertNavButtonSelected(privacyButton, "ETP sub-pane"); // Test nested sub-pane: etpCustomize → etp → privacy (should select privacy root) let paneChangePromise = waitForPaneChange("etpCustomize"); doc.location.hash = "etpCustomize"; await paneChangePromise; is( categories.currentView, "panePrivacy", "Privacy nav button selected for nested sub-pane (etpCustomize)" ); assertNavButtonSelected(privacyButton, "Nested sub-pane"); // Test different sub-pane: customHomepage parent is home let homeButton = getNavButton(doc, "paneHome"); let win = gBrowser.contentWindow; paneChangePromise = waitForPaneChange("home"); EventUtils.synthesizeMouseAtCenter(homeButton.buttonEl, {}, win); await paneChangePromise; is( categories.currentView, "paneHome", "Home nav button selected for customHomepage sub-pane" ); assertNavButtonSelected(homeButton, "customHomepage sub-pane"); // Test top-level pane without parent (regression test) paneChangePromise = waitForPaneChange("privacy"); EventUtils.synthesizeMouseAtCenter(privacyButton.buttonEl, {}, win); await paneChangePromise; is( categories.currentView, "panePrivacy", "Privacy nav button selected for privacy pane itself" ); assertNavButtonSelected(privacyButton, "Top-level pane"); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // Test that direct URL navigation to a sub-pane selects the parent button. add_task(async function test_direct_url_navigation_to_subpane() { let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, "about:preferences#etp" ); let doc = tab.linkedBrowser.contentDocument; await BrowserTestUtils.waitForMutationCondition( doc.body, { childList: true, subtree: true }, () => doc.getElementById("categories") ); let categories = doc.getElementById("categories"); let privacyButton = getNavButton(doc, "panePrivacy"); is( categories.currentView, "panePrivacy", "Privacy nav button selected with direct #etp URL navigation" ); assertNavButtonSelected(privacyButton, "Direct URL navigation"); BrowserTestUtils.removeTab(tab); }); // Test that arrow key navigation works when focused on parent nav button while viewing sub-pane. add_task(async function test_arrow_key_navigation_from_subpane() { await openPreferencesViaOpenPreferencesAPI("etp", { leaveOpen: true }); let doc = gBrowser.contentDocument; let win = gBrowser.contentWindow; let privacyButton = getNavButton(doc, "panePrivacy"); let searchButton = getNavButton(doc, "paneSearch"); // Focus the selected Privacy button (currently selected because we're on ETP sub-pane) privacyButton.buttonEl.focus(); is(doc.activeElement, privacyButton, "Privacy nav button is focused"); // Press ArrowUp to navigate to Search pane let paneChangePromise = waitForPaneChange("search"); EventUtils.synthesizeKey("KEY_ArrowUp", {}, win); await paneChangePromise; // Verify Search button is now selected and we navigated to Search pane ok(searchButton.selected, "Search button selected after arrow up"); is(win.history.state, "paneSearch", "Navigated to Search pane"); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // Test that back button from sub-pane maintains correct parent selection. add_task(async function test_back_button_from_subpane() { // Start on privacy (top-level pane) await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); let doc = gBrowser.contentDocument; let win = gBrowser.contentWindow; let categories = doc.getElementById("categories"); is( categories.currentView, "panePrivacy", "Privacy selected for privacy pane" ); // Navigate to ETP sub-pane let paneChangePromise = waitForPaneChange("etp"); win.gotoPref("paneEtp"); await paneChangePromise; // Privacy button should still be selected is( categories.currentView, "panePrivacy", "Privacy selected when navigating to ETP" ); // Wait for ETP pane to fully load await BrowserTestUtils.waitForMutationCondition( doc.getElementById("mainPrefPane"), { childList: true, subtree: true }, () => doc.querySelector('setting-pane[data-category="paneEtp"]') ); let etpPane = doc.querySelector('setting-pane[data-category="paneEtp"]'); await etpPane.updateComplete; // Click back button let backButton = etpPane.pageHeaderEl.backButtonEl; ok(backButton, "Back button exists on ETP pane"); ok(BrowserTestUtils.isVisible(backButton), "Back button is visible"); paneChangePromise = waitForPaneChange("privacy"); EventUtils.synthesizeMouseAtCenter(backButton, {}, win); await paneChangePromise; // Verify we're back on main privacy pane and it's still selected is( categories.currentView, "panePrivacy", "Privacy still selected after clicking back button" ); is(win.history.state, "panePrivacy", "Navigated back to privacy pane"); BrowserTestUtils.removeTab(gBrowser.selectedTab); });