Merge m-c to s-c.
authorRichard Newman <rnewman@mozilla.com>
Fri, 27 Apr 2012 10:36:01 -0700
changeset 95734 31faeb7325134829e50aaa4f725f04c59b9f725f
parent 95733 c019b4bbe9ffc30ba0a1ee72a475fc0bce48d47d (current diff)
parent 94873 0f8ea3826bf79f3b6c09b4da14abdd76200004ff (diff)
child 95735 8e59a61ca83136b880fe273dfa7ac69f2cb36cd2
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone15.0a1
Merge m-c to s-c.
browser/devtools/webconsole/test/browser_gcli_break.html
browser/devtools/webconsole/test/browser_gcli_break.js
browser/devtools/webconsole/test/browser_gcli_commands.js
browser/devtools/webconsole/test/browser_gcli_helpers.js
browser/devtools/webconsole/test/browser_gcli_inspect.html
browser/devtools/webconsole/test/browser_gcli_inspect.js
browser/devtools/webconsole/test/browser_gcli_integrate.js
browser/devtools/webconsole/test/browser_gcli_require.js
browser/devtools/webconsole/test/browser_gcli_web.js
build/autoconf/glib.m4
content/html/content/test/test_bug514437.html
content/html/content/test/test_bug588683-1.html
content/html/content/test/test_bug588683-2.html
content/html/content/test/test_bug588683-3.html
content/html/content/test/test_bug588683-4.html
--- a/accessible/src/generic/ARIAGridAccessible.cpp
+++ b/accessible/src/generic/ARIAGridAccessible.cpp
@@ -648,48 +648,36 @@ ARIAGridAccessible::SelectColumn(PRInt32
       rv = SetARIASelected(cell, true);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-ARIAGridAccessible::UnselectRow(PRInt32 aRow)
+void
+ARIAGridAccessible::UnselectRow(PRUint32 aRowIdx)
 {
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
+  nsAccessible* row = GetRowAt(aRowIdx);
 
-  nsAccessible *row = GetRowAt(aRow);
-  NS_ENSURE_ARG(row);
-
-  return SetARIASelected(row, false);
+  if (row)
+    SetARIASelected(row, false);
 }
 
-NS_IMETHODIMP
-ARIAGridAccessible::UnselectColumn(PRInt32 aColumn)
+void
+ARIAGridAccessible::UnselectCol(PRUint32 aColIdx)
 {
-  NS_ENSURE_ARG(IsValidColumn(aColumn));
-
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
   AccIterator rowIter(this, filters::GetRow);
 
-  nsAccessible *row = nsnull;
+  nsAccessible* row = nsnull;
   while ((row = rowIter.Next())) {
-    nsAccessible *cell = GetCellInRowAt(row, aColumn);
-    if (cell) {
-      nsresult rv = SetARIASelected(cell, false);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+    nsAccessible* cell = GetCellInRowAt(row, aColIdx);
+    if (cell)
+      SetARIASelected(cell, false);
   }
-
-  return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Protected
 
 bool
 ARIAGridAccessible::IsValidRow(PRInt32 aRow)
 {
--- a/accessible/src/generic/ARIAGridAccessible.h
+++ b/accessible/src/generic/ARIAGridAccessible.h
@@ -69,16 +69,18 @@ public:
   virtual mozilla::a11y::TableAccessible* AsTable() { return this; }
 
   // nsAccessNode
   virtual void Shutdown();
 
   // TableAccessible
   virtual PRUint32 ColCount();
   virtual PRUint32 RowCount();
+  virtual void UnselectCol(PRUint32 aColIdx);
+  virtual void UnselectRow(PRUint32 aRowIdx);
 
 protected:
   /**
    * Return true if the given row index is valid.
    */
   bool IsValidRow(PRInt32 aRow);
 
   /**
--- a/accessible/src/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -1134,38 +1134,30 @@ nsHTMLTableAccessible::SelectColumn(PRIn
                                      nsISelectionPrivate::TABLESELECTION_COLUMN,
                                      true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return AddRowOrColumnToSelection(aColumn,
                                    nsISelectionPrivate::TABLESELECTION_COLUMN);
 }
 
-NS_IMETHODIMP
-nsHTMLTableAccessible::UnselectRow(PRInt32 aRow)
+void
+nsHTMLTableAccessible::UnselectRow(PRUint32 aRowIdx)
 {
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
-  return
-    RemoveRowsOrColumnsFromSelection(aRow,
-                                     nsISelectionPrivate::TABLESELECTION_ROW,
-                                     false);
+  RemoveRowsOrColumnsFromSelection(aRowIdx,
+                                   nsISelectionPrivate::TABLESELECTION_ROW,
+                                   false);
 }
 
-NS_IMETHODIMP
-nsHTMLTableAccessible::UnselectColumn(PRInt32 aColumn)
+void
+nsHTMLTableAccessible::UnselectCol(PRUint32 aColIdx)
 {
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
-  return
-    RemoveRowsOrColumnsFromSelection(aColumn,
-                                     nsISelectionPrivate::TABLESELECTION_COLUMN,
-                                     false);
+  RemoveRowsOrColumnsFromSelection(aColIdx,
+                                   nsISelectionPrivate::TABLESELECTION_COLUMN,
+                                   false);
 }
 
 nsresult
 nsHTMLTableAccessible::AddRowOrColumnToSelection(PRInt32 aIndex,
                                                  PRUint32 aTarget)
 {
   bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
 
--- a/accessible/src/html/nsHTMLTableAccessible.h
+++ b/accessible/src/html/nsHTMLTableAccessible.h
@@ -124,19 +124,21 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessible Table
   NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE
 
   // TableAccessible
   virtual nsAccessible* Caption();
+  virtual void Summary(nsString& aSummary);
   virtual PRUint32 ColCount();
   virtual PRUint32 RowCount();
-  virtual void Summary(nsString& aSummary);
+  virtual void UnselectCol(PRUint32 aColIdx);
+  virtual void UnselectRow(PRUint32 aRowIdx);
   virtual bool IsProbablyLayoutTable();
 
   // nsAccessNode
   virtual void Shutdown();
 
   // nsAccessible
   virtual mozilla::a11y::TableAccessible* AsTable() { return this; }
   virtual void Description(nsString& aDescription);
--- a/accessible/src/xpcom/xpcAccessibleTable.cpp
+++ b/accessible/src/xpcom/xpcAccessibleTable.cpp
@@ -66,8 +66,36 @@ xpcAccessibleTable::IsProbablyForLayout(
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = false;
   if (!mTable)
     return NS_ERROR_FAILURE;
 
   *aResult = mTable->IsProbablyLayoutTable();
   return NS_OK;
 }
+
+nsresult
+xpcAccessibleTable::UnselectColumn(PRInt32 aColIdx)
+{
+  if (!mTable)
+    return NS_ERROR_FAILURE;
+
+  if (aColIdx < 0 || aColIdx >= mTable->ColCount())
+    return NS_ERROR_INVALID_ARG;
+
+  mTable->UnselectCol(aColIdx);
+  return NS_OK;
+}
+
+nsresult
+xpcAccessibleTable::UnselectRow(PRInt32 aRowIdx)
+{
+  if (!mTable)
+    return NS_ERROR_FAILURE;
+
+  if (aRowIdx < 0 || aRowIdx >= mTable->RowCount())
+    return NS_ERROR_INVALID_ARG;
+
+  mTable->UnselectRow(aRowIdx);
+  return NS_OK;
+}
+
+
--- a/accessible/src/xpcom/xpcAccessibleTable.h
+++ b/accessible/src/xpcom/xpcAccessibleTable.h
@@ -18,19 +18,21 @@ class TableAccessible;
 }
 
 class xpcAccessibleTable
 {
 public:
   xpcAccessibleTable(mozilla::a11y::TableAccessible* aTable) : mTable(aTable) { }
 
   nsresult GetCaption(nsIAccessible** aCaption);
+  nsresult GetSummary(nsAString& aSummary);
   nsresult GetColumnCount(PRInt32* aColumnCount);
   nsresult GetRowCount(PRInt32* aRowCount);
-  nsresult GetSummary(nsAString& aSummary);
+  nsresult UnselectColumn(PRInt32 aColIdx);
+  nsresult UnselectRow(PRInt32 aRowIdx);
   nsresult IsProbablyForLayout(bool* aIsForLayout);
 
 protected:
   mozilla::a11y::TableAccessible* mTable;
 };
 
 #define NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE \
   NS_IMETHOD GetCaption(nsIAccessible** aCaption) \
@@ -57,14 +59,16 @@ protected:
   NS_SCRIPTABLE NS_IMETHOD GetSelectedColumnCount(PRUint32 *aSelectedColumnCount); \
   NS_SCRIPTABLE NS_IMETHOD GetSelectedRowCount(PRUint32 *aSelectedRowCount); \
   NS_SCRIPTABLE NS_IMETHOD GetSelectedCells(nsIArray * *aSelectedCells); \
   NS_SCRIPTABLE NS_IMETHOD GetSelectedCellIndices(PRUint32 *cellsArraySize NS_OUTPARAM, PRInt32 **cellsArray NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetSelectedColumnIndices(PRUint32 *rowsArraySize NS_OUTPARAM, PRInt32 **rowsArray NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetSelectedRowIndices(PRUint32 *rowsArraySize NS_OUTPARAM, PRInt32 **rowsArray NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD SelectRow(PRInt32 rowIndex); \
   NS_SCRIPTABLE NS_IMETHOD SelectColumn(PRInt32 columnIndex); \
-  NS_SCRIPTABLE NS_IMETHOD UnselectColumn(PRInt32 columnIndex); \
-  NS_IMETHOD UnselectRow(PRInt32 aRowIdx); \
+  NS_SCRIPTABLE NS_IMETHOD UnselectColumn(PRInt32 aColIdx) \
+    { return xpcAccessibleTable::UnselectColumn(aColIdx); } \
+  NS_IMETHOD UnselectRow(PRInt32 aRowIdx) \
+    { return xpcAccessibleTable::UnselectRow(aRowIdx); } \
   NS_IMETHOD IsProbablyForLayout(bool* aResult) \
   { return xpcAccessibleTable::IsProbablyForLayout(aResult); } \
 
 #endif // MOZILLA_A11Y_XPCOM_XPACCESSIBLETABLE_H_
--- a/accessible/src/xul/nsXULListboxAccessible.cpp
+++ b/accessible/src/xul/nsXULListboxAccessible.cpp
@@ -770,39 +770,29 @@ nsXULListboxAccessible::SelectRow(PRInt3
 
 NS_IMETHODIMP
 nsXULListboxAccessible::SelectColumn(PRInt32 aColumn)
 {
   // xul:listbox and xul:richlistbox support row selection only.
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsXULListboxAccessible::UnselectRow(PRInt32 aRow)
+void
+nsXULListboxAccessible::UnselectRow(PRUint32 aRowIdx)
 {
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-  
   nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
     do_QueryInterface(mContent);
   NS_ASSERTION(control,
                "Doesn't implement nsIDOMXULMultiSelectControlElement.");
 
-  nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
-  control->GetItemAtIndex(aRow, getter_AddRefs(item));
-  NS_ENSURE_TRUE(item, NS_ERROR_INVALID_ARG);
-
-  return control->RemoveItemFromSelection(item);
-}
-
-NS_IMETHODIMP
-nsXULListboxAccessible::UnselectColumn(PRInt32 aColumn)
-{
-  // xul:listbox and xul:richlistbox support row selection only.
-  return NS_OK;
+  if (control) {
+    nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+    control->GetItemAtIndex(aRowIdx, getter_AddRefs(item));
+    control->RemoveItemFromSelection(item);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULListboxAccessible: Widgets
 
 bool
 nsXULListboxAccessible::IsWidget() const
 {
--- a/accessible/src/xul/nsXULListboxAccessible.h
+++ b/accessible/src/xul/nsXULListboxAccessible.h
@@ -102,16 +102,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTable
   NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE
 
   // TableAccessible
   virtual PRUint32 ColCount();
   virtual PRUint32 RowCount();
+  virtual void UnselectRow(PRUint32 aRowIdx);
 
   // nsAccessNode
   virtual void Shutdown();
 
   // nsAccessible
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::TableAccessible* AsTable() { return this; }
   virtual mozilla::a11y::role NativeRole();
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp
@@ -540,33 +540,27 @@ nsXULTreeGridAccessible::SelectRow(PRInt
 }
 
 NS_IMETHODIMP
 nsXULTreeGridAccessible::SelectColumn(PRInt32 aColumnIndex)
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsXULTreeGridAccessible::UnselectRow(PRInt32 aRowIndex)
+void
+nsXULTreeGridAccessible::UnselectRow(PRUint32 aRowIdx)
 {
   if (!mTreeView)
-    return NS_ERROR_INVALID_ARG;
+    return;
 
   nsCOMPtr<nsITreeSelection> selection;
   mTreeView->GetSelection(getter_AddRefs(selection));
-  NS_ENSURE_STATE(selection);
-
-  return selection->ClearRange(aRowIndex, aRowIndex);
-}
-
-NS_IMETHODIMP
-nsXULTreeGridAccessible::UnselectColumn(PRInt32 aColumnIndex)
-{
-  return NS_OK;
+  
+  if (selection)
+    selection->ClearRange(aRowIdx, aRowIdx);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTreeGridAccessible: nsAccessNode implementation
 
 void
 nsXULTreeGridAccessible::Shutdown()
 {
--- a/accessible/src/xul/nsXULTreeGridAccessible.h
+++ b/accessible/src/xul/nsXULTreeGridAccessible.h
@@ -58,16 +58,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTable
   NS_DECL_OR_FORWARD_NSIACCESSIBLETABLE_WITH_XPCACCESSIBLETABLE
 
   // TableAccessible
   virtual PRUint32 ColCount();
   virtual PRUint32 RowCount();
+  virtual void UnselectRow(PRUint32 aRowIdx);
 
   // nsAccessNode
   virtual void Shutdown();
 
   // nsAccessible
   virtual mozilla::a11y::TableAccessible* AsTable() { return this; }
   virtual mozilla::a11y::role NativeRole();
 
--- a/accessible/tests/mochitest/actions/test_keys.html
+++ b/accessible/tests/mochitest/actions/test_keys.html
@@ -21,18 +21,18 @@
 
       is(acc.keyboardShortcut, aKey,
          "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID));
     }
 
     function doTest()
     {
       testKeyboardShortcut("input1", "");
-      testKeyboardShortcut("input2", MAC ? "⌃%b" : "Alt+Shift+b");
-      testKeyboardShortcut("link", MAC ? "⌃%l" : "Alt+Shift+l");
+      testKeyboardShortcut("input2", MAC ? "⌃⌥b" : "Alt+Shift+b");
+      testKeyboardShortcut("link", MAC ? "⌃⌥l" : "Alt+Shift+l");
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
--- a/accessible/tests/mochitest/bounds/test_zoom.html
+++ b/accessible/tests/mochitest/bounds/test_zoom.html
@@ -17,16 +17,24 @@
   <script type="application/javascript"
           src="../layout.js"></script>
   <script type="application/javascript"
           src="../browser.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
+      // Bug 746176: Failure of this whole test file on OS X.
+      if (MAC) {
+        todo(false, "Fix bug 746176 on Mac");
+        closeBrowserWindow();
+        SimpleTest.finish();
+        return;
+      }
+
       var tabDocument = currentTabDocument();
       var p1 = tabDocument.getElementById("p1");
       var p2 = tabDocument.getElementById("p2");
 
       var imgMap = tabDocument.getElementById("imgmap");
       ensureImageMapTree(imgMap);
       var imgMapAcc = getAccessible(imgMap);
       var area = imgMapAcc.firstChild;
--- a/accessible/tests/mochitest/events/test_focus_autocomplete.xul
+++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul
@@ -379,18 +379,18 @@
     // Tests
 
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gInitQueue = null;
     function initTests()
     {
-      if (SEAMONKEY) {
-        todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237)");
+      if (SEAMONKEY || MAC) {
+        todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)");
         shutdownAutoComplete();
         SimpleTest.finish();
         return;
       }
 
       gInitQueue = new eventQueue();
       gInitQueue.push(new loadFormAutoComplete("iframe"));
       gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello"));
--- a/accessible/tests/mochitest/events/test_focus_browserui.xul
+++ b/accessible/tests/mochitest/events/test_focus_browserui.xul
@@ -91,23 +91,27 @@
                                browserWindow()));
 
       // open new url, focus moves to new document
       gQueue.push(new loadURI(gButtonDocURI));
 
       // back one page in history, moves moves on input of tab document
       gQueue.push(new goBack());
 
+if (!MAC) {
       // open new tab, focus moves to urlbar
       gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: browserWindow() },
                                new focusChecker(urlbarInput)));
 
       // close open tab, focus goes on input of tab document
       gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: browserWindow() },
                                new focusChecker(inputInDocument)));
+} else {
+      todo(false, "Reenable on Mac after fixing bug 746178!");
+}
 
       gQueue.onFinish = function()
       {
         closeBrowserWindow();
       }
       gQueue.invoke();
     }
 
--- a/accessible/tests/mochitest/events/test_focus_contextmenu.xul
+++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xul
@@ -22,16 +22,23 @@
 
   <script type="application/javascript">
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTests()
     {
+      // bug 746183 - Whole file times out on OS X
+      if (MAC) {
+        todo(false, "Reenable on mac after fixing bug 746183!");
+        SimpleTest.finish();
+        return;
+      }
+
       // Test focus events.
       gQueue = new eventQueue();
 
       gQueue.push(new synthFocus("button"));
       gQueue.push(new synthContextMenu("button",
                                        new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
       gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button")));
 
--- a/accessible/tests/mochitest/events/test_focus_general.html
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -104,18 +104,18 @@
         gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
       }
       gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
       gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
       gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
       if (SEAMONKEY) {
         todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)");
       } else {
-      if (LINUX)
-        todo(false, "shift tab from editable document fails on linux!");
+      if (LINUX || MAC)
+        todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!");
       else
         gQueue.push(new synthShiftTab("link", new focusChecker("link")));
       } // ! SEAMONKEY
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
--- a/accessible/tests/mochitest/events/test_focus_general.xul
+++ b/accessible/tests/mochitest/events/test_focus_general.xul
@@ -68,32 +68,36 @@
       gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
       // select first menu item ("item 1"), focus on menu item
       gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1")));
       // choose select menu item, focus gets back to menubutton
       gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton")));
       // show popup again for the next test
       gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
 
+if (MAC) {
       // click menubutton of the 'menubutton' button while popup of button open.
       gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" }));
       // close popup, focus stays on menubutton, fire focus event
       gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb")));
       // click menubutton, open popup, focus stays on menubutton
       gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" }));
       // select first menu item ("item 1"), focus on menu item
       gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1")));
       // choose select menu item, focus gets back to menubutton
       gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb")));
       // open popup, focus stays on menubutton
       gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker()));
       // select second menu item ("item 2"), focus on menu item
       gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2")));
       // click on menu item of menubutton menu, focus menubutton
       gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb")));
+} else {
+      todo(false, "mbb tests time out on OS X, fix bug 746970 and reenable!");
+}
 
       // focus colorpicker button
       gQueue.push(new synthFocus("colorpicker"));
       // click on button, open popup, focus goes to current color button
       var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 };
       var checker = new focusChecker(getColorBtn, btnObj);
       gQueue.push(new synthClick("colorpicker", checker));
       // select sibling color button, focus on it
--- a/accessible/tests/mochitest/events/test_focus_listcontrols.xul
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul
@@ -45,35 +45,41 @@
       gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1")));
       gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true }));
       gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox")));
 
       gQueue.push(new synthFocus("menulist"));
       gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine")));
       gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
       gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
+if (!MAC) {
       gQueue.push(new synthDownKey("menulist", new nofocusChecker("ml_marmalade")));
       gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker("ml_marmalade")));
       gQueue.push(new synthEnterKey("ml_marmalade", new focusChecker("menulist")));
+} else {
+      todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X");
+}
 
       var textentry = getAccessible("emenulist").firstChild;
       gQueue.push(new synthFocus("emenulist", new focusChecker(textentry)));
       gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine")));
       gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade")));
       gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry)));
       gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade")));
       gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry)));
 
       // no focus events for unfocused list controls when current item is
       // changed.
       gQueue.push(new synthFocus("emptylistbox"));
 
       gQueue.push(new changeCurrentItem("listbox", "lb_item1"));
       gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
+if (!MAC) {
       gQueue.push(new changeCurrentItem("menulist", "ml_tangerine"));
+}
       gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
--- a/accessible/tests/mochitest/events/test_focus_selects.html
+++ b/accessible/tests/mochitest/events/test_focus_selects.html
@@ -23,16 +23,23 @@
   <script type="application/javascript">
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
 
     function doTests()
     {
+      // Bug 746534 - File causes crash or hang on OS X
+      if (MAC) {
+        todo(false, "Bug 746534 - test file causes crash or hang on OS X");
+        SimpleTest.finish();
+        return;
+      }
+
       gQueue = new eventQueue();
 
       // first item is focused until there's selection
       gQueue.push(new synthFocus("list", new focusChecker("orange")));
 
       // item is selected and stays focused
       gQueue.push(new synthDownKey("list", new nofocusChecker()));
 
--- a/accessible/tests/mochitest/events/test_focus_tabbox.xul
+++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul
@@ -22,16 +22,22 @@
 
   <script type="application/javascript">
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTests()
     {
+      if (MAC) {
+        todo(false, "Tests disabled because of imminent failure.");
+        SimpleTest.finish();
+        return;
+      }
+
       // Test focus events.
       gQueue = new eventQueue();
 
       gQueue.push(new synthClick("tab1", new focusChecker("tab1")));
       gQueue.push(new synthTab("tab1", new focusChecker("checkbox1")));
       gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true },
                                new focusChecker("textbox")));
       gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
--- a/accessible/tests/mochitest/hittest/test_general.html
+++ b/accessible/tests/mochitest/hittest/test_general.html
@@ -14,17 +14,21 @@
 
   <script type="application/javascript">
     function doTest()
     {
       // Not specific case, child and deepchild testing.
       var list = getAccessible("list");
       var listitem = getAccessible("listitem");
       var image = getAccessible("image");
+if (!MAC) {
       testChildAtPoint(list, 1, 1, listitem, image.firstChild);
+} else {
+      todo(false, "Bug 746974 - children must match on all platforms, disable failing test on Mac");
+}
 
       // ::MustPrune case (in this case childAtPoint doesn't look inside a
       // textbox), point is inside of textbox.
       var txt = getAccessible("txt");
       testChildAtPoint(txt, 1, 1, txt, txt);
 
       // ::MustPrune case, point is outside of textbox accessible but is in
       // document.
--- a/accessible/tests/mochitest/hittest/test_zoom.html
+++ b/accessible/tests/mochitest/hittest/test_zoom.html
@@ -15,29 +15,33 @@
   <script type="application/javascript"
           src="../layout.js"></script>
   <script type="application/javascript"
           src="../browser.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
+if (!MAC) {
       var tabDocument = currentTabDocument();
       var p1 = tabDocument.body.firstElementChild;
       var p2 = tabDocument.body.lastElementChild;
 
       hitTest(tabDocument, p1, p1.firstChild);
       hitTest(tabDocument, p2, p2.firstChild);
 
       zoomDocument(tabDocument, 2.0);
 
       hitTest(tabDocument, p1, p1.firstChild);
       hitTest(tabDocument, p2, p2.firstChild);
 
       closeBrowserWindow();
+} else {
+      todo(false, "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!");
+}
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     openBrowserWindow(doTest,
                       "data:text/html,<html><body><p>para 1</p><p>para 2</p></body></html>",
                       { left: 100, top: 100 });
   </script>
--- a/accessible/tests/mochitest/states/test_inputs.html
+++ b/accessible/tests/mochitest/states/test_inputs.html
@@ -106,16 +106,17 @@
     // autocomplete states
     testStates("autocomplete-default", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-off", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-formoff", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-list", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-list2", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-tel", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     testStates("autocomplete-email", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+    testStates("autocomplete-search", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
     SimpleTest.finish();
   }
 
   SimpleTest.waitForExplicitFinish();
   addA11yLoadEvent(doTest);
   </script>
 </head>
 
@@ -155,16 +156,17 @@
      title="File input control should be propogate states to descendants">
     Mozilla Bug 699017
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=733382"
      title="Editable state bit should be present on readonly inputs">
     Mozilla Bug 733382
   </a>
+
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
 
   <form>
     <input id="input" type="input">
@@ -238,10 +240,17 @@
 
   Email Address:
   <input id="autocomplete-email" type="email" list="contacts" value="xyzzy">
   <datalist id="contacts">
     <option>xyzzy@plughs.com</option>
     <option>nobody@mozilla.org</option>
   </datalist>
 
+  </br>Search for:
+  <input id="autocomplete-search" type="search" list="searchhisty" value="Gamma">
+  <datalist id="searchhisty">
+    <option>Gamma Rays</option>
+    <option>Gamma Ray Bursts</option>
+  </datalist>
+
   </body>
 </html>
--- a/accessible/tests/mochitest/tree/test_cssoverflow.html
+++ b/accessible/tests/mochitest/tree/test_cssoverflow.html
@@ -82,16 +82,23 @@
     // Do tests
 
     var gQueue = null;
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     function doTests()
     {
+      // Shift+Tab not working, and a test timeout, bug 746977
+      if (MAC) {
+        todo(false, "Shift+tab isn't working on OS X, needs to be disabled until bug 746977 is fixed!");
+        SimpleTest.finish();
+        return;
+      }
+
       gQueue = new eventQueue();
 
       // CSS 'overflow: scroll' property setting and unsetting causes accessible
       // recreation (and fire show/hide events). For example, the focus and
       // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its
       // accessible recreation. The focus event should be fired on new
       // accessible.
       gQueue.push(new focusAnchor("a"));
--- a/accessible/tests/mochitest/tree/test_dochierarchy.html
+++ b/accessible/tests/mochitest/tree/test_dochierarchy.html
@@ -26,17 +26,17 @@
       getAccessible(window.parent.document, [nsIAccessibleDocument]) :
       getAccessible(document, [nsIAccessibleDocument]);
     var testDoc = getAccessible(document, [nsIAccessibleDocument]);
     var iframeDoc = getAccessible("iframe").firstChild.
       QueryInterface(nsIAccessibleDocument);
 
     is(root.parentDocument, null,
        "Wrong parent document of root accessible");
-    is(root.childDocumentCount, 1,
+    is(root.childDocumentCount, SEAMONKEY ? 1 : 3,
        "Wrong child document count of root accessible");
     is(root.getChildDocumentAt(0), tabDoc,
        "Wrong child document at index 0 of root accessible");
 
     is(tabDoc.parentDocument, root,
        "Wrong parent document of tab document");
     is(tabDoc.childDocumentCount, 1,
        "Wrong child document count of tab document");
--- a/accessible/tests/mochitest/tree/test_txtctrl.html
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -98,16 +98,29 @@
             role: ROLE_TEXT_LEAF,
             children: []
           }
         ]
       };
 
       testAccessibleTree("txc7", accTree);
 
+      // input@type="search", value
+      accTree = {
+        role: ROLE_ENTRY,
+        children: [
+          { // text child
+            role: ROLE_TEXT_LEAF,
+            children: []
+          }
+        ]
+      };
+
+      testAccessibleTree("txc8", accTree);
+
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
@@ -147,10 +160,17 @@
 
   Email Address:
   <input id="txc7" type="email" list="contacts" value="xyzzy">
   <datalist id="contacts">
     <option>xyzzy@plughs.com</option>
     <option>nobody@mozilla.org</option>
   </datalist>
 
+  </br>Search for:
+  <input id="txc8" type="search" list="searchhisty" value="Gamma">
+  <datalist id="searchhisty">
+    <option>Gamma Rays</option>
+    <option>Gamma Ray Bursts</option>
+  </datalist>
+
 </body>
 </html>
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -1,14 +1,13 @@
 dnl
 dnl Local autoconf macros used with mozilla
 dnl The contents of this file are under the Public Domain.
 dnl 
 
-builtin(include, build/autoconf/glib.m4)dnl
 builtin(include, build/autoconf/nspr.m4)dnl
 builtin(include, build/autoconf/nss.m4)dnl
 builtin(include, build/autoconf/pkg.m4)dnl
 builtin(include, build/autoconf/codeset.m4)dnl
 builtin(include, build/autoconf/altoptions.m4)dnl
 builtin(include, build/autoconf/mozprog.m4)dnl
 builtin(include, build/autoconf/mozheader.m4)dnl
 builtin(include, build/autoconf/mozcommonheader.m4)dnl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1040,32 +1040,40 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Disable the error console
 pref("devtools.errorconsole.enabled", false);
 
+// Enable the developer toolbar
+pref("devtools.toolbar.enabled", false);
+
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
 pref("devtools.inspector.htmlPanelOpen", false);
 pref("devtools.inspector.sidebarOpen", false);
 pref("devtools.inspector.activeSidebar", "ruleview");
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", false);
 pref("devtools.layoutview.open", false);
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", false);
+pref("devtools.debugger.remote-enabled", false);
+pref("devtools.debugger.remote-host", "localhost");
+pref("devtools.debugger.remote-port", 6000);
 
 // The default Debugger UI height
 pref("devtools.debugger.ui.height", 250);
+pref("devtools.debugger.ui.remote-win.width", 900);
+pref("devtools.debugger.ui.remote-win.height", 400);
 
 // Enable the style inspector
 pref("devtools.styleinspector.enabled", true);
 
 // Enable the Tilt inspector
 pref("devtools.tilt.enabled", true);
 pref("devtools.tilt.intro_transition", true);
 pref("devtools.tilt.outro_transition", true);
@@ -1078,18 +1086,24 @@ pref("devtools.scratchpad.enabled", true
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.transitions", true);
 
 // Enable tools for Chrome development.
 pref("devtools.chrome.enabled", false);
 
-// Disable the GCLI enhanced command line.
-pref("devtools.gcli.enable", false);
+// Display the introductory text
+pref("devtools.gcli.hideIntro", false);
+
+// How eager are we to show help: never=1, sometimes=2, always=3
+pref("devtools.gcli.eagerHelper", 2);
+
+// Do we allow the 'pref set' command
+pref("devtools.gcli.allowSet", false);
 
 // The last Web Console height. This is initially 0 which means that the Web
 // Console will use the default height next time it shows.
 // Change to -1 if you do not want the Web Console to remember its last height.
 pref("devtools.hud.height", 0);
 
 // Remember the Web Console position. Possible values:
 //   above - above the web page,
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -171,34 +171,45 @@
             <menuitem id="appmenu_printSetup"
                       label="&printSetupCmd.label;"
                       command="cmd_pageSetup"/>
           </menupopup>
       </splitmenu>
       <menuseparator class="appmenu-menuseparator"/>
       <menu id="appmenu_webDeveloper"
             label="&appMenuWebDeveloper.label;">
-        <menupopup id="appmenu_webDeveloper_popup"
-                   onpopupshowing="onWebDeveloperMenuShowing();">
+        <menupopup id="appmenu_webDeveloper_popup">
+          <menuitem id="appmenu_devToolbar"
+                    type="checkbox"
+                    autocheck="false"
+                    hidden="true"
+                    label="&devToolbarMenu.label;"
+                    command="Tools:DevToolbar"
+                    key="key_devToolbar"/>
           <menuitem id="appmenu_webConsole"
                     label="&webConsoleCmd.label;"
                     type="checkbox"
                     command="Tools:WebConsole"
                     key="key_webConsole"/>
           <menuitem id="appmenu_pageInspect"
                     hidden="true"
                     label="&inspectMenu.label;"
                     type="checkbox"
                     command="Tools:Inspect"
                     key="key_inspect"/>
           <menuitem id="appmenu_debugger"
                     hidden="true"
+                    type="checkbox"
                     label="&debuggerMenu.label;"
                     key="key_debugger"
                     command="Tools:Debugger"/>
+          <menuitem id="appmenu_remoteDebugger"
+                    hidden="true"
+                    label="&remoteDebuggerMenu.label;"
+                    command="Tools:RemoteDebugger"/>
           <menuitem id="appmenu_scratchpad"
                     hidden="true"
                     label="&scratchpad.label;"
                     key="key_scratchpad"
                     command="Tools:Scratchpad"/>
           <menuitem id="appmenu_styleeditor"
                     hidden="true"
                     label="&styleeditor.label;"
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -220,17 +220,17 @@ var FullZoom = {
    * @param aBrowser
    *        (optional) browser object displaying the document
    */
   onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
     if (!aURI || (aIsTabSwitch && !this.siteSpecific))
       return;
 
     // Avoid the cps roundtrip and apply the default/global pref.
-    if (isBlankPageURL(aURI.spec)) {
+    if (aURI.spec == "about:blank") {
       this._applyPrefToSetting(undefined, aBrowser);
       return;
     }
 
     let browser = aBrowser || gBrowser.selectedBrowser;
 
     // Media documents should always start at 1, and are not affected by prefs.
     if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -526,36 +526,48 @@
                         accesskey="&syncSyncNowItem.accesskey;"
                         observes="sync-syncnow-state"
                         oncommand="gSyncUI.doSync(event);"/>
 #endif
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
-                <menupopup id="menuWebDeveloperPopup"
-                           onpopupshowing="onWebDeveloperMenuShowing();">
+                <menupopup id="menuWebDeveloperPopup">
+                  <menuitem id="menu_devToolbar"
+                            type="checkbox"
+                            autocheck="false"
+                            hidden="true"
+                            label="&devToolbarMenu.label;"
+                            accesskey="&devToolbarMenu.accesskey;"
+                            key="key_devToolbar"
+                            command="Tools:DevToolbar"/>
                   <menuitem id="webConsole"
                             type="checkbox"
                             label="&webConsoleCmd.label;"
                             accesskey="&webConsoleCmd.accesskey;"
                             key="key_webConsole"
                             command="Tools:WebConsole"/>
                   <menuitem id="menu_pageinspect"
                             type="checkbox"
                             hidden="true"
                             label="&inspectMenu.label;"
                             accesskey="&inspectMenu.accesskey;"
                             key="key_inspect"
                             command="Tools:Inspect"/>
                   <menuitem id="menu_debugger"
                             hidden="true"
+                            type="checkbox"
                             label="&debuggerMenu.label;"
                             key="key_debugger"
                             command="Tools:Debugger"/>
+                  <menuitem id="menu_remoteDebugger"
+                            hidden="true"
+                            label="&remoteDebuggerMenu.label;"
+                            command="Tools:RemoteDebugger"/>
                   <menuitem id="menu_scratchpad"
                             hidden="true"
                             label="&scratchpad.label;"
                             accesskey="&scratchpad.accesskey;"
                             key="key_scratchpad"
                             command="Tools:Scratchpad"/>
                   <menuitem id="menu_styleeditor"
                             hidden="true"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -121,19 +121,21 @@
     <command id="cmd_fullZoomReduce"  oncommand="FullZoom.reduce()"/>
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
+    <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true"/>
     <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/>
     <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true"/>
+    <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true"/>
     <command id="Tools:StyleEditor" oncommand="StyleEditor.openChrome();" disabled="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
@@ -250,16 +252,23 @@
 #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_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
     <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/>
+    <key id="key_devToolbar" key="&devToolbar.commandkey;" command="Tools:DevToolbar"
+#ifdef XP_MACOSX
+         modifiers="accel,alt"
+#else
+         modifiers="accel,shift"
+#endif
+    />
     <key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();"
 #ifdef XP_MACOSX
         modifiers="accel,alt"
 #else
         modifiers="accel,shift"
 #endif
     />
     <key id="key_debugger" key="&debuggerMenu.commandkey;" command="Tools:Debugger"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -174,16 +174,22 @@ XPCOMUtils.defineLazyGetter(this, "Popup
     return new tmp.PopupNotifications(gBrowser,
                                       document.getElementById("notification-popup"),
                                       document.getElementById("notification-popup-box"));
   } catch (ex) {
     Cu.reportError(ex);
   }
 });
 
+XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp);
+  return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
+});
+
 XPCOMUtils.defineLazyGetter(this, "InspectorUI", function() {
   let tmp = {};
   Cu.import("resource:///modules/inspector.jsm", tmp);
   return new tmp.InspectorUI(window);
 });
 
 XPCOMUtils.defineLazyGetter(this, "DebuggerUI", function() {
   let tmp = {};
@@ -1691,34 +1697,56 @@ function delayedStartup(isLoadingBlank, 
   TabView.init();
 
   setUrlAndSearchBarWidthForConditionalForwardButton();
   window.addEventListener("resize", function resizeHandler(event) {
     if (event.target == window)
       setUrlAndSearchBarWidthForConditionalForwardButton();
   });
 
+  // Enable developer toolbar?
+  let devToolbarEnabled = gPrefService.getBoolPref("devtools.toolbar.enabled");
+  if (devToolbarEnabled) {
+    document.getElementById("menu_devToolbar").hidden = false;
+    document.getElementById("Tools:DevToolbar").removeAttribute("disabled");
+#ifdef MENUBAR_CAN_AUTOHIDE
+    document.getElementById("appmenu_devToolbar").hidden = false;
+#endif
+  }
+
   // Enable Inspector?
   let enabled = gPrefService.getBoolPref("devtools.inspector.enabled");
   if (enabled) {
     document.getElementById("menu_pageinspect").hidden = false;
     document.getElementById("Tools:Inspect").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
     document.getElementById("appmenu_pageInspect").hidden = false;
 #endif
+    document.getElementById("developer-toolbar-inspector").hidden = false;
   }
 
   // Enable Debugger?
   let enabled = gPrefService.getBoolPref("devtools.debugger.enabled");
   if (enabled) {
     document.getElementById("menu_debugger").hidden = false;
     document.getElementById("Tools:Debugger").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
     document.getElementById("appmenu_debugger").hidden = false;
 #endif
+    document.getElementById("developer-toolbar-debugger").hidden = false;
+  }
+
+  // Enable Remote Debugger?
+  let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+  if (enabled) {
+    document.getElementById("menu_remoteDebugger").hidden = false;
+    document.getElementById("Tools:RemoteDebugger").removeAttribute("disabled");
+#ifdef MENUBAR_CAN_AUTOHIDE
+    document.getElementById("appmenu_remoteDebugger").hidden = false;
+#endif
   }
 
   // Enable Error Console?
   // XXX Temporarily always-enabled, see bug 601201
   let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled");
   if (consoleEnabled) {
     document.getElementById("javascriptConsole").hidden = false;
     document.getElementById("key_errorConsole").removeAttribute("disabled");
@@ -9276,20 +9304,16 @@ var StyleEditor = {
     args.wrappedJSObject = args;
     let chromeWindow = Services.ww.openWindow(null, CHROME_URL, "_blank",
                                               CHROME_WINDOW_FLAGS, args);
     chromeWindow.focus();
     return chromeWindow;
   }
 };
 
-function onWebDeveloperMenuShowing() {
-  document.getElementById("Tools:WebConsole").setAttribute("checked", HUDConsoleUI.getOpenHUD() != null);
-}
-
 
 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
 #ifdef XP_WIN
   // Only show resizers on Windows 2000 and XP
   let sysInfo = Components.classes["@mozilla.org/system-info;1"]
                           .getService(Components.interfaces.nsIPropertyBag2);
   return parseFloat(sysInfo.getProperty("version")) < 6;
 #else
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1034,16 +1034,70 @@
         <!-- registered tools go here -->
       </hbox>
 #ifndef XP_MACOSX
       <toolbarbutton id="highlighter-closebutton"
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
     </toolbar>
+
+    <panel id="gcli-tooltip"
+           type="arrow"
+           noautofocus="true"
+           noautohide="true"
+           class="gcli-panel">
+      <iframe id="gcli-tooltip-frame"
+              src="chrome://browser/content/devtools/gcliblank.xhtml"
+              flex="1"/>
+    </panel>
+    <panel id="gcli-output"
+           type="arrow"
+           noautofocus="true"
+           noautohide="true"
+           class="gcli-panel">
+      <iframe id="gcli-output-frame"
+              src="chrome://browser/content/devtools/gcliblank.xhtml"
+              flex="1"/>
+    </panel>
+
+    <toolbar id="developer-toolbar"
+             class="devtools-toolbar"
+             hidden="true">
+#ifdef XP_MACOSX
+          <toolbarbutton id="developer-toolbar-closebutton"
+                         oncommand="DeveloperToolbar.hide();"
+                         tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+          <stack class="gclitoolbar-stack-node" flex="1">
+            <description class="gclitoolbar-prompt">&#187;</description>
+            <description class="gclitoolbar-complete-node"/>
+            <textbox class="gclitoolbar-input-node" rows="1"/>
+          </stack>
+          <toolbarbutton id="developer-toolbar-webconsole"
+                         label="&webConsoleButton.label;"
+                         class="devtools-toolbarbutton"
+                         command="Tools:WebConsole"/>
+          <toolbarbutton id="developer-toolbar-inspector"
+                         label="&inspectorButton.label;"
+                         class="devtools-toolbarbutton"
+                         hidden="true"
+                         command="Tools:Inspect"/>
+          <toolbarbutton id="developer-toolbar-debugger"
+                         label="&scriptsButton.label;"
+                         class="devtools-toolbarbutton"
+                         hidden="true"
+                         command="Tools:Debugger"/>
+#ifndef XP_MACOSX
+          <toolbarbutton id="developer-toolbar-closebutton"
+                         oncommand="DeveloperToolbar.hide();"
+                         tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+   </toolbar>
+
     <toolbar id="addon-bar"
              toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;"
              collapsed="true"
              class="toolbar-primary chromeclass-toolbar"
              context="toolbar-context-menu" toolboxid="navigator-toolbox"
              mode="icons" iconsize="small" defaulticonsize="small"
              lockiconsize="true"
              defaultset="addonbar-closebutton,spring,status-bar"
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -245,16 +245,17 @@ endif
                  test_bug435035.html \
                  test_bug462673.html \
                  page_style_sample.html \
                  feed_tab.html \
                  plugin_unknown.html \
                  plugin_test.html \
                  plugin_test2.html \
                  plugin_test3.html \
+                 plugin_alternate_content.html \
                  plugin_both.html \
                  plugin_both2.html \
                  plugin_clickToPlayAllow.html \
                  plugin_clickToPlayDeny.html \
                  alltabslistener.html \
                  zoom_test.html \
                  dummy_page.html \
                  browser_tabMatchesInAwesomebar.js \
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -402,10 +402,26 @@ function test13c() {
 }
 
 // Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
 function test14() {
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 14, Plugin should be activated");
 
+  var plugin = get_test_plugin();
+  plugin.disabled = false;
+  plugin.blocklisted = false;
+  Services.perms.removeAll();
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  prepareTest(test15, gTestRoot + "plugin_alternate_content.html");
+}
+
+// Tests that the overlay is shown instead of alternate content when
+// plugins are click to play
+function test15() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var doc = gTestBrowser.contentDocument;
+  var mainBox = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+  ok(mainBox, "Test 15, Plugin with id=" + plugin.id + " overlay should exist");
+
   finishTest();
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugin_alternate_content.html
@@ -0,0 +1,9 @@
+<!-- bug 739575 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<object id="test" type="application/x-test" style="height: 200px; width:200px">
+<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p>
+</object>
+</body></html>
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -40,90 +40,137 @@
  *
  * ***** END LICENSE BLOCK ***** */
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
+const DBG_XUL = "chrome://browser/content/debugger.xul";
+const REMOTE_PROFILE_NAME = "_remote-debug";
+
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 let EXPORTED_SYMBOLS = ["DebuggerUI"];
 
 /**
  * Provides a simple mechanism of managing debugger instances per tab.
  *
  * @param nsIDOMWindow aWindow
  *        The chrome window for which the DebuggerUI instance is created.
  */
 function DebuggerUI(aWindow) {
   this.chromeWindow = aWindow;
 }
 
 DebuggerUI.prototype = {
+  /**
+   * Called by the DebuggerPane to update the Debugger toggle switches with the
+   * debugger state.
+   */
+  refreshCommand: function DUI_refreshCommand() {
+    let selectedTab = this.chromeWindow.getBrowser().selectedTab;
+    let command = this.chromeWindow.document.getElementById("Tools:Debugger");
+
+    if (this.getDebugger(selectedTab) != null) {
+      command.setAttribute("checked", "true");
+    } else {
+      command.removeAttribute("checked");
+    }
+  },
 
   /**
    * Starts a debugger for the current tab, or stops it if already started.
    * @return DebuggerPane if the debugger is started, null if it's stopped.
    */
   toggleDebugger: function DUI_toggleDebugger() {
     let tab = this.chromeWindow.gBrowser.selectedTab;
 
     if (tab._scriptDebugger) {
       tab._scriptDebugger.close();
       return null;
     }
-    return new DebuggerPane(tab);
+    return new DebuggerPane(this, tab);
+  },
+
+  /**
+   * Starts a remote debugger in a new process, or stops it if already started.
+   * @see DebuggerProcess.constructor
+   * @return DebuggerProcess if the debugger is started, null if it's stopped.
+   */
+  toggleRemoteDebugger: function DUI_toggleRemoteDebugger(aOnClose, aOnRun) {
+    let win = this.chromeWindow;
+
+    if (win._remoteDebugger) {
+      win._remoteDebugger.close();
+      return null;
+    }
+    return new DebuggerProcess(win, aOnClose, aOnRun);
   },
 
   /**
    * Get the debugger for a specified tab.
    * @return DebuggerPane if a debugger exists for the tab, null otherwise
    */
   getDebugger: function DUI_getDebugger(aTab) {
-    return aTab._scriptDebugger;
+    return '_scriptDebugger' in aTab ? aTab._scriptDebugger : null;
   },
 
   /**
    * Get the preferences associated with the debugger frontend.
    * @return object
    */
   get preferences() {
-    return DebuggerUIPreferences;
+    return DebuggerPreferences;
   }
 };
 
 /**
  * Creates a pane that will host the debugger.
  *
  * @param XULElement aTab
  *        The tab in which to create the debugger.
  */
-function DebuggerPane(aTab) {
+function DebuggerPane(aDebuggerUI, aTab) {
+  this._globalUI = aDebuggerUI;
   this._tab = aTab;
+  this._initServer();
   this._create();
 }
 
 DebuggerPane.prototype = {
 
   /**
+   * Initializes the debugger server.
+   */
+  _initServer: function DP__initServer() {
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+  },
+
+  /**
    * Creates and initializes the widgets containing the debugger UI.
    */
   _create: function DP__create() {
     this._tab._scriptDebugger = this;
 
     let gBrowser = this._tab.linkedBrowser.getTabBrowser();
     let ownerDocument = gBrowser.parentNode.ownerDocument;
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "hud-splitter");
 
     this._frame = ownerDocument.createElement("iframe");
-    this._frame.height = DebuggerUIPreferences.height;
+    this._frame.height = DebuggerPreferences.height;
 
     this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this._frame);
 
     this.close = this.close.bind(this);
     let self = this;
 
@@ -134,39 +181,43 @@ DebuggerPane.prototype = {
 
       // Bind shortcuts for accessing the breakpoint methods in the debugger.
       let bkp = self.debuggerWindow.DebuggerController.Breakpoints;
       self.addBreakpoint = bkp.addBreakpoint;
       self.removeBreakpoint = bkp.removeBreakpoint;
       self.getBreakpoint = bkp.getBreakpoint;
     }, true);
 
-    this._frame.setAttribute("src", "chrome://browser/content/debugger.xul");
+    this._frame.setAttribute("src", DBG_XUL);
+
+    this._globalUI.refreshCommand();
   },
 
   /**
    * Closes the debugger, removing child nodes and event listeners.
    */
   close: function DP_close() {
     if (!this._tab) {
       return;
     }
-    this._tab._scriptDebugger = null;
+    delete this._tab._scriptDebugger;
     this._tab = null;
 
-    DebuggerUIPreferences.height = this._frame.height;
+    DebuggerPreferences.height = this._frame.height;
     this._frame.removeEventListener("Debugger:Close", this.close, true);
     this._frame.removeEventListener("unload", this.close, true);
 
     this._nbox.removeChild(this._splitter);
     this._nbox.removeChild(this._frame);
 
     this._splitter = null;
     this._frame = null;
     this._nbox = null;
+
+    this._globalUI.refreshCommand();
   },
 
   /**
    * Gets the debugger content window.
    * @return nsIDOMWindow if a debugger window exists, null otherwise
    */
   get debuggerWindow() {
     return this._frame ? this._frame.contentWindow : null;
@@ -181,19 +232,122 @@ DebuggerPane.prototype = {
     if (debuggerWindow) {
       return debuggerWindow.DebuggerController.Breakpoints.store;
     }
     return null;
   }
 };
 
 /**
- * Various debugger UI preferences (currently just the pane height).
+ * Creates a process that will hold the remote debugger.
+ *
+ * @param function aOnClose
+ *        Optional, a function called when the process exits.
+ * @param function aOnRun
+ *        Optional, a function called when the process starts running.
+ * @param nsIDOMWindow aWindow
+ *        The chrome window for which the remote debugger instance is created.
  */
-let DebuggerUIPreferences = {
+function DebuggerProcess(aWindow, aOnClose, aOnRun) {
+  this._win = aWindow;
+  this._closeCallback = aOnClose;
+  this._runCallback = aOnRun;
+  this._initProfile();
+  this._create();
+}
+
+DebuggerProcess.prototype = {
+
+  /**
+   * Initializes the debugger server.
+   */
+  _initServer: function RDP__initServer() {
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+    DebuggerServer.closeListener();
+    DebuggerServer.openListener(DebuggerPreferences.remotePort, false);
+  },
+
+  /**
+   * Initializes a profile for the remote debugger process.
+   */
+  _initProfile: function RDP__initProfile() {
+    let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
+      .createInstance(Ci.nsIToolkitProfileService);
+
+    let dbgProfileName;
+    try {
+      dbgProfileName = profileService.selectedProfile.name + REMOTE_PROFILE_NAME;
+    } catch(e) {
+      dbgProfileName = REMOTE_PROFILE_NAME;
+      Cu.reportError(e);
+    }
+
+    this._dbgProfile = profileService.createProfile(null, null, dbgProfileName);
+    profileService.flush();
+  },
+
+  /**
+   * Creates and initializes the profile & process for the remote debugger.
+   */
+  _create: function RDP__create() {
+    this._win._remoteDebugger = this;
+
+    let file = FileUtils.getFile("CurProcD",
+      [Services.appinfo.OS == "WINNT" ? "firefox.exe"
+                                      : "firefox-bin"]);
+
+    let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+    process.init(file);
+
+    let args = [
+      "-no-remote", "-P", this._dbgProfile.name,
+      "-chrome", DBG_XUL,
+      "-width", DebuggerPreferences.remoteWinWidth,
+      "-height", DebuggerPreferences.remoteWinHeight];
+
+    process.runwAsync(args, args.length, { observe: this.close.bind(this) });
+    this._dbgProcess = process;
+
+    if (typeof this._runCallback === "function") {
+      this._runCallback.call({}, this);
+    }
+  },
+
+  /**
+   * Closes the remote debugger, removing the profile and killing the process.
+   */
+  close: function RDP_close() {
+    if (!this._win) {
+      return;
+    }
+    delete this._win._remoteDebugger;
+    this._win = null;
+
+    if (this._dbgProcess.isRunning) {
+      this._dbgProcess.kill();
+    }
+    if (this._dbgProfile) {
+      this._dbgProfile.remove(false);
+    }
+    if (typeof this._closeCallback === "function") {
+      this._closeCallback.call({}, this);
+    }
+
+    this._dbgProcess = null;
+    this._dbgProfile = null;
+  }
+};
+
+/**
+ * Various debugger preferences.
+ */
+let DebuggerPreferences = {
 
   /**
    * Gets the preferred height of the debugger pane.
    * @return number
    */
   get height() {
     if (this._height === undefined) {
       this._height = Services.prefs.getIntPref("devtools.debugger.ui.height");
@@ -205,8 +359,40 @@ let DebuggerUIPreferences = {
    * Sets the preferred height of the debugger pane.
    * @param number value
    */
   set height(value) {
     Services.prefs.setIntPref("devtools.debugger.ui.height", value);
     this._height = value;
   }
 };
+
+/**
+ * Gets the preferred width of the remote debugger window.
+ * @return number
+ */
+XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinWidth", function() {
+  return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.width");
+});
+
+/**
+ * Gets the preferred height of the remote debugger window.
+ * @return number
+ */
+XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinHeight", function() {
+  return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.height");
+});
+
+/**
+ * Gets the preferred default remote debugging host.
+ * @return string
+ */
+XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteHost", function() {
+  return Services.prefs.getCharPref("devtools.debugger.remote-host");
+});
+
+/**
+ * Gets the preferred default remote debugging port.
+ * @return number
+ */
+XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remotePort", function() {
+  return Services.prefs.getIntPref("devtools.debugger.remote-port");
+});
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -109,29 +109,28 @@ let DebuggerController = {
     DebuggerView.Properties.destroy();
 
     DebuggerController.SourceScripts.disconnect();
     DebuggerController.StackFrames.disconnect();
     DebuggerController.ThreadState.disconnect();
 
     this.dispatchEvent("Debugger:Unloaded");
     this._disconnect();
+    this._isRemote && this._quitApp();
   },
 
   /**
    * Initializes a debugger client and connects it to the debugger server,
    * wiring event handlers as necessary.
    */
   _connect: function DC__connect() {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
+    let transport =
+      this._isRemote ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
+                     : DebuggerServer.connectPipe();
 
-    let transport = DebuggerServer.connectPipe();
     let client = this.client = new DebuggerClient(transport);
 
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
 
     client.connect(function(aType, aTraits) {
       client.listTabs(function(aResponse) {
         let tab = aResponse.tabs[aResponse.selected];
@@ -216,16 +215,41 @@ let DebuggerController = {
           });
         });
 
       }.bind(this));
     }.bind(this));
   },
 
   /**
+   * Returns true if this is a remote debugger instance.
+   * @return boolean
+   */
+  get _isRemote() {
+    return !window.parent.content;
+  },
+
+  /**
+   * Attempts to quit the current process if allowed.
+   */
+  _quitApp: function DC__quitApp() {
+    let canceled = Cc["@mozilla.org/supports-PRBool;1"]
+      .createInstance(Ci.nsISupportsPRBool);
+
+    Services.obs.notifyObservers(canceled, "quit-application-requested", null);
+
+    // Somebody canceled our quit request.
+    if (canceled.data) {
+      return;
+    }
+
+    Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+  },
+
+  /**
    * Convenience method, dispatching a custom event.
    *
    * @param string aType
    *        The name of the event.
    * @param string aDetail
    *        The data passed when initializing the event.
    */
   dispatchEvent: function DC_dispatchEvent(aType, aDetail) {
@@ -465,52 +489,71 @@ StackFrames.prototype = {
       let thisVar = localScope.addVar("this");
       thisVar.setGrip({
         type: frame.this.type,
         class: frame.this.class
       });
       this._addExpander(thisVar, frame.this);
     }
 
+    if (frame.environment) {
+      // Add nodes for every argument.
+      let variables = frame.environment.bindings.arguments;
+      for each (let variable in variables) {
+        let name = Object.getOwnPropertyNames(variable)[0];
+        let paramVar = localScope.addVar(name);
+        let paramVal = variable[name].value;
+        paramVar.setGrip(paramVal);
+        this._addExpander(paramVar, paramVal);
+      }
+
+      // Add nodes for every other variable in scope.
+      variables = frame.environment.bindings.variables;
+      for (let variable in variables) {
+        let paramVar = localScope.addVar(variable);
+        let paramVal = variables[variable].value;
+        paramVar.setGrip(paramVal);
+        this._addExpander(paramVar, paramVal);
+      }
+
+      // If we already found 'arguments', we are done here.
+      if ("arguments" in frame.environment.bindings.variables) {
+        // Signal that variables have been fetched.
+        DebuggerController.dispatchEvent("Debugger:FetchedVariables");
+        return;
+      }
+    }
+
+    // Sometimes in call frames with arguments we don't get 'arguments' in the
+    // environment (bug 746601) and we have to construct it manually. Note, that
+    // in this case arguments.callee will be absent, even in the cases where it
+    // shouldn't be.
     if (frame.arguments && frame.arguments.length > 0) {
       // Add "arguments".
       let argsVar = localScope.addVar("arguments");
       argsVar.setGrip({
         type: "object",
         class: "Arguments"
       });
       this._addExpander(argsVar, frame.arguments);
 
-      // Add variables for every argument.
-      let objClient = this.activeThread.pauseGrip(frame.callee);
-      objClient.getSignature(function SF_getSignature(aResponse) {
-        for (let i = 0, l = aResponse.parameters.length; i < l; i++) {
-          let param = aResponse.parameters[i];
-          let paramVar = localScope.addVar(param);
-          let paramVal = frame.arguments[i];
+      // Signal that variables have been fetched.
+      DebuggerController.dispatchEvent("Debugger:FetchedVariables");
+    }
 
-          paramVar.setGrip(paramVal);
-          this._addExpander(paramVar, paramVal);
-        }
-
-        // Signal that call parameters have been fetched.
-        DebuggerController.dispatchEvent("Debugger:FetchedParameters");
-
-      }.bind(this));
-    }
   },
 
   /**
-   * Adds a onexpand callback for a variable, lazily handling the addition of
+   * Adds an 'onexpand' callback for a variable, lazily handling the addition of
    * new properties.
    */
   _addExpander: function SF__addExpander(aVar, aObject) {
     // No need for expansion for null and undefined values, but we do need them
     // for frame.arguments which is a regular array.
-    if (!aObject || typeof aObject !== "object" ||
+    if (!aVar || !aObject || typeof aObject !== "object" ||
         (aObject.type !== "object" && !Array.isArray(aObject))) {
       return;
     }
 
     // Force the twisty to show up.
     aVar.forceShowArrow();
     aVar.onexpand = this._addVarProperties.bind(this, aVar, aObject);
   },
@@ -681,26 +724,27 @@ SourceScripts.prototype = {
     this.activeThread.removeListener("scriptsadded", this._onScriptsAdded);
     this.activeThread.removeListener("scriptscleared", this._onScriptsCleared);
   },
 
   /**
    * Handler for the debugger client's unsolicited newScript notification.
    */
   _onNewScript: function SS__onNewScript(aNotification, aPacket) {
-    this._addScript({ url: aPacket.url, startLine: aPacket.startLine });
+    this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
   },
 
   /**
    * Handler for the thread client's scriptsadded notification.
    */
   _onScriptsAdded: function SS__onScriptsAdded() {
     for each (let script in this.activeThread.cachedScripts) {
-      this._addScript(script);
+      this._addScript(script, false);
     }
+    DebuggerView.Scripts.commitScripts();
   },
 
   /**
    * Handler for the thread client's scriptscleared notification.
    */
   _onScriptsCleared: function SS__onScriptsCleared() {
     DebuggerView.Scripts.empty();
   },
@@ -743,51 +787,70 @@ SourceScripts.prototype = {
     let q = aUrl.indexOf('?');
     if (q > -1) {
       return aUrl.slice(0, q);
     }
     return aUrl;
   },
 
   /**
+   * Gets the prePath for a script URL.
+   *
+   * @param string aUrl
+   *        The script url.
+   * @return string
+   *         The script prePath if the url is valid, null otherwise.
+   */
+  _getScriptPrePath: function SS__getScriptDomain(aUrl) {
+    try {
+      return Services.io.newURI(aUrl, null, null).prePath + "/";
+    } catch (e) {
+    }
+    return null;
+  },
+
+  /**
    * Gets a unique, simplified label from a script url.
    * ex: a). ici://some.address.com/random/subrandom/
    *     b). ni://another.address.org/random/subrandom/page.html
    *     c). san://interesting.address.gro/random/script.js
    *     d). si://interesting.address.moc/random/another/script.js
    * =>
    *     a). subrandom/
    *     b). page.html
    *     c). script.js
    *     d). another/script.js
    *
    * @param string aUrl
    *        The script url.
    * @param string aHref
    *        The content location href to be used. If unspecified, it will
-   *        defalult to debugged panrent window location.
+   *        default to the script url prepath.
    * @return string
    *         The simplified label.
    */
   _getScriptLabel: function SS__getScriptLabel(aUrl, aHref) {
     let url = this._trimUrlQuery(aUrl);
 
     if (this._labelsCache[url]) {
       return this._labelsCache[url];
     }
 
-    let href = aHref || window.parent.content.location.href;
+    let content = window.parent.content;
+    let domain = content ? content.location.href : this._getScriptPrePath(aUrl);
+
+    let href = aHref || domain;
     let pathElements = url.split("/");
     let label = pathElements.pop() || (pathElements.pop() + "/");
 
-    // if the label as a leaf name is alreay present in the scripts list
+    // If the label as a leaf name is already present in the scripts list.
     if (DebuggerView.Scripts.containsLabel(label)) {
       label = url.replace(href.substring(0, href.lastIndexOf("/") + 1), "");
 
-      // if the path/to/script is exactly the same, we're in different domains
+      // If the path/to/script is exactly the same, we're in different domains.
       if (DebuggerView.Scripts.containsLabel(label)) {
         label = url;
       }
     }
 
     return this._labelsCache[url] = label;
   },
 
@@ -795,25 +858,26 @@ SourceScripts.prototype = {
    * Clears the labels cache, populated by SS_getScriptLabel.
    * This should be done every time the content location changes.
    */
   _clearLabelsCache: function SS__clearLabelsCache() {
     this._labelsCache = {};
   },
 
   /**
-   * Add the specified script to the list and display it in the editor if the
-   * editor is empty.
+   * Add the specified script to the list.
+   *
+   * @param object aScript
+   *        The script object coming from the active thread.
+   * @param boolean aForceFlag
+   *        True to force the script to be immediately added.
    */
-  _addScript: function SS__addScript(aScript) {
-    DebuggerView.Scripts.addScript(this._getScriptLabel(aScript.url), aScript);
-
-    if (DebuggerView.editor.getCharCount() == 0) {
-      this.showScript(aScript);
-    }
+  _addScript: function SS__addScript(aScript, aForceFlag) {
+    DebuggerView.Scripts.addScript(
+      this._getScriptLabel(aScript.url), aScript, aForceFlag);
   },
 
   /**
    * Load the editor with the script text if available, otherwise fire an event
    * to load and display the script text.
    *
    * @param object aScript
    *        The script object coming from the active thread.
@@ -871,17 +935,17 @@ SourceScripts.prototype = {
       url: aScript.url
     });
   },
 
   /**
    * Handles notifications to load a source script from the cache or from a
    * local file.
    *
-   * XXX: Tt may be better to use nsITraceableChannel to get to the sources
+   * XXX: It may be better to use nsITraceableChannel to get to the sources
    * without relying on caching when we can (not for eval, etc.):
    * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
    */
   _onLoadSource: function SS__onLoadSource(aEvent) {
     let url = aEvent.detail.url;
     let options = aEvent.detail.options;
     let self = this;
 
@@ -962,17 +1026,17 @@ SourceScripts.prototype = {
    * Log an error message in the error console when a script fails to load.
    *
    * @param string aUrl
    *        The URL of the source script.
    * @param string aStatus
    *        The failure status code.
    */
   _logError: function SS__logError(aUrl, aStatus) {
-    Components.utils.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus]));
+    Cu.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus]));
   },
 };
 
 /**
  * Handles all the breakpoints in the current debugger.
  */
 function Breakpoints() {
   this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this);
@@ -1254,16 +1318,37 @@ let L10N = {
   }
 };
 
 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
   return Services.strings.createBundle(DBG_STRINGS_URI);
 });
 
 /**
+ * Shortcuts for accessing various debugger preferences.
+ */
+let Prefs = {};
+
+/**
+ * Gets the preferred default remote debugging host.
+ * @return string
+ */
+XPCOMUtils.defineLazyGetter(Prefs, "remoteHost", function() {
+  return Services.prefs.getCharPref("devtools.debugger.remote-host");
+});
+
+/**
+ * Gets the preferred default remote debugging port.
+ * @return number
+ */
+XPCOMUtils.defineLazyGetter(Prefs, "remotePort", function() {
+  return Services.prefs.getIntPref("devtools.debugger.remote-port");
+});
+
+/**
  * Preliminary setup for the DebuggerController object.
  */
 DebuggerController.init();
 DebuggerController.ThreadState = new ThreadState();
 DebuggerController.StackFrames = new StackFrames();
 DebuggerController.SourceScripts = new SourceScripts();
 DebuggerController.Breakpoints = new Breakpoints();
 
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -85,53 +85,72 @@ let DebuggerView = {
   }
 };
 
 /**
  * Functions handling the scripts UI.
  */
 function ScriptsView() {
   this._onScriptsChange = this._onScriptsChange.bind(this);
+  this._onScriptsSearch = this._onScriptsSearch.bind(this);
 }
 
 ScriptsView.prototype = {
 
   /**
    * Removes all elements from the scripts container, leaving it empty.
    */
   empty: function DVS_empty() {
     while (this._scripts.firstChild) {
       this._scripts.removeChild(this._scripts.firstChild);
     }
   },
 
   /**
+   * Removes the input in the searchbox and unhides all the scripts.
+   */
+  clearSearch: function DVS_clearSearch() {
+    this._searchbox.value = "";
+    this._onScriptsSearch({});
+  },
+
+  /**
    * Checks whether the script with the specified URL is among the scripts
    * known to the debugger and shown in the list.
    *
    * @param string aUrl
    *        The script URL.
    * @return boolean
    */
   contains: function DVS_contains(aUrl) {
+    if (this._tmpScripts.some(function(element) {
+      return element.script.url == aUrl;
+    })) {
+      return true;
+    }
     if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) {
       return true;
     }
     return false;
   },
 
   /**
    * Checks whether the script with the specified label is among the scripts
    * known to the debugger and shown in the list.
    *
    * @param string aLabel
    *        The script label.
    * @return boolean
    */
   containsLabel: function DVS_containsLabel(aLabel) {
+    if (this._tmpScripts.some(function(element) {
+      return element.label == aLabel;
+    })) {
+      return true;
+    }
     if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) {
       return true;
     }
     return false;
   },
 
   /**
    * Selects the script with the specified URL from the list.
@@ -167,80 +186,258 @@ ScriptsView.prototype = {
    * @return string | null
    */
   get selected() {
     return this._scripts.selectedItem ?
            this._scripts.selectedItem.value : null;
   },
 
   /**
+   * Returns the list of labels in the scripts container.
+   * @return array
+   */
+  get scriptLabels() {
+    let labels = [];
+    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
+      labels.push(this._scripts.getItemAtIndex(i).label);
+    }
+    return labels;
+  },
+
+  /**
    * Returns the list of URIs for scripts in the page.
    * @return array
    */
   get scriptLocations() {
     let locations = [];
     for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
       locations.push(this._scripts.getItemAtIndex(i).value);
     }
     return locations;
   },
 
   /**
-   * Adds a script to the scripts container.
-   * If the script already exists (was previously added), null is returned.
-   * Otherwise, the newly created element is returned.
+   * Gets the number of visible (hidden=false) scripts in the container.
+   * @return number
+   */
+  get visibleItemsCount() {
+    let count = 0;
+    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
+      count += this._scripts.getItemAtIndex(i).hidden ? 0 : 1;
+    }
+    return count;
+  },
+
+  /**
+   * Prepares a script to be added to the scripts container. This allows
+   * for a large number of scripts to be batched up before being
+   * alphabetically sorted and added in the container.
+   * @see ScriptsView.commitScripts
+   *
+   * If aForceFlag is true, the script will be immediately inserted at the
+   * necessary position in the container so that all the scripts remain sorted.
+   * This can be much slower than batching up multiple scripts.
+   *
+   * @param string aLabel
+   *        The simplified script location to be shown.
+   * @param string aScript
+   *        The source script.
+   * @param boolean aForceFlag
+   *        True to force the script to be immediately added.
+   */
+  addScript: function DVS_addScript(aLabel, aScript, aForceFlag) {
+    // Batch the script to be added later.
+    if (!aForceFlag) {
+      this._tmpScripts.push({ label: aLabel, script: aScript });
+      return;
+    }
+
+    // Find the target position in the menulist and insert the script there.
+    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
+      if (this._scripts.getItemAtIndex(i).label > aLabel) {
+        this._createScriptElement(aLabel, aScript, i);
+        return;
+      }
+    }
+    // The script is alphabetically the last one.
+    this._createScriptElement(aLabel, aScript, -1, true);
+  },
+
+  /**
+   * Adds all the prepared scripts to the scripts container.
+   * If a script already exists (was previously added), nothing happens.
+   */
+  commitScripts: function DVS_commitScripts() {
+    let newScripts = this._tmpScripts;
+    this._tmpScripts = [];
+
+    if (!newScripts || !newScripts.length) {
+      return;
+    }
+    newScripts.sort(function(a, b) {
+      return a.label.toLowerCase() > b.label.toLowerCase();
+    });
+
+    for (let i = 0, l = newScripts.length; i < l; i++) {
+      let item = newScripts[i];
+      this._createScriptElement(item.label, item.script, -1, true);
+    }
+  },
+
+  /**
+   * Creates a custom script element and adds it to the scripts container.
+   * If the script with the specified label already exists, nothing happens.
    *
    * @param string aLabel
    *        The simplified script location to be shown.
    * @param string aScript
    *        The source script.
-   * @return object
-   *         The newly created html node representing the added script.
+   * @param number aIndex
+   *        The index where to insert to new script in the container.
+   *        Pass -1 to append the script at the end.
+   * @param boolean aSelectIfEmptyFlag
+   *        True to set the newly created script as the currently selected item
+   *        if there are no other existing scripts in the container.
    */
-  addScript: function DVS_addScript(aLabel, aScript) {
+  _createScriptElement: function DVS__createScriptElement(
+    aLabel, aScript, aIndex, aSelectIfEmptyFlag)
+  {
     // Make sure we don't duplicate anything.
-    if (this.containsLabel(aLabel)) {
-      return null;
+    if (aLabel == "null" || this.containsLabel(aLabel)) {
+      return;
     }
 
-    let script = this._scripts.appendItem(aLabel, aScript.url);
-    script.setAttribute("tooltiptext", aScript.url);
-    script.setUserData("sourceScript", aScript, null);
+    let scriptItem =
+      aIndex == -1 ? this._scripts.appendItem(aLabel, aScript.url)
+                   : this._scripts.insertItemAt(aIndex, aLabel, aScript.url);
 
-    this._scripts.selectedItem = script;
-    return script;
+    scriptItem.setAttribute("tooltiptext", aScript.url);
+    scriptItem.setUserData("sourceScript", aScript, null);
+
+    if (this._scripts.itemCount == 1 && aSelectIfEmptyFlag) {
+      this._scripts.selectedItem = scriptItem;
+    }
   },
 
   /**
-   * The cached click listener for the scripts container.
+   * The click listener for the scripts container.
    */
   _onScriptsChange: function DVS__onScriptsChange() {
     let script = this._scripts.selectedItem.getUserData("sourceScript");
+    this._preferredScript = script;
     DebuggerController.SourceScripts.showScript(script);
   },
 
   /**
-   * The cached scripts container.
+   * The search listener for the scripts search box.
+   */
+  _onScriptsSearch: function DVS__onScriptsSearch(e) {
+    let editor = DebuggerView.editor;
+    let scripts = this._scripts;
+    let rawValue = this._searchbox.value.toLowerCase();
+
+    let rawLength = rawValue.length;
+    let lastColon = rawValue.lastIndexOf(":");
+    let lastAt = rawValue.lastIndexOf("@");
+
+    let fileEnd = lastColon != -1 ? lastColon : lastAt != -1 ? lastAt : rawLength;
+    let lineEnd = lastAt != -1 ? lastAt : rawLength;
+
+    let file = rawValue.slice(0, fileEnd);
+    let line = window.parseInt(rawValue.slice(fileEnd + 1, lineEnd)) || -1;
+    let token = rawValue.slice(lineEnd + 1);
+
+    // Presume we won't find anything.
+    scripts.selectedItem = this._preferredScript;
+
+    // If we're not searching for a file anymore, unhide all the scripts.
+    if (!file) {
+      for (let i = 0, l = scripts.itemCount; i < l; i++) {
+        scripts.getItemAtIndex(i).hidden = false;
+      }
+    } else {
+      for (let i = 0, l = scripts.itemCount, found = false; i < l; i++) {
+        let item = scripts.getItemAtIndex(i);
+        let target = item.value.toLowerCase();
+
+        // Search is not case sensitive, and is tied to the url not the label.
+        if (target.match(file)) {
+          item.hidden = false;
+
+          if (!found) {
+            found = true;
+            scripts.selectedItem = item;
+          }
+        }
+        // Hide what doesn't match our search.
+        else {
+          item.hidden = true;
+        }
+      }
+    }
+    if (line > -1) {
+      editor.setCaretPosition(line - 1);
+    }
+    if (token) {
+      let offset = editor.find(token, { ignoreCase: true });
+      if (offset > -1) {
+        editor.setCaretPosition(0);
+        editor.setCaretOffset(offset);
+      }
+    }
+  },
+
+  /**
+   * The keyup listener for the scripts search box.
+   */
+  _onScriptsKeyUp: function DVS__onScriptsKeyUp(e) {
+    if (e.keyCode === e.DOM_VK_ESCAPE) {
+      DebuggerView.editor.focus();
+      return;
+    }
+
+    if (e.keyCode === e.DOM_VK_RETURN || e.keyCode === e.DOM_VK_ENTER) {
+      let editor = DebuggerView.editor;
+      let offset = editor.findNext(true);
+      if (offset > -1) {
+        editor.setCaretPosition(0);
+        editor.setCaretOffset(offset);
+      }
+    }
+  },
+
+  /**
+   * The cached scripts container and search box.
    */
   _scripts: null,
+  _searchbox: null,
 
   /**
    * Initialization function, called when the debugger is initialized.
    */
   initialize: function DVS_initialize() {
     this._scripts = document.getElementById("scripts");
+    this._searchbox = document.getElementById("scripts-search");
     this._scripts.addEventListener("select", this._onScriptsChange, false);
+    this._searchbox.addEventListener("select", this._onScriptsSearch, false);
+    this._searchbox.addEventListener("input", this._onScriptsSearch, false);
+    this._searchbox.addEventListener("keyup", this._onScriptsKeyUp, false);
+    this.commitScripts();
   },
 
   /**
    * Destruction function, called when the debugger is shut down.
    */
   destroy: function DVS_destroy() {
     this._scripts.removeEventListener("select", this._onScriptsChange, false);
+    this._searchbox.removeEventListener("select", this._onScriptsSearch, false);
+    this._searchbox.removeEventListener("input", this._onScriptsSearch, false);
+    this._searchbox.removeEventListener("keyup", this._onScriptsKeyUp, false);
     this._scripts = null;
+    this._searchbox = null;
   }
 };
 
 /**
  * Functions handling the html stackframes UI.
  */
 function StackFramesView() {
   this._onFramesScroll = this._onFramesScroll.bind(this);
@@ -272,16 +469,18 @@ StackFramesView.prototype = {
     else if (aState == "attached") {
       status.textContent = L10N.getStr("runningState");
       resume.label = L10N.getStr("pauseLabel");
     }
     // No valid state parameter.
     else {
       status.textContent = "";
     }
+
+    DebuggerView.Scripts.clearSearch();
   },
 
   /**
    * Removes all elements from the stackframes container, leaving it empty.
    */
   empty: function DVF_empty() {
     while (this._frames.firstChild) {
       this._frames.removeChild(this._frames.firstChild);
@@ -295,17 +494,17 @@ StackFramesView.prototype = {
   emptyText: function DVF_emptyText() {
     // Make sure the container is empty first.
     this.empty();
 
     let item = document.createElement("div");
 
     // The empty node should look grayed out to avoid confusion.
     item.className = "empty list-item";
-    item.appendChild(document.createTextNode(L10N.getStr("emptyText")));
+    item.appendChild(document.createTextNode(L10N.getStr("emptyStackText")));
 
     this._frames.appendChild(item);
   },
 
   /**
    * Adds a frame to the stackframes container.
    * If the frame already exists (was previously added), null is returned.
    * Otherwise, the newly created element is returned.
@@ -376,17 +575,17 @@ StackFramesView.prototype = {
 
   /**
    * Deselects a frame from the stackframe container.
    *
    * @param number aDepth
    *        The frame depth specified by the debugger.
    */
   unhighlightFrame: function DVF_unhighlightFrame(aDepth) {
-    this.highlightFrame(aDepth, true)
+    this.highlightFrame(aDepth, true);
   },
 
   /**
    * Gets the current dirty state.
    *
    * @return boolean value
    *         True if should load more frames.
    */
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -76,16 +76,18 @@
   <div id="body" class="vbox flex">
     <xul:toolbar id="dbg-toolbar">
       <xul:button id="close">&debuggerUI.closeButton;</xul:button>
       <xul:button id="resume"/>
       <xul:button id="step-over">&debuggerUI.stepOverButton;</xul:button>
       <xul:button id="step-in">&debuggerUI.stepInButton;</xul:button>
       <xul:button id="step-out">&debuggerUI.stepOutButton;</xul:button>
       <xul:menulist id="scripts"/>
+      <xul:textbox id="scripts-search" type="search"
+                   emptytext="&debuggerUI.emptyFilterText;"/>
     </xul:toolbar>
     <div id="dbg-content" class="hbox flex">
       <div id="stack" class="vbox">
         <div class="title unselectable">&debuggerUI.stackTitle;</div>
         <div id="stackframes" class="vbox flex"></div>
       </div>
       <div id="script" class="vbox flex">
         <div class="title unselectable">&debuggerUI.scriptTitle;</div>
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -41,16 +41,17 @@ topsrcdir       = @top_srcdir@
 srcdir          = @srcdir@
 VPATH           = @srcdir@
 relativesrcdir  = browser/devtools/debugger/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
+	browser_dbg_createRemote.js \
 	browser_dbg_debuggerstatement.js \
 	browser_dbg_listtabs.js \
 	browser_dbg_tabactor-01.js \
 	browser_dbg_tabactor-02.js \
 	browser_dbg_contextactor-01.js \
 	browser_dbg_contextactor-02.js \
 	testactors.js \
 	browser_dbg_nav-01.js \
@@ -65,16 +66,19 @@ include $(topsrcdir)/config/rules.mk
 	browser_dbg_panesize.js \
 	browser_dbg_stack-01.js \
 	browser_dbg_stack-02.js \
 	browser_dbg_stack-03.js \
 	browser_dbg_stack-04.js \
 	browser_dbg_stack-05.js \
 	browser_dbg_location-changes.js \
 	browser_dbg_script-switching.js \
+	browser_dbg_scripts-sorting.js \
+	browser_dbg_scripts-searching-01.js \
+	browser_dbg_scripts-searching-02.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_update-editor-mode.js \
 	$(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \
 	browser_dbg_clean-exit.js \
 	browser_dbg_bug723069_editor-breakpoints.js \
 	browser_dbg_bug731394_editor-contextmenu.js \
 	browser_dbg_displayName.js \
 	browser_dbg_iframes.js \
--- a/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js
@@ -18,47 +18,54 @@ let gBreakpoints = null;
 
 function test()
 {
   let tempScope = {};
   Cu.import("resource:///modules/source-editor.jsm", tempScope);
   let SourceEditor = tempScope.SourceEditor;
   let scriptShown = false;
   let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
+    resumed = true;
+
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
-      runTest();
+      executeSoon(startTest);
     });
 
-    gDebuggee.firstCall();
+    executeSoon(function() {
+      gDebuggee.firstCall();
+    });
   });
 
-  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
-    let url = aEvent.detail.url;
-    if (url.indexOf("-02.js") != -1) {
-      scriptShown = true;
-      window.removeEventListener(aEvent.type, _onEvent);
-      runTest();
-    }
-  });
+  function onScriptShown(aEvent)
+  {
+    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
+    executeSoon(startTest);
+  }
 
-  function runTest()
+  window.addEventListener("Debugger:ScriptShown", onScriptShown);
+
+  function startTest()
   {
-    if (scriptShown && framesAdded) {
-      Services.tm.currentThread.dispatch({ run: onScriptShown }, 0);
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      window.removeEventListener("Debugger:ScriptShown", onScriptShown);
+      testStarted = true;
+      Services.tm.currentThread.dispatch({ run: performTest }, 0);
     }
   }
 
-  function onScriptShown()
+  function performTest()
   {
     gScripts = gDebugger.DebuggerView.Scripts;
 
     is(gDebugger.DebuggerController.activeThread.state, "paused",
       "Should only be getting stack frames while paused.");
 
     is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts.");
 
--- a/browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js
@@ -16,47 +16,53 @@ function test()
 {
   let tempScope = {};
   Cu.import("resource:///modules/source-editor.jsm", tempScope);
   let SourceEditor = tempScope.SourceEditor;
 
   let contextMenu = null;
   let scriptShown = false;
   let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
+    resumed = true;
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
-      runTest();
+      executeSoon(startTest);
     });
-    gDebuggee.firstCall();
+
+    executeSoon(function() {
+      gDebuggee.firstCall();
+    });
   });
 
-  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
-    let url = aEvent.detail.url;
-    if (url.indexOf("-02.js") != -1) {
-      scriptShown = true;
-      window.removeEventListener(aEvent.type, _onEvent);
-      runTest();
-    }
-  });
+  function onScriptShown(aEvent) {
+    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
+    executeSoon(startTest);
+  }
+
+  window.addEventListener("Debugger:ScriptShown", onScriptShown);
 
-  function runTest()
+  function startTest()
   {
-    if (scriptShown && framesAdded) {
-      Services.tm.currentThread.dispatch({ run: onScriptShown }, 0);
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      testStarted = true;
+      window.removeEventListener("Debugger:ScriptShown", onScriptShown);
+      Services.tm.currentThread.dispatch({ run: performTest }, 0);
     }
   }
 
-  function onScriptShown()
+  function performTest()
   {
     let scripts = gDebugger.DebuggerView.Scripts._scripts;
 
     is(gDebugger.DebuggerController.activeThread.state, "paused",
       "Should only be getting stack frames while paused.");
 
     is(scripts.itemCount, 2, "Found the expected number of scripts.");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_createRemote.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var gProcess = null;
+var gTab = null;
+var gDebuggee = null;
+
+function test() {
+  remote_debug_tab_pane(STACK_URL, aOnClosing, function(aTab, aDebuggee, aProcess) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gProcess = aProcess;
+
+    testSimpleCall();
+  });
+}
+
+function testSimpleCall() {
+  Services.tm.currentThread.dispatch({ run: function() {
+
+    ok(gProcess._dbgProcess,
+      "The remote debugger process wasn't created properly!");
+    ok(gProcess._dbgProcess.isRunning,
+      "The remote debugger process isn't running!");
+    is(typeof gProcess._dbgProcess.pid, "number",
+      "The remote debugger process doesn't have a pid (?!)");
+
+    info("process location: " + gProcess._dbgProcess.location);
+    info("process pid: " + gProcess._dbgProcess.pid);
+    info("process name: " + gProcess._dbgProcess.processName);
+    info("process sig: " + gProcess._dbgProcess.processSignature);
+
+    ok(gProcess._dbgProfile,
+      "The remote debugger profile wasn't created properly!");
+    ok(gProcess._dbgProfile.localDir,
+      "The remote debugger profile doesn't have a localDir...");
+    ok(gProcess._dbgProfile.rootDir,
+      "The remote debugger profile doesn't have a rootDir...");
+    ok(gProcess._dbgProfile.name,
+      "The remote debugger profile doesn't have a name...");
+
+    info("profile localDir: " + gProcess._dbgProfile.localDir);
+    info("profile rootDir: " + gProcess._dbgProfile.rootDir);
+    info("profile name: " + gProcess._dbgProfile.name);
+
+    let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
+      .createInstance(Ci.nsIToolkitProfileService);
+
+    let profile = profileService.getProfileByName(gProcess._dbgProfile.name);
+
+    ok(profile,
+      "The remote debugger profile wasn't *actually* created properly!");
+    is(profile.localDir.path, gProcess._dbgProfile.localDir.path,
+      "The remote debugger profile doesn't have the correct localDir!");
+    is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path,
+      "The remote debugger profile doesn't have the correct rootDir!");
+
+    DebuggerUI.toggleRemoteDebugger();
+  }}, 0);
+}
+
+function aOnClosing() {
+  ok(!gProcess._dbgProcess.isRunning,
+    "The remote debugger process isn't closed as it should be!");
+  is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
+    "The remote debugger process didn't die cleanly.");
+
+  info("process exit value: " + gProcess._dbgProcess.exitValue);
+
+  info("profile localDir: " + gProcess._dbgProfile.localDir.path);
+  info("profile rootDir: " + gProcess._dbgProfile.rootDir.path);
+  info("profile name: " + gProcess._dbgProfile.name);
+
+  executeSoon(function() {
+    finish();
+  });
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gProcess = null;
+  gTab = null;
+  gDebuggee = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
@@ -80,38 +80,38 @@ function testSimpleCall() {
 
   gDebuggee.simpleCall();
 }
 
 function resumeAndFinish() {
   gDebugger.DebuggerController.activeThread.resume(function() {
     let vs = gDebugger.DebuggerView.Scripts;
     let ss = gDebugger.DebuggerController.SourceScripts;
-    ss._onScriptsCleared();
+    vs.empty();
+    vs._scripts.removeEventListener("select", vs._onScriptsChange, false);
 
     is(ss._trimUrlQuery("a/b/c.d?test=1&random=4"), "a/b/c.d",
       "Trimming the url query isn't done properly.");
 
     let urls = [
       { href: "ici://some.address.com/random/", leaf: "subrandom/" },
       { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
       { href: "san://interesting.address.gro/random/", leaf: "script.js" },
       { href: "si://interesting.address.moc/random/", leaf: "script.js" },
       { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
       { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
       { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
       { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
     ];
 
-    vs._scripts.removeEventListener("select", vs._onScriptsChange, false);
-
     urls.forEach(function(url) {
       executeSoon(function() {
         let loc = url.href + url.leaf;
         vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
+        vs.commitScripts();
       });
     });
 
     executeSoon(function() {
       for (let i = 0; i < vs._scripts.itemCount; i++) {
         let lab = vs._scripts.getItemAtIndex(i).getAttribute("label");
         let loc = urls[i].href + urls[i].leaf;
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -22,20 +22,20 @@ function test()
     testFrameParameters();
   });
 }
 
 function testFrameParameters()
 {
   dump("Started testFrameParameters!\n");
 
-  gDebugger.addEventListener("Debugger:FetchedParameters", function test() {
-    dump("Entered Debugger:FetchedParameters!\n");
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    dump("Entered Debugger:FetchedVariables!\n");
 
-    gDebugger.removeEventListener("Debugger:FetchedParameters", test, false);
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
       dump("After currentThread.dispatch!\n");
 
       var frames = gDebugger.DebuggerView.StackFrames._frames,
           childNodes = frames.childNodes,
           localScope = gDebugger.DebuggerView.Properties.localScope,
           localNodes = localScope.querySelector(".details").childNodes;
@@ -47,43 +47,52 @@ function testFrameParameters()
       dump("localNodes - " + localNodes.constructor + "\n");
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
-      is(localNodes.length, 8,
+      is(localNodes.length, 11,
         "The localScope should contain all the created variable elements.");
 
       is(localNodes[0].querySelector(".info").textContent, "[object Proxy]",
         "Should have the right property value for 'this'.");
 
-      is(localNodes[1].querySelector(".info").textContent, "[object Arguments]",
-        "Should have the right property value for 'arguments'.");
-
-      is(localNodes[2].querySelector(".info").textContent, "[object Object]",
+      is(localNodes[1].querySelector(".info").textContent, "[object Object]",
         "Should have the right property value for 'aArg'.");
 
-      is(localNodes[3].querySelector(".info").textContent, '"beta"',
+      is(localNodes[2].querySelector(".info").textContent, '"beta"',
         "Should have the right property value for 'bArg'.");
 
-      is(localNodes[4].querySelector(".info").textContent, "3",
+      is(localNodes[3].querySelector(".info").textContent, "3",
         "Should have the right property value for 'cArg'.");
 
-      is(localNodes[5].querySelector(".info").textContent, "false",
+      is(localNodes[4].querySelector(".info").textContent, "false",
         "Should have the right property value for 'dArg'.");
 
-      is(localNodes[6].querySelector(".info").textContent, "null",
+      is(localNodes[5].querySelector(".info").textContent, "null",
         "Should have the right property value for 'eArg'.");
 
-      is(localNodes[7].querySelector(".info").textContent, "undefined",
+      is(localNodes[6].querySelector(".info").textContent, "undefined",
         "Should have the right property value for 'fArg'.");
 
+      is(localNodes[7].querySelector(".info").textContent, "1",
+        "Should have the right property value for 'a'.");
+
+      is(localNodes[8].querySelector(".info").textContent, "[object Object]",
+        "Should have the right property value for 'b'.");
+
+      is(localNodes[9].querySelector(".info").textContent, "[object Object]",
+        "Should have the right property value for 'c'.");
+
+      is(localNodes[10].querySelector(".info").textContent, "[object Arguments]",
+        "Should have the right property value for 'arguments'.");
+
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
     content.window);
 }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -22,20 +22,20 @@ function test()
     testFrameParameters();
   });
 }
 
 function testFrameParameters()
 {
   dump("Started testFrameParameters!\n");
 
-  gDebugger.addEventListener("Debugger:FetchedParameters", function test() {
-    dump("Entered Debugger:FetchedParameters!\n");
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    dump("Entered Debugger:FetchedVariables!\n");
 
-    gDebugger.removeEventListener("Debugger:FetchedParameters", test, false);
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
       dump("After currentThread.dispatch!\n");
 
       var frames = gDebugger.DebuggerView.StackFrames._frames,
           localScope = gDebugger.DebuggerView.Properties.localScope,
           localNodes = localScope.querySelector(".details").childNodes;
 
@@ -45,82 +45,98 @@ function testFrameParameters()
       dump("localNodes - " + localNodes.constructor + "\n");
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
-      is(localNodes.length, 8,
+      is(localNodes.length, 11,
         "The localScope should contain all the created variable elements.");
 
       is(localNodes[0].querySelector(".info").textContent, "[object Proxy]",
         "Should have the right property value for 'this'.");
 
-      // Expand the __proto__ and arguments tree nodes. This causes their
-      // properties to be retrieved and displayed.
+      // Expand the '__proto__', 'arguments' and 'a' tree nodes. This causes
+      // their properties to be retrieved and displayed.
       localNodes[0].expand();
-      localNodes[1].expand();
+      localNodes[9].expand();
+      localNodes[10].expand();
 
       // Poll every few milliseconds until the properties are retrieved.
       // It's important to set the timer in the chrome window, because the
       // content window timers are disabled while the debuggee is paused.
       let count = 0;
       let intervalID = window.setInterval(function(){
         if (++count > 50) {
           ok(false, "Timed out while polling for the properties.");
           resumeAndFinish();
         }
-        if (!localNodes[0].fetched || !localNodes[1].fetched) {
+        if (!localNodes[0].fetched ||
+            !localNodes[9].fetched ||
+            !localNodes[10].fetched) {
           return;
         }
         window.clearInterval(intervalID);
         is(localNodes[0].querySelector(".property > .title > .key")
                         .textContent, "__proto__ ",
           "Should have the right property name for __proto__.");
 
         ok(localNodes[0].querySelector(".property > .title > .value")
                         .textContent.search(/object/) != -1,
           "__proto__ should be an object.");
 
-        is(localNodes[1].querySelector(".info").textContent, "[object Arguments]",
+        is(localNodes[9].querySelector(".info").textContent, "[object Object]",
+          "Should have the right property value for 'c'.");
+
+        is(localNodes[9].querySelectorAll(".property > .title > .key")[1]
+                        .textContent, "a",
+          "Should have the right property name for 'a'.");
+
+        is(localNodes[9].querySelectorAll(".property > .title > .value")[1]
+                        .textContent, 1,
+          "Should have the right value for 'c.a'.");
+
+        is(localNodes[10].querySelector(".info").textContent,
+          "[object Arguments]",
           "Should have the right property value for 'arguments'.");
 
-        is(localNodes[1].querySelector(".property > .title > .key")
+        is(localNodes[10].querySelector(".property > .title > .key")
                         .textContent, "length",
-          "Should have the right property name for length.");
+          "Should have the right property name for 'length'.");
 
-        is(localNodes[1].querySelector(".property > .title > .value")
+        is(localNodes[10].querySelector(".property > .title > .value")
                         .textContent, 5,
           "Should have the right argument length.");
 
         resumeAndFinish();
       }, 100);
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
-  gDebugger.DebuggerController.activeThread.addOneTimeListener("framescleared", function() {
+  let thread = gDebugger.DebuggerController.activeThread;
+  thread.addOneTimeListener("framescleared", function() {
     Services.tm.currentThread.dispatch({ run: function() {
       var frames = gDebugger.DebuggerView.StackFrames._frames;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
       closeDebuggerAndFinish(gTab);
     }}, 0);
   });
 
-  gDebugger.DebuggerController.activeThread.resume();
+  thread.resume();
 }
 
 registerCleanupFunction(function() {
   removeTab(gTab);
   gPane = null;
   gTab = null;
   gDebugger = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js
+++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js
@@ -3,57 +3,59 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that switching the displayed script in the UI works as advertised.
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
 
-let tempScope = {};
-Cu.import("resource:///modules/source-editor.jsm", tempScope);
-let SourceEditor = tempScope.SourceEditor;
-
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 var gScripts = null;
 
 function test()
 {
   let scriptShown = false;
   let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
+    resumed = true;
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
-      runTest();
+      executeSoon(startTest);
     });
 
-    gDebuggee.firstCall();
+    executeSoon(function() {
+      gDebuggee.firstCall();
+    });
   });
 
-  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
-    let url = aEvent.detail.url;
-    if (url.indexOf("-02.js") != -1) {
-      scriptShown = true;
-      window.removeEventListener(aEvent.type, _onEvent);
-      runTest();
-    }
-  });
+  function onScriptShown(aEvent)
+  {
+    scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
+    executeSoon(startTest);
+  }
 
-  function runTest()
+  window.addEventListener("Debugger:ScriptShown", onScriptShown);
+
+  function startTest()
   {
-    if (scriptShown && framesAdded) {
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      window.removeEventListener("Debugger:ScriptShown", onScriptShown);
+      testStarted = true;
       Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0);
     }
   }
 }
 
 function testScriptsDisplay() {
   gScripts = gDebugger.DebuggerView.Scripts._scripts;
 
@@ -74,18 +76,16 @@ function testScriptsDisplay() {
   ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL +
     label2), "Second script url is incorrect.");
 
   ok(gDebugger.DebuggerView.Scripts.containsLabel(
     label1), "First script label is incorrect.");
   ok(gDebugger.DebuggerView.Scripts.containsLabel(
     label2), "Second script label is incorrect.");
 
-  dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n");
-
   ok(gDebugger.editor.getText().search(/debugger/) != -1,
     "The correct script was loaded initially.");
 
   is(gDebugger.editor.getDebugLocation(), 5,
      "editor debugger location is correct.");
 
   window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
     let url = aEvent.detail.url;
@@ -95,18 +95,16 @@ function testScriptsDisplay() {
     }
   });
 
   gDebugger.DebuggerView.Scripts.selectScript(EXAMPLE_URL + label1);
 }
 
 function testSwitchPaused()
 {
-  dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n");
-
   ok(gDebugger.editor.getText().search(/debugger/) == -1,
     "The second script is no longer displayed.");
 
   ok(gDebugger.editor.getText().search(/firstCall/) != -1,
     "The first script is displayed.");
 
   is(gDebugger.editor.getDebugLocation(), -1,
      "editor debugger location has been cleared.");
@@ -122,16 +120,18 @@ function testSwitchPaused()
 
     gDebugger.DebuggerView.Scripts.selectScript(EXAMPLE_URL +
                                                 "test-script-switching-02.js");
   });
 }
 
 function testSwitchRunning()
 {
+  dump("Debugger editor text:\n" + gDebugger.editor.getText() + "\n");
+
   ok(gDebugger.editor.getText().search(/debugger/) != -1,
     "The second script is displayed again.");
 
   ok(gDebugger.editor.getText().search(/firstCall/) == -1,
     "The first script is no longer displayed.");
 
   is(gDebugger.editor.getDebugLocation(), -1,
      "editor debugger location is still -1.");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
@@ -0,0 +1,188 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+var gEditor = null;
+var gScripts = null;
+var gSearchBox = null;
+var gMenulist = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+
+  debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.debuggerWindow;
+
+    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+      framesAdded = true;
+      runTest();
+    });
+
+    gDebuggee.simpleCall();
+  });
+
+  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
+    window.removeEventListener(aEvent.type, _onEvent);
+    scriptShown = true;
+    runTest();
+  });
+
+  function runTest()
+  {
+    if (scriptShown && framesAdded) {
+      Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
+    }
+  }
+}
+
+function testScriptSearching() {
+  gDebugger.DebuggerController.activeThread.resume(function() {
+    gEditor = gDebugger.DebuggerView.editor;
+    gScripts = gDebugger.DebuggerView.Scripts;
+    gSearchBox = gScripts._searchbox;
+    gMenulist = gScripts._scripts;
+
+    write(":12");
+    ok(gEditor.getCaretPosition().line == 11 &&
+       gEditor.getCaretPosition().col == 0,
+      "The editor didn't jump to the correct line.");
+
+    write("@debugger");
+    ok(gEditor.getCaretPosition().line == 2 &&
+       gEditor.getCaretPosition().col == 44,
+      "The editor didn't jump to the correct token. (1)");
+
+    EventUtils.sendKey("RETURN");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (2)");
+
+    EventUtils.sendKey("ENTER");
+    ok(gEditor.getCaretPosition().line == 12 &&
+       gEditor.getCaretPosition().col == 8,
+      "The editor didn't jump to the correct token. (3)");
+
+    EventUtils.sendKey("ENTER");
+    ok(gEditor.getCaretPosition().line == 19 &&
+       gEditor.getCaretPosition().col == 4,
+      "The editor didn't jump to the correct token. (4)");
+
+    EventUtils.sendKey("RETURN");
+    ok(gEditor.getCaretPosition().line == 2 &&
+       gEditor.getCaretPosition().col == 44,
+      "The editor didn't jump to the correct token. (5)");
+
+
+    write(":bogus@debugger;");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (7)");
+
+    write(":13@debugger;");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (7)");
+
+    write(":@debugger;");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (8)");
+
+    write("::@debugger;");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (9)");
+
+    write(":::@debugger;");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (10)");
+
+
+    write(":i am not a number");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't remain at the correct token. (11)");
+
+    write("@__i do not exist__");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't remain at the correct token. (12)");
+
+
+    write(":1:2:3:a:b:c:::12");
+    ok(gEditor.getCaretPosition().line == 11 &&
+       gEditor.getCaretPosition().col == 0,
+      "The editor didn't jump to the correct line. (13)");
+
+    write("@don't@find@me@instead@find@debugger");
+    ok(gEditor.getCaretPosition().line == 2 &&
+       gEditor.getCaretPosition().col == 44,
+      "The editor didn't jump to the correct token. (14)");
+
+    EventUtils.sendKey("RETURN");
+    ok(gEditor.getCaretPosition().line == 8 &&
+       gEditor.getCaretPosition().col == 2,
+      "The editor didn't jump to the correct token. (15)");
+
+    EventUtils.sendKey("ENTER");
+    ok(gEditor.getCaretPosition().line == 12 &&
+       gEditor.getCaretPosition().col == 8,
+      "The editor didn't jump to the correct token. (16)");
+
+    EventUtils.sendKey("RETURN");
+    ok(gEditor.getCaretPosition().line == 19 &&
+       gEditor.getCaretPosition().col == 4,
+      "The editor didn't jump to the correct token. (17)");
+
+    EventUtils.sendKey("ENTER");
+    ok(gEditor.getCaretPosition().line == 2 &&
+       gEditor.getCaretPosition().col == 44,
+      "The editor didn't jump to the correct token. (18)");
+
+
+    clear();
+    ok(gEditor.getCaretPosition().line == 2 &&
+       gEditor.getCaretPosition().col == 44,
+      "The editor didn't remain at the correct token. (19)");
+    is(gScripts.visibleItemsCount, 1,
+      "Not all the scripts are shown after the search. (20)");
+
+    closeDebuggerAndFinish(gTab);
+  });
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+  dump("editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+  gEditor = null;
+  gScripts = null;
+  gSearchBox = null;
+  gMenulist = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
@@ -0,0 +1,146 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+var gEditor = null;
+var gScripts = null;
+var gSearchBox = null;
+var gMenulist = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.debuggerWindow;
+
+    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+      framesAdded = true;
+      runTest();
+    });
+
+    gDebuggee.firstCall();
+  });
+
+  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
+    let url = aEvent.detail.url;
+    if (url.indexOf("-02.js") != -1) {
+      scriptShown = true;
+      window.removeEventListener(aEvent.type, _onEvent);
+      runTest();
+    }
+  });
+
+  function runTest()
+  {
+    if (scriptShown && framesAdded) {
+      Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
+    }
+  }
+}
+
+function testScriptSearching() {
+  gDebugger.DebuggerController.activeThread.resume(function() {
+    gEditor = gDebugger.DebuggerView.editor;
+    gScripts = gDebugger.DebuggerView.Scripts;
+    gSearchBox = gScripts._searchbox;
+    gMenulist = gScripts._scripts;
+
+    firstSearch();
+  });
+}
+
+function firstSearch() {
+  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
+    dump("Current script url:\n" + aEvent.detail.url + "\n");
+    dump("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    let url = aEvent.detail.url;
+    if (url.indexOf("-01.js") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        dump("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 4 &&
+           gEditor.getCaretPosition().col == 0,
+          "The editor didn't jump to the correct line. (1)");
+        is(gScripts.visibleItemsCount, 1,
+          "Not all the correct scripts are shown after the search. (1)");
+
+        secondSearch();
+      });
+    }
+  });
+  write(".*-01\.js:5");
+}
+
+function secondSearch() {
+  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
+    dump("Current script url:\n" + aEvent.detail.url + "\n");
+    dump("Debugger editor text:\n" + gEditor.getText() + "\n");
+
+    let url = aEvent.detail.url;
+    if (url.indexOf("-02.js") != -1) {
+      window.removeEventListener(aEvent.type, _onEvent);
+
+      executeSoon(function() {
+        dump("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+        ok(gEditor.getCaretPosition().line == 5 &&
+           gEditor.getCaretPosition().col == 8,
+          "The editor didn't jump to the correct line. (2)");
+        is(gScripts.visibleItemsCount, 1,
+          "Not all the correct scripts are shown after the search. (2)");
+
+        finalCheck();
+      });
+    }
+  });
+  write(".*-02\.js@debugger;");
+}
+
+function finalCheck() {
+  clear();
+  ok(gEditor.getCaretPosition().line == 5 &&
+     gEditor.getCaretPosition().col == 8,
+    "The editor didn't remain at the correct token. (3)");
+  is(gScripts.visibleItemsCount, 2,
+    "Not all the scripts are shown after the search. (3)");
+
+  closeDebuggerAndFinish(gTab);
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+  dump("editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+  gEditor = null;
+  gScripts = null;
+  gSearchBox = null;
+  gMenulist = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js
@@ -0,0 +1,124 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test() {
+  debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.debuggerWindow;
+
+    testSimpleCall();
+  });
+}
+
+function testSimpleCall() {
+  gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+    Services.tm.currentThread.dispatch({ run: function() {
+      resumeAndFinish();
+    }}, 0);
+  });
+
+  gDebuggee.simpleCall();
+}
+
+function resumeAndFinish() {
+  gDebugger.DebuggerController.activeThread.resume(function() {
+    checkScriptsOrder();
+    addScriptsAndCheckOrder(1, function() {
+      addScriptsAndCheckOrder(2, function() {
+        addScriptsAndCheckOrder(3, function() {
+          closeDebuggerAndFinish(gTab);
+        });
+      });
+    });
+  });
+}
+
+function addScriptsAndCheckOrder(method, callback) {
+  let vs = gDebugger.DebuggerView.Scripts;
+  let ss = gDebugger.DebuggerController.SourceScripts;
+  vs.empty();
+  vs._scripts.removeEventListener("select", vs._onScriptsChange, false);
+
+  let urls = [
+    { href: "ici://some.address.com/random/", leaf: "subrandom/" },
+    { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
+    { href: "san://interesting.address.gro/random/", leaf: "script.js" },
+    { href: "si://interesting.address.moc/random/", leaf: "script.js" },
+    { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
+    { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
+    { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
+    { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
+  ];
+
+  urls.sort(function(a, b) {
+    return Math.random() - 0.5;
+  });
+
+  switch (method) {
+    case 1:
+      urls.forEach(function(url) {
+        let loc = url.href + url.leaf;
+        vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
+      });
+      vs.commitScripts();
+      break;
+
+    case 2:
+      urls.forEach(function(url) {
+        let loc = url.href + url.leaf;
+        vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true);
+      });
+      break;
+
+    case 3:
+      let i = 0
+      for (; i < urls.length / 2; i++) {
+        let url = urls[i];
+        let loc = url.href + url.leaf;
+        vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
+      }
+      vs.commitScripts();
+
+      for (; i < urls.length; i++) {
+        let url = urls[i];
+        let loc = url.href + url.leaf;
+        vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true);
+      }
+      break;
+  }
+
+  executeSoon(function() {
+    checkScriptsOrder(method);
+    callback();
+  });
+}
+
+function checkScriptsOrder(method) {
+  let labels = gDebugger.DebuggerView.Scripts.scriptLabels;
+  let sorted = labels.reduce(function(prev, curr, index, array) {
+    return array[index - 1] < array[index];
+  });
+
+  ok(sorted,
+    "Using method " + method + ", " +
+    "the scripts weren't in the correct order: " + labels.toSource());
+
+  return sorted;
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
+++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
@@ -17,42 +17,48 @@ var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 var gScripts = null;
 
 function test()
 {
   let scriptShown = false;
   let framesAdded = false;
+  let testStarted = false;
+  let resumed = false;
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
+    resumed = true;
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       framesAdded = true;
-      runTest();
+      executeSoon(startTest);
     });
-    gDebuggee.firstCall();
+
+    executeSoon(function() {
+      gDebuggee.firstCall();
+    });
   });
 
-  window.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
-    let url = aEvent.detail.url;
-    if (url.indexOf("editor-mode") != -1) {
-      scriptShown = true;
-      window.removeEventListener(aEvent.type, _onEvent);
-      runTest();
-    }
-  });
+  function onScriptShown(aEvent) {
+    scriptShown = aEvent.detail.url.indexOf("test-editor-mode") != -1;
+    executeSoon(startTest);
+  }
 
-  function runTest()
+  window.addEventListener("Debugger:ScriptShown", onScriptShown);
+
+  function startTest()
   {
-    if (scriptShown && framesAdded) {
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      window.removeEventListener("Debugger:ScriptShown", onScriptShown);
+      testStarted = true;
       Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0);
     }
   }
 }
 
 function testScriptsDisplay() {
   gScripts = gDebugger.DebuggerView.Scripts._scripts;
 
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -87,22 +87,35 @@ function attach_thread_actor_for_url(aCl
     });
   });
 }
 
 function debug_tab_pane(aURL, aOnDebugging)
 {
   let tab = addTab(aURL, function() {
     gBrowser.selectedTab = gTab;
-
     let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
 
     let pane = DebuggerUI.toggleDebugger();
     pane._frame.addEventListener("Debugger:Connecting", function dbgConnected() {
       pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
 
       // Wait for the initial resume...
       pane.debuggerWindow.gClient.addOneTimeListener("resumed", function() {
         aOnDebugging(tab, debuggee, pane);
       });
     }, true);
   });
 }
+
+function remote_debug_tab_pane(aURL, aOnClosing, aOnDebugging)
+{
+  let tab = addTab(aURL, function() {
+    gBrowser.selectedTab = gTab;
+    let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
+
+    DebuggerUI.toggleRemoteDebugger(aOnClosing, function dbgRan(process) {
+
+      // Wait for the remote debugging process to start...
+      aOnDebugging(tab, debuggee, process);
+    });
+  });
+}
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -393,17 +393,17 @@ InspectorUI.prototype = {
 
   /**
    * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
   get isInspectorOpen()
   {
-    return this.toolbar && !this.toolbar.hidden && this.highlighter;
+    return !!(this.toolbar && !this.toolbar.hidden && this.highlighter);
   },
 
   /**
    * Return the default selection element for the inspected document.
    */
   get defaultSelection()
   {
     let doc = this.win.document;
@@ -642,17 +642,17 @@ InspectorUI.prototype = {
       this.breadcrumbs.destroy();
       this.breadcrumbs = null;
     }
 
     delete this._currentInspector;
     if (!aKeepInspector)
       this.store.deleteInspector(this.winID);
 
-    this.inspectMenuitem.setAttribute("checked", false);
+    this.inspectMenuitem.removeAttribute("checked");
     this.browser = this.win = null; // null out references to browser and window
     this.winID = null;
     this.selection = null;
     this.closing = false;
     this.isDirty = false;
 
     delete this.treePanel;
     delete this.stylePanel;
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -12,8 +12,10 @@ browser.jar:
 *   content/browser/devtools/layoutview/view.xhtml  (layoutview/view.xhtml)
     content/browser/devtools/layoutview/view.css  (layoutview/view.css)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
 *   content/browser/source-editor-overlay.xul     (sourceeditor/source-editor-overlay.xul)
 *   content/browser/debugger.xul                  (debugger/debugger.xul)
     content/browser/debugger.css                  (debugger/debugger.css)
     content/browser/debugger-controller.js        (debugger/debugger-controller.js)
     content/browser/debugger-view.js              (debugger/debugger-view.js)
+    content/browser/devtools/gcli.css             (webconsole/gcli.css)
+    content/browser/devtools/gcliblank.xhtml      (webconsole/gcliblank.xhtml)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -0,0 +1,476 @@
+/* ***** 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 the Firefox Developer Toolbar.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com> (Original Author)
+ *   Joe Walker <jwalker@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 ***** */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let EXPORTED_SYMBOLS = [ "DeveloperToolbar", "loadCommands" ];
+
+XPCOMUtils.defineLazyGetter(this, "gcli", function () {
+  let obj = {};
+  Components.utils.import("resource:///modules/gcli.jsm", obj);
+  return obj.gcli;
+});
+
+let console = gcli._internal.console;
+
+/**
+ * Load the various Command JSMs.
+ * Should be called when the developer toolbar first opens.
+ */
+function loadCommands()
+{
+  Components.utils.import("resource:///modules/GcliCommands.jsm", {});
+  Components.utils.import("resource:///modules/GcliTiltCommands.jsm", {});
+}
+
+
+
+let commandsLoaded = false;
+
+/**
+ * A component to manage the global developer toolbar, which contains a GCLI
+ * and buttons for various developer tools.
+ * @param aChromeWindow The browser window to which this toolbar is attached
+ * @param aToolbarElement See browser.xul:<toolbar id="developer-toolbar">
+ */
+function DeveloperToolbar(aChromeWindow, aToolbarElement)
+{
+  if (!commandsLoaded) {
+    loadCommands();
+    commandsLoaded = true;
+  }
+
+  this._chromeWindow = aChromeWindow;
+
+  this._element = aToolbarElement;
+  this._element.hidden = true;
+  this._doc = this._element.ownerDocument;
+
+  this._command = this._doc.getElementById("Tools:DevToolbar");
+
+  aChromeWindow.getBrowser().tabContainer.addEventListener("TabSelect", this, false);
+}
+
+/**
+ * Inspector notifications dispatched through the nsIObserverService
+ */
+const NOTIFICATIONS = {
+  /** DeveloperToolbar.show() has been called */
+  SHOW: "developer-toolbar-show",
+
+  /** DeveloperToolbar.hide() has been called */
+  HIDE: "developer-toolbar-hide"
+};
+
+/**
+ * Attach notification constants to the object prototype so tests etc can
+ * use them without needing to import anything
+ */
+DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
+
+/**
+ * Is the toolbar open?
+ */
+Object.defineProperty(DeveloperToolbar.prototype, 'visible', {
+  get: function DT_visible() {
+    return !this._element.hidden;
+  },
+  enumerable: true
+});
+
+/**
+ * Called from browser.xul in response to menu-click or keyboard shortcut to
+ * toggle the toolbar
+ */
+DeveloperToolbar.prototype.toggle = function DT_toggle()
+{
+  if (this.visible) {
+    this.hide();
+  } else {
+    this.show();
+    this._input.focus();
+  }
+};
+
+/**
+ * Even if the user has not clicked on 'Got it' in the intro, we only show it
+ * once per session.
+ * Warning this is slightly messed up because this.DeveloperToolbar is not the
+ * same as this.DeveloperToolbar when in browser.js context.
+ */
+DeveloperToolbar.introShownThisSession = false;
+
+/**
+ * Show the developer toolbar
+ */
+DeveloperToolbar.prototype.show = function DT_show()
+{
+  this._command.setAttribute("checked", "true");
+
+  this._input = this._doc.querySelector(".gclitoolbar-input-node");
+
+  this.tooltipPanel = new TooltipPanel(this._doc, this._input);
+  this.outputPanel = new OutputPanel(this._doc, this._input);
+
+  let contentDocument = this._chromeWindow.getBrowser().contentDocument;
+
+  this.display = gcli._internal.createDisplay({
+    contentDocument: contentDocument,
+    chromeDocument: this._doc,
+    chromeWindow: this._chromeWindow,
+
+    hintElement: this.tooltipPanel.hintElement,
+    inputElement: this._input,
+    completeElement: this._doc.querySelector(".gclitoolbar-complete-node"),
+    backgroundElement: this._doc.querySelector(".gclitoolbar-stack-node"),
+    outputDocument: this.outputPanel.document,
+
+    environment: {
+      chromeDocument: this._doc,
+      contentDocument: contentDocument
+    },
+
+    tooltipClass: 'gcliterm-tooltip',
+    eval: null,
+    scratchpad: null
+  });
+
+  this.display.onVisibilityChange.add(this.outputPanel._visibilityChanged, this.outputPanel);
+  this.display.onVisibilityChange.add(this.tooltipPanel._visibilityChanged, this.tooltipPanel);
+  this.display.onOutput.add(this.outputPanel._outputChanged, this.outputPanel);
+
+  this._element.hidden = false;
+  this._notify(NOTIFICATIONS.SHOW);
+
+  if (!DeveloperToolbar.introShownThisSession) {
+    this.display.maybeShowIntro();
+    DeveloperToolbar.introShownThisSession = true;
+  }
+};
+
+/**
+ * Hide the developer toolbar
+ */
+DeveloperToolbar.prototype.hide = function DT_hide()
+{
+  this._command.setAttribute("checked", "false");
+
+  this.display.onVisibilityChange.remove(this.outputPanel._visibilityChanged, this.outputPanel);
+  this.display.onVisibilityChange.remove(this.tooltipPanel._visibilityChanged, this.tooltipPanel);
+  this.display.onOutput.remove(this.outputPanel._outputChanged, this.outputPanel);
+  this.display.destroy();
+
+  // We could "delete this.display" etc if we have hard-to-track-down memory
+  // leaks as a belt-and-braces approach, however this prevents our DOM node
+  // hunter from looking in all the nooks and crannies, so it's better if we
+  // can be leak-free without
+
+  this.outputPanel.remove();
+  delete this.outputPanel;
+
+  this.tooltipPanel.remove();
+  delete this.tooltipPanel;
+
+  this._element.hidden = true;
+  this._notify(NOTIFICATIONS.HIDE);
+};
+
+/**
+ * Utility for sending notifications
+ * @param aTopic a NOTIFICATION constant
+ */
+DeveloperToolbar.prototype._notify = function DT_notify(aTopic)
+{
+  let data = { toolbar: this };
+  data.wrappedJSObject = data;
+  Services.obs.notifyObservers(data, aTopic, null);
+};
+
+/**
+ * Update various parts of the UI when the current tab changes
+ * @param aEvent
+ */
+DeveloperToolbar.prototype.handleEvent = function DT_handleEvent(aEvent)
+{
+  if (aEvent.type == "TabSelect") {
+    this._chromeWindow.HUDConsoleUI.refreshCommand();
+    this._chromeWindow.DebuggerUI.refreshCommand();
+
+    if (this.visible) {
+      let contentDocument = this._chromeWindow.getBrowser().contentDocument;
+
+      this.display.reattach({
+        contentDocument: contentDocument,
+        chromeWindow: this._chromeWindow,
+        environment: {
+          chromeDocument: this._doc,
+          contentDocument: contentDocument
+        },
+      });
+    }
+  }
+};
+
+/**
+ * Add class="gcli-panel-inner-arrowcontent" to a panel's
+ * |<xul:box class="panel-inner-arrowcontent">| so we can alter the styling
+ * without complex CSS expressions.
+ * @param aPanel The panel to affect
+ */
+function getContentBox(aPanel)
+{
+  let container = aPanel.ownerDocument.getAnonymousElementByAttribute(
+          aPanel, "anonid", "container");
+  return container.querySelector(".panel-inner-arrowcontent");
+}
+
+/**
+ * Helper function to calculate the sum of the vertical padding and margins
+ * between a nested node |aNode| and an ancestor |aRoot|. Iff all of the
+ * children of aRoot are 'only-childs' until you get to aNode then to avoid
+ * scroll-bars, the 'correct' height of aRoot is verticalSpacing + aNode.height.
+ * @param aNode The child node whose height is known.
+ * @param aRoot The parent height whose height we can affect.
+ * @return The sum of the vertical padding/margins in between aNode and aRoot.
+ */
+function getVerticalSpacing(aNode, aRoot)
+{
+  let win = aNode.ownerDocument.defaultView;
+
+  function pxToNum(styles, property) {
+    return parseInt(styles.getPropertyValue(property).replace(/px$/, ''), 10);
+  }
+
+  let vertSpacing = 0;
+  do {
+    let styles = win.getComputedStyle(aNode);
+    vertSpacing += pxToNum(styles, "padding-top");
+    vertSpacing += pxToNum(styles, "padding-bottom");
+    vertSpacing += pxToNum(styles, "margin-top");
+    vertSpacing += pxToNum(styles, "margin-bottom");
+    vertSpacing += pxToNum(styles, "border-top-width");
+    vertSpacing += pxToNum(styles, "border-bottom-width");
+
+    let prev = aNode.previousSibling;
+    while (prev != null) {
+      vertSpacing += prev.clientHeight;
+      prev = prev.previousSibling;
+    }
+
+    let next = aNode.nextSibling;
+    while (next != null) {
+      vertSpacing += next.clientHeight;
+      next = next.nextSibling;
+    }
+
+    aNode = aNode.parentNode;
+  } while (aNode !== aRoot);
+
+  return vertSpacing + 9;
+}
+
+/**
+ * Panel to handle command line output.
+ * @param aChromeDoc document from which we can pull the parts we need.
+ * @param aInput the input element that should get focus.
+ */
+function OutputPanel(aChromeDoc, aInput)
+{
+  this._input = aInput;
+  this._panel = aChromeDoc.getElementById("gcli-output");
+  this._frame = aChromeDoc.getElementById("gcli-output-frame");
+  this._anchor = aChromeDoc.getElementById("developer-toolbar");
+
+  this._content = getContentBox(this._panel);
+  this._content.classList.add("gcli-panel-inner-arrowcontent");
+
+  this.document = this._frame.contentDocument;
+  this.document.body.classList.add("gclichrome-output");
+
+  this._div = this.document.querySelector("div");
+  this._div.classList.add('gcli-row-out');
+  this._div.setAttribute('aria-live', 'assertive');
+
+  this.displayedOutput = undefined;
+}
+
+/**
+ * Display the OutputPanel.
+ */
+OutputPanel.prototype.show = function OP_show()
+{
+  this._panel.ownerDocument.defaultView.setTimeout(function() {
+    this._resize();
+  }.bind(this), 0);
+
+  this._resize();
+  this._panel.openPopup(this._anchor, "before_end", -300, 0, false, false, null);
+
+  this._input.focus();
+};
+
+/**
+ * Internal helper to set the height of the output panel to fit the available
+ * content;
+ */
+OutputPanel.prototype._resize = function CLP_resize()
+{
+  let vertSpacing = getVerticalSpacing(this._content, this._panel);
+  let idealHeight = this.document.body.scrollHeight + vertSpacing;
+  this._panel.sizeTo(400, Math.min(idealHeight, 500));
+};
+
+/**
+ * Called by GCLI when a command is executed.
+ */
+OutputPanel.prototype._outputChanged = function OP_outputChanged(aEvent)
+{
+  if (aEvent.output.hidden) {
+    return;
+  }
+
+  this.remove();
+
+  this.displayedOutput = aEvent.output;
+  this.update();
+
+  this.displayedOutput.onChange.add(this.update, this);
+  this.displayedOutput.onClose.add(this.remove, this);
+};
+
+/**
+ * Called when displayed Output says it's changed or from outputChanged, which
+ * happens when there is a new displayed Output.
+ */
+OutputPanel.prototype.update = function OP_update()
+{
+  if (this.displayedOutput.data == null) {
+    while (this._div.hasChildNodes()) {
+      this._div.removeChild(this._div.firstChild);
+    }
+  } else {
+    this.displayedOutput.toDom(this._div);
+    this.show();
+  }
+};
+
+/**
+ * Detach listeners from the currently displayed Output.
+ */
+OutputPanel.prototype.remove = function OP_remove()
+{
+  this._panel.hidePopup();
+
+  if (this.displayedOutput) {
+    this.displayedOutput.onChange.remove(this.update, this);
+    this.displayedOutput.onClose.remove(this.remove, this);
+    delete this.displayedOutput;
+  }
+};
+
+/**
+ * Called by GCLI to indicate that we should show or hide one either the
+ * tooltip panel or the output panel.
+ */
+OutputPanel.prototype._visibilityChanged = function OP_visibilityChanged(aEvent)
+{
+  if (aEvent.outputVisible === true) {
+    // this.show is called by _outputChanged
+  } else {
+    this._panel.hidePopup();
+  }
+};
+
+
+/**
+ * Panel to handle tooltips.
+ * @param aChromeDoc document from which we can pull the parts we need.
+ * @param aInput the input element that should get focus.
+ */
+function TooltipPanel(aChromeDoc, aInput)
+{
+  this._input = aInput;
+  this._panel = aChromeDoc.getElementById("gcli-tooltip");
+  this._frame = aChromeDoc.getElementById("gcli-tooltip-frame");
+  this._anchor = aChromeDoc.getElementById("developer-toolbar");
+
+  this._content = getContentBox(this._panel);
+  this._content.classList.add("gcli-panel-inner-arrowcontent");
+
+  this.document = this._frame.contentDocument;
+  this.document.body.classList.add("gclichrome-tooltip");
+
+  this.hintElement = this.document.querySelector("div");
+}
+
+/**
+ * Display the TooltipPanel.
+ */
+TooltipPanel.prototype.show = function TP_show()
+{
+  let vertSpacing = getVerticalSpacing(this._content, this._panel);
+  let idealHeight = this.document.body.scrollHeight + vertSpacing;
+  this._panel.sizeTo(350, Math.min(idealHeight, 500));
+  this._panel.openPopup(this._anchor, "before_start", 0, 0, false, false, null);
+
+  this._input.focus();
+};
+
+/**
+ * Hide the TooltipPanel.
+ */
+TooltipPanel.prototype.remove = function TP_remove()
+{
+  this._panel.hidePopup();
+};
+
+/**
+ * Called by GCLI to indicate that we should show or hide one either the
+ * tooltip panel or the output panel.
+ */
+TooltipPanel.prototype._visibilityChanged = function TP_visibilityChanged(aEvent)
+{
+  if (aEvent.tooltipVisible === true) {
+    this.show();
+  } else {
+    this._panel.hidePopup();
+  }
+};
--- a/browser/devtools/shared/Templater.jsm
+++ b/browser/devtools/shared/Templater.jsm
@@ -37,43 +37,64 @@
  * ***** END LICENSE BLOCK ***** */
 
 
 var EXPORTED_SYMBOLS = [ "Templater", "template" ];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 const Node = Components.interfaces.nsIDOMNode;
 
+/**
+ * For full documentation, see:
+ * https://github.com/mozilla/domtemplate/blob/master/README.md
+ */
+
 // WARNING: do not 'use_strict' without reading the notes in _envEval();
 
 /**
  * Begin a new templating process.
  * @param node A DOM element or string referring to an element's id
  * @param data Data to use in filling out the template
  * @param options Options to customize the template processing. One of:
  * - allowEval: boolean (default false) Basic template interpolations are
- * either property paths (e.g. ${a.b.c.d}), however if allowEval=true then we
- * allow arbitrary JavaScript
+ *   either property paths (e.g. ${a.b.c.d}), or if allowEval=true then we
+ *   allow arbitrary JavaScript
+ * - stack: string or array of strings (default empty array) The template
+ *   engine maintains a stack of tasks to help debug where it is. This allows
+ *   this stack to be prefixed with a template name
+ * - blankNullUndefined: By default DOMTemplate exports null and undefined
+ *   values using the strings 'null' and 'undefined', which can be helpful for
+ *   debugging, but can introduce unnecessary extra logic in a template to
+ *   convert null/undefined to ''. By setting blankNullUndefined:true, this
+ *   conversion is handled by DOMTemplate
  */
 function template(node, data, options) {
   var template = new Templater(options || {});
   template.processNode(node, data);
   return template;
 }
 
 /**
  * Construct a Templater object. Use template() in preference to this ctor.
  * @deprecated Use template(node, data, options);
  */
 function Templater(options) {
   if (options == null) {
     options = { allowEval: true };
   }
   this.options = options;
-  this.stack = [];
+  if (options.stack && Array.isArray(options.stack)) {
+    this.stack = options.stack;
+  }
+  else if (typeof options.stack === 'string') {
+    this.stack = [ options.stack ];
+  }
+  else {
+    this.stack = [];
+  }
 }
 
 /**
  * Cached regex used to find ${...} sections in some text.
  * Performance note: This regex uses ( and ) to capture the 'script' for
  * further processing. Not all of the uses of this regex use this feature so
  * if use of the capturing group is a performance drain then we should split
  * this regex in two.
@@ -85,17 +106,17 @@ Templater.prototype._templateRegion = /\
  * See Templater._processTextNode() for details.
  */
 Templater.prototype._splitSpecial = /\uF001|\uF002/;
 
 /**
  * Cached regex used to detect if a script is capable of being interpreted
  * using Template._property() or if we need to use Template._envEval()
  */
-Templater.prototype._isPropertyScript = /^[a-zA-Z0-9.]*$/;
+Templater.prototype._isPropertyScript = /^[_a-zA-Z0-9.]*$/;
 
 /**
  * Recursive function to walk the tree processing the attributes as it goes.
  * @param node the node to process. If you pass a string in instead of a DOM
  * element, it is assumed to be an id for use with document.getElementById()
  * @param data the data to use for node processing.
  */
 Templater.prototype.processNode = function(node, data) {
@@ -148,17 +169,21 @@ Templater.prototype.processNode = functi
             var capture = node.hasAttribute('capture' + name.substring(2));
             node.addEventListener(name.substring(2), func, capture);
             if (capture) {
               node.removeAttribute('capture' + name.substring(2));
             }
           } else {
             // Replace references in all other attributes
             var newValue = value.replace(this._templateRegion, function(path) {
-              return this._envEval(path.slice(2, -1), data, value);
+              var insert = this._envEval(path.slice(2, -1), data, value);
+              if (this.options.blankNullUndefined && insert == null) {
+                insert = '';
+              }
+              return insert;
             }.bind(this));
             // Remove '_' prefix of attribute names so the DOM won't try
             // to use them before we've processed the template
             if (name.charAt(0) === '_') {
               node.removeAttribute(name);
               node.setAttribute(name.substring(1), newValue);
             } else if (value !== newValue) {
               attrs[i].value = newValue;
@@ -172,17 +197,17 @@ Templater.prototype.processNode = functi
 
     // Loop through our children calling processNode. First clone them, so the
     // set of nodes that we visit will be unaffected by additions or removals.
     var childNodes = Array.prototype.slice.call(node.childNodes);
     for (var j = 0; j < childNodes.length; j++) {
       this.processNode(childNodes[j], data);
     }
 
-    if (node.nodeType === Node.TEXT_NODE) {
+    if (node.nodeType === 3 /*Node.TEXT_NODE*/) {
       this._processTextNode(node, data);
     }
   } finally {
     delete data.__element;
     this.stack.pop();
   }
 };
 
@@ -342,40 +367,51 @@ Templater.prototype._processTextNode = f
     parts.forEach(function(part) {
       if (part === null || part === undefined || part === '') {
         return;
       }
       if (part.charAt(0) === '$') {
         part = this._envEval(part.slice(1), data, node.data);
       }
       this._handleAsync(part, node, function(reply, siblingNode) {
-        reply = this._toNode(reply, siblingNode.ownerDocument);
-        siblingNode.parentNode.insertBefore(reply, siblingNode);
+        var doc = siblingNode.ownerDocument;
+        if (reply == null) {
+          reply = this.options.blankNullUndefined ? '' : '' + reply;
+        }
+        if (typeof reply.cloneNode === 'function') {
+          // i.e. if (reply instanceof Element) { ...
+          reply = this._maybeImportNode(reply, doc);
+          siblingNode.parentNode.insertBefore(reply, siblingNode);
+        } else if (typeof reply.item === 'function' && reply.length) {
+          // if thing is a NodeList, then import the children
+          for (var i = 0; i < reply.length; i++) {
+            var child = this._maybeImportNode(reply.item(i), doc);
+            siblingNode.parentNode.insertBefore(child, siblingNode);
+          }
+        }
+        else {
+          // if thing isn't a DOM element then wrap its string value in one
+          reply = doc.createTextNode(reply.toString());
+          siblingNode.parentNode.insertBefore(reply, siblingNode);
+        }
+
       }.bind(this));
     }, this);
     node.parentNode.removeChild(node);
   }
 };
 
 /**
- * Helper to convert a 'thing' to a DOM Node.
- * This is (obviously) a no-op for DOM Elements (which are detected using
- * 'typeof thing.cloneNode !== "function"' (is there a better way that will
- * work in all environments, including a .jsm?)
- * Non DOM elements are converted to a string and wrapped in a TextNode.
+ * Return node or a import of node, if it's not in the given document
+ * @param node The node that we want to be properly owned
+ * @param doc The document that the given node should belong to
+ * @return A node that belongs to the given document
  */
-Templater.prototype._toNode = function(thing, document) {
-  if (thing == null) {
-    thing = '' + thing;
-  }
-  // if thing isn't a DOM element then wrap its string value in one
-  if (typeof thing.cloneNode !== 'function') {
-    thing = document.createTextNode(thing.toString());
-  }
-  return thing;
+Templater.prototype._maybeImportNode = function(node, doc) {
+  return node.ownerDocument === doc ? node : doc.importNode(node, true);
 };
 
 /**
  * A function to handle the fact that some nodes can be promises, so we check
  * and resolve if needed using a marker node to keep our place before calling
  * an inserter function.
  * @param thing The object which could be real data or a promise of real data
  * we use it directly if it's not a promise, or resolve it if it is.
@@ -424,38 +460,38 @@ Templater.prototype._stripBraces = funct
  * a string to be cut into an array using <tt>split('.')</tt>
  * @param data the data to use for node processing
  * @param newValue (optional) If defined, this value will replace the
  * original value for the data at the path specified.
  * @return The value pointed to by <tt>path</tt> before any
  * <tt>newValue</tt> is applied.
  */
 Templater.prototype._property = function(path, data, newValue) {
-  this.stack.push(path);
   try {
     if (typeof path === 'string') {
       path = path.split('.');
     }
     var value = data[path[0]];
     if (path.length === 1) {
       if (newValue !== undefined) {
         data[path[0]] = newValue;
       }
       if (typeof value === 'function') {
         return value.bind(data);
       }
       return value;
     }
     if (!value) {
-      this._handleError('Can\'t find path=' + path);
+      this._handleError('"' + path[0] + '" is undefined');
       return null;
     }
     return this._property(path.slice(1), value, newValue);
-  } finally {
-    this.stack.pop();
+  } catch (ex) {
+    this._handleError('Path error with \'' + path + '\'', ex);
+    return '${' + path + '}';
   }
 };
 
 /**
  * Like eval, but that creates a context of the variables in <tt>env</tt> in
  * which the script is evaluated.
  * WARNING: This script uses 'with' which is generally regarded to be evil.
  * The alternative is to create a Function at runtime that takes X parameters
@@ -464,47 +500,45 @@ Templater.prototype._property = function
  * @param script The string to be evaluated.
  * @param data The environment in which to eval the script.
  * @param frame Optional debugging string in case of failure.
  * @return The return value of the script, or the error message if the script
  * execution failed.
  */
 Templater.prototype._envEval = function(script, data, frame) {
   try {
-    this.stack.push(frame);
+    this.stack.push(frame.replace(/\s+/g, ' '));
     if (this._isPropertyScript.test(script)) {
       return this._property(script, data);
     } else {
       if (!this.options.allowEval) {
         this._handleError('allowEval is not set, however \'' + script + '\'' +
             ' can not be resolved using a simple property path.');
         return '${' + script + '}';
       }
       with (data) {
         return eval(script);
       }
     }
   } catch (ex) {
-    this._handleError('Template error evaluating \'' + script + '\'' +
-        ' environment=' + Object.keys(data).join(', '), ex);
+    this._handleError('Template error evaluating \'' + script + '\'', ex);
     return '${' + script + '}';
   } finally {
     this.stack.pop();
   }
 };
 
 /**
  * A generic way of reporting errors, for easy overloading in different
  * environments.
  * @param message the error message to report.
  * @param ex optional associated exception.
  */
 Templater.prototype._handleError = function(message, ex) {
-  this._logError(message);
-  this._logError('In: ' + this.stack.join(' > '));
+  this._logError(message + ' (In: ' + this.stack.join(' > ') + ')');
   if (ex) {
     this._logError(ex);
   }
 };
 
 
 /**
  * A generic way of reporting errors, for easy overloading in different
--- a/browser/devtools/shared/test/Makefile.in
+++ b/browser/devtools/shared/test/Makefile.in
@@ -44,20 +44,30 @@ VPATH     = @srcdir@
 relativesrcdir  = browser/devtools/shared/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
   browser_promise_basic.js \
   browser_templater_basic.js \
+  browser_toolbar_basic.js \
+  browser_gcli_commands.js \
+  browser_gcli_inspect.js \
+  browser_gcli_integrate.js \
+  browser_gcli_require.js \
+  browser_gcli_web.js \
+  browser_gcli_break.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_templater_basic.html \
+  browser_toolbar_basic.html \
+  browser_gcli_inspect.html \
+  browser_gcli_break.html \
   $(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)
rename from browser/devtools/webconsole/test/browser_gcli_break.html
rename to browser/devtools/shared/test/browser_gcli_break.html
--- a/browser/devtools/webconsole/test/browser_gcli_break.html
+++ b/browser/devtools/shared/test/browser_gcli_break.html
@@ -1,19 +1,19 @@
 <!DOCTYPE HTML>
 <html>
-	<head>
+  <head>
     <meta charset="utf-8">
-		<title>Browser GCLI break command test</title>
+    <title>Browser GCLI break command test</title>
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript">
       function firstCall() {
         eval("window.line0 = Error().lineNumber; secondCall();");
       }
       function secondCall() {
         eval("debugger;");
       }
     </script>
-	</head>
-	<body>
-	</body>
+  </head>
+  <body>
+  </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_gcli_break.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the break command works as it should
+
+const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_gcli_break.html";
+
+function test() {
+  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
+    testBreakCommands();
+  });
+}
+
+function testBreakCommands() {
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "brea",
+    directTabText: "k",
+    status: "ERROR"
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "break",
+    status: "ERROR"
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "break add",
+    status: "ERROR"
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "break add line",
+    emptyParameters: [ " <file>", " <line>" ],
+    status: "ERROR"
+  });
+
+  let pane = DebuggerUI.toggleDebugger();
+  pane._frame.addEventListener("Debugger:Connecting", function dbgConnected() {
+    pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
+
+    // Wait for the initial resume.
+    let client = pane.debuggerWindow.gClient;
+    client.addOneTimeListener("resumed", function() {
+      client.activeThread.addOneTimeListener("framesadded", function() {
+        DeveloperToolbarTest.checkInputStatus({
+          typed: "break add line " + TEST_URI + " " + content.wrappedJSObject.line0,
+          status: "VALID"
+        });
+        DeveloperToolbarTest.exec({
+          args: {
+            type: 'line',
+            file: TEST_URI,
+            line: content.wrappedJSObject.line0
+          },
+          completed: false
+        });
+
+        DeveloperToolbarTest.checkInputStatus({
+          typed: "break list",
+          status: "VALID"
+        });
+        DeveloperToolbarTest.exec();
+
+        client.activeThread.resume(function() {
+          DeveloperToolbarTest.checkInputStatus({
+            typed: "break del 0",
+            status: "VALID"
+          });
+          DeveloperToolbarTest.exec({
+            args: { breakid: 0 },
+            completed: false
+          });
+
+          finish();
+        });
+      });
+
+      // Trigger newScript notifications using eval.
+      content.wrappedJSObject.firstCall();
+    });
+  }, true);
+}
rename from browser/devtools/webconsole/test/browser_gcli_commands.js
rename to browser/devtools/shared/test/browser_gcli_commands.js
--- a/browser/devtools/webconsole/test/browser_gcli_commands.js
+++ b/browser/devtools/shared/test/browser_gcli_commands.js
@@ -1,80 +1,55 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// For more information on GCLI see:
-// - https://github.com/mozilla/gcli/blob/master/docs/index.md
-// - https://wiki.mozilla.org/DevTools/Features/GCLI
-
-let tmp = {};
-Components.utils.import("resource:///modules/gcli.jsm", tmp);
-let gcli = tmp.gcli;
+// Test various GCLI commands
 
-let hud;
-let gcliterm;
+let imported = {};
+Components.utils.import("resource:///modules/HUDService.jsm", imported);
 
-registerCleanupFunction(function() {
-  gcliterm = undefined;
-  hud = undefined;
-  Services.prefs.clearUserPref("devtools.gcli.enable");
-});
+const TEST_URI = "data:text/html;charset=utf-8,gcli-commands";
 
 function test() {
-  Services.prefs.setBoolPref("devtools.gcli.enable", true);
-  addTab("http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_inspect.html");
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
-}
-
-function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
+  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
+    testEcho();
+    testConsoleClear();
+    testConsoleOpenClose(tab);
 
-  openConsole();
-
-  hud = HUDService.getHudByWindow(content);
-  gcliterm = hud.gcliterm;
-
-  testEcho();
-
-  // gcli._internal.console.error("Command Tests Completed");
+    imported = undefined;
+    finish();
+  });
 }
 
 function testEcho() {
-  let nodes = exec("echo message");
-  is(nodes.length, 2, "after echo");
-  is(nodes[0].textContent, "echo message", "output 0");
-  is(nodes[1].textContent.trim(), "message", "output 1");
-
-  testConsoleClear();
+  DeveloperToolbarTest.exec({
+    typed: "echo message",
+    args: { message: "message" },
+    outputMatch: /^message$/,
+  });
 }
 
 function testConsoleClear() {
-  let nodes = exec("console clear");
-  is(nodes.length, 1, "after console clear 1");
-
-  executeSoon(function() {
-    let nodes = hud.outputNode.querySelectorAll("richlistitem");
-    is(nodes.length, 0, "after console clear 2");
-
-    testConsoleClose();
+  DeveloperToolbarTest.exec({
+    typed: "console clear",
+    args: {},
+    blankOutput: true,
   });
 }
 
-function testConsoleClose() {
-  ok(hud.hudId in HUDService.hudReferences, "console open");
-
-  exec("console close");
-
-  ok(!(hud.hudId in HUDService.hudReferences), "console closed");
-
-  finishTest();
-}
+function testConsoleOpenClose(tab) {
+  DeveloperToolbarTest.exec({
+    typed: "console open",
+    args: {},
+    blankOutput: true,
+  });
 
-function exec(command) {
-  gcliterm.clearOutput();
-  let nodes = hud.outputNode.querySelectorAll("richlistitem");
-  is(nodes.length, 0, "setup - " + command);
+  let hud = imported.HUDService.getHudByWindow(content);
+  ok(hud.hudId in imported.HUDService.hudReferences, "console open");
 
-  gcliterm.opts.console.inputter.setInput(command);
-  gcliterm.opts.requisition.exec();
+  DeveloperToolbarTest.exec({
+    typed: "console close",
+    args: {},
+    blankOutput: true,
+  });
 
-  return hud.outputNode.querySelectorAll("richlistitem");
+  ok(!(hud.hudId in imported.HUDService.hudReferences), "console closed");
 }
rename from browser/devtools/webconsole/test/browser_gcli_inspect.html
rename to browser/devtools/shared/test/browser_gcli_inspect.html
rename from browser/devtools/webconsole/test/browser_gcli_inspect.js
rename to browser/devtools/shared/test/browser_gcli_inspect.js
--- a/browser/devtools/webconsole/test/browser_gcli_inspect.js
+++ b/browser/devtools/shared/test/browser_gcli_inspect.js
@@ -1,95 +1,68 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// For more information on GCLI see:
-// - https://github.com/mozilla/gcli/blob/master/docs/index.md
-// - https://wiki.mozilla.org/DevTools/Features/GCLI
-
 // Tests that the inspect command works as it should
 
-let tempScope = {};
-Components.utils.import("resource:///modules/gcli.jsm", tempScope);
-let gcli = tempScope.gcli;
-
-registerCleanupFunction(function() {
-  gcliterm = undefined;
-  requisition = undefined;
-
-  Services.prefs.clearUserPref("devtools.gcli.enable");
-});
+const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_gcli_inspect.html";
 
 function test() {
-  Services.prefs.setBoolPref("devtools.gcli.enable", true);
-  addTab("http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_inspect.html");
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
-}
-
-let gcliterm;
-let requisition;
-
-function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
-
-  try {
-    openConsole();
+  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
+    testInspect();
 
-    let hud = HUDService.getHudByWindow(content);
-    gcliterm = hud.gcliterm;
-    requisition = gcliterm.opts.requisition;
-
-    testSetup();
-    testCreateCommands();
-  }
-  catch (ex) {
-    ok(false, "Caught exception: " + ex)
-    gcli._internal.console.error("Test Failure", ex);
-  }
-  finally {
-    closeConsole();
-    finishTest();
-  }
+    finish();
+  });
 }
 
-function testSetup() {
-  ok(gcliterm, "We have a GCLI term");
-  ok(requisition, "We have a Requisition");
-}
-
-function testCreateCommands() {
-  type("inspec");
-  is(gcliterm.completeNode.textContent, " inspect", "Completion for \"inspec\"");
-  is(requisition.getStatus().toString(), "ERROR", "inspec is ERROR");
+function testInspect() {
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspec",
+    directTabText: "t",
+    status: "ERROR"
+  });
 
-  type("inspect");
-  is(requisition.getStatus().toString(), "ERROR", "inspect is ERROR");
-
-  type("inspect h1");
-  is(requisition.getStatus().toString(), "ERROR", "inspect h1 is ERROR");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect",
+    emptyParameters: [ " <node>" ],
+    status: "ERROR"
+  });
 
-  type("inspect span");
-  is(requisition.getStatus().toString(), "ERROR", "inspect span is ERROR");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect h1",
+    status: "ERROR"
+  });
 
-  type("inspect div");
-  is(requisition.getStatus().toString(), "VALID", "inspect div is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect span",
+    status: "ERROR"
+  });
 
-  type("inspect .someclass");
-  is(requisition.getStatus().toString(), "VALID", "inspect .someclass is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect div",
+    status: "VALID"
+  });
 
-  type("inspect #someid");
-  is(requisition.getStatus().toString(), "VALID", "inspect #someid is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect .someclass",
+    status: "VALID"
+  });
 
-  type("inspect button[disabled]");
-  is(requisition.getStatus().toString(), "VALID", "inspect button[disabled] is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect #someid",
+    status: "VALID"
+  });
 
-  type("inspect p>strong");
-  is(requisition.getStatus().toString(), "VALID", "inspect p>strong is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect button[disabled]",
+    status: "VALID"
+  });
 
-  type("inspect :root");
-  is(requisition.getStatus().toString(), "VALID", "inspect :root is VALID");
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect p>strong",
+    status: "VALID"
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "inspect :root",
+    status: "VALID"
+  });
 }
-
-function type(command) {
-  gcliterm.inputNode.value = command.slice(0, -1);
-  gcliterm.inputNode.focus();
-  EventUtils.synthesizeKey(command.slice(-1), {});
-}
rename from browser/devtools/webconsole/test/browser_gcli_integrate.js
rename to browser/devtools/shared/test/browser_gcli_integrate.js
--- a/browser/devtools/webconsole/test/browser_gcli_integrate.js
+++ b/browser/devtools/shared/test/browser_gcli_integrate.js
@@ -1,106 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// For more information on GCLI see:
-// - https://github.com/mozilla/gcli/blob/master/docs/index.md
-// - https://wiki.mozilla.org/DevTools/Features/GCLI
-
 // Tests that source URLs in the Web Console can be clicked to display the
 // standard View Source window.
 
-let tempScope = {};
-Components.utils.import("resource:///modules/gcli.jsm", tempScope);
-let gcli = tempScope.gcli;
-let require = gcli._internal.require;
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-
-registerCleanupFunction(function() {
-  require = undefined;
-  Services.prefs.clearUserPref("devtools.gcli.enable");
-});
-
 function test() {
-  Services.prefs.setBoolPref("devtools.gcli.enable", true);
-  addTab(TEST_URI);
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
-}
-
-function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
-
-  try {
-    openConsole();
-
-    testCreateCommands();
-    testCallCommands();
-    testRemoveCommands();
-  }
-  catch (ex) {
-    gcli._internal.console.error('Test Failure', ex);
-    ok(false, '' + ex);
-  }
-  finally {
-    closeConsole();
-    finishTest();
-  }
+  testCreateCommands();
+  testRemoveCommands();
 }
 
 let tselarr = {
   name: 'tselarr',
   params: [
     { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } },
     { name: 'arr', type: { name: 'array', subtype: 'string' } },
   ],
   exec: function(args, env) {
     return "flu " + args.num + "-" + args.arr.join("_");
   }
 };
 
 function testCreateCommands() {
-  let gcli = require("gcli/index");
-  gcli.addCommand(tselarr);
+  let gcliIndex = require("gcli/index");
+  gcliIndex.addCommand(tselarr);
 
   let canon = require("gcli/canon");
   let tselcmd = canon.getCommand("tselarr");
   ok(tselcmd != null, "tselarr exists in the canon");
   ok(tselcmd instanceof canon.Command, "canon storing commands");
 }
 
-function testCallCommands() {
-  let hud = HUDService.getHudByWindow(content);
-  let gcliterm = hud.gcliterm;
-  ok(gcliterm, "We have a GCLI term");
-
-  // Test successful auto-completion
-  gcliterm.inputNode.value = "h";
-  gcliterm.inputNode.focus();
-  EventUtils.synthesizeKey("e", {});
-  is(gcliterm.completeNode.textContent, " help", "Completion for \"he\"");
-
-  // Test unsuccessful auto-completion
-  gcliterm.inputNode.value = "ec";
-  gcliterm.inputNode.focus();
-  EventUtils.synthesizeKey("d", {});
-  is(gcliterm.completeNode.textContent, " ecd", "Completion for \"ecd\"");
-
-  // Test a normal command's life cycle
-  gcliterm.opts.console.inputter.setInput("echo hello world");
-  gcliterm.opts.requisition.exec();
-
-  let nodes = hud.outputNode.querySelectorAll(".gcliterm-msg-body");
-
-  is(nodes.length, 1, "Right number of output nodes");
-  ok(/hello world/.test(nodes[0].textContent), "the command's output is correct.");
-
-  gcliterm.clearOutput();
-}
-
 function testRemoveCommands() {
-  let gcli = require("gcli/index");
-  gcli.removeCommand(tselarr);
+  let gcliIndex = require("gcli/index");
+  gcliIndex.removeCommand(tselarr);
 
   let canon = require("gcli/canon");
   let tselcmd = canon.getCommand("tselarr");
   ok(tselcmd == null, "tselcmd removed from the canon");
 }
rename from browser/devtools/webconsole/test/browser_gcli_require.js
rename to browser/devtools/shared/test/browser_gcli_require.js
--- a/browser/devtools/webconsole/test/browser_gcli_require.js
+++ b/browser/devtools/shared/test/browser_gcli_require.js
@@ -1,30 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// For more information on GCLI see:
-// - https://github.com/mozilla/gcli/blob/master/docs/index.md
-// - https://wiki.mozilla.org/DevTools/Features/GCLI
-
 // Tests that source URLs in the Web Console can be clicked to display the
 // standard View Source window.
 
-var modules = { gcli: null };
-
-Components.utils.import("resource:///modules/gcli.jsm", modules);
-
-var define, require, console;
-
 function test() {
-
-  define = modules.gcli._internal.define;
-  require = modules.gcli._internal.require;
-  console = modules.gcli._internal.console;
-
   define('gclitest/requirable', [], function(require, exports, module) {
     exports.thing1 = 'thing1';
     exports.thing2 = 2;
 
     let status = 'initial';
     exports.setStatus = function(aStatus) { status = aStatus; };
     exports.getStatus = function() { return status; };
   });
@@ -38,30 +23,24 @@ function test() {
   });
 
   testWorking();
   testLeakage();
   testMultiImport();
   testRecursive();
   testUncompilable();
 
-  finishTest();
-
   delete define.modules['gclitest/requirable'];
   delete define.globalDomain.modules['gclitest/requirable'];
   delete define.modules['gclitest/unrequirable'];
   delete define.globalDomain.modules['gclitest/unrequirable'];
   delete define.modules['gclitest/recurse'];
   delete define.globalDomain.modules['gclitest/recurse'];
 
-  define = null;
-  require = null;
-  console = null;
-
-  modules = null;
+  finish();
 }
 
 function testWorking() {
   // There are lots of requirement tests that we could be doing here
   // The fact that we can get anything at all working is a testament to
   // require doing what it should - we don't need to test the
   let requireable = require('gclitest/requirable');
   ok('thing1' == requireable.thing1, 'thing1 was required');
rename from browser/devtools/webconsole/test/browser_gcli_web.js
rename to browser/devtools/shared/test/browser_gcli_web.js
--- a/browser/devtools/webconsole/test/browser_gcli_web.js
+++ b/browser/devtools/shared/test/browser_gcli_web.js
@@ -14,17 +14,17 @@
  *
  *********************************** WARNING ***********************************
  *
  * Do not edit this file without understanding where it comes from,
  * Your changes are likely to be overwritten without warning.
  *
  * This test file is generated using a level 25 wizard spell cast on the
  * test files that run in the browser as part of GCLI's test suite.
- * For details of how to cast the spell, see GCLI's Makefile.dryice.js
+ * For details of how to cast the spell, see GCLI's gcli.js
  *
  * For more information on GCLI see:
  * - https://github.com/mozilla/gcli/blob/master/docs/index.md
  * - https://wiki.mozilla.org/DevTools/Features/GCLI
  *
  * The original source for this file is:
  *  https://github.com/mozilla/gcli/
  *
@@ -36,67 +36,97 @@
  *
  *
  *
  *
  *
  */
 
 ///////////////////////////////////////////////////////////////////////////////
-
-var obj = {};
-Components.utils.import("resource:///modules/gcli.jsm", obj);
-
-var define = obj.gcli._internal.define;
-var console = obj.gcli._internal.console;
-var Node = Components.interfaces.nsIDOMNode;
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite'], function(require, exports, module) {
 
   var examiner = require('gclitest/suite').examiner;
 
+  // A minimum fake dom to get us through the JS tests
+  var fakeWindow = {
+    isFake: true,
+    document: { title: 'Fake DOM' }
+  };
+  fakeWindow.window = fakeWindow;
+  examiner.defaultOptions = {
+    window: fakeWindow,
+    hideExec: true
+  };
+
   /**
    * A simple proxy to examiner.run, for convenience - this is run from the
    * top level.
+   * @param options Lookup of options that customize test running. Includes:
+   * - window (default=undefined) A reference to the DOM window. If left
+   *   undefined then a reduced set of tests will run.
+   * - isNode (default=false) Are we running under NodeJS, specifically, are we
+   *   using JSDom, which isn't a 100% complete DOM implementation.
+   *   Some tests are skipped when using NodeJS.
+   * - display (default=undefined) A reference to a Display implementation.
+   *   A reduced set of tests will run if left undefined
+   * - detailedResultLog (default=false) do we output a test summary to
+   *   |console.log| on test completion.
+   * - hideExec (default=false) Set the |hidden| property in calls to
+   *   |requisition.exec()| which prevents the display from becoming messed up,
+   *   however use of hideExec restricts the set of tests that are run
    */
   exports.run = function(options) {
     examiner.run(options || {});
+
+    // A better set of default than those specified above, come from the set
+    // that are passed to run().
+    examiner.defaultOptions = {
+      window: options.window,
+      display: options.display,
+      hideExec: options.hideExec
+    };
   };
+
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testTokenize', 'gclitest/testSplit', 'gclitest/testCli', 'gclitest/testExec', 'gclitest/testKeyboard', 'gclitest/testScratchpad', 'gclitest/testHistory', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testJs', 'gclitest/testUtil'], function(require, exports, module) {
+define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHistory', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
 
   // We need to make sure GCLI is initialized before we begin testing it
   require('gcli/index');
 
   var examiner = require('test/examiner');
 
   // It's tempting to want to unify these strings and make addSuite() do the
   // call to require(), however that breaks the build system which looks for
   // the strings passed to require
-  examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize'));
-  examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit'));
   examiner.addSuite('gclitest/testCli', require('gclitest/testCli'));
+  examiner.addSuite('gclitest/testCompletion', require('gclitest/testCompletion'));
   examiner.addSuite('gclitest/testExec', require('gclitest/testExec'));
+  examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
+  examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
   examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard'));
-  examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
-  examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
   examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire'));
   examiner.addSuite('gclitest/testResource', require('gclitest/testResource'));
-  examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
+  examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
+  examiner.addSuite('gclitest/testSpell', require('gclitest/testSpell'));
+  examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit'));
+  examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize'));
+  examiner.addSuite('gclitest/testTooltip', require('gclitest/testTooltip'));
+  examiner.addSuite('gclitest/testTypes', require('gclitest/testTypes'));
   examiner.addSuite('gclitest/testUtil', require('gclitest/testUtil'));
 
   exports.examiner = examiner;
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
@@ -130,250 +160,385 @@ var stati = {
  * Add a test suite. Generally used like:
  * test.addSuite('foo', require('path/to/foo'));
  */
 examiner.addSuite = function(name, suite) {
   examiner.suites[name] = new Suite(name, suite);
 };
 
 /**
+ * When run from an command, there are some options that we can't (and
+ * shouldn't) specify, so we allow a set of default options, which are merged
+ * with the specified options in |run()|.
+ */
+examiner.defaultOptions = {};
+
+/**
+ * Add properties to |options| from |examiner.defaultOptions| when |options|
+ * does not have a value for a given name.
+ */
+function mergeDefaultOptions(options) {
+  Object.keys(examiner.defaultOptions).forEach(function(name) {
+    if (options[name] == null) {
+      options[name] = examiner.defaultOptions[name];
+    }
+  });
+}
+
+/**
  * Run the tests defined in the test suite synchronously
- * @param options How the tests are run. Properties include:
- * - window: The browser window object to run the tests against
- * - useFakeWindow: Use a test subset and a fake DOM to avoid a real document
- * - detailedResultLog: console.log test passes and failures in more detail
  */
 examiner.run = function(options) {
-  examiner._checkOptions(options);
+  mergeDefaultOptions(options);
 
   Object.keys(examiner.suites).forEach(function(suiteName) {
     var suite = examiner.suites[suiteName];
     suite.run(options);
   }.bind(this));
 
   if (options.detailedResultLog) {
-    examiner.log();
+    examiner.detailedResultLog();
   }
   else {
     console.log('Completed test suite');
   }
 
   return examiner.suites;
 };
 
 /**
- * Check the options object. There should be either useFakeWindow or a window.
- * Setup the fake window if requested.
- */
-examiner._checkOptions = function(options) {
-  if (options.useFakeWindow) {
-    // A minimum fake dom to get us through the JS tests
-    var doc = { title: 'Fake DOM' };
-    var fakeWindow = {
-      window: { document: doc },
-      document: doc
-    };
-
-    options.window = fakeWindow;
-  }
-
-  if (!options.window) {
-    throw new Error('Tests need either window or useFakeWindow');
-  }
-};
-
-/**
  * Run all the tests asynchronously
  */
 examiner.runAsync = function(options, callback) {
-  examiner._checkOptions(options);
-  this.runAsyncInternal(0, options, callback);
+  mergeDefaultOptions(options);
+  this._runAsyncInternal(0, options, callback);
 };
 
 /**
  * Run all the test suits asynchronously
  */
-examiner.runAsyncInternal = function(i, options, callback) {
+examiner._runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(examiner.suites).length) {
     if (typeof callback === 'function') {
       callback();
     }
     return;
   }
 
   var suiteName = Object.keys(examiner.suites)[i];
   examiner.suites[suiteName].runAsync(options, function() {
     setTimeout(function() {
-      examiner.runAsyncInternal(i + 1, options, callback);
+      examiner._runAsyncInternal(i + 1, options, callback);
     }.bind(this), delay);
   }.bind(this));
 };
 
 /**
- *
- */
-examiner.reportToText = function() {
-  return JSON.stringify(examiner.toRemote());
-};
-
-/**
  * Create a JSON object suitable for serialization
  */
 examiner.toRemote = function() {
   return {
     suites: Object.keys(examiner.suites).map(function(suiteName) {
       return examiner.suites[suiteName].toRemote();
-    }.bind(this))
+    }.bind(this)),
+    summary: {
+      checks: this.checks,
+      status: this.status
+    }
   };
 };
 
 /**
+ * The number of checks in this set of test suites is the sum of the checks in
+ * the test suites.
+ */
+Object.defineProperty(examiner, 'checks', {
+  get: function() {
+    return  Object.keys(examiner.suites).reduce(function(current, suiteName) {
+      return current + examiner.suites[suiteName].checks;
+    }.bind(this), 0);
+  },
+  enumerable: true
+});
+
+/**
+ * The status of this set of test suites is the worst of the statuses of the
+ * contained test suites.
+ */
+Object.defineProperty(examiner, 'status', {
+  get: function() {
+    return Object.keys(examiner.suites).reduce(function(status, suiteName) {
+      var suiteStatus = examiner.suites[suiteName].status;
+      return status.index > suiteStatus.index ? status : suiteStatus;
+    }.bind(this), stati.notrun);
+  },
+  enumerable: true
+});
+
+/**
  * Output a test summary to console.log
  */
-examiner.log = function() {
-  var remote = this.toRemote();
-  remote.suites.forEach(function(suite) {
-    console.log(suite.name);
-    suite.tests.forEach(function(test) {
-      console.log('- ' + test.name, test.status.name, test.message || '');
-    });
-  });
+examiner.detailedResultLog = function() {
+  Object.keys(this.suites).forEach(function(suiteName) {
+    var suite = examiner.suites[suiteName];
+
+    console.log(suite.name + ': ' + suite.status.name + ' (funcs=' +
+            Object.keys(suite.tests).length +
+            ', checks=' + suite.checks + ')');
+
+    Object.keys(suite.tests).forEach(function(testName) {
+      var test = suite.tests[testName];
+      if (test.status !== stati.pass || test.failures.length !== 0) {
+        console.log('- ' + test.name + ': ' + test.status.name);
+        test.failures.forEach(function(failure) {
+          console.log('  - ' + failure.message);
+          if (failure.expected) {
+            console.log('    - Expected: ' + failure.expected);
+            console.log('    -   Actual: ' + failure.actual);
+          }
+        }.bind(this));
+      }
+    }.bind(this));
+  }.bind(this));
+
+  console.log();
+  console.log('Summary: ' + this.status.name + ' (' + this.checks + ' checks)');
 };
 
 /**
  * Used by assert to record a failure against the current test
+ * @param failure A set of properties describing the failure. Properties include:
+ * - message (string, required) A message describing the test
+ * - expected (optional) The expected data
+ * - actual (optional) The actual data
  */
-examiner.recordError = function(message) {
+examiner.recordFailure = function(failure) {
   if (!currentTest) {
-    console.error('No currentTest for ' + message);
+    console.error('No currentTest for ' + failure.message);
     return;
   }
 
   currentTest.status = stati.fail;
-
-  if (Array.isArray(message)) {
-    currentTest.messages.push.apply(currentTest.messages, message);
+  currentTest.failures.push(failure);
+};
+
+/**
+ * Used by assert to record a check pass
+ */
+examiner.recordPass = function() {
+  if (!currentTest) {
+    console.error('No currentTest');
+    return;
   }
-  else {
-    currentTest.messages.push(message);
-  }
+
+  currentTest.checks++;
+};
+
+/**
+ * When we want to note something alongside a test
+ */
+examiner.log = function(message) {
+  currentTest.failures.push({ message: message });
 };
 
 /**
  * A suite is a group of tests
  */
 function Suite(suiteName, suite) {
-  this.name = suiteName;
+  this.name = suiteName.replace(/gclitest\//, '');
   this.suite = suite;
 
   this.tests = {};
   Object.keys(suite).forEach(function(testName) {
     if (testName !== 'setup' && testName !== 'shutdown') {
       var test = new Test(this, testName, suite[testName]);
       this.tests[testName] = test;
     }
   }.bind(this));
 }
 
 /**
  * Run all the tests in this suite synchronously
  */
 Suite.prototype.run = function(options) {
-  if (typeof this.suite.setup == "function") {
-    this.suite.setup(options);
+  if (!this._setup(options)) {
+    return;
   }
 
   Object.keys(this.tests).forEach(function(testName) {
     var test = this.tests[testName];
     test.run(options);
   }.bind(this));
 
-  if (typeof this.suite.shutdown == "function") {
-    this.suite.shutdown(options);
-  }
+  this._shutdown(options);
 };
 
 /**
  * Run all the tests in this suite asynchronously
  */
 Suite.prototype.runAsync = function(options, callback) {
-  if (typeof this.suite.setup == "function") {
-    this.suite.setup(options);
+  if (!this._setup(options)) {
+    if (typeof callback === 'function') {
+      callback();
+    }
+    return;
   }
 
-  this.runAsyncInternal(0, options, function() {
-    if (typeof this.suite.shutdown == "function") {
-      this.suite.shutdown(options);
-    }
+  this._runAsyncInternal(0, options, function() {
+    this._shutdown(options);
 
     if (typeof callback === 'function') {
       callback();
     }
   }.bind(this));
 };
 
 /**
  * Function used by the async runners that can handle async recursion.
  */
-Suite.prototype.runAsyncInternal = function(i, options, callback) {
+Suite.prototype._runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(this.tests).length) {
     if (typeof callback === 'function') {
       callback();
     }
     return;
   }
 
   var testName = Object.keys(this.tests)[i];
   this.tests[testName].runAsync(options, function() {
     setTimeout(function() {
-      this.runAsyncInternal(i + 1, options, callback);
+      this._runAsyncInternal(i + 1, options, callback);
     }.bind(this), delay);
   }.bind(this));
 };
 
 /**
  * Create a JSON object suitable for serialization
  */
 Suite.prototype.toRemote = function() {
   return {
     name: this.name,
     tests: Object.keys(this.tests).map(function(testName) {
       return this.tests[testName].toRemote();
     }.bind(this))
   };
 };
 
+/**
+ * The number of checks in this suite is the sum of the checks in the contained
+ * tests.
+ */
+Object.defineProperty(Suite.prototype, 'checks', {
+  get: function() {
+    return Object.keys(this.tests).reduce(function(prevChecks, testName) {
+      return prevChecks + this.tests[testName].checks;
+    }.bind(this), 0);
+  },
+  enumerable: true
+});
+
+/**
+ * The status of a test suite is the worst of the statuses of the contained
+ * tests.
+ */
+Object.defineProperty(Suite.prototype, 'status', {
+  get: function() {
+    return Object.keys(this.tests).reduce(function(prevStatus, testName) {
+      var suiteStatus = this.tests[testName].status;
+      return prevStatus.index > suiteStatus.index ? prevStatus : suiteStatus;
+    }.bind(this), stati.notrun);
+  },
+  enumerable: true
+});
+
+/**
+ * Defensively setup the test suite
+ */
+Suite.prototype._setup = function(options) {
+  if (typeof this.suite.setup !== 'function') {
+    return true;
+  }
+
+  try {
+    this.suite.setup(options);
+    return true;
+  }
+  catch (ex) {
+    this._logToAllTests(stati.notrun, '' + ex);
+    console.error(ex);
+    if (ex.stack) {
+      console.error(ex.stack);
+    }
+    return false;
+  }
+};
+
+/**
+ * Defensively shutdown the test suite
+ */
+Suite.prototype._shutdown = function(options) {
+  if (typeof this.suite.shutdown !== 'function') {
+    return true;
+  }
+
+  try {
+    this.suite.shutdown(options);
+    return true;
+  }
+  catch (ex) {
+    this._logToAllTests(stati.fail, '' + ex);
+    console.error(ex);
+    if (ex.stack) {
+      console.error(ex.stack);
+    }
+    return false;
+  }
+};
+
+/**
+ * Something has gone wrong that affects all tests in this Suite
+ */
+Suite.prototype._logToAllTests = function(status, message) {
+  Object.keys(this.tests).forEach(function(testName) {
+    var test = this.tests[testName];
+    test.status = status;
+    test.failures.push({ message: message });
+  }.bind(this));
+};
+
 
 /**
  * A test represents data about a single test function
  */
 function Test(suite, name, func) {
   this.suite = suite;
   this.name = name;
   this.func = func;
   this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1');
 
-  this.messages = [];
+  this.failures = [];
   this.status = stati.notrun;
+  this.checks = 0;
 }
 
 /**
  * Run just a single test
  */
 Test.prototype.run = function(options) {
   currentTest = this;
   this.status = stati.executing;
-  this.messages = [];
+  this.failures = [];
+  this.checks = 0;
 
   try {
     this.func.apply(this.suite, [ options ]);
   }
   catch (ex) {
     this.status = stati.fail;
-    this.messages.push('' + ex);
+    this.failures.push({ message: '' + ex });
     console.error(ex);
     if (ex.stack) {
       console.error(ex.stack);
     }
   }
 
   if (this.status === stati.executing) {
     this.status = stati.pass;
@@ -397,28 +562,1885 @@ Test.prototype.runAsync = function(optio
 /**
  * Create a JSON object suitable for serialization
  */
 Test.prototype.toRemote = function() {
   return {
     name: this.name,
     title: this.title,
     status: this.status,
-    messages: this.messages
+    failures: this.failures,
+    checks: this.checks
   };
 };
 
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
+define('gclitest/testCli', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gclitest/commands', 'test/assert'], function(require, exports, module) {
+
+
+var Requisition = require('gcli/cli').Requisition;
+var Status = require('gcli/types').Status;
+var commands = require('gclitest/commands');
+
+var test = require('test/assert');
+
+exports.setup = function() {
+  commands.setup();
+};
+
+exports.shutdown = function() {
+  commands.shutdown();
+};
+
+
+var assign1;
+var assign2;
+var assignC;
+var requ;
+var debug = false;
+var status;
+var statuses;
+
+function update(input) {
+  if (!requ) {
+    requ = new Requisition();
+  }
+  requ.update(input.typed);
+
+  if (debug) {
+    console.log('####### TEST: typed="' + input.typed +
+        '" cur=' + input.cursor.start +
+        ' cli=', requ);
+  }
+
+  status = requ.getStatus();
+  assignC = requ.getAssignmentAt(input.cursor.start);
+  statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) {
+    return Array(s.string.length + 1).join(s.status.toString()[0]);
+  }).join('');
+
+  if (requ.commandAssignment.value) {
+    assign1 = requ.getAssignment(0);
+    assign2 = requ.getAssignment(1);
+  }
+  else {
+    assign1 = undefined;
+    assign2 = undefined;
+  }
+}
+
+function verifyPredictionsContains(name, predictions) {
+  return predictions.every(function(prediction) {
+    return name === prediction.name;
+  }, this);
+}
+
+
+exports.testBlank = function() {
+  update({ typed: '', cursor: { start: 0, end: 0 } });
+  test.is(        '', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is(undefined, requ.commandAssignment.value);
+
+  update({ typed: ' ', cursor: { start: 1, end: 1 } });
+  test.is(        'V', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is(undefined, requ.commandAssignment.value);
+
+  update({ typed: ' ', cursor: { start: 0, end: 0 } });
+  test.is(        'V', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is(undefined, requ.commandAssignment.value);
+};
+
+exports.testIncompleteMultiMatch = function() {
+  update({ typed: 't', cursor: { start: 1, end: 1 } });
+  test.is(        'I', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.ok(assignC.getPredictions().length > 0);
+  verifyPredictionsContains('tsv', assignC.getPredictions());
+  verifyPredictionsContains('tsr', assignC.getPredictions());
+  test.is(undefined, requ.commandAssignment.value);
+};
+
+exports.testIncompleteSingleMatch = function() {
+  update({ typed: 'tselar', cursor: { start: 6, end: 6 } });
+  test.is(        'IIIIII', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is(1, assignC.getPredictions().length);
+  test.is('tselarr', assignC.getPredictions()[0].name);
+  test.is(undefined, requ.commandAssignment.value);
+};
+
+exports.testTsv = function() {
+  update({ typed: 'tsv', cursor: { start: 3, end: 3 } });
+  test.is(        'VVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is('tsv', requ.commandAssignment.value.name);
+
+  update({ typed: 'tsv ', cursor: { start: 4, end: 4 } });
+  test.is(        'VVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is(0, assignC.paramIndex);
+  test.is('tsv', requ.commandAssignment.value.name);
+
+  update({ typed: 'tsv ', cursor: { start: 2, end: 2 } });
+  test.is(        'VVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is('tsv', requ.commandAssignment.value.name);
+
+  update({ typed: 'tsv o', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVI', statuses);
+  test.is(Status.ERROR, status);
+  test.is(0, assignC.paramIndex);
+  test.ok(assignC.getPredictions().length >= 2);
+  test.is(commands.option1, assignC.getPredictions()[0].value);
+  test.is(commands.option2, assignC.getPredictions()[1].value);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('o', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsv option', cursor: { start: 10, end: 10 } });
+  test.is(        'VVVVIIIIII', statuses);
+  test.is(Status.ERROR, status);
+  test.is(0, assignC.paramIndex);
+  test.ok(assignC.getPredictions().length >= 2);
+  test.is(commands.option1, assignC.getPredictions()[0].value);
+  test.is(commands.option2, assignC.getPredictions()[1].value);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsv option', cursor: { start: 1, end: 1 } });
+  test.is(        'VVVVEEEEEE', statuses);
+  test.is(Status.ERROR, status);
+  test.is(-1, assignC.paramIndex);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsv option ', cursor: { start: 11, end: 11 } });
+  test.is(        'VVVVEEEEEEV', statuses);
+  test.is(Status.ERROR, status);
+  test.is(1, assignC.paramIndex);
+  test.is(0, assignC.getPredictions().length);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsv option1', cursor: { start: 11, end: 11 } });
+  test.is(        'VVVVVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option1', assign1.arg.text);
+  test.is(commands.option1, assign1.value);
+  test.is(0, assignC.paramIndex);
+
+  update({ typed: 'tsv option1 ', cursor: { start: 12, end: 12 } });
+  test.is(        'VVVVVVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option1', assign1.arg.text);
+  test.is(commands.option1, assign1.value);
+  test.is(1, assignC.paramIndex);
+
+  update({ typed: 'tsv option1 6', cursor: { start: 13, end: 13 } });
+  test.is(        'VVVVVVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option1', assign1.arg.text);
+  test.is(commands.option1, assign1.value);
+  test.is('6', assign2.arg.text);
+  test.is('6', assign2.value);
+  test.is('string', typeof assign2.value);
+  test.is(1, assignC.paramIndex);
+
+  update({ typed: 'tsv option2 6', cursor: { start: 13, end: 13 } });
+  test.is(        'VVVVVVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is('option2', assign1.arg.text);
+  test.is(commands.option2, assign1.value);
+  test.is('6', assign2.arg.text);
+  test.is(6, assign2.value);
+  test.is('number', typeof assign2.value);
+  test.is(1, assignC.paramIndex);
+};
+
+exports.testInvalid = function() {
+  update({ typed: 'zxjq', cursor: { start: 4, end: 4 } });
+  test.is(        'EEEE', statuses);
+  test.is('zxjq', requ.commandAssignment.arg.text);
+  test.is('', requ._unassigned.arg.text);
+  test.is(-1, assignC.paramIndex);
+
+  update({ typed: 'zxjq ', cursor: { start: 5, end: 5 } });
+  test.is(        'EEEEV', statuses);
+  test.is('zxjq', requ.commandAssignment.arg.text);
+  test.is('', requ._unassigned.arg.text);
+  test.is(-1, assignC.paramIndex);
+
+  update({ typed: 'zxjq one', cursor: { start: 8, end: 8 } });
+  test.is(        'EEEEVEEE', statuses);
+  test.is('zxjq', requ.commandAssignment.arg.text);
+  test.is('one', requ._unassigned.arg.text);
+};
+
+exports.testSingleString = function() {
+  update({ typed: 'tsr', cursor: { start: 3, end: 3 } });
+  test.is(        'VVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsr', requ.commandAssignment.value.name);
+  test.ok(assign1.arg.isBlank());
+  test.is(undefined, assign1.value);
+  test.is(undefined, assign2);
+
+  update({ typed: 'tsr ', cursor: { start: 4, end: 4 } });
+  test.is(        'VVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsr', requ.commandAssignment.value.name);
+  test.ok(assign1.arg.isBlank());
+  test.is(undefined, assign1.value);
+  test.is(undefined, assign2);
+
+  update({ typed: 'tsr h', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsr', requ.commandAssignment.value.name);
+  test.is('h', assign1.arg.text);
+  test.is('h', assign1.value);
+
+  update({ typed: 'tsr "h h"', cursor: { start: 9, end: 9 } });
+  test.is(        'VVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsr', requ.commandAssignment.value.name);
+  test.is('h h', assign1.arg.text);
+  test.is('h h', assign1.value);
+
+  update({ typed: 'tsr h h h', cursor: { start: 9, end: 9 } });
+  test.is(        'VVVVVVVVV', statuses);
+  test.is('tsr', requ.commandAssignment.value.name);
+  test.is('h h h', assign1.arg.text);
+  test.is('h h h', assign1.value);
+};
+
+exports.testSingleNumber = function() {
+  update({ typed: 'tsu', cursor: { start: 3, end: 3 } });
+  test.is(        'VVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsu', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsu ', cursor: { start: 4, end: 4 } });
+  test.is(        'VVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsu', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsu 1', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsu', requ.commandAssignment.value.name);
+  test.is('1', assign1.arg.text);
+  test.is(1, assign1.value);
+  test.is('number', typeof assign1.value);
+
+  update({ typed: 'tsu x', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVE', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsu', requ.commandAssignment.value.name);
+  test.is('x', assign1.arg.text);
+  test.is(undefined, assign1.value);
+};
+
+exports.testElement = function(options) {
+  update({ typed: 'tse', cursor: { start: 3, end: 3 } });
+  test.is(        'VVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.ok(assign1.arg.isBlank());
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tse :root', cursor: { start: 9, end: 9 } });
+  test.is(        'VVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.is(':root', assign1.arg.text);
+  if (!options.window.isFake) {
+    test.is(options.window.document.documentElement, assign1.value);
+  }
+
+  if (!options.window.isFake) {
+    var inputElement = options.window.document.getElementById('gcli-input');
+    if (inputElement) {
+      update({ typed: 'tse #gcli-input', cursor: { start: 15, end: 15 } });
+      test.is(        'VVVVVVVVVVVVVVV', statuses);
+      test.is(Status.VALID, status);
+      test.is('tse', requ.commandAssignment.value.name);
+      test.is('#gcli-input', assign1.arg.text);
+      test.is(inputElement, assign1.value);
+    }
+    else {
+      test.log('Skipping test that assumes gcli on the web');
+    }
+  }
+
+  update({ typed: 'tse #gcli-nomatch', cursor: { start: 17, end: 17 } });
+  // This is somewhat debatable because this input can't be corrected simply
+  // by typing so it's and error rather than incomplete, however without
+  // digging into the CSS engine we can't tell that so we default to incomplete
+  test.is(        'VVVVIIIIIIIIIIIII', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.is('#gcli-nomatch', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tse #', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVE', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.is('#', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tse .', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVE', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.is('.', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tse *', cursor: { start: 5, end: 5 } });
+  test.is(        'VVVVE', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tse', requ.commandAssignment.value.name);
+  test.is('*', assign1.arg.text);
+  test.is(undefined, assign1.value);
+};
+
+exports.testNestedCommand = function() {
+  update({ typed: 'tsn', cursor: { start: 3, end: 3 } });
+  test.is(        'III', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn', requ.commandAssignment.arg.text);
+  test.is(undefined, assign1);
+
+  update({ typed: 'tsn ', cursor: { start: 4, end: 4 } });
+  test.is(        'IIIV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn', requ.commandAssignment.arg.text);
+  test.is(undefined, assign1);
+
+  update({ typed: 'tsn x', cursor: { start: 5, end: 5 } });
+  // Commented out while we try out fuzzy matching
+  // test.is(        'EEEVE', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn x', requ.commandAssignment.arg.text);
+  test.is(undefined, assign1);
+
+  update({ typed: 'tsn dif', cursor: { start: 7, end: 7 } });
+  test.is(        'VVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn dif', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsn dif ', cursor: { start: 8, end: 8 } });
+  test.is(        'VVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn dif', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsn dif x', cursor: { start: 9, end: 9 } });
+  test.is(        'VVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsn dif', requ.commandAssignment.value.name);
+  test.is('x', assign1.arg.text);
+  test.is('x', assign1.value);
+
+  update({ typed: 'tsn ext', cursor: { start: 7, end: 7 } });
+  test.is(        'VVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn ext', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsn exte', cursor: { start: 8, end: 8 } });
+  test.is(        'VVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn exte', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsn exten', cursor: { start: 9, end: 9 } });
+  test.is(        'VVVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn exten', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'tsn extend', cursor: { start: 10, end: 10 } });
+  test.is(        'VVVVVVVVVV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn extend', requ.commandAssignment.value.name);
+  test.is('', assign1.arg.text);
+  test.is(undefined, assign1.value);
+
+  update({ typed: 'ts ', cursor: { start: 3, end: 3 } });
+  test.is(        'EEV', statuses);
+  test.is(Status.ERROR, status);
+  test.is('ts', requ.commandAssignment.arg.text);
+  test.is(undefined, assign1);
+};
+
+// From Bug 664203
+exports.testDeeplyNested = function() {
+  update({ typed: 'tsn deep down nested cmd', cursor: { start: 24, end: 24 } });
+  test.is(        'VVVVVVVVVVVVVVVVVVVVVVVV', statuses);
+  test.is(Status.VALID, status);
+  test.is('tsn deep down nested cmd', requ.commandAssignment.value.name);
+  test.is(undefined, assign1);
+
+  update({ typed: 'tsn deep down nested', cursor: { start: 20, end: 20 } });
+  test.is(        'IIIVIIIIVIIIIVIIIIII', statuses);
+  test.is(Status.ERROR, status);
+  test.is('tsn deep down nested', requ.commandAssignment.value.name);
+  test.is(undefined, assign1);
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/commands', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/util', 'gcli/types/selection', 'gcli/types/basic', 'gcli/types'], function(require, exports, module) {
+var commands = exports;
+
+
+var canon = require('gcli/canon');
+var util = require('gcli/util');
+
+var SelectionType = require('gcli/types/selection').SelectionType;
+var DeferredType = require('gcli/types/basic').DeferredType;
+var types = require('gcli/types');
+
+/**
+ * Registration and de-registration.
+ */
+commands.setup = function() {
+  // setup/shutdown need to register/unregister types, however that means we
+  // need to re-initialize commands.option1 and commands.option2 with the
+  // actual types
+  commands.option1.type = types.getType('string');
+  commands.option2.type = types.getType('number');
+
+  types.registerType(commands.optionType);
+  types.registerType(commands.optionValue);
+
+  canon.addCommand(commands.tsv);
+  canon.addCommand(commands.tsr);
+  canon.addCommand(commands.tse);
+  canon.addCommand(commands.tsj);
+  canon.addCommand(commands.tsb);
+  canon.addCommand(commands.tss);
+  canon.addCommand(commands.tsu);
+  canon.addCommand(commands.tsn);
+  canon.addCommand(commands.tsnDif);
+  canon.addCommand(commands.tsnExt);
+  canon.addCommand(commands.tsnExte);
+  canon.addCommand(commands.tsnExten);
+  canon.addCommand(commands.tsnExtend);
+  canon.addCommand(commands.tsnDeep);
+  canon.addCommand(commands.tsnDeepDown);
+  canon.addCommand(commands.tsnDeepDownNested);
+  canon.addCommand(commands.tsnDeepDownNestedCmd);
+  canon.addCommand(commands.tselarr);
+  canon.addCommand(commands.tsm);
+  canon.addCommand(commands.tsg);
+};
+
+commands.shutdown = function() {
+  canon.removeCommand(commands.tsv);
+  canon.removeCommand(commands.tsr);
+  canon.removeCommand(commands.tse);
+  canon.removeCommand(commands.tsj);
+  canon.removeCommand(commands.tsb);
+  canon.removeCommand(commands.tss);
+  canon.removeCommand(commands.tsu);
+  canon.removeCommand(commands.tsn);
+  canon.removeCommand(commands.tsnDif);
+  canon.removeCommand(commands.tsnExt);
+  canon.removeCommand(commands.tsnExte);
+  canon.removeCommand(commands.tsnExten);
+  canon.removeCommand(commands.tsnExtend);
+  canon.removeCommand(commands.tsnDeep);
+  canon.removeCommand(commands.tsnDeepDown);
+  canon.removeCommand(commands.tsnDeepDownNested);
+  canon.removeCommand(commands.tsnDeepDownNestedCmd);
+  canon.removeCommand(commands.tselarr);
+  canon.removeCommand(commands.tsm);
+  canon.removeCommand(commands.tsg);
+
+  types.deregisterType(commands.optionType);
+  types.deregisterType(commands.optionValue);
+};
+
+
+commands.option1 = { type: types.getType('string') };
+commands.option2 = { type: types.getType('number') };
+
+var lastOption = undefined;
+
+commands.optionType = new SelectionType({
+  name: 'optionType',
+  lookup: [
+    { name: 'option1', value: commands.option1 },
+    { name: 'option2', value: commands.option2 }
+  ],
+  noMatch: function() {
+    lastOption = undefined;
+  },
+  stringify: function(option) {
+    lastOption = option;
+    return SelectionType.prototype.stringify.call(this, option);
+  },
+  parse: function(arg) {
+    var conversion = SelectionType.prototype.parse.call(this, arg);
+    lastOption = conversion.value;
+    return conversion;
+  }
+});
+
+commands.optionValue = new DeferredType({
+  name: 'optionValue',
+  defer: function() {
+    if (lastOption && lastOption.type) {
+      return lastOption.type;
+    }
+    else {
+      return types.getType('blank');
+    }
+  }
+});
+
+commands.onCommandExec = util.createEvent('commands.onCommandExec');
+
+function createExec(name) {
+  return function(args, context) {
+    var data = {
+      command: commands[name],
+      args: args,
+      context: context
+    };
+    commands.onCommandExec(data);
+    return data;
+  };
+}
+
+commands.tsv = {
+  name: 'tsv',
+  params: [
+    { name: 'optionType', type: 'optionType' },
+    { name: 'optionValue', type: 'optionValue' }
+  ],
+  exec: createExec('tsv')
+};
+
+commands.tsr = {
+  name: 'tsr',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('tsr')
+};
+
+commands.tse = {
+  name: 'tse',
+  params: [ { name: 'node', type: 'node' } ],
+  exec: createExec('tse')
+};
+
+commands.tsj = {
+  name: 'tsj',
+  params: [ { name: 'javascript', type: 'javascript' } ],
+  exec: createExec('tsj')
+};
+
+commands.tsb = {
+  name: 'tsb',
+  params: [ { name: 'toggle', type: 'boolean' } ],
+  exec: createExec('tsb')
+};
+
+commands.tss = {
+  name: 'tss',
+  exec: createExec('tss')
+};
+
+commands.tsu = {
+  name: 'tsu',
+  params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ],
+  exec: createExec('tsu')
+};
+
+commands.tsn = {
+  name: 'tsn'
+};
+
+commands.tsnDif = {
+  name: 'tsn dif',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('tsnDif')
+};
+
+commands.tsnExt = {
+  name: 'tsn ext',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('tsnExt')
+};
+
+commands.tsnExte = {
+  name: 'tsn exte',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('')
+};
+
+commands.tsnExten = {
+  name: 'tsn exten',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('tsnExte')
+};
+
+commands.tsnExtend = {
+  name: 'tsn extend',
+  params: [ { name: 'text', type: 'string' } ],
+  exec: createExec('tsnExtend')
+};
+
+commands.tsnDeep = {
+  name: 'tsn deep',
+};
+
+commands.tsnDeepDown = {
+  name: 'tsn deep down',
+};
+
+commands.tsnDeepDownNested = {
+  name: 'tsn deep down nested',
+};
+
+commands.tsnDeepDownNestedCmd = {
+  name: 'tsn deep down nested cmd',
+  exec: createExec('tsnDeepDownNestedCmd')
+};
+
+commands.tselarr = {
+  name: 'tselarr',
+  params: [
+    { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } },
+    { name: 'arr', type: { name: 'array', subtype: 'string' } },
+  ],
+  exec: createExec('tselarr')
+};
+
+commands.tsm = {
+  name: 'tsm',
+  description: 'a 3-param test selection|string|number',
+  params: [
+    { name: 'abc', type: { name: 'selection', data: [ 'a', 'b', 'c' ] } },
+    { name: 'txt', type: 'string' },
+    { name: 'num', type: { name: 'number', max: 42, min: 0 } },
+  ],
+  exec: createExec('tsm')
+};
+
+commands.tsg = {
+  name: 'tsg',
+  description: 'a param group test',
+  params: [
+    { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } },
+    {
+      group: 'First',
+      params: [
+        { name: 'txt1', type: 'string', defaultValue: null },
+        { name: 'bool', type: 'boolean' }
+      ]
+    },
+    {
+      group: 'Second',
+      params: [
+        { name: 'txt2', type: 'string', defaultValue: 'd' },
+        { name: 'num', type: { name: 'number', min: 40 }, defaultValue: 42 }
+      ]
+    }
+  ],
+  exec: createExec('tsg')
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('test/assert', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+  exports.ok = ok;
+  exports.is = is;
+  exports.log = info;
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+var commands = require('gclitest/commands');
+
+
+exports.setup = function() {
+  commands.setup();
+};
+
+exports.shutdown = function() {
+  commands.shutdown();
+};
+
+
+function type(typed, tests, options) {
+  var inputter = options.display.inputter;
+  var completer = options.display.completer;
+
+  inputter.setInput(typed);
+
+  if (tests.cursor) {
+    inputter.setCursor({ start: tests.cursor, end: tests.cursor });
+  }
+
+  if (tests.emptyParameters == null) {
+    tests.emptyParameters = [];
+  }
+
+  var realParams = completer.emptyParameters;
+  test.is(tests.emptyParameters.length, realParams.length,
+          'emptyParameters.length for \'' + typed + '\'');
+
+  if (realParams.length === tests.emptyParameters.length) {
+    for (var i = 0; i < realParams.length; i++) {
+      test.is(tests.emptyParameters[i], realParams[i].replace(/\u00a0/g, ' '),
+              'emptyParameters[' + i + '] for \'' + typed + '\'');
+    }
+  }
+
+  if (tests.directTabText) {
+    test.is(tests.directTabText, completer.directTabText,
+            'directTabText for \'' + typed + '\'');
+  }
+  else {
+    test.is('', completer.directTabText,
+            'directTabText for \'' + typed + '\'');
+  }
+
+  if (tests.arrowTabText) {
+    test.is(' \u00a0\u21E5 ' + tests.arrowTabText,
+            completer.arrowTabText,
+            'arrowTabText for \'' + typed + '\'');
+  }
+  else {
+    test.is('', completer.arrowTabText,
+            'arrowTabText for \'' + typed + '\'');
+  }
+}
+
+exports.testActivate = function(options) {
+  if (!options.display) {
+    test.log('No display. Skipping activate tests');
+    return;
+  }
+
+  type('', { }, options);
+
+  type(' ', { }, options);
+
+  type('tsr', {
+    emptyParameters: [ ' <text>' ]
+  }, options);
+
+  type('tsr ', {
+    emptyParameters: [ '<text>' ]
+  }, options);
+
+  type('tsr b', { }, options);
+
+  type('tsb', {
+    emptyParameters: [ ' [toggle]' ]
+  }, options);
+
+  type('tsm', {
+    emptyParameters: [ ' <abc>', ' <txt>', ' <num>' ]
+  }, options);
+
+  type('tsm ', {
+    emptyParameters: [ ' <txt>', ' <num>' ],
+    directTabText: 'a'
+  }, options);
+
+  type('tsm a', {
+    emptyParameters: [ ' <txt>', ' <num>' ]
+  }, options);
+
+  type('tsm a ', {
+    emptyParameters: [ '<txt>', ' <num>' ]
+  }, options);
+
+  type('tsm a  ', {
+    emptyParameters: [ '<txt>', ' <num>' ]
+  }, options);
+
+  type('tsm a  d', {
+    emptyParameters: [ ' <num>' ]
+  }, options);
+
+  type('tsm a "d d"', {
+    emptyParameters: [ ' <num>' ]
+  }, options);
+
+  type('tsm a "d ', {
+    emptyParameters: [ ' <num>' ]
+  }, options);
+
+  type('tsm a "d d" ', {
+    emptyParameters: [ '<num>' ]
+  }, options);
+
+  type('tsm a "d d ', {
+    emptyParameters: [ ' <num>' ]
+  }, options);
+
+  type('tsm d r', {
+    emptyParameters: [ ' <num>' ]
+  }, options);
+
+  type('tsm a d ', {
+    emptyParameters: [ '<num>' ]
+  }, options);
+
+  type('tsm a d 4', { }, options);
+
+  type('tsg', {
+    emptyParameters: [ ' <solo>' ]
+  }, options);
+
+  type('tsg ', {
+    directTabText: 'aaa'
+  }, options);
+
+  type('tsg a', {
+    directTabText: 'aa'
+  }, options);
+
+  type('tsg b', {
+    directTabText: 'bb'
+  }, options);
+
+  type('tsg d', { }, options);
+
+  type('tsg aa', {
+    directTabText: 'a'
+  }, options);
+
+  type('tsg aaa', { }, options);
+
+  type('tsg aaa ', { }, options);
+
+  type('tsg aaa d', { }, options);
+
+  type('tsg aaa dddddd', { }, options);
+
+  type('tsg aaa dddddd ', { }, options);
+
+  type('tsg aaa "d', { }, options);
+
+  type('tsg aaa "d d', { }, options);
+
+  type('tsg aaa "d d"', { }, options);
+
+  type('tsn ex ', { }, options);
+
+  type('selarr', {
+    arrowTabText: 'tselarr'
+  }, options);
+
+  type('tselar 1', { }, options);
+
+  type('tselar 1', {
+    cursor: 7
+  }, options);
+
+  type('tselar 1', {
+    cursor: 6,
+    arrowTabText: 'tselarr'
+  }, options);
+
+  type('tselar 1', {
+    cursor: 5,
+    arrowTabText: 'tselarr'
+  }, options);
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testExec', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'test/assert'], function(require, exports, module) {
+
+
+var Requisition = require('gcli/cli').Requisition;
+var canon = require('gcli/canon');
+var commands = require('gclitest/commands');
+var nodetype = require('gcli/types/node');
+
+var test = require('test/assert');
+
+var actualExec;
+var actualOutput;
+var hideExec = false;
+
+exports.setup = function() {
+  commands.setup();
+  commands.onCommandExec.add(commandExeced);
+  canon.commandOutputManager.onOutput.add(commandOutputed);
+};
+
+exports.shutdown = function() {
+  commands.shutdown();
+  commands.onCommandExec.remove(commandExeced);
+  canon.commandOutputManager.onOutput.remove(commandOutputed);
+};
+
+function commandExeced(ev) {
+  actualExec = ev;
+}
+
+function commandOutputed(ev) {
+  actualOutput = ev.output;
+}
+
+function exec(command, expectedArgs) {
+  var environment = {};
+
+  var requisition = new Requisition(environment);
+  var outputObject = requisition.exec({ typed: command, hidden: hideExec });
+
+  test.is(command.indexOf(actualExec.command.name), 0, 'Command name: ' + command);
+
+  test.is(command, outputObject.typed, 'outputObject.command for: ' + command);
+  test.ok(outputObject.completed, 'outputObject.completed false for: ' + command);
+
+  if (expectedArgs == null) {
+    test.ok(false, 'expectedArgs == null for ' + command);
+    return;
+  }
+  if (actualExec.args == null) {
+    test.ok(false, 'actualExec.args == null for ' + command);
+    return;
+  }
+
+  test.is(Object.keys(expectedArgs).length, Object.keys(actualExec.args).length,
+          'Arg count: ' + command);
+  Object.keys(expectedArgs).forEach(function(arg) {
+    var expectedArg = expectedArgs[arg];
+    var actualArg = actualExec.args[arg];
+
+    if (Array.isArray(expectedArg)) {
+      if (!Array.isArray(actualArg)) {
+        test.ok(false, 'actual is not an array. ' + command + '/' + arg);
+        return;
+      }
+
+      test.is(expectedArg.length, actualArg.length,
+              'Array length: ' + command + '/' + arg);
+      for (var i = 0; i < expectedArg.length; i++) {
+        test.is(expectedArg[i], actualArg[i],
+                'Member: "' + command + '/' + arg + '/' + i);
+      }
+    }
+    else {
+      test.is(expectedArg, actualArg, 'Command: "' + command + '" arg: ' + arg);
+    }
+  });
+
+  test.is(environment, actualExec.context.environment, 'Environment');
+
+  if (!hideExec) {
+    test.is(false, actualOutput.error, 'output error is false');
+    test.is(command, actualOutput.typed, 'command is typed');
+    test.ok(typeof actualOutput.canonical === 'string', 'canonical exists');
+
+    test.is(actualExec.args, actualOutput.args, 'actualExec.args is actualOutput.args');
+  }
+}
+
+
+exports.testExec = function(options) {
+  hideExec = options.hideExec;
+
+  exec('tss', {});
+
+  // Bug 707008 - GCLI deferred types don't work properly
+  exec('tsv option1 10', { optionType: commands.option1, optionValue: '10' });
+  exec('tsv option2 10', { optionType: commands.option2, optionValue: 10 });
+
+  exec('tsr fred', { text: 'fred' });
+  exec('tsr fred bloggs', { text: 'fred bloggs' });
+  exec('tsr "fred bloggs"', { text: 'fred bloggs' });
+
+  exec('tsb', { toggle: false });
+  exec('tsb --toggle', { toggle: true });
+
+  exec('tsu 10', { num: 10 });
+  exec('tsu --num 10', { num: 10 });
+
+  // Bug 704829 - Enable GCLI Javascript parameters
+  // The answer to this should be 2
+  exec('tsj { 1 + 1 }', { javascript: '1 + 1' });
+
+  var origDoc = nodetype.getDocument();
+  nodetype.setDocument(mockDoc);
+  exec('tse :root', { node: mockBody });
+  nodetype.setDocument(origDoc);
+
+  exec('tsn dif fred', { text: 'fred' });
+  exec('tsn exten fred', { text: 'fred' });
+  exec('tsn extend fred', { text: 'fred' });
+
+  exec('tselarr 1', { num: '1', arr: [ ] });
+  exec('tselarr 1 a', { num: '1', arr: [ 'a' ] });
+  exec('tselarr 1 a b', { num: '1', arr: [ 'a', 'b' ] });
+
+  exec('tsm a 10 10', { abc: 'a', txt: '10', num: 10 });
+
+  // Bug 707009 - GCLI doesn't always fill in default parameters properly
+  exec('tsg aaa', { solo: 'aaa', txt1: null, bool: false, txt2: 'd', num: 42 });
+};
+
+var mockBody = {
+  style: {}
+};
+
+var mockDoc = {
+  querySelectorAll: function(css) {
+    if (css === ':root') {
+      return {
+        length: 1,
+        item: function(i) {
+          return mockBody;
+        }
+      };
+    }
+    throw new Error('mockDoc.querySelectorAll(\'' + css + '\') error');
+  }
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testHistory', ['require', 'exports', 'module' , 'test/assert', 'gcli/history'], function(require, exports, module) {
+
+var test = require('test/assert');
+var History = require('gcli/history').History;
+
+exports.setup = function() {
+};
+
+exports.shutdown = function() {
+};
+
+exports.testSimpleHistory = function () {
+  var history = new History({});
+  history.add('foo');
+  history.add('bar');
+  test.is('bar', history.backward());
+  test.is('foo', history.backward());
+
+  // Adding to the history again moves us back to the start of the history.
+  history.add('quux');
+  test.is('quux', history.backward());
+  test.is('bar', history.backward());
+  test.is('foo', history.backward());
+};
+
+exports.testBackwardsPastIndex = function () {
+  var history = new History({});
+  history.add('foo');
+  history.add('bar');
+  test.is('bar', history.backward());
+  test.is('foo', history.backward());
+
+  // Moving backwards past recorded history just keeps giving you the last
+  // item.
+  test.is('foo', history.backward());
+};
+
+exports.testForwardsPastIndex = function () {
+  var history = new History({});
+  history.add('foo');
+  history.add('bar');
+  test.is('bar', history.backward());
+  test.is('foo', history.backward());
+
+  // Going forward through the history again.
+  test.is('bar', history.forward());
+
+  // 'Present' time.
+  test.is('', history.forward());
+
+  // Going to the 'future' just keeps giving us the empty string.
+  test.is('', history.forward());
+};
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testJs', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/types/javascript', 'gcli/canon', 'test/assert'], function(require, exports, module) {
+
+
+var Requisition = require('gcli/cli').Requisition;
+var Status = require('gcli/types').Status;
+var javascript = require('gcli/types/javascript');
+var canon = require('gcli/canon');
+
+var test = require('test/assert');
+
+var debug = false;
+var requ;
+
+var assign;
+var status;
+var statuses;
+var tempWindow;
+
+
+exports.setup = function(options) {
+  tempWindow = javascript.getGlobalObject();
+  javascript.setGlobalObject(options.window);
+
+  Object.defineProperty(options.window, 'donteval', {
+    get: function() {
+      test.ok(false, 'donteval should not be used');
+      return { cant: '', touch: '', 'this': '' };
+    },
+    enumerable: true,
+    configurable : true
+  });
+};
+
+exports.shutdown = function(options) {
+  delete options.window.donteval;
+
+  javascript.setGlobalObject(tempWindow);
+  tempWindow = undefined;
+};
+
+function input(typed) {
+  if (!requ) {
+    requ = new Requisition();
+  }
+  var cursor = { start: typed.length, end: typed.length };
+  requ.update(typed);
+
+  if (debug) {
+    console.log('####### TEST: typed="' + typed +
+        '" cur=' + cursor.start +
+        ' cli=', requ);
+  }
+
+  status = requ.getStatus();
+  statuses = requ.getInputStatusMarkup(cursor.start).map(function(s) {
+    return Array(s.string.length + 1).join(s.status.toString()[0]);
+  }).join('');
+
+  if (requ.commandAssignment.value) {
+    assign = requ.getAssignment(0);
+  }
+  else {
+    assign = undefined;
+  }
+}
+
+function predictionsHas(name) {
+  return assign.getPredictions().some(function(prediction) {
+    return name === prediction.name;
+  }, this);
+}
+
+function check(expStatuses, expStatus, expAssign, expPredict) {
+  test.is('{', requ.commandAssignment.value.name, 'is exec');
+
+  test.is(expStatuses, statuses, 'unexpected status markup');
+  test.is(expStatus.toString(), status.toString(), 'unexpected status');
+  test.is(expAssign, assign.value, 'unexpected assignment');
+
+  if (expPredict != null) {
+    var contains;
+    if (Array.isArray(expPredict)) {
+      expPredict.forEach(function(p) {
+        contains = predictionsHas(p);
+        test.ok(contains, 'missing prediction ' + p);
+      });
+    }
+    else if (typeof expPredict === 'number') {
+      contains = true;
+      test.is(assign.getPredictions().length, expPredict, 'prediction count');
+      if (assign.getPredictions().length !== expPredict) {
+        assign.getPredictions().forEach(function(prediction) {
+          test.log('actual prediction: ', prediction);
+        });
+      }
+    }
+    else {
+      contains = predictionsHas(expPredict);
+      test.ok(contains, 'missing prediction ' + expPredict);
+    }
+
+    if (!contains) {
+      test.log('Predictions: ' + assign.getPredictions().map(function(p) {
+        return p.name;
+      }).join(', '));
+    }
+  }
+}
+
+exports.testBasic = function(options) {
+  if (!canon.getCommand('{')) {
+    test.log('Skipping exec tests because { is not registered');
+    return;
+  }
+
+  input('{');
+  check('V', Status.ERROR, undefined);
+
+  input('{ ');
+  check('VV', Status.ERROR, undefined);
+
+  input('{ w');
+  check('VVI', Status.ERROR, 'w', 'window');
+
+  input('{ windo');
+  check('VVIIIII', Status.ERROR, 'windo', 'window');
+
+  input('{ window');
+  check('VVVVVVVV', Status.VALID, 'window');
+
+  input('{ window.d');
+  check('VVIIIIIIII', Status.ERROR, 'window.d', 'window.document');
+
+  input('{ window.document.title');
+  check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0);
+
+  input('{ d');
+  check('VVI', Status.ERROR, 'd', 'document');
+
+  input('{ document.title');
+  check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0);
+
+  test.ok('donteval' in options.window, 'donteval exists');
+
+  input('{ don');
+  check('VVIII', Status.ERROR, 'don', 'donteval');
+
+  input('{ donteval');
+  check('VVVVVVVVVV', Status.VALID, 'donteval', 0);
+
+  /*
+  // This is a controversial test - technically we can tell that it's an error
+  // because 'donteval.' is a syntax error, however donteval is unsafe so we
+  // are playing safe by bailing out early. It's enough of a corner case that
+  // I don't think it warrants fixing
+  input('{ donteval.');
+  check('VVIIIIIIIII', Status.ERROR, 'donteval.', 0);
+  */
+
+  input('{ donteval.cant');
+  check('VVVVVVVVVVVVVVV', Status.VALID, 'donteval.cant', 0);
+
+  input('{ donteval.xxx');
+  check('VVVVVVVVVVVVVV', Status.VALID, 'donteval.xxx', 0);
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testKeyboard', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/canon', 'gclitest/commands', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) {
+
+
+var Requisition = require('gcli/cli').Requisition;
+var canon = require('gcli/canon');
+var commands = require('gclitest/commands');
+var javascript = require('gcli/types/javascript');
+
+var test = require('test/assert');
+
+var tempWindow;
+var inputter;
+
+exports.setup = function(options) {
+  tempWindow = javascript.getGlobalObject();
+  javascript.setGlobalObject(options.window);
+
+  if (options.display) {
+    inputter = options.display.inputter;
+  }
+
+  commands.setup();
+};
+
+exports.shutdown = function(options) {
+  commands.shutdown();
+
+  inputter = undefined;
+  javascript.setGlobalObject(tempWindow);
+  tempWindow = undefined;
+};
+
+var COMPLETES_TO = 'complete';
+var KEY_UPS_TO = 'keyup';
+var KEY_DOWNS_TO = 'keydown';
+
+function check(initial, action, after, choice, cursor, expectedCursor) {
+  var requisition;
+  if (inputter) {
+    requisition = inputter.requisition;
+    inputter.setInput(initial);
+  }
+  else {
+    requisition = new Requisition();
+    requisition.update(initial);
+  }
+
+  if (cursor == null) {
+    cursor = initial.length;
+  }
+  var assignment = requisition.getAssignmentAt(cursor);
+  switch (action) {
+    case COMPLETES_TO:
+      requisition.complete({ start: cursor, end: cursor }, choice);
+      break;
+
+    case KEY_UPS_TO:
+      assignment.increment();
+      break;
+
+    case KEY_DOWNS_TO:
+      assignment.decrement();
+      break;
+  }
+
+  test.is(after, requisition.toString(),
+          initial + ' + ' + action + ' -> ' + after);
+
+  if (expectedCursor != null) {
+    if (inputter) {
+      test.is(expectedCursor, inputter.getInputState().cursor.start,
+              'Ending cursor position for \'' + initial + '\'');
+    }
+  }
+}
+
+exports.testComplete = function(options) {
+  if (!inputter) {
+    test.log('Missing display, reduced checks');
+  }
+
+  check('tsela', COMPLETES_TO, 'tselarr ', 0);
+  check('tsn di', COMPLETES_TO, 'tsn dif ', 0);
+  check('tsg a', COMPLETES_TO, 'tsg aaa ', 0);
+
+  check('tsn e', COMPLETES_TO, 'tsn extend ', -5);
+  check('tsn e', COMPLETES_TO, 'tsn ext ', -4);
+  check('tsn e', COMPLETES_TO, 'tsn exte ', -3);
+  check('tsn e', COMPLETES_TO, 'tsn exten ', -2);
+  check('tsn e', COMPLETES_TO, 'tsn extend ', -1);
+  check('tsn e', COMPLETES_TO, 'tsn ext ', 0);
+  check('tsn e', COMPLETES_TO, 'tsn exte ', 1);
+  check('tsn e', COMPLETES_TO, 'tsn exten ', 2);
+  check('tsn e', COMPLETES_TO, 'tsn extend ', 3);
+  check('tsn e', COMPLETES_TO, 'tsn ext ', 4);
+  check('tsn e', COMPLETES_TO, 'tsn exte ', 5);
+  check('tsn e', COMPLETES_TO, 'tsn exten ', 6);
+  check('tsn e', COMPLETES_TO, 'tsn extend ', 7);
+  check('tsn e', COMPLETES_TO, 'tsn ext ', 8);
+
+  if (!canon.getCommand('{')) {
+    test.log('Skipping exec tests because { is not registered');
+  }
+  else {
+    check('{ wind', COMPLETES_TO, '{ window', 0);
+    check('{ window.docum', COMPLETES_TO, '{ window.document', 0);
+
+    // Bug 717228: This fails under node
+    if (!options.isNode) {
+      check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ', 0);
+    }
+    else {
+      test.log('Running under Node. Skipping tests due to bug 717228.');
+    }
+  }
+};
+
+exports.testInternalComplete = function(options) {
+  // Bug 664377
+  // check('tsela 1', COMPLETES_TO, 'tselarr 1', 0, 3, 8);
+};
+
+exports.testIncrDecr = function() {
+  check('tsu -70', KEY_UPS_TO, 'tsu -5');
+  check('tsu -7', KEY_UPS_TO, 'tsu -5');
+  check('tsu -6', KEY_UPS_TO, 'tsu -5');
+  check('tsu -5', KEY_UPS_TO, 'tsu -3');
+  check('tsu -4', KEY_UPS_TO, 'tsu -3');
+  check('tsu -3', KEY_UPS_TO, 'tsu 0');
+  check('tsu -2', KEY_UPS_TO, 'tsu 0');
+  check('tsu -1', KEY_UPS_TO, 'tsu 0');
+  check('tsu 0', KEY_UPS_TO, 'tsu 3');
+  check('tsu 1', KEY_UPS_TO, 'tsu 3');
+  check('tsu 2', KEY_UPS_TO, 'tsu 3');
+  check('tsu 3', KEY_UPS_TO, 'tsu 6');
+  check('tsu 4', KEY_UPS_TO, 'tsu 6');
+  check('tsu 5', KEY_UPS_TO, 'tsu 6');
+  check('tsu 6', KEY_UPS_TO, 'tsu 9');
+  check('tsu 7', KEY_UPS_TO, 'tsu 9');
+  check('tsu 8', KEY_UPS_TO, 'tsu 9');
+  check('tsu 9', KEY_UPS_TO, 'tsu 10');
+  check('tsu 10', KEY_UPS_TO, 'tsu 10');
+  check('tsu 100', KEY_UPS_TO, 'tsu -5');
+
+  check('tsu -70', KEY_DOWNS_TO, 'tsu 10');
+  check('tsu -7', KEY_DOWNS_TO, 'tsu 10');
+  check('tsu -6', KEY_DOWNS_TO, 'tsu 10');
+  check('tsu -5', KEY_DOWNS_TO, 'tsu -5');
+  check('tsu -4', KEY_DOWNS_TO, 'tsu -5');
+  check('tsu -3', KEY_DOWNS_TO, 'tsu -5');
+  check('tsu -2', KEY_DOWNS_TO, 'tsu -3');
+  check('tsu -1', KEY_DOWNS_TO, 'tsu -3');
+  check('tsu 0', KEY_DOWNS_TO, 'tsu -3');
+  check('tsu 1', KEY_DOWNS_TO, 'tsu 0');
+  check('tsu 2', KEY_DOWNS_TO, 'tsu 0');
+  check('tsu 3', KEY_DOWNS_TO, 'tsu 0');
+  check('tsu 4', KEY_DOWNS_TO, 'tsu 3');
+  check('tsu 5', KEY_DOWNS_TO, 'tsu 3');
+  check('tsu 6', KEY_DOWNS_TO, 'tsu 3');
+  check('tsu 7', KEY_DOWNS_TO, 'tsu 6');
+  check('tsu 8', KEY_DOWNS_TO, 'tsu 6');
+  check('tsu 9', KEY_DOWNS_TO, 'tsu 6');
+  check('tsu 10', KEY_DOWNS_TO, 'tsu 9');
+  check('tsu 100', KEY_DOWNS_TO, 'tsu 10');
+
+  // Bug 707007 - GCLI increment and decrement operations cycle through
+  // selection options in the wrong order
+  check('tselarr 1', KEY_DOWNS_TO, 'tselarr 2');
+  check('tselarr 2', KEY_DOWNS_TO, 'tselarr 3');
+  check('tselarr 3', KEY_DOWNS_TO, 'tselarr 1');
+
+  check('tselarr 3', KEY_UPS_TO, 'tselarr 2');
+};
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testRequire', ['require', 'exports', 'module' , 'test/assert', 'gclitest/requirable'], function(require, exports, module) {
+
+var test = require('test/assert');
+
+
+exports.testWorking = function() {
+  // There are lots of requirement tests that we could be doing here
+  // The fact that we can get anything at all working is a testament to
+  // require doing what it should - we don't need to test the
+  var requireable = require('gclitest/requirable');
+  test.is('thing1', requireable.thing1);
+  test.is(2, requireable.thing2);
+  test.ok(requireable.thing3 === undefined);
+};
+
+exports.testDomains = function() {
+  var requireable = require('gclitest/requirable');
+  test.ok(requireable.status === undefined);
+  requireable.setStatus(null);
+  test.is(null, requireable.getStatus());
+  test.ok(requireable.status === undefined);
+  requireable.setStatus('42');
+  test.is('42', requireable.getStatus());
+  test.ok(requireable.status === undefined);
+
+  if (define.Domain) {
+    var domain = new define.Domain();
+    var requireable2 = domain.require('gclitest/requirable');
+    test.is(undefined, requireable2.status);
+    test.is('initial', requireable2.getStatus());
+    requireable2.setStatus(999);
+    test.is(999, requireable2.getStatus());
+    test.is(undefined, requireable2.status);
+
+    test.is('42', requireable.getStatus());
+    test.is(undefined, requireable.status);
+  }
+};
+
+exports.testLeakage = function() {
+  var requireable = require('gclitest/requirable');
+  test.ok(requireable.setup === undefined);
+  test.ok(requireable.shutdown === undefined);
+  test.ok(requireable.testWorking === undefined);
+};
+
+exports.testMultiImport = function() {
+  var r1 = require('gclitest/requirable');
+  var r2 = require('gclitest/requirable');
+  test.is(r1, r2);
+};
+
+exports.testUncompilable = function() {
+  // This test is commented out because it breaks the RequireJS module
+  // loader and because it causes console output and because testing failure
+  // cases such as this is something of a luxury
+  // It's not totally clear how a module loader should perform with unusable
+  // modules, however at least it should go into a flat spin ...
+  // GCLI mini_require reports an error as it should
+  /*
+  if (define.Domain) {
+    try {
+      var unrequireable = require('gclitest/unrequirable');
+      t.fail();
+    }
+    catch (ex) {
+      console.error(ex);
+    }
+  }
+  */
+};
+
+exports.testRecursive = function() {
+  // See Bug 658583
+  /*
+  var recurse = require('gclitest/recurse');
+  */
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/requirable', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+  exports.thing1 = 'thing1';
+  exports.thing2 = 2;
+
+  var status = 'initial';
+  exports.setStatus = function(aStatus) { status = aStatus; };
+  exports.getStatus = function() { return status; };
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testResource', ['require', 'exports', 'module' , 'gcli/types/resource', 'gcli/types', 'test/assert'], function(require, exports, module) {
+
+
+var resource = require('gcli/types/resource');
+var types = require('gcli/types');
+var Status = require('gcli/types').Status;
+
+var test = require('test/assert');
+
+var tempDocument;
+
+exports.setup = function(options) {
+  tempDocument = resource.getDocument();
+  resource.setDocument(options.window.document);
+};
+
+exports.shutdown = function(options) {
+  resource.setDocument(tempDocument);
+  tempDocument = undefined;
+};
+
+exports.testPredictions = function(options) {
+  if (options.window.isFake) {
+    test.log('Skipping resource tests: options.window.isFake = true');
+    return;
+  }
+
+  var resource1 = types.getType('resource');
+  var predictions1 = resource1.parseString('').getPredictions();
+  test.ok(predictions1.length > 1, 'have resources');
+  predictions1.forEach(function(prediction) {
+    checkPrediction(resource1, prediction);
+  });
+
+  var resource2 = types.getType({ name: 'resource', include: 'text/javascript' });
+  var predictions2 = resource2.parseString('').getPredictions();
+  test.ok(predictions2.length > 1, 'have resources');
+  predictions2.forEach(function(prediction) {
+    checkPrediction(resource2, prediction);
+  });
+
+  var resource3 = types.getType({ name: 'resource', include: 'text/css' });
+  var predictions3 = resource3.parseString('').getPredictions();
+  // jsdom fails to support digging into stylesheets
+  if (!options.isNode) {
+    test.ok(predictions3.length >= 1, 'have resources');
+  }
+  else {
+    test.log('Running under Node. ' +
+             'Skipping checks due to jsdom document.stylsheets support.');
+  }
+  predictions3.forEach(function(prediction) {
+    checkPrediction(resource3, prediction);
+  });
+
+  var resource4 = types.getType({ name: 'resource' });
+  var predictions4 = resource4.parseString('').getPredictions();
+
+  test.is(predictions1.length, predictions4.length, 'type spec');
+  // Bug 734045
+  // test.is(predictions2.length + predictions3.length, predictions4.length, 'split');
+};
+
+function checkPrediction(res, prediction) {
+  var name = prediction.name;
+  var value = prediction.value;
+
+  var conversion = res.parseString(name);
+  test.is(conversion.getStatus(), Status.VALID, 'status VALID for ' + name);
+  test.is(conversion.value, value, 'value for ' + name);
+
+  var strung = res.stringify(value);
+  test.is(strung, name, 'stringify for ' + name);
+
+  test.is(typeof value.loadContents, 'function', 'resource for ' + name);
+  test.is(typeof value.element, 'object', 'resource for ' + name);
+}
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testScratchpad', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+
+var origScratchpad;
+
+exports.setup = function(options) {
+  if (options.display) {
+    origScratchpad = options.display.inputter.scratchpad;
+    options.display.inputter.scratchpad = stubScratchpad;
+  }
+};
+
+exports.shutdown = function(options) {
+  if (options.display) {
+    options.display.inputter.scratchpad = origScratchpad;
+  }
+};
+
+var stubScratchpad = {
+  shouldActivate: function(ev) {
+    return true;
+  },
+  activatedCount: 0,
+  linkText: 'scratchpad.linkText'
+};
+stubScratchpad.activate = function(value) {
+  stubScratchpad.activatedCount++;
+  return true;
+};
+
+
+exports.testActivate = function(options) {
+  if (!options.display) {
+    test.log('No display. Skipping scratchpad tests');
+    return;
+  }
+
+  var ev = {};
+  stubScratchpad.activatedCount = 0;
+  options.display.inputter.onKeyUp(ev);
+  test.is(1, stubScratchpad.activatedCount, 'scratchpad is activated');
+};
+
+
+});
+/*
+ * Copyright (c) 2009 Panagiotis Astithas
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+define('gclitest/testSpell', ['require', 'exports', 'module' , 'test/assert', 'gcli/types/spell'], function(require, exports, module) {
+
+var test = require('test/assert');
+var Speller = require('gcli/types/spell').Speller;
+
+exports.setup = function() {
+};
+
+exports.shutdown = function() {
+};
+
+exports.testSimple = function(options) {
+  var speller = new Speller();
+  speller.train(Object.keys(options.window));
+
+  test.is(speller.correct('document'), 'document');
+  test.is(speller.correct('documen'), 'document');
+  test.is(speller.correct('ocument'), 'document');
+  test.is(speller.correct('odcument'), 'document');
+
+  test.is(speller.correct('========='), null);
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testSplit', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands', 'gcli/cli'], function(require, exports, module) {
+
+var test = require('test/assert');
+
+var commands = require('gclitest/commands');
+var Requisition = require('gcli/cli').Requisition;
+
+exports.setup = function() {
+  commands.setup();
+};
+
+exports.shutdown = function() {
+  commands.shutdown();
+};
+
+exports.testSimple = function() {
+  var args;
+  var requ = new Requisition();
+
+  args = requ._tokenize('s');
+  requ._split(args);
+  test.is(0, args.length);
+  test.is('s', requ.commandAssignment.arg.text);
+};
+
+exports.testFlatCommand = function() {
+  var args;
+  var requ = new Requisition();
+
+  args = requ._tokenize('tsv');
+  requ._split(args);
+  test.is(0, args.length);
+  test.is('tsv', requ.commandAssignment.value.name);
+
+  args = requ._tokenize('tsv a b');
+  requ._split(args);
+  test.is('tsv', requ.commandAssignment.value.name);
+  test.is(2, args.length);
+  test.is('a', args[0].text);
+  test.is('b', args[1].text);
+};
+
+exports.testJavascript = function() {
+  var args;
+  var requ = new Requisition();
+
+  args = requ._tokenize('{');
+  requ._split(args);
+  test.is(1, args.length);
+  test.is('', args[0].text);
+  test.is('', requ.commandAssignment.arg.text);
+  test.is('{', requ.commandAssignment.value.name);
+};
+
+// BUG 663081 - add tests for sub commands
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
 define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli', 'gcli/argument'], function(require, exports, module) {
 
 
 var test = require('test/assert');
 var Requisition = require('gcli/cli').Requisition;
 var Argument = require('gcli/argument').Argument;
 var ScriptArgument = require('gcli/argument').ScriptArgument;
 
@@ -694,1526 +2716,224 @@ exports.testPathological = function() {
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('test/assert', ['require', 'exports', 'module' ], function(require, exports, module) {
-
-  exports.ok = ok;
-  exports.is = is;
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testSplit', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands', 'gcli/cli'], function(require, exports, module) {
+define('gclitest/testTooltip', ['require', 'exports', 'module' , 'test/assert', 'gclitest/commands'], function(require, exports, module) {
+
 
 var test = require('test/assert');
-
 var commands = require('gclitest/commands');
-var Requisition = require('gcli/cli').Requisition;
+
 
 exports.setup = function() {
   commands.setup();
 };
 
 exports.shutdown = function() {
   commands.shutdown();
 };
 
-exports.testSimple = function() {
-  var args;
-  var requ = new Requisition();
-
-  args = requ._tokenize('s');
-  requ._split(args);
-  test.is(0, args.length);
-  test.is('s', requ.commandAssignment.getArg().text);
-};
-
-exports.testFlatCommand = function() {
-  var args;
-  var requ = new Requisition();
-
-  args = requ._tokenize('tsv');
-  requ._split(args);
-  test.is(0, args.length);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-
-  args = requ._tokenize('tsv a b');
-  requ._split(args);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is(2, args.length);
-  test.is('a', args[0].text);
-  test.is('b', args[1].text);
-};
-
-exports.testJavascript = function() {
-  var args;
-  var requ = new Requisition();
-
-  args = requ._tokenize('{');
-  requ._split(args);
-  test.is(1, args.length);
-  test.is('', args[0].text);
-  test.is('', requ.commandAssignment.getArg().text);
-  test.is('{', requ.commandAssignment.getValue().name);
-};
-
-// BUG 663081 - add tests for sub commands
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/commands', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/util', 'gcli/types/basic', 'gcli/types'], function(require, exports, module) {
-var commands = exports;
-
-
-var canon = require('gcli/canon');
-var util = require('gcli/util');
-
-var SelectionType = require('gcli/types/basic').SelectionType;
-var DeferredType = require('gcli/types/basic').DeferredType;
-var types = require('gcli/types');
-
-/**
- * Registration and de-registration.
- */
-commands.setup = function() {
-  commands.option1.type = types.getType('number');
-  commands.option2.type = types.getType('boolean');
-
-  types.registerType(commands.optionType);
-  types.registerType(commands.optionValue);
-
-  canon.addCommand(commands.tsv);
-  canon.addCommand(commands.tsr);
-  canon.addCommand(commands.tse);
-  canon.addCommand(commands.tsj);
-  canon.addCommand(commands.tsb);
-  canon.addCommand(commands.tss);
-  canon.addCommand(commands.tsu);
-  canon.addCommand(commands.tsn);
-  canon.addCommand(commands.tsnDif);
-  canon.addCommand(commands.tsnExt);
-  canon.addCommand(commands.tsnExte);
-  canon.addCommand(commands.tsnExten);
-  canon.addCommand(commands.tsnExtend);
-  canon.addCommand(commands.tselarr);
-  canon.addCommand(commands.tsm);
-  canon.addCommand(commands.tsg);
-};
-
-commands.shutdown = function() {
-  canon.removeCommand(commands.tsv);
-  canon.removeCommand(commands.tsr);
-  canon.removeCommand(commands.tse);
-  canon.removeCommand(commands.tsj);
-  canon.removeCommand(commands.tsb);
-  canon.removeCommand(commands.tss);
-  canon.removeCommand(commands.tsu);
-  canon.removeCommand(commands.tsn);
-  canon.removeCommand(commands.tsnDif);
-  canon.removeCommand(commands.tsnExt);
-  canon.removeCommand(commands.tsnExte);
-  canon.removeCommand(commands.tsnExten);
-  canon.removeCommand(commands.tsnExtend);
-  canon.removeCommand(commands.tselarr);
-  canon.removeCommand(commands.tsm);
-  canon.removeCommand(commands.tsg);
-
-  types.deregisterType(commands.optionType);
-  types.deregisterType(commands.optionValue);
-};
-
-
-commands.option1 = { type: types.getType('string') };
-commands.option2 = { type: types.getType('number') };
-
-commands.optionType = new SelectionType({
-  name: 'optionType',
-  lookup: [
-    { name: 'option1', value: commands.option1 },
-    { name: 'option2', value: commands.option2 }
-  ],
-  noMatch: function() {
-    this.lastOption = null;
-  },
-  stringify: function(option) {
-    this.lastOption = option;
-    return SelectionType.prototype.stringify.call(this, option);
-  },
-  parse: function(arg) {
-    var conversion = SelectionType.prototype.parse.call(this, arg);
-    this.lastOption = conversion.value;
-    return conversion;
+
+function type(typed, tests, options) {
+  var inputter = options.display.inputter;
+  var tooltip = options.display.tooltip;
+
+  inputter.setInput(typed);
+  if (tests.cursor) {
+    inputter.setCursor({ start: tests.cursor, end: tests.cursor });
   }
-});
-
-commands.optionValue = new DeferredType({
-  name: 'optionValue',
-  defer: function() {
-    if (commands.optionType.lastOption) {
-      return commands.optionType.lastOption.type;
+
+  if (!options.isNode) {
+    if (tests.important) {
+      test.ok(tooltip.field.isImportant, 'Important for ' + typed);
     }
     else {
-      return types.getType('blank');
+      test.ok(!tooltip.field.isImportant, 'Not important for ' + typed);
+    }
+
+    if (tests.options) {
+      var names = tooltip.field.menu.items.map(function(item) {
+        return item.name.textContent ? item.name.textContent : item.name;
+      });
+      test.is(tests.options.join('|'), names.join('|'), 'Options for ' + typed);
+    }
+
+    if (tests.error) {
+      test.is(tests.error, tooltip.errorEle.textContent, 'Error for ' + typed);
+    }
+    else {
+      test.is('', tooltip.errorEle.textContent, 'No error for ' + typed);
     }
   }
-});
-
-commands.commandExec = util.createEvent('commands.commandExec');
-
-function createExec(name) {
-  return function(args, context) {
-    var data = {
-      command: commands[name],
-      args: args,
-      context: context
-    };
-    commands.commandExec(data);
-    return data;
-  };
 }
 
-commands.tsv = {
-  name: 'tsv',
-  params: [
-    { name: 'optionType', type: 'optionType' },
-    { name: 'optionValue', type: 'optionValue' }
-  ],
-  exec: createExec('tsv')
-};
-
-commands.tsr = {
-  name: 'tsr',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('tsr')
-};
-
-commands.tse = {
-  name: 'tse',
-  params: [ { name: 'node', type: 'node' } ],
-  exec: createExec('tse')
-};
-
-commands.tsj = {
-  name: 'tsj',
-  params: [ { name: 'javascript', type: 'javascript' } ],
-  exec: createExec('tsj')
-};
-
-commands.tsb = {
-  name: 'tsb',
-  params: [ { name: 'toggle', type: 'boolean' } ],
-  exec: createExec('tsb')
-};
-
-commands.tss = {
-  name: 'tss',
-  exec: createExec('tss')
-};
-
-commands.tsu = {
-  name: 'tsu',
-  params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ],
-  exec: createExec('tsu')
-};
-
-commands.tsn = {
-  name: 'tsn'
-};
-
-commands.tsnDif = {
-  name: 'tsn dif',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('tsnDif')
-};
-
-commands.tsnExt = {
-  name: 'tsn ext',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('tsnExt')
-};
-
-commands.tsnExte = {
-  name: 'tsn exte',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('')
-};
-
-commands.tsnExten = {
-  name: 'tsn exten',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('tsnExte')
-};
-
-commands.tsnExtend = {
-  name: 'tsn extend',
-  params: [ { name: 'text', type: 'string' } ],
-  exec: createExec('tsnExtend')
-};
-
-commands.tselarr = {
-  name: 'tselarr',
-  params: [
-    { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } },
-    { name: 'arr', type: { name: 'array', subtype: 'string' } },
-  ],
-  exec: createExec('tselarr')
-};
-
-commands.tsm = {
-  name: 'tsm',
-  hidden: true,
-  description: 'a 3-param test selection|string|number',
-  params: [
-    { name: 'abc', type: { name: 'selection', data: [ 'a', 'b', 'c' ] } },
-    { name: 'txt', type: 'string' },
-    { name: 'num', type: { name: 'number', max: 42, min: 0 } },
-  ],
-  exec: createExec('tsm')
-};
-
-commands.tsg = {
-  name: 'tsg',
-  hidden: true,
-  description: 'a param group test',
-  params: [
-    { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } },
-    {
-      group: 'First',
-      params: [
-        { name: 'txt1', type: 'string', defaultValue: null },
-        { name: 'boolean1', type: 'boolean' }
-      ]
-    },
-    {
-      group: 'Second',
-      params: [
-        { name: 'txt2', type: 'string', defaultValue: 'd' },
-        { name: 'num2', type: { name: 'number', defaultValue: 42 } }
-      ]
-    }
-  ],
-  exec: createExec('tsg')
-};
-
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testCli', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gclitest/commands', 'test/assert'], function(require, exports, module) {
-
-
-var Requisition = require('gcli/cli').Requisition;
-var Status = require('gcli/types').Status;
-var commands = require('gclitest/commands');
-
-var test = require('test/assert');
-
-exports.setup = function() {
-  commands.setup();
-};
-
-exports.shutdown = function() {
-  commands.shutdown();
-};
-
-
-var assign1;
-var assign2;
-var assignC;
-var requ;
-var debug = false;
-var status;
-var statuses;
-
-function update(input) {
-  if (!requ) {
-    requ = new Requisition();
+exports.testActivate = function(options) {
+  if (!options.display) {
+    test.log('No display. Skipping activate tests');
+    return;
   }
-  requ.update(input);
-
-  if (debug) {
-    console.log('####### TEST: typed="' + input.typed +
-        '" cur=' + input.cursor.start +
-        ' cli=', requ);
-  }
-
-  status = requ.getStatus();
-  assignC = requ.getAssignmentAt(input.cursor.start);
-  statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) {
-    return Array(s.string.length + 1).join(s.status.toString()[0]);
-  }).join('');
-
-  if (requ.commandAssignment.getValue()) {
-    assign1 = requ.getAssignment(0);
-    assign2 = requ.getAssignment(1);
-  }
-  else {
-    assign1 = undefined;
-    assign2 = undefined;
+
+  if (options.isNode) {
+    test.log('Running under Node. Reduced checks due to JSDom.textContent');
   }
-}
-
-function verifyPredictionsContains(name, predictions) {
-  return predictions.every(function(prediction) {
-    return name === prediction.name;
-  }, this);
-}
-
-
-exports.testBlank = function() {
-  update({ typed: '', cursor: { start: 0, end: 0 } });
-  test.is(        '', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is(null, requ.commandAssignment.getValue());
-
-  update({ typed: ' ', cursor: { start: 1, end: 1 } });
-  test.is(        'V', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is(null, requ.commandAssignment.getValue());
-
-  update({ typed: ' ', cursor: { start: 0, end: 0 } });
-  test.is(        'V', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is(null, requ.commandAssignment.getValue());
-};
-
-exports.testIncompleteMultiMatch = function() {
-  update({ typed: 't', cursor: { start: 1, end: 1 } });
-  test.is(        'I', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.ok(assignC.getPredictions().length > 0);
-  verifyPredictionsContains('tsv', assignC.getPredictions());
-  verifyPredictionsContains('tsr', assignC.getPredictions());
-  test.is(null, requ.commandAssignment.getValue());
-};
-
-exports.testIncompleteSingleMatch = function() {
-  update({ typed: 'tselar', cursor: { start: 6, end: 6 } });
-  test.is(        'IIIIII', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is(1, assignC.getPredictions().length);
-  test.is('tselarr', assignC.getPredictions()[0].name);
-  test.is(null, requ.commandAssignment.getValue());
-};
-
-exports.testTsv = function() {
-  update({ typed: 'tsv', cursor: { start: 3, end: 3 } });
-  test.is(        'VVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-
-  update({ typed: 'tsv ', cursor: { start: 4, end: 4 } });
-  test.is(        'VVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is(0, assignC.paramIndex);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-
-  update({ typed: 'tsv ', cursor: { start: 2, end: 2 } });
-  test.is(        'VVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-
-  update({ typed: 'tsv o', cursor: { start: 5, end: 5 } });
-  test.is(        'VVVVI', statuses);
-  test.is(Status.ERROR, status);
-  test.is(0, assignC.paramIndex);
-  test.is(2, assignC.getPredictions().length);
-  test.is(commands.option1, assignC.getPredictions()[0].value);
-  test.is(commands.option2, assignC.getPredictions()[1].value);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('o', assign1.getArg().text);
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsv option', cursor: { start: 10, end: 10 } });
-  test.is(        'VVVVIIIIII', statuses);
-  test.is(Status.ERROR, status);
-  test.is(0, assignC.paramIndex);
-  test.is(2, assignC.getPredictions().length);
-  test.is(commands.option1, assignC.getPredictions()[0].value);
-  test.is(commands.option2, assignC.getPredictions()[1].value);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option', assign1.getArg().text);
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsv option', cursor: { start: 1, end: 1 } });
-  test.is(        'VVVVEEEEEE', statuses);
-  test.is(Status.ERROR, status);
-  test.is(-1, assignC.paramIndex);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option', assign1.getArg().text);
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsv option ', cursor: { start: 11, end: 11 } });
-  test.is(        'VVVVEEEEEEV', statuses);
-  test.is(Status.ERROR, status);
-  test.is(1, assignC.paramIndex);
-  test.is(0, assignC.getPredictions().length);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option', assign1.getArg().text);
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsv option1', cursor: { start: 11, end: 11 } });
-  test.is(        'VVVVVVVVVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option1', assign1.getArg().text);
-  test.is(commands.option1, assign1.getValue());
-  test.is(0, assignC.paramIndex);
-
-  update({ typed: 'tsv option1 ', cursor: { start: 12, end: 12 } });
-  test.is(        'VVVVVVVVVVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option1', assign1.getArg().text);
-  test.is(commands.option1, assign1.getValue());
-  test.is(1, assignC.paramIndex);
-
-  update({ typed: 'tsv option1 6', cursor: { start: 13, end: 13 } });
-  test.is(        'VVVVVVVVVVVVV', statuses);
-  test.is(Status.VALID, status);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option1', assign1.getArg().text);
-  test.is(commands.option1, assign1.getValue());
-  test.is('6', assign2.getArg().text);
-  test.is(6, assign2.getValue());
-  test.is('number', typeof assign2.getValue());
-  test.is(1, assignC.paramIndex);
-
-  update({ typed: 'tsv option2 6', cursor: { start: 13, end: 13 } });
-  test.is(        'VVVVVVVVVVVVE', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsv', requ.commandAssignment.getValue().name);
-  test.is('option2', assign1.getArg().text);
-  test.is(commands.option2, assign1.getValue());
-  test.is('6', assign2.getArg().text);
-  test.is(null, assign2.getValue());
-  test.is(1, assignC.paramIndex);
-};
-
-exports.testInvalid = function() {
-  update({ typed: 'fred', cursor: { start: 4, end: 4 } });
-  test.is(        'EEEE', statuses);
-  test.is('fred', requ.commandAssignment.getArg().text);
-  test.is('', requ._unassigned.getArg().text);
-  test.is(-1, assignC.paramIndex);
-
-  update({ typed: 'fred ', cursor: { start: 5, end: 5 } });
-  test.is(        'EEEEV', statuses);
-  test.is('fred', requ.commandAssignment.getArg().text);
-  test.is('', requ._unassigned.getArg().text);
-  test.is(-1, assignC.paramIndex);
-
-  update({ typed: 'fred one', cursor: { start: 8, end: 8 } });
-  test.is(        'EEEEVEEE', statuses);
-  test.is('fred', requ.commandAssignment.getArg().text);
-  test.is('one', requ._unassigned.getArg().text);
-};
-
-exports.testSingleString = function() {
-  update({ typed: 'tsr', cursor: { start: 3, end: 3 } });
-  test.is(        'VVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsr', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  //test.is(undefined, assign1.getValue());
-  test.is(undefined, assign2);
-
-  update({ typed: 'tsr ', cursor: { start: 4, end: 4 } });
-  test.is(        'VVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsr', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  //test.is(undefined, assign1.getValue());
-  test.is(undefined, assign2);
-
-  update({ typed: 'tsr h', cursor: { start: 5, end: 5 } });
-  test.is(        'VVVVV', statuses);
-  test.is(Status.VALID, status);
-  test.is('tsr', requ.commandAssignment.getValue().name);
-  test.is('h', assign1.getArg().text);
-  test.is('h', assign1.getValue());
-
-  update({ typed: 'tsr "h h"', cursor: { start: 9, end: 9 } });
-  test.is(        'VVVVVVVVV', statuses);
-  test.is(Status.VALID, status);
-  test.is('tsr', requ.commandAssignment.getValue().name);
-  test.is('h h', assign1.getArg().text);
-  test.is('h h', assign1.getValue());
-
-  update({ typed: 'tsr h h h', cursor: { start: 9, end: 9 } });
-  test.is(        'VVVVVVVVV', statuses);
-  test.is('tsr', requ.commandAssignment.getValue().name);
-  test.is('h h h', assign1.getArg().text);
-  test.is('h h h', assign1.getValue());
-};
-
-// BUG 664203: Add test to see that a command without mandatory param -> ERROR
-
-exports.testSingleNumber = function() {
-  update({ typed: 'tsu', cursor: { start: 3, end: 3 } });
-  test.is(        'VVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsu', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsu ', cursor: { start: 4, end: 4 } });
-  test.is(        'VVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsu', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  test.is(null, assign1.getValue());
-
-  update({ typed: 'tsu 1', cursor: { start: 5, end: 5 } });
-  test.is(        'VVVVV', statuses);
-  test.is(Status.VALID, status);
-  test.is('tsu', requ.commandAssignment.getValue().name);
-  test.is('1', assign1.getArg().text);
-  test.is(1, assign1.getValue());
-  test.is('number', typeof assign1.getValue());
-
-  update({ typed: 'tsu x', cursor: { start: 5, end: 5 } });
-  test.is(        'VVVVE', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsu', requ.commandAssignment.getValue().name);
-  test.is('x', assign1.getArg().text);
-  test.is(null, assign1.getValue());
-};
-
-exports.testNestedCommand = function() {
-  update({ typed: 'tsn', cursor: { start: 3, end: 3 } });
-  test.is(        'III', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn', requ.commandAssignment.getValue().name);
-  test.is(undefined, assign1);
-
-  update({ typed: 'tsn ', cursor: { start: 4, end: 4 } });
-  test.is(        'IIIV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn', requ.commandAssignment.getValue().name);
-  test.is(undefined, assign1);
-
-  update({ typed: 'tsn x', cursor: { start: 5, end: 5 } });
-  test.is(        'EEEVE', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn x', requ.commandAssignment.getArg().text);
-  test.is(undefined, assign1);
-
-  update({ typed: 'tsn dif', cursor: { start: 7, end: 7 } });
-  test.is(        'VVVVVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn dif', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  //test.is(undefined, assign1.getValue());
-
-  update({ typed: 'tsn dif ', cursor: { start: 8, end: 8 } });
-  test.is(        'VVVVVVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn dif', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  //test.is(undefined, assign1.getValue());
-
-  update({ typed: 'tsn dif x', cursor: { start: 9, end: 9 } });
-  test.is(        'VVVVVVVVV', statuses);
-  test.is(Status.VALID, status);
-  test.is('tsn dif', requ.commandAssignment.getValue().name);
-  test.is('x', assign1.getArg().text);
-  test.is('x', assign1.getValue());
-
-  update({ typed: 'tsn ext', cursor: { start: 7, end: 7 } });
-  test.is(        'VVVVVVV', statuses);
-  test.is(Status.ERROR, status);
-  test.is('tsn ext', requ.commandAssignment.getValue().name);
-  //test.is(undefined, assign1.getArg());
-  //test.is(undefined, assign1.getValue());
+
+  type(' ', { }, options);
+
+  type('tsb ', {
+    important: true,
+    options: [ 'false', 'true' ]
+  }, options);
+
+  type('tsb t', {
+    important: true,
+    options: [ 'true' ]
+  }, options);
+
+  type('tsb tt', {
+    important: true,
+    options: [ ],
+    error: 'Can\'t use \'tt\'.'
+  }, options);
+
+
+  type('asdf', {
+    important: false,
+    options: [ ],
+    error: 'Can\'t use \'asdf\'.'
+  }, options);
+
+  type('', { }, options);
 };
 
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('gclitest/testExec', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'test/assert'], function(require, exports, module) {
-
-
-var Requisition = require('gcli/cli').Requisition;
-var Status = require('gcli/types').Status;
-var canon = require('gcli/canon');
-var commands = require('gclitest/commands');
-var nodetype = require('gcli/types/node');
-
-var test = require('test/assert');
-
-var actualExec;
-var actualOutput;
-
-exports.setup = function() {
-  commands.setup();
-  commands.commandExec.add(onCommandExec);
-  canon.commandOutputManager.addListener(onCommandOutput);
-};
-
-exports.shutdown = function() {
-  commands.shutdown();
-  commands.commandExec.remove(onCommandExec);
-  canon.commandOutputManager.removeListener(onCommandOutput);
-};
-
-function onCommandExec(ev) {
-  actualExec = ev;
-}
-
-function onCommandOutput(ev) {
-  actualOutput = ev.output;
-}
-
-function exec(command, expectedArgs) {
-  var environment = {};
-
-  var requisition = new Requisition(environment);
-  var reply = requisition.exec({ typed: command });
-
-  test.is(command.indexOf(actualExec.command.name), 0, 'Command name: ' + command);
-
-  if (reply !== true) {
-    test.ok(false, 'reply = false for command: ' + command);
-  }
-
-  if (expectedArgs == null) {
-    test.ok(false, 'expectedArgs == null for ' + command);
-    return;
-  }
-  if (actualExec.args == null) {
-    test.ok(false, 'actualExec.args == null for ' + command);
-    return;
-  }
-
-  test.is(Object.keys(expectedArgs).length, Object.keys(actualExec.args).length,
-          'Arg count: ' + command);
-  Object.keys(expectedArgs).forEach(function(arg) {
-    var expectedArg = expectedArgs[arg];
-    var actualArg = actualExec.args[arg];
-
-    if (Array.isArray(expectedArg)) {
-      if (!Array.isArray(actualArg)) {
-        test.ok(false, 'actual is not an array. ' + command + '/' + arg);
-        return;
-      }
-
-      test.is(expectedArg.length, actualArg.length,
-              'Array length: ' + command + '/' + arg);
-      for (var i = 0; i < expectedArg.length; i++) {
-        test.is(expectedArg[i], actualArg[i],
-                'Member: "' + command + '/' + arg + '/' + i);
-      }
-    }
-    else {
-      test.is(expectedArg, actualArg, 'Command: "' + command + '" arg: ' + arg);
-    }
-  });
-
-  test.is(environment, actualExec.context.environment, 'Environment');
-
-  test.is(false, actualOutput.error, 'output error is false');
-  test.is(command, actualOutput.typed, 'command is typed');
-  test.ok(typeof actualOutput.canonical === 'string', 'canonical exists');
-
-  test.is(actualExec.args, actualOutput.args, 'actualExec.args is actualOutput.args');
-}
-
-
-exports.testExec = function() {
-  exec('tss', {});
-
-  // Bug 707008 - GCLI defered types don't work properly
-  // exec('tsv option1 10', { optionType: commands.option1, optionValue: '10' });
-  // exec('tsv option2 10', { optionType: commands.option1, optionValue: 10 });
-
-  exec('tsr fred', { text: 'fred' });
-  exec('tsr fred bloggs', { text: 'fred bloggs' });
-  exec('tsr "fred bloggs"', { text: 'fred bloggs' });
-
-  exec('tsb', { toggle: false });
-  exec('tsb --toggle', { toggle: true });
-
-  exec('tsu 10', { num: 10 });
-  exec('tsu --num 10', { num: 10 });
-
-  // Bug 704829 - Enable GCLI Javascript parameters
-  // The answer to this should be 2
-  exec('tsj { 1 + 1 }', { javascript: '1 + 1' });
-
-  var origDoc = nodetype.getDocument();
-  nodetype.setDocument(mockDoc);
-  exec('tse :root', { node: mockBody });
-  nodetype.setDocument(origDoc);
-
-  exec('tsn dif fred', { text: 'fred' });
-  exec('tsn exten fred', { text: 'fred' });
-  exec('tsn extend fred', { text: 'fred' });
-
-  exec('tselarr 1', { num: '1', arr: [ ] });
-  exec('tselarr 1 a', { num: '1', arr: [ 'a' ] });
-  exec('tselarr 1 a b', { num: '1', arr: [ 'a', 'b' ] });
-
-  exec('tsm a 10 10', { abc: 'a', txt: '10', num: 10 });
-
-  // Bug 707009 - GCLI doesn't always fill in default parameters properly
-  // exec('tsg a', { solo: 'a', txt1: null, boolean1: false, txt2: 'd', num2: 42 });
-};
-
-var mockBody = {
-  style: {}
-};
-
-var mockDoc = {
-  querySelectorAll: function(css) {
-    if (css === ':root') {
-      return {
-        length: 1,
-        item: function(i) {
-          return mockBody;
-        }
-      };
-    }
-    throw new Error('mockDoc.querySelectorAll(\'' + css + '\') error');
-  }
-};
-
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testKeyboard', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/canon', 'gclitest/commands', 'gcli/types/node', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) {
-
-
-var Requisition = require('gcli/cli').Requisition;
-var Status = require('gcli/types').Status;
-var canon = require('gcli/canon');
-var commands = require('gclitest/commands');
-var nodetype = require('gcli/types/node');
-var javascript = require('gcli/types/javascript');
+define('gclitest/testTypes', ['require', 'exports', 'module' , 'test/assert', 'gcli/types'], function(require, exports, module) {
 
 var test = require('test/assert');
-
-var tempWindow;
-
-exports.setup = function(options) {
-  tempWindow = javascript.getGlobalObject();
-  javascript.setGlobalObject(options.window);
-
-  commands.setup();
-};
-
-exports.shutdown = function(options) {
-  commands.shutdown();
-
-  javascript.setGlobalObject(tempWindow);
-  tempWindow = undefined;
-};
-
-var COMPLETES_TO = 'complete';
-var KEY_UPS_TO = 'keyup';
-var KEY_DOWNS_TO = 'keydown';
-
-function check(initial, action, after) {
-  var requisition = new Requisition();
-  requisition.update({
-    typed: initial,
-    cursor: { start: initial.length, end: initial.length }
-  });
-  var assignment = requisition.getAssignmentAt(initial.length);
-  switch (action) {
-    case COMPLETES_TO:
-      assignment.complete();
-      break;
-
-    case KEY_UPS_TO:
-      assignment.increment();
-      break;
-
-    case KEY_DOWNS_TO:
-      assignment.decrement();
-      break;
-  }
-
-  test.is(after, requisition.toString(), initial + ' + ' + action + ' -> ' + after);
-}
-
-exports.testComplete = function(options) {
-  check('tsela', COMPLETES_TO, 'tselarr ');
-  check('tsn di', COMPLETES_TO, 'tsn dif ');
-  check('tsg a', COMPLETES_TO, 'tsg aaa ');
-
-  check('{ wind', COMPLETES_TO, '{ window');
-  check('{ window.docum', COMPLETES_TO, '{ window.document');
-
-  // Bug 717228: This fails under node
-  if (!options.isNode) {
-    check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ');
-  }
-};
-
-exports.testIncrDecr = function() {
-  check('tsu -70', KEY_UPS_TO, 'tsu -5');
-  check('tsu -7', KEY_UPS_TO, 'tsu -5');
-  check('tsu -6', KEY_UPS_TO, 'tsu -5');
-  check('tsu -5', KEY_UPS_TO, 'tsu -3');
-  check('tsu -4', KEY_UPS_TO, 'tsu -3');
-  check('tsu -3', KEY_UPS_TO, 'tsu 0');
-  check('tsu -2', KEY_UPS_TO, 'tsu 0');
-  check('tsu -1', KEY_UPS_TO, 'tsu 0');
-  check('tsu 0', KEY_UPS_TO, 'tsu 3');
-  check('tsu 1', KEY_UPS_TO, 'tsu 3');
-  check('tsu 2', KEY_UPS_TO, 'tsu 3');
-  check('tsu 3', KEY_UPS_TO, 'tsu 6');
-  check('tsu 4', KEY_UPS_TO, 'tsu 6');
-  check('tsu 5', KEY_UPS_TO, 'tsu 6');
-  check('tsu 6', KEY_UPS_TO, 'tsu 9');
-  check('tsu 7', KEY_UPS_TO, 'tsu 9');
-  check('tsu 8', KEY_UPS_TO, 'tsu 9');
-  check('tsu 9', KEY_UPS_TO, 'tsu 10');
-  check('tsu 10', KEY_UPS_TO, 'tsu 10');
-  check('tsu 100', KEY_UPS_TO, 'tsu -5');
-
-  check('tsu -70', KEY_DOWNS_TO, 'tsu 10');
-  check('tsu -7', KEY_DOWNS_TO, 'tsu 10');
-  check('tsu -6', KEY_DOWNS_TO, 'tsu 10');
-  check('tsu -5', KEY_DOWNS_TO, 'tsu -5');
-  check('tsu -4', KEY_DOWNS_TO, 'tsu -5');
-  check('tsu -3', KEY_DOWNS_TO, 'tsu -5');
-  check('tsu -2', KEY_DOWNS_TO, 'tsu -3');
-  check('tsu -1', KEY_DOWNS_TO, 'tsu -3');
-  check('tsu 0', KEY_DOWNS_TO, 'tsu -3');
-  check('tsu 1', KEY_DOWNS_TO, 'tsu 0');
-  check('tsu 2', KEY_DOWNS_TO, 'tsu 0');
-  check('tsu 3', KEY_DOWNS_TO, 'tsu 0');
-  check('tsu 4', KEY_DOWNS_TO, 'tsu 3');
-  check('tsu 5', KEY_DOWNS_TO, 'tsu 3');
-  check('tsu 6', KEY_DOWNS_TO, 'tsu 3');
-  check('tsu 7', KEY_DOWNS_TO, 'tsu 6');
-  check('tsu 8', KEY_DOWNS_TO, 'tsu 6');
-  check('tsu 9', KEY_DOWNS_TO, 'tsu 6');
-  check('tsu 10', KEY_DOWNS_TO, 'tsu 9');
-  check('tsu 100', KEY_DOWNS_TO, 'tsu 10');
-
-  // Bug 707007 - GCLI increment and decrement operations cycle through
-  // selection options in the wrong order
-  check('tselarr 1', KEY_DOWNS_TO, 'tselarr 2');
-  check('tselarr 2', KEY_DOWNS_TO, 'tselarr 3');
-  check('tselarr 3', KEY_DOWNS_TO, 'tselarr 1');
-
-  check('tselarr 3', KEY_UPS_TO, 'tselarr 2');
-};
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testScratchpad', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
-
-
-var test = require('test/assert');
-
-var origScratchpad;
-
-exports.setup = function(options) {
-  if (options.inputter) {
-    origScratchpad = options.inputter.scratchpad;
-    options.inputter.scratchpad = stubScratchpad;
-  }
-};
-
-exports.shutdown = function(options) {
-  if (options.inputter) {
-    options.inputter.scratchpad = origScratchpad;
-  }
-};
-
-var stubScratchpad = {
-  shouldActivate: function(ev) {
-    return true;
-  },
-  activatedCount: 0,
-  linkText: 'scratchpad.linkText'
-};
-stubScratchpad.activate = function(value) {
-  stubScratchpad.activatedCount++;
-  return true;
-};
-
-
-exports.testActivate = function(options) {
-  if (!options.inputter) {
-    console.log('No inputter. Skipping scratchpad tests');
-    return;
-  }
-
-  var ev = {};
-  stubScratchpad.activatedCount = 0;
-  options.inputter.onKeyUp(ev);
-  test.is(1, stubScratchpad.activatedCount, 'scratchpad is activated');
-};
-
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testHistory', ['require', 'exports', 'module' , 'test/assert', 'gcli/history'], function(require, exports, module) {
-
-var test = require('test/assert');
-var History = require('gcli/history').History;
+var types = require('gcli/types');
 
 exports.setup = function() {
 };
 
 exports.shutdown = function() {
 };
 
-exports.testSimpleHistory = function () {
-  var history = new History({});
-  history.add('foo');
-  history.add('bar');
-  test.is('bar', history.backward());
-  test.is('foo', history.backward());
-
-  // Adding to the history again moves us back to the start of the history.
-  history.add('quux');
-  test.is('quux', history.backward());
-  test.is('bar', history.backward());
-  test.is('foo', history.backward());
-};
-
-exports.testBackwardsPastIndex = function () {
-  var history = new History({});
-  history.add('foo');
-  history.add('bar');
-  test.is('bar', history.backward());
-  test.is('foo', history.backward());
-
-  // Moving backwards past recorded history just keeps giving you the last
-  // item.
-  test.is('foo', history.backward());
-};
-
-exports.testForwardsPastIndex = function () {
-  var history = new History({});
-  history.add('foo');
-  history.add('bar');
-  test.is('bar', history.backward());
-  test.is('foo', history.backward());
-
-  // Going forward through the history again.
-  test.is('bar', history.forward());
-
-  // 'Present' time.
-  test.is('', history.forward());
-
-  // Going to the 'future' just keeps giving us the empty string.
-  test.is('', history.forward());
-};
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testRequire', ['require', 'exports', 'module' , 'test/assert', 'gclitest/requirable'], function(require, exports, module) {
-
-var test = require('test/assert');
-
-
-exports.testWorking = function() {
-  // There are lots of requirement tests that we could be doing here
-  // The fact that we can get anything at all working is a testament to
-  // require doing what it should - we don't need to test the
-  var requireable = require('gclitest/requirable');
-  test.is('thing1', requireable.thing1);
-  test.is(2, requireable.thing2);
-  test.is(undefined, requireable.thing3);
-};
-
-exports.testDomains = function() {
-  var requireable = require('gclitest/requirable');
-  test.is(undefined, requireable.status);
-  requireable.setStatus(null);
-  test.is(null, requireable.getStatus());
-  test.is(undefined, requireable.status);
-  requireable.setStatus('42');
-  test.is('42', requireable.getStatus());
-  test.is(undefined, requireable.status);
-
-  if (define.Domain) {
-    var domain = new define.Domain();
-    var requireable2 = domain.require('gclitest/requirable');
-    test.is(undefined, requireable2.status);
-    test.is('initial', requireable2.getStatus());
-    requireable2.setStatus(999);
-    test.is(999, requireable2.getStatus());
-    test.is(undefined, requireable2.status);
-
-    test.is('42', requireable.getStatus());
-    test.is(undefined, requireable.status);
-  }
-};
-
-exports.testLeakage = function() {
-  var requireable = require('gclitest/requirable');
-  test.is(undefined, requireable.setup);
-  test.is(undefined, requireable.shutdown);
-  test.is(undefined, requireable.testWorking);
-};
-
-exports.testMultiImport = function() {
-  var r1 = require('gclitest/requirable');
-  var r2 = require('gclitest/requirable');
-  test.is(r1, r2);
-};
-
-exports.testUncompilable = function() {
-  // This test is commented out because it breaks the RequireJS module
-  // loader and because it causes console output and because testing failure
-  // cases such as this is something of a luxury
-  // It's not totally clear how a module loader should perform with unusable
-  // modules, however at least it should go into a flat spin ...
-  // GCLI mini_require reports an error as it should
-  /*
-  if (define.Domain) {
-    try {
-      var unrequireable = require('gclitest/unrequirable');
-      t.fail();
-    }
-    catch (ex) {
-      console.error(ex);
-    }
-  }
-  */
-};
-
-exports.testRecursive = function() {
-  // See Bug 658583
-  /*
-  var recurse = require('gclitest/recurse');
-  */
-};
-
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/requirable', ['require', 'exports', 'module' ], function(require, exports, module) {
-
-  exports.thing1 = 'thing1';
-  exports.thing2 = 2;
-
-  var status = 'initial';
-  exports.setStatus = function(aStatus) { status = aStatus; };
-  exports.getStatus = function() { return status; };
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testResource', ['require', 'exports', 'module' , 'gcli/types/resource', 'gcli/types', 'test/assert'], function(require, exports, module) {
-
-
-var resource = require('gcli/types/resource');
-var types = require('gcli/types');
-var Status = require('gcli/types').Status;
-
-var test = require('test/assert');
-
-var tempDocument;
-
-exports.setup = function(options) {
-  tempDocument = resource.getDocument();
-  resource.setDocument(options.window.document);
-};
-
-exports.shutdown = function(options) {
-  resource.setDocument(tempDocument);
-  tempDocument = undefined;
-};
-
-exports.testPredictions = function(options) {
-  if (options.useFakeWindow) {
-    console.log('Skipping resource tests: options.useFakeWindow = true');
+exports.testDefault = function(options) {
+  if (options.isNode) {
+    test.log('Running under Node. ' +
+             'Skipping tests due to issues with resource type.');
     return;
   }
 
-  var resource1 = types.getType('resource');
-  var predictions1 = resource1.parseString('').getPredictions();
-  test.ok(predictions1.length > 1, 'have resources');
-  predictions1.forEach(function(prediction) {
-    checkPrediction(resource1, prediction);
-  });
-
-  var resource2 = types.getType({ name: 'resource', include: 'text/javascript' });
-  var predictions2 = resource2.parseString('').getPredictions();
-  test.ok(predictions2.length > 1, 'have resources');
-  predictions2.forEach(function(prediction) {
-    checkPrediction(resource2, prediction);
-  });
-
-  var resource3 = types.getType({ name: 'resource', include: 'text/css' });
-  var predictions3 = resource3.parseString('').getPredictions();
-  // jsdom fails to support digging into stylesheets
-  if (!options.isNode) {
-    test.ok(predictions3.length > 1, 'have resources');
-  }
-  predictions3.forEach(function(prediction) {
-    checkPrediction(resource3, prediction);
-  });
-
-  var resource4 = types.getType({ name: 'resource' });
-  var predictions4 = resource4.parseString('').getPredictions();
-
-  test.is(predictions1.length, predictions4.length, 'type spec');
-  test.is(predictions2.length + predictions3.length, predictions4.length, 'split');
-};
-
-function checkPrediction(res, prediction) {
-  var name = prediction.name;
-  var value = prediction.value;
-
-  var conversion = res.parseString(name);
-  test.is(conversion.getStatus(), Status.VALID, 'status VALID for ' + name);
-  test.is(conversion.value, value, 'value for ' + name);
-
-  var strung = res.stringify(value);
-  test.is(strung, name, 'stringify for ' + name);
-
-  test.is(typeof value.loadContents, 'function', 'resource for ' + name);
-  test.is(typeof value.element, 'object', 'resource for ' + name);
-}
-
-});
-/*
- * Copyright 2009-2011 Mozilla Foundation and contributors
- * Licensed under the New BSD license. See LICENSE.txt or:
- * http://opensource.org/licenses/BSD-3-Clause
- */
-
-define('gclitest/testJs', ['require', 'exports', 'module' , 'gcli/cli', 'gcli/types', 'gcli/types/javascript', 'test/assert'], function(require, exports, module) {
-
-
-var Requisition = require('gcli/cli').Requisition;
-var Status = require('gcli/types').Status;
-var javascript = require('gcli/types/javascript');
-
-var test = require('test/assert');
-
-var debug = false;
-var requ;
-
-var assign;
-var status;
-var statuses;
-var tempWindow;
-
-
-exports.setup = function(options) {
-  tempWindow = javascript.getGlobalObject();
-  javascript.setGlobalObject(options.window);
-
-  Object.defineProperty(options.window, 'donteval', {
-    get: function() {
-      test.ok(false, 'donteval should not be used');
-      return { cant: '', touch: '', 'this': '' };
-    },
-    enumerable: true,
-    configurable : true
+  types.getTypeNames().forEach(function(name) {
+    if (name === 'selection') {
+      name = { name: 'selection', data: [ 'a', 'b' ] };
+    }
+    if (name === 'deferred') {
+      name = {
+        name: 'deferred',
+        defer: function() { return types.getType('string'); }
+      };
+    }
+    if (name === 'array') {
+      name = { name: 'array', subtype: 'string' };
+    }
+    var type = types.getType(name);
+    if (type.name !== 'boolean' && type.name !== 'array') {
+      test.ok(type.getBlank().value === undefined,
+              'default defined for ' + type.name);
+    }
   });
 };
 
-exports.shutdown = function(options) {
-  delete options.window.donteval;
-
-  javascript.setGlobalObject(tempWindow);
-  tempWindow = undefined;
-};
-
-function input(typed) {
-  if (!requ) {
-    requ = new Requisition();
-  }
-  var cursor = { start: typed.length, end: typed.length };
-  var input = { typed: typed, cursor: cursor };
-  requ.update(input);
-
-  if (debug) {
-    console.log('####### TEST: typed="' + typed +
-        '" cur=' + cursor.start +
-        ' cli=', requ);
-  }
-
-  status = requ.getStatus();
-  statuses = requ.getInputStatusMarkup(input.cursor.start).map(function(s) {
-    return Array(s.string.length + 1).join(s.status.toString()[0]);
-  }).join('');
-
-  if (requ.commandAssignment.getValue()) {
-    assign = requ.getAssignment(0);
-  }
-  else {
-    assign = undefined;
-  }
-}
-
-function predictionsHas(name) {
-  return assign.getPredictions().some(function(prediction) {
-    return name === prediction.name;
-  }, this);
-}
-
-function check(expStatuses, expStatus, expAssign, expPredict) {
-  test.is('{', requ.commandAssignment.getValue().name, 'is exec');
-
-  test.is(expStatuses, statuses, 'unexpected status markup');
-  test.is(expStatus.toString(), status.toString(), 'unexpected status');
-  test.is(expAssign, assign.getValue(), 'unexpected assignment');
-
-  if (expPredict != null) {
-    var contains;
-    if (Array.isArray(expPredict)) {
-      expPredict.forEach(function(p) {
-        contains = predictionsHas(p);
-        test.ok(contains, 'missing prediction ' + p);
-      });
-    }
-    else if (typeof expPredict === 'number') {
-      contains = true;
-      test.is(assign.getPredictions().length, expPredict, 'prediction count');
-      if (assign.getPredictions().length !== expPredict) {
-        assign.getPredictions().forEach(function(prediction) {
-          console.log('actual prediction: ', prediction);
-        });
-      }
-    }
-    else {
-      contains = predictionsHas(expPredict);
-      test.ok(contains, 'missing prediction ' + expPredict);
-    }
-
-    if (!contains) {
-      console.log('Predictions: ' + assign.getPredictions().map(function(p) {
-        return p.name;
-      }).join(', '));
-    }
-  }
-}
-
-exports.testBasic = function(options) {
-  input('{');
-  check('V', Status.ERROR, '');
-
-  input('{ ');
-  check('VV', Status.ERROR, '');
-
-  input('{ w');
-  check('VVI', Status.ERROR, 'w', 'window');
-
-  input('{ windo');
-  check('VVIIIII', Status.ERROR, 'windo', 'window');
-
-  input('{ window');
-  check('VVVVVVVV', Status.VALID, 'window');
-
-  input('{ window.d');
-  check('VVIIIIIIII', Status.ERROR, 'window.d', 'window.document');
-
-  input('{ window.document.title');
-  check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0);
-
-  input('{ d');
-  check('VVI', Status.ERROR, 'd', 'document');
-
-  input('{ document.title');
-  check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0);
-
-  test.ok('donteval' in options.window, 'donteval exists');
-
-  input('{ don');
-  check('VVIII', Status.ERROR, 'don', 'donteval');
-
-  input('{ donteval');
-  check('VVVVVVVVVV', Status.VALID, 'donteval', 0);
-
-  /*
-  // This is a controversial test - technically we can tell that it's an error
-  // because 'donteval.' is a syntax error, however donteval is unsafe so we
-  // are playing safe by bailing out early. It's enough of a corner case that
-  // I don't think it warrants fixing
-  input('{ donteval.');
-  check('VVIIIIIIIII', Status.ERROR, 'donteval.', 0);
-  */
-
-  input('{ donteval.cant');
-  check('VVVVVVVVVVVVVVV', Status.VALID, 'donteval.cant', 0);
-
-  input('{ donteval.xxx');
-  check('VVVVVVVVVVVVVV', Status.VALID, 'donteval.xxx', 0);
-};
-
-
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 define('gclitest/testUtil', ['require', 'exports', 'module' , 'gcli/util', 'test/assert'], function(require, exports, module) {
 
 var util = require('gcli/util');
 var test = require('test/assert');
 
 exports.testFindCssSelector = function(options) {
-  if (options.useFakeWindow) {
-    console.log('Skipping dom.findCssSelector tests due to useFakeWindow');
+  if (options.window.isFake) {
+    test.log('Skipping dom.findCssSelector tests due to window.isFake');
     return;
   }
 
   var nodes = options.window.document.querySelectorAll('*');
   for (var i = 0; i < nodes.length; i++) {
-    var selector = util.dom.findCssSelector(nodes[i]);
+    var selector = util.findCssSelector(nodes[i]);
     var matches = options.window.document.querySelectorAll(selector);
 
     test.is(matches.length, 1, 'multiple matches for ' + selector);
     test.is(matches[0], nodes[i], 'non-matching selector: ' + selector);
   }
 };
 
 
 });
 
-function undefine() {
-  delete define.modules['gclitest/index'];
-  delete define.modules['gclitest/suite'];
-  delete define.modules['test/examiner'];
-  delete define.modules['gclitest/testTokenize'];
-  delete define.modules['test/assert'];
-  delete define.modules['gclitest/testSplit'];
-  delete define.modules['gclitest/commands'];
-  delete define.modules['gclitest/testCli'];
-  delete define.modules['gclitest/testExec'];
-  delete define.modules['gclitest/testKeyboard'];
-  delete define.modules['gclitest/testScratchpad'];
-  delete define.modules['gclitest/testHistory'];
-  delete define.modules['gclitest/testRequire'];
-  delete define.modules['gclitest/requirable'];
-  delete define.modules['gclitest/testResource'];
-  delete define.modules['gclitest/testJs'];
-  delete define.modules['gclitest/testUtil'];
-
-  delete define.globalDomain.modules['gclitest/index'];
-  delete define.globalDomain.modules['gclitest/suite'];
-  delete define.globalDomain.modules['test/examiner'];
-  delete define.globalDomain.modules['gclitest/testTokenize'];
-  delete define.globalDomain.modules['test/assert'];
-  delete define.globalDomain.modules['gclitest/testSplit'];
-  delete define.globalDomain.modules['gclitest/commands'];
-  delete define.globalDomain.modules['gclitest/testCli'];
-  delete define.globalDomain.modules['gclitest/testExec'];
-  delete define.globalDomain.modules['gclitest/testKeyboard'];
-  delete define.globalDomain.modules['gclitest/testScratchpad'];
-  delete define.globalDomain.modules['gclitest/testHistory'];
-  delete define.globalDomain.modules['gclitest/testRequire'];
-  delete define.globalDomain.modules['gclitest/requirable'];
-  delete define.globalDomain.modules['gclitest/testResource'];
-  delete define.globalDomain.modules['gclitest/testJs'];
-  delete define.globalDomain.modules['gclitest/testUtil'];
+let testModuleNames = [
+  'gclitest/index',
+  'gclitest/suite',
+  'test/examiner',
+  'gclitest/testCli',
+  'gclitest/commands',
+  'test/assert',
+  'gclitest/testCompletion',
+  'gclitest/testExec',
+  'gclitest/testHistory',
+  'gclitest/testJs',
+  'gclitest/testKeyboard',
+  'gclitest/testRequire',
+  'gclitest/requirable',
+  'gclitest/testResource',
+  'gclitest/testScratchpad',
+  'gclitest/testSpell',
+  'gclitest/testSplit',
+  'gclitest/testTokenize',
+  'gclitest/testTooltip',
+  'gclitest/testTypes',
+  'gclitest/testUtil',
+];
+
+// Cached so it still exists during cleanup until we need it to
+let localDefine;
+
+const TEST_URI = "data:text/html;charset=utf-8,gcli-web";
+
+function test() {
+  localDefine = define;
+
+  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
+    var gclitest = define.globalDomain.require("gclitest/index");
+    gclitest.run({
+      display: DeveloperToolbar.display,
+      // window: browser.getBrowser().contentWindow
+    });
+
+    finish();
+  });
 }
 
 registerCleanupFunction(function() {
-  Services.prefs.clearUserPref("devtools.gcli.enable");
-  undefine();
-  obj = undefined;
-  define = undefined;
-  console = undefined;
-  Node = undefined;
+  testModuleNames.forEach(function(moduleName) {
+    delete localDefine.modules[moduleName];
+    delete localDefine.globalDomain.modules[moduleName];
+  });
+
+  localDefine = undefined;
 });
-
-function test() {
-  Services.prefs.setBoolPref("devtools.gcli.enable", true);
-  addTab("http://example.com/browser/browser/devtools/webconsole/test/test-console.html");
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
-}
-
-function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
-  var failed = false;
-
-  try {
-    openConsole();
-
-    var gcliterm = HUDService.getHudByWindow(content).gcliterm;
-
-    var gclitest = define.globalDomain.require("gclitest/index");
-    gclitest.run({
-      window: gcliterm.document.defaultView,
-      inputter: gcliterm.opts.console.inputter,
-      requisition: gcliterm.opts.requistion
-    });
-  }
-  catch (ex) {
-    failed = ex;
-    console.error("Test Failure", ex);
-    ok(false, "" + ex);
-  }
-  finally {
-    closeConsole();
-    finish();
-  }
-
-  if (failed) {
-    throw failed;
-  }
-}
--- a/browser/devtools/shared/test/browser_templater_basic.js
+++ b/browser/devtools/shared/test/browser_templater_basic.js
@@ -1,18 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the DOM Template engine works properly
 
-let tempScope = {};
-Cu.import("resource:///modules/devtools/Templater.jsm", tempScope);
-Cu.import("resource:///modules/devtools/Promise.jsm", tempScope);
-let template = tempScope.template;
-let Promise = tempScope.Promise;
+/*
+ * These tests run both in Mozilla/Mochitest and plain browsers (as does
+ * domtemplate)
+ * We should endevour to keep the source in sync.
+ */
+
+var imports = {};
+Cu.import("resource:///modules/devtools/Templater.jsm", imports);
+Cu.import("resource:///modules/devtools/Promise.jsm", imports);
 
 function test() {
   addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() {
     info("Starting DOM Templater Tests");
     runTest(0);
   });
 }
 
@@ -20,17 +24,17 @@ function runTest(index) {
   var options = tests[index] = tests[index]();
   var holder = content.document.createElement('div');
   holder.id = options.name;
   var body = content.document.body;
   body.appendChild(holder);
   holder.innerHTML = options.template;
 
   info('Running ' + options.name);
-  template(holder, options.data, options.options);
+  imports.template(holder, options.data, options.options);
 
   if (typeof options.result == 'string') {
     is(holder.innerHTML, options.result, options.name);
   }
   else {
     ok(holder.innerHTML.match(options.result), options.name);
   }
 
@@ -233,18 +237,50 @@ var tests = [
     options: { allowEval: true },
     result: '<p>2</p>'
   };},
 
   function() { return {
     name: 'propertyFail',
     template: '<p>${Math.max(1, 2)}</p>',
     result: '<p>${Math.max(1, 2)}</p>'
+  };},
+
+  // Bug 723431: DOMTemplate should allow customisation of display of
+  // null/undefined values
+  function() { return {
+    name: 'propertyUndefAttrFull',
+    template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>',
+    data: { nullvar: null, undefinedvar1: undefined },
+    result: '<p>null|undefined|undefined</p>'
+  };},
+
+  function() { return {
+    name: 'propertyUndefAttrBlank',
+    template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>',
+    data: { nullvar: null, undefinedvar1: undefined },
+    options: { blankNullUndefined: true },
+    result: '<p>||</p>'
+  };},
+
+  function() { return {
+    name: 'propertyUndefAttrFull',
+    template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>',
+    data: { nullvar: null, undefinedvar1: undefined },
+    result: '<div><p value="null"></p><p value="undefined"></p><p value="undefined"></p></div>'
+  };},
+
+  function() { return {
+    name: 'propertyUndefAttrBlank',
+    template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>',
+    data: { nullvar: null, undefinedvar1: undefined },
+    options: { blankNullUndefined: true },
+    result: '<div><p value=""></p><p value=""></p><p value=""></p></div>'
   };}
 ];
 
 function delayReply(data) {
-  var p = new Promise();
+  var p = new imports.Promise();
   executeSoon(function() {
     p.resolve(data);
   });
   return p;
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_toolbar_basic.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Developer Toolbar Tests</title>
+  <style type="text/css">
+  #single { color: red; }
+  </style>
+  <script type="text/javascript">var a=1;</script>
+</head>
+<body>
+
+<p id=single>
+1
+</p>
+
+<p class=twin>
+2a
+</p>
+
+<p class=twin>
+2b
+</p>
+
+<style>
+.twin { color: blue; }
+</style>
+<script>var b=2;</script>
+
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_toolbar_basic.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the developer toolbar works properly
+
+const URL = "http://example.com/browser/browser/devtools/shared/test/browser_toolbar_basic.html";
+
+function test() {
+  addTab(URL, function(browser, tab) {
+    info("Starting browser_toolbar_basic.js");
+    runTest();
+  });
+}
+
+function runTest() {
+  Services.obs.addObserver(checkOpen, DeveloperToolbar.NOTIFICATIONS.SHOW, false);
+  // TODO: reopen the window so the pref has a chance to take effect
+  // EventUtils.synthesizeKey("v", { ctrlKey: true, shiftKey: true });
+  DeveloperToolbarTest.show();
+}
+
+function checkOpen() {
+  Services.obs.removeObserver(checkOpen, DeveloperToolbar.NOTIFICATIONS.SHOW, false);
+  ok(DeveloperToolbar.visible, "DeveloperToolbar is visible");
+
+  Services.obs.addObserver(checkClosed, DeveloperToolbar.NOTIFICATIONS.HIDE, false);
+  // EventUtils.synthesizeKey("v", { ctrlKey: true, shiftKey: true });
+  DeveloperToolbarTest.hide();
+}
+
+function checkClosed() {
+  Services.obs.removeObserver(checkClosed, DeveloperToolbar.NOTIFICATIONS.HIDE, false);
+  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible");
+
+  finish();
+}
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -31,37 +31,282 @@
  * 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 ***** */
 
-let tab;
-let browser;
+let gcli;
+let console;
+let require;
+let define;
+
+(function() {
+  let tempScope = {};
+  Components.utils.import("resource:///modules/gcli.jsm", tempScope);
 
+  gcli = tempScope.gcli;
+  console = gcli._internal.console;
+  define = gcli._internal.define;
+  require = gcli._internal.require;
+})();
+
+/**
+ * Open a new tab at a URL and call a callback on load
+ */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
-  function onTabLoad() {
-    browser.removeEventListener("load", onTabLoad, true);
-    aCallback();
-  }
-
   gBrowser.selectedTab = gBrowser.addTab();
   content.location = aURL;
 
-  tab = gBrowser.selectedTab;
-  browser = gBrowser.getBrowserForTab(tab);
+  let tab = gBrowser.selectedTab;
+  let browser = gBrowser.getBrowserForTab(tab);
+
+  function onTabLoad() {
+    browser.removeEventListener("load", onTabLoad, true);
+    aCallback(browser, tab);
+  }
 
   browser.addEventListener("load", onTabLoad, true);
 }
 
 registerCleanupFunction(function tearDown() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 
-  tab = undefined;
-  browser = undefined;
+  console = undefined;
+  define = undefined;
+  require = undefined;
+  gcli = undefined;
 });
+
+/**
+ * Various functions for testing DeveloperToolbar
+ */
+let DeveloperToolbarTest = {
+  /**
+   * Paranoid DeveloperToolbar.show();
+   */
+  show: function DTT_show() {
+    if (DeveloperToolbar.visible) {
+      ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
+    }
+    else {
+      DeveloperToolbar.show();
+    }
+  },
+
+  /**
+   * Paranoid DeveloperToolbar.hide();
+   */
+  hide: function DTT_hide() {
+    if (!DeveloperToolbar.visible) {
+      ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar");
+    }
+    else {
+      DeveloperToolbar.display.inputter.setInput("");
+      DeveloperToolbar.hide();
+    }
+  },
+
+  /**
+   * Check that we can parse command input.
+   * Doesn't execute the command, just checks that we grok the input properly:
+   * 
+   * DeveloperToolbarTest.checkInputStatus({
+   *   // Test inputs
+   *   typed: "ech",           // Required
+   *   cursor: 3,              // Optional cursor position
+   *
+   *   // Thing to check
+   *   status: "INCOMPLETE",   // One of "VALID", "ERROR", "INCOMPLETE"
+   *   emptyParameters: [ "<message>" ], // Still to type
+   *   directTabText: "o",     // Simple completion text
+   *   arrowTabText: "",       // When the completion is not an extension
+   * });
+   */
+  checkInputStatus: function DTT_checkInputStatus(test) {
+    if (test.typed) {
+      DeveloperToolbar.display.inputter.setInput(test.typed);
+    }
+    else {
+     ok(false, "Missing typed for " + JSON.stringify(test));
+     return;
+    }
+
+    if (test.cursor) {
+      DeveloperToolbar.display.inputter.setCursor(test.cursor)
+    }
+
+    if (test.status) {
+      is(DeveloperToolbar.display.requisition.getStatus().toString(),
+         test.status,
+         "status for " + test.typed);
+    }
+
+    if (test.emptyParameters == null) {
+      test.emptyParameters = [];
+    }
+
+    let completer = DeveloperToolbar.display.completer;
+    let realParams = completer.emptyParameters;
+    is(realParams.length, test.emptyParameters.length,
+       'emptyParameters.length for \'' + test.typed + '\'');
+
+    if (realParams.length === test.emptyParameters.length) {
+      for (let i = 0; i < realParams.length; i++) {
+        is(realParams[i].replace(/\u00a0/g, ' '), test.emptyParameters[i],
+           'emptyParameters[' + i + '] for \'' + test.typed + '\'');
+      }
+    }
+
+    if (test.directTabText) {
+      is(completer.directTabText, test.directTabText,
+         'directTabText for \'' + test.typed + '\'');
+    }
+    else {
+      is(completer.directTabText, '', 'directTabText for \'' + test.typed + '\'');
+    }
+
+    if (test.arrowTabText) {
+      is(completer.arrowTabText, ' \u00a0\u21E5 ' + test.arrowTabText,
+         'arrowTabText for \'' + test.typed + '\'');
+    }
+    else {
+      is(completer.arrowTabText, '', 'arrowTabText for \'' + test.typed + '\'');
+    }
+  },
+
+  /**
+   * Execute a command:
+   *
+   * DeveloperToolbarTest.exec({
+   *   // Test inputs
+   *   typed: "echo hi",        // Optional, uses existing if undefined
+   *
+   *   // Thing to check
+   *   args: { message: "hi" }, // Check that the args were understood properly
+   *   outputMatch: /^hi$/,     // Regex to test against textContent of output
+   *   blankOutput: true,       // Special checks when there is no output
+   * });
+   */
+  exec: function DTT_exec(test) {
+    test = test || {};
+
+    if (test.typed) {
+      DeveloperToolbar.display.inputter.setInput(test.typed);
+    }
+
+    let typed = DeveloperToolbar.display.inputter.getInputState().typed;
+    let output = DeveloperToolbar.display.requisition.exec();
+
+    is(typed, output.typed, 'output.command for: ' + typed);
+
+    if (test.completed !== false) {
+      ok(output.completed, 'output.completed false for: ' + typed);
+    }
+    else {
+      // It is actually an error if we say something is async and it turns
+      // out not to be? For now we're saying 'no'
+      // ok(!output.completed, 'output.completed true for: ' + typed);
+    }
+
+    if (test.args != null) {
+      is(Object.keys(test.args).length, Object.keys(output.args).length,
+         'arg count for ' + typed);
+
+      Object.keys(output.args).forEach(function(arg) {
+        let expectedArg = test.args[arg];
+        let actualArg = output.args[arg];
+
+        if (Array.isArray(expectedArg)) {
+          if (!Array.isArray(actualArg)) {
+            ok(false, 'actual is not an array. ' + typed + '/' + arg);
+            return;
+          }
+
+          is(expectedArg.length, actualArg.length,
+             'array length: ' + typed + '/' + arg);
+          for (let i = 0; i < expectedArg.length; i++) {
+            is(expectedArg[i], actualArg[i],
+               'member: "' + typed + '/' + arg + '/' + i);
+          }
+        }
+        else {
+          is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg);
+        }
+      });
+    }
+
+    let displayed = DeveloperToolbar.outputPanel._div.textContent;
+
+    if (test.outputMatch) {
+      if (!test.outputMatch.test(displayed)) {
+        ok(false, "html output for " + typed + " (textContent sent to info)");
+        info("Actual textContent");
+        info(displayed);
+      }
+    }
+
+    if (test.blankOutput != null) {
+      if (!/^$/.test(displayed)) {
+        ok(false, "html output for " + typed + " (textContent sent to info)");
+        info("Actual textContent");
+        info(displayed);
+      }
+    }
+  },
+
+  /**
+   * Quick wrapper around the things you need to do to run DeveloperToolbar
+   * command tests:
+   * - Set the pref 'devtools.toolbar.enabled' to true
+   * - Add a tab pointing at |uri|
+   * - Open the DeveloperToolbar
+   * - Register a cleanup function to undo the above
+   * - Run the tests
+   *
+   * @param uri The uri of a page to load. Can be 'about:blank' or 'data:...'
+   * @param testFunc A function containing the tests to run. This should
+   * arrange for 'finish()' to be called on completion.
+   */
+  test: function DTT_test(uri, testFunc) {
+    let menuItem = document.getElementById("menu_devToolbar");
+    let command = document.getElementById("Tools:DevToolbar");
+    let appMenuItem = document.getElementById("appmenu_devToolbar");
+
+    registerCleanupFunction(function() {
+      DeveloperToolbarTest.hide();
+
+      // a.k.a Services.prefs.clearUserPref("devtools.toolbar.enabled");
+      if (menuItem) {
+        menuItem.hidden = true;
+      }
+      if (command) {
+        command.setAttribute("disabled", "true");
+      }
+      if (appMenuItem) {
+        appMenuItem.hidden = true;
+      }
+    });
+
+    // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true);
+    if (menuItem) {
+      menuItem.hidden = false;
+    }
+    if (command) {
+      command.removeAttribute("disabled");
+    }
+    if (appMenuItem) {
+      appMenuItem.hidden = false;
+    }
+
+    addTab(uri, function(browser, tab) {
+      DeveloperToolbarTest.show();
+      testFunc(browser, tab);
+    });
+  },
+};
--- a/browser/devtools/webconsole/GcliCommands.jsm
+++ b/browser/devtools/webconsole/GcliCommands.jsm
@@ -96,22 +96,34 @@ gcli.addCommand({
 
 /**
  * 'console close' command
  */
 gcli.addCommand({
   name: "console close",
   description: gcli.lookup("consolecloseDesc"),
   exec: function Command_consoleClose(args, context) {
-    let tab = HUDService.getHudReferenceById(context.environment.hudId).tab;
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
     HUDService.deactivateHUDForContext(tab);
   }
 });
 
 /**
+ * 'console open' command
+ */
+gcli.addCommand({
+  name: "console open",
+  description: gcli.lookup("consoleopenDesc"),
+  exec: function Command_consoleOpen(args, context) {
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
+    HUDService.activateHUDForContext(tab);
+  }
+});
+
+/**
  * 'inspect' command
  */
 gcli.addCommand({
   name: "inspect",
   description: gcli.lookup("inspectDesc"),
   manual: gcli.lookup("inspectManual"),
   params: [
     {
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -146,34 +146,16 @@ function LogFactory(aMessagePrefix)
 {
   function log(aMessage) {
     var _msg = aMessagePrefix + " " + aMessage + "\n";
     dump(_msg);
   }
   return log;
 }
 
-/**
- * Load the various Command JSMs.
- * Should be called when the console first opens.
- *
- * @return an object containing the EXPORTED_SYMBOLS from all the command
- * modules. In general there is no reason when JSMs need to export symbols
- * except when they need the host environment to inform them of things like the
- * current window/document/etc.
- */
-function loadCommands() {
-  let commandExports = {};
-
-  Cu.import("resource:///modules/GcliCommands.jsm", commandExports);
-  Cu.import("resource:///modules/GcliTiltCommands.jsm", commandExports);
-
-  return commandExports;
-}
-
 let log = LogFactory("*** HUDService:");
 
 const HUD_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 
 XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
   return Services.strings.createBundle(HUD_STRINGS_URI);
 });
 
@@ -1565,16 +1547,17 @@ HUD_SERVICE.prototype =
 
       let root = aContext.ownerDocument.getElementsByTagName('window')[0];
       root.parentNode.insertBefore(procInstr, root);
       aContext.ownerDocument.gcliCssProcInstr = procInstr;
     }
     if (procInstr.contexts.indexOf(hudId) == -1) {
       procInstr.contexts.push(hudId);
     }
+    HeadsUpDisplayUICommands.refreshCommand();
   },
 
   /**
    * Deactivate a HeadsUpDisplay for the given tab context.
    *
    * @param nsIDOMWindow aContext
    * @param aAnimated animate closing the web console?
    * @returns void
@@ -1595,16 +1578,17 @@ HUD_SERVICE.prototype =
 
       let hud = this.hudReferences[hudId];
       browser.webProgress.removeProgressListener(hud.progressListener);
       delete hud.progressListener;
 
       this.unregisterDisplay(hudId);
 
       window.focus();
+      HeadsUpDisplayUICommands.refreshCommand();
     }
 
     // Remove this context from the list of contexts that need the GCLI CSS
     // processing instruction and then remove the processing instruction if it
     // isn't needed any more.
     let procInstr = aContext.ownerDocument.gcliCssProcInstr;
     if (procInstr) {
       procInstr.contexts = procInstr.contexts.filter(function(id) {
@@ -3609,17 +3593,17 @@ HeadsUpDisplay.prototype = {
    * @param nsIDOMWindow aWindow
    * @returns void
    */
   createConsoleInput:
   function HUD_createConsoleInput(aWindow, aParentNode, aExistingConsole)
   {
     let usegcli = false;
     try {
-      usegcli = Services.prefs.getBoolPref("devtools.gcli.enable");
+      // usegcli = Services.prefs.getBoolPref("devtools.gcli.enable");
     }
     catch (ex) {}
 
     if (appName() == "FIREFOX") {
       if (!usegcli) {
         let context = Cu.getWeakReference(aWindow);
         let mixin = new JSTermFirefoxMixin(context, aParentNode,
                                            aExistingConsole);
@@ -6330,16 +6314,26 @@ ConsoleUtils = {
   }
 };
 
 //////////////////////////////////////////////////////////////////////////
 // HeadsUpDisplayUICommands
 //////////////////////////////////////////////////////////////////////////
 
 HeadsUpDisplayUICommands = {
+  refreshCommand: function UIC_refreshCommand() {
+    var window = HUDService.currentContext();
+    let command = window.document.getElementById("Tools:WebConsole");
+    if (this.getOpenHUD() != null) {
+      command.setAttribute("checked", true);
+    } else {
+      command.removeAttribute("checked");
+    }
+  },
+
   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 ownerDocument = gBrowser.selectedTab.ownerDocument;
     var hud = ownerDocument.getElementById(hudId);
@@ -6830,136 +6824,121 @@ function GcliTerm(aContentWindow, aHudId
   this.document = aDocument;
   this.console = aConsole;
   this.hintNode = aHintNode;
   this._window = this.context.get().QueryInterface(Ci.nsIDOMWindow);
 
   this.createUI();
   this.createSandbox();
 
-  this.show = this.show.bind(this);
-  this.hide = this.hide.bind(this);
-
-  // Allow GCLI:Inputter to decide how and when to open a scratchpad window
-  let scratchpad = {
-    shouldActivate: function Scratchpad_shouldActivate(aEvent) {
-      return aEvent.shiftKey &&
-          aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
-    },
-    activate: function Scratchpad_activate(aValue) {
-      aValue = aValue.replace(/^\s*{\s*/, '');
-      ScratchpadManager.openScratchpad({ text: aValue });
-      return true;
-    },
-    linkText: stringBundle.GetStringFromName('scratchpad.linkText')
-  };
-
-  this.opts = {
+  this.gcliConsole = gcli._internal.createDisplay({
+    contentDocument: aContentWindow.document,
+    chromeDocument: this.document,
+    outputDocument: this.document,
+    chromeWindow: this.document.defaultView,
+
+    hintElement: this.hintNode,
+    inputElement: this.inputNode,
+    completeElement: this.completeNode,
+    backgroundElement: this.inputStack,
+    consoleWrap: aConsoleWrap,
+
+    eval: this.evalInSandbox.bind(this),
+
     environment: {
-      hudId: this.hudId,
       chromeDocument: this.document,
       contentDocument: aContentWindow.document
     },
-    chromeDocument: this.document,
-    contentDocument: aContentWindow.document,
-    jsEnvironment: {
-      globalObject: unwrap(aContentWindow),
-      evalFunction: this.evalInSandbox.bind(this)
+
+    tooltipClass: 'gcliterm-tooltip',
+
+    // Allow GCLI:Inputter to decide how and when to open a scratchpad window
+    scratchpad: {
+      shouldActivate: function Scratchpad_shouldActivate(aEvent) {
+        return aEvent.shiftKey &&
+            aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
+      },
+      activate: function Scratchpad_activate(aValue) {
+        aValue = aValue.replace(/^\s*{\s*/, '');
+        ScratchpadManager.openScratchpad({ text: aValue });
+        return true;
+      },
+      linkText: stringBundle.GetStringFromName('scratchpad.linkText')
     },
-    inputElement: this.inputNode,
-    completeElement: this.completeNode,
-    inputBackgroundElement: this.inputStack,
-    hintElement: this.hintNode,
-    consoleWrap: aConsoleWrap,
-    scratchpad: scratchpad,
-    gcliTerm: this
-  };
-
-  gcli._internal.commandOutputManager.addListener(this.onCommandOutput, this);
-  gcli._internal.createView(this.opts);
-
-  if (!commandExports) {
-    commandExports = loadCommands();
-  }
+  });
+
+  this.gcliConsole.onVisibilityChange.add(this.onVisibilityChange, this);
+  this.gcliConsole.onOutput.add(this.onOutput, this);
 }
 
 GcliTerm.prototype = {
   /**
-   * Remove the hint column from the display.
-   */
-  hide: function GcliTerm_hide()
-  {
-    let permaHint = false;
-    try {
-      permaHint = Services.prefs.getBoolPref("devtools.gcli.permaHint");
-    }
-    catch (ex) {}
-
-    if (!permaHint) {
-      this.hintNode.parentNode.hidden = true;
-    }
-  },
-
-  /**
-   * Undo the effects of calling hide().
-   */
-  show: function GcliTerm_show()
-  {
-    this.hintNode.parentNode.hidden = false;
+   * Show or remove the hint column from the display.
+   */
+  onVisibilityChange: function GcliTerm_onVisibilityChange(ev)
+  {
+    if (ev.visible) {
+      this.hintNode.parentNode.hidden = false;
+    }
+    else {
+      let permaHint = false;
+      try {
+        permaHint = Services.prefs.getBoolPref("devtools.gcli.permaHint");
+      }
+      catch (ex) {}
+
+      if (!permaHint) {
+        this.hintNode.parentNode.hidden = true;
+      }
+    }
   },
 
   /**
    * Destroy the GcliTerm object. Call this method to avoid memory leaks.
    */
   destroy: function Gcli_destroy()
   {
-    gcli._internal.removeView(this.opts);
-    gcli._internal.commandOutputManager.removeListener(this.onCommandOutput, this);
-
-    delete this.opts.chromeDocument;
-    delete this.opts.inputElement;
-    delete this.opts.completeElement;
-    delete this.opts.inputBackgroundElement;
-    delete this.opts.hintElement;
-    delete this.opts.contentDocument;
-    delete this.opts.jsEnvironment;
-    delete this.opts.gcliTerm;
+    this.gcliConsole.onVisibilityChange.remove(this.onVisibilityChange, this);
+    this.gcliConsole.onOutput.remove(this.onOutput, this);
+    this.gcliConsole.destroy();
 
     delete this.context;
     delete this.document;
     delete this.console;
     delete this.hintNode;
     delete this._window;
 
     delete this.sandbox;
-    delete this.element
-    delete this.inputStack
-    delete this.completeNode
-    delete this.inputNode
+    delete this.element;
+    delete this.inputStack;
+    delete this.completeNode;
+    delete this.inputNode;
   },
 
   /**
    * Re-attaches a console when the contentWindow is recreated.
    *
    * @param nsIDOMWindow aContentWindow
    *        The content window that we're providing as the context to commands
    * @param object aConsole
    *        Console object to use within the GcliTerm.
    */
   reattachConsole: function Gcli_reattachConsole(aContentWindow, aConsole)
   {
     this.context = Cu.getWeakReference(aContentWindow);
     this.console = aConsole;
     this.createSandbox();
 
-    this.opts.environment.contentDocument = aContentWindow.document;
-    this.opts.contentDocument = aContentWindow.document;
-    this.opts.jsEnvironment.globalObject = unwrap(aContentWindow);
-
-    gcli._internal.reattachConsole(this.opts);
+    this.gcliConsole.reattach({
+      contentDocument: aContentWindow.document,
+      environment: {
+        chromeDocument: this.document,
+        contentDocument: aContentWindow.document
+      },
+    });
   },
 
   /**
    * Generates and attaches the GCLI Terminal part of the Web Console, which
    * essentially consists of the interactive JavaScript input facility.
    */
   createUI: function Gcli_createUI()
   {
@@ -6980,17 +6959,17 @@ GcliTerm.prototype = {
     this.inputNode.setAttribute("class", "gcliterm-input-node");
     this.inputNode.setAttribute("rows", "1");
     this.inputStack.appendChild(this.inputNode);
   },
 
   /**
    * Called by GCLI/canon when command line output changes.
    */
-  onCommandOutput: function Gcli_onCommandOutput(aEvent)
+  onOutput: function Gcli_onOutput(aEvent)
   {
     // When we can update the history of the console, then we should stop
     // filtering incomplete reports.
     if (!aEvent.output.completed) {
       return;
     }
 
     this.writeOutput(aEvent.output.typed, CATEGORY_INPUT);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/gcli.css
@@ -0,0 +1,74 @@
+/* ***** 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 the GCLI.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker <jwalker@mozilla.com> (original author)
+ *
+ * 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 ***** */
+
+.gclichrome-output {
+  max-width: 350px;
+}
+
+.gclichrome-tooltip {
+  max-width: 350px;
+}
+
+.gcli-help-name {
+  text-align: end;
+}
+
+.gcli-help-synopsis {
+  cursor: pointer;
+  display: inline-block;
+}
+
+.gcli-help-synopsis:before {
+  content: '\bb';
+}
+
+.gcli-menu-option {
+  overflow: hidden;
+  white-space: nowrap;
+  cursor: pointer;
+}
+
+.gcli-menu-template {
+  border-collapse: collapse;
+  width: 100%;
+}
+
+.gcli-menu-error {
+  overflow: hidden;
+  white-space: nowrap;
+}
+
--- a/browser/devtools/webconsole/gcli.jsm
+++ b/browser/devtools/webconsole/gcli.jsm
@@ -31,17 +31,17 @@
  * - mini_require: A very basic commonjs AMD (Asynchronous Modules Definition)
  *   'require' implementation (which is just good enough to load GCLI). For
  *   more, see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition.
  *   This alleviates the need for requirejs (http://requirejs.org/) which is
  *   used when running in the browser. This code is provided by dryice.
  * - A build of GCLI itself, packaged using dryice
  * - suffix-gcli.jsm - code to require the gcli object for EXPORTED_SYMBOLS.
  *
- * See Makefile.dryice.js for more details of this build.
+ * See gcli.js for more details of this build.
  * For more details on dryice, see the https://github.com/mozilla/dryice
  *
  *******************************************************************************
  *
  *
  *
  *
  *
@@ -52,20 +52,21 @@
  */
 
 ///////////////////////////////////////////////////////////////////////////////
 
 var EXPORTED_SYMBOLS = [ "gcli" ];
 
 
 /**
- * Expose a Node object. This allows us to use the Node constants without
- * resorting to hardcoded numbers
+ * Expose Node/HTMLElement objects. This allows us to use the Node constants
+ * without resorting to hardcoded numbers
  */
 var Node = Components.interfaces.nsIDOMNode;
+var HTMLElement = Components.interfaces.nsIDOMHTMLElement;
 
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /**
  * Define setTimeout and clearTimeout to match the browser functions
  */
 var setTimeout;
@@ -223,17 +224,17 @@ var console = {};
     }
 
     if (aThing === null) {
       return "null";
     }
 
     if (typeof aThing == "object") {
       var type = getCtorName(aThing);
-      if (type == "XULElement") {
+      if (aThing instanceof Node && aThing.tagName) {
         return debugElement(aThing);
       }
       type = (type == "Object" ? "" : type + " ");
       var json;
       try {
         json = JSON.stringify(aThing);
       }
       catch (ex) {
@@ -287,18 +288,18 @@ var console = {};
 
     if (typeof aThing == "object") {
       var reply = "";
       var type = getCtorName(aThing);
       if (type == "Error") {
         reply += "  " + aThing.message + "\n";
         reply += logProperty("stack", aThing.stack);
       }
-      else if (type == "XULElement") {
-        reply += "  " + debugElement(aThing) + " (XUL)\n";
+      else if (aThing instanceof Node && aThing.tagName) {
+        reply += "  " + debugElement(aThing) + "\n";
       }
       else {
         var keys = Object.getOwnPropertyNames(aThing);
         if (keys.length > 0) {
           reply += type + "\n";
           keys.forEach(function(aProp) {
             reply += logProperty(aProp, aThing[aProp]);
           }, this);
@@ -686,39 +687,47 @@ var mozl10n = {};
     }
     catch (ex) {
       throw new Error("Failure in lookupFormat('" + name + "')");
     }
   };
 
 })(mozl10n);
 
-define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/cli', 'gcli/commands/help', 'gcli/ui/console'], function(require, exports, module) {
+define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/types/selection', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/help', 'gcli/ui/ffdisplay'], function(require, exports, module) {
 
   // The API for use by command authors
   exports.addCommand = require('gcli/canon').addCommand;
   exports.removeCommand = require('gcli/canon').removeCommand;
   exports.lookup = mozl10n.lookup;
   exports.lookupFormat = mozl10n.lookupFormat;
 
   // Internal startup process. Not exported
   require('gcli/types/basic').startup();
+  require('gcli/types/command').startup();
   require('gcli/types/javascript').startup();
   require('gcli/types/node').startup();
   require('gcli/types/resource').startup();
-  require('gcli/cli').startup();
+  require('gcli/types/setting').startup();
+  require('gcli/types/selection').startup();
+
+  require('gcli/settings').startup();
+  require('gcli/ui/intro').startup();
+  require('gcli/ui/focus').startup();
+  require('gcli/ui/fields/basic').startup();
+  require('gcli/ui/fields/javascript').startup();
+  require('gcli/ui/fields/selection').startup();
+
   require('gcli/commands/help').startup();
 
-  var Requisition = require('gcli/cli').Requisition;
-  var Console = require('gcli/ui/console').Console;
-
-  var cli = require('gcli/cli');
-  var jstype = require('gcli/types/javascript');
-  var nodetype = require('gcli/types/node');
-  var resource = require('gcli/types/resource');
+  // Some commands require customizing for Firefox before we include them
+  // require('gcli/cli').startup();
+  // require('gcli/commands/pref').startup();
+
+  var FFDisplay = require('gcli/ui/ffdisplay').FFDisplay;
 
   /**
    * API for use by HUDService only.
    * This code is internal and subject to change without notice.
    */
   exports._internal = {
     require: require,
     define: define,
@@ -729,65 +738,22 @@ define('gcli/index', ['require', 'export
      * members:
      * - contentDocument: From the window of the attached tab
      * - chromeDocument: GCLITerm.document
      * - environment.hudId: GCLITerm.hudId
      * - jsEnvironment.globalObject: 'window'
      * - jsEnvironment.evalFunction: 'eval' in a sandbox
      * - inputElement: GCLITerm.inputNode
      * - completeElement: GCLITerm.completeNode
-     * - gcliTerm: GCLITerm
      * - hintElement: GCLITerm.hintNode
      * - inputBackgroundElement: GCLITerm.inputStack
      */
-    createView: function(opts) {
-      jstype.setGlobalObject(opts.jsEnvironment.globalObject);
-      nodetype.setDocument(opts.contentDocument);
-      cli.setEvalFunction(opts.jsEnvironment.evalFunction);
-      resource.setDocument(opts.contentDocument);
-
-      if (opts.requisition == null) {
-        opts.requisition = new Requisition(opts.environment, opts.chromeDocument);
-      }
-
-      opts.console = new Console(opts);
-    },
-
-    /**
-     * Called when the page to which we're attached changes
-     */
-    reattachConsole: function(opts) {
-      jstype.setGlobalObject(opts.jsEnvironment.globalObject);
-      nodetype.setDocument(opts.contentDocument);
-      cli.setEvalFunction(opts.jsEnvironment.evalFunction);
-
-      opts.requisition.environment = opts.environment;
-      opts.requisition.document = opts.chromeDocument;
-
-      opts.console.reattachConsole(opts);
-    },
-
-    /**
-     * Undo the effects of createView() to prevent memory leaks
-     */
-    removeView: function(opts) {
-      opts.console.destroy();
-      delete opts.console;
-
-      opts.requisition.destroy();
-      delete opts.requisition;
-
-      cli.unsetEvalFunction();
-      nodetype.unsetDocument();
-      jstype.unsetGlobalObject();
-      resource.unsetDocument();
-      resource.clearResourceCache();
-    },
-
-    commandOutputManager: require('gcli/canon').commandOutputManager
+    createDisplay: function(opts) {
+      return new FFDisplay(opts);
+    }
   };
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */