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:
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.