| ... | ... | @@ -2026,13 +2026,13 @@ var gUnifiedExtensions = { | 
| 2026 | 2026 |      // extension.hideUnifiedWhenEmpty, which can effect the visibility of the
 | 
| 2027 | 2027 |      // unified-extensions-button.
 | 
| 2028 | 2028 |      // See tor-browser#41581.
 | 
| 2029 |  | -    this._hideNoScriptObserver = () => this._updateVisibility();
 | 
|  | 2029 | +    this._hideNoScriptObserver = () => this._updateHideEmpty();
 | 
| 2030 | 2030 |      Services.prefs.addObserver(HIDE_NO_SCRIPT_PREF, this._hideNoScriptObserver);
 | 
| 2031 | 2031 |      Services.prefs.addObserver(
 | 
| 2032 | 2032 |        HIDE_UNIFIED_WHEN_EMPTY_PREF,
 | 
| 2033 | 2033 |        this._hideNoScriptObserver
 | 
| 2034 | 2034 |      );
 | 
| 2035 |  | -    this._updateVisibility();
 | 
|  | 2035 | +    this._updateHideEmpty(); // Will trigger updateButtonVisibility;
 | 
| 2036 | 2036 |  
 | 
| 2037 | 2037 |      this._initialized = true;
 | 
| 2038 | 2038 |    },
 | 
| ... | ... | @@ -2071,10 +2071,14 @@ var gUnifiedExtensions = { | 
| 2071 | 2071 |    },
 | 
| 2072 | 2072 |  
 | 
| 2073 | 2073 |    onAppMenuShowing() {
 | 
|  | 2074 | +    // Only show the extension menu item if the extension button is not pinned
 | 
|  | 2075 | +    // and the extension popup is not empty.
 | 
|  | 2076 | +    // NOTE: This condition is different than _shouldShowButton.
 | 
|  | 2077 | +    const hideExtensionItem = this.buttonAlwaysVisible || this._hideEmpty;
 | 
| 2074 | 2078 |      document.getElementById("appMenu-extensions-themes-button").hidden =
 | 
| 2075 |  | -      !this.buttonAlwaysVisible;
 | 
|  | 2079 | +      !hideExtensionItem;
 | 
| 2076 | 2080 |      document.getElementById("appMenu-unified-extensions-button").hidden =
 | 
| 2077 |  | -      this.buttonAlwaysVisible;
 | 
|  | 2081 | +      hideExtensionItem;
 | 
| 2078 | 2082 |    },
 | 
| 2079 | 2083 |  
 | 
| 2080 | 2084 |    onLocationChange(browser, webProgress, _request, _uri, flags) {
 | 
| ... | ... | @@ -2089,9 +2093,14 @@ var gUnifiedExtensions = { | 
| 2089 | 2093 |    },
 | 
| 2090 | 2094 |  
 | 
| 2091 | 2095 |    updateButtonVisibility() {
 | 
|  | 2096 | +    if (this._hideEmpty === null) {
 | 
|  | 2097 | +      return;
 | 
|  | 2098 | +    }
 | 
| 2092 | 2099 |      // TODO: Bug 1778684 - Auto-hide button when there is no active extension.
 | 
|  | 2100 | +    // Hide the extension button when it is empty. See tor-browser#41581.
 | 
|  | 2101 | +    // Likely will conflict with mozilla's Bug 1778684. See tor-browser#42635.
 | 
| 2093 | 2102 |      let shouldShowButton =
 | 
| 2094 |  | -      this.buttonAlwaysVisible ||
 | 
|  | 2103 | +      this._shouldShowButton ||
 | 
| 2095 | 2104 |        // If anything is anchored to the button, keep it visible.
 | 
| 2096 | 2105 |        this._button.open ||
 | 
| 2097 | 2106 |        // Button will be open soon - see ensureButtonShownBeforeAttachingPanel.
 | 
| ... | ... | @@ -2117,7 +2126,7 @@ var gUnifiedExtensions = { | 
| 2117 | 2126 |    },
 | 
| 2118 | 2127 |  
 | 
| 2119 | 2128 |    ensureButtonShownBeforeAttachingPanel(panel) {
 | 
| 2120 |  | -    if (!this.buttonAlwaysVisible && !this._button.open) {
 | 
|  | 2129 | +    if (!this._shouldShowButton && !this._button.open) {
 | 
| 2121 | 2130 |        // When the panel is anchored to the button, its "open" attribute will be
 | 
| 2122 | 2131 |        // set, which visually renders as a "button pressed". Until we get there,
 | 
| 2123 | 2132 |        // we need to make sure that the button is visible so that it can serve
 | 
| ... | ... | @@ -2131,7 +2140,7 @@ var gUnifiedExtensions = { | 
| 2131 | 2140 |      if (this._button.open) {
 | 
| 2132 | 2141 |        this._buttonShownBeforeButtonOpen = false;
 | 
| 2133 | 2142 |      }
 | 
| 2134 |  | -    if (!this.buttonAlwaysVisible && !this._button.open) {
 | 
|  | 2143 | +    if (!this._shouldShowButton && !this._button.open) {
 | 
| 2135 | 2144 |        this.updateButtonVisibility();
 | 
| 2136 | 2145 |      }
 | 
| 2137 | 2146 |    },
 | 
| ... | ... | @@ -2261,18 +2270,35 @@ var gUnifiedExtensions = { | 
| 2261 | 2270 |    },
 | 
| 2262 | 2271 |  
 | 
| 2263 | 2272 |    /**
 | 
| 2264 |  | -   * Potentially hide the unified-extensions-button if it would be empty.
 | 
|  | 2273 | +   * Whether the extension button should be hidden because it is empty. Or
 | 
|  | 2274 | +   * `null` when uninitialised.
 | 
|  | 2275 | +   *
 | 
|  | 2276 | +   * @type {?boolean}
 | 
| 2265 | 2277 |     */
 | 
| 2266 |  | -  // See tor-browser#41581.
 | 
| 2267 |  | -  // The behaviour overlaps with a proposal in mozilla Bug 1778684, which has
 | 
| 2268 |  | -  // not yet been implemented as of June 2024 (start of ESR 128).
 | 
| 2269 |  | -  // See tor-browser#42635
 | 
| 2270 |  | -  _updateVisibility() {
 | 
| 2271 |  | -    this.button.classList.toggle(
 | 
| 2272 |  | -      "hide-empty",
 | 
|  | 2278 | +  _hideEmpty: null,
 | 
|  | 2279 | +
 | 
|  | 2280 | +  /**
 | 
|  | 2281 | +   * Update the _hideEmpty attribute when the preference or hasExtensionsInPanel
 | 
|  | 2282 | +   * value may have changed.
 | 
|  | 2283 | +   */
 | 
|  | 2284 | +  _updateHideEmpty() {
 | 
|  | 2285 | +    const prevHideEmpty = this._hideEmpty;
 | 
|  | 2286 | +    this._hideEmpty =
 | 
| 2273 | 2287 |        Services.prefs.getBoolPref(HIDE_UNIFIED_WHEN_EMPTY_PREF, true) &&
 | 
| 2274 |  | -        !this.hasExtensionsInPanel()
 | 
| 2275 |  | -    );
 | 
|  | 2288 | +      !this.hasExtensionsInPanel();
 | 
|  | 2289 | +    if (this._hideEmpty !== prevHideEmpty) {
 | 
|  | 2290 | +      this.updateButtonVisibility();
 | 
|  | 2291 | +    }
 | 
|  | 2292 | +  },
 | 
|  | 2293 | +
 | 
|  | 2294 | +  /**
 | 
|  | 2295 | +   * Whether we should show the extension button, regardless of whether it is
 | 
|  | 2296 | +   * needed as a popup anchor, etc.
 | 
|  | 2297 | +   *
 | 
|  | 2298 | +   * @type {boolean}
 | 
|  | 2299 | +   */
 | 
|  | 2300 | +  get _shouldShowButton() {
 | 
|  | 2301 | +    return this.buttonAlwaysVisible && !this._hideEmpty;
 | 
| 2276 | 2302 |    },
 | 
| 2277 | 2303 |  
 | 
| 2278 | 2304 |    /**
 | 
| ... | ... | @@ -2860,15 +2886,18 @@ var gUnifiedExtensions = { | 
| 2860 | 2886 |    },
 | 
| 2861 | 2887 |  
 | 
| 2862 | 2888 |    onWidgetRemoved() {
 | 
| 2863 |  | -    this._updateVisibility();
 | 
|  | 2889 | +    // hasExtensionsInPanel may have changed.
 | 
|  | 2890 | +    this._updateHideEmpty();
 | 
| 2864 | 2891 |    },
 | 
| 2865 | 2892 |  
 | 
| 2866 | 2893 |    onWidgetDestroyed() {
 | 
| 2867 |  | -    this._updateVisibility();
 | 
|  | 2894 | +    // hasExtensionsInPanel may have changed.
 | 
|  | 2895 | +    this._updateHideEmpty();
 | 
| 2868 | 2896 |    },
 | 
| 2869 | 2897 |  
 | 
| 2870 | 2898 |    onWidgetAdded(aWidgetId, aArea) {
 | 
| 2871 |  | -    this._updateVisibility();
 | 
|  | 2899 | +    // hasExtensionsInPanel may have changed.
 | 
|  | 2900 | +    this._updateHideEmpty();
 | 
| 2872 | 2901 |  
 | 
| 2873 | 2902 |      // When we pin a widget to the toolbar from a narrow window, the widget
 | 
| 2874 | 2903 |      // will be overflowed directly. In this case, we do not want to change the
 | 
| ... | ... | @@ -2885,7 +2914,8 @@ var gUnifiedExtensions = { | 
| 2885 | 2914 |    },
 | 
| 2886 | 2915 |  
 | 
| 2887 | 2916 |    onWidgetOverflow(aNode) {
 | 
| 2888 |  | -    this._updateVisibility();
 | 
|  | 2917 | +    // hasExtensionsInPanel may have changed.
 | 
|  | 2918 | +    this._updateHideEmpty();
 | 
| 2889 | 2919 |  
 | 
| 2890 | 2920 |      // We register a CUI listener for each window so we make sure that we
 | 
| 2891 | 2921 |      // handle the event for the right window here.
 | 
| ... | ... | @@ -2897,7 +2927,8 @@ var gUnifiedExtensions = { | 
| 2897 | 2927 |    },
 | 
| 2898 | 2928 |  
 | 
| 2899 | 2929 |    onWidgetUnderflow(aNode) {
 | 
| 2900 |  | -    this._updateVisibility();
 | 
|  | 2930 | +    // hasExtensionsInPanel may have changed.
 | 
|  | 2931 | +    this._updateHideEmpty();
 | 
| 2901 | 2932 |  
 | 
| 2902 | 2933 |      // We register a CUI listener for each window so we make sure that we
 | 
| 2903 | 2934 |      // handle the event for the right window here.
 |