tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e3bbe153c40e5c5236b9f38777c17d3152867275
parent c1821b690103332c8838baff520f1a4db207b132
Author: Henry Wilkes <henry@torproject.org>
Date:   Wed,  4 Oct 2023 19:16:56 +0100

BB 41581: Hide NoScript extension's toolbar button by default.

This hides it from both the toolbar and the unified extensions panel.

We also hide the unified-extension-button if the panel would be empty:
not including the NoScript button when it is hidden. As a result, this
will be hidden by default until a user installs another extension (or
shows the NoScript button and unpins it).

Diffstat:
Mbrowser/base/content/browser-addons.js | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/components/extensions/parent/ext-browserAction.js | 16++++++++++++++++
Mtoolkit/mozapps/extensions/content/aboutaddons.html | 26++++++++++++++++++++++++++
Mtoolkit/mozapps/extensions/content/aboutaddons.js | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 223 insertions(+), 5 deletions(-)

diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js @@ -28,6 +28,9 @@ ChromeUtils.defineLazyGetter(lazy, "l10n", function () { ); }); +const HIDE_NO_SCRIPT_PREF = "extensions.hideNoScript"; +const HIDE_UNIFIED_WHEN_EMPTY_PREF = "extensions.hideUnifiedWhenEmpty"; + /** * Mapping of error code -> [ * error-id, @@ -2031,6 +2034,18 @@ var gUnifiedExtensions = { Glean.extensionsButton.prefersHiddenButton.set(!this.buttonAlwaysVisible); + // Listen out for changes in extensions.hideNoScript and + // extension.hideUnifiedWhenEmpty, which can effect the visibility of the + // unified-extensions-button. + // See tor-browser#41581. + this._hideNoScriptObserver = () => this._updateHideEmpty(); + Services.prefs.addObserver(HIDE_NO_SCRIPT_PREF, this._hideNoScriptObserver); + Services.prefs.addObserver( + HIDE_UNIFIED_WHEN_EMPTY_PREF, + this._hideNoScriptObserver + ); + this._updateHideEmpty(); // Will trigger updateButtonVisibility; + this._initialized = true; }, @@ -2052,6 +2067,15 @@ var gUnifiedExtensions = { gNavToolbox.removeEventListener("aftercustomization", this); CustomizableUI.removeListener(this); AddonManager.removeManagerListener(this); + + Services.prefs.removeObserver( + HIDE_NO_SCRIPT_PREF, + this._hideNoScriptObserver + ); + Services.prefs.removeObserver( + HIDE_UNIFIED_WHEN_EMPTY_PREF, + this._hideNoScriptObserver + ); }, _updateButtonBarListeners() { @@ -2081,10 +2105,14 @@ var gUnifiedExtensions = { }, onAppMenuShowing() { + // Only show the extension menu item if the extension button is not pinned + // and the extension popup is not empty. + // NOTE: This condition is different than _shouldShowButton. + const hideExtensionItem = this.buttonAlwaysVisible || this._hideEmpty; document.getElementById("appMenu-extensions-themes-button").hidden = - !this.buttonAlwaysVisible; + !hideExtensionItem; document.getElementById("appMenu-unified-extensions-button").hidden = - this.buttonAlwaysVisible; + hideExtensionItem; }, onLocationChange(browser, webProgress, _request, _uri, flags) { @@ -2099,9 +2127,14 @@ var gUnifiedExtensions = { }, updateButtonVisibility() { + if (this._hideEmpty === null) { + return; + } // TODO: Bug 1778684 - Auto-hide button when there is no active extension. + // Hide the extension button when it is empty. See tor-browser#41581. + // Likely will conflict with mozilla's Bug 1778684. See tor-browser#42635. let shouldShowButton = - this.buttonAlwaysVisible || + this._shouldShowButton || // If anything is anchored to the button, keep it visible. this._button.open || // Button will be open soon - see ensureButtonShownBeforeAttachingPanel. @@ -2127,7 +2160,7 @@ var gUnifiedExtensions = { }, ensureButtonShownBeforeAttachingPanel(panel) { - if (!this.buttonAlwaysVisible && !this._button.open) { + if (!this._shouldShowButton && !this._button.open) { // When the panel is anchored to the button, its "open" attribute will be // set, which visually renders as a "button pressed". Until we get there, // we need to make sure that the button is visible so that it can serve @@ -2141,7 +2174,7 @@ var gUnifiedExtensions = { if (this._button.open) { this._buttonShownBeforeButtonOpen = false; } - if (!this.buttonAlwaysVisible && !this._button.open) { + if (!this._shouldShowButton && !this._button.open) { this.updateButtonVisibility(); } }, @@ -2248,6 +2281,15 @@ var gUnifiedExtensions = { return false; } + // When an extensions is about to be removed, it may still appear in + // getActiveExtensions. + // This is needed for hasExtensionsInPanel, when called through + // onWidgetDestroy when an extension is being removed. + // See tor-browser#41581. + if (extension.hasShutdown) { + return false; + } + // Ignore hidden and extensions that cannot access the current window // (because of PB mode when we are in a private window), since users // cannot do anything with those extensions anyway. @@ -2267,6 +2309,38 @@ var gUnifiedExtensions = { }, /** + * Whether the extension button should be hidden because it is empty. Or + * `null` when uninitialised. + * + * @type {?boolean} + */ + _hideEmpty: null, + + /** + * Update the _hideEmpty attribute when the preference or hasExtensionsInPanel + * value may have changed. + */ + _updateHideEmpty() { + const prevHideEmpty = this._hideEmpty; + this._hideEmpty = + Services.prefs.getBoolPref(HIDE_UNIFIED_WHEN_EMPTY_PREF, true) && + !this.hasExtensionsInPanel(); + if (this._hideEmpty !== prevHideEmpty) { + this.updateButtonVisibility(); + } + }, + + /** + * Whether we should show the extension button, regardless of whether it is + * needed as a popup anchor, etc. + * + * @type {boolean} + */ + get _shouldShowButton() { + return this.buttonAlwaysVisible && !this._hideEmpty; + }, + + /** * Returns true when there are active extensions listed/shown in the unified * extensions panel, and false otherwise (e.g. when extensions are pinned in * the toolbar OR there are 0 active extensions). @@ -2277,7 +2351,11 @@ var gUnifiedExtensions = { * @returns {boolean} Whether there are extensions listed in the panel. */ hasExtensionsInPanel(policies = this.getActivePolicies()) { + const hideNoScript = Services.prefs.getBoolPref(HIDE_NO_SCRIPT_PREF, true); return policies.some(policy => { + if (hideNoScript && policy.extension?.isNoScript) { + return false; + } let widget = this.browserActionFor(policy)?.widget; return ( !widget || @@ -2984,7 +3062,20 @@ var gUnifiedExtensions = { } }, + onWidgetRemoved() { + // hasExtensionsInPanel may have changed. + this._updateHideEmpty(); + }, + + onWidgetDestroyed() { + // hasExtensionsInPanel may have changed. + this._updateHideEmpty(); + }, + onWidgetAdded(aWidgetId, aArea) { + // hasExtensionsInPanel may have changed. + this._updateHideEmpty(); + if (CustomizableUI.isWebExtensionWidget(aWidgetId)) { this.updateAttention(); } @@ -3010,6 +3101,9 @@ var gUnifiedExtensions = { }, onWidgetOverflow(aNode) { + // hasExtensionsInPanel may have changed. + this._updateHideEmpty(); + // We register a CUI listener for each window so we make sure that we // handle the event for the right window here. if (window !== aNode.ownerGlobal) { @@ -3020,6 +3114,9 @@ var gUnifiedExtensions = { }, onWidgetUnderflow(aNode) { + // hasExtensionsInPanel may have changed. + this._updateHideEmpty(); + // We register a CUI listener for each window so we make sure that we // handle the event for the right window here. if (window !== aNode.ownerGlobal) { diff --git a/browser/components/extensions/parent/ext-browserAction.js b/browser/components/extensions/parent/ext-browserAction.js @@ -281,6 +281,22 @@ this.browserAction = class extends ExtensionAPIPersistent { node.append(rowWrapper, messagebarWrapper); node.viewButton = button; + if (extension.isNoScript) { + // Hide NoScript by default. + // See tor-browser#41581. + const HIDE_NO_SCRIPT_PREF = "extensions.hideNoScript"; + const changeNoScriptVisibility = () => { + node.hidden = Services.prefs.getBoolPref(HIDE_NO_SCRIPT_PREF, true); + }; + // Since we expect the NoScript widget to only be destroyed on exit, + // we do not set up to remove the observer. + Services.prefs.addObserver( + HIDE_NO_SCRIPT_PREF, + changeNoScriptVisibility + ); + changeNoScriptVisibility(); + } + return node; }, diff --git a/toolkit/mozapps/extensions/content/aboutaddons.html b/toolkit/mozapps/extensions/content/aboutaddons.html @@ -541,6 +541,32 @@ <div class="addon-detail-mlmodel"> <addon-mlmodel-details></addon-mlmodel-details> </div> + <!-- Add an option to show the NoScript toolbar button, if this is the + - NoScript addon. See tor-browser#41581. --> + <div + class="addon-detail-row addon-detail-row-noscript-visibility" + role="radiogroup" + hidden="hidden" + > + <span + class="addon-noscript-visibility-label" + data-l10n-id="basebrowser-addon-noscript-visibility-label" + ></span> + <div class="addon-detail-actions"> + <label class="radio-container-with-text"> + <input type="radio" name="noscript-visibility" value="show" /> + <span + data-l10n-id="basebrowser-addon-noscript-visibility-show" + ></span> + </label> + <label class="radio-container-with-text"> + <input type="radio" name="noscript-visibility" value="hide" /> + <span + data-l10n-id="basebrowser-addon-noscript-visibility-hide" + ></span> + </label> + </div> + </div> <div class="addon-detail-row addon-detail-row-updates" role="group" diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js @@ -2220,6 +2220,8 @@ class AddonSitePermissionsList extends HTMLElement { } customElements.define("addon-sitepermissions-list", AddonSitePermissionsList); +const HIDE_NO_SCRIPT_PREF = "extensions.hideNoScript"; + class AddonDetails extends HTMLElement { connectedCallback() { if (!this.children.length) { @@ -2227,12 +2229,61 @@ class AddonDetails extends HTMLElement { } this.deck.addEventListener("view-changed", this); this.descriptionShowMoreButton.addEventListener("click", this); + + // If this is for the NoScript extension, we listen for changes in the + // visibility of its toolbar button. + // See tor-browser#41581. + // NOTE: The addon should be set before being connected, so isNoScript will + // return a correct value. + if (this.isNoScript && !this._noScriptVisibilityObserver) { + this._noScriptVisibilityObserver = () => this.updateNoScriptVisibility(); + Services.prefs.addObserver( + HIDE_NO_SCRIPT_PREF, + this._noScriptVisibilityObserver + ); + } } disconnectedCallback() { this.inlineOptions.destroyBrowser(); this.deck.removeEventListener("view-changed", this); this.descriptionShowMoreButton.removeEventListener("click", this); + + if (this._noScriptVisibilityObserver) { + Services.prefs.removeObserver( + HIDE_NO_SCRIPT_PREF, + this._noScriptVisibilityObserver + ); + // Clear in case this is called again, or if connectedCallback is called. + delete this._noScriptVisibilityObserver; + } + } + + /** + * Whether this is a description for the NoScript extension. + * + * @type {boolean} + */ + get isNoScript() { + return this.addon?.id === "{73a6fe31-595d-460b-a920-fcc0f8843232}"; + } + + /** + * Update the shown visibility value for the NoScript extension's toolbar + * button. + */ + updateNoScriptVisibility() { + if (!this.isNoScript) { + return; + } + const visibility = Services.prefs.getBoolPref(HIDE_NO_SCRIPT_PREF, true) + ? "hide" + : "show"; + for (const input of this.querySelectorAll( + ".addon-detail-row-noscript-visibility input" + )) { + input.checked = input.value === visibility; + } } handleEvent(e) { @@ -2459,6 +2510,27 @@ class AddonDetails extends HTMLElement { "upgrade" ); + // If this is the NoScript extension, we want to show an option to change + // the visibility of its toolbar button. + // See tor-browser#41581. + const visibilityRow = this.querySelector( + ".addon-detail-row-noscript-visibility" + ); + visibilityRow.hidden = !this.isNoScript; + if (this.isNoScript) { + // Set up the aria-label for the role="radiogroup". + const visibilityLabel = visibilityRow.querySelector( + ".addon-noscript-visibility-label" + ); + visibilityLabel.id = ExtensionCommon.makeWidgetId( + `${addon.id}-noscript-visibility-label` + ); + visibilityRow.setAttribute("aria-labelledby", visibilityLabel.id); + + // Set the initial displayed value. + this.updateNoScriptVisibility(); + } + if (addon.type != "extension") { // Don't show any private browsing related section for non-extension // addon types, because not relevant or they are either always allowed @@ -2854,6 +2926,13 @@ class AddonCard extends HTMLElement { addon.quarantineIgnoredByUser = e.target.value == "1"; break; } + case "noscript-visibility": { + // Update the NoScript toolbar button visibility. + // See tor-browser#41581. + const hide = e.target.value !== "show"; + Services.prefs.setBoolPref(HIDE_NO_SCRIPT_PREF, hide); + break; + } } } else if (e.type == "mousedown") { // Open panel on mousedown when the mouse is used.