Bug 601667 - Web Console toolbar styling, part 1; f=mihai.sucan,ddahl r=l10n,sdwilsh,dao,kdangoor, a=blocking2.0
authorPatrick Walton <pwalton@mozilla.com>
Thu, 02 Dec 2010 06:50:29 -0400
changeset 58464 d48937dcda74b37ace70e85309a7c6d9f57ab0ec
parent 58463 b92216d0aea951e89dc0f92bebb649fe0e8a5e75
child 58465 841893070e47c0078dc1d46cf9d07b2d8190c403
push id17310
push userrcampbell@mozilla.com
push dateThu, 02 Dec 2010 11:04:57 +0000
treeherdermozilla-central@7212a3782e38 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersl10n, sdwilsh, dao, kdangoor, blocking2
bugs601667
milestone2.0b8pre
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
Bug 601667 - Web Console toolbar styling, part 1; f=mihai.sucan,ddahl r=l10n,sdwilsh,dao,kdangoor, a=blocking2.0
toolkit/components/console/hudservice/HUDService.jsm
toolkit/components/console/hudservice/tests/browser/Makefile.in
toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_581231_close_button.js
toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_601667_filter_buttons.js
--- a/toolkit/components/console/hudservice/HUDService.jsm
+++ b/toolkit/components/console/hudservice/HUDService.jsm
@@ -3188,53 +3188,72 @@ HeadsUpDisplay.prototype = {
 
   /**
    * Make the filter toolbar where we can toggle logging filters
    *
    * @returns nsIDOMNode
    */
   makeFilterToolbar: function HUD_makeFilterToolbar()
   {
-    let buttons = ["Network", "CSSParser", "Exception", "Error",
-                   "Info", "Warn", "Log",];
-
-    const pageButtons = [
-      { prefKey: "network", name: "PageNet" },
-      { prefKey: "cssparser", name: "PageCSS" },
-      { prefKey: "exception", name: "PageJS" }
-    ];
-    const consoleButtons = [
-      { prefKey: "error", name: "ConsoleErrors" },
-      { prefKey: "warn", name: "ConsoleWarnings" },
-      { prefKey: "info", name: "ConsoleInfo" },
-      { prefKey: "log", name: "ConsoleLog" }
+    const BUTTONS = [
+      {
+        name: "PageNet",
+        category: "net",
+        severities: [
+          { name: "ConsoleErrors", prefKey: "network" },
+          { name: "ConsoleLog", prefKey: "networkinfo" }
+        ]
+      },
+      {
+        name: "PageCSS",
+        category: "css",
+        severities: [
+          { name: "ConsoleErrors", prefKey: "csserror" },
+          { name: "ConsoleWarnings", prefKey: "cssparser" }
+        ]
+      },
+      {
+        name: "PageJS",
+        category: "js",
+        severities: [
+          { name: "ConsoleErrors", prefKey: "exception" },
+          { name: "ConsoleWarnings", prefKey: "jswarn" }
+        ]
+      },
+      {
+        name: "PageWebDeveloper",
+        category: "webdev",
+        severities: [
+          { name: "ConsoleErrors", prefKey: "error" },
+          { name: "ConsoleWarnings", prefKey: "warn" },
+          { name: "ConsoleInfo", prefKey: "info" },
+          { name: "ConsoleLog", prefKey: "log" }
+        ]
+      }
     ];
 
     let toolbar = this.makeXULNode("toolbar");
     toolbar.setAttribute("class", "hud-console-filter-toolbar");
-    toolbar.setAttribute("mode", "text");
-
-    let pageCategoryTitle = this.getStr("categoryPage");
-    this.addButtonCategory(toolbar, pageCategoryTitle, pageButtons);
-
-    let separator = this.makeXULNode("separator");
-    separator.setAttribute("orient", "vertical");
-    toolbar.appendChild(separator);
-
-    let consoleCategoryTitle = this.getStr("categoryConsole");
-    this.addButtonCategory(toolbar, consoleCategoryTitle, consoleButtons);
+    toolbar.setAttribute("mode", "full");
+
+    this.makeCloseButton(toolbar);
+
+    for (let i = 0; i < BUTTONS.length; i++) {
+      this.makeFilterButton(toolbar, BUTTONS[i]);
+    }
 
     toolbar.appendChild(this.filterSpacer);
     toolbar.appendChild(this.filterBox);
+    this.makeClearConsoleButton(toolbar);
+
     return toolbar;
   },
 
   /**
-   * Creates the context menu on the console, which contains the "clear
-   * console" functionality.
+   * Creates the context menu on the console.
    *
    * @param nsIDOMNode aOutputNode
    *        The console output DOM node.
    * @returns void
    */
   createConsoleMenu: function HUD_createConsoleMenu(aConsoleWrapper) {
     let menuPopup = this.makeXULNode("menupopup");
     let id = this.hudId + "-output-contextmenu";
@@ -3262,89 +3281,119 @@ HeadsUpDisplay.prototype = {
     selectAllItem.setAttribute("label", this.getStr("selectAllCmd.label"));
     selectAllItem.setAttribute("accesskey",
                                this.getStr("selectAllCmd.accesskey"));
     selectAllItem.setAttribute("hudId", this.hudId);
     selectAllItem.setAttribute("buttonType", "selectAll");
     selectAllItem.setAttribute("oncommand", "HUDConsoleUI.command(this);");
     menuPopup.appendChild(selectAllItem);
 
-    menuPopup.appendChild(this.makeXULNode("menuseparator"));
-
-    let clearItem = this.makeXULNode("menuitem");
-    clearItem.setAttribute("label", this.getStr("clearConsoleCmd.label"));
-    clearItem.setAttribute("accesskey",
-                           this.getStr("clearConsoleCmd.accesskey"));
-    clearItem.setAttribute("hudId", this.hudId);
-    clearItem.setAttribute("buttonType", "clear");
-    clearItem.setAttribute("oncommand", "HUDConsoleUI.command(this);");
-    menuPopup.appendChild(clearItem);
-
     aConsoleWrapper.appendChild(menuPopup);
     aConsoleWrapper.setAttribute("context", id);
   },
 
-  makeButton: function HUD_makeButton(aName, aPrefKey, aType)
+  /**
+   * Creates one of the filter buttons on the toolbar.
+   *
+   * @param nsIDOMNode aParent
+   *        The node to which the filter button should be appended.
+   * @param object aDescriptor
+   *        A descriptor that contains info about the button. Contains "name",
+   *        "category", and "prefKey" properties, and optionally a "severities"
+   *        property.
+   * @return void
+   */
+  makeFilterButton: function HUD_makeFilterButton(aParent, aDescriptor)
   {
-    var self = this;
-    let prefKey = aPrefKey;
-
-    let btn;
-    if (aType == "checkbox") {
-      btn = this.makeXULNode("checkbox");
-      btn.setAttribute("type", aType);
-    } else {
-      btn = this.makeXULNode("toolbarbutton");
+    let toolbarButton = this.makeXULNode("toolbarbutton");
+    aParent.appendChild(toolbarButton);
+
+    let toggleFilter = HeadsUpDisplayUICommands.toggleFilter;
+    toolbarButton.addEventListener("click", toggleFilter, false);
+
+    let name = aDescriptor.name;
+    toolbarButton.setAttribute("type", "menu-button");
+    toolbarButton.setAttribute("label", this.getStr("btn" + name));
+    toolbarButton.setAttribute("tooltip", this.getStr("tip" + name));
+    toolbarButton.setAttribute("category", aDescriptor.category);
+    toolbarButton.setAttribute("hudId", this.hudId);
+    toolbarButton.classList.add("webconsole-filter-button");
+
+    let menuPopup = this.makeXULNode("menupopup");
+    toolbarButton.appendChild(menuPopup);
+
+    let allChecked = true;
+    for (let i = 0; i < aDescriptor.severities.length; i++) {
+      let severity = aDescriptor.severities[i];
+      let menuItem = this.makeXULNode("menuitem");
+      menuItem.setAttribute("label", this.getStr("btn" + severity.name));
+      menuItem.setAttribute("type", "checkbox");
+      menuItem.setAttribute("autocheck", "false");
+      menuItem.setAttribute("hudId", this.hudId);
+
+      let prefKey = severity.prefKey;
+      menuItem.setAttribute("prefKey", prefKey);
+
+      let checked = this.filterPrefs[prefKey];
+      menuItem.setAttribute("checked", checked);
+      if (!checked) {
+        allChecked = false;
+      }
+
+      menuItem.addEventListener("command", toggleFilter, false);
+
+      menuPopup.appendChild(menuItem);
     }
 
-    btn.setAttribute("hudId", this.hudId);
-    btn.setAttribute("buttonType", prefKey);
-    btn.setAttribute("class", "hud-filter-btn");
-    let key = "btn" + aName;
-    btn.setAttribute("label", this.getStr(key));
-    key = "tip" + aName;
-    btn.setAttribute("tooltip", this.getStr(key));
-
-    if (aType == "checkbox") {
-      btn.setAttribute("checked", this.filterPrefs[prefKey]);
-      function toggle(btn) {
-        self.consoleFilterCommands.toggle(btn);
-      };
-
-      btn.setAttribute("oncommand", "HUDConsoleUI.toggleFilter(this);");
-    }
-    else {
-      var command = "HUDConsoleUI.command(this)";
-      btn.setAttribute("oncommand", command);
-    }
-    return btn;
+    toolbarButton.setAttribute("checked", allChecked);
   },
 
   /**
-   * Appends a category title and a series of buttons to the filter bar.
+   * Creates the close button on the toolbar.
    *
-   * @param nsIDOMNode aToolbar
-   *        The DOM node to which to add the category.
-   * @param string aTitle
-   *        The title for the category.
-   * @param Array aButtons
-   *        The buttons, specified as objects with "name" and "prefKey"
-   *        properties.
-   * @returns nsIDOMNode
+   * @param nsIDOMNode aParent
+   *        The toolbar to attach the close button to.
+   * @return void
    */
-  addButtonCategory: function(aToolbar, aTitle, aButtons) {
-    let lbl = this.makeXULNode("label");
-    lbl.setAttribute("class", "hud-filter-cat");
-    lbl.setAttribute("value", aTitle);
-    aToolbar.appendChild(lbl);
-
-    for (let i = 0; i < aButtons.length; i++) {
-      let btn = aButtons[i];
-      aToolbar.appendChild(this.makeButton(btn.name, btn.prefKey, "checkbox"));
+  makeCloseButton: function HUD_makeCloseButton(aToolbar)
+  {
+    function HUD_closeButton_onCommand() {
+      let tab = this.ownerDocument.defaultView.gBrowser.selectedTab;
+      HUDService.deactivateHUDForContext(tab);
     }
+
+    let closeButton = this.makeXULNode("toolbarbutton");
+    closeButton.classList.add("webconsole-close-button");
+    closeButton.addEventListener("command", HUD_closeButton_onCommand, false);
+
+    aToolbar.appendChild(closeButton);
+  },
+
+  /**
+   * Creates the "Clear Console" button.
+   *
+   * @param nsIDOMNode aParent
+   *        The toolbar to attach the "Clear Console" button to.
+   * @param string aHUDId
+   *        The ID of the console.
+   * @return void
+   */
+  makeClearConsoleButton: function HUD_makeClearConsoleButton(aToolbar)
+  {
+    let hudId = this.hudId;
+    function HUD_clearButton_onCommand() {
+      HUDService.clearDisplay(hudId);
+    }
+
+    let clearButton = this.makeXULNode("toolbarbutton");
+    clearButton.setAttribute("label", this.getStr("btnClear"));
+    clearButton.classList.add("webconsole-clear-console-button");
+    clearButton.addEventListener("command", HUD_clearButton_onCommand, false);
+
+    aToolbar.appendChild(clearButton);
   },
 
   createHUD: function HUD_createHUD()
   {
     let self = this;
     if (this.HUDBox) {
       return this.HUDBox;
     }
@@ -4568,22 +4617,16 @@ JSTermFirefoxMixin.prototype = {
 
     let inputNode = this.xulElementFactory("textbox");
     inputNode.setAttribute("class", "jsterm-input-node");
     inputNode.setAttribute("flex", "1");
     inputNode.setAttribute("multiline", "true");
     inputNode.setAttribute("rows", "1");
     inputContainer.appendChild(inputNode);
 
-    let closeButton = this.xulElementFactory("button");
-    closeButton.setAttribute("class", "jsterm-close-button");
-    inputContainer.appendChild(closeButton);
-    closeButton.addEventListener("command", HeadsUpDisplayUICommands.toggleHUD,
-                                 false);
-
     if (this.existingConsoleNode == undefined) {
       // create elements
       let term = this.xulElementFactory("vbox");
       term.setAttribute("class", "jsterm-wrapper-node");
       term.setAttribute("flex", "1");
 
       let outputNode = this.xulElementFactory("vbox");
       outputNode.setAttribute("class", "jsterm-output-node");
@@ -4899,37 +4942,94 @@ HeadsUpDisplayUICommands = {
       });
     }
     else {
       HUDService.activateHUDForContext(gBrowser.selectedTab, true);
       HUDService.animate(hudId, ANIMATE_IN);
     }
   },
 
-  toggleFilter: function UIC_toggleFilter(aButton) {
-    var filter = aButton.getAttribute("buttonType");
-    var hudId = aButton.getAttribute("hudId");
-    var state = HUDService.getFilterState(hudId, filter);
-    if (state) {
-      HUDService.setFilterState(hudId, filter, false);
-      aButton.setAttribute("checked", false);
+  /**
+   * The event handler that is called whenever a user switches a filter on or
+   * off.
+   *
+   * @param nsIDOMEvent aEvent
+   *        The event that triggered the filter change.
+   * @return boolean
+   */
+  toggleFilter: function UIC_toggleFilter(aEvent) {
+    let hudId = this.getAttribute("hudId");
+    switch (this.tagName) {
+      case "toolbarbutton": {
+        let originalTarget = aEvent.originalTarget;
+        let classes = originalTarget.classList;
+
+        if (originalTarget.localName !== "toolbarbutton") {
+          // Oddly enough, the click event is sent to the menu button when
+          // selecting a menu item with the mouse. Detect this case and bail
+          // out.
+          break;
+        }
+
+        if (!classes.contains("toolbarbutton-menubutton-button") &&
+            originalTarget.getAttribute("type") === "menu-button") {
+          // This is a filter button with a drop-down. The user clicked the
+          // drop-down, so do nothing. (The menu will automatically appear
+          // without our intervention.)
+          break;
+        }
+
+        let state = this.getAttribute("checked") !== "true";
+        this.setAttribute("checked", state);
+
+        // This is a filter button with a drop-down, and the user clicked the
+        // main part of the button. Go through all the severities and toggle
+        // their associated filters.
+        let menuItems = this.querySelectorAll("menuitem");
+        for (let i = 0; i < menuItems.length; i++) {
+          menuItems[i].setAttribute("checked", state);
+          let prefKey = menuItems[i].getAttribute("prefKey");
+          HUDService.setFilterState(hudId, prefKey, state);
+        }
+        break;
+      }
+
+      case "menuitem": {
+        let state = this.getAttribute("checked") !== "true";
+        this.setAttribute("checked", state);
+
+        let prefKey = this.getAttribute("prefKey");
+        HUDService.setFilterState(hudId, prefKey, state);
+
+        // Adjust the state of the button appropriately.
+        let menuPopup = this.parentNode;
+
+        let allChecked = true;
+        let menuItem = menuPopup.firstChild;
+        while (menuItem) {
+          if (menuItem.getAttribute("checked") !== "true") {
+            allChecked = false;
+            break;
+          }
+          menuItem = menuItem.nextSibling;
+        }
+
+        let toolbarButton = menuPopup.parentNode;
+        toolbarButton.setAttribute("checked", allChecked);
+        break;
+      }
     }
-    else {
-      HUDService.setFilterState(hudId, filter, true);
-      aButton.setAttribute("checked", true);
-    }
+
+    return true;
   },
 
   command: function UIC_command(aButton) {
     var filter = aButton.getAttribute("buttonType");
     var hudId = aButton.getAttribute("hudId");
     switch (filter) {
-      case "clear":
-        HUDService.clearDisplay(hudId);
-        break;
       case "selectAll":
         let outputNode = HUDService.getOutputNodeById(hudId);
         let chromeWindow = outputNode.ownerDocument.defaultView;
         let commandController = chromeWindow.commandController;
         commandController.selectAll(outputNode);
         break;
       case "saveBodies": {
         let checked = aButton.getAttribute("checked") === "true";
@@ -4946,18 +5046,21 @@ HeadsUpDisplayUICommands = {
 //////////////////////////////////////////////////////////////////////////
 
 var prefs = Services.prefs;
 
 const GLOBAL_STORAGE_INDEX_ID = "GLOBAL_CONSOLE";
 const PREFS_BRANCH_PREF = "devtools.hud.display.filter";
 const PREFS_PREFIX = "devtools.hud.display.filter.";
 const PREFS = { network: PREFS_PREFIX + "network",
+                networkinfo: PREFS_PREFIX + "networkinfo",
+                csserror: PREFS_PREFIX + "csserror",
                 cssparser: PREFS_PREFIX + "cssparser",
                 exception: PREFS_PREFIX + "exception",
+                jswarn: PREFS_PREFIX + "jswarn",
                 error: PREFS_PREFIX + "error",
                 info: PREFS_PREFIX + "info",
                 warn: PREFS_PREFIX + "warn",
                 log: PREFS_PREFIX + "log",
                 global: PREFS_PREFIX + "global",
               };
 
 function ConsoleStorage()
@@ -4988,58 +5091,71 @@ function ConsoleStorage()
 
   // TODO: for FINAL release,
   // use the sitePreferencesService to save specific site prefs
   // see bug 570545
 
   if (filterPrefs) {
     defaultDisplayPrefs = {
       network: (prefs.getBoolPref(PREFS.network) ? true: false),
+      networkinfo: (prefs.getBoolPref(PREFS.networkinfo) ? true: false),
+      csserror: (prefs.getBoolPref(PREFS.csserror) ? true: false),
       cssparser: (prefs.getBoolPref(PREFS.cssparser) ? true: false),
       exception: (prefs.getBoolPref(PREFS.exception) ? true: false),
+      jswarn: (prefs.getBoolPref(PREFS.jswarn) ? true: false),
       error: (prefs.getBoolPref(PREFS.error) ? true: false),
       info: (prefs.getBoolPref(PREFS.info) ? true: false),
       warn: (prefs.getBoolPref(PREFS.warn) ? true: false),
       log: (prefs.getBoolPref(PREFS.log) ? true: false),
       global: (prefs.getBoolPref(PREFS.global) ? true: false),
     };
   }
   else {
     prefs.setBoolPref(PREFS_BRANCH_PREF, false);
     // default prefs for each HeadsUpDisplay
     prefs.setBoolPref(PREFS.network, true);
+    prefs.setBoolPref(PREFS.networkinfo, true);
+    prefs.setBoolPref(PREFS.csserror, true);
     prefs.setBoolPref(PREFS.cssparser, true);
     prefs.setBoolPref(PREFS.exception, true);
+    prefs.setBoolPref(PREFS.jswarn, true);
     prefs.setBoolPref(PREFS.error, true);
     prefs.setBoolPref(PREFS.info, true);
     prefs.setBoolPref(PREFS.warn, true);
     prefs.setBoolPref(PREFS.log, true);
     prefs.setBoolPref(PREFS.global, false);
 
     defaultDisplayPrefs = {
       network: prefs.getBoolPref(PREFS.network),
+      networkinfo: prefs.getBoolPref(PREFS.networkinfo),
+      csserror: prefs.getBoolPref(PREFS.csserror),
       cssparser: prefs.getBoolPref(PREFS.cssparser),
       exception: prefs.getBoolPref(PREFS.exception),
+      jswarn: prefs.getBoolPref(PREFS.jswarn),
       error: prefs.getBoolPref(PREFS.error),
       info: prefs.getBoolPref(PREFS.info),
       warn: prefs.getBoolPref(PREFS.warn),
       log: prefs.getBoolPref(PREFS.log),
       global: prefs.getBoolPref(PREFS.global),
     };
   }
   this.defaultDisplayPrefs = defaultDisplayPrefs;
 }
 
 ConsoleStorage.prototype = {
 
   updateDefaultDisplayPrefs:
   function CS_updateDefaultDisplayPrefs(aPrefsObject) {
     prefs.setBoolPref(PREFS.network, (aPrefsObject.network ? true : false));
+    prefs.setBoolPref(PREFS.networkinfo,
+                      (aPrefsObject.networkinfo ? true : false));
+    prefs.setBoolPref(PREFS.csserror, (aPrefsObject.csserror ? true : false));
     prefs.setBoolPref(PREFS.cssparser, (aPrefsObject.cssparser ? true : false));
     prefs.setBoolPref(PREFS.exception, (aPrefsObject.exception ? true : false));
+    prefs.setBoolPref(PREFS.jswarn, (aPrefsObject.jswarn ? true : false));
     prefs.setBoolPref(PREFS.error, (aPrefsObject.error ? true : false));
     prefs.setBoolPref(PREFS.info, (aPrefsObject.info ? true : false));
     prefs.setBoolPref(PREFS.warn, (aPrefsObject.warn ? true : false));
     prefs.setBoolPref(PREFS.log, (aPrefsObject.log ? true : false));
     prefs.setBoolPref(PREFS.global, (aPrefsObject.global ? true : false));
   },
 
   sequenceId: function CS_sequencerId()
--- a/toolkit/components/console/hudservice/tests/browser/Makefile.in
+++ b/toolkit/components/console/hudservice/tests/browser/Makefile.in
@@ -47,16 +47,17 @@ include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
 	browser_webconsole_bug_580030_errors_after_page_reload.js \
 	browser_webconsole_basic_net_logging.js \
 	browser_webconsole_bug_579412_input_focus.js \
 	browser_webconsole_bug_580001_closing_after_completion.js \
 	browser_webconsole_bug_580400_groups.js \
 	browser_webconsole_bug_588730_text_node_insertion.js \
+	browser_webconsole_bug_601667_filter_buttons.js \
 	browser_webconsole_bug_597136_external_script_errors.js \
 	browser_webconsole_bug_597136_network_requests_from_chrome.js \
 	browser_webconsole_completion.js \
 	browser_webconsole_console_logging_api.js \
 	browser_webconsole_consoleonpage.js \
 	browser_webconsole_chrome.js \
 	browser_webconsole_display_accessors.js \
 	browser_webconsole_execution_scope.js \
--- a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_581231_close_button.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_581231_close_button.js
@@ -22,17 +22,17 @@ function testCloseButton() {
 
   openConsole();
 
   hudId = HUDService.displaysIndex()[0];
   hudBox = HUDService.getHeadsUpDisplay(hudId);
 
   HUDService.disableAnimation(hudId);
   executeSoon(function() {
-    let closeButton = hudBox.querySelector(".jsterm-close-button");
+    let closeButton = hudBox.querySelector(".webconsole-close-button");
     ok(closeButton != null, "we have the close button");
 
     // XXX: ASSERTION: ###!!! ASSERTION: XPConnect is being called on a scope without a 'Components' property!: 'Error', file /home/ddahl/code/moz/mozilla-central/mozilla-central/js/src/xpconnect/src/xpcwrappednativescope.cpp, line 795
 
     EventUtils.synthesizeMouse(closeButton, 0, 0, {});
 
     executeSoon(function (){
       ok(!(hudId in HUDService.windowRegistry), "the console is closed when " +
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_601667_filter_buttons.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the filter button UI logic works correctly.
+
+const TEST_URI = "http://example.com/";
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", testFilterButtons, true);
+}
+
+function testFilterButtons() {
+  browser.removeEventListener("load", testFilterButtons, true);
+  openConsole();
+
+  hudId = HUDService.displaysIndex()[0];
+  hudBox = HUDService.hudReferences[hudId].HUDBox;
+
+  testMenuFilterButton("net");
+  testMenuFilterButton("css");
+  testMenuFilterButton("js");
+  testMenuFilterButton("webdev");
+
+  finishTest();
+}
+
+function testMenuFilterButton(aCategory) {
+  let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+  let button = hudBox.querySelector(selector);
+  ok(button, "we have the \"" + aCategory + "\" button");
+
+  let firstMenuItem = button.querySelector("menuitem");
+  ok(firstMenuItem, "we have the first menu item for the \"" + aCategory +
+     "\" button");
+
+  // Turn all the filters off, if they were on.
+  let menuItem = firstMenuItem;
+  while (menuItem != null) {
+    if (isChecked(menuItem)) {
+      chooseMenuItem(menuItem);
+    }
+    menuItem = menuItem.nextSibling;
+  }
+
+  // Turn all the filters on; make sure the button gets checked.
+  menuItem = firstMenuItem;
+  let prefKey;
+  while (menuItem) {
+    prefKey = menuItem.getAttribute("prefKey");
+    chooseMenuItem(menuItem);
+    ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+       aCategory + " is checked after clicking it");
+    ok(HUDService.filterPrefs[hudId][prefKey], prefKey + " messages are " +
+       "on after clicking the appropriate menu item");
+    menuItem = menuItem.nextSibling;
+  }
+  ok(isChecked(button), "the button for category " + aCategory + " is " +
+     "checked after turning on all its menu items");
+
+  // Turn one filter off; make sure the button is no longer checked.
+  prefKey = firstMenuItem.getAttribute("prefKey");
+  chooseMenuItem(firstMenuItem);
+  ok(!isChecked(firstMenuItem), "the first menu item for category " +
+     aCategory + " is no longer checked after clicking it");
+  ok(!HUDService.filterPrefs[hudId][prefKey], prefKey + " messages are " +
+     "turned off after clicking the appropriate menu item");
+  ok(!isChecked(button), "the button for category " + aCategory + " is no " +
+     "longer checked after turning off its first menu item");
+
+  // Turn all the filters on by clicking the main part of the button.
+  let anonymousNodes = document.getAnonymousNodes(button);
+  let subbutton;
+  for (let i = 0; i < anonymousNodes.length; i++) {
+    let node = anonymousNodes[i];
+    if (node.classList.contains("toolbarbutton-menubutton-button")) {
+      subbutton = node;
+      break;
+    }
+  }
+  ok(subbutton, "we have the subbutton for category " + aCategory);
+
+  clickButton(subbutton);
+  ok(isChecked(button), "the button for category " + aCategory + " is " +
+     "checked after clicking its main part");
+
+  menuItem = firstMenuItem;
+  while (menuItem) {
+    let prefKey = menuItem.getAttribute("prefKey");
+    ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+       aCategory + " is checked after clicking the button");
+    ok(HUDService.filterPrefs[hudId][prefKey], prefKey + " messages are " +
+       "on after clicking the button");
+    menuItem = menuItem.nextSibling; i++;
+  }
+
+  // Turn all the filters off by clicking the main part of the button.
+  clickButton(subbutton);
+  ok(!isChecked(subbutton), "the button for category " + aCategory + " is " +
+     "no longer checked after clicking it");
+
+  menuItem = firstMenuItem;
+  while (menuItem) {
+    let prefKey = menuItem.getAttribute("prefKey");
+    ok(!isChecked(menuItem), "menu item " + prefKey + " for category " +
+       aCategory + " is no longer checked after clicking the button");
+    ok(!HUDService.filterPrefs[hudId][prefKey], prefKey + " messages are " +
+       "off after clicking the button");
+    menuItem = menuItem.nextSibling;
+  }
+
+  // Turn all the filters on again by clicking the button.
+  clickButton(subbutton);
+}
+
+function clickButton(aNode) {
+  EventUtils.sendMouseEvent({ type: "click" }, aNode);
+}
+
+function chooseMenuItem(aNode) {
+  let event = document.createEvent("XULCommandEvent");
+  event.initCommandEvent("command", true, true, window, 0, false, false, false,
+                         false, null);
+  aNode.dispatchEvent(event);
+}
+
+function isChecked(aNode) {
+  return aNode.getAttribute("checked") === "true";
+}
+