Commit e3bbe153 authored by henry's avatar henry Committed by Dan Ballard
Browse files

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).
parent c1821b69
Loading
Loading
Loading
Loading
+102 −5
Original line number Original line Diff line number Diff line
@@ -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 -> [
 * Mapping of error code -> [
 *   error-id,
 *   error-id,
@@ -2031,6 +2034,18 @@ var gUnifiedExtensions = {


    Glean.extensionsButton.prefersHiddenButton.set(!this.buttonAlwaysVisible);
    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;
    this._initialized = true;
  },
  },


@@ -2052,6 +2067,15 @@ var gUnifiedExtensions = {
    gNavToolbox.removeEventListener("aftercustomization", this);
    gNavToolbox.removeEventListener("aftercustomization", this);
    CustomizableUI.removeListener(this);
    CustomizableUI.removeListener(this);
    AddonManager.removeManagerListener(this);
    AddonManager.removeManagerListener(this);

    Services.prefs.removeObserver(
      HIDE_NO_SCRIPT_PREF,
      this._hideNoScriptObserver
    );
    Services.prefs.removeObserver(
      HIDE_UNIFIED_WHEN_EMPTY_PREF,
      this._hideNoScriptObserver
    );
  },
  },


  _updateButtonBarListeners() {
  _updateButtonBarListeners() {
@@ -2081,10 +2105,14 @@ var gUnifiedExtensions = {
  },
  },


  onAppMenuShowing() {
  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 =
    document.getElementById("appMenu-extensions-themes-button").hidden =
      !this.buttonAlwaysVisible;
      !hideExtensionItem;
    document.getElementById("appMenu-unified-extensions-button").hidden =
    document.getElementById("appMenu-unified-extensions-button").hidden =
      this.buttonAlwaysVisible;
      hideExtensionItem;
  },
  },


  onLocationChange(browser, webProgress, _request, _uri, flags) {
  onLocationChange(browser, webProgress, _request, _uri, flags) {
@@ -2099,9 +2127,14 @@ var gUnifiedExtensions = {
  },
  },


  updateButtonVisibility() {
  updateButtonVisibility() {
    if (this._hideEmpty === null) {
      return;
    }
    // TODO: Bug 1778684 - Auto-hide button when there is no active extension.
    // 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 =
    let shouldShowButton =
      this.buttonAlwaysVisible ||
      this._shouldShowButton ||
      // If anything is anchored to the button, keep it visible.
      // If anything is anchored to the button, keep it visible.
      this._button.open ||
      this._button.open ||
      // Button will be open soon - see ensureButtonShownBeforeAttachingPanel.
      // Button will be open soon - see ensureButtonShownBeforeAttachingPanel.
@@ -2127,7 +2160,7 @@ var gUnifiedExtensions = {
  },
  },


  ensureButtonShownBeforeAttachingPanel(panel) {
  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
      // 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,
      // 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
      // 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) {
    if (this._button.open) {
      this._buttonShownBeforeButtonOpen = false;
      this._buttonShownBeforeButtonOpen = false;
    }
    }
    if (!this.buttonAlwaysVisible && !this._button.open) {
    if (!this._shouldShowButton && !this._button.open) {
      this.updateButtonVisibility();
      this.updateButtonVisibility();
    }
    }
  },
  },
@@ -2248,6 +2281,15 @@ var gUnifiedExtensions = {
        return false;
        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
      // Ignore hidden and extensions that cannot access the current window
      // (because of PB mode when we are in a private window), since users
      // (because of PB mode when we are in a private window), since users
      // cannot do anything with those extensions anyway.
      // cannot do anything with those extensions anyway.
@@ -2266,6 +2308,38 @@ var gUnifiedExtensions = {
    return policies;
    return policies;
  },
  },


  /**
   * 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
   * Returns true when there are active extensions listed/shown in the unified
   * extensions panel, and false otherwise (e.g. when extensions are pinned in
   * extensions panel, and false otherwise (e.g. when extensions are pinned in
@@ -2277,7 +2351,11 @@ var gUnifiedExtensions = {
   * @returns {boolean} Whether there are extensions listed in the panel.
   * @returns {boolean} Whether there are extensions listed in the panel.
   */
   */
  hasExtensionsInPanel(policies = this.getActivePolicies()) {
  hasExtensionsInPanel(policies = this.getActivePolicies()) {
    const hideNoScript = Services.prefs.getBoolPref(HIDE_NO_SCRIPT_PREF, true);
    return policies.some(policy => {
    return policies.some(policy => {
      if (hideNoScript && policy.extension?.isNoScript) {
        return false;
      }
      let widget = this.browserActionFor(policy)?.widget;
      let widget = this.browserActionFor(policy)?.widget;
      return (
      return (
        !widget ||
        !widget ||
@@ -2984,7 +3062,20 @@ var gUnifiedExtensions = {
    }
    }
  },
  },


  onWidgetRemoved() {
    // hasExtensionsInPanel may have changed.
    this._updateHideEmpty();
  },

  onWidgetDestroyed() {
    // hasExtensionsInPanel may have changed.
    this._updateHideEmpty();
  },

  onWidgetAdded(aWidgetId, aArea) {
  onWidgetAdded(aWidgetId, aArea) {
    // hasExtensionsInPanel may have changed.
    this._updateHideEmpty();

    if (CustomizableUI.isWebExtensionWidget(aWidgetId)) {
    if (CustomizableUI.isWebExtensionWidget(aWidgetId)) {
      this.updateAttention();
      this.updateAttention();
    }
    }
@@ -3010,6 +3101,9 @@ var gUnifiedExtensions = {
  },
  },


  onWidgetOverflow(aNode) {
  onWidgetOverflow(aNode) {
    // hasExtensionsInPanel may have changed.
    this._updateHideEmpty();

    // We register a CUI listener for each window so we make sure that we
    // We register a CUI listener for each window so we make sure that we
    // handle the event for the right window here.
    // handle the event for the right window here.
    if (window !== aNode.ownerGlobal) {
    if (window !== aNode.ownerGlobal) {
@@ -3020,6 +3114,9 @@ var gUnifiedExtensions = {
  },
  },


  onWidgetUnderflow(aNode) {
  onWidgetUnderflow(aNode) {
    // hasExtensionsInPanel may have changed.
    this._updateHideEmpty();

    // We register a CUI listener for each window so we make sure that we
    // We register a CUI listener for each window so we make sure that we
    // handle the event for the right window here.
    // handle the event for the right window here.
    if (window !== aNode.ownerGlobal) {
    if (window !== aNode.ownerGlobal) {
+16 −0
Original line number Original line Diff line number Diff line
@@ -281,6 +281,22 @@ this.browserAction = class extends ExtensionAPIPersistent {
        node.append(rowWrapper, messagebarWrapper);
        node.append(rowWrapper, messagebarWrapper);
        node.viewButton = button;
        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;
        return node;
      },
      },


+26 −0
Original line number Original line Diff line number Diff line
@@ -541,6 +541,32 @@
          <div class="addon-detail-mlmodel">
          <div class="addon-detail-mlmodel">
            <addon-mlmodel-details></addon-mlmodel-details>
            <addon-mlmodel-details></addon-mlmodel-details>
          </div>
          </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
          <div
            class="addon-detail-row addon-detail-row-updates"
            class="addon-detail-row addon-detail-row-updates"
            role="group"
            role="group"
+79 −0
Original line number Original line Diff line number Diff line
@@ -2220,6 +2220,8 @@ class AddonSitePermissionsList extends HTMLElement {
}
}
customElements.define("addon-sitepermissions-list", AddonSitePermissionsList);
customElements.define("addon-sitepermissions-list", AddonSitePermissionsList);


const HIDE_NO_SCRIPT_PREF = "extensions.hideNoScript";

class AddonDetails extends HTMLElement {
class AddonDetails extends HTMLElement {
  connectedCallback() {
  connectedCallback() {
    if (!this.children.length) {
    if (!this.children.length) {
@@ -2227,12 +2229,61 @@ class AddonDetails extends HTMLElement {
    }
    }
    this.deck.addEventListener("view-changed", this);
    this.deck.addEventListener("view-changed", this);
    this.descriptionShowMoreButton.addEventListener("click", 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() {
  disconnectedCallback() {
    this.inlineOptions.destroyBrowser();
    this.inlineOptions.destroyBrowser();
    this.deck.removeEventListener("view-changed", this);
    this.deck.removeEventListener("view-changed", this);
    this.descriptionShowMoreButton.removeEventListener("click", 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) {
  handleEvent(e) {
@@ -2459,6 +2510,27 @@ class AddonDetails extends HTMLElement {
      "upgrade"
      "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") {
    if (addon.type != "extension") {
      // Don't show any private browsing related section for non-extension
      // Don't show any private browsing related section for non-extension
      // addon types, because not relevant or they are either always allowed
      // 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";
          addon.quarantineIgnoredByUser = e.target.value == "1";
          break;
          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") {
    } else if (e.type == "mousedown") {
      // Open panel on mousedown when the mouse is used.
      // Open panel on mousedown when the mouse is used.