Bug 534398 - Implement Heads Up Display console, js workspaces and related tools (r=dietrich, r=mrbkap)
☠☠ backed out by 35c98f789ddf ☠ ☠
authorddahl@mozilla.com
Fri, 18 Jun 2010 16:18:16 -0700
changeset 43820 552ea4bd4e5968fa695a91b31ef6c019622a242b
parent 43819 41bdc98c32bd8795e4b2e3fdb8fabb8198db4998
child 43821 35c98f789ddfd091d3612cabae30e2521e06c078
push idunknown
push userunknown
push dateunknown
reviewersdietrich, mrbkap
bugs534398
milestone1.9.3a6pre
Bug 534398 - Implement Heads Up Display console, js workspaces and related tools (r=dietrich, r=mrbkap)
browser/base/content/browser-menubar.inc
browser/base/content/browser-sets.inc
browser/base/content/browser.js
browser/locales/en-US/chrome/browser/browser.dtd
toolkit/components/console/Makefile.in
toolkit/components/console/content/headsUpDisplay.css
toolkit/components/console/hudservice/HUDService.jsm
toolkit/components/console/hudservice/Makefile.in
toolkit/components/console/hudservice/tests/Makefile.in
toolkit/components/console/hudservice/tests/browser/Makefile.in
toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
toolkit/components/console/hudservice/tests/browser/test-console.html
toolkit/components/console/hudservice/tests/browser/test-data.json
toolkit/components/console/hudservice/tests/browser/test-filter.html
toolkit/components/console/hudservice/tests/browser/test-mutation.html
toolkit/components/console/hudservice/tests/browser/test-network.html
toolkit/components/console/hudservice/tests/browser/test-observe-http-ajax.html
toolkit/components/console/hudservice/tests/browser/testscript.js
toolkit/components/console/jar.mn
toolkit/locales/en-US/chrome/global/headsUpDisplay.dtd
toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
toolkit/locales/jar.mn
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -606,16 +606,21 @@
                         accesskey="&inspectMenu.accesskey;"
                         key="key_inspect"
                         command="Tools:Inspect"/>
               <menuitem id="javascriptConsole"
                         label="&errorConsoleCmd.label;"
                         accesskey="&errorConsoleCmd.accesskey;"
                         key="key_errorConsole"
                         oncommand="toJavaScriptConsole();"/>
+              <menuitem id="headsUpDisplayConsole"
+                        label="&hudConsoleCmd.label;"
+                        accesskey="&hudConsoleCmd.accesskey;"
+                        key="key_hudConsole"
+                        oncommand="HUDConsoleUI.toggleHUD();"/>
               <menuitem id="menu_pageInfo"
                         accesskey="&pageInfoCmd.accesskey;"
                         label="&pageInfoCmd.label;"
 #ifndef XP_WIN
                         key="key_viewInfo"
 #endif
                         command="View:PageInfo"/>
               <menuseparator id="sanitizeSeparator"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -217,16 +217,17 @@
 #endif
 #ifdef XP_GNOME
     <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
     <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
 #else
     <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
 #endif
     <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift"/>
+    <key id="key_hudConsole" key="&hudConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();" modifiers="accel,shift"/>
     <key id="key_inspect" key="&inspectMenu.commandkey;" command="Tools:Inspect" modifiers="accel,shift"/>
     <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile"  modifiers="accel"/>
     <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
     <key id="printKb" key="&printCmd.commandkey;" command="cmd_print"  modifiers="accel"/>
     <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
     <key id="key_undo"
          key="&undoCmd.key;"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7666,8 +7666,19 @@ var TabContextMenu = {
     // XXXzeniko should't we just disable this item as we disable
     // the tabbrowser-multiple items above - for consistency?
     document.getElementById("context_undoCloseTab").hidden =
       Cc["@mozilla.org/browser/sessionstore;1"].
       getService(Ci.nsISessionStore).
       getClosedTabCount(window) == 0;
   }
 };
+
+XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () {
+  Cu.import("resource://gre/modules/HUDService.jsm");
+  try {
+    return HUDService.consoleUI;
+  }
+  catch (ex) {
+    Components.utils.reportError(ex);
+  }
+});
+
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -154,16 +154,20 @@
 <!ENTITY downloadsUnix.commandkey     "y">
 <!ENTITY addons.label                 "Add-ons">
 <!ENTITY addons.accesskey             "A">
 
 <!ENTITY errorConsoleCmd.label        "Error Console">
 <!ENTITY errorConsoleCmd.accesskey    "C">
 <!ENTITY errorConsoleCmd.commandkey   "j">
 
+<!ENTITY hudConsoleCmd.label          "Heads Up Display">
+<!ENTITY hudConsoleCmd.accesskey      "U">
+<!ENTITY hudConsoleCmd.commandkey     "k">
+
 <!ENTITY inspectMenu.label            "Inspect">
 <!ENTITY inspectMenu.accesskey        "T">
 <!ENTITY inspectMenu.commandkey       "I">
 
 <!ENTITY fileMenu.label         "File"> 
 <!ENTITY fileMenu.accesskey       "F">
 <!ENTITY newNavigatorCmd.label        "New Window">
 <!ENTITY newNavigatorCmd.key        "N">
--- a/toolkit/components/console/Makefile.in
+++ b/toolkit/components/console/Makefile.in
@@ -38,12 +38,15 @@
 
 DEPTH     = ../../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+PARALLEL_DIRS = hudservice \
+	$(NULL)
+
 EXTRA_PP_COMPONENTS = jsconsole-clhandler.js
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/content/headsUpDisplay.css
@@ -0,0 +1,205 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DevTools code
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   David Dahl <ddahl@mozilla.com>
+
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.hud-box {
+    border-bottom: 1px solid #aaa;
+}
+
+.hud-outer-wrapper {
+    width: 100%; 
+    height: 100%;
+}
+
+.hud-filter-btn > .toolbarbutton-icon { display: none; }
+
+.hud-console-wrapper {
+    width: 100%; 
+    overflow: auto; 
+    max-height: 400px; 
+    height: 400px; 
+}
+
+.hud-main-label {
+    font-size: 1em;
+    padding-top: 0.33em;
+    font-weight: bold;
+}
+
+.hud-output-node div {
+    -moz-user-select: text;
+}
+
+.hud-output-node .hud-network {
+    color: blue;
+}
+
+.hud-output-node .hud-error {
+    color: red;
+}
+
+.hud-output-node .hud-log {
+    color: black;
+}
+
+.hud-output-node .hud-warn {
+    color: orange;
+}
+
+.hud-output-node .hud-info {
+    color: green;
+}
+
+.hud-output-node .hud-exception {
+    color: red; font-weight: bold;
+}
+
+.hud-hidden {
+    display: none;
+}
+
+.hud-msg-node {    
+    width: 100%;
+    margin-top: 0.3em; 
+    margin-bottom: 0.3em; 
+    padding-left: 0.3em;
+    border-bottom: 1px solid #eee;   
+}
+
+.hud-output-node {
+    height:380px; 
+    max-height: 380px;
+    border-bottom: 1px solid #ddd; 
+    border-top: 1px solid #ddd; 
+    overflow-x: auto; overflow: auto;
+    font: 1em monospace; background-color: white;
+    width: 100%;
+}
+
+/* JSTerm Styles */
+
+.jsterm-wrapper-node {
+    font-family: monospace; 
+    font-size: 1em;
+    background-color: #000; 
+    border: 1px solid #333; 
+    padding: 0.1em;
+    width: 100%;
+    height: 400px;
+}
+
+.jsterm-output-node {
+    width: 100%; 
+    height: 400px; 
+    color: white; 
+    background-color: black; 
+    overflow: auto; 
+    overflow-x: auto; 
+    position: absolute; 
+    -moz-box-direction: reverse;
+}
+
+.jsterm-output-node div {
+    -moz-user-select: text;
+}
+
+.jsterm-scroll-to-node {
+    height: 1px; width: 1px; position: relative; top: 92%; display: block;
+}
+
+.jsterm-input-node {
+    width: 98%;
+    font-family: monospace;
+    font-size: 1.2em; 
+    line-height: 1.6em;
+    background-color: black;
+    color: white;
+}
+
+.jsterm-output-line {
+    font-size: 1.2em;
+}
+
+%include ../../../themes/pinstripe/global/shared.inc
+
+toolbar[mode="text"] .toolbarbutton-text {
+    margin: 1px 2px !important;
+}
+
+toolbarbutton:hover:active,
+toolbarbutton[open="true"] {
+    padding-top: 2px;
+    padding-bottom: 2px;
+    -moz-padding-start: 2px;
+    -moz-padding-end: 1px;
+}
+
+toolbarbutton[disabled="true"],
+toolbarbutton[disabled="true"]:hover,
+toolbarbutton[disabled="true"]:hover:active,
+toolbarbutton[disabled="true"][open="true"] {
+    padding-top: 2px;
+    padding-bottom: 2px;
+    -moz-padding-start: 2px;
+    -moz-padding-end: 1px;
+}
+
+/* ..... checked state ..... */
+
+toolbarbutton[checked="true"] {
+    border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow !important;
+    padding-top: 2px !important;
+    padding-bottom: 2px !important;
+    -moz-padding-start: 2px !important;
+    -moz-padding-end: 1px !important;
+}
+
+/* ..... unchecked state ..... */
+
+toolbarbutton[checked="false"] {
+    padding-top: 2px !important;
+    padding-bottom: 2px !important;
+    -moz-padding-start: 2px !important;
+    -moz-padding-end: 1px !important;
+}
+
+toolbar {
+    padding: 1px 0px;
+    -moz-box-align: center;
+}
+
+
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/HUDService.jsm
@@ -0,0 +1,3282 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DevTools (HeadsUpDisplay) Console Code
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   David Dahl <ddahl@mozilla.com> (original author)
+ *   Rob Campbell <rcampbell@mozilla.com>
+ *   Johnathan Nightingale <jnightingale@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["HUDService"];
+
+XPCOMUtils.defineLazyServiceGetter(this, "scriptError",
+                                   "@mozilla.org/scripterror;1",
+                                   "nsIScriptError");
+
+XPCOMUtils.defineLazyServiceGetter(this, "activityDistributor",
+                                   "@mozilla.org/network/http-activity-distributor;1",
+                                   "nsIHttpActivityDistributor");
+
+XPCOMUtils.defineLazyServiceGetter(this, "sss",
+                                   "@mozilla.org/content/style-sheet-service;1",
+                                   "nsIStyleSheetService");
+
+function LogFactory(aMessagePrefix)
+{
+  function log(aMessage) {
+    var _msg = aMessagePrefix + " " + aMessage + "\n";
+    dump(_msg);
+  }
+  return log;
+}
+
+let log = LogFactory("*** HUDService:");
+
+const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
+const ELEMENT_NS = "html:";
+const HUD_STYLESHEET_URI = "chrome://global/content/headsUpDisplay.css";
+const HUD_STRINGS_URI = "chrome://global/locale/headsUpDisplay.properties";
+
+XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
+  return Services.strings.createBundle(HUD_STRINGS_URI);
+});
+
+const ERRORS = { LOG_MESSAGE_MISSING_ARGS:
+                 "Missing arguments: aMessage, aConsoleNode and aMessageNode are required.",
+                 CANNOT_GET_HUD: "Cannot getHeads Up Display with provided ID",
+                 MISSING_ARGS: "Missing arguments",
+                 LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode",
+};
+
+function HUD_SERVICE()
+{
+  // TODO: provide mixins for FENNEC: bug 568621
+  if (appName() == "FIREFOX") {
+    var mixins = new FirefoxApplicationHooks();
+  }
+  else {
+    throw new Error("Unsupported Application");
+  }
+
+  this.mixins = mixins;
+  this.storage = new ConsoleStorage();
+  this.defaultFilterPrefs = this.storage.defaultDisplayPrefs;
+  this.defaultGlobalConsolePrefs = this.storage.defaultGlobalConsolePrefs;
+
+  // load stylesheet with StyleSheetService
+  var uri = Services.io.newURI(HUD_STYLESHEET_URI, null, null);
+  sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
+
+  // begin observing HTTP traffic
+  this.startHTTPObservation();
+};
+
+HUD_SERVICE.prototype =
+{
+  /**
+   * L10N shortcut function
+   *
+   * @param string aName
+   * @returns string
+   */
+  getStr: function HS_getStr(aName)
+  {
+    return stringBundle.GetStringFromName(aName);
+  },
+
+  /**
+   * L10N shortcut function
+   *
+   * @param string aName
+   * @returns (format) string
+   */
+  getFormatStr: function HS_getFormatStr(aName, aArray)
+  {
+    return stringBundle.formatStringFromName(aName, aArray, aArray.length);
+  },
+
+  /**
+   * getter for UI commands to be used by the frontend
+   *
+   * @returns object
+   */
+  get consoleUI() {
+    return HeadsUpDisplayUICommands;
+  },
+
+  /**
+   * Collection of HUDIds that map to the tabs/windows/contexts
+   * that a HeadsUpDisplay can be activated for.
+   */
+  activatedContexts: [],
+
+  /**
+   * Registry of HeadsUpDisplay DOM node ids
+   */
+  _headsUpDisplays: {},
+
+  /**
+   * Mapping of HUDIds to URIspecs
+   */
+  displayRegistry: {},
+
+  /**
+   * Mapping of URISpecs to HUDIds
+   */
+  uriRegistry: {},
+
+  /**
+   * The nsILoadGroups being tracked
+   */
+  loadGroups: {},
+
+  /**
+   * The sequencer is a generator (after initialization) that returns unique
+   * integers
+   */
+  sequencer: null,
+
+  /**
+   * Each HeadsUpDisplay has a set of filter preferences
+   */
+  filterPrefs: {},
+
+  /**
+   * We keep track of all of the DOMMutation event listers in this object
+   */
+  mutationEventFunctions: {},
+
+  /**
+   * Event handler to get window errors
+   * TODO: a bit of a hack but is able to associate
+   * errors thrown in a window's scope we do not know
+   * about because of the nsIConsoleMessages not having a
+   * window reference.
+   * see bug 567165
+   *
+   * @param nsIDOMWindow aWindow
+   * @returns boolean
+   */
+  setOnErrorHandler: function HS_setOnErrorHandler(aWindow) {
+    var self = this;
+    var window = aWindow.wrappedJSObject;
+    var console = window.console;
+    var origOnerrorFunc = window.onerror;
+    window.onerror = function windowOnError(aErrorMsg, aURL, aLineNumber)
+    {
+      var lineNum = "";
+      if (aLineNumber) {
+        lineNum = self.getFormatStr("errLine", [aLineNumber]);
+      }
+      console.error(aErrorMsg + " @ " + aURL + " " + lineNum);
+      if (origOnerrorFunc) {
+        origOnerrorFunc(aErrorMsg, aURL, aLineNumber);
+      }
+      return false;
+    };
+  },
+
+  /**
+   * Tell the HUDService that a HeadsUpDisplay can be activated
+   * for the window or context that has 'aContextDOMId' node id
+   *
+   * @param string aContextDOMId
+   * @return void
+   */
+  registerActiveContext: function HS_registerActiveContext(aContextDOMId)
+  {
+    this.activatedContexts.push(aContextDOMId);
+  },
+
+  /**
+   * Firefox-specific current tab getter
+   *
+   * @returns nsIDOMWindow
+   */
+  currentContext: function HS_currentContext() {
+    return this.mixins.getCurrentContext();
+  },
+
+  /**
+   * Tell the HUDService that a HeadsUpDisplay should be deactivated
+   *
+   * @param string aContextDOMId
+   * @return void
+   */
+  unregisterActiveContext: function HS_deregisterActiveContext(aContextDOMId)
+  {
+    var domId = aContextDOMId.split("_")[1];
+    var idx = this.activatedContexts.indexOf(domId);
+    if (idx > -1) {
+      this.activatedContexts.splice(idx, 1);
+    }
+  },
+
+  /**
+   * Tells callers that a HeadsUpDisplay can be activated for the context
+   *
+   * @param string aContextDOMId
+   * @return boolean
+   */
+  canActivateContext: function HS_canActivateContext(aContextDOMId)
+  {
+    var domId = aContextDOMId.split("_")[1];
+    for (var idx in this.activatedContexts) {
+      if (this.activatedContexts[idx] == domId){
+        return true;
+      }
+    }
+    return false;
+  },
+
+  /**
+   * Activate a HeadsUpDisplay for the current window
+   *
+   * @param nsIDOMWindow aContext
+   * @returns void
+   */
+  activateHUDForContext: function HS_activateHUDForContext(aContext)
+  {
+    var window = aContext.linkedBrowser.contentWindow;
+    var id = aContext.linkedBrowser.parentNode.getAttribute("id");
+    this.registerActiveContext(id);
+    HUDService.windowInitializer(window);
+  },
+
+  /**
+   * Deactivate a HeadsUpDisplay for the current window
+   *
+   * @param nsIDOMWindow aContext
+   * @returns void
+   */
+  deactivateHUDForContext: function HS_deactivateHUDForContext(aContext)
+  {
+    var gBrowser = HUDService.currentContext().gBrowser;
+    var window = aContext.linkedBrowser.contentWindow;
+    var browser = gBrowser.getBrowserForDocument(window.top.document);
+    var tabId = gBrowser.getNotificationBox(browser).getAttribute("id");
+    var hudId = "hud_" + tabId;
+    var displayNode = this.getHeadsUpDisplay(hudId);
+
+    this.unregisterActiveContext(hudId);
+    this.unregisterDisplay(hudId);
+    window.wrappedJSObject.console = null;
+  },
+
+  /**
+   * Clear the specified HeadsUpDisplay
+   *
+   * @param string aId
+   * @returns void
+   */
+  clearDisplay: function HS_clearDisplay(aId)
+  {
+    var displayNode = this.getOutputNodeById(aId);
+    var outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
+
+    while (outputNode.firstChild) {
+      outputNode.removeChild(outputNode.firstChild);
+    }
+  },
+
+  /**
+   * get a unique ID from the sequence generator
+   *
+   * @returns integer
+   */
+  sequenceId: function HS_sequencerId()
+  {
+    if (!this.sequencer) {
+      this.sequencer = this.createSequencer(-1);
+    }
+    return this.sequencer.next();
+  },
+
+  /**
+   * get the default filter prefs
+   *
+   * @param string aHUDId
+   * @returns JS Object
+   */
+  getDefaultFilterPrefs: function HS_getDefaultFilterPrefs(aHUDId) {
+    return this.filterPrefs[aHUDId];
+  },
+
+  /**
+   * get the current filter prefs
+   *
+   * @param string aHUDId
+   * @returns JS Object
+   */
+  getFilterPrefs: function HS_getFilterPrefs(aHUDId) {
+    return this.filterPrefs[aHUDId];
+  },
+
+  /**
+   * get the filter state for a specific toggle button on a heads up display
+   *
+   * @param string aHUDId
+   * @param string aToggleType
+   * @returns boolean
+   */
+  getFilterState: function HS_getFilterState(aHUDId, aToggleType)
+  {
+    if (!aHUDId) {
+      return false;
+    }
+    try {
+      var bool = this.filterPrefs[aHUDId][aToggleType];
+      return bool;
+    }
+    catch (ex) {
+      return false;
+    }
+  },
+
+  /**
+   * set the filter state for a specific toggle button on a heads up display
+   *
+   * @param string aHUDId
+   * @param string aToggleType
+   * @param boolean aState
+   * @returns void
+   */
+  setFilterState: function HS_setFilterState(aHUDId, aToggleType, aState)
+  {
+    this.filterPrefs[aHUDId][aToggleType] = aState;
+    this.toggleBinaryFilter(aToggleType, aHUDId);
+  },
+
+  /**
+   * toggle a binary filter on the filter toolbar
+   *
+   * @param string aFilter
+   * @param string aHUDId
+   * @returns void
+   */
+  toggleBinaryFilter:
+  function HS_toggleBinaryFilter(aFilter, aHUDId)
+  {
+    // this is used when document-specific listeners have to be
+    // started or stopped
+    if (aFilter == "mutation") {
+      this.toggleMutationListeners(aHUDId);
+    }
+  },
+
+  /**
+   * Register a new Heads Up Display
+   *
+   * @param string aHUDId
+   * @param string aURISpec
+   * @returns void
+   */
+  registerDisplay: function HS_registerDisplay(aHUDId, aURISpec)
+  {
+    // register a display DOM node Id and HUD uriSpec with the service
+
+    if (!aHUDId || !aURISpec){
+      throw new Error(ERRORS.MISSING_ARGS);
+    }
+    this.filterPrefs[aHUDId] = this.defaultFilterPrefs;
+    this.displayRegistry[aHUDId] = aURISpec;
+    this._headsUpDisplays[aHUDId] = { id: aHUDId, };
+    this.registerActiveContext(aHUDId);
+    // init storage objects:
+    this.storage.createDisplay(aHUDId);
+
+    var huds = this.uriRegistry[aURISpec];
+    var foundHUDId = false;
+
+    if (huds) {
+      var len = huds.length;
+      for (var i = 0; i < len; i++) {
+        if (huds[i] == aHUDId) {
+          foundHUDId = true;
+          break;
+        }
+      }
+      if (!foundHUDId) {
+        this.uriRegistry[aURISpec].push(aHUDId);
+      }
+    }
+    else {
+      this.uriRegistry[aURISpec] = [aHUDId];
+    }
+  },
+
+  /**
+   * When a display is being destroyed, unregister it first
+   *
+   * @param string aId
+   * @returns void
+   */
+  unregisterDisplay: function HS_unregisterDisplay(aId)
+  {
+    // remove HUD DOM node and
+    // remove display references from local registries get the outputNode
+    var outputNode = this.mixins.getOutputNodeById(aId);
+    var parent = outputNode.parentNode;
+    var splitters = parent.querySelectorAll("splitter");
+    var len = splitters.length;
+    for (var i = 0; i < len; i++) {
+      if (splitters[i].getAttribute("class") == "hud-splitter") {
+        splitters[i].parentNode.removeChild(splitters[i]);
+        break;
+      }
+    }
+    parent.removeChild(outputNode);
+
+    delete this._headsUpDisplays[aId];
+
+    let displays = this.displays();
+
+    var uri  = this.displayRegistry[aId];
+    var specHudArr = this.uriRegistry[uri];
+
+    for (var i = 0; i < specHudArr.length; i++) {
+      if (specHudArr[i] == aId) {
+        specHudArr.splice(i, 1);
+      }
+    }
+    delete displays[aId];
+    delete this.displayRegistry[aId];
+  },
+
+  /**
+   * Shutdown all HeadsUpDisplays on xpcom-shutdown
+   *
+   * @returns void
+   */
+  shutdown: function HS_shutdown()
+  {
+    for (var displayId in this._headsUpDisplays) {
+      this.unregisterDisplay(displayId);
+    }
+  },
+
+  /**
+   * get the nsIDOMNode outputNode via a nsIURI.spec
+   *
+   * @param string aURISpec
+   * @returns nsIDOMNode
+   */
+  getDisplayByURISpec: function HS_getDisplayByURISpec(aURISpec)
+  {
+    // TODO: what about data:uris? see bug 568626
+    var hudIds = this.uriRegistry[aURISpec];
+    if (hudIds.length == 1) {
+      // only one HUD connected to this URISpec
+      return this.getHeadsUpDisplay(hudIds[0]);
+    }
+    else {
+      // TODO: how to determine more fully the origination of this activity?
+      // see bug 567165
+      return this.getHeadsUpDisplay(hudIds[0]);
+    }
+  },
+
+  /**
+   * Gets HUD DOM Node
+   * @param string id
+   *        The Heads Up Display DOM Id
+   * @returns nsIDOMNode
+   */
+  getHeadsUpDisplay: function HS_getHeadsUpDisplay(aId)
+  {
+    return this.mixins.getOutputNodeById(aId);
+  },
+
+  /**
+   * gets the nsIDOMNode outputNode by ID via the gecko app mixins
+   *
+   * @param string aId
+   * @returns nsIDOMNode
+   */
+  getOutputNodeById: function HS_getOutputNodeById(aId)
+  {
+    return this.mixins.getOutputNodeById(aId);
+  },
+
+  /**
+   * Gets an object that contains active DOM Node Ids for all Heads Up Displays
+   *
+   * @returns object
+   */
+  displays: function HS_displays() {
+    return this._headsUpDisplays;
+  },
+
+  /**
+   * Get an array of HUDIds that match a uri.spec
+   *
+   * @param string aURISpec
+   * @returns array
+   */
+  getHUDIdsForURISpec: function HS_getHUDIdsForURISpec(aURISpec)
+  {
+    if (this.uriRegistry[aURISpec]) {
+      return this.uriRegistry[aURISpec];
+    }
+    return [];
+  },
+
+  /**
+   * Gets an array that contains active DOM Node Ids for all HUDs
+   * @returns array
+   */
+  displaysIndex: function HS_displaysIndex()
+  {
+    var props = [];
+    for (var prop in this._headsUpDisplays) {
+      props.push(prop);
+    }
+    return props;
+  },
+
+  /**
+   * get the current filter string for the HeadsUpDisplay
+   *
+   * @param string aHUDId
+   * @returns string
+   */
+  getFilterStringByHUDId: function HS_getFilterStringbyHUDId(aHUDId) {
+    var hud = this.getHeadsUpDisplay(aHUDId);
+    var filterStr = hud.querySelectorAll(".hud-filter-box")[0].value;
+    return filterStr || null;
+  },
+
+  /**
+   * The filter strings per HeadsUpDisplay
+   *
+   */
+  hudFilterStrings: {},
+
+  /**
+   * Update the filter text in the internal tracking object for all
+   * filter strings
+   *
+   * @param nsIDOMNode aTextBoxNode
+   * @returns void
+   */
+  updateFilterText: function HS_updateFiltertext(aTextBoxNode)
+  {
+    var hudId = aTextBoxNode.getAttribute(hudId);
+    this.hudFilterStrings[hudId] = aTextBoxNode.value || null;
+  },
+
+  /**
+   * Filter each message being logged into the console
+   *
+   * @param string aFilterString
+   * @param nsIDOMNode aMessageNode
+   * @returns JS Object
+   */
+  filterLogMessage:
+  function HS_filterLogMessage(aFilterString, aMessageNode)
+  {
+    aFilterString = aFilterString.toLowerCase();
+    var messageText = aMessageNode.innerHTML.toLowerCase();
+    var idx = messageText.indexOf(aFilterString);
+    if (idx > -1) {
+      return { strLength: aFilterString.length, strIndex: idx };
+    }
+    else {
+      return null;
+    }
+  },
+
+  /**
+   * Get the filter textbox from a HeadsUpDisplay
+   *
+   * @param string aHUDId
+   * @returns nsIDOMNode
+   */
+  getFilterTextBox: function HS_getFilterTextBox(aHUDId)
+  {
+    var hud = this.getHeadsUpDisplay(aHUDId);
+    return hud.querySelectorAll(".hud-filter-box")[0];
+  },
+
+  /**
+   * Logs a HUD-generated console message
+   * @param object aMessage
+   *        The message to log, which is a JS object, this is the
+   *        "raw" log message
+   * @param nsIDOMNode aConsoleNode
+   *        The output DOM node to log the messageNode to
+   * @param nsIDOMNode aMessageNode
+   *        The message DOM Node that will be appended to aConsoleNode
+   * @returns void
+   */
+  logHUDMessage: function HS_logHUDMessage(aMessage,
+                                           aConsoleNode,
+                                           aMessageNode,
+                                           aFilterState,
+                                           aFilterString)
+  {
+    if (!aFilterState) {
+      // do not log anything
+      return;
+    }
+
+    if (!aMessage) {
+      throw new Error(ERRORS.MISSING_ARGS);
+    }
+
+    if (aFilterString) {
+      var filtered = this.filterLogMessage(aFilterString, aMessageNode);
+      if (filtered) {
+        // we have successfully filtered a message, we need to log it
+        aConsoleNode.appendChild(aMessageNode);
+        aMessageNode.scrollIntoView(false);
+      }
+      else {
+        // we need to ignore this message by changing its css class - we are
+        // still logging this, it is just hidden
+        var hiddenMessage = ConsoleUtils.hideLogMessage(aMessageNode);
+        aConsoleNode.appendChild(hiddenMessage);
+      }
+    }
+    else {
+      // log everything
+      aConsoleNode.appendChild(aMessageNode);
+      aMessageNode.scrollIntoView(false);
+    }
+    // store this message in the storage module:
+    this.storage.recordEntry(aMessage.hudId, aMessage);
+  },
+
+  /**
+   * logs a message to the Heads Up Display that originates
+   * in the nsIConsoleService
+   *
+   * @param nsIConsoleMessage aMessage
+   * @param nsIDOMNode aConsoleNode
+   * @param nsIDOMNode aMessageNode
+   * @returns void
+   */
+  logConsoleMessage: function HS_logConsoleMessage(aMessage,
+                                                   aConsoleNode,
+                                                   aMessageNode,
+                                                   aFilterState,
+                                                   aFilterString)
+  {
+    if (aFilterState){
+      aConsoleNode.appendChild(aMessageNode);
+      aMessageNode.scrollIntoView(false);
+    }
+    // store this message in the storage module:
+    this.storage.recordEntry(aMessage.hudId, aMessage);
+  },
+
+  /**
+   * Logs a Message.
+   * @param aMessage
+   *        The message to log, which is a JS object, this is the
+   *        "raw" log message
+   * @param aConsoleNode
+   *        The output DOM node to log the messageNode to
+   * @param The message DOM Node that will be appended to aConsoleNode
+   * @returns void
+   */
+  logMessage: function HS_logMessage(aMessage, aConsoleNode, aMessageNode)
+  {
+    if (!aMessage) {
+      throw new Error(ERRORS.MISSING_ARGS);
+    }
+
+    var hud = this.getHeadsUpDisplay(aMessage.hudId);
+    // check filter before logging to the outputNode
+    var filterState = this.getFilterState(aMessage.hudId, aMessage.logLevel);
+    var filterString = this.getFilterStringByHUDId(aMessage.hudId);
+
+    switch (aMessage.origin) {
+      case "network":
+      case "HUDConsole":
+        this.logHUDMessage(aMessage, aConsoleNode, aMessageNode, filterState, filterString);
+        break;
+      case "console-listener":
+        this.logHUDMessage(aMessage, aConsoleNode, aMessageNode, filterState, filterString);
+        break;
+      default:
+        // noop
+        break;
+    }
+  },
+
+  /**
+   * report consoleMessages recieved via the HUDConsoleObserver service
+   * @param nsIConsoleMessage aConsoleMessage
+   * @returns void
+   */
+  reportConsoleServiceMessage:
+  function HS_reportConsoleServiceMessage(aConsoleMessage)
+  {
+    this.logActivity("console-listener", null, aConsoleMessage);
+  },
+
+  /**
+   * report scriptErrors recieved via the HUDConsoleObserver service
+   * @param nsIScriptError aScriptError
+   * @returns void
+   */
+  reportConsoleServiceContentScriptError:
+  function HS_reportConsoleServiceContentScriptError(aScriptError)
+  {
+    try {
+      var uri = Services.io.newURI(aScriptError.sourceName, null, null);
+    }
+    catch(ex) {
+      var uri = { spec: "" };
+    }
+    this.logActivity("console-listener", uri, aScriptError);
+  },
+
+  /**
+   * generates an nsIScriptError
+   *
+   * @param object aMessage
+   * @param integer flag
+   * @returns nsIScriptError
+   */
+  generateConsoleMessage:
+  function HS_generateConsoleMessage(aMessage, flag)
+  {
+    let message = scriptError; // nsIScriptError
+    message.init(aMessage.message, null, null, 0, 0, flag,
+                 "HUDConsole");
+    return message;
+  },
+
+  /**
+   * Register a Gecko app's specialized ApplicationHooks object
+   *
+   * @returns void or throws "UNSUPPORTED APPLICATION" error
+   */
+  registerApplicationHooks:
+  function HS_registerApplications(aAppName, aHooksObject)
+  {
+    switch(aAppName) {
+      case "FIREFOX":
+        this.applicationHooks = aHooksObject;
+        return;
+      default:
+        throw new Error("MOZ APPLICATION UNSUPPORTED");
+    }
+  },
+
+  /**
+   * Registry of ApplicationHooks used by specified Gecko Apps
+   *
+   * @returns Specific Gecko 'ApplicationHooks' Object/Mixin
+   */
+  applicationHooks: null,
+
+  /**
+   * Given an nsIChannel, return the corresponding nsILoadContext
+   *
+   * @param nsIChannel aChannel
+   * @returns nsILoadContext
+   */
+  getLoadContext: function HS_getLoadContext(aChannel)
+  {
+    if (!aChannel) {
+      return null;
+    }
+    var loadContext;
+    var callbacks = aChannel.notificationCallbacks;
+
+    loadContext =
+      aChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
+    if (!loadContext) {
+      loadContext =
+        aChannel.QueryInterface(Ci.nsIRequest).loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
+    }
+    return loadContext;
+  },
+
+  /**
+   * Given an nsILoadContext, return the corresponding nsIDOMWindow
+   *
+   * @param nsILoadContext aLoadContext
+   * @returns nsIDOMWindow
+   */
+  getWindowFromContext: function HS_getWindowFromContext(aLoadContext)
+  {
+    if (!aLoadContext) {
+      throw new Error("loadContext is null");
+    }
+    if (aLoadContext.isContent) {
+      if (aLoadContext.associatedWindow) {
+        return aLoadContext.associatedWindow;
+      }
+      else if (aLoadContext.topWindow) {
+        return aLoadContext.topWindow;
+      }
+    }
+    throw new Error("Cannot get window from " + aLoadContext);
+  },
+
+  getChromeWindowFromContentWindow:
+  function HS_getChromeWindowFromContentWindow(aContentWindow)
+  {
+    if (!aContentWindow) {
+      throw new Error("Cannot get contentWindow via nsILoadContext");
+    }
+    var win = aContentWindow.QueryInterface(Ci.nsIDOMWindow)
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebNavigation)
+      .QueryInterface(Ci.nsIDocShellTreeItem)
+      .rootTreeItem
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindow)
+      .QueryInterface(Ci.nsIDOMChromeWindow);
+    return win;
+  },
+
+  /**
+   * get the outputNode from the window object
+   *
+   * @param nsIDOMWindow aWindow
+   * @returns nsIDOMNode
+   */
+  getOutputNodeFromWindow:
+  function HS_getOutputNodeFromWindow(aWindow)
+  {
+    var browser = gBrowser.getBrowserForDocument(aWindow.top.document);
+    var tabId = gBrowser.getNotificationBox(browser).getAttribute("id");
+    var hudId = "hud_" + tabId;
+    var displayNode = this.getHeadsUpDisplay(hudId);
+    return displayNode.querySelectorAll(".hud-output-node")[0];
+  },
+
+  /**
+   * Try to get the outputNode via the nsIRequest
+   * TODO: get node via request, see bug 552140
+   * @param nsIRequest aRequest
+   * @returns nsIDOMNode
+   */
+  getOutputNodeFromRequest: function HS_getOutputNodeFromRequest(aRequest)
+  {
+    var context = this.getLoadContext(aRequest);
+    var window = this.getWindowFromContext(context);
+    return this.getOutputNodeFromWindow(window);
+  },
+
+  getLoadContextFromChannel: function HS_getLoadContextFromChannel(aChannel)
+  {
+    try {
+      return aChannel.QueryInterface(Ci.nsIChannel).notificationCallbacks.getInterface(Ci.nsILoadContext);
+    }
+    catch (ex) {
+      // noop, keep this output quiet. see bug 552140
+    }
+    try {
+      return aChannel.QueryInterface(Ci.nsIChannel).loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
+    }
+    catch (ex) {
+      // noop, keep this output quiet. see bug 552140
+    }
+    return null;
+  },
+
+  getWindowFromLoadContext:
+  function HS_getWindowFromLoadContext(aLoadContext)
+  {
+    if (aLoadContext.topWindow) {
+      return aLoadContext.topWindow;
+    }
+    else {
+      return aLoadContext.associatedWindow;
+    }
+  },
+
+  /**
+   * Begin observing HTTP traffic that we care about,
+   * namely traffic that originates inside any context that a Heads Up Display
+   * is active for.
+   */
+  startHTTPObservation: function HS_httpObserverFactory()
+  {
+    // creates an observer for http traffic
+    var self = this;
+    var httpObserver = {
+      observeActivity :
+      function (aChannel, aActivityType, aActivitySubtype,
+                aTimestamp, aExtraSizeData, aExtraStringData)
+      {
+        var loadGroup;
+        if (aActivityType ==
+            activityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION) {
+          try {
+            var loadContext = self.getLoadContextFromChannel(aChannel);
+            // TODO: get image request data from the channel
+            // see bug 552140
+            var window = self.getWindowFromLoadContext(loadContext);
+            window = XPCNativeWrapper.unwrap(window);
+            var chromeWin = self.getChromeWindowFromContentWindow(window);
+            var vboxes =
+              chromeWin.document.getElementsByTagName("vbox");
+            var hudId;
+            for (var i = 0; i < vboxes.length; i++) {
+              if (vboxes[i].getAttribute("class") == "hud-box") {
+                hudId = vboxes[i].getAttribute("id");
+              }
+            }
+            loadGroup = self.getLoadGroup(hudId);
+          }
+          catch (ex) {
+            loadGroup = aChannel.QueryInterface(Ci.nsIChannel)
+                        .QueryInterface(Ci.nsIRequest).loadGroup;
+          }
+
+          if (!loadGroup) {
+              return;
+          }
+
+          aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
+
+          var transCodes = this.httpTransactionCodes;
+
+          var httpActivity = {
+            channel: aChannel,
+            loadGroup: loadGroup,
+            type: aActivityType,
+            subType: aActivitySubtype,
+            timestamp: aTimestamp,
+            extraSizeData: aExtraSizeData,
+            extraStringData: aExtraStringData,
+            stage: transCodes[aActivitySubtype],
+          };
+          if (aActivitySubtype ==
+              activityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER ) {
+                // create a unique ID to track this transaction and be able to
+                // update the logged node with subsequent http transactions
+                httpActivity.httpId = self.sequenceId();
+                let loggedNode =
+                  self.logActivity("network", aChannel.URI, httpActivity);
+                self.httpTransactions[aChannel] =
+                  new Number(httpActivity.httpId);
+          }
+        }
+      },
+
+      httpTransactionCodes: {
+        0x5001: "REQUEST_HEADER",
+        0x5002: "REQUEST_BODY_SENT",
+        0x5003: "RESPONSE_START",
+        0x5004: "RESPONSE_HEADER",
+        0x5005: "RESPONSE_COMPLETE",
+        0x5006: "TRANSACTION_CLOSE",
+      }
+    };
+
+    activityDistributor.addObserver(httpObserver);
+  },
+
+  // keep tracked of trasactions where the request header was logged
+  // update logged transactions thereafter.
+  httpTransactions: {},
+
+  /**
+   * Logs network activity
+   *
+   * @param nsIURI aURI
+   * @param object aActivityObject
+   * @returns void
+   */
+  logNetActivity: function HS_logNetActivity(aType, aURI, aActivityObject)
+  {
+    var displayNode, outputNode, hudId;
+    try {
+      displayNode =
+      this.getDisplayByLoadGroup(aActivityObject.loadGroup,
+                                 {URI: aURI}, aActivityObject);
+      if (!displayNode) {
+        return;
+      }
+      outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
+      hudId = displayNode.getAttribute("id");
+
+      if (!outputNode) {
+        outputNode = this.getOutputNodeFromRequest(aActivityObject.request);
+        hudId = outputNode.ownerDocument.querySelectorAll(".hud-box")[0].
+                getAttribute("id");
+      }
+
+      // check if network activity logging is "on":
+      if (!this.getFilterState(hudId, "network")) {
+        return;
+      }
+
+      // get an id to attach to the dom node for lookup of node
+      // when updating the log entry with additional http transactions
+      var domId = "hud-log-node-" + this.sequenceId();
+
+      var message = { logLevel: aType,
+                      activityObj: aActivityObject,
+                      hudId: hudId,
+                      origin: "network",
+                      domId: domId,
+                    };
+      var msgType = this.getStr("typeNetwork");
+      var msg = msgType + " " +
+        aActivityObject.channel.requestMethod +
+        " " +
+        aURI.spec;
+      message.message = msg;
+      var messageObject =
+      this.messageFactory(message, aType, outputNode, aActivityObject);
+      this.logMessage(messageObject.messageObject, outputNode, messageObject.messageNode);
+    }
+    catch (ex) {
+      Cu.reportError(ex);
+    }
+  },
+
+  /**
+   * Logs console listener activity
+   *
+   * @param nsIURI aURI
+   * @param object aActivityObject
+   * @returns void
+   */
+  logConsoleActivity: function HS_logConsoleActivity(aURI, aActivityObject)
+  {
+    var displayNode, outputNode, hudId;
+    try {
+        var hudIds = this.uriRegistry[aURI.spec];
+        hudId = hudIds[0];
+    }
+    catch (ex) {
+      // TODO: uri spec is not tracked becasue the net request is
+      // using a different loadGroup
+      // see bug 568034
+      if (!displayNode) {
+        return;
+      }
+    }
+
+    var _msgLogLevel = this.scriptMsgLogLevel[aActivityObject.flags];
+    var msgLogLevel = this.getStr(_msgLogLevel);
+
+    // check if we should be logging this message:
+    var filterState = this.getFilterState(hudId, msgLogLevel);
+
+    if (!filterState) {
+      // Ignore log message
+      return;
+    }
+
+    // in this case, the "activity object" is the
+    // nsIScriptError or nsIConsoleMessage
+    var message = {
+      activity: aActivityObject,
+      origin: "console-listener",
+      hudId: hudId,
+    };
+
+    try {
+      var logLevel = this.scriptErrorFlags[aActivityObject.flags];
+    }
+    catch (ex) {
+      var logLevel = "warn";
+    }
+    var lineColSubs = [aActivityObject.columnNumber,
+                       aActivityObject.lineNumber];
+    var lineCol = this.getFormatStr("errLineCol", lineColSubs);
+
+    var errFileSubs = [aActivityObject.sourceName];
+    var errFile = this.getFormatStr("errFile", errFileSubs);
+
+    var msgCategory = this.getStr("msgCategory");
+
+    message.logLevel = logLevel;
+    message.level = logLevel;
+
+    message.message = msgLogLevel + " " +
+                      aActivityObject.errorMessage + " " +
+                      errFile + " " +
+                      lineCol + " " +
+                      msgCategory + " " + aActivityObject.category;
+
+    displayNode = this.getHeadsUpDisplay(hudId);
+    outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
+
+    var messageObject =
+    this.messageFactory(message, message.level, outputNode, aActivityObject);
+
+    this.logMessage(messageObject.messageObject, outputNode, messageObject.messageNode);
+  },
+
+  /**
+   * Parse log messages for origin or listener type
+   * Get the correct outputNode if it exists
+   * Finally, call logMessage to write this message to
+   * storage and optionally, a DOM output node
+   *
+   * @param string aType
+   * @param nsIURI aURI
+   * @param object (or nsIScriptError) aActivityObj
+   * @returns void
+   */
+  logActivity: function HS_logActivity(aType, aURI, aActivityObject)
+  {
+    var displayNode, outputNode, hudId;
+
+    if (aType == "network") {
+      var result = this.logNetActivity(aType, aURI, aActivityObject);
+    }
+    else if (aType == "console-listener") {
+      this.logConsoleActivity(aURI, aActivityObject);
+    }
+  },
+
+  /**
+   * update loadgroup when the window object is re-created
+   *
+   * @param string aId
+   * @param nsILoadGroup aLoadGroup
+   * @returns void
+   */
+  updateLoadGroup: function HS_updateLoadGroup(aId, aLoadGroup)
+  {
+    if (this.loadGroups[aId] == undefined) {
+      this.loadGroups[aId] = { id: aId,
+                               loadGroup: Cu.getWeakReference(aLoadGroup) };
+    }
+    else {
+      this.loadGroups[aId].loadGroup = Cu.getWeakReference(aLoadGroup);
+    }
+  },
+
+  /**
+   * gets the load group that corresponds to a HUDId
+   *
+   * @param string aId
+   * @returns nsILoadGroup
+   */
+  getLoadGroup: function HS_getLoadGroup(aId)
+  {
+    try {
+      return this.loadGroups[aId].loadGroup.get();
+    }
+    catch (ex) {
+      return null;
+    }
+  },
+
+  /**
+   * gets outputNode for a specific heads up display by loadGroup
+   *
+   * @param nsILoadGroup aLoadGroup
+   * @returns nsIDOMNode
+   */
+  getDisplayByLoadGroup:
+  function HS_getDisplayByLoadGroup(aLoadGroup, aChannel, aActivityObject)
+  {
+    if (!aLoadGroup) {
+      return null;
+    }
+    var trackedLoadGroups = this.getAllLoadGroups();
+    var len = trackedLoadGroups.length;
+    for (var i = 0; i < len; i++) {
+      try {
+        var unwrappedLoadGroup =
+        XPCNativeWrapper.unwrap(trackedLoadGroups[i].loadGroup);
+        if (aLoadGroup == unwrappedLoadGroup) {
+          return this.getOutputNodeById(trackedLoadGroups[i].hudId);
+        }
+      }
+      catch (ex) {
+        // noop
+      }
+    }
+    // TODO: also need to check parent loadGroup(s) incase of iframe activity?;
+    // see bug 568643
+    return null;
+  },
+
+  /**
+   * gets all nsILoadGroups that are being tracked by this service
+   * the loadgroups are matched to HUDIds in an object and an array is returned
+   * @returns array
+   */
+  getAllLoadGroups: function HS_getAllLoadGroups()
+  {
+    var loadGroups = [];
+    for (var hudId in this.loadGroups) {
+      let loadGroupObj = { loadGroup: this.loadGroups[hudId].loadGroup.get(),
+                           hudId: this.loadGroups[hudId].id,
+                         };
+      loadGroups.push(loadGroupObj);
+    }
+    return loadGroups;
+  },
+
+  /**
+   * gets the DOM Node that maps back to what context/tab that
+   * activity originated via the URI
+   *
+   * @param nsIURI aURI
+   * @returns nsIDOMNode
+   */
+  getActivityOutputNode: function HS_getActivityOutputNode(aURI)
+  {
+    // determine which outputNode activity tied to aURI should be logged to.
+    var display = this.getDisplayByURISpec(aURI.spec);
+    if (display) {
+      return this.getOutputNodeById(display);
+    }
+    else {
+      throw new Error("Cannot get outputNode by hudId");
+    }
+  },
+
+  /**
+   * Wrapper method that generates a LogMessage object
+   *
+   * @param object aMessage
+   * @param string aLevel
+   * @param nsIDOMNode aOutputNode
+   * @param object aActivityObject
+   * @returns
+   */
+  messageFactory:
+  function messageFactory(aMessage, aLevel, aOutputNode, aActivityObject)
+  {
+    // generate a LogMessage object
+    return new LogMessage(aMessage, aLevel, aOutputNode,  aActivityObject);
+  },
+
+  /**
+   * Initialize the JSTerm object to create a JS Workspace
+   *
+   * @param nsIDOMWindow aContext
+   * @param nsIDOMNode aParentNode
+   * @returns void
+   */
+  initializeJSTerm: function HS_initializeJSTerm(aContext, aParentNode)
+  {
+    // create Initial JS Workspace:
+    var context = Cu.getWeakReference(aContext);
+    var firefoxMixin = new JSTermFirefoxMixin(context, aParentNode);
+    var jsTerm = new JSTerm(context, aParentNode, firefoxMixin);
+    // TODO: injection of additional functionality needs re-thinking/api
+    // see bug 559748
+  },
+
+  /**
+   * Passed a HUDId, the corresponding window is returned
+   *
+   * @param string aHUDId
+   * @returns nsIDOMWindow
+   */
+  getContentWindowFromHUDId: function HS_getContentWindowFromHUDId(aHUDId)
+  {
+    var hud = this.getHeadsUpDisplay(aHUDId);
+    var nodes = hud.parentNode.childNodes;
+
+    for (var i = 0; i < nodes.length; i++) {
+      if (nodes[i].contentWindow) {
+        return nodes[i].contentWindow;
+      }
+    }
+    throw new Error("HS_getContentWindowFromHUD: Cannot get contentWindow");
+  },
+
+  /**
+   * attaches the DOMMutation listeners to a nsIDOMWindow object
+   *
+   * @param nsIDOMWindow aWindow
+   * @param string aHUDId
+   * @returns void
+   */
+  attachMutationListeners:
+  function HS_attachMutationListeners(aWindow, aHUDId)
+  {
+    try {
+      // remove first in case it is on already
+      new ConsoleDOMListeners(aWindow, aHUDId, true);
+      // add mutation listeners
+      var domListeners = new ConsoleDOMListeners(aWindow, aHUDId);
+    }
+    catch (ex) {
+      Cu.reportError(ex);
+    }
+  },
+
+  /**
+   * removes DOMMutation listeners
+   *
+   * @param nsIDOMWindow aWindow
+   * @param string aHUDId
+   * @returns void
+   */
+  removeMutationListeners:
+  function HS_removeMutationListeners(aWindow, aHUDId)
+  {
+    // turns off the listeners if active
+    try {
+      new ConsoleDOMListeners(aWindow, aHUDId, true);
+    }
+    catch (ex) {
+      Cu.reportError(ex);
+    }
+  },
+
+  /**
+   * toggle on and off teh DOMMutation listeners
+   *
+   * @param string aHUDId
+   * @returns void
+   */
+  toggleMutationListeners: function HS_toggleMutationListeners(aHUDId)
+  {
+    // get the contentWindow from the HUDId
+    var window = this.getContentWindowFromHUDId(aHUDId);
+    var filterState = this.getFilterState(aHUDId, "mutation");
+
+    if (!filterState) {
+      // turn it off
+      this.removeMutationListeners(window);
+    }
+    else {
+      this.attachMutationListeners(window, aHUDId);
+    }
+  },
+
+  mutationListenerIndex: {},
+
+  /**
+   * Creates a generator that always returns a unique number for use in the
+   * indexes
+   *
+   * @returns Generator
+   */
+  createSequencer: function HS_createSequencer(aInt)
+  {
+    function sequencer(aInt)
+    {
+      while(1) {
+        aInt++;
+        yield aInt;
+      }
+    }
+    return sequencer(aInt);
+  },
+
+  scriptErrorFlags: {
+    0: "error",
+    1: "warn",
+    2: "exception",
+    4: "strict"
+  },
+
+  /**
+   * replacement strings (L10N)
+   */
+  scriptMsgLogLevel: {
+    0: "typeError",
+    1: "typeWarning",
+    2: "typeException",
+    4: "typeStrict",
+  },
+
+  /**
+   * windowInitializer - checks what Gecko app is running and inits the HUD
+   *
+   * @param nsIDOMWindow aContentWindow
+   * @returns void
+   */
+  windowInitializer: function HS_WindowInitalizer(aContentWindow)
+  {
+    var xulWindow = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebNavigation)
+      .QueryInterface(Ci.nsIDocShellTreeItem)
+      .rootTreeItem
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindow);
+
+    if (aContentWindow.document.location.href == "about:blank" &&
+        HUDWindowObserver.initialConsoleCreated == false) {
+      // TODO: need to make this work with about:blank in the future
+      // see bug 568661
+      return;
+    }
+
+    let xulWindow = XPCNativeWrapper.unwrap(xulWindow);
+    let gBrowser = xulWindow.gBrowser;
+
+    if (gBrowser && !HUDWindowObserver.initialConsoleCreated) {
+      HUDWindowObserver.initialConsoleCreated = true;
+    }
+
+    let _browser =
+      gBrowser.getBrowserForDocument(aContentWindow.document.wrappedJSObject);
+    let nBox = gBrowser.getNotificationBox(_browser);
+    let nBoxId = nBox.getAttribute("id");
+    let hudId = "hud_" + nBoxId;
+
+    if (!this.canActivateContext(hudId)) {
+      return;
+    }
+
+    this.registerDisplay(hudId, aContentWindow.document.location.href);
+
+    // check if aContentWindow has a console Object
+    let _console = aContentWindow.wrappedJSObject.console;
+    if (!_console) {
+      // no console exists. does the HUD exist?
+      let hudNode;
+      let childNodes = nBox.childNodes;
+
+      for (var i = 0; i < childNodes.length; i++) {
+        let id = childNodes[i].getAttribute("id");
+        if (id.split("_")[0] == "hud") {
+          hudNode = childNodes[i];
+          break;
+        }
+      }
+
+      if (!hudNode) {
+        // get nBox object and call new HUD
+        let config = { parentNode: nBox,
+                       contentWindow: aContentWindow
+                     };
+
+        let _hud = new HeadsUpDisplay(config);
+      }
+      else {
+        // only need to attach a console object to the window object
+        let config = { hudNode: hudNode,
+                       consoleOnly: true,
+                       contentWindow: aContentWindow
+                     };
+
+        let _hud = new HeadsUpDisplay(config);
+        aContentWindow.wrappedJSObject.console = _hud.console;
+        var mutationFlag = this.getFilterState(this.hudId, "mutation");
+        if (mutationFlag) {
+          this.attachMutationListeners(aContentWindow, this.hudId);
+        }
+      }
+    }
+    // capture JS Errors
+    this.setOnErrorHandler(aContentWindow);
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////
+// HeadsUpDisplay
+//////////////////////////////////////////////////////////////////////////
+
+/*
+ * HeadsUpDisplay is an interactive console initialized *per tab*  that
+ * displays console log data as well as provides an interactive terminal to
+ * manipulate the current tab's document content.
+ * */
+function HeadsUpDisplay(aConfig)
+{
+  // sample config: { parentNode: aDOMNode,
+  //                  // or
+  //                  parentNodeId: "myHUDParent123",
+  //
+  //                  placement: "appendChild"
+  //                  // or
+  //                  placement: "insertBefore",
+  //                  placementChildNodeIndex: 0,
+  //                }
+  //
+  // or, just create a new console - as there is already a HUD in place
+  // config: { hudNode: existingHUDDOMNode,
+  //           consoleOnly: true,
+  //           contentWindow: aWindow
+  //         }
+
+  if (aConfig.consoleOnly) {
+    this.HUDBox = aConfig.hudNode;
+    this.parentNode = aConfig.hudNode.parentNode;
+    this.notificationBox = this.parentNode;
+    this.contentWindow = aConfig.contentWindow;
+    this.uriSpec = aConfig.contentWindow.location.href;
+    this.reattachConsole();
+    return;
+  }
+
+  this.HUDBox = null;
+
+  if (aConfig.parentNode) {
+    // TODO: need to replace these DOM calls with internal functions
+    // that operate on each application's node structure
+    // better yet, we keep these functions in a "bridgeModule" or the HUDService
+    // to keep a registry of nodeGetters for each application
+    // see bug 568647
+    this.parentNode = aConfig.parentNode;
+    this.notificationBox = aConfig.parentNode;
+    this.chromeDocument = aConfig.parentNode.ownerDocument;
+    this.contentWindow = aConfig.contentWindow;
+    this.uriSpec = aConfig.contentWindow.location.href;
+    this.hudId = "hud_" + aConfig.parentNode.getAttribute("id");
+  }
+  else {
+    // parentNodeId is the node's id where we attach the HUD
+    // TODO: is the "navigator:browser" below used in all Gecko Apps?
+    // see bug 568647
+    let windowEnum = Services.wm.getEnumerator("navigator:browser");
+    let parentNode;
+    let contentDocument;
+    let contentWindow;
+    let chromeDocument;
+
+    // TODO: the following  part is still very Firefox specific
+    // see bug 568647
+
+    while (windowEnum.hasMoreElements()) {
+      let window = windowEnum.getNext();
+      try {
+        let gBrowser = window.gBrowser;
+        let _browsers = gBrowser.browsers;
+        let browserLen = _browsers.length;
+
+        for (var i = 0; i < browserLen; i++) {
+          var _notificationBox = gBrowser.getNotificationBox(_browsers[i]);
+          this.notificationBox = _notificationBox;
+
+          if (_notificationBox.getAttribute("id") == aConfig.parentNodeId) {
+            this.parentNodeId = _notificationBox.getAttribute("id");
+            this.hudId = "hud_" + this.parentNodeId;
+
+            parentNode = _notificationBox;
+
+            this.contentDocument =
+              _notificationBox.childNodes[0].contentDocument;
+            this.contentWindow =
+              _notificationBox.childNodes[0].contentWindow;
+            this.uriSpec = aConfig.contentWindow.location.href;
+
+            this.chromeDocument =
+              _notificationBox.ownerDocument;
+
+            break;
+          }
+        }
+      }
+      catch (ex) {
+        Cu.reportError(ex);
+      }
+
+      if (parentNode) {
+        break;
+      }
+    }
+    if (!parentNode) {
+      throw new Error(this.ERRORS.PARENTNODE_NOT_FOUND);
+    }
+    this.parentNode = parentNode;
+  }
+  // create XUL, HTML and textNode Factories:
+  try  {
+    this.HTMLFactory = NodeFactory("html", "html", this.chromeDocument);
+  }
+  catch(ex) {
+    Cu.reportError(ex);
+  }
+
+  this.XULFactory = NodeFactory("xul", "xul", this.chromeDocument);
+  this.textFactory = NodeFactory("text", "xul", this.chromeDocument);
+
+  // create a panel dynamically and attach to the parentNode
+  let hudBox = this.createHUD();
+
+  let splitter = this.chromeDocument.createElement("splitter");
+  splitter.setAttribute("collapse", "before");
+  splitter.setAttribute("resizeafter", "flex");
+  splitter.setAttribute("class", "hud-splitter");
+
+  let grippy = this.chromeDocument.createElement("grippy");
+  this.notificationBox.insertBefore(splitter,
+                                    this.notificationBox.childNodes[1]);
+  splitter.appendChild(grippy);
+
+  let console = this.createConsole();
+
+  this.contentWindow.wrappedJSObject.console = console;
+  // check prefs to see if we should attact mutation listeners
+  var mutationFlag = HUDService.getFilterState(this.hudId, "mutation");
+  if (mutationFlag) {
+    HUDService.attachMutationListeners(this.contentWindow, this.hudId);
+  }
+  // create the JSTerm input element
+  try {
+    this.createConsoleInput(this.contentWindow, this.consoleWrap, this.outputNode);
+  }
+  catch (ex) {
+    Cu.reportError(ex);
+  }
+}
+
+HeadsUpDisplay.prototype = {
+  /**
+   * L10N shortcut function
+   *
+   * @param string aName
+   * @returns string
+   */
+  getStr: function HUD_getStr(aName)
+  {
+    return stringBundle.GetStringFromName(aName);
+  },
+
+  /**
+   * L10N shortcut function
+   *
+   * @param string aName
+   * @param array aArray
+   * @returns string
+   */
+  getFormatStr: function HUD_getFormatStr(aName, aArray)
+  {
+    return stringBundle.formatStringFromName(aName, aArray, aArray.length);
+  },
+
+  /**
+   * creates and attaches the console input node
+   *
+   * @param nsIDOMWindow aWindow
+   * @returns void
+   */
+  createConsoleInput:
+  function HUD_createConsoleInput(aWindow, aParentNode, aExistingConsole)
+  {
+    var context = Cu.getWeakReference(aWindow);
+
+    if (appName() == "FIREFOX") {
+      let outputCSSClassOverride = "hud-msg-node hud-console";
+      let mixin = new JSTermFirefoxMixin(context, aParentNode, aExistingConsole, outputCSSClassOverride);
+      let inputNode = new JSTerm(context, aParentNode, mixin);
+    }
+    else {
+      throw new Error("Unsupported Gecko Application");
+    }
+  },
+
+  /**
+   * Re-attaches a console when the contentWindow is recreated
+   *
+   * @returns void
+   */
+  reattachConsole: function HUD_reattachConsole()
+  {
+    this.hudId = this.HUDBox.getAttribute("id");
+
+    // set outputNode
+    this.outputNode = this.HUDBox.querySelectorAll(".hud-output-node")[0];
+
+    this.chromeDocument = this.HUDBox.ownerDocument;
+
+    if (this.outputNode) {
+      // createConsole
+      this.createConsole();
+    }
+    else {
+      throw new Error("Cannot get output node");
+    }
+  },
+
+  /**
+   * Gets the loadGroup for the contentWindow
+   *
+   * @returns nsILoadGroup
+   */
+  get loadGroup()
+  {
+    var loadGroup = this.contentWindow
+                    .QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocumentLoader).loadGroup;
+    return loadGroup;
+  },
+
+  /**
+   * Shortcut to make HTML nodes
+   *
+   * @param string aTag
+   * @returns nsIDOMNode
+   */
+  makeHTMLNode:
+  function HUD_makeHTMLNode(aTag)
+  {
+    try {
+      return this.HTMLFactory(aTag);
+    }
+    catch (ex) {
+      var ns = ELEMENT_NS;
+      var nsUri = ELEMENT_NS_URI;
+      var tag = ns + aTag;
+      return this.chromeDocument.createElementNS(nsUri, tag);
+    }
+  },
+
+  /**
+   * Shortcut to make XUL nodes
+   *
+   * @param string aTag
+   * @returns nsIDOMNode
+   */
+  makeXULNode:
+  function HUD_makeXULNode(aTag)
+  {
+    return this.XULFactory(aTag);
+  },
+
+  /**
+   * Clears the HeadsUpDisplay output node of any log messages
+   *
+   * @returns void
+   */
+  clearConsoleOutput: function HUD_clearConsoleOutput()
+  {
+    for each (var node in this.outputNode.childNodes) {
+      this.outputNode.removeChild(node);
+    }
+  },
+
+  /**
+   * Build the UI of each HeadsUpDisplay
+   *
+   * @returns nsIDOMNode
+   */
+  makeHUDNodes: function HUD_makeHUDNodes()
+  {
+    let self = this;
+    this.HUDBox = this.makeXULNode("vbox");
+    this.HUDBox.setAttribute("id", this.hudId);
+    this.HUDBox.setAttribute("class", "hud-box");
+
+    let outerWrap = this.makeXULNode("vbox");
+    outerWrap.setAttribute("class", "hud-outer-wrapper");
+    outerWrap.setAttribute("flex", "1");
+
+    let consoleCommandSet = this.makeXULNode("commandset");
+    outerWrap.appendChild(consoleCommandSet);
+
+    let consoleWrap = this.makeXULNode("vbox");
+    this.consoleWrap = consoleWrap;
+    consoleWrap.setAttribute("class", "hud-console-wrapper");
+    consoleWrap.setAttribute("flex", "1");
+
+    this.outputNode = this.makeXULNode("vbox");
+    this.outputNode.setAttribute("class", "hud-output-node");
+    this.outputNode.setAttribute("flex", "1");
+
+    this.filterBox = this.makeXULNode("textbox");
+    this.filterBox.setAttribute("class", "hud-filter-box");
+    this.filterBox.setAttribute("hudId", this.hudId);
+
+    this.filterClearButton = this.makeXULNode("button");
+    this.filterClearButton.setAttribute("class", "hud-filter-clear");
+    this.filterClearButton.setAttribute("label", this.getStr("stringFilterClear"));
+    this.filterClearButton.setAttribute("hudId", this.hudId);
+
+    this.setFilterTextBoxEvents();
+
+    this.consoleClearButton = this.makeXULNode("button");
+    this.consoleClearButton.setAttribute("class", "hud-console-clear");
+    this.consoleClearButton.setAttribute("label", this.getStr("btnClear"));
+    this.consoleClearButton.setAttribute("buttonType", "clear");
+    this.consoleClearButton.setAttribute("hudId", this.hudId);
+    var command = "HUDConsoleUI.command(this)";
+    this.consoleClearButton.setAttribute("oncommand", command);
+
+    this.filterPrefs = HUDService.getDefaultFilterPrefs(this.hudId);
+
+    let consoleFilterToolbar = this.makeFilterToolbar();
+    consoleFilterToolbar.setAttribute("mode", "text");
+    consoleFilterToolbar.setAttribute("id", "viewGroup");
+    this.consoleFilterToolbar = consoleFilterToolbar;
+    consoleWrap.appendChild(consoleFilterToolbar);
+
+    consoleWrap.appendChild(this.outputNode);
+    outerWrap.appendChild(consoleWrap);
+
+    this.jsTermParentNode = outerWrap;
+    this.HUDBox.appendChild(outerWrap);
+    return this.HUDBox;
+  },
+
+
+  /**
+   * sets the click events for all binary toggle filter buttons
+   *
+   * @returns void
+   */
+  setFilterTextBoxEvents: function HUD_setFilterTextBoxEvents()
+  {
+    var self = this;
+    function keyPress(aEvent)
+    {
+      HUDService.updateFilterText(aEvent.target);
+    }
+    this.filterBox.addEventListener("keydown", keyPress, false);
+
+    function filterClick(aEvent) {
+      self.filterBox.value = "";
+    }
+    this.filterClearButton.addEventListener("click", filterClick, false);
+  },
+
+  /**
+   * Make the filter toolbar where we can toggle logging filters
+   *
+   * @returns nsIDOMNode
+   */
+  makeFilterToolbar: function HUD_makeFilterToolbar()
+  {
+    let buttons = ["Mutation", "Network", "CSSParser",
+                   "Exception", "Error",
+                   "Info", "Warn", "Log",];
+
+    let toolbar = this.makeXULNode("toolbar");
+    toolbar.setAttribute("class", "hud-console-filter-toolbar");
+
+    toolbar.appendChild(this.consoleClearButton);
+    let btn;
+    for (var i = 0; i < buttons.length; i++) {
+      if (buttons[i] == "Clear") {
+        btn = this.makeButton(buttons[i], "plain");
+      }
+      else {
+        btn = this.makeButton(buttons[i], "checkbox");
+      }
+      toolbar.appendChild(btn);
+    }
+    toolbar.appendChild(this.filterBox);
+    toolbar.appendChild(this.filterClearButton);
+    return toolbar;
+  },
+
+  makeButton: function HUD_makeButton(aName, aType)
+  {
+    var self = this;
+    let prefKey = aName.toLowerCase();
+    let btn = this.makeXULNode("toolbarbutton");
+
+    if (aType == "checkbox") {
+      btn.setAttribute("type", aType);
+    }
+    btn.setAttribute("hudId", this.hudId);
+    btn.setAttribute("buttonType", prefKey);
+    btn.setAttribute("class", "toolbarbutton-text toolbarbutton-1 bookmark-item hud-filter-btn");
+    let key = "btn" + aName;
+    btn.setAttribute("label", this.getStr(key));
+    key = "tip" + aName;
+    btn.setAttribute("tooltip", this.getStr(key));
+
+    if (aType == "checkbox") {
+      btn.setAttribute("checked", this.filterPrefs[prefKey]);
+      function toggle(btn) {
+        self.consoleFilterCommands.toggle(btn);
+      };
+
+      btn.setAttribute("oncommand", "HUDConsoleUI.toggleFilter(this);");
+    }
+    else {
+      var command = "HUDConsoleUI.command(this)";
+      btn.setAttribute("oncommand", command);
+    }
+    return btn;
+  },
+
+  createHUD: function HUD_createHUD()
+  {
+    let self = this;
+    if (this.HUDBox) {
+      return this.HUDBox;
+    }
+    else  {
+      this.makeHUDNodes();
+
+      let nodes = this.notificationBox.insertBefore(this.HUDBox,
+        this.notificationBox.childNodes[0]);
+
+      return this.HUDBox;
+    }
+  },
+
+  get console() { this._console || this.createConsole(); },
+
+  getLogCount: function HUD_getLogCount()
+  {
+    return this.outputNode.childNodes.length;
+  },
+
+  getLogNodes: function HUD_getLogNodes()
+  {
+    return this.outputNode.childNodes;
+  },
+
+  /**
+   * This console will accept a message, get the tab's meta-data and send
+   * properly-formatted message object to the service identifying
+   * where it came from, etc...
+   *
+   * @returns console
+   */
+  createConsole: function HUD_createConsole()
+  {
+    return new HUDConsole(this);
+  },
+
+  ERRORS: {
+    HUD_BOX_DOES_NOT_EXIST: "Heads Up Display does not exist",
+    TAB_ID_REQUIRED: "Tab DOM ID is required",
+    PARENTNODE_NOT_FOUND: "parentNode element not found"
+  }
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+// HUDConsole factory function
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The console object that is attached to each contentWindow
+ *
+ * @param object aHeadsUpDisplay
+ * @returns object
+ */
+function HUDConsole(aHeadsUpDisplay)
+{
+  this.hud = aHeadsUpDisplay;
+  this.hudId = this.hud.hudId;
+  this.outputNode = this.hud.outputNode;
+  this.chromeDocument = this.hud.chromeDocument;
+  this.makeHTMLNode = this.hud.makeHTMLNode;
+  this.created = new Date();
+  this.hud._console = this;
+  HUDService.updateLoadGroup(this.hudId, this.hud.loadGroup);
+};
+
+HUDConsole.prototype = {
+  created: null,
+
+  log: function console_log(aMessage)
+  {
+    this.message = aMessage;
+    this.sendToHUDService("log");
+  },
+
+  info: function console_info(aMessage)
+  {
+    this.message = aMessage;
+    this.sendToHUDService("info");
+  },
+
+  warn: function console_warn(aMessage)
+  {
+    this.message = aMessage;
+    this.sendToHUDService("warn");
+  },
+
+  error: function console_error(aMessage)
+  {
+    this.message = aMessage;
+    this.sendToHUDService("error");
+  },
+
+  exception: function console_exception(aMessage)
+  {
+    this.message = aMessage;
+    this.sendToHUDService("exception");
+  },
+
+  timeStamp: function Console_timeStamp()
+  {
+    return ConsoleUtils.timeStamp(new Date());
+  },
+
+  sendToHUDService: function console_send(aLevel)
+  {
+    // check to see if logging is on for this level before logging!
+    var filterState = HUDService.getFilterState(this.hudId, aLevel);
+
+    if (!filterState) {
+      // Ignoring log message
+      return;
+    }
+
+    let ts = this.timeStamp();
+    let messageNode =
+      this.hud.makeHTMLNode("div");
+
+    let klass = "hud-msg-node hud-" + aLevel;
+
+    messageNode.setAttribute("class", klass);
+
+    let timestampedMessage =
+      this.chromeDocument.createTextNode(ts + ": " + this.message);
+
+    messageNode.appendChild(timestampedMessage);
+    // need a constructor here to properly set all attrs
+    let messageObject = {
+      logLevel: aLevel,
+      hudId: this.hud.hudId,
+      message: this.hud.message,
+      timeStamp: ts,
+      origin: "HUDConsole",
+    };
+
+    HUDService.logMessage(messageObject, this.hud.outputNode, messageNode);
+  }
+};
+
+
+
+/**
+ * Creates a DOM Node factory for either XUL nodes or HTML nodes - as
+ * well as textNodes
+ * @param   aFactoryType
+ *          "xul" or "html"
+ * @returns DOM Node Factory function
+ */
+function NodeFactory(aFactoryType, aNameSpace, aDocument)
+{
+  // aDocument is presumed to be a XULDocument
+  const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
+
+  if (aFactoryType == "text") {
+    function factory(aText) {
+      return aDocument.createTextNode(aText);
+    }
+    return factory;
+  }
+  else {
+    if (aNameSpace == "xul") {
+      function factory(aTag)
+      {
+        return aDocument.createElement(aTag);
+      }
+      return factory;
+    }
+    else {
+      function factory(aTag)
+      {
+        var tag = "html:" + aTag;
+        return aDocument.createElementNS(ELEMENT_NS_URI, tag);
+      }
+      return factory;
+    }
+  }
+}
+
+//////////////////////////////////////////////////////////////////////////
+// JSTerm
+//////////////////////////////////////////////////////////////////////////
+
+/**
+ * JSTerm
+ *
+ * JavaScript Terminal: creates input nodes for console code interpretation
+ * and 'JS Workspaces'
+ */
+
+/**
+ * Create a JSTerminal or attach a JSTerm input node to an existing output node
+ *
+ *
+ *
+ * @param object aContext
+ *        Usually nsIDOMWindow, but doesn't have to be
+ * @param nsIDOMNode aParentNode
+ * @param object aMixin
+ *        Gecko-app (or Jetpack) specific utility object
+ * @returns void
+ */
+function JSTerm(aContext, aParentNode, aMixin)
+{
+  // set the context, attach the UI by appending to aParentNode
+
+  this.application = appName();
+  this.context = aContext;
+  this.parentNode = aParentNode;
+  this.mixins = aMixin;
+
+  this.elementFactory =
+    NodeFactory("html", "html", aParentNode.ownerDocument);
+
+  this.xulElementFactory =
+    NodeFactory("xul", "xul", aParentNode.ownerDocument);
+
+  this.textFactory = NodeFactory("text", "xul", aParentNode.ownerDocument);
+
+  this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
+
+  this.historyIndex = 0;
+  this.historyPlaceHolder = 0;  // this.history.length;
+  this.log = LogFactory("*** JSTerm:");
+  this.init();
+}
+
+JSTerm.prototype = {
+  init: function JST_init()
+  {
+    this.createSandbox();
+    this.inputNode = this.mixins.inputNode;
+    this.scrollToNode = this.mixins.scrollToNode;
+    let eventHandler = this.keyDown();
+    this.inputNode.addEventListener('keypress', eventHandler, false);
+    this.outputNode = this.mixins.outputNode;
+    if (this.mixins.cssClassOverride) {
+      this.cssClassOverride = this.mixins.cssClassOverride;
+    }
+  },
+
+  get codeInputString()
+  {
+    // TODO: filter the input for windows line breaks, conver to unix
+    // see bug 572812
+    return this.inputNode.value;
+  },
+
+  generateUI: function JST_generateUI()
+  {
+    this.mixins.generateUI();
+  },
+
+  attachUI: function JST_attachUI()
+  {
+    this.mixins.attachUI();
+  },
+
+  createSandbox: function JST_setupSandbox()
+  {
+    // create a JS Sandbox out of this.context
+    this._window.wrappedJSObject.jsterm = {};
+    this.console = this._window.wrappedJSObject.console;
+    this.sandbox = new Cu.Sandbox(this._window);
+    this.sandbox.window = this._window;
+    this.sandbox.console = this.console;
+    this.sandbox.__proto__ = this._window.wrappedJSObject;
+  },
+
+  get _window()
+  {
+    return this.context.get().QueryInterface(Ci.nsIDOMWindowInternal);
+  },
+
+  execute: function JST_execute()
+  {
+    // attempt to execute the content of the inputNode
+    var str = this.inputNode.value;
+    if (!str) {
+      this.console.log("no value to execute");
+      return;
+    }
+    try {
+      var result =
+      Cu.evalInSandbox(str, this.sandbox, "default", "HUD Console", 1);
+      this.writeOutput(str);
+
+      if (result !== undefined) {
+        this.writeOutput(result);
+      }
+    }
+    catch (ex) {
+      if (ex) {
+        this.console.error(ex);
+      }
+    }
+
+    this.history.push(str);
+    this.historyIndex++;
+    this.historyPlaceHolder = this.history.length;
+    this.inputNode.value = "";
+  },
+
+  writeOutput: function JST_writeOutput(aOutputMessage)
+  {
+    var node = this.elementFactory("div");
+    if (this.cssClassOverride) {
+      node.setAttribute("class", this.cssClassOverride);
+    }
+    else {
+      node.setAttribute("class", "jsterm-output-line");
+    }
+    var textNode = this.textFactory(aOutputMessage);
+    node.appendChild(textNode);
+    this.outputNode.appendChild(node);
+    node.scrollIntoView(false);
+  },
+
+  keyDown: function JSTF_keyDown(aEvent)
+  {
+    var self = this;
+    function handleKeyDown(aEvent) {
+      // ctrl-a
+      var setTimeout = aEvent.target.ownerDocument.defaultView.setTimeout;
+      var target = aEvent.target;
+      var tmp;
+
+      if (aEvent.ctrlKey) {
+        switch (aEvent.charCode) {
+          case 97:
+            // control-a
+            tmp = self.codeInputString;
+              setTimeout(function() {
+                self.inputNode.value = tmp;
+                self.inputNode.setSelectionRange(0, 0);
+              },0);
+              break;
+          case 101:
+            // control-e
+            tmp = self.codeInputString;
+            self.inputNode.value = "";
+            setTimeout(function(){
+              var endPos = tmp.length + 1;
+              self.inputNode.value = tmp;
+            },0);
+            break;
+          default:
+            return;
+        }
+        return;
+      }
+      else if (aEvent.shiftKey && aEvent.keyCode == 13) {
+        // shift return
+        // TODO: expand the inputNode height by one line
+        return;
+      }
+      else {
+        switch(aEvent.keyCode) {
+          case 13:
+            // return
+            self.execute();
+            break;
+          case 38:
+            // up arrow: history previous
+            if (self.caretInFirstLine()){
+              self.historyPeruse(true);
+            }
+            break;
+          case 40:
+            // down arrow: history next
+            if (self.caretInLastLine()){
+              self.historyPeruse(false);
+            }
+            break;
+          case 9:
+            // tab key
+            // TODO: this.tabComplete();
+            // see bug 568649
+            var bool = aEvent.cancelable;
+            if (bool) {
+              aEvent.preventDefault();
+            }
+            else {
+              // noop
+            }
+            aEvent.target.focus();
+            break;
+          default:
+            break;
+        }
+        return;
+      }
+    }
+    return handleKeyDown;
+  },
+
+  historyPeruse: function JST_historyPeruse(aFlag) {
+    if (!this.history.length) {
+      return;
+    }
+    // Up Arrow key
+    if (aFlag) {
+      var idx = this.historyPlaceHolder--;
+      if (idx < - 1) {
+        return;
+      }
+      var inputVal = this.history[idx - 1];
+
+      if (inputVal){
+        this.inputNode.value = this.history[idx - 1];
+      }
+    }
+    else {
+      var idx = this.historyPlaceHolder++;
+      if (idx > (len + 1)) {
+        return;
+      }
+      var inputVal = this.history[idx + 1];
+
+      if (inputVal){
+        this.inputNode.value = this.history[idx + 1];
+      }
+    }
+  },
+
+  refocus: function JSTF_refocus()
+  {
+    this.inputNode.blur();
+    this.inputNode.focus();
+  },
+
+  caretInFirstLine: function JSTF_caretInFirstLine()
+  {
+    var firstLineBreak = this.codeInputString.indexOf("\n");
+    return ((firstLineBreak == -1) ||
+            (this.codeInputString.selectionStart <= firstLineBreak));
+  },
+
+  caretInLastLine: function JSTF_caretInLastLine()
+  {
+    var lastLineBreak = this.codeInputString.lastIndexOf("\n");
+    return (this.inputNode.selectionEnd > lastLineBreak);
+  },
+
+  history: [],
+
+  tabComplete: function JSTF_tabComplete(aInputValue) {
+    // parse input value:
+    // TODO: see bug 568649
+  }
+};
+
+/**
+ * JSTermFirefoxMixin
+ *
+ * JavaScript Terminal Firefox Mixin
+ *
+ */
+function
+JSTermFirefoxMixin(aContext,
+                   aParentNode,
+                   aExistingConsole,
+                   aCSSClassOverride)
+{
+  // aExisting Console is the existing outputNode to use in favor of
+  // creating a new outputNode - this is so we can just attach the inputNode to
+  // a normal HeadsUpDisplay console output, and re-use code.
+  this.cssClassOverride = aCSSClassOverride;
+  this.context = aContext;
+  this.parentNode = aParentNode;
+  this.existingConsoleNode = aExistingConsole;
+  this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
+
+  if (aParentNode.ownerDocument) {
+    this.elementFactory =
+      NodeFactory("html", "html", aParentNode.ownerDocument);
+
+    this.xulElementFactory =
+      NodeFactory("xul", "xul", aParentNode.ownerDocument);
+
+    this.textFactory = NodeFactory("text", "xul", aParentNode.ownerDocument);
+    this.generateUI();
+    this.attachUI();
+  }
+  else {
+    throw new Error("aParentNode should be a DOM node with an ownerDocument property ");
+  }
+}
+
+JSTermFirefoxMixin.prototype = {
+  /**
+   * Generates and attaches the UI for an entire JS Workspace or
+   * just the input node used under the console output
+   *
+   * @returns void
+   */
+  generateUI: function JSTF_generateUI()
+  {
+    let inputNode = this.xulElementFactory("textbox");
+    inputNode.setAttribute("class", "jsterm-input-node");
+
+    if (this.existingConsoleNode == undefined) {
+      // create elements
+      let term = this.elementFactory("div");
+      term.setAttribute("class", "jsterm-wrapper-node");
+      term.setAttribute("flex", "1");
+
+      let outputNode = this.elementFactory("div");
+      outputNode.setAttribute("class", "jsterm-output-node");
+
+      let scrollToNode = this.elementFactory("div");
+      scrollToNode.setAttribute("class", "jsterm-scroll-to-node");
+
+      // construction
+      outputNode.appendChild(scrollToNode);
+      term.appendChild(outputNode);
+      term.appendChild(inputNode);
+
+      this.scrollToNode = scrollToNode;
+      this.outputNode = outputNode;
+      this.inputNode = inputNode;
+      this.term = term;
+    }
+    else {
+      this.inputNode = inputNode;
+      this.term = inputNode;
+      this.outputNode = this.existingConsoleNode;
+    }
+  },
+
+  get inputValue()
+  {
+    return this.inputNode.value;
+  },
+
+  attachUI: function JSTF_attachUI()
+  {
+    this.parentNode.appendChild(this.term);
+  }
+};
+
+/**
+ * LogMessage represents a single message logged to the "outputNode" console
+ */
+function LogMessage(aMessage, aLevel, aOutputNode, aActivityObject)
+{
+  if (!aOutputNode || !aOutputNode.ownerDocument) {
+    throw new Error("aOutputNode is required and should be type nsIDOMNode");
+  }
+  if (!aMessage.origin) {
+    throw new Error("Cannot create and log a message without an origin");
+  }
+  this.message = aMessage;
+  if (aMessage.domId) {
+    // domId is optional - we only need it if the logmessage is
+    // being asynchronously updated
+    this.domId = aMessage.domId;
+  }
+  this.activityObject = aActivityObject;
+  this.outputNode = aOutputNode;
+  this.level = aLevel;
+  this.origin = aMessage.origin;
+
+  this.elementFactory =
+  NodeFactory("html", "html", aOutputNode.ownerDocument);
+
+  this.xulElementFactory =
+  NodeFactory("xul", "xul", aOutputNode.ownerDocument);
+
+  this.textFactory = NodeFactory("text", "xul", aOutputNode.ownerDocument);
+
+  this.createLogNode();
+}
+
+LogMessage.prototype = {
+
+  /**
+   * create a console log div node
+   *
+   * @returns nsIDOMNode
+   */
+  createLogNode: function LM_createLogNode()
+  {
+    this.messageNode = this.elementFactory("div");
+
+    var ts = this.timestamp();
+    var timestampedMessage = ts + ": "  + this.message.message;
+    var messageTxtNode = this.textFactory(timestampedMessage);
+
+    this.messageNode.appendChild(messageTxtNode);
+
+    var klass = "hud-msg-node hud-" + this.level;
+    this.messageNode.setAttribute("class", klass);
+
+    var self = this;
+
+    var messageObject = {
+      logLevel: self.level,
+      message: self.message,
+      timestamp: ts,
+      activity: self.activityObject,
+      origin: self.origin,
+      hudId: self.message.hudId,
+    };
+
+    this.messageObject = messageObject;
+  },
+
+  timestamp: function LM_timestamp()
+  {
+    // TODO: L10N see bug 568656
+    // TODO: DUPLICATED CODE to be consolidated with the utils timestamping
+    // see bug 568657
+    function logDateString(d)
+    {
+      function pad(n, mil)
+      {
+        if (mil) {
+          return n < 100 ? '0' + n : n;
+        }
+        return n < 10 ? '0' + n : n;
+      }
+      return pad(d.getHours())+':'
+        + pad(d.getMinutes())+':'
+        + pad(d.getSeconds()) + ":"
+        + pad(d.getMilliseconds(), true);
+      }
+    return logDateString(new Date());
+  }
+};
+
+
+/**
+ * Firefox-specific Application Hooks.
+ * Each Gecko-based application will need an object like this in
+ * order to use the Heads Up Display
+ */
+function FirefoxApplicationHooks()
+{ }
+
+FirefoxApplicationHooks.prototype = {
+
+  /**
+   * Firefox-specific method for getting an array of chrome Window objects
+   */
+  get chromeWindows()
+  {
+    var windows = [];
+    var enumerator = Services.ww.getWindowEnumerator(null);
+    while (enumerator.hasMoreElements()) {
+      windows.push(enumerator.getNext());
+    }
+    return windows;
+  },
+
+  /**
+   * Firefox-specific method for getting the DOM node (per tab) that message
+   * nodes are appended to.
+   * @param aId
+   *        The DOM node's id.
+   */
+  getOutputNodeById: function FAH_getOutputNodeById(aId)
+  {
+    if (!aId) {
+      throw new Error("FAH_getOutputNodeById: id is null!!");
+    }
+    var enumerator = Services.ww.getWindowEnumerator(null);
+    while (enumerator.hasMoreElements()) {
+      let window = enumerator.getNext();
+      let node = window.document.getElementById(aId);
+      if (node) {
+        return node;
+      }
+    }
+    throw new Error("Cannot get outputNode by id");
+  },
+
+  /**
+   * gets the current contentWindow (Firefox-specific)
+   *
+   * @returns nsIDOMWindow
+   */
+  getCurrentContext: function FAH_getCurrentContext()
+  {
+    return Services.wm.getMostRecentWindow("navigator:browser");
+  }
+};
+
+/**
+ * ConsoleDOMListeners
+ *   Attach DOM Mutation listeners to a document
+ * @param nsIDOMWindow aWindow
+ * @param string aHUDId
+ * @param boolean aRemoveBool
+ * @returns void
+ */
+function ConsoleDOMListeners(aWindow, aHUDId, aRemoveBool)
+{
+  this.hudId = aHUDId;
+  this.window = XPCNativeWrapper.unwrap(aWindow);
+  this.console = this.window.console;
+  this.document = this.window.document;
+  this.trackedEvents = ['DOMSubtreeModified',
+                        'DOMNodeInserted',
+                        'DOMNodeRemoved',
+                        'DOMNodeRemovedFromDocument',
+                        'DOMNodeInsertedIntoDocument',
+                        'DOMAttrModified',
+                        'DOMCharacterDataModified',
+                        'DOMElementNameChanged',
+                        'DOMAttributeNameChanged',
+                       ];
+  if (aRemoveBool) {
+    var removeFunc = this.removeAllListeners(aHUDId);
+    removeFunc();
+  }
+  this.init();
+}
+
+ConsoleDOMListeners.prototype = {
+  init: function CDL_init()
+  {
+    for (var event in this.trackedEvents) {
+      let evt = this.trackedEvents[event];
+      let callback = this.eventListenerFactory(evt);
+
+      this.document.addEventListener(evt, callback, false);
+      this.storeMutationFunc(this.hudId, callback, evt);
+    }
+  },
+
+  /**
+   * function factory that generates an event handler for DOM Mutations
+   *
+   * @param string aEventName
+   * @returns function
+   */
+  eventListenerFactory: function CDL_eventListenerFactory(aEventName)
+  {
+    var self = this;
+    function callback(aEvent)
+    {
+      var nodeTag = aEvent.target.tagName;
+      var nodeClass = aEvent.target.getAttribute("class");
+      if (!nodeClass) {
+        nodeClass = "null";
+      }
+
+      var nodeId = aEvent.target.getAttribute("id");
+
+      if (!nodeId) {
+        nodeId = "null";
+      }
+
+      var message = "DOM Mutation Event: '"
+                    + aEventName + "'"
+                    + " on node. "
+                    + " id: " + nodeId
+                    + " class: " + nodeClass
+                    + " tag: " + nodeTag;
+
+      self.console.info(message);
+    }
+    return callback;
+  },
+
+  /**
+   * generates a function that removes all DOM Mutation listeners
+   * per HeadsUpDisplay
+   *  TODO: needs some tweaks, see bug 568658
+   *
+   * @param string aHUDId
+   * @returns function
+   */
+  removeAllListeners: function CDL_removeAllListeners(aHUDId)
+  {
+    var self = this;
+    function removeListeners()
+    {
+      for (var idx in HUDService.mutationEventFunctions[aHUDId]) {
+        let evtObj = HUDService.mutationEventFunctions[aHUDId][idx];
+        self.document.removeEventListener(evtObj.name, evtObj.func, false);
+      }
+    }
+    return removeListeners;
+  },
+
+  /**
+   * store a DOM Mutation function for later retrieval,
+   * removal and destruction
+   *
+   * @param string aHUDId
+   * @param function aFunc
+   * @param string aEventName
+   * @returns void
+   */
+  storeMutationFunc:
+  function CDL_storeMutationFunc(aHUDId, aFunc, aEventName)
+  {
+    var evtObj = {func: aFunc, name: aEventName};
+    if (!HUDService.mutationEventFunctions[aHUDId]) {
+      HUDService.mutationEventFunctions[aHUDId] = [];
+    }
+    HUDService.mutationEventFunctions[aHUDId].push(evtObj);
+  },
+
+  /**
+   * Removes the stored DOMMutation functions from the storage object
+   *
+   * @param string aHUDId
+   * @returns void
+   */
+  removeStoredMutationFuncs:
+  function CDL_removeStoredMutationFuncs(aHUDId)
+  {
+    delete HUDService.mutationEventFunctions[aHUDId];
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Utility functions used by multiple callers
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+ * ConsoleUtils: a collection of globally used functions
+ *
+ */
+
+ConsoleUtils = {
+
+  /**
+   * Generates a millisecond resolution timestamp for console messages
+   *
+   * @returns string
+   */
+  timeStamp: function ConsoleUtils_timeStamp()
+  {
+    function logDateString(d){
+      function pad(n, mil){
+        if (mil) {
+          return n < 100 ? '0'+n : n;
+        }
+        return n < 10 ? '0'+n : n;
+      }
+      return pad(d.getHours())+':'
+        + pad(d.getMinutes())+':'
+        + pad(d.getSeconds()) + ":"
+        + pad(d.getMilliseconds(), true);
+    }
+    return logDateString(new Date());
+
+  },
+
+  /**
+   * Hides a log message by changing its class
+   *
+   * @param nsIDOMNode aMessageNode
+   * @returns nsIDOMNode
+   */
+  hideLogMessage: function ConsoleUtils_hideLogMessage(aMessageNode) {
+    var klass = aMessageNode.getAttribute("class");
+    klass += " hud-hidden";
+    aMessageNode.setAttribute("class", klass);
+    return aMessageNode;
+  }
+};
+
+/**
+ * Creates a DOM Node factory for either XUL nodes or HTML nodes - as
+ * well as textNodes
+ * @param   aFactoryType
+ *          "xul", "html" or "text"
+ * @returns DOM Node Factory function
+ */
+function NodeFactory(aFactoryType, aNameSpace, aDocument)
+{
+  // aDocument is presumed to be a XULDocument
+  const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
+
+  if (aFactoryType == "text") {
+    function factory(aText) {
+      return aDocument.createTextNode(aText);
+    }
+    return factory;
+  }
+  else {
+    if (aNameSpace == "xul") {
+      function factory(aTag) {
+        return aDocument.createElement(aTag);
+      }
+      return factory;
+    }
+    else {
+      function factory(aTag) {
+        var tag = "html:" + aTag;
+        return aDocument.createElementNS(ELEMENT_NS_URI, tag);
+      }
+      return factory;
+    }
+  }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// HeadsUpDisplayUICommands
+//////////////////////////////////////////////////////////////////////////
+
+HeadsUpDisplayUICommands = {
+  toggleHUD: function UIC_toggleHUD() {
+    var window = HUDService.currentContext();
+    var gBrowser = window.gBrowser;
+    var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
+    var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id");
+    var hudId = "hud_" + tabId;
+    var hud = gBrowser.selectedTab.ownerDocument.getElementById(hudId);
+    if (hud) {
+      HUDService.deactivateHUDForContext(gBrowser.selectedTab);
+    }
+    else {
+      HUDService.activateHUDForContext(gBrowser.selectedTab);
+    }
+  },
+
+  toggleFilter: function UIC_toggleFilter(aButton) {
+    var filter = aButton.getAttribute("buttonType");
+    var hudId = aButton.getAttribute("hudId");
+    var state = HUDService.getFilterState(hudId, filter);
+    if (state) {
+      HUDService.setFilterState(hudId, filter, false);
+      aButton.setAttribute("checked", false);
+    }
+    else {
+      HUDService.setFilterState(hudId, filter, true);
+      aButton.setAttribute("checked", true);
+    }
+  },
+
+  command: function UIC_command(aButton) {
+    var filter = aButton.getAttribute("buttonType");
+    var hudId = aButton.getAttribute("hudId");
+    if (filter == "clear") {
+      HUDService.clearDisplay(hudId);
+    }
+  },
+
+  toggleMutationListeners: function UIC_toggleMutationListeners(aButton)
+  {
+    var hudId = aButton.getAttribute("hudId");
+    // if the button is for mutations, tell HUD to toggle it
+    HUDService.toggleMutationListeners(hudId);
+  },
+};
+
+//////////////////////////////////////////////////////////////////////////
+// ConsoleStorage
+//////////////////////////////////////////////////////////////////////////
+
+var prefs = Services.prefs;
+
+const GLOBAL_STORAGE_INDEX_ID = "GLOBAL_CONSOLE";
+const PREFS_BRANCH_PREF = "devtools.hud.display.filter";
+const PREFS_PREFIX = "devtools.hud.display.filter.";
+const PREFS = { mutation: PREFS_PREFIX + "mutation",
+                network: PREFS_PREFIX + "network",
+                cssparser: PREFS_PREFIX + "cssparser",
+                exception: PREFS_PREFIX + "exception",
+                error: PREFS_PREFIX + "error",
+                info: PREFS_PREFIX + "info",
+                warn: PREFS_PREFIX + "warn",
+                log: PREFS_PREFIX + "log",
+                global: PREFS_PREFIX + "global",
+              };
+
+function ConsoleStorage()
+{
+  this.sequencer = null;
+  this.consoleDisplays = {};
+  // each display will have an index that tracks each ConsoleEntry
+  this.displayIndexes = {};
+  this.globalStorageIndex = [];
+  this.globalDisplay = {};
+  this.createDisplay(GLOBAL_STORAGE_INDEX_ID);
+  // TODO: need to create a method that truncates the message
+  // see bug 570543
+
+  // store an index of display prefs
+  this.displayPrefs = {};
+
+  // check prefs for existence, create & load if absent, load them if present
+  let filterPrefs;
+  let defaultDisplayPrefs;
+
+  try {
+    filterPrefs = prefs.getBoolPref(PREFS_BRANCH_PREF);
+  }
+  catch (ex) {
+    filterPrefs = false;
+  }
+
+  // TODO: for FINAL release,
+  // use the sitePreferencesService to save specific site prefs
+  // see bug 570545
+
+  if (filterPrefs) {
+    defaultDisplayPrefs = {
+      mutation: (prefs.getBoolPref(PREFS.mutation) ? true: false),
+      network: (prefs.getBoolPref(PREFS.network) ? true: false),
+      cssparser: (prefs.getBoolPref(PREFS.cssparser) ? true: false),
+      exception: (prefs.getBoolPref(PREFS.exception) ? true: false),
+      error: (prefs.getBoolPref(PREFS.error) ? true: false),
+      info: (prefs.getBoolPref(PREFS.info) ? true: false),
+      warn: (prefs.getBoolPref(PREFS.warn) ? true: false),
+      log: (prefs.getBoolPref(PREFS.log) ? true: false),
+      global: (prefs.getBoolPref(PREFS.global) ? true: false),
+    };
+  }
+  else {
+    prefs.setBoolPref(PREFS_BRANCH_PREF, false);
+    // default prefs for each HeadsUpDisplay
+    prefs.setBoolPref(PREFS.mutation, false);
+    prefs.setBoolPref(PREFS.network, true);
+    prefs.setBoolPref(PREFS.cssparser, true);
+    prefs.setBoolPref(PREFS.exception, true);
+    prefs.setBoolPref(PREFS.error, true);
+    prefs.setBoolPref(PREFS.info, true);
+    prefs.setBoolPref(PREFS.warn, true);
+    prefs.setBoolPref(PREFS.log, true);
+    prefs.setBoolPref(PREFS.global, false);
+
+    defaultDisplayPrefs = {
+      mutation: prefs.getBoolPref(PREFS.mutation),
+      network: prefs.getBoolPref(PREFS.network),
+      cssparser: prefs.getBoolPref(PREFS.cssparser),
+      exception: prefs.getBoolPref(PREFS.exception),
+      error: prefs.getBoolPref(PREFS.error),
+      info: prefs.getBoolPref(PREFS.info),
+      warn: prefs.getBoolPref(PREFS.warn),
+      log: prefs.getBoolPref(PREFS.log),
+      global: prefs.getBoolPref(PREFS.global),
+    };
+  }
+  this.defaultDisplayPrefs = defaultDisplayPrefs;
+}
+
+ConsoleStorage.prototype = {
+
+  updateDefaultDisplayPrefs:
+  function CS_updateDefaultDisplayPrefs(aPrefsObject) {
+    prefs.setBoolPref(PREFS.mutation, (aPrefsObject.mutation ? true : false));
+    prefs.setBoolPref(PREFS.network, (aPrefsObject.network ? true : false));
+    prefs.setBoolPref(PREFS.cssparser, (aPrefsObject.cssparser ? true : false));
+    prefs.setBoolPref(PREFS.exception, (aPrefsObject.exception ? true : false));
+    prefs.setBoolPref(PREFS.error, (aPrefsObject.error ? true : false));
+    prefs.setBoolPref(PREFS.info, (aPrefsObject.info ? true : false));
+    prefs.setBoolPref(PREFS.warn, (aPrefsObject.warn ? true : false));
+    prefs.setBoolPref(PREFS.log, (aPrefsObject.log ? true : false));
+    prefs.setBoolPref(PREFS.global, (aPrefsObject.global ? true : false));
+  },
+
+  sequenceId: function CS_sequencerId()
+  {
+    if (!this.sequencer) {
+      this.sequencer = this.createSequencer();
+    }
+    return this.sequencer.next();
+  },
+
+  createSequencer: function CS_createSequencer()
+  {
+    function sequencer(aInt) {
+      while(1) {
+        aInt++;
+        yield aInt;
+      }
+    }
+    return sequencer(-1);
+  },
+
+  globalStore: function CS_globalStore(aIndex)
+  {
+    return this.displayStore(GLOBAL_CONSOLE_DOM_NODE_ID);
+  },
+
+  displayStore: function CS_displayStore(aId)
+  {
+    var self = this;
+    var idx = -1;
+    var id = aId;
+    var aLength = self.displayIndexes[id].length;
+
+    function displayStoreGenerator(aInt, aLength)
+    {
+      // create a generator object to iterate through any of the display stores
+      // from any index-starting-point
+      while(1) {
+        // throw if we exceed the length of displayIndexes?
+        aInt++;
+        var indexIt = self.displayIndexes[id];
+        var index = indexIt[aInt];
+        if (aLength < aInt) {
+          // try to see if we have more entries:
+          var newLength = self.displayIndexes[id].length;
+          if (newLength > aLength) {
+            aLength = newLength;
+          }
+          else {
+            throw new StopIteration();
+          }
+        }
+        var entry = self.consoleDisplays[id][index];
+        yield entry;
+      }
+    }
+
+    return displayStoreGenerator(-1, aLength);
+  },
+
+  recordEntries: function CS_recordEntries(aHUDId, aConfigArray)
+  {
+    var len = aConfigArray.length;
+    for (var i = 0; i < len; i++){
+      this.recordEntry(aHUDId, aConfigArray[i]);
+    }
+  },
+
+
+  recordEntry: function CS_recordEntry(aHUDId, aConfig)
+  {
+    var id = this.sequenceId();
+
+    this.globalStorageIndex[id] = { hudId: aHUDId };
+
+    var displayStorage = this.consoleDisplays[aHUDId];
+
+    var displayIndex = this.displayIndexes[aHUDId];
+
+    if (displayStorage && displayIndex) {
+      var entry = new ConsoleEntry(aConfig, id);
+      displayIndex.push(entry.id);
+      displayStorage[entry.id] = entry;
+      return entry;
+    }
+    else {
+      throw new Error("Cannot get displayStorage or index object for id " + aHUDId);
+    }
+  },
+
+  getEntry: function CS_getEntry(aId)
+  {
+    var display = this.globalStorageIndex[aId];
+    var storName = display.hudId;
+    return this.consoleDisplays[storName][aId];
+  },
+
+  updateEntry: function CS_updateEntry(aUUID)
+  {
+    // update an individual entry
+    // TODO: see bug 568634
+  },
+
+  createDisplay: function CS_createdisplay(aId)
+  {
+    if (!this.consoleDisplays[aId]) {
+      this.consoleDisplays[aId] = {};
+      this.displayIndexes[aId] = [];
+    }
+  },
+
+  removeDisplay: function CS_removeDisplay(aId)
+  {
+    try {
+      delete this.consoleDisplays[aId];
+      delete this.displayIndexes[aId];
+    }
+    catch (ex) {
+      Cu.reportError("Could not remove console display for id " + aId);
+    }
+  }
+};
+
+/**
+ * A Console log entry
+ *
+ * @param JSObject aConfig, object literal with ConsolEntry properties
+ * @param integer aId
+ * @returns void
+ */
+
+function ConsoleEntry(aConfig, id)
+{
+  if (!aConfig.logLevel && aConfig.message) {
+    throw new Error("Missing Arguments when creating a console entry");
+  }
+
+  this.config = aConfig;
+  this.id = id;
+  for (var prop in aConfig) {
+    if (!(typeof aConfig[prop] == "function")){
+      this[prop] = aConfig[prop];
+    }
+  }
+
+  if (aConfig.logLevel == "network") {
+    this.transactions = { };
+    if (aConfig.activity) {
+      this.transactions[aConfig.activity.stage] = aConfig.activity;
+    }
+  }
+
+}
+
+ConsoleEntry.prototype = {
+
+  updateTransaction: function CE_updateTransaction(aActivity) {
+    this.transactions[aActivity.stage] = aActivity;
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////
+// HUDWindowObserver
+//////////////////////////////////////////////////////////////////////////
+
+HUDWindowObserver = {
+  QueryInterface: XPCOMUtils.generateQI(
+    [Ci.nsIObserver,]
+  ),
+
+  init: function HWO_init()
+  {
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.obs.addObserver(this, "content-document-global-created", false);
+  },
+
+  observe: function HWO_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == "content-document-global-created") {
+      HUDService.windowInitializer(aSubject);
+    }
+    else if (aTopic == "xpcom-shutdown") {
+      this.uninit();
+    }
+  },
+
+  uninit: function HWO_uninit()
+  {
+    Services.obs.removeObserver(this, "content-document-global-created");
+    HUDService.shutdown();
+  },
+
+  /**
+   * once an initial console is created set this to true so we don't
+   * over initialize
+   */
+  initialConsoleCreated: false,
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HUDConsoleObserver
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * HUDConsoleObserver: Observes nsIConsoleService for global consoleMessages,
+ * if a message originates inside a contentWindow we are tracking,
+ * then route that message to the HUDService for logging.
+ */
+
+HUDConsoleObserver = {
+  QueryInterface: XPCOMUtils.generateQI(
+    [Ci.nsIObserver]
+  ),
+
+  init: function HCO_init()
+  {
+    Services.console.registerListener(this);
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+  },
+
+  observe: function HCO_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == "xpcom-shutdown") {
+      Services.console.unregisterListener(this);
+    }
+
+    if (aSubject instanceof Ci.nsIConsoleMessage) {
+      var err = aSubject.QueryInterface(Ci.nsIScriptError);
+      switch (err.category) {
+        case "XPConnect JavaScript":
+        case "component javascript":
+        case "chrome javascript":
+          // we ignore these CHROME-originating errors as we only
+          // care about content
+          return;
+        case "HUDConsole":
+        case "CSS Parser":
+        case "content javascript":
+          HUDService.reportConsoleServiceContentScriptError(err);
+          return;
+        default:
+          HUDService.reportConsoleServiceMessage(aSubject);
+          return;
+      }
+    }
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////
+// appName
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * Get the app's name so we can properly dispatch app-specific
+ * methods per API call
+ * @returns Gecko application name
+ */
+function appName()
+{
+  let APP_ID = Services.appinfo.QueryInterface(Ci.nsIXULRuntime).ID;
+
+  let APP_ID_TABLE = {
+    "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "FIREFOX" ,
+    "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "THUNDERBIRD",
+    "{a23983c0-fd0e-11dc-95ff-0800200c9a66}": "FENNEC" ,
+    "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SEAMONKEY",
+  };
+
+  let name = APP_ID_TABLE[APP_ID];
+
+  if (name){
+    return name;
+  }
+  throw new Error("appName: UNSUPPORTED APPLICATION UUID");
+}
+
+///////////////////////////////////////////////////////////////////////////
+// HUDService (exported symbol)
+///////////////////////////////////////////////////////////////////////////
+
+try {
+  // start the HUDService
+  // This is in a try block because we want to kill everything if
+  // *any* of this fails
+  var HUDService = new HUD_SERVICE();
+  HUDWindowObserver.init();
+  HUDConsoleObserver.init();
+}
+catch (ex) {
+  Cu.reportError("HUDService failed initialization.\n" + ex);
+  // TODO: kill anything that may have started up
+  // see bug 568665
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/Makefile.in
@@ -0,0 +1,55 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is  HUDService code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# 
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   David Dahl <ddahl@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = hudservice
+
+EXTRA_JS_MODULES = HUDService.jsm \
+		$(NULL)
+
+ifdef ENABLE_TESTS
+	DIRS += tests
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/Makefile.in
@@ -0,0 +1,50 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is HUDService code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     David Dahl <ddahl@mozilla.com>  (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE		= test_hudservice
+
+DIRS = browser
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/Makefile.in
@@ -0,0 +1,64 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is HUD test code.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#  David Dahl <ddahl@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH			= ../../../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH			= @srcdir@
+relativesrcdir  = toolkit/components/console/hudservice/tests/browser
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_TEST_FILES = \
+	browser_HUDServiceTestsAll.js \
+	$(NULL)
+
+_BROWSER_TEST_PAGES = \
+	test-console.html \
+	test-network.html \
+	test-mutation.html \
+	testscript.js \
+	test-filter.html \
+	test-observe-http-ajax.html \
+	test-data.json \
+	$(NULL)
+
+libs:: $(_BROWSER_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
+
+libs:: $(_BROWSER_TEST_PAGES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_HUDServiceTestsAll.js
@@ -0,0 +1,423 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DevTools test code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Dahl <ddahl@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "HUDService", function () {
+  Cu.import("resource://gre/modules/HUDService.jsm");
+  try {
+    return HUDService;
+  }
+  catch (ex) {
+    dump(ex + "\n");
+  }
+});
+
+let log = function _log(msg) {
+  dump("*** HUD Browser Test Log: " + msg + "\n");
+};
+
+const TEST_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-console.html";
+
+const TEST_HTTP_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-observe-http-ajax.html";
+
+const TEST_NETWORK_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-network.html";
+
+const TEST_FILTER_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-filter.html";
+
+const TEST_MUTATION_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-mutation.html";
+
+function noCacheUriSpec(aUriSpec) {
+  return aUriSpec + "?_=" + Date.now();
+}
+
+content.location.href = TEST_URI;
+
+function testRegistries() {
+  var displaysIdx = HUDService.displaysIndex();
+  ok(displaysIdx.length == 1, "one display id found");
+
+  var display = displaysIdx[0];
+  var registry = HUDService.displayRegistry;
+  var uri = registry[display];
+  ok(registry[display], "we have a URI: " + registry[display]);
+
+  var uriRegistry = HUDService.uriRegistry;
+  ok(uriRegistry[uri].length == 1, "uri registry is working");
+}
+
+function testGetDisplayByURISpec() {
+  var outputNode = HUDService.getDisplayByURISpec(TEST_URI);
+  hudId = outputNode.getAttribute("id");
+  ok(hudId == HUDService.displaysIndex()[0], "outputNode fetched by URIspec");
+}
+
+function introspectLogNodes() {
+  var console =
+    tab.linkedBrowser.contentWindow.wrappedJSObject.console;
+  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");
+
+  var len = HUDService.displaysIndex().length;
+  let id = HUDService.displaysIndex()[len - 1];
+  log("hudId:: " + id);
+  let hudBox = tab.ownerDocument.getElementById(id);
+  log("hudBox: " + hudBox);
+
+  let outputNode =
+    hudBox.querySelectorAll(".hud-output-node")[0];
+  log("childElementCount " + outputNode.childElementCount);
+  ok(outputNode.childElementCount > 0, "more than 1 child node");
+
+  let domLogEntries =
+    outputNode.childNodes;
+
+  let count = outputNode.childNodes.length;
+  ok(count > 0, "LogCount: " + count);
+
+  let klasses = ["hud-msg-node hud-log",
+                 "hud-msg-node hud-warn",
+                 "hud-msg-node hud-info",
+                 "hud-msg-node hud-error",
+                 "hud-msg-node hud-exception",
+                 "hud-msg-node hud-network"];
+
+  function verifyClass(klass) {
+    let len = klasses.length;
+    for (var i = 0; i < len; i++) {
+      if (klass == klasses[i]) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  for (var i = 0; i < count; i++) {
+    let klass = domLogEntries[i].getAttribute("class");
+    ok(verifyClass(klass),
+       "Log Node class verified: " + klass);
+  }
+}
+
+function getAllHUDS() {
+  var allHuds = HUDService.displays();
+  ok(typeof allHuds == "object", "allHuds is an object");
+  var idx = HUDService.displaysIndex();
+
+  hudId = idx[0];
+
+  ok(typeof idx == "object", "displays is an object");
+  ok(typeof idx.push == "function", "displaysIndex is an array");
+
+  var len = idx.length;
+  ok(idx.length > 0, "idx.length > 0: " + len);
+}
+
+function testGetDisplayByLoadGroup() {
+  var outputNode = HUDService.getDisplayByURISpec(TEST_URI);
+  var hudId = outputNode.getAttribute("id");
+  log("test hudId: " + hudId);
+  var loadGroup = HUDService.getLoadGroup(hudId);
+  log("test loadGroup: " + loadGroup);
+  var display = HUDService.getDisplayByLoadGroup(loadGroup);
+  ok(display.getAttribute("id") == hudId, "got display by loadGroup");
+
+  content.location = TEST_HTTP_URI;
+
+  executeSoon(function () {
+                let id = HUDService.displaysIndex()[0];
+                log(id);
+
+                let domLogEntries =
+                  outputNode.childNodes;
+
+                let count = outputNode.childNodes.length;
+                ok(count > 0, "LogCount: " + count);
+
+                let klasses = ["hud-network"];
+
+                function verifyClass(klass) {
+                  let len = klasses.length;
+                  for (var i = 0; i < len; i++) {
+                    if (klass == klasses[i]) {
+                      return true;
+                    }
+                  }
+                  return false;
+                }
+
+                for (var i = 0; i < count; i++) {
+                  let klass = domLogEntries[i].getAttribute("class");
+                  if (klass != "hud-network") {
+                    continue;
+                  }
+                  ok(verifyClass(klass),
+                     "Log Node network class verified");
+                }
+
+              });
+}
+
+function testUnregister()  {
+  HUDService.deactivateHUDForContext(tab);
+  ok(HUDService.displays()[0] == undefined,
+     "No heads up displays are registered");
+  HUDService.shutdown();
+}
+
+function getHUDById() {
+  let hud = HUDService.getHeadsUpDisplay(hudId);
+  ok(hud.getAttribute("id") == hudId, "found HUD node by Id.");
+}
+
+function testGetContentWindowFromHUDId() {
+  let window = HUDService.getContentWindowFromHUDId(hudId);
+  ok(window.document, "we have a contentWindow");
+}
+
+function testConsoleLoggingAPI(aMethod)
+{
+  filterBox.value = "foo";
+  browser.contentWindow.wrappedJSObject.console[aMethod]("foo-bar-baz");
+  browser.contentWindow.wrappedJSObject.console[aMethod]("bar-baz");
+  var count = outputNode.querySelectorAll(".hud-hidden").length;
+  ok(count == 1, "1 hidden " + aMethod  + " node found");
+  HUDService.clearDisplay(hudId);
+
+  // now toggle the current method off - make sure no visible message
+  // nodes are logged
+  filterBox.value = "";
+  HUDService.setFilterState(hudId, aMethod, false);
+  browser.contentWindow.wrappedJSObject.console[aMethod]("foo-bar-baz");
+  count = outputNode.querySelectorAll(".hud-hidden").length;
+  ok(count == 0, aMethod + " logging tunred off, 0 messages logged");
+  HUDService.clearDisplay(hudId);
+}
+
+function testLogEntry(aOutputNode, aMatchString, aSuccessErrObj)
+{
+  executeSoon(function (){
+                var msgs = aOutputNode.childNodes;
+                for (var i = 0; i < msgs.length; i++) {
+                  log(msgs[i].innerHTML);
+                  var message = msgs[i].innerHTML.indexOf(aMatchString);
+                  if (message > -1) {
+                    ok(true, aSuccessErrObj.success);
+
+                    return;
+                  }
+                }
+                ok(false, aSuccessErrObj.err);
+              });
+}
+
+// test networking logging
+function testNet()
+{
+  HUDService.setFilterState(hudId, "network", true);
+  filterBox.value = "";
+  content.location = TEST_NETWORK_URI;
+  var successMsg =
+    "Found the loggged network message referencing a js file";
+  var errMsg = "Could not get logged network message for js file";
+  var display = HUDService.getHeadsUpDisplay(hudId);
+  // var outputNode = display.querySelectorAll(".hud-output-node")[0];
+  testLogEntry(outputNode,
+               "Network:", { success: successMsg, err: errMsg });
+  content.location.href = noCacheUriSpec(TEST_NETWORK_URI);
+}
+
+// test DOM Mutation logging
+function testDOMMutation()
+{
+  log("Testing DOMMutation!!!!");
+  HUDService.setFilterState(hudId, "mutation", true);
+  filterBox.value = "";
+  log("CONTENT: " + content);
+  content.location = TEST_MUTATION_URI;
+  executeSoon(function() {
+                content.wrappedJSObject.addEventListener("DOMContentLoaded",
+                function () {
+                  log("*********** Mutation onload **************");
+                  var successMsg = "Found Mutation Log Message";
+                  var errMsg = "Could NOT find Mutation Log Message";
+                  var display = HUDService.getHeadsUpDisplay(hudId);
+                  var outputNode = display.querySelectorAll(".hud-output-node")[0];
+                  testLogEntry(outputNode,
+                  "Mutation", { success: successMsg, err: errMsg });
+                  }, false);
+                content.location.href = TEST_NETWORK_URI;
+              });
+}
+
+function testCreateDisplay() {
+  ok(typeof cs.consoleDisplays == "object",
+     "consoledisplays exist");
+  ok(typeof cs.displayIndexes == "object",
+     "console indexes exist");
+  cs.createDisplay("foo");
+  ok(typeof cs.consoleDisplays["foo"] == "object",
+     "foo display exists");
+  ok(typeof cs.displayIndexes["foo"] == "object",
+     "foo index exists");
+}
+
+function testRecordEntry() {
+  var config = {
+    logLevel: "network",
+    message: "HumminaHummina!",
+    activity: {
+      stage: "barStage",
+      data: "bar bar bar bar"
+    }
+  };
+  var entry = cs.recordEntry("foo", config);
+  var res = entry.id;
+  ok(entry.id != null, "Entry.id is: " + res);
+  ok(cs.displayIndexes["foo"].length == 1,
+     "We added one entry.");
+  entry = cs.getEntry(res);
+  ok(entry.id > -1,
+     "We got an entry through the global interface");
+}
+
+function testRecordManyEntries() {
+  var configArr = [];
+
+  for (var i = 0; i < 1000; i++){
+    let config = {
+      logLevel: "network",
+      message: "HumminaHummina!",
+      activity: {
+        stage: "barStage",
+        data: "bar bar bar bar"
+      }
+    };
+    configArr.push(config);
+  }
+
+  var start = Date.now();
+  cs.recordEntries("foo", configArr);
+  var end = Date.now();
+  var elapsed = end - start;
+  ok(cs.displayIndexes["foo"].length == 1001,
+     "1001 entries in foo now");
+}
+
+function testIteration() {
+  var id = "foo";
+  var it = cs.displayStore(id);
+  var entry = it.next();
+  var entry2 = it.next();
+
+  let entries = [];
+  for (var i = 0; i < 100; i++) {
+    let _entry = it.next();
+    entries.push(_entry);
+  }
+
+  ok(entries.length == 100, "entries length == 100");
+
+  let entries2 = [];
+
+  for (var i = 0; i < 100; i++){
+    let _entry = it.next();
+    entries2.push(_entry);
+  }
+
+  ok(entries[0].id != entries2[0].id,
+     "two distinct pages of log entries");
+}
+
+let tab, browser, hudId, hud, filterBox, outputNode, cs;
+
+let win = gBrowser.selectedBrowser;
+tab = gBrowser.selectedTab;
+browser = gBrowser.getBrowserForTab(tab);
+
+function test() {
+  waitForExplicitFinish();
+  browser.addEventListener("DOMContentLoaded", function onLoad(event) {
+    browser.removeEventListener("DOMContentLoaded", onLoad, false);
+
+    HUDService.activateHUDForContext(tab);
+    hudId = HUDService.displaysIndex()[0];
+    hud = HUDService.getHeadsUpDisplay(hudId);
+    cs = HUDService.storage;
+    // enter some filter text into the filter textbox
+    filterBox = hud.querySelectorAll(".hud-filter-box")[0];
+    outputNode = HUDService.getHeadsUpDisplay(hudId);
+
+
+    executeSoon(function () {
+      testRegistries();
+      testGetDisplayByURISpec();
+      introspectLogNodes();
+      getAllHUDS();
+      getHUDById();
+      testGetDisplayByLoadGroup();
+      testGetContentWindowFromHUDId();
+
+      content.location.href = TEST_FILTER_URI;
+
+      testConsoleLoggingAPI("log");
+      testConsoleLoggingAPI("info");
+      testConsoleLoggingAPI("warn");
+      testConsoleLoggingAPI("error");
+      testConsoleLoggingAPI("exception");
+
+      testNet();
+      // testDOMMutation();
+
+      // ConsoleStorageTests
+      testCreateDisplay();
+      testRecordEntry();
+      testRecordManyEntries();
+      testIteration();
+
+      testUnregister();
+      finish();
+    });
+  }, false);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-console.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+    <title>Console test</title>
+    <script type="text/javascript">
+      dump("i am dumping this");
+      function test() {
+        var str = "Dolske Digs Bacon, Now and Forevermore."
+        for (var i=0; i < 5; i++) {
+          console.log(str);
+        } 
+      }
+      console.info("INLINE SCRIPT:");
+      test();
+      console.warn("I'm warning you, he will eat up all yr bacon.");
+      console.error("Error Message");
+    </script>
+  </head>
+  <body>
+    <h1>Heads Up Display Demo</h1>
+    <button onclick="test();">Log stuff about Dolske</button>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-data.json
@@ -0,0 +1,1 @@
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-filter.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+    <title>Console test</title>
+    <script type="text/javascript">
+    </script>
+  </head>
+  <body>
+    <h1>Heads Up Display Filter Test Page</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-mutation.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <title>Console mutation test</title>
+    <script>
+      window.onload = function (){
+        var node = document.createElement("div");
+        document.body.appendChild(node);
+      };
+  </script>
+  </head>
+  <body>
+    <h1>Heads Up Display DOM Mutation Test Page</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-network.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+    <title>Console network test</title>
+    <script src="testscript.js"></script>
+  </head>
+  <body>
+    <h1>Heads Up Display Network  Test Page</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-observe-http-ajax.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+    <title>Console HTTP test page</title>
+    <script type="text/javascript">
+      function test() {
+        var xmlhttp = new XMLHttpRequest();
+        xmlhttp.open('get', 'test-data.json', false);
+        xmlhttp.send(null);
+      }
+    </script>
+  </head>
+  <body onload="test();">
+    <h1>Heads Up Display HTTP & AJAX Test Page</h1>
+    <h2>This page fires an ajax request so we can see the http logging of the console</h2>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/testscript.js
@@ -0,0 +1,1 @@
+console.log("running network console logging tests");
--- a/toolkit/components/console/jar.mn
+++ b/toolkit/components/console/jar.mn
@@ -1,5 +1,6 @@
 toolkit.jar:
 *+ content/global/console.js                            (content/console.js)
 *+ content/global/console.xul                           (content/console.xul)
 +  content/global/console.css                           (content/console.css)
 +  content/global/consoleBindings.xml                   (content/consoleBindings.xml)
+*+  content/global/headsUpDisplay.css                    (content/headsUpDisplay.css)
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.dtd
@@ -0,0 +1,64 @@
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is DevTools Code
+   -
+   - The Initial Developer of the Original Code is
+   - Mozilla Foundation
+   - Portions created by the Initial Developer are Copyright (C) 2010
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   David Dahl <ddahl@mozilla.com>  
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!ENTITY hud.title "Heads Up Display">
+
+<!ENTITY errFile.label   "Source File:">
+<!ENTITY errLine.label   "Line:">
+<!ENTITY errColumn.label "Column:">
+
+<!ENTITY all.label          "All">
+<!ENTITY all.accesskey      "A">
+<!ENTITY errors.label       "Errors">
+<!ENTITY errors.accesskey   "E">
+<!ENTITY warnings.label     "Warnings">
+<!ENTITY warnings.accesskey "W">
+<!ENTITY messages.label     "Messages">
+<!ENTITY messages.accesskey "M">
+<!ENTITY clear.label        "Clear">
+<!ENTITY clear.accesskey    "C">
+<!ENTITY codeEval.label     "Code:">
+<!ENTITY codeEval.accesskey "o">
+<!ENTITY evaluate.label     "Evaluate">
+<!ENTITY evaluate.accesskey "v">
+
+<!ENTITY copyCmd.label       "Copy">  
+<!ENTITY copyCmd.accesskey   "C"> 
+<!ENTITY copyCmd.commandkey  "C"> 
+<!ENTITY closeCmd.commandkey "w">  
+<!ENTITY focus1.commandkey   "l">  
+<!ENTITY focus2.commandkey   "d">  
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
@@ -0,0 +1,37 @@
+typeError=Error: 
+typeWarning=Warning: 
+typeNetwork=Network: 
+typeException=Exception:  
+typeCssParser=CSS Parser: 
+typeStrict=Strict Warning: 
+msgCategory=Category: 
+errFile=Source File: %S
+errLine=Line: %S
+errLineCol=Line: %S, Column: %S
+errCode=Source Code:
+hudTitle=Heads Up Display
+jsWorkspaceTitle=JS Workspace
+btnHide=Hide
+btnPrefs=Preferences
+btnMutation=DOM Mutation
+tipMutation=Toggle DOM Mutation event logging
+btnNetwork=Network
+tipNetwork=Toggle Network event logging 
+btnCSSParser=CSS Warnings
+tipCSSParser=Toggle CSS Warning logging
+btnException=Exceptions
+tipException=Toggle Exception logging
+btnError=Error
+tipError=Toggle console.error logging
+btnInfo=Info
+tipInfo=Toggle console.info logging
+btnWarn=Warnings
+tipWarn=Toggle console.warn logging
+btnLog=Log
+tipLog=Toggle console.log logging
+btnGlobal=Global Messages
+tipGlobal=Toggle Global Message logging
+localConsole=Local Console
+btnClear=Clear Console
+tipClear=Clear the console output
+stringFilterClear=Clear Filter
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -30,16 +30,18 @@
   locale/@AB_CD@/global/filefield.properties            (%chrome/global/filefield.properties)
   locale/@AB_CD@/global/filepicker.dtd                  (%chrome/global/filepicker.dtd)
   locale/@AB_CD@/global/filepicker.properties           (%chrome/global/filepicker.properties)
   locale/@AB_CD@/global/findbar.dtd                     (%chrome/global/findbar.dtd)
   locale/@AB_CD@/global/findbar.properties              (%chrome/global/findbar.properties)
 + locale/@AB_CD@/global/finddialog.dtd                  (%chrome/global/finddialog.dtd)
 + locale/@AB_CD@/global/finddialog.properties           (%chrome/global/finddialog.properties)
   locale/@AB_CD@/global/globalKeys.dtd                  (%chrome/global/globalKeys.dtd)
+* locale/@AB_CD@/global/headsUpDisplay.dtd              (%chrome/global/headsUpDisplay.dtd)
++ locale/@AB_CD@/global/headsUpDisplay.properties       (%chrome/global/headsUpDisplay.properties)
 + locale/@AB_CD@/global/intl.css                        (%chrome/global/intl.css)
 + locale/@AB_CD@/global/intl.properties                 (%chrome/global/intl.properties)
 + locale/@AB_CD@/global/keys.properties                 (%chrome/global/keys.properties)
 + locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)
   locale/@AB_CD@/global/notification.dtd                (%chrome/global/notification.dtd)
   locale/@AB_CD@/global/preferences.dtd                 (%chrome/global/preferences.dtd)
 + locale/@AB_CD@/global/printdialog.dtd                 (%chrome/global/printdialog.dtd)