Backed out 2 changesets (bug 1482610) for mochitest failures on test_keycodes.xul. CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Tue, 14 Aug 2018 20:58:27 +0300
changeset 486635 ba4dba41c7a8836a75d38771a4c3f272d8fced93
parent 486634 69e6aadbb2299dd10a54759850d9cf6fc5b18585
child 486636 89ccbf0db23e336905be45b2fa4388f7e3ab6c90
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1482610
milestone63.0a1
backs out79a59b25e5354e991ba27af9aec9820fe172501a
f5873a719ec0ba10ca55a09c3a36320ee6abbedb
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1482610) for mochitest failures on test_keycodes.xul. CLOSED TREE Backed out changeset 79a59b25e535 (bug 1482610) Backed out changeset f5873a719ec0 (bug 1482610)
browser/base/content/browser-menubar.inc
browser/base/content/browser-sets.inc
browser/base/content/browser-sidebar.js
browser/base/content/browser.xul
browser/components/extensions/parent/ext-sidebarAction.js
browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -194,29 +194,25 @@
                               command="cmd_CustomizeToolbars"/>
                   </menupopup>
                 </menu>
                 <menu id="viewSidebarMenuMenu"
                       label="&viewSidebarMenu.label;"
                       accesskey="&viewSidebarMenu.accesskey;">
                   <menupopup id="viewSidebarMenu">
                     <menuitem id="menu_bookmarksSidebar"
-                              type="checkbox"
                               key="viewBookmarksSidebarKb"
-                              oncommand="SidebarUI.toggle('viewBookmarksSidebar');"
-                              label="&bookmarksButton.label;"/>
+                              observes="viewBookmarksSidebar"/>
                     <menuitem id="menu_historySidebar"
-                              type="checkbox"
                               key="key_gotoHistory"
-                              oncommand="SidebarUI.toggle('viewHistorySidebar');"
+                              observes="viewHistorySidebar"
                               label="&historyButton.label;"/>
                     <menuitem id="menu_tabsSidebar"
-                              type="checkbox"
                               class="sync-ui-item"
-                              oncommand="SidebarUI.toggle('viewTabsSidebar');"
+                              observes="viewTabsSidebar"
                               label="&syncedTabs.sidebar.label;"/>
                   </menupopup>
                 </menu>
                 <menuseparator/>
                 <menu id="viewFullZoomMenu" label="&fullZoom.label;"
                       accesskey="&fullZoom.accesskey;"
                       onpopupshowing="FullZoom.updateMenu();">
                   <menupopup>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -117,31 +117,45 @@
              label="&zoomWindow.label;"
              oncommand="zoomWindow();" />
 #endif
   </commandset>
 
 #include ../../components/places/content/placesCommands.inc.xul
 
   <broadcasterset id="mainBroadcasterSet">
+    <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
+                 type="checkbox" group="sidebar"
+                 sidebarurl="chrome://browser/content/places/bookmarksSidebar.xul"
+                 oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
+
+    <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
+                 type="checkbox" group="sidebar"
+                 sidebarurl="chrome://browser/content/places/historySidebar.xul"
+                 oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
+
     <broadcaster id="isImage"/>
     <broadcaster id="canViewSource"/>
     <broadcaster id="isFrameImage"/>
 
     <!-- Sync broadcasters -->
     <!-- A broadcaster of a number of attributes suitable for "sync now" UI -
         A 'syncstatus' attribute is set while actively syncing, and the label
         attribute which changes from "sync now" to "syncing" etc. -->
     <broadcaster id="sync-status" onmouseover="gSync.refreshSyncButtonsTooltip();"/>
     <!-- broadcasters of the "hidden" attribute to reflect setup state for
          menus -->
     <broadcaster id="sync-setup-state" hidden="true"/>
     <broadcaster id="sync-unverified-state" hidden="true"/>
     <broadcaster id="sync-syncnow-state" hidden="true"/>
     <broadcaster id="sync-reauth-state" hidden="true"/>
+    <broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
+                 type="checkbox" group="sidebar"
+                 sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
+                 oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
     <broadcaster id="workOfflineMenuitemState"/>
     <broadcaster id="devtoolsMenuBroadcaster_RecordExecution"
                  label="&devtoolsRecordExecution.label;"
                  command="Tools:RecordExecution"/>
     <broadcaster id="devtoolsMenuBroadcaster_SaveRecording"
                  label="&devtoolsSaveRecording.label;"
                  command="Tools:SaveRecording"/>
     <broadcaster id="devtoolsMenuBroadcaster_ReplayExecution"
--- a/browser/base/content/browser-sidebar.js
+++ b/browser/base/content/browser-sidebar.js
@@ -1,45 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * SidebarUI controls showing and hiding the browser sidebar.
+ *
+ * @note
+ * Some of these methods take a commandID argument - we expect to find a
+ * xul:broadcaster element with the specified ID.
+ * The following attributes on that element may be used and/or modified:
+ *  - id           (required) the string to match commandID. The convention
+ *                 is to use this naming scheme: 'view<sidebar-name>Sidebar'.
+ *  - sidebarurl   (required) specifies the URL to load in this sidebar.
+ *  - sidebartitle or label (in that order) specify the title to
+ *                 display on the sidebar.
+ *  - checked      indicates whether the sidebar is currently displayed.
+ *                 Note that this attribute is updated when
+ *                 the sidebar's visibility is changed.
+ *  - group        this attribute must be set to "sidebar".
  */
 var SidebarUI = {
-  get sidebars() {
-    if (this._sidebars) {
-      return this._sidebars;
-    }
-    return this._sidebars = new Map([
-      ["viewBookmarksSidebar", {
-        title: document.getElementById("sidebar-switcher-bookmarks")
-                       .getAttribute("label"),
-        url: "chrome://browser/content/places/bookmarksSidebar.xul",
-        menuId: "menu_bookmarksSidebar",
-        buttonId: "sidebar-switcher-bookmarks",
-      }],
-      ["viewHistorySidebar", {
-        title: document.getElementById("sidebar-switcher-history")
-                       .getAttribute("label"),
-        url: "chrome://browser/content/places/historySidebar.xul",
-        menuId: "menu_historySidebar",
-        buttonId: "sidebar-switcher-history",
-      }],
-      ["viewTabsSidebar", {
-        title: document.getElementById("sidebar-switcher-tabs")
-                       .getAttribute("label"),
-        url: "chrome://browser/content/syncedtabs/sidebar.xhtml",
-        menuId: "menu_tabsSidebar",
-        buttonId: "sidebar-switcher-tabs",
-      }],
-    ]);
-  },
-
   // Avoid getting the browser element from init() to avoid triggering the
   // <browser> constructor during startup if the sidebar is hidden.
   get browser() {
     if (this._browser)
       return this._browser;
     return this._browser = document.getElementById("sidebar");
   },
   POSITION_START_PREF: "sidebar.position_start",
@@ -236,17 +221,17 @@ var SidebarUI = {
 
     if (sourceUI._box.hidden) {
       // just hidden means we have adopted the hidden state.
       return true;
     }
 
     // dynamically generated sidebars will fail this check, but we still
     // consider it adopted.
-    if (!this.sidebars.has(commandID)) {
+    if (!document.getElementById(commandID)) {
       return true;
     }
 
     this._box.setAttribute("width", sourceUI._box.boxObject.width);
     this.showInitially(commandID);
 
     return true;
   },
@@ -276,17 +261,17 @@ var SidebarUI = {
 
     // If we're not adopting settings from a parent window, set them now.
     let wasOpen = this._box.getAttribute("checked");
     if (!wasOpen) {
       return;
     }
 
     let commandID = this._box.getAttribute("sidebarcommand");
-    if (commandID && this.sidebars.has(commandID)) {
+    if (commandID && document.getElementById(commandID)) {
       this.showInitially(commandID);
     } else {
       this._box.removeAttribute("checked");
       // Remove the |sidebarcommand| attribute, because the element it
       // refers to no longer exists, so we should assume this sidebar
       // panel has been uninstalled. (249883)
       // We use setAttribute rather than removeAttribute so it persists
       // correctly.
@@ -324,138 +309,169 @@ var SidebarUI = {
   /**
    * True if the sidebar is currently open.
    */
   get isOpen() {
     return !this._box.hidden;
   },
 
   /**
-   * The ID of the current sidebar.
+   * The ID of the current sidebar (ie, the ID of the broadcaster being used).
    */
   get currentID() {
     return this.isOpen ? this._box.getAttribute("sidebarcommand") : "";
   },
 
   get title() {
     return this._title.value;
   },
 
   set title(value) {
     this._title.value = value;
   },
 
+  getBroadcasterById(id) {
+    let sidebarBroadcaster = document.getElementById(id);
+    if (sidebarBroadcaster && sidebarBroadcaster.localName == "broadcaster") {
+      return sidebarBroadcaster;
+    }
+    return null;
+  },
+
   /**
    * Toggle the visibility of the sidebar. If the sidebar is hidden or is open
    * with a different commandID, then the sidebar will be opened using the
    * specified commandID. Otherwise the sidebar will be hidden.
    *
-   * @param  {string}  commandID     ID of the sidebar.
+   * @param  {string}  commandID     ID of the xul:broadcaster element to use.
    * @param  {DOMNode} [triggerNode] Node, usually a button, that triggered the
    *                                 visibility toggling of the sidebar.
    * @return {Promise}
    */
   toggle(commandID = this.lastOpenedId, triggerNode) {
     // First priority for a default value is this.lastOpenedId which is set during show()
     // and not reset in hide(), unlike currentID. If show() hasn't been called and we don't
     // have a persisted command either, or the command doesn't exist anymore, then
     // fallback to a default sidebar.
     if (!commandID) {
       commandID = this._box.getAttribute("sidebarcommand");
     }
-    if (!commandID || !this.sidebars.has(commandID)) {
+    if (!commandID || !this.getBroadcasterById(commandID)) {
       commandID = this.DEFAULT_SIDEBAR_ID;
     }
 
     if (this.isOpen && commandID == this.currentID) {
       this.hide(triggerNode);
       return Promise.resolve();
     }
     return this.show(commandID, triggerNode);
   },
 
-  _loadSidebarExtension(commandID) {
-    let sidebar = this.sidebars.get(commandID);
-    let {extensionId} = sidebar;
+  _loadSidebarExtension(sidebarBroadcaster) {
+    let extensionId = sidebarBroadcaster.getAttribute("extensionId");
     if (extensionId) {
-      SidebarUI.browser.contentWindow.loadPanel(extensionId, sidebar.panel,
-                                                sidebar.browserStyle);
+      let extensionUrl = sidebarBroadcaster.getAttribute("panel");
+      let browserStyle = sidebarBroadcaster.getAttribute("browserStyle");
+      SidebarUI.browser.contentWindow.loadPanel(extensionId, extensionUrl, browserStyle);
     }
   },
 
   /**
-   * Show the sidebar.
+   * Show the sidebar, using the parameters from the specified broadcaster.
+   * @see SidebarUI note.
    *
    * This wraps the internal method, including a ping to telemetry.
    *
-   * @param {string}  commandID     ID of the sidebar to use.
+   * @param {string}  commandID     ID of the xul:broadcaster element to use.
    * @param {DOMNode} [triggerNode] Node, usually a button, that triggered the
    *                                showing of the sidebar.
    */
   show(commandID, triggerNode) {
-    return this._show(commandID).then(() => {
-      this._loadSidebarExtension(commandID);
+    return this._show(commandID).then((sidebarBroadcaster) => {
+      this._loadSidebarExtension(sidebarBroadcaster);
 
       if (triggerNode) {
         updateToggleControlLabel(triggerNode);
       }
 
       this._fireFocusedEvent();
     });
   },
 
   /**
    * Show the sidebar, without firing the focused event or logging telemetry.
    * This is intended to be used when the sidebar is opened automatically
    * when a window opens (not triggered by user interaction).
    *
-   * @param {string} commandID ID of the sidebar.
+   * @param {string} commandID ID of the xul:broadcaster element to use.
    */
   showInitially(commandID) {
-    return this._show(commandID).then(() => {
-      this._loadSidebarExtension(commandID);
+    return this._show(commandID).then((sidebarBroadcaster) => {
+      this._loadSidebarExtension(sidebarBroadcaster);
     });
   },
 
   /**
    * Implementation for show. Also used internally for sidebars that are shown
    * when a window is opened and we don't want to ping telemetry.
    *
-   * @param {string} commandID ID of the sidebar.
+   * @param {string} commandID ID of the xul:broadcaster element to use.
    */
   _show(commandID) {
-    return new Promise(resolve => {
-      this.selectMenuItem(commandID);
+    return new Promise((resolve, reject) => {
+      let sidebarBroadcaster = this.getBroadcasterById(commandID);
+      if (!sidebarBroadcaster) {
+        reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
+        return;
+      }
+
+      let broadcasters = document.querySelectorAll("broadcaster[group=sidebar]");
+      for (let broadcaster of broadcasters) {
+        if (broadcaster != sidebarBroadcaster) {
+          broadcaster.removeAttribute("checked");
+        } else {
+          sidebarBroadcaster.setAttribute("checked", "true");
+        }
+      }
 
       this._box.hidden = this._splitter.hidden = false;
       this.setPosition();
 
       this.hideSwitcherPanel();
 
       this._box.setAttribute("checked", "true");
-      this._box.setAttribute("sidebarcommand", commandID);
-      this.lastOpenedId = commandID;
+      this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
+      this.lastOpenedId = sidebarBroadcaster.id;
+
+      let title = sidebarBroadcaster.getAttribute("sidebartitle") ||
+                  sidebarBroadcaster.getAttribute("label");
 
-      let {url, title} = this.sidebars.get(commandID);
-      this.title = title;
+      // When loading a web page in the sidebar there is no title set on the
+      // broadcaster, as it is instead set by openWebPanel. Don't clear out
+      // the title in this case.
+      if (title) {
+        this.title = title;
+      }
+
+      let url = sidebarBroadcaster.getAttribute("sidebarurl");
       this.browser.setAttribute("src", url); // kick off async load
 
       if (this.browser.contentDocument.location.href != url) {
         this.browser.addEventListener("load", event => {
           // We're handling the 'load' event before it bubbles up to the usual
           // (non-capturing) event handlers. Let it bubble up before resolving.
           setTimeout(() => {
-            resolve();
+            resolve(sidebarBroadcaster);
 
             // Now that the currentId is updated, fire a show event.
             this._fireShowEvent();
           }, 0);
         }, {capture: true, once: true});
       } else {
-        resolve();
+        resolve(sidebarBroadcaster);
 
         // Now that the currentId is updated, fire a show event.
         this._fireShowEvent();
       }
 
       let selBrowser = gBrowser.selectedBrowser;
       selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
         {commandID, isOpen: true}
@@ -472,56 +488,43 @@ var SidebarUI = {
   hide(triggerNode) {
     if (!this.isOpen) {
       return;
     }
 
     this.hideSwitcherPanel();
 
     let commandID = this._box.getAttribute("sidebarcommand");
-    this.selectMenuItem("");
+    let sidebarBroadcaster = document.getElementById(commandID);
+
+    if (sidebarBroadcaster.getAttribute("checked") != "true") {
+      return;
+    }
 
     // Replace the document currently displayed in the sidebar with about:blank
     // so that we can free memory by unloading the page. We need to explicitly
     // create a new content viewer because the old one doesn't get destroyed
     // until about:blank has loaded (which does not happen as long as the
     // element is hidden).
     this.browser.setAttribute("src", "about:blank");
     this.browser.docShell.createAboutBlankContentViewer(null);
 
+    sidebarBroadcaster.removeAttribute("checked");
     this._box.removeAttribute("checked");
     this._box.hidden = this._splitter.hidden = true;
 
     let selBrowser = gBrowser.selectedBrowser;
     selBrowser.focus();
     selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
       {commandID, isOpen: false}
     );
     if (triggerNode) {
       updateToggleControlLabel(triggerNode);
     }
   },
-
-  /**
-   * Sets the checked state only on the menu items of the specified sidebar, or
-   * none if the argument is an empty string.
-   */
-  selectMenuItem(commandID) {
-    for (let [id, {menuId, buttonId}] of this.sidebars) {
-      let menu = document.getElementById(menuId);
-      let button = document.getElementById(buttonId);
-      if (id == commandID) {
-        menu.setAttribute("checked", "true");
-        button.setAttribute("checked", "true");
-      } else {
-        menu.removeAttribute("checked");
-        button.removeAttribute("checked");
-      }
-    }
-  },
 };
 
 // Add getters related to the position here, since we will want them
 // available for both startDelayedLoad and init.
 XPCOMUtils.defineLazyPreferenceGetter(SidebarUI, "_positionStart",
   SidebarUI.POSITION_START_PREF, true, SidebarUI.setPosition.bind(SidebarUI));
 XPCOMUtils.defineLazyGetter(SidebarUI, "isRTL", () => {
   return getComputedStyle(document.documentElement).direction == "rtl";
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -312,35 +312,34 @@
            class="cui-widget-panel"
            role="group"
            type="arrow"
            hidden="true"
            flip="slide"
            orient="vertical"
            position="bottomcenter topleft">
       <toolbarbutton id="sidebar-switcher-bookmarks"
-                     type="checkbox"
-                     label="&bookmarksButton.label;"
                      class="subviewbutton subviewbutton-iconic"
                      key="viewBookmarksSidebarKb"
+                     observes="viewBookmarksSidebar"
                      oncommand="SidebarUI.show('viewBookmarksSidebar');">
         <observes element="viewBookmarksSidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarbutton id="sidebar-switcher-history"
-                     type="checkbox"
                      label="&historyButton.label;"
                      class="subviewbutton subviewbutton-iconic"
                      key="key_gotoHistory"
+                     observes="viewHistorySidebar"
                      oncommand="SidebarUI.show('viewHistorySidebar');">
         <observes element="viewHistorySidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarbutton id="sidebar-switcher-tabs"
-                     type="checkbox"
                      label="&syncedTabs.sidebar.label;"
                      class="subviewbutton subviewbutton-iconic sync-ui-item"
+                     observes="viewTabsSidebar"
                      oncommand="SidebarUI.show('viewTabsSidebar');">
         <observes element="viewTabsSidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarseparator/>
       <!-- Extension toolbarbuttons go here. -->
       <toolbarseparator id="sidebar-extensions-separator"/>
       <toolbarbutton id="sidebar-reverse-position"
                      class="subviewbutton"
@@ -1212,17 +1211,17 @@
 
   <deck id="content-deck" flex="1">
     <hbox flex="1" id="browser">
       <vbox id="browser-border-start" hidden="true" layer="true"/>
       <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
         <sidebarheader id="sidebar-header" align="center">
           <toolbarbutton id="sidebar-switcher-target" flex="1" class="tabbable">
             <image id="sidebar-icon" consumeanchor="sidebar-switcher-target"/>
-            <label id="sidebar-title" crop="end" flex="1" control="sidebar"/>
+            <label id="sidebar-title" persist="value" crop="end" flex="1" control="sidebar"/>
             <image id="sidebar-switcher-arrow"/>
           </toolbarbutton>
           <image id="sidebar-throbber"/>
 # To ensure the button label's intrinsic width doesn't expand the sidebar
 # if the label is long, the button needs flex=1.
 # To ensure the button doesn't expand unnecessarily for short labels, the
 # spacer should significantly out-flex the button.
           <spacer flex="1000"/>
--- a/browser/components/extensions/parent/ext-sidebarAction.js
+++ b/browser/components/extensions/parent/ext-sidebarAction.js
@@ -113,19 +113,22 @@ this.sidebarAction = class extends Exten
       let menu = document.getElementById(this.menuId);
       if (menu) {
         menu.remove();
       }
       let button = document.getElementById(this.buttonId);
       if (button) {
         button.remove();
       }
+      let broadcaster = document.getElementById(this.id);
+      if (broadcaster) {
+        broadcaster.remove();
+      }
       let header = document.getElementById("sidebar-switcher-target");
       header.removeEventListener("SidebarShown", this.updateHeader);
-      SidebarUI.sidebars.delete(this.id);
     }
     windowTracker.removeOpenListener(this.windowOpenListener);
     windowTracker.removeCloseListener(this.windowCloseListener);
   }
 
   build() {
     this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
                        (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
@@ -138,52 +141,56 @@ this.sidebarAction = class extends Exten
           SidebarUI.lastOpenedId == this.id) {
         SidebarUI.show(this.id);
       }
     }
   }
 
   createMenuItem(window, details) {
     let {document, SidebarUI} = window;
-    let keyId = `ext-key-id-${this.id}`;
 
-    SidebarUI.sidebars.set(this.id, {
-      title: details.title,
-      url: sidebarURL,
-      menuId: this.menuId,
-      buttonId: this.buttonId,
-      // The following properties are specific to extensions
-      extensionId: this.extension.id,
-      panel: details.panel,
-      browserStyle: this.browserStyle,
-    });
+    // Use of the broadcaster allows browser-sidebar.js to properly manage the
+    // checkmarks in the menus.
+    let broadcaster = document.createElementNS(XUL_NS, "broadcaster");
+    broadcaster.setAttribute("id", this.id);
+    broadcaster.setAttribute("autoCheck", "false");
+    broadcaster.setAttribute("type", "checkbox");
+    broadcaster.setAttribute("group", "sidebar");
+    broadcaster.setAttribute("label", details.title);
+    broadcaster.setAttribute("sidebarurl", sidebarURL);
+    broadcaster.setAttribute("panel", details.panel);
+    if (this.browserStyle) {
+      broadcaster.setAttribute("browserStyle", "true");
+    }
+    broadcaster.setAttribute("extensionId", this.extension.id);
+    let id = `ext-key-id-${this.id}`;
+    broadcaster.setAttribute("key", id);
+
+    // oncommand gets attached to menuitem, so we use the observes attribute to
+    // get the command id we pass to SidebarUI.
+    broadcaster.setAttribute("oncommand", "SidebarUI.toggle(this.getAttribute('observes'))");
 
     let header = document.getElementById("sidebar-switcher-target");
     header.addEventListener("SidebarShown", this.updateHeader);
 
     // Insert a menuitem for View->Show Sidebars.
     let menuitem = document.createElementNS(XUL_NS, "menuitem");
     menuitem.setAttribute("id", this.menuId);
-    menuitem.setAttribute("type", "checkbox");
-    menuitem.setAttribute("label", details.title);
-    menuitem.setAttribute("oncommand", `SidebarUI.toggle("${this.id}");`);
+    menuitem.setAttribute("observes", this.id);
     menuitem.setAttribute("class", "menuitem-iconic webextension-menuitem");
-    menuitem.setAttribute("key", keyId);
     this.setMenuIcon(menuitem, details);
 
     // Insert a toolbarbutton for the sidebar dropdown selector.
     let toolbarbutton = document.createElementNS(XUL_NS, "toolbarbutton");
     toolbarbutton.setAttribute("id", this.buttonId);
-    toolbarbutton.setAttribute("type", "checkbox");
-    toolbarbutton.setAttribute("label", details.title);
-    toolbarbutton.setAttribute("oncommand", `SidebarUI.show("${this.id}");`);
+    toolbarbutton.setAttribute("observes", this.id);
     toolbarbutton.setAttribute("class", "subviewbutton subviewbutton-iconic webextension-menuitem");
-    toolbarbutton.setAttribute("key", keyId);
     this.setMenuIcon(toolbarbutton, details);
 
+    document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
     document.getElementById("viewSidebarMenu").appendChild(menuitem);
     let separator = document.getElementById("sidebar-extensions-separator");
     separator.parentNode.insertBefore(toolbarbutton, separator);
     SidebarUI.updateShortcut({button: toolbarbutton});
 
     return menuitem;
   }
 
@@ -193,67 +200,71 @@ this.sidebarAction = class extends Exten
 
     menuitem.setAttribute("style", `
       --webextension-menuitem-image: url("${getIcon(16)}");
       --webextension-menuitem-image-2x: url("${getIcon(32)}");
     `);
   }
 
   /**
-   * Update the menu items with the tab context data in `tabData`.
+   * Update the broadcaster and menuitem `node` with the tab context data
+   * in `tabData`.
    *
    * @param {ChromeWindow} window
    *        Browser chrome window.
    * @param {object} tabData
    *        Tab specific sidebar configuration.
    */
   updateButton(window, tabData) {
     let {document, SidebarUI} = window;
     let title = tabData.title || this.extension.name;
     let menu = document.getElementById(this.menuId);
     if (!menu) {
       menu = this.createMenuItem(window, tabData);
     }
 
-    let urlChanged = tabData.panel !== SidebarUI.sidebars.get(this.id).panel;
+    // Update the broadcaster first, it will update both menus.
+    let broadcaster = document.getElementById(this.id);
+    broadcaster.setAttribute("tooltiptext", title);
+    broadcaster.setAttribute("label", title);
+
+    let urlChanged = tabData.panel !== broadcaster.getAttribute("panel");
     if (urlChanged) {
-      SidebarUI.sidebars.get(this.id).panel = tabData.panel;
+      broadcaster.setAttribute("panel", tabData.panel);
     }
 
-    menu.setAttribute("label", title);
     this.setMenuIcon(menu, tabData);
 
     let button = document.getElementById(this.buttonId);
-    button.setAttribute("label", title);
     this.setMenuIcon(button, tabData);
 
     // Update the sidebar if this extension is the current sidebar.
     if (SidebarUI.currentID === this.id) {
       SidebarUI.title = title;
       let header = document.getElementById("sidebar-switcher-target");
       this.setMenuIcon(header, tabData);
       if (SidebarUI.isOpen && urlChanged) {
         SidebarUI.show(this.id);
       }
     }
   }
 
   /**
-   * Update the menu items for a given window.
+   * Update the broadcaster and menuitem for a given window.
    *
    * @param {ChromeWindow} window
    *        Browser chrome window.
    */
   updateWindow(window) {
     let nativeTab = window.gBrowser.selectedTab;
     this.updateButton(window, this.tabContext.get(nativeTab));
   }
 
   /**
-   * Update the menu items when the extension changes the icon,
+   * Update the broadcaster and menuitem when the extension changes the icon,
    * title, url, etc. If it only changes a parameter for a single tab, `target`
    * will be that tab. If it only changes a parameter for a single window,
    * `target` will be that window. Otherwise `target` will be null.
    *
    * @param {XULElement|ChromeWindow|null} target
    *        Browser tab or browser chrome window, may be null.
    */
   updateOnChange(target) {
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
@@ -83,16 +83,19 @@ async function runTests(options) {
 
   let sidebarActionId;
   function checkDetails(details, windowId) {
     let {document} = Services.wm.getOuterWindowWithId(windowId);
     if (!sidebarActionId) {
       sidebarActionId = `${makeWidgetId(extension.id)}-sidebar-action`;
     }
 
+    let command = document.getElementById(sidebarActionId);
+    ok(command, "command exists");
+
     let menuId = `menu_${sidebarActionId}`;
     let menu = document.getElementById(menuId);
     ok(menu, "menu exists");
 
     let title = details.title || options.manifest.name;
 
     is(getListStyleImage(menu), details.icon, "icon URL is correct");
     is(menu.getAttribute("label"), title, "image label is correct");