From 92c159f358fc494a27701fbc02c1917be5a98ea8 Mon Sep 17 00:00:00 2001 From: Alex Kontos Date: Tue, 5 Aug 2025 15:35:07 +0100 Subject: [PATCH] feat: add option to disable Ctrl+W close shortcut --- browser/base/content/browser.js | 7 +++ browser/base/content/nonbrowser-mac.js | 6 ++ browser/components/tests/browser/browser.ini | 0 .../tests/browser/browser_close_disabled.js | 62 +++++++++++++++++++ browser/modules/BrowserUIUtils.sys.mjs | 7 +++ waterfox/browser/app/profile/03-features.js | 3 + 6 files changed, 85 insertions(+) create mode 100644 browser/components/tests/browser/browser.ini create mode 100644 browser/components/tests/browser/browser_close_disabled.js diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 685493f6f3cb..0202e48d6a16 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1377,6 +1377,13 @@ var gLastOpenDirectory = { this._lastDir ); } + + if (BrowserUIUtils.closeShortcutDisabled) { + document.getElementById("key_close").remove(); + document.getElementById("menu_close").removeAttribute("key"); + document.getElementById("key_closeWindow").remove(); + document.getElementById("menu_closeWindow").removeAttribute("key"); + } }, reset() { this._lastDir = null; diff --git a/browser/base/content/nonbrowser-mac.js b/browser/base/content/nonbrowser-mac.js index 4ddcce210f3f..163a4ef11d4c 100644 --- a/browser/base/content/nonbrowser-mac.js +++ b/browser/base/content/nonbrowser-mac.js @@ -110,6 +110,12 @@ var NonBrowserWindow = { document.getElementById("key_quitApplication").remove(); document.getElementById("menu_FileQuitItem").removeAttribute("key"); } + if (BrowserUIUtils.closeShortcutDisabled) { + document.getElementById("key_close").remove(); + document.getElementById("menu_close").removeAttribute("key"); + document.getElementById("key_closeWindow").remove(); + document.getElementById("menu_closeWindow").removeAttribute("key"); + } } this.delayedStartupTimeoutId = setTimeout(() => this.delayedStartup(), 0); diff --git a/browser/components/tests/browser/browser.ini b/browser/components/tests/browser/browser.ini new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/browser/components/tests/browser/browser_close_disabled.js b/browser/components/tests/browser/browser_close_disabled.js new file mode 100644 index 000000000000..3af90764c4e5 --- /dev/null +++ b/browser/components/tests/browser/browser_close_disabled.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function test_appMenu_close_disabled() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.closeShortcut.disabled", true]], + }); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + let doc = win.document; + + let menuButton = doc.getElementById("PanelUI-menu-button"); + menuButton.click(); + await BrowserTestUtils.waitForEvent(win.PanelUI.mainView, "ViewShown"); + + let closeButton = doc.querySelector(`[key="key_closeWindow"]`); + is(closeButton, null, "No close button with shortcut key"); + + await BrowserTestUtils.closeWindow(win); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_close_shortcut_disabled() { + async function testCloseShortcut(shouldClose) { + let win = await BrowserTestUtils.openNewBrowserWindow(); + + let closeRequested = false; + let observer = { + observe(subject, topic, data) { + is(topic, "close-application-requested", "Right observer topic"); + ok(shouldClose, "Close shortcut should NOT have worked"); + + // Don't actually close the browser when testing. + let cancelClose = subject.QueryInterface(Ci.nsISupportsPRBool); + cancelClose.data = true; + + closeRequested = true; + }, + }; + Services.obs.addObserver(observer, "close-application-requested"); + + let modifiers = { accelKey: true }; + if (AppConstants.platform == "win") { + modifiers.shiftKey = true; + } + EventUtils.synthesizeKey("w", modifiers, win); + + await BrowserTestUtils.closeWindow(win); + Services.obs.removeObserver(observer, "close-application-requested"); + + is(closeRequested, shouldClose, "Expected close state"); + } + + // Close shortcut should work when pref is not set. + await testCloseShortcut(true); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.closeShortcut.disabled", true]], + }); + await testCloseShortcut(false); +}); diff --git a/browser/modules/BrowserUIUtils.sys.mjs b/browser/modules/BrowserUIUtils.sys.mjs index ab2ae3c1e56a..559dbf67dd80 100644 --- a/browser/modules/BrowserUIUtils.sys.mjs +++ b/browser/modules/BrowserUIUtils.sys.mjs @@ -174,3 +174,10 @@ XPCOMUtils.defineLazyPreferenceGetter( "browser.quitShortcut.disabled", false ); + +XPCOMUtils.defineLazyPreferenceGetter( + BrowserUIUtils, + "closeShortcutDisabled", + "browser.closeShortcut.disabled", + false +); diff --git a/waterfox/browser/app/profile/03-features.js b/waterfox/browser/app/profile/03-features.js index 2dbd443f0a3a..b3a72ee29c3b 100644 --- a/waterfox/browser/app/profile/03-features.js +++ b/waterfox/browser/app/profile/03-features.js @@ -214,6 +214,9 @@ pref("browser.ml.chat.enabled", false); // Disable built-in ML chat features. // Image Format Support pref("image.jxl.enabled", true); // Enable support for the JPEG XL image format. +// Keyboard Shortcuts +pref("browser.closeShortcut.disabled", false); // false = Ctrl+W (or Cmd+W) closes tab/window (default behavior). true = disables this shortcut. + // Restart Menu (often provided by extensions or custom builds, not a standard Firefox feature) pref("browser.restart_menu.showpanelmenubtn", true); // If a restart menu is present, show its button in a panel menu. pref("browser.restart_menu.purgecache", false); // If true, purges caches upon restart initiated via this menu.