Bug 760876 - Part 4: fix broken tests; r=robcee
authorMihai Sucan <mihai.sucan@gmail.com>
Tue, 10 Sep 2013 21:37:48 +0300
changeset 147060 e7c825ddd5408f05e1072c2edf882ea609ecd833
parent 147059 a4e288cfa8d3ad35b917874c2a1dd6813e73b821
child 147061 114177e01bda58bfa6c8c4f4c30412c9f6d0d4c7
push idunknown
push userunknown
push dateunknown
reviewersrobcee
bugs760876
milestone26.0a1
Bug 760876 - Part 4: fix broken tests; r=robcee
browser/devtools/commandline/test/browser_cmd_commands.js
browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
browser/devtools/webconsole/test/Makefile.in
browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
browser/devtools/webconsole/test/browser_cached_messages.js
browser/devtools/webconsole/test/browser_console.js
browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
browser/devtools/webconsole/test/browser_console_consolejsm_output.js
browser/devtools/webconsole/test/browser_console_dead_objects.js
browser/devtools/webconsole/test/browser_console_error_source_click.js
browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
browser/devtools/webconsole/test/browser_console_variables_view.js
browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js
browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
browser/devtools/webconsole/test/browser_longstring_hang.js
browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
browser/devtools/webconsole/test/browser_output_longstring_expand.js
browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
browser/devtools/webconsole/test/browser_result_format_as_string.js
browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js
browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js
browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js
browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js
browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js
browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js
browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js
browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js
browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js
browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js
browser/devtools/webconsole/test/browser_webconsole_bug_626484_output_copy_order.js
browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js
browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js
browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
browser/devtools/webconsole/test/browser_webconsole_bug_770099_bad_policyuri.js
browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
browser/devtools/webconsole/test/browser_webconsole_console_extras.js
browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
browser/devtools/webconsole/test/browser_webconsole_copying_multiple_messages_inserts_newlines_in_between.js
browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
browser/devtools/webconsole/test/browser_webconsole_for_of.js
browser/devtools/webconsole/test/browser_webconsole_js_input_and_output_styling.js
browser/devtools/webconsole/test/browser_webconsole_jsterm.js
browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js
browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
browser/devtools/webconsole/test/browser_webconsole_log_node_classes.js
browser/devtools/webconsole/test/browser_webconsole_message_node_id.js
browser/devtools/webconsole/test/browser_webconsole_null_and_undefined_output.js
browser/devtools/webconsole/test/browser_webconsole_output_order.js
browser/devtools/webconsole/test/browser_webconsole_view_source.js
browser/devtools/webconsole/test/head.js
browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html
browser/devtools/webconsole/test/test-network.html
browser/devtools/webconsole/test/test-result-format-as-string.html
--- a/browser/devtools/commandline/test/browser_cmd_commands.js
+++ b/browser/devtools/commandline/test/browser_cmd_commands.js
@@ -23,34 +23,33 @@ tests.testConsole = function(options) {
     subject.QueryInterface(Ci.nsISupportsString);
     hud = HUDService.getHudReferenceById(subject.data);
     ok(hud, "console open");
 
     hud.jsterm.execute("pprint(window)", onExecute);
   }
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
-  function onExecute () {
-    let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
-    ok(labels.length > 0, "output for pprint(window)");
+  function onExecute (msg) {
+    ok(msg, "output for pprint(window)");
 
     hud.jsterm.once("messages-cleared", onClear);
 
     helpers.audit(options, [
       {
         setup: "console clear",
         exec: {
           output: ""
         },
       }
     ]);
   }
 
   function onClear() {
-    let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
+    let labels = hud.outputNode.querySelectorAll(".message");
     is(labels.length, 0, "no output in console");
 
     helpers.audit(options, [
       {
         setup: "console close",
         exec: {
           output: ""
         },
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
@@ -45,22 +45,22 @@ function test()
   }, true);
 
   content.location = "data:text/html;charset=utf8,<p>test window focus for Scratchpad.";
 }
 
 function testFocus(sw, hud) {
   let sp = sw.Scratchpad;
 
-  function onMessage(subj) {
-    Services.obs.removeObserver(onMessage, "web-console-message-created");
+  function onMessage(event, messages) {
+    let msg = [...messages][0];
 
-    var loc = hud.jsterm.outputNode.querySelector(".webconsole-location");
+    var loc = msg.querySelector(".location");
     ok(loc, "location element exists");
-    is(loc.value, sw.Scratchpad.uniqueName + ":1",
+    is(loc.textContent.trim(), sw.Scratchpad.uniqueName + ":1",
         "location value is correct");
 
     sw.addEventListener("focus", function onFocus() {
       sw.removeEventListener("focus", onFocus, true);
 
       let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
 
       ok(win, "there are active Scratchpad windows");
@@ -74,17 +74,17 @@ function testFocus(sw, hud) {
     }, true);
 
     // Simulate a click on the "Scratchpad/N:1" link.
     EventUtils.synthesizeMouse(loc, 2, 2, {}, hud.iframeWindow);
   }
 
   // Sending messages to web console is an asynchronous operation. That's
   // why we have to setup an observer here.
-  Services.obs.addObserver(onMessage, "web-console-message-created", false);
+  hud.ui.once("messages-added", onMessage);
 
   sp.setText("console.log('foo');");
   sp.run().then(function ([selection, error, result]) {
     is(selection, "console.log('foo');", "selection is correct");
     is(error, undefined, "error is correct");
     is(result.type, "undefined", "result is correct");
   });
 }
--- a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -88,24 +88,27 @@ function test() {
       name: "button shows the page errors from tab 1",
       errors: 4,
       warnings: 1,
       callback: openWebConsole.bind(null, tab1, onWebConsoleOpen),
     });
   }
 
   function onWebConsoleOpen(hud) {
+    dump("lolz!!\n");
     waitForValue({
       name: "web console shows the page errors",
       validator: function() {
-        return hud.outputNode.querySelectorAll(".hud-exception").length;
+        return hud.outputNode.querySelectorAll(".message[category=exception][severity=error]").length;
       },
       value: 4,
       success: checkConsoleOutput.bind(null, hud),
-      failure: finish,
+      failure: () => {
+        finish();
+      },
     });
   }
 
   function checkConsoleOutput(hud) {
     let msgs = ["foobarBug762996a", "foobarBug762996b", "foobarBug762996load",
                 "foobarBug762996click", "foobarBug762996consoleLog",
                 "foobarBug762996css", "fooBug788445"];
     msgs.forEach(function(msg) {
@@ -170,17 +173,17 @@ function test() {
         warnings: 0,
         callback: waitForValue.bind(null, waitForConsoleOutputAfterReload),
       });
     }
 
     let waitForConsoleOutputAfterReload = {
       name: "the Web Console displays the correct number of errors after reload",
       validator: function() {
-        return hud.outputNode.querySelectorAll(".hud-exception").length;
+        return hud.outputNode.querySelectorAll(".message[category=exception][severity=error]").length;
       },
       value: 3,
       success: function() {
         isnot(hud.outputNode.textContent.indexOf("foobarBug762996load"), -1,
               "foobarBug762996load found in console output after page reload");
         testEnd();
       },
       failure: testEnd,
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -15,28 +15,24 @@ MOCHITEST_BROWSER_FILES = \
 	browser_webconsole_bug_597136_network_requests_from_chrome.js \
 	browser_webconsole_completion.js \
 	browser_webconsole_console_logging_api.js \
 	browser_webconsole_change_font_size.js \
 	browser_webconsole_chrome.js \
 	browser_webconsole_execution_scope.js \
 	browser_webconsole_for_of.js \
 	browser_webconsole_history.js \
-	browser_webconsole_js_input_and_output_styling.js \
 	browser_webconsole_js_input_expansion.js \
 	browser_webconsole_live_filtering_of_message_types.js \
 	browser_webconsole_live_filtering_on_search_strings.js \
 	browser_warn_user_about_replaced_api.js \
-	browser_webconsole_copying_multiple_messages_inserts_newlines_in_between.js \
 	browser_webconsole_bug_586388_select_all.js  \
 	browser_webconsole_bug_588967_input_expansion.js \
-	browser_webconsole_log_node_classes.js \
 	browser_webconsole_network_panel.js \
 	browser_webconsole_jsterm.js \
-	browser_webconsole_null_and_undefined_output.js \
 	browser_webconsole_output_order.js \
 	browser_webconsole_property_provider.js \
 	browser_webconsole_bug_587617_output_copy.js \
 	browser_webconsole_bug_585237_line_limit.js \
 	browser_webconsole_bug_582201_duplicate_errors.js \
 	browser_webconsole_bug_580454_timestamp_l10n.js \
 	browser_webconsole_netlogging.js \
 	browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js \
@@ -68,17 +64,16 @@ MOCHITEST_BROWSER_FILES = \
 	browser_webconsole_bug_613642_prune_scroll.js \
 	browser_webconsole_bug_618078_network_exceptions.js \
 	browser_webconsole_bug_613280_jsterm_copy.js \
 	browser_webconsole_bug_630733_response_redirect_headers.js \
 	browser_webconsole_bug_621644_jsterm_dollar.js \
 	browser_webconsole_bug_632817.js \
 	browser_webconsole_bug_611795.js \
 	browser_webconsole_bug_618311_close_panels.js \
-	browser_webconsole_bug_626484_output_copy_order.js \
 	browser_webconsole_bug_632347_iterators_generators.js \
 	browser_webconsole_bug_642108_pruneTest.js \
 	browser_webconsole_bug_585956_console_trace.js \
 	browser_webconsole_bug_595223_file_uri.js \
 	browser_webconsole_bug_632275_getters_document_width.js \
 	browser_webconsole_bug_644419_log_limits.js \
 	browser_webconsole_bug_646025_console_file_location.js \
 	browser_webconsole_bug_642615_autocomplete.js \
--- a/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
+++ b/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
@@ -51,24 +51,24 @@ function testWithoutNetActivity() {
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   }).then(onConsoleMessage);
 }
 
 function onConsoleMessage(aResults) {
   output.focus();
-  output.selectedItem = [...aResults[0].matched][0];
+  let message = [...aResults[0].matched][0];
 
   goUpdateCommand(COMMAND_NAME);
   ok(!isEnabled(), COMMAND_NAME + "is disabled");
 
   // Test that the "Copy Link Location" menu item is hidden for non-network
   // messages.
-  waitForContextMenu(menu, output.selectedItem, () => {
+  waitForContextMenu(menu, message, () => {
     let isHidden = menu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isHidden, CONTEXT_MENU_ID + " is hidden");
   }, testWithNetActivity);
 }
 
 function testWithNetActivity() {
   HUD.jsterm.clearOutput();
   content.location.reload(); // Reloading will produce network logging
@@ -83,25 +83,27 @@ function testWithNetActivity() {
       category: CATEGORY_NETWORK,
       severity: SEVERITY_LOG,
     }],
   }).then(onNetworkMessage);
 }
 
 function onNetworkMessage(aResults) {
   output.focus();
-  output.selectedItem = [...aResults[0].matched][0];
+  let message = [...aResults[0].matched][0];
+  HUD.ui.output.selectMessage(message);
 
   goUpdateCommand(COMMAND_NAME);
   ok(isEnabled(), COMMAND_NAME + " is enabled");
 
-  waitForClipboard(output.selectedItem.url, () => goDoCommand(COMMAND_NAME),
+  waitForClipboard(message.url, () => goDoCommand(COMMAND_NAME),
                    testMenuWithNetActivity, testMenuWithNetActivity);
+
+  function testMenuWithNetActivity() {
+    // Test that the "Copy Link Location" menu item is visible for network-related
+    // messages.
+    waitForContextMenu(menu, message, () => {
+      let isVisible = !menu.querySelector(CONTEXT_MENU_ID).hidden;
+      ok(isVisible, CONTEXT_MENU_ID + " is visible");
+    }, finishTest);
+  }
 }
 
-function testMenuWithNetActivity() {
-  // Test that the "Copy Link Location" menu item is visible for network-related
-  // messages.
-  waitForContextMenu(menu, output.selectedItem, () => {
-    let isVisible = !menu.querySelector(CONTEXT_MENU_ID).hidden;
-    ok(isVisible, CONTEXT_MENU_ID + " is visible");
-  }, finishTest);
-}
--- a/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
+++ b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
@@ -1,14 +1,14 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// Tests that makes sure messages are not considered repeated when console.log()
+// Test that makes sure messages are not considered repeated when console.log()
 // is invoked with different objects, see bug 865288.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
 
 let hud = null;
 
 function test() {
   addTab(TEST_URI);
@@ -36,33 +36,32 @@ function consoleOpened(aHud) {
       severity: SEVERITY_LOG,
       count: 3,
       repeats: 1,
       objects: true,
     }],
   }).then(checkMessages);
 }
 
-function checkMessages(aResults)
+function checkMessages([result])
 {
-  let result = aResults[0];
   let msgs = [...result.matched];
   is(msgs.length, 3, "3 message elements");
   let m = -1;
 
   function nextMessage()
   {
     let msg = msgs[++m];
     if (msg) {
       ok(msg, "message element #" + m);
 
-      let clickable = msg.querySelector(".hud-clickable");
+      let clickable = msg.querySelector(".body a");
       ok(clickable, "clickable object #" + m);
 
-      scrollOutputToNode(msg);
+      msg.scrollIntoView(false);
       clickObject(clickable);
     }
     else {
       finishTest();
     }
   }
 
   nextMessage();
--- a/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -25,24 +25,26 @@ function test()
 
 function consoleOpened(hud)
 {
   gWebConsole = hud;
   gJSTerm = hud.jsterm;
   gJSTerm.execute("fooObj", onExecuteFooObj);
 }
 
-function onExecuteFooObj()
+function onExecuteFooObj(msg)
 {
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output message found");
-  isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
+
+  let anchor = msg.querySelector("a");
+  ok(anchor, "object anchor");
+  isnot(anchor.textContent.indexOf("[object Object]"), -1, "message text check");
 
   gJSTerm.once("variablesview-fetched", onFooObjFetch);
-  EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+  EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
 }
 
 function onFooObjFetch(aEvent, aVar)
 {
   gVariablesView = aVar._variablesView;
   ok(gVariablesView, "variables view object");
 
   findVariableViewProperties(aVar, [
@@ -64,24 +66,25 @@ function onTestPropFound(aResults)
 }
 
 function onSidebarClosed()
 {
   gJSTerm.clearOutput();
   gJSTerm.execute("window", onExecuteWindow);
 }
 
-function onExecuteWindow()
+function onExecuteWindow(msg)
 {
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output message found");
-  isnot(msg.textContent.indexOf("[object Window]"), -1, "message text check");
+  let anchor = msg.querySelector("a");
+  ok(anchor, "object anchor");
+  isnot(anchor.textContent.indexOf("[object Window]"), -1, "message text check");
 
   gJSTerm.once("variablesview-fetched", onWindowFetch);
-  EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+  EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
 }
 
 function onWindowFetch(aEvent, aVar)
 {
   gVariablesView = aVar._variablesView;
   ok(gVariablesView, "variables view object");
 
   findVariableViewProperties(aVar, [
--- a/browser/devtools/webconsole/test/browser_cached_messages.js
+++ b/browser/devtools/webconsole/test/browser_cached_messages.js
@@ -1,13 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
+// Test to see if the cached messages are displayed when the console UI is opened.
+
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-webconsole-error-observer.html";
 
 function test()
 {
   waitForExplicitFinish();
 
   expectUncaughtException();
 
@@ -15,62 +17,41 @@ function test()
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     testOpenUI(true);
   }, true);
 }
 
 function testOpenUI(aTestReopen)
 {
-  // test to see if the messages are
-  // displayed when the console UI is opened
-
-  let messages = {
-    "log Bazzle" : false,
-    "error Bazzle" : false,
-    "bazBug611032" : false,
-    "cssColorBug611032" : false,
-  };
-
   openConsole(null, function(hud) {
-    waitForSuccess({
-      name: "cached messages displayed",
-      validatorFn: function()
-      {
-        let foundAll = true;
-        for (let msg in messages) {
-          let found = messages[msg];
-          if (!found) {
-            found = hud.outputNode.textContent.indexOf(msg) > -1;
-            if (found) {
-              info("found message '" + msg + "'");
-              messages[msg] = found;
-            }
-          }
-          foundAll = foundAll && found;
-        }
-        return foundAll;
-      },
-      successFn: function()
-      {
-        // Make sure the CSS warning is given the correct category - bug 768019.
-        let cssNode = hud.outputNode.querySelector(".webconsole-msg-cssparser");
-        ok(cssNode, "CSS warning message element");
-        isnot(cssNode.textContent.indexOf("cssColorBug611032"), -1,
-              "CSS warning message element content is correct");
-
-        closeConsole(gBrowser.selectedTab, function() {
-          aTestReopen && info("will reopen the Web Console");
-          executeSoon(aTestReopen ? testOpenUI : finishTest);
-        });
-      },
-      failureFn: function()
-      {
-        for (let msg in messages) {
-          if (!messages[msg]) {
-            ok(false, "failed to find '" + msg + "'");
-          }
-        }
-        finishTest();
-      },
+    waitForMessages({
+      webconsole: hud,
+      messages: [
+        {
+          text: "log Bazzle",
+          category: CATEGORY_WEBDEV,
+          severity: SEVERITY_LOG,
+        },
+        {
+          text: "error Bazzle",
+          category: CATEGORY_WEBDEV,
+          severity: SEVERITY_ERROR,
+        },
+        {
+          text: "bazBug611032",
+          category: CATEGORY_JS,
+          severity: SEVERITY_ERROR,
+        },
+        {
+          text: "cssColorBug611032",
+          category: CATEGORY_CSS,
+          severity: SEVERITY_WARNING,
+        },
+      ],
+    }).then(() => {
+      closeConsole(gBrowser.selectedTab, function() {
+        aTestReopen && info("will reopen the Web Console");
+        executeSoon(aTestReopen ? testOpenUI : finishTest);
+      });
     });
   });
 }
--- a/browser/devtools/webconsole/test/browser_console.js
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -59,45 +59,37 @@ function consoleOpened(hud)
   let output = hud.outputNode;
   function performChecks()
   {
     let text = output.textContent;
     chromeConsole = text.indexOf("bug587757a");
     contentConsole = text.indexOf("bug587757b");
     execValue = text.indexOf("browser.xul");
     exception = text.indexOf("foobarExceptionBug587757");
-
-    xhrRequest = false;
-    let urls = output.querySelectorAll(".webconsole-msg-url");
-    for (let url of urls) {
-      if (url.value.indexOf(TEST_URI) > -1) {
-        xhrRequest = true;
-        break;
-      }
-    }
+    xhrRequest = text.indexOf("test-console.html");
   }
 
   function showResults()
   {
     isnot(chromeConsole, -1, "chrome window console.log() is displayed");
     isnot(contentConsole, -1, "content window console.log() is displayed");
     isnot(execValue, -1, "jsterm eval result is displayed");
     isnot(exception, -1, "exception is displayed");
-    ok(xhrRequest, "xhr request is displayed");
+    isnot(xhrRequest, -1, "xhr request is displayed");
   }
 
   waitForSuccess({
     name: "messages displayed",
     validatorFn: () => {
       performChecks();
       return chromeConsole > -1 &&
              contentConsole > -1 &&
              execValue > -1 &&
              exception > -1 &&
-             xhrRequest;
+             xhrRequest > -1;
     },
     successFn: () => {
       showResults();
       executeSoon(finishTest);
     },
     failureFn: () => {
       showResults();
       info("output: " + output.textContent);
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -63,17 +63,17 @@ function test()
       onMessageFound(results);
     });
   }
 
   function onMessageFound(results)
   {
     let msg = [...results[0].matched][0];
     ok(msg, "message element found");
-    let locationNode = msg.querySelector(".webconsole-location");
+    let locationNode = msg.querySelector(".location");
     ok(locationNode, "message location element found");
 
     let title = locationNode.getAttribute("title");
     info("location node title: " + title);
     isnot(title.indexOf(" -> "), -1, "error comes from a subscript");
 
     let viewSource = browserconsole.viewSource;
     let URL = null;
--- a/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
+++ b/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
@@ -120,16 +120,16 @@ function test()
         findVariableViewProperties(aVar, [{
           name: "bug851231prop",
           value: "bug851231value",
         }], { webconsole: hud }).then(finishTest);
       };
 
       hud.jsterm.on("variablesview-fetched", onFetch);
 
-      scrollOutputToNode(clickable);
+      clickable.scrollIntoView(false);
 
       info("wait for variablesview-fetched");
       executeSoon(() =>
         EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow));
     });
   }
 }
--- a/browser/devtools/webconsole/test/browser_console_dead_objects.js
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -29,38 +29,37 @@ function test()
 
   function onAddVariable()
   {
     gBrowser.removeCurrentTab();
 
     hud.jsterm.execute("foobarzTezt", onReadVariable);
   }
 
-  function onReadVariable()
+  function onReadVariable(msg)
   {
     isnot(hud.outputNode.textContent.indexOf("[object DeadObject]"), -1,
           "dead object found");
 
     hud.jsterm.setInputValue("foobarzTezt");
 
     for (let c of ".hello") {
       EventUtils.synthesizeKey(c, {}, hud.iframeWindow);
     }
 
-    hud.jsterm.execute(null, onReadProperty);
+    hud.jsterm.execute(null, onReadProperty.bind(null, msg));
   }
 
-  function onReadProperty()
+  function onReadProperty(deadObjectMessage)
   {
     isnot(hud.outputNode.textContent.indexOf("can't access dead object"), -1,
           "'cannot access dead object' message found");
 
     // Click the second execute output.
-    let clickable = hud.outputNode.querySelectorAll(".webconsole-msg-output")[1]
-                    .querySelector(".hud-clickable");
+    let clickable = deadObjectMessage.querySelector("a");
     ok(clickable, "clickable object found");
     isnot(clickable.textContent.indexOf("[object DeadObject]"), -1,
           "message text check");
 
     hud.jsterm.once("variablesview-fetched", onFetched);
     EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
   }
 
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -56,17 +56,17 @@ function test()
     let viewSourceCalled = false;
     hud.viewSource = () => viewSourceCalled = true;
 
     for (let result of results) {
       viewSourceCalled = false;
 
       let msg = [...results[0].matched][0];
       ok(msg, "message element found for: " + result.text);
-      let locationNode = msg.querySelector(".webconsole-location");
+      let locationNode = msg.querySelector(".location");
       ok(locationNode, "message location element found");
 
       EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
 
       ok(viewSourceCalled, "view source opened");
     }
 
     hud.viewSource = viewSource;
--- a/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
+++ b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
@@ -17,42 +17,38 @@ function test()
 }
 
 function performTest(hud)
 {
   hud.jsterm.clearOutput(true);
 
   hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
   hud.jsterm.execute("console.log('fooBug676722', myObj)");
-  waitForSuccess({
-    name: "eval results are shown",
-    validatorFn: function()
-    {
-      return hud.outputNode.textContent.indexOf("fooBug676722") > -1 &&
-             hud.outputNode.querySelector(".hud-clickable");
-    },
-    successFn: function()
-    {
-      isnot(hud.outputNode.textContent.indexOf("myObj = {"), -1,
-            "myObj = ... is shown");
 
-      let clickable = hud.outputNode.querySelector(".hud-clickable");
-      ok(clickable, "the console.log() object .hud-clickable was found");
-      isnot(clickable.textContent.indexOf("Object"), -1,
-            "clickable node content is correct");
-
-      hud.jsterm.once("variablesview-fetched",
-        (aEvent, aVar) => {
-          ok(aVar, "object inspector opened on click");
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "fooBug676722",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      objects: true,
+    }],
+  }).then(([result]) => {
+    let clickable = result.clickableElements[0];
+    ok(clickable, "the console.log() object anchor was found");
+    isnot(clickable.textContent.indexOf("Object"), -1,
+          "clickable node content is correct");
 
-          findVariableViewProperties(aVar, [{
-            name: "abba",
-            value: "omgBug676722",
-          }], { webconsole: hud }).then(finishTest);
-        });
+    hud.jsterm.once("variablesview-fetched",
+      (aEvent, aVar) => {
+        ok(aVar, "object inspector opened on click");
 
-      executeSoon(function() {
-        EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+        findVariableViewProperties(aVar, [{
+          name: "abba",
+          value: "omgBug676722",
+        }], { webconsole: hud }).then(finishTest);
       });
-    },
-    failureFn: finishTest,
+
+    executeSoon(function() {
+      EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+    });
   });
 }
--- a/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
+++ b/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
@@ -77,19 +77,19 @@ function onBrowserConsoleOpen(hud)
     ],
   }).then(testFiltering);
 
   function testFiltering(results)
   {
     let msg = [...results[2].matched][0];
     ok(msg, "message element for do-not-show-me (nsIConsoleMessage)");
     isnot(msg.textContent.indexOf("do-not-show"), -1, "element content is correct");
-    ok(!msg.classList.contains("hud-filtered-by-type"), "element is not filtered");
+    ok(!msg.classList.contains("filtered-by-type"), "element is not filtered");
 
     hud.setFilterState("jslog", false);
 
-    ok(msg.classList.contains("hud-filtered-by-type"), "element is filtered");
+    ok(msg.classList.contains("filtered-by-type"), "element is filtered");
 
     hud.setFilterState("jslog", true);
 
     finishTest();
   }
 }
--- a/browser/devtools/webconsole/test/browser_console_variables_view.js
+++ b/browser/devtools/webconsole/test/browser_console_variables_view.js
@@ -20,26 +20,28 @@ function test()
 
 function consoleOpened(hud)
 {
   gWebConsole = hud;
   gJSTerm = hud.jsterm;
   gJSTerm.execute("fooObj", onExecuteFooObj);
 }
 
-function onExecuteFooObj()
+function onExecuteFooObj(msg)
 {
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output message found");
   isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
 
+  let anchor = msg.querySelector("a");
+  ok(anchor, "object link found");
+
   gJSTerm.once("variablesview-fetched", onFooObjFetch);
 
   executeSoon(() =>
-    EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+    EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
   );
 }
 
 function onFooObjFetch(aEvent, aVar)
 {
   gVariablesView = aVar._variablesView;
   ok(gVariablesView, "variables view object");
 
@@ -145,22 +147,25 @@ function onPropUpdateError(aEvent, aVar)
 
 function onRenamedTestPropFoundAgain(aResults)
 {
   let prop = aResults[0].matchedProp;
   ok(prop, "matched the renamed |testProp| property again");
 
   let outputNode = gWebConsole.outputNode;
 
-  waitForSuccess({
-    name: "exception in property update reported in the web console output",
-    validatorFn: () => outputNode.textContent.indexOf("foobarzFailure") != -1,
-    successFn: testPropDelete.bind(null, prop),
-    failureFn: testPropDelete.bind(null, prop),
-  });
+  waitForMessages({
+    webconsole: gWebConsole,
+    messages: [{
+      name: "exception in property update reported in the web console output",
+      text: "foobarzFailure",
+      category: CATEGORY_OUTPUT,
+      severity: SEVERITY_ERROR,
+    }],
+  }).then(testPropDelete.bind(null, prop));
 }
 
 function testPropDelete(aProp)
 {
   gVariablesView.window.focus();
   aProp.focus();
 
   executeSoon(() => {
--- a/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
@@ -54,25 +54,27 @@ function onFramesAdded()
   executeSoon(() =>
     openConsole(null, () =>
       gJSTerm.execute("fooObj", onExecuteFooObj)
     )
   );
 }
 
 
-function onExecuteFooObj()
+function onExecuteFooObj(msg)
 {
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output message found");
   isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
 
+  let anchor = msg.querySelector("a");
+  ok(anchor, "object link found");
+
   gJSTerm.once("variablesview-fetched", onFooObjFetch);
 
-  executeSoon(() => EventUtils.synthesizeMouse(msg, 2, 2, {},
+  executeSoon(() => EventUtils.synthesizeMouse(anchor, 2, 2, {},
                                                gWebConsole.iframeWindow));
 }
 
 function onFooObjFetch(aEvent, aVar)
 {
   gVariablesView = aVar._variablesView;
   ok(gVariablesView, "variables view object");
 
--- a/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js
@@ -49,25 +49,27 @@ function inspectorOpened(aPanel)
 
 function onFramesAdded()
 {
   info("onFramesAdded");
 
   openConsole(null, () => gJSTerm.execute("fooObj", onExecuteFooObj));
 }
 
-function onExecuteFooObj()
+function onExecuteFooObj(msg)
 {
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output message found");
   isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
 
+  let anchor = msg.querySelector("a");
+  ok(anchor, "object link found");
+
   gJSTerm.once("variablesview-fetched", onFooObjFetch);
 
-  EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow);
+  EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow);
 }
 
 function onFooObjFetch(aEvent, aVar)
 {
   gVariablesView = aVar._variablesView;
   ok(gVariablesView, "variables view object");
 
   findVariableViewProperties(aVar, [
--- a/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
+++ b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
@@ -33,22 +33,21 @@ function onExecuteFoo()
 
   gJSTerm.clearOutput();
 
   // Test for Bug 690529 - Web Console and Scratchpad should evaluate
   // expressions in the scope of the content window, not in a sandbox.
   executeSoon(() => gJSTerm.execute("foo2 = 'newFoo'; window.foo2", onNewFoo2));
 }
 
-function onNewFoo2()
+function onNewFoo2(msg)
 {
   is(gWebConsole.outputNode.textContent.indexOf("undefined"), -1,
      "|undefined| is not displayed after adding |foo2|");
 
-  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
   ok(msg, "output result found");
 
   isnot(msg.textContent.indexOf("newFoo"), -1,
         "'newFoo' is displayed after adding |foo2|");
 
   gJSTerm.clearOutput();
 
   info("openDebugger");
--- a/browser/devtools/webconsole/test/browser_longstring_hang.js
+++ b/browser/devtools/webconsole/test/browser_longstring_hang.js
@@ -1,34 +1,28 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that very long strings do not hang the browser.
 
 function test()
 {
-  waitForExplicitFinish();
-
-  let DebuggerServer = Cu.import("resource://gre/modules/devtools/dbg-server.jsm",
-                                 {}).DebuggerServer;
-
   addTab("http://example.com/browser/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html");
 
   let hud = null;
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     openConsole(null, performTest);
   }, true);
 
   function performTest(aHud)
   {
     hud = aHud;
-
     info("wait for the initial long string");
 
     waitForMessages({
       webconsole: hud,
       messages: [
         {
           name: "find 'foobar', no 'foobaz', in long string output",
           text: "foobar",
@@ -39,35 +33,32 @@ function test()
       ],
     }).then(onInitialString);
   }
 
   function onInitialString(aResults)
   {
     let clickable = aResults[0].longStrings[0];
     ok(clickable, "long string ellipsis is shown");
-
-    scrollOutputToNode(clickable);
+    clickable.scrollIntoView(false);
 
-    executeSoon(() => {
-      EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+    EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
 
-      info("wait for long string expansion");
+    info("wait for long string expansion");
 
-      waitForMessages({
-        webconsole: hud,
-        messages: [
-          {
-            name: "find 'foobaz' after expand, but no 'boom!' at the end",
-            text: "foobaz",
-            noText: "boom!",
-            category: CATEGORY_WEBDEV,
-            longString: false,
-          },
-          {
-            text: "too long to be displayed",
-            longString: false,
-          },
-        ],
-      }).then(finishTest);
-    });
+    waitForMessages({
+      webconsole: hud,
+      messages: [
+        {
+          name: "find 'foobaz' after expand, but no 'boom!' at the end",
+          text: "foobaz",
+          noText: "boom!",
+          category: CATEGORY_WEBDEV,
+          longString: false,
+        },
+        {
+          text: "too long to be displayed",
+          longString: false,
+        },
+      ],
+    }).then(finishTest);
   }
 }
--- a/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
+++ b/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
@@ -19,38 +19,32 @@ function test()
 
 function performTest(hud)
 {
   hud.jsterm.clearOutput(true);
 
   hud.jsterm.execute("console.log('fooBug773466a')");
   hud.jsterm.execute("myObj = Object.create(null)");
   hud.jsterm.execute("console.dir(myObj)");
-  waitForSuccess({
-    name: "eval results are shown",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-inspector");
+
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "fooBug773466a",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
     },
-    successFn: function()
     {
-      isnot(hud.outputNode.textContent.indexOf("fooBug773466a"), -1,
-            "fooBug773466a shows");
-      ok(hud.outputNode.querySelector(".webconsole-msg-inspector"),
-         "the console.dir() tree shows");
-
-      content.console.log("fooBug773466b");
-
-      waitForSuccess(waitForAnotherConsoleLogCall);
-    },
-    failureFn: finishTest,
+      name: "console.dir output",
+      consoleDir: "[object Object]",
+    }],
+  }).then(() => {
+    content.console.log("fooBug773466b");
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "fooBug773466b",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+      }],
+    }).then(finishTest);
   });
-
-  let waitForAnotherConsoleLogCall = {
-    name: "eval result after console.dir()",
-    validatorFn: function()
-    {
-      return hud.outputNode.textContent.indexOf("fooBug773466b") > -1;
-    },
-    successFn: finishTest,
-    failureFn: finishTest,
-  };
 }
--- a/browser/devtools/webconsole/test/browser_output_longstring_expand.js
+++ b/browser/devtools/webconsole/test/browser_output_longstring_expand.js
@@ -24,130 +24,70 @@ function test()
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     openConsole(null, performTest);
   }, true);
 
   function performTest(aHud)
   {
     hud = aHud;
-
     hud.jsterm.clearOutput(true);
     hud.jsterm.execute("console.log('bazbaz', '" + longString +"', 'boom')");
 
-    waitForSuccess(waitForConsoleLog);
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        name: "console.log output",
+        text: ["bazbaz", "boom", initialString],
+        noText: "foobar",
+        longString: true,
+      }],
+    }).then(onConsoleMessage);
   }
 
-  let waitForConsoleLog = {
-    name: "console.log output shown",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-console");
-    },
-    successFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-console");
-      is(msg.textContent.indexOf("foobar"), -1,
-         "foobar is not shown");
-      isnot(msg.textContent.indexOf("bazbaz"), -1,
-            "bazbaz is shown");
-      isnot(msg.textContent.indexOf("boom"), -1,
-            "boom is shown");
-      isnot(msg.textContent.indexOf(initialString), -1,
-            "initial string is shown");
+  function onConsoleMessage([result])
+  {
+    let clickable = result.longStrings[0];
+    ok(clickable, "long string ellipsis is shown");
 
-      let clickable = msg.querySelector(".longStringEllipsis");
-      ok(clickable, "long string ellipsis is shown");
+    clickable.scrollIntoView(false);
 
-      scrollToVisible(clickable);
+    EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
 
-      executeSoon(function() {
-        EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
-        waitForSuccess(waitForFullString);
-      });
-    },
-    failureFn: finishTest,
-  };
-
-  let waitForFullString = {
-    name: "full string shown",
-    validatorFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-log");
-      return msg.textContent.indexOf(longString) > -1;
-    },
-    successFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-log");
-      isnot(msg.textContent.indexOf("bazbaz"), -1,
-            "bazbaz is shown");
-      isnot(msg.textContent.indexOf("boom"), -1,
-            "boom is shown");
-
-      let clickable = msg.querySelector(".longStringEllipsis");
-      ok(!clickable, "long string ellipsis is not shown");
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        name: "full string",
+        text: ["bazbaz", "boom", longString],
+        category: CATEGORY_WEBDEV,
+        longString: false,
+      }],
+    }).then(() => {
+      hud.jsterm.clearOutput(true);
+      hud.jsterm.execute("'" + longString +"'", onExecute);
+    });
+  }
 
-      executeSoon(function() {
-        hud.jsterm.clearOutput(true);
-        hud.jsterm.execute("'" + longString +"'");
-        waitForSuccess(waitForExecute);
-      });
-    },
-    failureFn: finishTest,
-  };
+  function onExecute(msg)
+  {
+    isnot(msg.textContent.indexOf(initialString), -1,
+        "initial string is shown");
+    is(msg.textContent.indexOf(longString), -1,
+        "full string is not shown");
 
-  let waitForExecute = {
-    name: "execute() output shown",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
-      isnot(msg.textContent.indexOf(initialString), -1,
-           "initial string is shown");
-      is(msg.textContent.indexOf(longString), -1,
-         "full string is not shown");
+    let clickable = msg.querySelector(".longStringEllipsis");
+    ok(clickable, "long string ellipsis is shown");
 
-      let clickable = msg.querySelector(".longStringEllipsis");
-      ok(clickable, "long string ellipsis is shown");
-
-      scrollToVisible(clickable);
+    clickable.scrollIntoView(false);
 
-      executeSoon(function() {
-        EventUtils.synthesizeMouse(clickable, 3, 4, {}, hud.iframeWindow);
-        waitForSuccess(waitForFullStringAfterExecute);
-      });
-    },
-    failureFn: finishTest,
-  };
+    EventUtils.synthesizeMouse(clickable, 3, 4, {}, hud.iframeWindow);
 
-  let waitForFullStringAfterExecute = {
-    name: "full string shown again",
-    validatorFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
-      return msg.textContent.indexOf(longString) > -1;
-    },
-    successFn: function()
-    {
-      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
-      let clickable = msg.querySelector(".longStringEllipsis");
-      ok(!clickable, "long string ellipsis is not shown");
-
-      executeSoon(finishTest);
-    },
-    failureFn: finishTest,
-  };
-
-  function scrollToVisible(aNode)
-  {
-    let richListBoxNode = aNode.parentNode;
-    while (richListBoxNode.tagName != "richlistbox") {
-      richListBoxNode = richListBoxNode.parentNode;
-    }
-
-    let boxObject = richListBoxNode.scrollBoxObject;
-    let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
-    nsIScrollBoxObject.ensureElementIsVisible(aNode);
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        name: "full string",
+        text: longString,
+        category: CATEGORY_OUTPUT,
+        longString: false,
+      }],
+    }).then(finishTest);
   }
 }
--- a/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
+++ b/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
@@ -1,15 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// Tests that makes sure messages are not considered repeated when coming from
+// Test that makes sure messages are not considered repeated when coming from
 // different lines of code, or from different severities, etc.
 // See bugs 720180 and 800510.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
 
 function test() {
   const PREF = "devtools.webconsole.persistlog";
   Services.prefs.setBoolPref(PREF, true);
@@ -96,23 +96,21 @@ function testConsoleRepeats(hud) {
 
   waitForMessages({
     webconsole: hud,
     messages: [
       {
         name: "'undefined' jsterm input message",
         text: "undefined",
         category: CATEGORY_INPUT,
-        repeats: 1,
       },
       {
         name: "'undefined' jsterm output message",
         text: "undefined",
         category: CATEGORY_OUTPUT,
-        repeats: 1,
       },
       {
         name: "'undefined' console.log message",
         text: "undefined",
         category: CATEGORY_WEBDEV,
         repeats: 1,
       },
     ],
--- a/browser/devtools/webconsole/test/browser_result_format_as_string.js
+++ b/browser/devtools/webconsole/test/browser_result_format_as_string.js
@@ -17,33 +17,25 @@ function test()
     openConsole(null, performTest);
   }, true);
 }
 
 function performTest(hud)
 {
   hud.jsterm.clearOutput(true);
 
-  hud.jsterm.execute("document.querySelector('p')");
-  waitForSuccess({
-    name: "eval result shown",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      is(hud.outputNode.textContent.indexOf("bug772506_content"), -1,
-            "no content element found");
-      ok(!hud.outputNode.querySelector("div"), "no div element found");
+  hud.jsterm.execute("document.querySelector('p')", (msg) => {
+    is(hud.outputNode.textContent.indexOf("bug772506_content"), -1,
+       "no content element found");
+    ok(!hud.outputNode.querySelector("#foobar"), "no #foobar element found");
 
-      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
-      ok(msg, "eval output node found");
-      is(msg.textContent.indexOf("HTMLDivElement"), -1,
-         "HTMLDivElement string not displayed");
-      EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
-      ok(!gBrowser._bug772506, "no content variable");
+    ok(msg, "eval output node found");
+    is(msg.textContent.indexOf("HTMLDivElement"), -1,
+       "HTMLDivElement string is not displayed");
+    isnot(msg.textContent.indexOf("HTMLParagraphElement"), -1,
+          "HTMLParagraphElement string is displayed");
 
-      finishTest();
-    },
-    failureFn: finishTest,
+    EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
+    ok(!gBrowser._bug772506, "no content variable");
+
+    finishTest();
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
@@ -5,17 +5,17 @@
 // on it while the "block mixed content" settings are _off_.
 // It then checks that the loading mixed content warning messages
 // are logged to the console and have the correct "Learn More"
 // url appended to them.
 // Bug 875456 - Log mixed content messages from the Mixed Content
 // Blocker to the Security Pane in the Web Console
 
 const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
-const LEARN_MORE_URI = "https://developer.mozilla.org/Security/MixedContent";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
 
 function test()
 {
   SpecialPowers.pushPrefEnv({"set":
       [["security.mixed_content.block_active_content", false],
        ["security.mixed_content.block_display_content", false]
   ]}, loadingMixedContentTest);
 }
@@ -25,45 +25,48 @@ function loadingMixedContentTest()
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad, true);
     openConsole(null, function testSecurityErrorLogged (hud) {
       waitForMessages({
         webconsole: hud,
         messages: [
           {
-          name: "Logged mixed active content",
-          text: "Loading mixed (insecure) active content on a secure page \"http://example.com/\"",
-          category: CATEGORY_SECURITY,
-          severity: SEVERITY_WARNING
-        },
-        {
-          name: "Logged mixed passive content - image",
-          text: "Loading mixed (insecure) display content on a secure page \"http://example.com/tests/image/test/mochitest/blue.png\"",
-          category: CATEGORY_SECURITY,
-          severity: SEVERITY_WARNING
-        },
+            name: "Logged mixed active content",
+            text: "Loading mixed (insecure) active content on a secure page \"http://example.com/\"",
+            category: CATEGORY_SECURITY,
+            severity: SEVERITY_WARNING,
+            objects: true,
+          },
+          {
+            name: "Logged mixed passive content - image",
+            text: "Loading mixed (insecure) display content on a secure page \"http://example.com/tests/image/test/mochitest/blue.png\"",
+            category: CATEGORY_SECURITY,
+            severity: SEVERITY_WARNING,
+            objects: true,
+          },
         ],
-      }).then(() => testClickOpenNewTab(hud));
+      }).then((results) => testClickOpenNewTab(hud, results));
     });
   }, true);
 }
 
-function testClickOpenNewTab(hud) {
-  let warningNode = hud.outputNode.querySelector(".webconsole-learn-more-link");
+function testClickOpenNewTab(hud, results) {
+  let warningNode = results[0].clickableElements[0];
+  ok(warningNode, "link element");
+  ok(warningNode.classList.contains("learn-more-link"), "link class name");
 
-    // Invoke the click event and check if a new tab would
-    // open to the correct page.
-    let linkOpened = false;
-    let oldOpenUILinkIn = window.openUILinkIn;
-    window.openUILinkIn = function(aLink) {
-      if (aLink == LEARN_MORE_URI) {
-        linkOpened = true;
-      }
+  // Invoke the click event and check if a new tab would open to the correct page.
+  let linkOpened = false;
+  let oldOpenUILinkIn = window.openUILinkIn;
+  window.openUILinkIn = function(aLink) {
+    if (aLink == LEARN_MORE_URI) {
+      linkOpened = true;
     }
+  }
 
-    EventUtils.synthesizeMouse(warningNode, 2, 2, {},
-                               warningNode.ownerDocument.defaultView);
-    ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
-    window.openUILinkIn = oldOpenUILinkIn;
+  EventUtils.synthesizeMouse(warningNode, 2, 2, {},
+                             warningNode.ownerDocument.defaultView);
+  ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
+  window.openUILinkIn = oldOpenUILinkIn;
 
-    finishTest();
+  finishTest();
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js
@@ -6,40 +6,39 @@
 // Tests that the page's resources are displayed in the console as they're
 // loaded
 
 const TEST_NETWORK_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network.html" + "?_date=" + Date.now();
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console basic network logging test");
   browser.addEventListener("load", onLoad, true);
-}
 
-function onLoad(aEvent) {
-  browser.removeEventListener(aEvent.type, onLoad, true);
-  openConsole(null, function() {
-    browser.addEventListener("load", testBasicNetLogging, true);
-    content.location = TEST_NETWORK_URI;
-  });
+  function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, function(hud) {
+      content.location = TEST_NETWORK_URI;
+      waitForMessages({
+        webconsole: hud,
+        messages: [{
+          text: "running network console",
+          category: CATEGORY_WEBDEV,
+          severity: SEVERITY_LOG,
+        },
+        {
+          text: "test-network.html",
+          category: CATEGORY_NETWORK,
+          severity: SEVERITY_LOG,
+        },
+        {
+          text: "testscript.js",
+          category: CATEGORY_NETWORK,
+          severity: SEVERITY_LOG,
+        },
+        {
+          text: "test-image.png",
+          category: CATEGORY_NETWORK,
+          severity: SEVERITY_LOG,
+        }],
+      }).then(finishTest);
+    });
+  }
 }
-
-function testBasicNetLogging(aEvent) {
-  browser.removeEventListener(aEvent.type, testBasicNetLogging, true);
-
-  outputNode = HUDService.getHudByWindow(content).outputNode;
-
-  waitForSuccess({
-    name: "network console message",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("running network console") > -1;
-    },
-    successFn: function()
-    {
-      findLogEntry("test-network.html");
-      findLogEntry("testscript.js");
-      findLogEntry("test-image.png");
-      finishTest();
-    },
-    failureFn: finishTest,
-  });
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
@@ -8,17 +8,17 @@
 // url appended to them. After the first test finishes, it invokes
 // a second test that overrides the mixed content blocker settings
 // by clicking on the doorhanger shield and validates that the
 // appropriate messages are logged to console.
 // Bug 875456 - Log mixed content messages from the Mixed Content
 // Blocker to the Security Pane in the Web Console
 
 const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
-const LEARN_MORE_URI = "https://developer.mozilla.org/Security/MixedContent";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
 
 function test()
 {
   SpecialPowers.pushPrefEnv({"set": [["security.mixed_content.block_active_content", true],
                             ["security.mixed_content.block_display_content", true]]}, blockMixedContentTest1);
 }
 
 function blockMixedContentTest1()
@@ -29,27 +29,29 @@ function blockMixedContentTest1()
     openConsole(null, function testSecurityErrorLogged (hud) {
       waitForMessages({
         webconsole: hud,
         messages: [
           {
             name: "Logged blocking mixed active content",
             text: "Blocked loading mixed active content \"http://example.com/\"",
             category: CATEGORY_SECURITY,
-            severity: SEVERITY_ERROR
+            severity: SEVERITY_ERROR,
+            objects: true,
           },
           {
             name: "Logged blocking mixed passive content - image",
             text: "Blocked loading mixed active content \"http://example.com/\"",
             category: CATEGORY_SECURITY,
-            severity: SEVERITY_ERROR
+            severity: SEVERITY_ERROR,
+            objects: true,
           },
         ],
-      }).then(() => {
-        testClickOpenNewTab(hud);
+      }).then(([result]) => {
+        testClickOpenNewTab(hud, result);
         // Call the second (MCB override) test.
         mixedContentOverrideTest2(hud);
       });
     });
   }, true);
 }
 
 function mixedContentOverrideTest2(hud)
@@ -58,47 +60,51 @@ function mixedContentOverrideTest2(hud)
   ok(notification, "Mixed Content Doorhanger didn't appear");
   // Click on the doorhanger.
   notification.secondaryActions[0].callback();
 
   waitForMessages({
     webconsole: hud,
     messages: [
       {
-      name: "Logged blocking mixed active content",
-      text: "Loading mixed (insecure) active content on a secure"+
-        " page \"http://example.com/\"",
-      category: CATEGORY_SECURITY,
-      severity: SEVERITY_WARNING
-    },
-    {
-      name: "Logged blocking mixed passive content - image",
-      text: "Loading mixed (insecure) display content on a secure page"+
-        " \"http://example.com/tests/image/test/mochitest/blue.png\"",
-      category: CATEGORY_SECURITY,
-      severity: SEVERITY_WARNING
-    },
+        name: "Logged blocking mixed active content",
+        text: "Loading mixed (insecure) active content on a secure"+
+          " page \"http://example.com/\"",
+        category: CATEGORY_SECURITY,
+        severity: SEVERITY_WARNING,
+        objects: true,
+      },
+      {
+        name: "Logged blocking mixed passive content - image",
+        text: "Loading mixed (insecure) display content on a secure page"+
+          " \"http://example.com/tests/image/test/mochitest/blue.png\"",
+        category: CATEGORY_SECURITY,
+        severity: SEVERITY_WARNING,
+        objects: true,
+      },
     ],
-  }).then(() => {
-    testClickOpenNewTab(hud);
+  }).then(([result]) => {
+    testClickOpenNewTab(hud, result);
     finishTest();
   });
 }
 
-function testClickOpenNewTab(hud) {
-  let warningNode = hud.outputNode.querySelector(".webconsole-learn-more-link");
+function testClickOpenNewTab(hud, match) {
+  let warningNode = match.clickableElements[0];
+  ok(warningNode, "link element");
+  ok(warningNode.classList.contains("learn-more-link"), "link class name");
 
-    // Invoke the click event and check if a new tab would
-    // open to the correct page.
-    let linkOpened = false;
-    let oldOpenUILinkIn = window.openUILinkIn;
-    window.openUILinkIn = function(aLink) {
-      if (aLink == LEARN_MORE_URI) {
-        linkOpened = true;
-      }
+  // Invoke the click event and check if a new tab would
+  // open to the correct page.
+  let linkOpened = false;
+  let oldOpenUILinkIn = window.openUILinkIn;
+  window.openUILinkIn = function(aLink) {
+    if (aLink == LEARN_MORE_URI) {
+      linkOpened = true;
     }
+  }
 
-    EventUtils.synthesizeMouse(warningNode, 2, 2, {},
-                               warningNode.ownerDocument.defaultView);
-    ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
-    window.openUILinkIn = oldOpenUILinkIn;
+  EventUtils.synthesizeMouse(warningNode, 2, 2, {},
+                             warningNode.ownerDocument.defaultView);
+  ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
+  window.openUILinkIn = oldOpenUILinkIn;
 
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js
@@ -4,65 +4,44 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Tests that exceptions thrown by content don't show up twice in the Web
 // Console.
 
 const TEST_DUPLICATE_ERROR_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-duplicate-error.html";
 
 function test() {
-  expectUncaughtException();
-  addTab(TEST_DUPLICATE_ERROR_URI);
-  browser.addEventListener("DOMContentLoaded", testDuplicateErrors, false);
-}
-
-function testDuplicateErrors() {
-  browser.removeEventListener("DOMContentLoaded", testDuplicateErrors,
-                              false);
-  openConsole(null, function(hud) {
-    hud.jsterm.clearOutput();
+  addTab("data:text/html;charset=utf8,hello world");
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
 
-    Services.console.registerListener(consoleObserver);
-
-    expectUncaughtException();
-    content.location.reload();
-  });
-}
-
-var consoleObserver = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
-  observe: function (aMessage)
+  function consoleOpened(hud)
   {
-    // we ignore errors we don't care about
-    if (!(aMessage instanceof Ci.nsIScriptError) ||
-      aMessage.category != "content javascript") {
-      return;
-    }
-
-    Services.console.unregisterListener(this);
+    expectUncaughtException();
+    content.location = TEST_DUPLICATE_ERROR_URI;
 
-    outputNode = HUDService.getHudByWindow(content).outputNode;
-
-    waitForSuccess({
-      name: "fooDuplicateError1 error displayed",
-      validatorFn: function()
-      {
-        return outputNode.textContent.indexOf("fooDuplicateError1") > -1;
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "fooDuplicateError1",
+        category: CATEGORY_JS,
+        severity: SEVERITY_ERROR,
       },
-      successFn: function()
       {
-        let text = outputNode.textContent;
-        let error1pos = text.indexOf("fooDuplicateError1");
-        ok(error1pos > -1, "found fooDuplicateError1");
-        if (error1pos > -1) {
-          ok(text.indexOf("fooDuplicateError1", error1pos + 1) == -1,
-            "no duplicate for fooDuplicateError1");
-        }
+        text: "test-duplicate-error.html",
+        category: CATEGORY_NETWORK,
+        severity: SEVERITY_LOG,
+      }],
+    }).then(() => {
+      let text = hud.outputNode.textContent;
+      let error1pos = text.indexOf("fooDuplicateError1");
+      ok(error1pos > -1, "found fooDuplicateError1");
+      if (error1pos > -1) {
+        ok(text.indexOf("fooDuplicateError1", error1pos + 1) == -1,
+          "no duplicate for fooDuplicateError1");
+      }
 
-        findLogEntry("test-duplicate-error.html");
-
-        finishTest();
-      },
-      failureFn: finishTest,
+      finishTest();
     });
   }
-};
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js
@@ -39,71 +39,68 @@ function testGen() {
 
   let prefBranch = Services.prefs.getBranch("devtools.hud.loglimit.");
   prefBranch.setIntPref("console", 20);
 
   for (let i = 0; i < 30; i++) {
     console.log("foo #" + i); // must change message to prevent repeats
   }
 
-  waitForSuccess({
-    name: "20 console.log messages displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("foo #29") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "foo #29",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
   is(countMessageNodes(), 20, "there are 20 message nodes in the output " +
      "when the log limit is set to 20");
 
   console.log("bar bug585237");
 
-  waitForSuccess({
-    name: "another console.log message displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bar bug585237") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bar bug585237",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
   is(countMessageNodes(), 20, "there are still 20 message nodes in the " +
      "output when adding one more");
 
   prefBranch.setIntPref("console", 30);
   for (let i = 0; i < 20; i++) {
     console.log("boo #" + i); // must change message to prevent repeats
   }
 
-  waitForSuccess({
-    name: "another 20 console.log message displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("boo #19") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "boo #19",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
   is(countMessageNodes(), 30, "there are 30 message nodes in the output " +
      "when the log limit is set to 30");
 
   prefBranch.clearUserPref("console");
   hud = testDriver = prefBranch = console = outputNode = null;
   finishTest();
 
   yield undefined;
 }
 
 function countMessageNodes() {
-  return outputNode.querySelectorAll(".hud-msg-node").length;
+  return outputNode.querySelectorAll(".message").length;
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js
@@ -6,76 +6,87 @@
  * Contributor(s):
  *  Patrick Walton <pcwalton@mozilla.com>
  *
  * ***** END LICENSE BLOCK ***** */
 
 const TEST_URI = "http://example.com/";
 
 function test() {
+  let hud;
+
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testSelectionWhenMovingBetweenBoxes);
   }, true);
-}
 
-function testSelectionWhenMovingBetweenBoxes(hud) {
-  let jsterm = hud.jsterm;
+  function testSelectionWhenMovingBetweenBoxes(aHud) {
+    hud = aHud;
+    let jsterm = hud.jsterm;
 
-  // Fill the console with some output.
-  jsterm.clearOutput();
-  jsterm.execute("1 + 2");
-  jsterm.execute("3 + 4");
-  jsterm.execute("5 + 6");
+    // Fill the console with some output.
+    jsterm.clearOutput();
+    jsterm.execute("1 + 2");
+    jsterm.execute("3 + 4");
+    jsterm.execute("5 + 6");
 
-  waitForSuccess({
-    name: "execution results displayed",
-    validatorFn: function()
-    {
-      return hud.outputNode.textContent.indexOf("5 + 6") > -1 &&
-             hud.outputNode.textContent.indexOf("11") > -1;
-    },
-    successFn: performTestsAfterOutput.bind(null, hud),
-    failureFn: finishTest,
-  });
-}
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "3",
+        category: CATEGORY_OUTPUT,
+      },
+      {
+        text: "7",
+        category: CATEGORY_OUTPUT,
+      },
+      {
+        text: "11",
+        category: CATEGORY_OUTPUT,
+      }],
+    }).then(performTestsAfterOutput);
+  }
 
-function performTestsAfterOutput(hud) {
-  let outputNode = hud.outputNode;
+  function performTestsAfterOutput() {
+    let outputNode = hud.outputNode;
 
-  ok(outputNode.childNodes.length >= 3, "the output node has children after " +
-     "executing some JavaScript");
+    ok(outputNode.childNodes.length >= 3, "the output node has children after " +
+       "executing some JavaScript");
 
-  // Test that the global Firefox "Select All" functionality (e.g. Edit >
-  // Select All) works properly in the Web Console.
-  let commandController = hud.ui._commandController;
-  ok(commandController != null, "the window has a command controller object");
+    // Test that the global Firefox "Select All" functionality (e.g. Edit >
+    // Select All) works properly in the Web Console.
+    let commandController = hud.ui._commandController;
+    ok(commandController != null, "the window has a command controller object");
+
+    commandController.selectAll();
 
-  commandController.selectAll(outputNode);
-  is(outputNode.selectedCount, outputNode.childNodes.length, "all console " +
-     "messages are selected after performing a regular browser select-all " +
-     "operation");
+    let selectedCount = hud.ui.output.getSelectedMessages().length;
+    is(selectedCount, outputNode.childNodes.length,
+       "all console messages are selected after performing a regular browser " +
+       "select-all operation");
 
-  outputNode.selectedIndex = -1;
+    hud.iframeWindow.getSelection().removeAllRanges();
 
-  // Test the context menu "Select All" (which has a different code path) works
-  // properly as well.
-  let contextMenuId = outputNode.getAttribute("context");
-  let contextMenu = hud.ui.document.getElementById(contextMenuId);
-  ok(contextMenu != null, "the output node has a context menu");
+    // Test the context menu "Select All" (which has a different code path) works
+    // properly as well.
+    let contextMenuId = outputNode.parentNode.getAttribute("context");
+    let contextMenu = hud.ui.document.getElementById(contextMenuId);
+    ok(contextMenu != null, "the output node has a context menu");
 
-  let selectAllItem = contextMenu.querySelector("*[command='cmd_selectAll']");
-  ok(selectAllItem != null,
-     "the context menu on the output node has a \"Select All\" item");
+    let selectAllItem = contextMenu.querySelector("*[command='cmd_selectAll']");
+    ok(selectAllItem != null,
+       "the context menu on the output node has a \"Select All\" item");
+
+    outputNode.focus();
 
-  outputNode.focus();
-
-  selectAllItem.doCommand();
+    selectAllItem.doCommand();
 
-  is(outputNode.selectedCount, outputNode.childNodes.length, "all console " +
-     "messages are selected after performing a select-all operation from " +
-     "the context menu");
+    let selectedCount = hud.ui.output.getSelectedMessages().length;
+    is(selectedCount, outputNode.childNodes.length,
+       "all console messages are selected after performing a select-all " +
+       "operation from the context menu");
 
-  outputNode.selectedIndex = -1;
+    hud.iframeWindow.getSelection().removeAllRanges();
 
-  finishTest();
+    finishTest();
+  }
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js
@@ -20,72 +20,62 @@ function test() {
   }, true);
 }
 
 function consoleOpened(aHud) {
   HUD = aHud;
 
   // See bugs 574036, 586386 and 587617.
   outputNode = HUD.outputNode;
-  let selection = getSelection();
-  let jstermInput = HUD.jsterm.inputNode;
-  let console = content.wrappedJSObject.console;
-  let contentSelection = content.wrappedJSObject.getSelection();
 
   HUD.jsterm.clearOutput();
 
   let controller = top.document.commandDispatcher.
                    getControllerForCommand("cmd_copy");
   is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
 
-  console.log("Hello world! bug587617");
+  content.console.log("Hello world! bug587617");
+
+  waitForMessages({
+    webconsole: HUD,
+    messages: [{
+      text: "bug587617",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(([result]) => {
+    let msg = [...result.matched][0];
+    HUD.ui.output.selectMessage(msg);
 
-  waitForSuccess({
-    name: "console log 'Hello world!' message",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug587617") > -1;
-    },
-    successFn: function()
-    {
-      outputNode.selectedIndex = 0;
-      outputNode.focus();
+    outputNode.focus();
+
+    goUpdateCommand("cmd_copy");
+    controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
+    is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
 
-      goUpdateCommand("cmd_copy");
-      controller = top.document.commandDispatcher.
-        getControllerForCommand("cmd_copy");
-      is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
-      let selectedNode = outputNode.getItemAtIndex(0);
-      waitForClipboard(getExpectedClipboardText(selectedNode), clipboardSetup,
-                       testContextMenuCopy, testContextMenuCopy);
-    },
-    failureFn: finishTest,
+    let selection = HUD.iframeWindow.getSelection() + "";
+    isnot(selection.indexOf("bug587617"), -1,
+          "selection text includes 'bug587617'");
+
+    waitForClipboard(selection, () => goDoCommand("cmd_copy"),
+                     testContextMenuCopy, testContextMenuCopy);
   });
 }
 
 // Test that the context menu "Copy" (which has a different code path) works
 // properly as well.
 function testContextMenuCopy() {
-  let contextMenuId = outputNode.getAttribute("context");
+  let contextMenuId = outputNode.parentNode.getAttribute("context");
   let contextMenu = HUD.ui.document.getElementById(contextMenuId);
   ok(contextMenu, "the output node has a context menu");
 
   let copyItem = contextMenu.querySelector("*[command='cmd_copy']");
   ok(copyItem, "the context menu on the output node has a \"Copy\" item");
 
+  let selection = HUD.iframeWindow.getSelection() + "";
+
   copyItem.doCommand();
 
-  let selectedNode = outputNode.getItemAtIndex(0);
-
+  waitForClipboard(selection, () => goDoCommand("cmd_copy"),
+                   finishTest, finishTest);
   HUD = outputNode = null;
-  waitForClipboard(getExpectedClipboardText(selectedNode), clipboardSetup,
-    finishTest, finishTest);
 }
 
-function getExpectedClipboardText(aItem) {
-  return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
-         aItem.clipboardText;
-}
-
-function clipboardSetup() {
-  goDoCommand("cmd_copy");
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js
@@ -37,39 +37,32 @@ function tab2Loaded(aEvent) {
 }
 
 function tab1Reloaded(aEvent) {
   tab1.linkedBrowser.removeEventListener(aEvent.type, tab1Reloaded, true);
 
   let hud1 = HUDService.getHudByWindow(tab1.linkedBrowser.contentWindow);
   let outputNode1 = hud1.outputNode;
 
-  waitForSuccess({
-    name: "iframe network request displayed in tab1",
-    validatorFn: function()
-    {
-      let selector = ".webconsole-msg-url[value='" + TEST_IFRAME_URI +"']";
-      return outputNode1.querySelector(selector);
-    },
-    successFn: function()
-    {
-      let hud2 = HUDService.getHudByWindow(tab2.linkedBrowser.contentWindow);
-      let outputNode2 = hud2.outputNode;
+  waitForMessages({
+    webconsole: hud1,
+    messages: [{
+      text: TEST_IFRAME_URI,
+      category: CATEGORY_NETWORK,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(() => {
+    let hud2 = HUDService.getHudByWindow(tab2.linkedBrowser.contentWindow);
+    let outputNode2 = hud2.outputNode;
 
-      isnot(outputNode1, outputNode2,
-            "the two HUD outputNodes must be different");
+    isnot(outputNode1, outputNode2,
+      "the two HUD outputNodes must be different");
+
+    let msg = "Didn't find the iframe network request in tab2";
+    testLogEntry(outputNode2, TEST_IFRAME_URI, msg, true, true);
 
-      let msg = "Didn't find the iframe network request in tab2";
-      testLogEntry(outputNode2, TEST_IFRAME_URI, msg, true, true);
-
-      testEnd();
-    },
-    failureFn: testEnd,
+    closeConsole(tab2, function() {
+      gBrowser.removeTab(tab2);
+      tab1 = tab2 = null;
+      executeSoon(finishTest);
+    });
   });
 }
-
-function testEnd() {
-  closeConsole(tab2, function() {
-    gBrowser.removeTab(tab2);
-    tab1 = tab2 = null;
-    executeSoon(finishTest);
-  });
-}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
@@ -31,17 +31,17 @@ function tabLoad2(aEvent) {
     webconsole: HUD,
     messages: [{
       text: "test-console.html",
       category: CATEGORY_NETWORK,
       severity: SEVERITY_LOG,
     }],
   }).then(([result]) => {
     let msg = [...result.matched][0];
-    outputItem = msg.querySelector(".hud-clickable");
+    outputItem = msg.querySelector(".body .url");
     ok(outputItem, "found a network message");
     document.addEventListener("popupshown", networkPanelShown, false);
 
     // Send the mousedown and click events such that the network panel opens.
     EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
     EventUtils.sendMouseEvent({type: "click"}, outputItem);
   });
 }
@@ -93,22 +93,21 @@ function networkPanelHidden(aEvent) {
   EventUtils.sendMouseEvent({type: "click", button: 2},
     outputItem);
 
   executeSoon(function() {
     document.removeEventListener("popupshown", networkPanelShowFailure, false);
 
     // Done with the network output. Now test the jsterm output and the property
     // panel.
-    HUD.jsterm.execute("document", () => {
+    HUD.jsterm.execute("document", (msg) => {
       info("jsterm execute 'document' callback");
 
       HUD.jsterm.once("variablesview-open", onVariablesViewOpen);
-      let outputItem = outputNode
-                       .querySelector(".webconsole-msg-output .hud-clickable");
+      let outputItem = msg.querySelector(".body a");
       ok(outputItem, "jsterm output message found");
 
       // Send the mousedown and click events such that the property panel opens.
       EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
       EventUtils.sendMouseEvent({type: "click"}, outputItem);
     });
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
@@ -3,34 +3,39 @@
  * 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/. */
 
 const TEST_FILE = "test-network.html";
 
 function tabReload(aEvent) {
   browser.removeEventListener(aEvent.type, tabReload, true);
 
-  outputNode = hud.outputNode;
-
-  waitForSuccess({
-    name: "console.log() message displayed",
-    validatorFn: function()
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "running network console logging tests",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    },
     {
-      return outputNode.textContent
-             .indexOf("running network console logging tests") > -1;
+      text: "test-network.html",
+      category: CATEGORY_NETWORK,
+      severity: SEVERITY_LOG,
     },
-    successFn: function()
+    {
+      text: "test-image.png",
+      category: CATEGORY_NETWORK,
+      severity: SEVERITY_LOG,
+    },
     {
-      findLogEntry("test-network.html");
-      findLogEntry("test-image.png");
-      findLogEntry("testscript.js");
-      finishTest();
-    },
-    failureFn: finishTest,
-  });
+      text: "testscript.js",
+      category: CATEGORY_NETWORK,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(finishTest);
 }
 
 function test() {
   let jar = getJar(getRootDirectory(gTestPath));
   let dir = jar ?
             extractJarToTmp(jar) :
             getChromeDir(getResolvedURI(gTestPath));
   dir.append(TEST_FILE);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
@@ -33,38 +33,36 @@ function consoleOpened(aHud) {
     }],
   }).then(() => {
     waitForMessages({
       webconsole: hud,
       messages: [{
         text: "test-network.html",
         category: CATEGORY_NETWORK,
         severity: SEVERITY_LOG,
-        successFn: testScroll,
-        failureFn: finishTest,
       }],
     }).then(testScroll);
     content.location.reload();
   });
 }
 
-function testScroll() {
-  let msgNode = hud.outputNode.querySelector(".webconsole-msg-network");
-  ok(msgNode.classList.contains("hud-filtered-by-type"),
+function testScroll([result]) {
+  let scrollNode = hud.outputNode.parentNode;
+  let msgNode = [...result.matched][0];
+  ok(msgNode.classList.contains("filtered-by-type"),
     "network message is filtered by type");
-  ok(msgNode.classList.contains("hud-filtered-by-string"),
+  ok(msgNode.classList.contains("filtered-by-string"),
     "network message is filtered by string");
 
-  let scrollBox = hud.outputNode.scrollBoxObject.element;
-  ok(scrollBox.scrollTop > 0, "scroll location is not at the top");
+  ok(scrollNode.scrollTop > 0, "scroll location is not at the top");
 
   // Make sure the Web Console output is scrolled as near as possible to the
   // bottom.
-  let nodeHeight = hud.outputNode.querySelector(".hud-log").clientHeight;
-  ok(scrollBox.scrollTop >= scrollBox.scrollHeight - scrollBox.clientHeight -
+  let nodeHeight = msgNode.clientHeight;
+  ok(scrollNode.scrollTop >= scrollNode.scrollHeight - scrollNode.clientHeight -
      nodeHeight * 2, "scroll location is correct");
 
   hud.setFilterState("network", true);
   hud.setFilterState("networkinfo", true);
 
   executeSoon(finishTest);
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
@@ -143,87 +143,64 @@ function testGen() {
     inputValues[cpos][4] : printOutput;
 
   let consoleTest = inputValues[cpos][5] || inputValue;
 
   HUD.jsterm.clearOutput();
 
   // Test the console.log() output.
 
+  let outputItem;
+  function onExecute(msg) {
+    outputItem = msg;
+    subtestNext();
+  }
+
   HUD.jsterm.execute("console.log(" + consoleTest + ")");
 
-  waitForSuccess({
-    name: "console.log message for test #" + cpos,
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".hud-log");
-    },
-    successFn: subtestNext,
-    failureFn: testNext,
-  });
+  waitForMessages({
+    webconsole: HUD,
+    messages: [{
+      name: "console API output is correct for inputValues[" + cpos + "]",
+      text: consoleOutput,
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(subtestNext);
 
   yield undefined;
 
-  let outputItem = HUD.outputNode.querySelector(".hud-log:last-child");
-  ok(outputItem,
-    "found the window.console output line for inputValues[" + cpos + "]");
-  ok(outputItem.textContent.indexOf(consoleOutput) > -1,
-    "console API output is correct for inputValues[" + cpos + "]");
-
   HUD.jsterm.clearOutput();
 
   // Test jsterm print() output.
 
   HUD.jsterm.setInputValue("print(" + inputValue + ")");
-  HUD.jsterm.execute();
-
-  waitForSuccess({
-    name: "jsterm print() output for test #" + cpos,
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
-    },
-    successFn: subtestNext,
-    failureFn: testNext,
-  });
+  HUD.jsterm.execute(null, onExecute);
 
   yield undefined;
 
-  outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
-                                            "last-child");
   ok(outputItem,
     "found the jsterm print() output line for inputValues[" + cpos + "]");
   ok(outputItem.textContent.indexOf(printOutput) > -1,
     "jsterm print() output is correct for inputValues[" + cpos + "]");
 
   // Test jsterm execution output.
 
   HUD.jsterm.clearOutput();
   HUD.jsterm.setInputValue(inputValue);
-  HUD.jsterm.execute();
-
-  waitForSuccess({
-    name: "jsterm output for test #" + cpos,
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
-    },
-    successFn: subtestNext,
-    failureFn: testNext,
-  });
+  HUD.jsterm.execute(null, onExecute);
 
   yield undefined;
 
-  outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
-                                            "last-child");
   ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
   ok(outputItem.textContent.indexOf(expectedOutput) > -1,
     "jsterm output is correct for inputValues[" + cpos + "]");
 
-  let messageBody = outputItem.querySelector(".webconsole-msg-body");
+  let messageBody = outputItem.querySelector(".body a") ||
+                    outputItem.querySelector(".body");
   ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
 
   // Test click on output.
   let eventHandlerID = eventHandlers.length + 1;
 
   let variablesViewShown = function(aEvent, aView, aOptions) {
     if (aOptions.label.indexOf(expectedOutput) == -1) {
       return;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
@@ -7,16 +7,18 @@
  *  Mihai Șucan <mihai.sucan@gmail.com>
  *
  * ***** END LICENSE BLOCK ***** */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-601177-log-levels.html";
 
 function test()
 {
+  //requestLongerTimeout(20);
+  //FIXME
   Services.prefs.setBoolPref("javascript.options.strict", true);
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("javascript.options.strict");
   });
 
   addTab("data:text/html;charset=utf-8,Web Console test for bug 601177: log levels");
 
   browser.addEventListener("load", function onLoad() {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
@@ -21,53 +21,37 @@ function consoleOpened(HUD) {
   }
 
   content.console.log(longMessage);
 
   for (let i = 0; i < 50; i++) {
     content.console.log("test message " + i);
   }
 
-  HUD.jsterm.execute("1+1");
+  HUD.jsterm.execute("1+1", performTest);
 
-  function performTest() {
-    let scrollBox = HUD.outputNode.scrollBoxObject.element;
-    isnot(scrollBox.scrollTop, 0, "scroll location is not at the top");
+  function performTest(node) {
+    let scrollNode = HUD.outputNode.parentNode;
+    isnot(scrollNode.scrollTop, 0, "scroll location is not at the top");
 
-    let node = HUD.outputNode.getItemAtIndex(HUD.outputNode.itemCount - 1);
     let rectNode = node.getBoundingClientRect();
-    let rectOutput = HUD.outputNode.getBoundingClientRect();
+    let rectOutput = scrollNode.getBoundingClientRect();
 
     // Visible scroll viewport.
-    let height = scrollBox.scrollHeight - scrollBox.scrollTop;
+    let height = scrollNode.scrollHeight - scrollNode.scrollTop;
 
     // Top position of the last message node, relative to the outputNode.
-    let top = rectNode.top - rectOutput.top;
+    let top = rectNode.top + scrollNode.scrollTop;
+    let bottom = top + node.clientHeight;
+    info("output height " + height + " node top " + top + " node bottom " + bottom + " node height " + node.clientHeight);
 
-    // Bottom position of the last message node, relative to the outputNode.
-    let bottom = rectNode.bottom - rectOutput.top;
-
-    ok(top >= 0 && Math.floor(bottom) <= height + 1,
-       "last message is visible");
+    ok(top >= 0 && bottom <= height, "last message is visible");
 
     finishTest();
   };
-
-  waitForSuccess({
-    name: "console output displayed",
-    validatorFn: function()
-    {
-      return HUD.outputNode.itemCount >= 103;
-    },
-    successFn: performTest,
-    failureFn: function() {
-      info("itemCount: " + HUD.outputNode.itemCount);
-      finishTest();
-    },
-  });
 }
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console test for bug 601352");
   browser.addEventListener("load", function tabLoad(aEvent) {
     browser.removeEventListener(aEvent.type, tabLoad, true);
     openConsole(null, consoleOpened);
   }, true);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
@@ -5,78 +5,49 @@
 const TEST_URI = 'data:text/html;charset=utf-8,<div style="-moz-opacity:0;">test repeated' +
                  ' css warnings</div><p style="-moz-opacity:0">hi</p>';
 
 function onContentLoaded()
 {
   browser.removeEventListener("load", onContentLoaded, true);
 
   let HUD = HUDService.getHudByWindow(content);
-  let jsterm = HUD.jsterm;
-  let outputNode = HUD.outputNode;
 
   let cssWarning = "Unknown property '-moz-opacity'.  Declaration dropped.";
-  let textFound = false;
-  let repeats = 0;
 
-  function displayResults()
-  {
-    ok(textFound, "css warning was found");
-    is(repeats, 2, "The unknown CSS property warning is displayed only once");
-  }
-
-  waitForSuccess({
-    name: "2 repeated CSS warnings",
-    validatorFn: () => {
-      let node = outputNode.querySelector(".webconsole-msg-cssparser");
-      if (!node) {
-        return false;
-      }
-
-      textFound = node.textContent.indexOf(cssWarning) > -1;
-      repeats = node.querySelector(".webconsole-msg-repeat")
-                .getAttribute("value");
-      return textFound && repeats == 2;
-    },
-    successFn: () => {
-      displayResults();
-      testConsoleLogRepeats();
-    },
-    failureFn: () => {
-      displayResults();
-      finishTest();
-    },
-  });
+  waitForMessages({
+    webconsole: HUD,
+    messages: [{
+      text: cssWarning,
+      category: CATEGORY_CSS,
+      severity: SEVERITY_WARNING,
+      repeats: 2,
+    }],
+  }).then(testConsoleLogRepeats);
 }
 
 function testConsoleLogRepeats()
 {
   let HUD = HUDService.getHudByWindow(content);
   let jsterm = HUD.jsterm;
-  let outputNode = HUD.outputNode;
 
   jsterm.clearOutput();
 
   jsterm.setInputValue("for (let i = 0; i < 10; ++i) console.log('this is a line of reasonably long text that I will use to verify that the repeated text node is of an appropriate size.');");
   jsterm.execute();
 
-  waitForSuccess({
-    timeout: 10000,
-    name: "10 repeated console.log messages",
-    validatorFn: function()
-    {
-      let node = outputNode.querySelector(".webconsole-msg-console");
-      return node && node.childNodes[3].firstChild.getAttribute("value") == 10;
-    },
-    successFn: finishTest,
-    failureFn: function() {
-      info("output content: " + outputNode.textContent);
-      finishTest();
-    },
-  });
+  waitForMessages({
+    webconsole: HUD,
+    messages: [{
+      text: "this is a line of reasonably long text",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      repeats: 10,
+    }],
+  }).then(finishTest);
 }
 
 /**
  * Unit test for bug 611795:
  * Repeated CSS messages get collapsed into one.
  */
 function test()
 {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js
@@ -9,33 +9,33 @@
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 613280";
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, function(HUD) {
       content.console.log("foobarBazBug613280");
-      waitForSuccess({
-        name: "a message is displayed",
-        validatorFn: function()
-        {
-          return HUD.outputNode.itemCount > 0;
-        },
-        successFn: performTest.bind(null, HUD),
-        failureFn: finishTest,
-      });
+      waitForMessages({
+        webconsole: HUD,
+        messages: [{
+          text: "foobarBazBug613280",
+          category: CATEGORY_WEBDEV,
+          severity: SEVERITY_LOG,
+        }],
+      }).then(performTest.bind(null, HUD));
     });
   }, true);
 }
 
-function performTest(HUD) {
+function performTest(HUD, [result]) {
+  let msg = [...result.matched][0];
   let input = HUD.jsterm.inputNode;
   let selection = getSelection();
-  let contentSelection = browser.contentWindow.wrappedJSObject.getSelection();
+  let contentSelection = content.getSelection();
 
   let clipboard_setup = function() {
     goDoCommand("cmd_copy");
   };
 
   let clipboard_copy_done = function() {
     finishTest();
   };
@@ -57,29 +57,24 @@ function performTest(HUD) {
 
     goUpdateCommand("cmd_copy");
   }
 
   let controller = top.document.commandDispatcher.
                    getControllerForCommand("cmd_copy");
   is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
 
-  HUD.jsterm.execute("'bug613280: hello world!'");
-
-  HUD.outputNode.selectedIndex = HUD.outputNode.itemCount - 1;
+  HUD.ui.output.selectMessage(msg);
   HUD.outputNode.focus();
 
   goUpdateCommand("cmd_copy");
 
   controller = top.document.commandDispatcher.
                getControllerForCommand("cmd_copy");
   is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
 
-  ok(HUD.outputNode.selectedItem, "we have a selected message");
+  let selectionText = HUD.iframeWindow.getSelection() + "";
+  isnot(selectionText.indexOf("foobarBazBug613280"), -1,
+        "selection text includes 'foobarBazBug613280'");
 
-  waitForClipboard(getExpectedClipboardText(HUD.outputNode.selectedItem),
-    clipboard_setup, clipboard_copy_done, clipboard_copy_done);
+  waitForClipboard(selectionText, clipboard_setup, clipboard_copy_done,
+                   clipboard_copy_done);
 }
-
-function getExpectedClipboardText(aItem) {
-  return "[" + WCU_l10n.timestampString(aItem.timestamp) + "] " +
-         aItem.clipboardText;
-}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
@@ -11,85 +11,121 @@ let hud, testDriver;
 
 function testNext() {
   testDriver.next();
 }
 
 function testGen() {
   hud.jsterm.clearOutput();
   let outputNode = hud.outputNode;
-  let scrollBox = outputNode.scrollBoxObject.element;
+  let scrollBox = outputNode.parentNode;
 
   for (let i = 0; i < 150; i++) {
     content.console.log("test message " + i);
   }
 
-  waitForSuccess({
-    name: "150 console.log messages displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-log").length == 150;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "test message 149",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  let oldScrollTop = scrollBox.scrollTop;
-  ok(oldScrollTop > 0, "scroll location is not at the top");
+  ok(scrollBox.scrollTop > 0, "scroll location is not at the top");
 
   // scroll to the first node
   outputNode.focus();
 
+  scrollBox.onscroll = () => {
+    if (scrollBox.scrollTop == 0) {
+      // Wait for scroll to 0.
+      return;
+    }
+    scrollBox.onscroll = null;
+    isnot(scrollBox.scrollTop, 0, "scroll location updated (moved to top)");
+    testNext();
+  };
   EventUtils.synthesizeKey("VK_HOME", {});
 
-  let topPosition = scrollBox.scrollTop;
-  isnot(topPosition, oldScrollTop, "scroll location updated (moved to top)");
+  yield undefined;
 
   // add a message and make sure scroll doesn't change
   content.console.log("test message 150");
 
-  waitForSuccess({
-    name: "console.log message no. 151 displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-log").length == 151;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "test message 150",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  is(scrollBox.scrollTop, topPosition, "scroll location is still at the top");
+  scrollBox.onscroll = () => {
+    if (scrollBox.scrollTop != 0) {
+      // Wait for scroll to stabilize at the top.
+      return;
+    }
+    scrollBox.onscroll = null;
+    is(scrollBox.scrollTop, 0, "scroll location is still at the top");
+    testNext();
+  };
+
+  // Make sure that scroll stabilizes at the top. executeSoon() is needed for
+  // the yield to work.
+  executeSoon(scrollBox.onscroll);
+
+  yield undefined;
 
   // scroll back to the bottom
   outputNode.lastChild.focus();
-  EventUtils.synthesizeKey("VK_END", {});
 
-  oldScrollTop = outputNode.scrollTop;
+  scrollBox.onscroll = () => {
+    if (scrollBox.scrollTop == 0) {
+      // Wait for scroll to bottom.
+      return;
+    }
+    scrollBox.onscroll = null;
+    isnot(scrollBox.scrollTop, 0, "scroll location updated (moved to bottom)");
+    testNext();
+  };
+  EventUtils.synthesizeKey("VK_END", {});
+  yield;
+
+  let oldScrollTop = scrollBox.scrollTop;
 
   content.console.log("test message 151");
 
-  waitForSuccess({
-    name: "console.log message no. 152 displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-log").length == 152;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "test message 151",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(() => {
+    scrollBox.onscroll = () => {
+      if (scrollBox.scrollTop == oldScrollTop) {
+        // Wait for scroll to change.
+        return;
+      }
+      scrollBox.onscroll = null;
+      isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
+      testNext();
+    };
   });
 
   yield undefined;
 
-  isnot(scrollBox.scrollTop, oldScrollTop,
-        "scroll location updated (moved to bottom)");
-
   hud = testDriver = null;
   finishTest();
   
   yield undefined;
 }
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console test for bug 613642: remember scroll location");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js
@@ -12,79 +12,75 @@ let hud, testDriver;
 function testNext() {
   testDriver.next();
 }
 
 function testGen() {
   hud.jsterm.clearOutput();
 
   let outputNode = hud.outputNode;
-  let oldPref = Services.prefs.getIntPref("devtools.hud.loglimit.console");
 
   Services.prefs.setIntPref("devtools.hud.loglimit.console", 140);
-  let scrollBoxElement = outputNode.scrollBoxObject.element;
-  let boxObject = outputNode.scrollBoxObject;
+  let scrollBoxElement = outputNode.parentNode;
 
   for (let i = 0; i < 150; i++) {
     content.console.log("test message " + i);
   }
 
-  waitForSuccess({
-    name: "150 console.log messages displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-log").length == 140;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "test message 149",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
   let oldScrollTop = scrollBoxElement.scrollTop;
-  ok(oldScrollTop > 0, "scroll location is not at the top");
+  isnot(oldScrollTop, 0, "scroll location is not at the top");
 
   let firstNode = outputNode.firstChild;
   ok(firstNode, "found the first message");
 
-  let msgNode = outputNode.querySelectorAll("richlistitem")[80];
+  let msgNode = outputNode.children[80];
   ok(msgNode, "found the 80th message");
 
   // scroll to the middle message node
-  boxObject.ensureElementIsVisible(msgNode);
+  msgNode.scrollIntoView(false);
 
   isnot(scrollBoxElement.scrollTop, oldScrollTop,
         "scroll location updated (scrolled to message)");
 
   oldScrollTop = scrollBoxElement.scrollTop;
 
   // add a message
   content.console.log("hello world");
 
-  waitForSuccess({
-    name: "console.log message #151 displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("hello world") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "hello world",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
   // Scroll location needs to change, because one message is also removed, and
   // we need to scroll a bit towards the top, to keep the current view in sync.
   isnot(scrollBoxElement.scrollTop, oldScrollTop,
         "scroll location updated (added a message)");
 
   isnot(outputNode.firstChild, firstNode,
         "first message removed");
 
-  Services.prefs.setIntPref("devtools.hud.loglimit.console", oldPref);
+  Services.prefs.clearUserPref("devtools.hud.loglimit.console");
 
   hud = testDriver = null;
   finishTest();
 
   yield undefined;
 }
 
 function test() {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js
@@ -5,62 +5,50 @@
  *
  * Contributor(s):
  *   Mihai Șucan <mihai.sucan@gmail.com>
  */
 
 function consoleOpened(hud) {
   hud.jsterm.clearOutput();
 
-  let outputNode = hud.outputNode;
-  let boxObject = outputNode.scrollBoxObject.element;
+  let scrollNode = hud.outputNode.parentNode;
 
   for (let i = 0; i < 150; i++) {
     content.console.log("test message " + i);
   }
 
   let oldScrollTop = -1;
 
-  waitForSuccess({
-    name: "console.log messages displayed",
-    validatorFn: function()
-    {
-      return outputNode.itemCount == 150;
-    },
-    successFn: function()
-    {
-      oldScrollTop = boxObject.scrollTop;
-      ok(oldScrollTop > 0, "scroll location is not at the top");
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "test message 149",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(() => {
+    oldScrollTop = scrollNode.scrollTop;
+    isnot(oldScrollTop, 0, "scroll location is not at the top");
 
-      hud.jsterm.execute("'hello world'");
-
-      waitForSuccess(waitForExecute);
-    },
-    failureFn: finishTest,
+    hud.jsterm.execute("'hello world'", onExecute);
   });
 
-  let waitForExecute = {
-    name: "jsterm output displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      isnot(boxObject.scrollTop, oldScrollTop, "scroll location updated");
+  function onExecute(msg)
+  {
+    isnot(scrollNode.scrollTop, oldScrollTop, "scroll location updated");
+
+    oldScrollTop = scrollNode.scrollTop;
 
-      oldScrollTop = boxObject.scrollTop;
-      outputNode.scrollBoxObject.ensureElementIsVisible(outputNode.lastChild);
-
-      is(boxObject.scrollTop, oldScrollTop, "scroll location is the same");
+    msg.scrollIntoView(false);
 
-      finishTest();
-    },
-    failureFn: finishTest,
-  };
+    is(scrollNode.scrollTop, oldScrollTop, "scroll location is the same");
+
+    finishTest();
+  }
 }
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console test for bug 614793: jsterm result scroll");
   browser.addEventListener("load", function onLoad(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
@@ -26,17 +26,17 @@ function test() {
 }
 
 function performTest(results) {
   let HUD = HUDService.getHudByWindow(content);
 
   let networkMessage = [...results[0].matched][0];
   ok(networkMessage, "network message element");
 
-  let networkLink = networkMessage.querySelector(".webconsole-msg-link");
+  let networkLink = networkMessage.querySelector(".url");
   ok(networkLink, "found network message link");
 
   let popupset = document.getElementById("mainPopupSet");
   ok(popupset, "found #mainPopupSet");
 
   let popupsShown = 0;
   let hiddenPopups = 0;
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js
@@ -7,60 +7,32 @@
  *   Mihai Sucan <mihai.sucan@gmail.com>
  */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html";
 
 function test$(HUD) {
   HUD.jsterm.clearOutput();
 
-  HUD.jsterm.setInputValue("$(document.body)");
-  HUD.jsterm.execute();
+  HUD.jsterm.execute("$(document.body)", (msg) => {
+    ok(msg.textContent.indexOf("<p>") > -1,
+       "jsterm output is correct for $()");
 
-  waitForSuccess({
-    name: "jsterm output for $()",
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
-    },
-    successFn: function()
-    {
-      let outputItem = HUD.outputNode.
-                       querySelector(".webconsole-msg-output:last-child");
-      ok(outputItem.textContent.indexOf("<p>") > -1,
-         "jsterm output is correct for $()");
-
-      test$$(HUD);
-    },
-    failureFn: test$$.bind(null, HUD),
+    test$$(HUD);
   });
 }
 
 function test$$(HUD) {
   HUD.jsterm.clearOutput();
 
-  HUD.jsterm.setInputValue("$$(document)");
-  HUD.jsterm.execute();
-
-  waitForSuccess({
-    name: "jsterm output for $$()",
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
-    },
-    successFn: function()
-    {
-      let outputItem = HUD.outputNode.
-                       querySelector(".webconsole-msg-output:last-child");
-      ok(outputItem.textContent.indexOf("621644") > -1,
-         "jsterm output is correct for $$()");
-
-      executeSoon(finishTest);
-    },
-    failureFn: finishTest,
+  HUD.jsterm.setInputValue();
+  HUD.jsterm.execute("$$(document)", (msg) => {
+    ok(msg.textContent.indexOf("621644") > -1,
+       "jsterm output is correct for $$()");
+    finishTest();
   });
 }
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, test$);
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_626484_output_copy_order.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-let itemsSet, HUD, outputNode;
-
-function test() {
-  addTab("data:text/html;charset=utf-8,Web Console test for bug 626484");
-  browser.addEventListener("load", function tabLoaded(aEvent) {
-    browser.removeEventListener(aEvent.type, tabLoaded, true);
-    openConsole(null, consoleOpened);
-  }, true);
-}
-
-
-function consoleOpened(aHud) {
-  HUD = aHud;
-  outputNode = HUD.outputNode;
-  HUD.jsterm.clearOutput();
-
-  let console = content.wrappedJSObject.console;
-  console.log("The first line.");
-  console.log("The second line.");
-  console.log("The last line.");
-  itemsSet = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1],
-    [2, 1, 0]];
-
-  waitForSuccess({
-    name: "console.log messages displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-log").length == 3;
-    },
-    successFn: nextTest,
-    failureFn: finishTest,
-  });
-}
-
-function nextTest() {
-  if (itemsSet.length === 0) {
-    outputNode.clearSelection();
-    HUD.jsterm.clearOutput();
-    HUD = outputNode = null;
-    executeSoon(finishTest);
-  }
-  else {
-    outputNode.clearSelection();
-    let items = itemsSet.shift();
-    items.forEach(function (index) {
-      outputNode.addItemToSelection(outputNode.getItemAtIndex(index));
-    });
-    outputNode.focus();
-    waitForClipboard(getExpectedClipboardText(items.length),
-      clipboardSetup, nextTest, nextTest);
-  }
-}
-
-function getExpectedClipboardText(aItemCount) {
-  let expectedClipboardText = [];
-  for (let i = 0; i < aItemCount; i++) {
-    let item = outputNode.getItemAtIndex(i);
-    expectedClipboardText.push("[" +
-      WCU_l10n.timestampString(item.timestamp) + "] " +
-      item.clipboardText);
-  }
-  return expectedClipboardText.join("\n");
-}
-
-function clipboardSetup() {
-  goDoCommand("cmd_copy");
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -50,31 +50,20 @@ function consoleOpened(HUD) {
   is(completion, null, "no matches for iter2");
 
   completion = JSPropertyProvider(win, "window.");
   ok(completion, "matches available for window");
   ok(completion.matches.length, "matches available for window (length)");
 
   jsterm.clearOutput();
 
-  jsterm.execute("window");
-
-  waitForSuccess({
-    name: "jsterm window object output",
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
-      let node = HUD.outputNode.querySelector(".webconsole-msg-output");
-      EventUtils.synthesizeMouse(node, 2, 2, {}, HUD.iframeWindow);
-    },
-    failureFn: finishTest,
+  jsterm.execute("window", (msg) => {
+    jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
+    let anchor = msg.querySelector(".body a");
+    EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
   });
 }
 
 function testVariablesView(aWebconsole, aEvent, aView) {
   findVariableViewProperties(aView, [
     { name: "gen1", isGenerator: true },
     { name: "gen2", isGenerator: true },
     { name: "iter1", isIterator: true },
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
@@ -180,17 +180,17 @@ function testLiveFilteringOnSearchString
 
   HUDService.lastFinishedRequest.callback = null;
   lastRequest = null;
   requestCallback = null;
   finishTest();
 }
 
 function countMessageNodes() {
-  let messageNodes = hud.outputNode.querySelectorAll(".hud-msg-node");
+  let messageNodes = hud.outputNode.querySelectorAll(".message");
   let displayedMessageNodes = 0;
   let view = hud.iframeWindow;
   for (let i = 0; i < messageNodes.length; i++) {
     let computedStyle = view.getComputedStyle(messageNodes[i], null);
     if (computedStyle.display !== "none")
       displayedMessageNodes++;
   }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
@@ -40,50 +40,50 @@ function populateConsole(aHudRef) {
                                             "css log " + i);
     aHudRef.ui.outputMessage(CATEGORY_CSS, node);
   }
 }
 
 function testCSSPruning(hudRef) {
   populateConsoleRepeats(hudRef);
 
-  let waitForNoRepeatedNodes = {
-    name:  "number of nodes is LOG_LIMIT",
-    validatorFn: function()
-    {
-      return countMessageNodes() == LOG_LIMIT;
-    },
-    successFn: function()
-    {
+  waitForMessages({
+    webconsole: hudRef,
+    messages: [{
+      text: "css log x",
+      category: CATEGORY_CSS,
+      severity: SEVERITY_WARNING,
+      repeats: 5,
+    }],
+  }).then(() => {
+    populateConsole(hudRef);
+    waitForMessages({
+      webconsole: hudRef,
+      messages: [{
+        text: "css log 0",
+        category: CATEGORY_CSS,
+        severity: SEVERITY_WARNING,
+      },
+      {
+        text: "css log 24", // LOG_LIMIT + 5
+        category: CATEGORY_CSS,
+        severity: SEVERITY_WARNING,
+      }],
+    }).then(([result]) => {
+      is(countMessageNodes(), LOG_LIMIT, "number of messages");
+
       is(Object.keys(hudRef.ui._repeatNodes).length, LOG_LIMIT,
          "repeated nodes pruned from repeatNodes");
 
-      let msg = hudRef.outputNode.querySelector(".webconsole-msg-cssparser " +
-                                                ".webconsole-msg-repeat");
-      is(msg.getAttribute("value"), 1,
+      let msg = [...result.matched][0];
+      let repeats = msg.querySelector(".repeats");
+      is(repeats.getAttribute("value"), 1,
          "repeated nodes pruned from repeatNodes (confirmed)");
 
       finishTest();
-    },
-    failureFn: finishTest,
-  };
-
-  waitForSuccess({
-    name: "repeated nodes in cssNodes",
-    validatorFn: function()
-    {
-      let msg = hudRef.outputNode.querySelector(".webconsole-msg-cssparser " +
-                                                ".webconsole-msg-repeat");
-      return msg && msg.getAttribute("value") == 5;
-    },
-    successFn: function()
-    {
-      populateConsole(hudRef);
-      waitForSuccess(waitForNoRepeatedNodes);
-    },
-    failureFn: finishTest,
+    });
   });
 }
 
 function countMessageNodes() {
   let outputNode = HUDService.getHudByWindow(content).outputNode;
-  return outputNode.querySelectorAll(".hud-msg-node").length;
+  return outputNode.querySelectorAll(".message").length;
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js
@@ -151,24 +151,26 @@ function loadImage() {
     return;
   }
 
   is(gCounter, 11, "loaded 11 files");
 
   waitForMessages({
     webconsole: hud,
     messages: [{
-      text: "test-image.png?_fubar=10",
+      text: "test-image.png",
+      url: "test-image.png?_fubar=10",
       category: CATEGORY_NETWORK,
       severity: SEVERITY_LOG,
     }],
   }).then(() => {
-    testLogEntry(outputNode, "test-image.png?_fubar=0", "first message is pruned", false, true);
-    findLogEntry("test-image.png?_fubar=1");
-    // Check if the sentinel entry is still there.
+    let msgs = outputNode.querySelectorAll(".message[category=network]");
+    is(msgs.length, 10, "number of network messages");
+    isnot(msgs[0].url.indexOf("fubar=1"), -1, "first network message");
+    isnot(msgs[1].url.indexOf("fubar=2"), -1, "second network message");
     findLogEntry("testing Net limits");
 
     Services.prefs.clearUserPref("devtools.hud.loglimit.network");
     testCssLimits();
   });
 }
 
 function testCssLimits() {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -69,31 +69,20 @@ function autocompletePopupHidden()
     failureFn: finishTest,
   });
 }
 
 function testPropertyPanel()
 {
   let jsterm = gHUD.jsterm;
   jsterm.clearOutput();
-  jsterm.execute("document");
-
-  waitForSuccess({
-    name: "jsterm document object output",
-    validatorFn: function()
-    {
-      return gHUD.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      jsterm.once("variablesview-fetched", onVariablesViewReady);
-      let node = gHUD.outputNode.querySelector(".webconsole-msg-output");
-      EventUtils.synthesizeMouse(node, 2, 2, {}, gHUD.iframeWindow);
-    },
-    failureFn: finishTest,
+  jsterm.execute("document", (msg) => {
+    jsterm.once("variablesview-fetched", onVariablesViewReady);
+    let anchor = msg.querySelector(".body a");
+    EventUtils.synthesizeMouse(anchor, 2, 2, {}, gHUD.iframeWindow);
   });
 }
 
 function onVariablesViewReady(aEvent, aView)
 {
   findVariableViewProperties(aView, [
     { name: "body", value: "HTMLBodyElement" },
   ], { webconsole: gHUD }).then(finishTest);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -81,43 +81,37 @@ function performWebConsoleTests(hud)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let jsterm = hud.jsterm;
   outputNode = hud.outputNode;
 
   jsterm.clearOutput();
   jsterm.execute("$0", onNodeOutput);
 
-  function onNodeOutput()
+  function onNodeOutput(node)
   {
-    let node = outputNode.querySelector(".webconsole-msg-output");
     isnot(node.textContent.indexOf("[object HTMLHeadingElement"), -1,
           "correct output for $0");
 
     jsterm.clearOutput();
     jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate);
   }
 
-  function onNodeUpdate()
+  function onNodeUpdate(node)
   {
-    let node = outputNode.querySelector(".webconsole-msg-output");
     isnot(node.textContent.indexOf("bug653531"), -1,
           "correct output for $0.textContent");
     let inspector = gDevTools.getToolbox(target).getPanel("inspector");
     is(inspector.selection.node.textContent, "bug653531",
        "node successfully updated");
 
-    executeSoon(finishUp);
+    executeSoon(finishTest);
   }
 }
 
-function finishUp() {
-  finishTest();
-}
-
 function test()
 {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     waitForFocus(createDocument, content);
   }, true);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
@@ -1,16 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests that console.group/groupEnd works as intended.
-const GROUP_INDENT = 12;
 
 let testDriver, hud;
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console test for bug 664131: Expand console " +
          "object with group methods");
   browser.addEventListener("load", function onLoad(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad, true);
@@ -28,106 +27,81 @@ function testNext() {
 
 function testGen() {
   outputNode = hud.outputNode;
 
   hud.jsterm.clearOutput();
 
   content.console.group("bug664131a");
 
-  waitForSuccess({
-    name: "console.group displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug664131a") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bug664131a",
+      consoleGroup: 1,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
-  is(msg.length, 1, "one message node displayed");
-  is(msg[0].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
-
   content.console.log("bug664131a-inside");
 
-  waitForSuccess({
-    name: "console.log message displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug664131a-inside") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bug664131a-inside",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      groupDepth: 1,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
-  is(msg.length, 2, "two message nodes displayed");
-  is(msg[1].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
-
   content.console.groupEnd("bug664131a");
   content.console.log("bug664131-outside");
 
-  waitForSuccess({
-    name: "console.log message displayed after groupEnd()",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug664131-outside") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bug664131-outside",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      groupDepth: 0,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
-  is(msg.length, 3, "three message nodes displayed");
-  is(msg[2].style.marginLeft, "0px", "correct group indent found");
-
   content.console.groupCollapsed("bug664131b");
 
-  waitForSuccess({
-    name: "console.groupCollapsed displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug664131b") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bug664131b",
+      consoleGroup: 1,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
-  is(msg.length, 4, "four message nodes displayed");
-  is(msg[3].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
-
-
   // Test that clearing the console removes the indentation.
   hud.jsterm.clearOutput();
   content.console.log("bug664131-cleared");
 
-  waitForSuccess({
-    name: "console.log displayed after clearOutput",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("bug664131-cleared") > -1;
-    },
-    successFn: testNext,
-    failureFn: finishTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "bug664131-cleared",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+      groupDepth: 0,
+    }],
+  }).then(testNext);
 
   yield undefined;
 
-  msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
-  is(msg.length, 1, "one message node displayed");
-  is(msg[0].style.marginLeft, "0px", "correct group indent found");
-
   testDriver = hud = null;
   finishTest();
 
   yield undefined;
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
@@ -32,43 +32,41 @@ function testMixedContent(hud) {
     messages: [{
       text: "example.com",
       category: CATEGORY_NETWORK,
       severity: SEVERITY_WARNING,
     }],
   }).then((results) => {
     let msg = [...results[0].matched][0];
     ok(msg, "page load logged");
-
-    let mixedContent = msg.querySelector(".webconsole-mixed-content");
-    ok(mixedContent, ".webconsole-mixed-content element");
+    ok(msg.classList.contains("mixed-content"), ".mixed-content element");
 
-    let link = msg.querySelector(".webconsole-mixed-content-link");
+    let link = msg.querySelector(".learn-more-link");
     ok(link, "mixed content link element");
-    is(link.value, "[Mixed Content]", "link text is accurate");
+    is(link.textContent, "[Mixed Content]", "link text is accurate");
 
     let oldOpenLink = hud.openLink;
     let linkOpened = false;
     hud.openLink = (url) => {
-      is(url, "https://developer.mozilla.org/Security/MixedContent",
+      is(url, "https://developer.mozilla.org/docs/Security/MixedContent",
          "url opened");
       linkOpened = true;
     };
 
     EventUtils.synthesizeMouse(link, 2, 2, {}, link.ownerDocument.defaultView);
 
     ok(linkOpened, "clicking the Mixed Content link opened a page");
 
     hud.openLink = oldOpenLink;
 
-    ok(!msg.classList.contains("hud-filtered-by-type"), "message is not filtered");
+    ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
 
     hud.setFilterState("netwarn", false);
 
-    ok(msg.classList.contains("hud-filtered-by-type"), "message is filtered");
+    ok(msg.classList.contains("filtered-by-type"), "message is filtered");
 
     hud.setFilterState("netwarn", true);
 
     Services.prefs.clearUserPref("security.mixed_content.block_display_content");
     Services.prefs.clearUserPref("security.mixed_content.block_active_content");
 
     finishTest();
   });
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js
@@ -33,29 +33,28 @@ function test() {
           },
           {
             name: "Insecure form action error displayed successfully",
             text: INSECURE_FORM_ACTION_MSG,
             category: CATEGORY_SECURITY,
             severity: SEVERITY_WARNING
           },
         ],
-      }).then( () => testClickOpenNewTab(hud));
+      }).then(testClickOpenNewTab.bind(null, hud));
     });
   }, true);
 }
 
-function testClickOpenNewTab(hud) {
-  let warningNode = hud.outputNode.querySelector(
-    ".webconsole-msg-body .webconsole-learn-more-link");
+function testClickOpenNewTab(hud, [result]) {
+  let msg = [...result.matched][0];
+  let warningNode = msg.querySelector(".learn-more-link");
+  ok(warningNode, "learn more link");
 
-  /*
-   * Invoke the click event and check if a new tab would open to the correct
-   * page
-   */
+  // Invoke the click event and check if a new tab would open to the correct
+  // page
   let linkOpened = false;
   let oldOpenUILinkIn = window.openUILinkIn;
   window.openUILinkIn = function(aLink) {
     if (aLink == INSECURE_PASSWORDS_URI) {
       linkOpened = true;
     }
   }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
@@ -72,48 +72,48 @@ function testOnNetActivity() {
       category: CATEGORY_NETWORK,
       severity: SEVERITY_LOG,
     }],
   }).then(onNetworkMessage);
 }
 
 function onNetworkMessage(aResults) {
   outputNode.focus();
-  outputNode.selectedItem = [...aResults[0].matched][0];
+  let msg = [...aResults[0].matched][0];
+  ok(msg, "network message");
+  HUD.ui.output.selectMessage(msg);
 
   let currentTab = gBrowser.selectedTab;
   let newTab = null;
 
   gBrowser.tabContainer.addEventListener("TabOpen", function onOpen(aEvent) {
     gBrowser.tabContainer.removeEventListener("TabOpen", onOpen, true);
     newTab = aEvent.target;
     newTab.linkedBrowser.addEventListener("load", onTabLoaded, true);
   }, true);
 
   function onTabLoaded() {
     newTab.linkedBrowser.removeEventListener("load", onTabLoaded, true);
     gBrowser.removeTab(newTab);
     gBrowser.selectedTab = currentTab;
-    executeSoon(testOnNetActivity_contextmenu);
+    executeSoon(testOnNetActivity_contextmenu.bind(null, msg));
   }
 
   // Check if the command is enabled for a network message.
   goUpdateCommand(COMMAND_NAME);
   let controller = top.document.commandDispatcher
                    .getControllerForCommand(COMMAND_NAME);
   ok(controller.isCommandEnabled(COMMAND_NAME),
      COMMAND_NAME + " should be enabled.");
 
   // Try to open the URL.
   goDoCommand(COMMAND_NAME);
 }
 
-function testOnNetActivity_contextmenu() {
-  let target = outputNode.querySelector(".webconsole-msg-network");
-
+function testOnNetActivity_contextmenu(msg) {
   outputNode.focus();
-  outputNode.selectedItem = target;
+  HUD.ui.output.selectMessage(msg);
 
-  waitForContextMenu(contextMenu, target, () => {
+  waitForContextMenu(contextMenu, msg, () => {
     let isShown = !contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isShown, CONTEXT_MENU_ID + " should be shown.");
   }, finishTest);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -21,38 +21,41 @@ function test()
 }
 
 function testViewSource(aHud)
 {
   registerCleanupFunction(function() {
     nodes = dbg = toolbox = target = index = src = line = null;
   });
 
-  let JSSelector = ".webconsole-msg-exception .webconsole-location";
-  let consoleSelector = ".webconsole-msg-console .webconsole-location";
-
-  waitForSuccess({
-    name: "find the location node",
-    validatorFn: function()
-    {
-      return aHud.outputNode.querySelector(JSSelector) &&
-             aHud.outputNode.querySelector(consoleSelector);
+  waitForMessages({
+    webconsole: aHud,
+    messages: [{
+      text: "document.bar",
+      category: CATEGORY_JS,
+      severity: SEVERITY_ERROR,
     },
-    successFn: function()
     {
-      nodes = [aHud.outputNode.querySelector(JSSelector),
-               aHud.outputNode.querySelector(consoleSelector)];
+      text: "Blah Blah",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(([exceptionRule, consoleRule]) => {
+    let exceptionMsg = [...exceptionRule.matched][0];
+    let consoleMsg = [...consoleRule.matched][0];
+    nodes = [exceptionMsg.querySelector(".location"),
+             consoleMsg.querySelector(".location")];
+    ok(nodes[0], ".location node for the exception message");
+    ok(nodes[1], ".location node for the console message");
 
-      target = TargetFactory.forTab(gBrowser.selectedTab);
-      toolbox = gDevTools.getToolbox(target);
-      toolbox.once("jsdebugger-selected", checkLineAndClickNext);
+    target = TargetFactory.forTab(gBrowser.selectedTab);
+    toolbox = gDevTools.getToolbox(target);
+    toolbox.once("jsdebugger-selected", checkLineAndClickNext);
 
-      EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
-    },
-    failureFn: finishTest,
+    EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
   });
 }
 
 function checkLineAndClickNext(aEvent, aPanel)
 {
   if (index == 3) {
     finishTest();
     return;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_770099_bad_policyuri.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_770099_bad_policyuri.js
@@ -23,33 +23,18 @@ function loadDocument(theHud) {
   hud = theHud;
   hud.jsterm.clearOutput();
   browser.addEventListener("load", onLoad, true);
   content.location = TEST_BAD_POLICY_URI;
 }
 
 function onLoad(aEvent) {
   browser.removeEventListener("load", onLoad, true);
-  testPolicyURIMessage();
+
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "can't fetch policy",
+      category: CATEGORY_SECURITY,
+      severity: SEVERITY_ERROR,
+    }],
+  }).then(finishTest);
 }
-
-function testPolicyURIMessage() {
-  let aOutputNode = hud.outputNode;
- 
-  waitForSuccess(
-    {
-      name: "CSP policy URI warning displayed successfully",
-      validatorFn: function() {
-        return aOutputNode.querySelector(".webconsole-msg-error");
-      },
-
-      successFn: function() {
-        //tests on the urlnode
-        let node = aOutputNode.querySelector(".webconsole-msg-error");
-        isnot(node.textContent.indexOf("can't fetch policy"), -1,
-                                       "CSP Policy URI message found");
-        finishTest();
-      },
-
-      failureFn: finishTest,
-    }
-  );
-}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -21,45 +21,50 @@ function test()
 function testViewSource(aHud)
 {
   hud = aHud;
 
   registerCleanupFunction(function() {
     nodes = hud = StyleEditorUI = null;
   });
 
-  let selector = ".webconsole-msg-cssparser .webconsole-location";
-
-  waitForSuccess({
-    name: "find the location node",
-    validatorFn: function()
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "'font-weight'",
+      category: CATEGORY_CSS,
+      severity: SEVERITY_WARNING,
+    },
     {
-      return hud.outputNode.querySelector(selector);
-    },
-    successFn: function()
-    {
-      nodes = hud.outputNode.querySelectorAll(selector);
-      is(nodes.length, 2, "correct number of css messages");
+      text: "'color'",
+      category: CATEGORY_CSS,
+      severity: SEVERITY_WARNING,
+    }],
+  }).then(([error1Rule, error2Rule]) => {
+    let error1Msg = [...error1Rule.matched][0];
+    let error2Msg = [...error2Rule.matched][0];
+    nodes = [error1Msg.querySelector(".location"),
+             error2Msg.querySelector(".location")];
+    ok(nodes[0], ".location node for the first error");
+    ok(nodes[1], ".location node for the second error");
 
-      let target = TargetFactory.forTab(gBrowser.selectedTab);
-      let toolbox = gDevTools.getToolbox(target);
-      toolbox.once("styleeditor-selected", (event, panel) => {
-        StyleEditorUI = panel.UI;
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    let toolbox = gDevTools.getToolbox(target);
+    toolbox.once("styleeditor-selected", (event, panel) => {
+      StyleEditorUI = panel.UI;
 
-        let count = 0;
-        StyleEditorUI.on("editor-added", function() {
-          if (++count == 2) {
-            onStyleEditorReady(panel);
-          }
-        });
+      let count = 0;
+      StyleEditorUI.on("editor-added", function() {
+        if (++count == 2) {
+          onStyleEditorReady(panel);
+        }
       });
+    });
 
-      EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
-    },
-    failureFn: finishTest,
+    EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
   });
 }
 
 function onStyleEditorReady(aPanel)
 {
   let win = aPanel.panelWindow;
   ok(win, "Style Editor Window is defined");
   ok(StyleEditorUI, "Style Editor UI is defined");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
@@ -11,30 +11,32 @@ function test()
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad, true);
     openConsole(null, function testHSTSErrorLogged (hud) {
       waitForMessages({
         webconsole: hud,
         messages: [
           {
-          name: "Invalid HSTS header error displayed successfully",
-          text: HSTS_INVALID_HEADER_MSG,
-          category: CATEGORY_SECURITY,
-          severity: SEVERITY_WARNING
-        },
+            name: "Invalid HSTS header error displayed successfully",
+            text: HSTS_INVALID_HEADER_MSG,
+            category: CATEGORY_SECURITY,
+            severity: SEVERITY_WARNING,
+            objects: true,
+          },
         ],
-      }).then(() => testClickOpenNewTab(hud));
+      }).then((results) => testClickOpenNewTab(hud, results));
     });
   }, true);
 }
 
-function testClickOpenNewTab(hud) {
-  let warningNode = hud.outputNode.querySelector(
-    ".webconsole-learn-more-link");
+function testClickOpenNewTab(hud, results) {
+  let warningNode = results[0].clickableElements[0];
+  ok(warningNode, "link element");
+  ok(warningNode.classList.contains("learn-more-link"), "link class name");
 
   // Invoke the click event and check if a new tab would
   // open to the correct page.
   let linkOpened = false;
   let oldOpenUILinkIn = window.openUILinkIn;
   window.openUILinkIn = function(aLink) {
     if (aLink == LEARN_MORE_URI) {
       linkOpened = true;
--- a/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
@@ -11,29 +11,30 @@ function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
 function consoleOpened(hud) {
-  waitForSuccess({
-    name: "two nodes displayed",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelectorAll(".hud-msg-node").length == 2;
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "start",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
     },
-    successFn: function()
     {
-      let nodes = hud.outputNode.querySelectorAll(".hud-msg-node");
-      ok(/start/.test(nodes[0].textContent), "start found");
-      ok(/end/.test(nodes[1].textContent), "end found - complete!");
-
-      finishTest();
-    },
-    failureFn: finishTest,
+      text: "end",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(() => {
+    let nodes = hud.outputNode.querySelectorAll(".message");
+    is(nodes.length, 2, "only two messages are displayed");
+    finishTest();
   });
 
   let button = content.document.querySelector("button");
   ok(button, "we have the button");
   EventUtils.sendMouseEvent({ type: "click" }, button, content);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
@@ -67,77 +67,75 @@ function testConsoleLoggingAPI(aMethod) 
   function nextTest() {
     subtestDriver.next();
   }
 
   waitForSuccess({
     name: "1 hidden " + aMethod + " node via string filtering",
     validatorFn: function()
     {
-      return outputNode.querySelectorAll(".hud-filtered-by-string").length == 1;
+      return outputNode.querySelectorAll(".filtered-by-string").length == 1;
     },
     successFn: nextTest,
     failureFn: nextTest,
   });
 
   yield undefined;
 
   hud.jsterm.clearOutput();
 
   // now toggle the current method off - make sure no visible message
 
   // TODO: move all filtering tests into a separate test file: see bug 608135
   setStringFilter("");
-  hud.setFilterState(aMethod, false);
+  let filter = aMethod == "debug" ? "log" : aMethod;
+  hud.setFilterState(filter, false);
   console[aMethod]("foo-bar-baz");
 
   waitForSuccess({
     name: "1 message hidden for " + aMethod + " (logging turned off)",
     validatorFn: function()
     {
-      return outputNode.querySelectorAll("description").length == 1;
+      return outputNode.querySelectorAll(".filtered-by-type").length == 1;
     },
     successFn: nextTest,
     failureFn: nextTest,
   });
 
   yield undefined;
 
   hud.jsterm.clearOutput();
-  hud.setFilterState(aMethod, true);
+  hud.setFilterState(filter, true);
   console[aMethod]("foo-bar-baz");
 
   waitForSuccess({
     name: "1 message shown for " + aMethod + " (logging turned on)",
     validatorFn: function()
     {
-      return outputNode.querySelectorAll("description").length == 1;
+      return outputNode.querySelectorAll(".message:not(.filtered-by-type)").length == 1;
     },
     successFn: nextTest,
     failureFn: nextTest,
   });
 
   yield undefined;
 
   hud.jsterm.clearOutput();
   setStringFilter("");
 
   // test for multiple arguments.
   console[aMethod]("foo", "bar");
 
-  waitForSuccess({
-    name: "show both console arguments for " + aMethod,
-    validatorFn: function()
-    {
-      let node = outputNode.querySelector(".hud-msg-node");
-      return node && /"foo" "bar"/.test(node.textContent);
-    },
-    successFn: nextTest,
-    failureFn: nextTest,
-  });
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: '"foo" "bar"',
+      category: CATEGORY_WEBDEV,
+    }],
+  }).then(nextTest);
 
   yield undefined;
   testDriver.next();
   yield undefined;
 }
 
 function setStringFilter(aValue) {
   hud.ui.filterBox.value = aValue;
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_copying_multiple_messages_inserts_newlines_in_between.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- *
- * Contributor(s):
- *  Patrick Walton <pcwalton@mozilla.com>
- *
- * ***** END LICENSE BLOCK ***** */
-
-// Tests that copying multiple messages inserts newlines in between.
-
-const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 586142";
-
-let hud;
-
-function test()
-{
-  addTab(TEST_URI);
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
-}
-
-function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
-  openConsole(null, testNewlines);
-}
-
-function testNewlines(aHud) {
-  hud = aHud;
-  hud.jsterm.clearOutput();
-
-  for (let i = 0; i < 20; i++) {
-    content.console.log("Hello world #" + i);
-  }
-
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "Hello world #19",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-    }],
-  }).then(testClipboard);
-}
-
-function testClipboard() {
-  let outputNode = hud.outputNode;
-
-  info("messages in output: " + outputNode.itemCount);
-  ok(outputNode.itemCount >= 20, "expected number of messages");
-
-  outputNode.selectAll();
-  outputNode.focus();
-
-  let clipboardTexts = [];
-  for (let i = 0; i < outputNode.itemCount; i++) {
-    let item = outputNode.getItemAtIndex(i);
-    clipboardTexts.push("[" +
-                        WCU_l10n.timestampString(item.timestamp) +
-                        "] " + item.clipboardText);
-  }
-
-  waitForClipboard(clipboardTexts.join("\n"),
-                   function() { goDoCommand("cmd_copy"); },
-                   finishTest, finishTest);
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
@@ -15,31 +15,27 @@ function test() {
   }, true);
 }
 
 function testExecutionScope(hud) {
   let jsterm = hud.jsterm;
 
   jsterm.clearOutput();
   jsterm.execute("window.location.href;");
-
-  waitForSuccess({
-    name: "jsterm execution output (two nodes)",
-    validatorFn: function()
-    {
-      return jsterm.outputNode.querySelectorAll(".hud-msg-node").length == 2;
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "window.location.href;",
+      category: CATEGORY_INPUT,
     },
-    successFn: function()
     {
-      let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
-
-      is(/window.location.href;/.test(nodes[0].textContent), true,
-        "'window.location.href;' written to output");
-
-      isnot(nodes[1].textContent.indexOf(TEST_URI), -1,
-        "command was executed in the window scope");
-
-      executeSoon(finishTest);
-    },
-    failureFn: finishTest,
+      text: TEST_URI,
+      category: CATEGORY_OUTPUT,
+    }],
+  }).then(([input, output]) => {
+    let inputNode = [...input.matched][0];
+    let outputNode = [...output.matched][0];
+    is(inputNode.getAttribute("category"), "input", "input node category is correct");
+    is(outputNode.getAttribute("category"), "output", "output node category is correct");
+    finishTest();
   });
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_for_of.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_for_of.js
@@ -10,26 +10,15 @@ function test() {
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testForOf);
   }, true);
 }
 
 function testForOf(hud) {
   var jsterm = hud.jsterm;
-  jsterm.execute("{ [x.tagName for (x of document.body.childNodes) if (x.nodeType === 1)].join(' '); }");
-
-  waitForSuccess({
-    name: "jsterm output displayed",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      let node = hud.outputNode.querySelector(".webconsole-msg-output");
+  jsterm.execute("{ [x.tagName for (x of document.body.childNodes) if (x.nodeType === 1)].join(' '); }",
+    (node) => {
       ok(/H1 DIV H2 P/.test(node.textContent),
         "for-of loop should find all top-level nodes");
       finishTest();
-    },
-    failureFn: finishTest,
-  });
+    });
 }
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_js_input_and_output_styling.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-// Tests that the correct CSS styles are applied to the lines of console
-// output.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-
-function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole(null, testJSInputAndOutputStyling);
-  }, true);
-}
-
-function testJSInputAndOutputStyling(hud) {
-  let jsterm = hud.jsterm;
-
-  jsterm.clearOutput();
-  jsterm.execute("2 + 2");
-
-  waitForSuccess({
-    name: "jsterm output is displayed",
-    validatorFn: function()
-    {
-      return jsterm.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      let jsInputNode = jsterm.outputNode.querySelector(".hud-msg-node");
-      isnot(jsInputNode.textContent.indexOf("2 + 2"), -1,
-            "JS input node contains '2 + 2'");
-      ok(jsInputNode.classList.contains("webconsole-msg-input"),
-         "JS input node is of the CSS class 'webconsole-msg-input'");
-
-      let output = jsterm.outputNode.querySelector(".webconsole-msg-output");
-      isnot(output.textContent.indexOf("4"), -1,
-            "JS output node contains '4'");
-
-      finishTest();
-    },
-    failureFn: finishTest,
-  });
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
@@ -17,60 +17,55 @@ function test() {
     });
   }, true);
 }
 
 function nextTest() {
   testDriver.next();
 }
 
-function checkResult(msg, desc, lines) {
-  waitForSuccess({
-    name: "correct number of results shown for " + desc,
-    validatorFn: function()
-    {
-      let nodes = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
-      return nodes.length == lines;
-    },
-    successFn: function()
-    {
-      let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
-      if (typeof msg == "string") {
-        is(labels[lines-1].textContent.trim(), msg,
-           "correct message shown for " + desc);
-      }
-      else if (typeof msg == "function") {
-        ok(msg(labels), "correct message shown for " + desc);
-      }
+function checkResult(msg, desc) {
+  waitForMessages({
+    webconsole: jsterm.hud.owner,
+    messages: [{
+      name: desc,
+      category: CATEGORY_OUTPUT,
+    }],
+  }).then(([result]) => {
+    let node = [...result.matched][0].querySelector(".body");
+    if (typeof msg == "string") {
+      is(node.textContent.trim(), msg,
+        "correct message shown for " + desc);
+    }
+    else if (typeof msg == "function") {
+      ok(msg(node), "correct message shown for " + desc);
+    }
 
-      nextTest();
-    },
-    failureFn: nextTest,
+    nextTest();
   });
 }
 
 function testJSTerm(hud)
 {
   jsterm = hud.jsterm;
+  const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
   jsterm.clearOutput();
-  jsterm.execute("'id=' + $('#header').getAttribute('id')");
-  checkResult('"id=header"', "$() worked", 1);
+  jsterm.execute("$('#header').getAttribute('id')");
+  checkResult('"header"', "$() worked");
   yield undefined;
 
   jsterm.clearOutput();
-  jsterm.execute("headerQuery = $$('h1')");
-  jsterm.execute("'length=' + headerQuery.length");
-  checkResult('"length=1"', "$$() worked", 2);
+  jsterm.execute("$$('h1').length");
+  checkResult("1", "$$() worked");
   yield undefined;
 
   jsterm.clearOutput();
-  jsterm.execute("xpathQuery = $x('.//*', document.body);");
-  jsterm.execute("'headerFound='  + (xpathQuery[0] == headerQuery[0])");
-  checkResult('"headerFound=true"', "$x() worked", 2);
+  jsterm.execute("$x('.//*', document.body)[0] == $$('h1')[0]");
+  checkResult("true", "$x() worked");
   yield undefined;
 
   // no jsterm.clearOutput() here as we clear the output using the clear() fn.
   jsterm.execute("clear()");
 
   waitForSuccess({
     name: "clear() worked",
     validatorFn: function()
@@ -79,116 +74,109 @@ function testJSTerm(hud)
     },
     successFn: nextTest,
     failureFn: nextTest,
   });
 
   yield undefined;
 
   jsterm.clearOutput();
-  jsterm.execute("'keysResult=' + (keys({b:1})[0] == 'b')");
-  checkResult('"keysResult=true"', "keys() worked", 1);
+  jsterm.execute("keys({b:1})[0] == 'b'");
+  checkResult("true", "keys() worked", 1);
   yield undefined;
 
   jsterm.clearOutput();
-  jsterm.execute("'valuesResult=' + (values({b:1})[0] == 1)");
-  checkResult('"valuesResult=true"', "values() worked", 1);
+  jsterm.execute("values({b:1})[0] == 1");
+  checkResult("true", "values() worked", 1);
   yield undefined;
 
   jsterm.clearOutput();
 
-  let tabs = gBrowser.tabs.length;
-
-  jsterm.execute("help()");
-  let output = jsterm.outputNode.querySelector(".webconsole-msg-output");
-  ok(!output, "help() worked");
-
-  jsterm.execute("help");
-  output = jsterm.outputNode.querySelector(".webconsole-msg-output");
-  ok(!output, "help worked");
-
-  jsterm.execute("?");
-  output = jsterm.outputNode.querySelector(".webconsole-msg-output");
-  ok(!output, "? worked");
+  let openedLinks = 0;
+  let onExecuteCalls = 0;
+  let oldOpenLink = hud.openLink;
+  hud.openLink = (url) => {
+    if (url == HELP_URL) {
+      openedLinks++;
+    }
+  };
 
-  let foundTab = null;
-  waitForSuccess({
-    name: "help tabs opened",
-    validatorFn: function()
-    {
-      let newTabOpen = gBrowser.tabs.length == tabs + 3;
-      if (!newTabOpen) {
-        return false;
-      }
+  function onExecute() {
+    onExecuteCalls++;
+    if (onExecuteCalls == 3) {
+      nextTest();
+    }
+  }
 
-      foundTab = gBrowser.tabs[tabs];
-      return true;
-    },
-    successFn: function()
-    {
-      gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
-      gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
-      gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
-      nextTest();
-    },
-    failureFn: nextTest,
-  });
+  jsterm.execute("help()", onExecute);
+  jsterm.execute("help", onExecute);
+  jsterm.execute("?", onExecute);
   yield undefined;
 
+  let output = jsterm.outputNode.querySelector(".message[category='output']");
+  ok(!output, "no output for help() calls");
+  is(openedLinks, 3, "correct number of pages opened by the help calls");
+  hud.openLink = oldOpenLink;
+
   jsterm.clearOutput();
   jsterm.execute("pprint({b:2, a:1})");
-  checkResult('"  b: 2\n  a: 1"', "pprint()", 1);
+  checkResult('"  b: 2\n  a: 1"', "pprint()");
   yield undefined;
 
   // check instanceof correctness, bug 599940
   jsterm.clearOutput();
   jsterm.execute("[] instanceof Array");
-  checkResult("true", "[] instanceof Array == true", 1);
+  checkResult("true", "[] instanceof Array == true");
   yield undefined;
 
   jsterm.clearOutput();
   jsterm.execute("({}) instanceof Object");
-  checkResult("true", "({}) instanceof Object == true", 1);
+  checkResult("true", "({}) instanceof Object == true");
   yield undefined;
 
   // check for occurrences of Object XRayWrapper, bug 604430
   jsterm.clearOutput();
   jsterm.execute("document");
-  checkResult(function(nodes) {
-    return nodes[0].textContent.search(/\[object xraywrapper/i) == -1;
-  }, "document - no XrayWrapper", 1);
+  checkResult(function(node) {
+    return node.textContent.search(/\[object xraywrapper/i) == -1;
+  }, "document - no XrayWrapper");
   yield undefined;
 
   // check that pprint(window) and keys(window) don't throw, bug 608358
   jsterm.clearOutput();
   jsterm.execute("pprint(window)");
-  checkResult(null, "pprint(window)", 1);
+  checkResult(null, "pprint(window)");
   yield undefined;
 
   jsterm.clearOutput();
   jsterm.execute("keys(window)");
-  checkResult(null, "keys(window)", 1);
+  checkResult(null, "keys(window)");
   yield undefined;
 
   // bug 614561
   jsterm.clearOutput();
   jsterm.execute("pprint('hi')");
-  checkResult('"  0: "h"\n  1: "i""', "pprint('hi')", 1);
+  checkResult('"  0: "h"\n  1: "i""', "pprint('hi')");
   yield undefined;
 
   // check that pprint(function) shows function source, bug 618344
   jsterm.clearOutput();
   jsterm.execute("pprint(print)");
-  checkResult(function(nodes) {
-    return nodes[0].textContent.indexOf("aOwner.helperResult") > -1;
-  }, "pprint(function) shows source", 1);
+  checkResult(function(node) {
+    return node.textContent.indexOf("aOwner.helperResult") > -1;
+  }, "pprint(function) shows source");
   yield undefined;
 
   // check that an evaluated null produces "null", bug 650780
   jsterm.clearOutput();
   jsterm.execute("null");
-  checkResult("null", "null is null", 1);
+  checkResult("null", "null is null");
+  yield undefined;
+
+  jsterm.clearOutput();
+  jsterm.execute("undefined");
+  checkResult("undefined", "undefined is printed");
   yield undefined;
 
   jsterm = testDriver = null;
   executeSoon(finishTest);
   yield undefined;
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js
@@ -33,31 +33,31 @@ function consoleOpened(aHud) {
       text: "foobarz #49",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   }).then(testLiveFilteringOfMessageTypes);
 }
 
 function testLiveFilteringOfMessageTypes() {
-  is(hud.outputNode.itemCount, 50, "number of messages");
+  is(hud.outputNode.children.length, 50, "number of messages");
 
   hud.setFilterState("log", false);
   is(countMessageNodes(), 0, "the log nodes are hidden when the " +
     "corresponding filter is switched off");
 
   hud.setFilterState("log", true);
   is(countMessageNodes(), 50, "the log nodes reappear when the " +
     "corresponding filter is switched on");
 
   finishTest();
 }
 
 function countMessageNodes() {
-  let messageNodes = hud.outputNode.querySelectorAll(".hud-log");
+  let messageNodes = hud.outputNode.querySelectorAll(".message");
   let displayedMessageNodes = 0;
   let view = hud.iframeWindow;
   for (let i = 0; i < messageNodes.length; i++) {
     let computedStyle = view.getComputedStyle(messageNodes[i], null);
     if (computedStyle.display !== "none") {
       displayedMessageNodes++;
     }
   }
--- a/browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
@@ -32,17 +32,17 @@ function consoleOpened(aHud) {
       text: "http://www.example.com/ 49",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   }).then(testLiveFilteringOnSearchStrings);
 }
 
 function testLiveFilteringOnSearchStrings() {
-  is(hud.outputNode.itemCount, 50, "number of messages");
+  is(hud.outputNode.children.length, 50, "number of messages");
 
   setStringFilter("http");
   isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
     "search string is set to \"http\"");
 
   setStringFilter("hxxp");
   is(countMessageNodes(), 0, "the log nodes are hidden when the search " +
     "string is set to \"hxxp\"");
@@ -80,17 +80,17 @@ function testLiveFilteringOnSearchString
     "the string \"foo\"bar'baz\"boo'\"");
 
   finishTest();
 }
 
 function countMessageNodes() {
   let outputNode = hud.outputNode;
 
-  let messageNodes = outputNode.querySelectorAll(".hud-log");
+  let messageNodes = outputNode.querySelectorAll(".message");
   let displayedMessageNodes = 0;
   let view = hud.iframeWindow;
   for (let i = 0; i < messageNodes.length; i++) {
     let computedStyle = view.getComputedStyle(messageNodes[i], null);
     if (computedStyle.display !== "none") {
       displayedMessageNodes++;
     }
   }
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_log_node_classes.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-// Tests that console logging via the console API produces nodes of the correct
-// CSS classes.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-
-function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole(null, consoleOpened);
-  }, true);
-}
-
-function consoleOpened(aHud) {
-  let console = content.console;
-  outputNode = aHud.outputNode;
-
-  ok(console, "console exists");
-  console.log("I am a log message");
-  console.error("I am an error");
-  console.info("I am an info message");
-  console.warn("I am a warning  message");
-
-  waitForSuccess({
-    name: "console.warn displayed",
-    validatorFn: function()
-    {
-      return aHud.outputNode.textContent.indexOf("a warning") > -1;
-    },
-    successFn: testLogNodeClasses,
-    failureFn: finishTest,
-  });
-}
-
-function testLogNodeClasses() {
-  let domLogEntries = outputNode.childNodes;
-
-  let count = outputNode.childNodes.length;
-  ok(count > 0, "LogCount: " + count);
-
-  let klasses = ["hud-log",
-                 "hud-warn",
-                 "hud-info",
-                 "hud-error",
-                 "hud-exception",
-                 "hud-network"];
-
-  function verifyClass(classList) {
-    let len = klasses.length;
-    for (var i = 0; i < len; i++) {
-      if (classList.contains(klasses[i])) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  for (var i = 0; i < count; i++) {
-    let classList = domLogEntries[i].classList;
-    ok(verifyClass(classList),
-       "Log Node class verified: " + domLogEntries[i].getAttribute("class"));
-  }
-
-  finishTest();
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_message_node_id.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_message_node_id.js
@@ -10,20 +10,22 @@ function test() {
   browser.addEventListener("DOMContentLoaded", onLoad, false);
 }
 
 function onLoad() {
   browser.removeEventListener("DOMContentLoaded", onLoad, false);
   openConsole(null, function(hud) {
     content.console.log("a log message");
 
-    waitForSuccess({
-      name: "console.log message shown with an ID attribute",
-      validatorFn: function()
-      {
-        let node = hud.outputNode.querySelector(".hud-msg-node");
-        return node && node.getAttribute("id");
-      },
-      successFn: finishTest,
-      failureFn: finishTest,
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "a log message",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+      }],
+    }).then(([result]) => {
+      let msg = [...result.matched][0];
+      ok(msg.getAttribute("id"), "log message has an ID");
+      finishTest();
     });
   });
 }
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_null_and_undefined_output.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-// Test that JavaScript expressions that evaluate to null or undefined produce
-// meaningful output.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-
-function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole(null, testNullAndUndefinedOutput);
-  }, true);
-}
-
-function testNullAndUndefinedOutput(hud) {
-  let jsterm = hud.jsterm;
-  let outputNode = jsterm.outputNode;
-
-  jsterm.clearOutput();
-  jsterm.execute("null;");
-
-  waitForSuccess({
-    name: "null displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-msg-node").length == 2;
-    },
-    successFn: function()
-    {
-      let nodes = outputNode.querySelectorAll(".hud-msg-node");
-      isnot(nodes[1].textContent.indexOf("null"), -1,
-            "'null' printed to output");
-
-      jsterm.clearOutput();
-      jsterm.execute("undefined;");
-      waitForSuccess(waitForUndefined);
-    },
-    failureFn: finishTest,
-  });
-
-  let waitForUndefined = {
-    name: "undefined displayed",
-    validatorFn: function()
-    {
-      return outputNode.querySelectorAll(".hud-msg-node").length == 2;
-    },
-    successFn: function()
-    {
-      let nodes = outputNode.querySelectorAll(".hud-msg-node");
-      isnot(nodes[1].textContent.indexOf("undefined"), -1,
-            "'undefined' printed to output");
-
-      finishTest();
-    },
-    failureFn: finishTest,
-  };
-}
-
--- a/browser/devtools/webconsole/test/browser_webconsole_output_order.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_order.js
@@ -18,27 +18,34 @@ function test() {
 
 function testOutputOrder(hud) {
   let jsterm = hud.jsterm;
   let outputNode = jsterm.outputNode;
 
   jsterm.clearOutput();
   jsterm.execute("console.log('foo', 'bar');");
 
-  waitForSuccess({
-    name: "console.log message displayed",
-    validatorFn: function()
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "console.log('foo', 'bar');",
+      category: CATEGORY_INPUT,
+    },
     {
-      return outputNode.querySelectorAll(".hud-msg-node").length == 3;
+      text: "undefined",
+      category: CATEGORY_OUTPUT,
     },
-    successFn: function()
     {
-      let nodes = outputNode.querySelectorAll(".hud-msg-node");
-      let executedStringFirst =
-        /console\.log\('foo', 'bar'\);/.test(nodes[0].textContent);
-      let outputSecond = /"foo" "bar"/.test(nodes[2].textContent);
-      ok(executedStringFirst && outputSecond, "executed string comes first");
-
-      finishTest();
-    },
-    failureFn: finishTest,
+      text: '"foo" "bar"',
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(([function_call, result, console_message]) => {
+    let fncall_node = [...function_call.matched][0];
+    let result_node = [...result.matched][0];
+    let console_message_node = [...console_message.matched][0];
+    is(fncall_node.nextElementSibling, result_node,
+       "console.log() is followed by undefined");
+    is(result_node.nextElementSibling, console_message_node,
+       "undefined is followed by 'foo' 'bar'");
+    finishTest();
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -5,47 +5,43 @@
 // standard View Source window.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
-    openConsole(null, function(hud) {
-      executeSoon(function() {
-        testViewSource(hud);
-      });
-    });
+    openConsole(null, testViewSource);
   }, true);
 }
 
 function testViewSource(hud) {
   let button = content.document.querySelector("button");
-  button = XPCNativeWrapper.unwrap(button);
   ok(button, "we have the button on the page");
 
   expectUncaughtException();
-  EventUtils.sendMouseEvent({ type: "click" }, button, XPCNativeWrapper.unwrap(content));
+  EventUtils.sendMouseEvent({ type: "click" }, button, content);
 
-  waitForSuccess({
-    name: "find the location node",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-location");
-    },
-    successFn: function()
-    {
-      let locationNode = hud.outputNode.querySelector(".webconsole-location");
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "fooBazBaz is not defined",
+      category: CATEGORY_JS,
+      severity: SEVERITY_ERROR,
+    }],
+  }).then(([result]) => {
+    let msg = [...result.matched][0];
+    ok(msg, "error message");
+    let locationNode = msg.querySelector(".location");
+    ok(locationNode, "location node");
 
-      Services.ww.registerNotification(observer);
+    Services.ww.registerNotification(observer);
 
-      EventUtils.sendMouseEvent({ type: "click" }, locationNode);
-    },
-    failureFn: finishTest,
+    EventUtils.sendMouseEvent({ type: "click" }, locationNode);
   });
 }
 
 let observer = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened") {
       return;
     }
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -30,16 +30,19 @@ const CATEGORY_OUTPUT = 5;
 const CATEGORY_SECURITY = 6;
 
 // The possible message severities.
 const SEVERITY_ERROR = 0;
 const SEVERITY_WARNING = 1;
 const SEVERITY_INFO = 2;
 const SEVERITY_LOG = 3;
 
+// The indent of a console group in pixels.
+const GROUP_INDENT = 12;
+
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
@@ -102,42 +105,33 @@ function afterAllTabsLoaded(callback, wi
  * @param {boolean} [aFailIfFound=false]
  *        fail the test if the string is found in the output node.
  * @param {string} aClass [optional]
  *        find only messages with the given CSS class.
  */
 function testLogEntry(aOutputNode, aMatchString, aMsg, aOnlyVisible,
                       aFailIfFound, aClass)
 {
-  let selector = ".hud-msg-node";
+  let selector = ".message";
   // Skip entries that are hidden by the filter.
   if (aOnlyVisible) {
-    selector += ":not(.hud-filtered-by-type)";
+    selector += ":not(.filtered-by-type):not(.filtered-by-string)";
   }
   if (aClass) {
     selector += "." + aClass;
   }
 
   let msgs = aOutputNode.querySelectorAll(selector);
   let found = false;
   for (let i = 0, n = msgs.length; i < n; i++) {
     let message = msgs[i].textContent.indexOf(aMatchString);
     if (message > -1) {
       found = true;
       break;
     }
-
-    // Search the labels too.
-    let labels = msgs[i].querySelectorAll("label");
-    for (let j = 0; j < labels.length; j++) {
-      if (labels[j].getAttribute("value").indexOf(aMatchString) > -1) {
-        found = true;
-        break;
-      }
-    }
   }
 
   is(found, !aFailIfFound, aMsg);
 }
 
 /**
  * A convenience method to call testLogEntry().
  *
@@ -239,17 +233,17 @@ function waitForContextMenu(aPopup, aBut
 
 /**
  * Dump the output of all open Web Consoles - used only for debugging purposes.
  */
 function dumpConsoles()
 {
   if (gPendingOutputTest) {
     console.log("dumpConsoles start");
-    for (let hud of HUDService.consoles) {
+    for (let [, hud] of HUDService.consoles) {
       if (!hud.outputNode) {
         console.debug("no output content for", hud.hudId);
         continue;
       }
 
       console.debug("output content for", hud.hudId);
       for (let elem of hud.outputNode.childNodes) {
         dumpMessageElement(elem);
@@ -264,18 +258,18 @@ function dumpConsoles()
 /**
  * Dump to output debug information for the given webconsole message.
  *
  * @param nsIDOMNode aMessage
  *        The message element you want to display.
  */
 function dumpMessageElement(aMessage)
 {
-  let text = getMessageElementText(aMessage);
-  let repeats = aMessage.querySelector(".webconsole-msg-repeat");
+  let text = aMessage.textContent;
+  let repeats = aMessage.querySelector(".repeats");
   if (repeats) {
     repeats = repeats.getAttribute("value");
   }
   console.debug("id", aMessage.getAttribute("id"),
                 "date", aMessage.timestamp,
                 "class", aMessage.className,
                 "category", aMessage.category,
                 "severity", aMessage.severity,
@@ -826,34 +820,16 @@ function openDebugger(aOptions = {})
     console.debug("failed to open the toolbox for 'jsdebugger'", aReason);
     deferred.reject(aReason);
   });
 
   return deferred.promise;
 }
 
 /**
- * Get the full text displayed by a Web Console message.
- *
- * @param nsIDOMElement aElement
- *        The message element from the Web Console output.
- * @return string
- *         The full text displayed by the given message element.
- */
-function getMessageElementText(aElement)
-{
-  let text = aElement.textContent;
-  let labels = aElement.querySelectorAll("label");
-  for (let label of labels) {
-    text += " " + label.getAttribute("value");
-  }
-  return text;
-}
-
-/**
  * Wait for messages in the Web Console output.
  *
  * @param object aOptions
  *        Options for what you want to wait for:
  *        - webconsole: the webconsole instance you work with.
  *        - matchCondition: "any" or "all". Default: "all". The promise
  *        returned by this function resolves when all of the messages are
  *        matched, if the |matchCondition| is "all". If you set the condition to
@@ -878,26 +854,31 @@ function getMessageElementText(aElement)
  *            message. Optionally this can be an object of the form
  *            { file, fn, line } that can match the specified file, function
  *            and/or line number in the trace message.
  *            - consoleTime: string that matches a console.time() timer name.
  *            Provide this if you want to match a console.time() message.
  *            - consoleTimeEnd: same as above, but for console.timeEnd().
  *            - consoleDir: boolean, set to |true| to match a console.dir()
  *            message.
+ *            - consoleGroup: boolean, set to |true| to match a console.group()
+ *            message.
  *            - longString: boolean, set to |true} to match long strings in the
  *            message.
  *            - type: match messages that are instances of the given object. For
  *            example, you can point to Messages.NavigationMarker to match any
  *            such message.
  *            - objects: boolean, set to |true| if you expect inspectable
  *            objects in the message.
  *            - source: object of the shape { url, line }. This is used to
  *            match the source URL and line number of the error message or
  *            console API call.
+ *            - groupDepth: number used to check the depth of the message in
+ *            a group.
+ *            - url: URL to match for network requests.
  * @return object
  *         A promise object is returned once the messages you want are found.
  *         The promise is resolved with the array of rule objects you give in
  *         the |messages| property. Each objects is the same as provided, with
  *         additional properties:
  *         - matched: a Set of web console messages that matched the rule.
  *         - clickableElements: a list of inspectable objects. This is available
  *         if any of the following properties are present in the rule:
@@ -914,35 +895,38 @@ function waitForMessages(aOptions)
   let rulesMatched = 0;
   let listenerAdded = false;
   let deferred = promise.defer();
   aOptions.matchCondition = aOptions.matchCondition || "all";
 
   function checkText(aRule, aText)
   {
     let result;
-    if (typeof aRule == "string") {
+    if (Array.isArray(aRule)) {
+      result = aRule.every((s) => checkText(s, aText));
+    }
+    else if (typeof aRule == "string") {
       result = aText.indexOf(aRule) > -1;
     }
     else if (aRule instanceof RegExp) {
       result = aRule.test(aText);
     }
     return result;
   }
 
   function checkConsoleTrace(aRule, aElement)
   {
-    let elemText = getMessageElementText(aElement);
+    let elemText = aElement.textContent;
     let trace = aRule.consoleTrace;
 
     if (!checkText("Stack trace from ", elemText)) {
       return false;
     }
 
-    let clickable = aElement.querySelector(".hud-clickable");
+    let clickable = aElement.querySelector(".body a");
     if (!clickable) {
       ok(false, "console.trace() message is missing .hud-clickable");
       displayErrorContext(aRule, aElement);
       return false;
     }
     aRule.clickableElements = [clickable];
 
     if (trace.file &&
@@ -972,68 +956,79 @@ function waitForMessages(aOptions)
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
 
     return true;
   }
 
   function checkConsoleTime(aRule, aElement)
   {
-    let elemText = getMessageElementText(aElement);
+    let elemText = aElement.textContent;
     let time = aRule.consoleTime;
 
     if (!checkText(time + ": timer started", elemText)) {
       return false;
     }
 
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
 
     return true;
   }
 
   function checkConsoleTimeEnd(aRule, aElement)
   {
-    let elemText = getMessageElementText(aElement);
+    let elemText = aElement.textContent;
     let time = aRule.consoleTimeEnd;
     let regex = new RegExp(time + ": -?\\d+ms");
 
     if (!checkText(regex, elemText)) {
       return false;
     }
 
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
 
     return true;
   }
 
   function checkConsoleDir(aRule, aElement)
   {
-    if (!aElement.classList.contains("webconsole-msg-inspector")) {
+    if (!aElement.classList.contains("inlined-variables-view")) {
       return false;
     }
 
-    let elemText = getMessageElementText(aElement);
+    let elemText = aElement.textContent;
     if (!checkText(aRule.consoleDir, elemText)) {
       return false;
     }
 
     let iframe = aElement.querySelector("iframe");
     if (!iframe) {
       ok(false, "console.dir message has no iframe");
       return false;
     }
 
     return true;
   }
 
+  function checkConsoleGroup(aRule, aElement)
+  {
+    if (!isNaN(parseInt(aRule.consoleGroup))) {
+      aRule.groupDepth = aRule.consoleGroup;
+    }
+    aRule.category = CATEGORY_WEBDEV;
+    aRule.severity = SEVERITY_LOG;
+
+    return true;
+  }
+
   function checkSource(aRule, aElement)
   {
-    let location = aElement.querySelector(".webconsole-location");
+    let location = aElement.querySelector(".location");
     if (!location) {
       return false;
     }
 
     if (!checkText(aRule.source.url, location.getAttribute("title"))) {
       return false;
     }
 
@@ -1041,17 +1036,17 @@ function waitForMessages(aOptions)
       return false;
     }
 
     return true;
   }
 
   function checkMessage(aRule, aElement)
   {
-    let elemText = getMessageElementText(aElement);
+    let elemText = aElement.textContent;
 
     if (aRule.text && !checkText(aRule.text, elemText)) {
       return false;
     }
 
     if (aRule.noText && checkText(aRule.noText, elemText)) {
       return false;
     }
@@ -1067,16 +1062,20 @@ function waitForMessages(aOptions)
     if (aRule.consoleTimeEnd && !checkConsoleTimeEnd(aRule, aElement)) {
       return false;
     }
 
     if (aRule.consoleDir && !checkConsoleDir(aRule, aElement)) {
       return false;
     }
 
+    if (aRule.consoleGroup && !checkConsoleGroup(aRule, aElement)) {
+      return false;
+    }
+
     if (aRule.source && !checkSource(aRule, aElement)) {
       return false;
     }
 
     if (aRule.type) {
       // The rule tries to match the newer types of messages, based on their
       // object constructor.
       if (!aElement._messageObject ||
@@ -1100,48 +1099,63 @@ function waitForMessages(aOptions)
       if (partialMatch) {
         is(aElement.category, aRule.category,
            "message category for rule: " + displayRule(aRule));
         displayErrorContext(aRule, aElement);
       }
       return false;
     }
 
-    if (aRule.severity && aElement.severity != aRule.severity) {
+    if ("severity" in aRule && aElement.severity != aRule.severity) {
       if (partialMatch) {
         is(aElement.severity, aRule.severity,
            "message severity for rule: " + displayRule(aRule));
         displayErrorContext(aRule, aElement);
       }
       return false;
     }
 
-    if (aRule.repeats) {
-      let repeats = aElement.querySelector(".webconsole-msg-repeat");
+    if (aRule.category == CATEGORY_NETWORK && "url" in aRule &&
+        !checkText(aRule.url, aElement.url)) {
+      return false;
+    }
+
+    if ("repeats" in aRule) {
+      let repeats = aElement.querySelector(".repeats");
       if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
         return false;
       }
     }
 
+    if ("groupDepth" in aRule) {
+      let timestamp = aElement.querySelector(".timestamp");
+      let indent = (GROUP_INDENT * aRule.groupDepth) + "px";
+      if (!timestamp || timestamp.style.marginRight != indent) {
+        is(timestamp.style.marginRight, indent,
+           "group depth check failed for message rule: " + displayRule(aRule));
+        return false;
+      }
+    }
+
     if ("longString" in aRule) {
       let longStrings = aElement.querySelectorAll(".longStringEllipsis");
       if (aRule.longString != !!longStrings[0]) {
         if (partialMatch) {
           is(!!longStrings[0], aRule.longString,
              "long string existence check failed for message rule: " +
              displayRule(aRule));
           displayErrorContext(aRule, aElement);
         }
         return false;
       }
       aRule.longStrings = longStrings;
     }
 
     if ("objects" in aRule) {
-      let clickables = aElement.querySelectorAll(".hud-clickable");
+      let clickables = aElement.querySelectorAll(".body a");
       if (aRule.objects != !!clickables[0]) {
         if (partialMatch) {
           is(!!clickables[0], aRule.objects,
              "objects existence check failed for message rule: " +
              displayRule(aRule));
           displayErrorContext(aRule, aElement);
         }
         return false;
@@ -1156,19 +1170,19 @@ function waitForMessages(aOptions)
     aRule.matched.add(aElement);
 
     return aRule.matched.size == count;
   }
 
   function onMessagesAdded(aEvent, aNewElements)
   {
     for (let elem of aNewElements) {
-      let location = elem.querySelector(".webconsole-location");
+      let location = elem.querySelector(".location");
       if (location) {
-        let url = location.getAttribute("title");
+        let url = location.title;
         // Prevent recursion with the browser console and any potential
         // messages coming from head.js.
         if (url.indexOf("browser/devtools/webconsole/test/head.js") != -1) {
           continue;
         }
       }
 
       for (let rule of rules) {
@@ -1245,35 +1259,16 @@ function waitForMessages(aOptions)
       webconsole.ui.on("messages-added", onMessagesAdded);
       webconsole.ui.on("messages-updated", onMessagesAdded);
     }
   });
 
   return deferred.promise;
 }
 
-
-/**
- * Scroll the Web Console output to the given node.
- *
- * @param nsIDOMNode aNode
- *        The node to scroll to.
- */
-function scrollOutputToNode(aNode)
-{
-  let richListBoxNode = aNode.parentNode;
-  while (richListBoxNode.tagName != "richlistbox") {
-    richListBoxNode = richListBoxNode.parentNode;
-  }
-
-  let boxObject = richListBoxNode.scrollBoxObject;
-  let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
-  nsIScrollBoxObject.ensureElementIsVisible(aNode);
-}
-
 function whenDelayedStartupFinished(aWindow, aCallback)
 {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
--- a/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html
+++ b/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html
@@ -6,17 +6,17 @@
          http://creativecommons.org/publicdomain/zero/1.0/ -->
 <script type="application/javascript">
 (function() {
 var longString = "abbababazomglolztest";
 for (var i = 0; i < 10; i++) {
   longString += longString + longString;
 }
 
-longString = "foobar" + (new Array(20000)).join("a") + "foobaz" +
+longString = "foobar" + (new Array(9000)).join("a") + "foobaz" +
              longString + "boom!";
 console.log(longString);
 })();
 </script>
   </head>
   <body>
     <p>Web Console test for bug 859170 - very long strings hang the browser.</p>
   </body>
--- a/browser/devtools/webconsole/test/test-network.html
+++ b/browser/devtools/webconsole/test/test-network.html
@@ -1,11 +1,11 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
     <meta charset="utf-8">
     <title>Console network test</title>
-    <script src="testscript.js"></script>
+    <script src="testscript.js?foo"></script>
   </head>
   <body>
     <h1>Heads Up Display Network  Test Page</h1>
     <img src="test-image.png"></img>
   </body>
 </html>
--- a/browser/devtools/webconsole/test/test-result-format-as-string.html
+++ b/browser/devtools/webconsole/test/test-result-format-as-string.html
@@ -6,16 +6,17 @@
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
   </head>
   <body>
     <p>Make sure js eval results are formatted as strings.</p>
     <script>
       document.querySelector("p").toSource = function() {
         var element = document.createElement("div");
+        element.id = "foobar";
         element.textContent = "bug772506_content";
         element.setAttribute("onmousemove",
           "(function () {" +
           "  gBrowser._bug772506 = 'foobar';" +
           "})();"
         );
         return element;
       };