Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Fri, 11 May 2012 14:35:58 -0700
changeset 106177 ff94073c1907847eb47adad281abf16a9fb0892c
parent 106176 7b00180f4f3463a576fffb72f58a44956ff07205 (current diff)
parent 93802 7b5d636382b2e67ad277bfbbdf73c781ce954767 (diff)
child 106178 c0537e1c5e8b097d14d8b73251a077103e2ef44f
push id23447
push userdanderson@mozilla.com
push dateTue, 11 Sep 2012 17:34:27 +0000
treeherdermozilla-central@fdfaef738a00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge from mozilla-central.
accessible/src/atk/nsMaiInterfaceTable.cpp
accessible/src/base/Makefile.in
accessible/src/base/nsAccessNode.cpp
accessible/src/base/nsAccessNode.h
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsAccessible.h
accessible/src/generic/ARIAGridAccessible.cpp
accessible/src/html/nsHTMLSelectAccessible.cpp
accessible/src/html/nsHTMLSelectAccessible.h
accessible/src/html/nsHTMLTableAccessible.cpp
accessible/src/html/nsHTMLTableAccessible.h
accessible/src/msaa/nsAccessNodeWrap.cpp
accessible/src/xul/Makefile.in
accessible/src/xul/nsXULListboxAccessible.cpp
accessible/src/xul/nsXULTreeGridAccessible.cpp
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/highlighter.css
browser/devtools/jar.mn
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/test/Makefile.in
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
browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js
browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/themes/gnomestripe/preview.png
browser/themes/pinstripe/preview.png
browser/themes/winstripe/icon-aero.png
browser/themes/winstripe/preview.png
configure.in
content/base/src/nsFrameLoader.cpp
content/base/src/nsFrameLoader.h
content/base/src/nsGkAtomList.h
content/base/src/nsXMLHttpRequest.cpp
content/html/content/public/nsHTMLVideoElement.h
content/html/content/src/nsHTMLVideoElement.cpp
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/ipc/ContentParent.cpp
dom/plugins/ipc/PluginInstanceParent.h
gfx/gl/GLContextProviderCGL.mm
image/src/RasterImage.cpp
js/src/Makefile.in
js/src/configure.in
js/src/gc/Barrier.h
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/jsapi.h
js/src/jsgc.cpp
js/src/tests/manifest.py
js/src/tests/progressbar.py
js/src/tests/results.py
js/src/tests/tasks_unix.py
js/src/tests/tasks_win.py
js/src/tests/tests.py
js/src/vm/RegExpObject-inl.h
js/src/yarr/pcre/AUTHORS
js/src/yarr/pcre/COPYING
js/src/yarr/pcre/chartables.c
js/src/yarr/pcre/dftables
js/src/yarr/pcre/pcre.h
js/src/yarr/pcre/pcre.pri
js/src/yarr/pcre/pcre_compile.cpp
js/src/yarr/pcre/pcre_exec.cpp
js/src/yarr/pcre/pcre_internal.h
js/src/yarr/pcre/pcre_tables.cpp
js/src/yarr/pcre/pcre_ucp_searchfuncs.cpp
js/src/yarr/pcre/pcre_xclass.cpp
js/src/yarr/pcre/ucpinternal.h
js/src/yarr/pcre/ucptable.cpp
js/xpconnect/src/xpcpublic.h
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsVideoFrame.cpp
layout/ipc/RenderFrameParent.cpp
layout/reftests/ogg-video/poster-ref-red160x120.html
layout/reftests/ogg-video/reftest.list
layout/reftests/webm-video/reftest.list
layout/svg/base/src/nsSVGOuterSVGFrame.cpp
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/xul/app/mobile.js
mozglue/android/nsGeckoUtils.cpp
netwerk/cache/nsDeleteDir.cpp
netwerk/protocol/http/nsHttpConnection.cpp
services/crypto/component/nsSyncJPAKE.cpp
testing/testsuite-targets.mk
toolkit/mozapps/extensions/LightweightThemeManager.jsm
toolkit/mozapps/installer/packager.mk
uriloader/prefetch/OfflineCacheUpdateParent.cpp
widget/android/AndroidBridge.h
widget/windows/nsDragService.cpp
widget/windows/nsDragService.h
xpcom/base/nsCycleCollector.cpp
xpcom/string/public/nsAString.h
xpcom/string/public/nsTSubstring.h
xpcom/string/src/nsTStringObsolete.cpp
xpcom/string/src/nsTSubstring.cpp
--- a/accessible/src/atk/Makefile.in
+++ b/accessible/src/atk/Makefile.in
@@ -64,28 +64,17 @@ CPPSRCS = \
   nsMaiInterfaceHyperlinkImpl.cpp \
   nsMaiInterfaceTable.cpp \
   nsMaiInterfaceDocument.cpp \
   nsMaiInterfaceImage.cpp \
   RootAccessibleWrap.cpp \
   $(NULL)
 
 EXPORTS = \
-  ARIAGridAccessibleWrap.h \
-  AtkSocketAccessible.h \
   nsAccessNodeWrap.h \
-  nsAccessibleWrap.h \
-  nsDocAccessibleWrap.h \
-  nsTextAccessibleWrap.h \
-  nsXULMenuAccessibleWrap.h \
-  nsXULListboxAccessibleWrap.h \
-  nsXULTreeGridAccessibleWrap.h \
-  nsHyperTextAccessibleWrap.h \
-  nsHTMLImageAccessibleWrap.h \
-  nsHTMLTableAccessibleWrap.h \
   $(NULL)
 
 # we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 CFLAGS		+= $(MOZ_GTK2_CFLAGS)
--- a/accessible/src/atk/nsAccessNodeWrap.cpp
+++ b/accessible/src/atk/nsAccessNodeWrap.cpp
@@ -62,16 +62,15 @@ nsAccessNodeWrap::
 // destruction
 //-----------------------------------------------------
 nsAccessNodeWrap::~nsAccessNodeWrap()
 {
 }
 
 void nsAccessNodeWrap::InitAccessibility()
 {
-  nsAccessNode::InitXPAccessibility();
 }
 
 void nsAccessNodeWrap::ShutdownAccessibility()
 {
   nsAccessNode::ShutdownXPAccessibility();
 }
 
--- a/accessible/src/atk/nsMaiInterfaceTable.cpp
+++ b/accessible/src/atk/nsMaiInterfaceTable.cpp
@@ -70,32 +70,26 @@ refAtCB(AtkTable *aTable, gint aRow, gin
     AtkObject *cellAtkObj = nsAccessibleWrap::GetAtkObject(cell);
     if (cellAtkObj) {
         g_object_ref(cellAtkObj);
     }
     return cellAtkObj;
 }
 
 static gint
-getIndexAtCB(AtkTable *aTable, gint aRow, gint aColumn)
+getIndexAtCB(AtkTable* aTable, gint aRow, gint aColumn)
 {
-    nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
-    if (!accWrap)
-        return -1;
+  nsAccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+  if (!accWrap)
+    return -1;
 
-    nsCOMPtr<nsIAccessibleTable> accTable;
-    accWrap->QueryInterface(NS_GET_IID(nsIAccessibleTable),
-                            getter_AddRefs(accTable));
-    NS_ENSURE_TRUE(accTable, -1);
+  TableAccessible* table = accWrap->AsTable();
+  NS_ENSURE_TRUE(table, -1);
 
-    PRInt32 index;
-    nsresult rv = accTable->GetCellIndexAt(aRow, aColumn, &index);
-    NS_ENSURE_SUCCESS(rv, -1);
-
-    return static_cast<gint>(index);
+  return static_cast<gint>(table->CellIndexAt(aRow, aColumn));
 }
 
 static gint
 getColumnAtIndexCB(AtkTable *aTable, gint aIndex)
 {
     nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
     if (!accWrap)
         return -1;
--- a/accessible/src/base/Makefile.in
+++ b/accessible/src/base/Makefile.in
@@ -66,16 +66,17 @@ CPPSRCS = \
   nsAccessible.cpp \
   nsAccessiblePivot.cpp \
   nsAccTreeWalker.cpp \
   nsBaseWidgetAccessible.cpp \
   nsEventShell.cpp \
   nsCaretAccessible.cpp \
   nsTextAccessible.cpp \
   nsTextEquivUtils.cpp \
+  RoleAsserts.cpp \
   StyleInfo.cpp \
   TextAttrs.cpp \
   TextUpdater.cpp \
   $(NULL)
 
 EXPORTS = \
   a11yGeneric.h \
   nsAccDocManager.h \
new file mode 100644
--- /dev/null
+++ b/accessible/src/base/RoleAsserts.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAccessibleRole.h"
+#include "Role.h"
+
+#include "mozilla/Assertions.h"
+
+using namespace mozilla::a11y;
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role) \
+  MOZ_STATIC_ASSERT(roles::geckoRole == nsIAccessibleRole::ROLE_ ## geckoRole, "internal and xpcom roles differ!");
+#include "RoleMap.h"
+#undef ROLE
--- a/accessible/src/base/nsAccessNode.cpp
+++ b/accessible/src/base/nsAccessNode.cpp
@@ -46,29 +46,26 @@
 
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDOMWindow.h"
 #include "nsIFrame.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPresShell.h"
 #include "nsIServiceManager.h"
-#include "nsIStringBundle.h"
 #include "nsFocusManager.h"
 #include "nsPresContext.h"
 #include "mozilla/Services.h"
 
 using namespace mozilla::a11y;
 
 /* For documentation of the accessibility architecture, 
  * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
  */
 
-nsIStringBundle *nsAccessNode::gStringBundle = 0;
-
 ApplicationAccessible* nsAccessNode::gApplicationAccessible = nsnull;
 
 /*
  * Class nsAccessNode
  */
  
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessible. nsISupports
@@ -144,35 +141,22 @@ nsAccessNode::GetApplicationAccessible()
       NS_RELEASE(gApplicationAccessible);
       return nsnull;
     }
   }
 
   return gApplicationAccessible;
 }
 
-void nsAccessNode::InitXPAccessibility()
-{
-  nsCOMPtr<nsIStringBundleService> stringBundleService =
-    mozilla::services::GetStringBundleService();
-  if (stringBundleService) {
-    // Static variables are released in ShutdownAllXPAccessibility();
-    stringBundleService->CreateBundle(ACCESSIBLE_BUNDLE_URL, 
-                                      &gStringBundle);
-  }
-}
-
 void nsAccessNode::ShutdownXPAccessibility()
 {
   // Called by nsAccessibilityService::Shutdown()
   // which happens when xpcom is shutting down
   // at exit of program
 
-  NS_IF_RELEASE(gStringBundle);
-
   // Release gApplicationAccessible after everything else is shutdown
   // so we don't accidently create it again while tearing down root accessibles
   ApplicationAccessibleWrap::Unload();
   if (gApplicationAccessible) {
     gApplicationAccessible->Shutdown();
     NS_RELEASE(gApplicationAccessible);
   }
 }
--- a/accessible/src/base/nsAccessNode.h
+++ b/accessible/src/base/nsAccessNode.h
@@ -64,31 +64,27 @@ class RootAccessible;
 }
 }
 
 class nsIPresShell;
 class nsPresContext;
 class nsIFrame;
 class nsIDocShellTreeItem;
 
-#define ACCESSIBLE_BUNDLE_URL "chrome://global-platform/locale/accessible.properties"
-#define PLATFORM_KEYS_BUNDLE_URL "chrome://global-platform/locale/platformKeys.properties"
-
 class nsAccessNode: public nsISupports
 {
 public:
 
   nsAccessNode(nsIContent* aContent, nsDocAccessible* aDoc);
   virtual ~nsAccessNode();
 
-    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-    NS_DECL_CYCLE_COLLECTION_CLASS(nsAccessNode)
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsAccessNode)
 
-    static void InitXPAccessibility();
-    static void ShutdownXPAccessibility();
+  static void ShutdownXPAccessibility();
 
   /**
    * Return an application accessible.
    */
   static ApplicationAccessible* GetApplicationAccessible();
 
   /**
    * Return the document accessible for this access node.
@@ -159,19 +155,16 @@ public:
   void Language(nsAString& aLocale);
 
 protected:
   void LastRelease();
 
   nsCOMPtr<nsIContent> mContent;
   nsDocAccessible* mDoc;
 
-  // Static data, we do our own refcounting for our static data.
-  static nsIStringBundle* gStringBundle;
-
 private:
   nsAccessNode() MOZ_DELETE;
   nsAccessNode(const nsAccessNode&) MOZ_DELETE;
   nsAccessNode& operator =(const nsAccessNode&) MOZ_DELETE;
   
   static ApplicationAccessible* gApplicationAccessible;
 };
 
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -77,16 +77,17 @@
 
 #include "nsIDocument.h"
 #include "nsIContent.h"
 #include "nsIForm.h"
 #include "nsIFormControl.h"
 
 #include "nsLayoutUtils.h"
 #include "nsIPresShell.h"
+#include "nsIStringBundle.h"
 #include "nsPresContext.h"
 #include "nsIFrame.h"
 #include "nsIView.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIScrollableFrame.h"
 #include "nsFocusManager.h"
 
 #include "nsXPIDLString.h"
@@ -621,22 +622,34 @@ nsAccessible::GetIndexInParent(PRInt32 *
 {
   NS_ENSURE_ARG_POINTER(aIndexInParent);
 
   *aIndexInParent = IndexInParent();
   return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void 
-nsAccessible::TranslateString(const nsAString& aKey, nsAString& aStringOut)
+nsAccessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
 {
+  nsCOMPtr<nsIStringBundleService> stringBundleService =
+    services::GetStringBundleService();
+  if (!stringBundleService)
+    return;
+
+  nsCOMPtr<nsIStringBundle> stringBundle;
+  stringBundleService->CreateBundle(
+    "chrome://global-platform/locale/accessible.properties", 
+    getter_AddRefs(stringBundle));
+  if (!stringBundle)
+    return;
+
   nsXPIDLString xsValue;
-
-  gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue));
-  aStringOut.Assign(xsValue);
+  nsresult rv = stringBundle->GetStringFromName(aKey.get(), getter_Copies(xsValue));
+  if (NS_SUCCEEDED(rv))
+    aStringOut.Assign(xsValue);
 }
 
 PRUint64
 nsAccessible::VisibilityState()
 {
   PRUint64 vstates = states::INVISIBLE | states::OFFSCREEN;
 
   nsIFrame* frame = GetFrame();
@@ -3288,18 +3301,19 @@ nsAccessible::GetLevelInternal()
 
 void
 KeyBinding::ToPlatformFormat(nsAString& aValue) const
 {
   nsCOMPtr<nsIStringBundle> keyStringBundle;
   nsCOMPtr<nsIStringBundleService> stringBundleService =
       mozilla::services::GetStringBundleService();
   if (stringBundleService)
-    stringBundleService->CreateBundle(PLATFORM_KEYS_BUNDLE_URL,
-                                      getter_AddRefs(keyStringBundle));
+    stringBundleService->CreateBundle(
+      "chrome://global-platform/locale/platformKeys.properties",
+      getter_AddRefs(keyStringBundle));
 
   if (!keyStringBundle)
     return;
 
   nsAutoString separator;
   keyStringBundle->GetStringFromName(NS_LITERAL_STRING("MODIFIER_SEPARATOR").get(),
                                      getter_Copies(separator));
 
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -657,17 +657,17 @@ public:
   /**
    * Return container widget this accessible belongs to.
    */
   virtual nsAccessible* ContainerWidget() const;
 
   /**
    * Return the localized string for the given key.
    */
-  static void TranslateString(const nsAString& aKey, nsAString& aStringOut);
+  static void TranslateString(const nsString& aKey, nsAString& aStringOut);
 
   /**
    * Return true if the accessible is defunct.
    */
   bool IsDefunct() const { return mFlags & eIsDefunct; }
 
 protected:
 
--- a/accessible/src/generic/ARIAGridAccessible.cpp
+++ b/accessible/src/generic/ARIAGridAccessible.cpp
@@ -129,40 +129,16 @@ ARIAGridAccessible::GetCellAt(PRInt32 aR
   nsAccessible *cell = GetCellInRowAt(row, aColumnIndex);
   NS_ENSURE_ARG(cell);
 
   NS_ADDREF(*aCell = cell);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-ARIAGridAccessible::GetCellIndexAt(PRInt32 aRowIndex, PRInt32 aColumnIndex,
-                                   PRInt32* aCellIndex)
-{
-  NS_ENSURE_ARG_POINTER(aCellIndex);
-  *aCellIndex = -1;
-
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
-  NS_ENSURE_ARG(aRowIndex >= 0 && aColumnIndex >= 0);
-
-  PRInt32 rowCount = 0;
-  GetRowCount(&rowCount);
-  NS_ENSURE_ARG(aRowIndex < rowCount);
-
-  PRInt32 colsCount = 0;
-  GetColumnCount(&colsCount);
-  NS_ENSURE_ARG(aColumnIndex < colsCount);
-
-  *aCellIndex = colsCount * aRowIndex + aColumnIndex;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 ARIAGridAccessible::GetColumnIndexAt(PRInt32 aCellIndex,
                                      PRInt32* aColumnIndex)
 {
   NS_ENSURE_ARG_POINTER(aColumnIndex);
   *aColumnIndex = -1;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
--- a/accessible/src/generic/Makefile.in
+++ b/accessible/src/generic/Makefile.in
@@ -30,8 +30,30 @@ include $(topsrcdir)/config/rules.mk
 LOCAL_INCLUDES = \
   -I$(srcdir)/../xpcom \
   -I$(srcdir)/../base \
   -I$(srcdir)/../html \
   -I$(srcdir)/../xul \
   -I$(srcdir)/../../../layout/generic \
   -I$(srcdir)/../../../layout/xul/base/src \
   $(NULL)
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../atk \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../msaa \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../mac \
+  $(NULL)
+else
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../other \
+  $(NULL)
+endif
+endif
+endif
--- a/accessible/src/generic/TableAccessible.h
+++ b/accessible/src/generic/TableAccessible.h
@@ -46,17 +46,18 @@ public:
   /**
    * Return the accessible for the cell at the given row and column indices.
    */
   virtual nsAccessible* CellAt(PRUint32 aRowIdx, PRUint32 aColIdx) { return nsnull; }
 
   /**
    * Return the index of the cell at the given row and column.
    */
-  virtual PRInt32 CellIndexAt(PRUint32 aRowIdx, PRUint32 aColIdx) { return -1; }
+  virtual PRInt32 CellIndexAt(PRUint32 aRowIdx, PRUint32 aColIdx)
+    { return ColCount() * aRowIdx + aColIdx; }
 
   /**
    * Return the column index of the cell with the given index.
    */
   virtual PRInt32 ColIndexAt(PRUint32 aCellIdx) { return -1; }
 
   /**
    * Return the row index of the cell with the given index.
--- a/accessible/src/html/Makefile.in
+++ b/accessible/src/html/Makefile.in
@@ -75,8 +75,30 @@ LOCAL_INCLUDES = \
   -I$(srcdir)/../base \
   -I$(srcdir)/../generic \
   -I$(srcdir)/../xpcom \
   -I$(srcdir)/../../../content/base/src \
   -I$(srcdir)/../../../content/html/content/src \
   -I$(srcdir)/../../../layout/generic \
   -I$(srcdir)/../../../layout/xul/base/src \
   $(NULL)
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../atk \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../msaa \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../mac \
+  $(NULL)
+else
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../other \
+  $(NULL)
+endif
+endif
+endif
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp
+++ b/accessible/src/html/nsHTMLSelectAccessible.cpp
@@ -260,19 +260,22 @@ PRUint64
 nsHTMLSelectOptionAccessible::NativeState()
 {
   // As a nsHTMLSelectOptionAccessible we can have the following states:
   // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
   // Upcall to nsAccessible, but skip nsHyperTextAccessible impl
   // because we don't want EDITABLE or SELECTABLE_TEXT
   PRUint64 state = nsAccessible::NativeState();
 
-  PRUint64 selectState = 0;
-  nsIContent* selectContent = GetSelectState(&selectState);
-  if (!selectContent || selectState & states::INVISIBLE)
+  nsAccessible* select = GetSelect();
+  if (!select)
+    return state;
+
+  PRUint64 selectState = select->State();
+  if (selectState & states::INVISIBLE)
     return state;
 
   // Focusable and selectable
   if (!(state & states::UNAVAILABLE))
     state |= (states::FOCUSABLE | states::SELECTABLE);
 
   // Are we selected?
   bool isSelected = false;
@@ -387,40 +390,16 @@ nsHTMLSelectOptionAccessible::SetSelecte
 
 nsAccessible*
 nsHTMLSelectOptionAccessible::ContainerWidget() const
 {
   return mParent && mParent->IsListControl() ? mParent : nsnull;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// nsHTMLSelectOptionAccessible: private methods
-
-nsIContent*
-nsHTMLSelectOptionAccessible::GetSelectState(PRUint64* aState)
-{
-  *aState = 0;
-
-  nsIContent* selectNode = mContent;
-  while (selectNode && selectNode->Tag() != nsGkAtoms::select) {
-    selectNode = selectNode->GetParent();
-  }
-
-  if (selectNode) {
-    nsAccessible* select = mDoc->GetAccessible(selectNode);
-    if (select) {
-      *aState = select->State();
-      return selectNode;
-    }
-  }
-  return nsnull; 
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
 // nsHTMLSelectOptGroupAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 nsHTMLSelectOptGroupAccessible::
   nsHTMLSelectOptGroupAccessible(nsIContent* aContent,
                                  nsDocAccessible* aDoc) :
   nsHTMLSelectOptionAccessible(aContent, aDoc)
 {
--- a/accessible/src/html/nsHTMLSelectAccessible.h
+++ b/accessible/src/html/nsHTMLSelectAccessible.h
@@ -128,30 +128,36 @@ public:
   virtual PRUint8 ActionCount();
 
   // Widgets
   virtual nsAccessible* ContainerWidget() const;
 
 private:
   
   /**
-   * Get Select element's accessible state
-   * @param aState, Select element state
-   * @return Select element content, returns null if not avaliable
+   * Return a select accessible the option belongs to if any.
    */ 
-  nsIContent* GetSelectState(PRUint64* aState);
+  nsAccessible* GetSelect() const
+  {
+    if (mParent && mParent->IsListControl()) {
+      nsAccessible* combobox = mParent->Parent();
+      return combobox && combobox->IsCombobox() ? combobox : mParent.get();
+    }
+
+    return nsnull;
+  }
 
   /**
    * Return a combobox accessible the option belongs to if any.
    */
   nsAccessible* GetCombobox() const
   {
     if (mParent && mParent->IsListControl()) {
       nsAccessible* combobox = mParent->Parent();
-      return combobox->IsCombobox() ? combobox : nsnull;
+      return combobox && combobox->IsCombobox() ? combobox : nsnull;
     }
 
     return nsnull;
   }
 };
 
 /*
  * Opt Groups inside the select, contained within the list
--- a/accessible/src/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -895,30 +895,24 @@ nsHTMLTableAccessible::GetCellAt(PRInt32
     // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
     // return itself as a cell what makes Orca hang.
     NS_ADDREF(*aTableCellAccessible = cell);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsHTMLTableAccessible::GetCellIndexAt(PRInt32 aRow, PRInt32 aColumn,
-                                      PRInt32 *aIndex)
+PRInt32
+nsHTMLTableAccessible::CellIndexAt(PRUint32 aRowIdx, PRUint32 aColIdx)
 {
-  NS_ENSURE_ARG_POINTER(aIndex);
+  nsITableLayout* tableLayout = GetTableLayout();
 
-  nsITableLayout *tableLayout = GetTableLayout();
-  NS_ENSURE_STATE(tableLayout);
-
-  nsresult rv = tableLayout->GetIndexByRowAndColumn(aRow, aColumn, aIndex);
-  if (rv == NS_TABLELAYOUT_CELL_NOT_FOUND)
-    return NS_ERROR_INVALID_ARG;
-
-  return NS_OK;
+  PRInt32 index = -1;
+  tableLayout->GetIndexByRowAndColumn(aRowIdx, aColIdx, &index);
+  return index;
 }
 
 NS_IMETHODIMP
 nsHTMLTableAccessible::GetColumnIndexAt(PRInt32 aIndex, PRInt32 *aColumn)
 {
   NS_ENSURE_ARG_POINTER(aColumn);
 
   if (IsDefunct())
--- a/accessible/src/html/nsHTMLTableAccessible.h
+++ b/accessible/src/html/nsHTMLTableAccessible.h
@@ -127,16 +127,17 @@ public:
   // 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 PRInt32 CellIndexAt(PRUint32 aRowIdx, PRUint32 aColIdx);
   virtual void UnselectCol(PRUint32 aColIdx);
   virtual void UnselectRow(PRUint32 aRowIdx);
   virtual bool IsProbablyLayoutTable();
 
   // nsAccessNode
   virtual void Shutdown();
 
   // nsAccessible
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -43,25 +43,28 @@ var AccessFu = {
     this.prefsBranch.addObserver('accessfu', this, false);
 
     let accessPref = ACCESSFU_DISABLE;
     try {
       accessPref = this.prefsBranch.getIntPref('accessfu');
     } catch (x) {
     }
 
-    if (this.amINeeded(accessPref))
-      this.enable();
+    this.processPreferences(accessPref);
   },
 
   /**
    * Start AccessFu mode, this primarily means controlling the virtual cursor
    * with arrow keys.
    */
   enable: function enable() {
+    if (this._enabled)
+      return;
+    this._enabled = true;
+
     dump('AccessFu enable');
     this.addPresenter(new VisualPresenter());
 
     // Implicitly add the Android presenter on Android.
     if (Services.appinfo.OS == 'Android')
       this.addPresenter(new AndroidPresenter());
 
     VirtualCursorController.attach(this.chromeWin);
@@ -72,50 +75,57 @@ var AccessFu = {
     this.chromeWin.addEventListener('scroll', this, true);
     this.chromeWin.addEventListener('TabOpen', this, true);
   },
 
   /**
    * Disable AccessFu and return to default interaction mode.
    */
   disable: function disable() {
+    if (!this._enabled)
+      return;
+    this._enabled = false;
+
     dump('AccessFu disable');
 
     this.presenters.forEach(function(p) { p.detach(); });
     this.presenters = [];
 
     VirtualCursorController.detach();
 
     Services.obs.removeObserver(this, 'accessible-event');
     this.chromeWin.removeEventListener('DOMActivate', this, true);
     this.chromeWin.removeEventListener('resize', this, true);
     this.chromeWin.removeEventListener('scroll', this, true);
     this.chromeWin.removeEventListener('TabOpen', this, true);
   },
 
-  amINeeded: function(aPref) {
-    switch (aPref) {
-      case ACCESSFU_ENABLE:
-        return true;
-      case ACCESSFU_AUTO:
-        if (Services.appinfo.OS == 'Android') {
-          let msg = Cc['@mozilla.org/android/bridge;1'].
-            getService(Ci.nsIAndroidBridge).handleGeckoMessage(
-              JSON.stringify(
-                { gecko: {
-                    type: 'Accessibility:IsEnabled',
-                    eventType: 1,
-                    text: []
-                  }
-                }));
-          return JSON.parse(msg).enabled;
+  processPreferences: function processPreferences(aPref) {
+    if (Services.appinfo.OS == 'Android') {
+      if (aPref == ACCESSFU_AUTO) {
+        if (!this._observingSystemSettings) {
+          Services.obs.addObserver(this, 'Accessibility:Settings', false);
+          this._observingSystemSettings = true;
         }
-      default:
-        return false;
+        Cc['@mozilla.org/android/bridge;1'].
+          getService(Ci.nsIAndroidBridge).handleGeckoMessage(
+            JSON.stringify({ gecko: { type: 'Accessibility:Ready' } }));
+        return;
+      }
+
+      if (this._observingSystemSettings) {
+        Services.obs.removeObserver(this, 'Accessibility:Settings');
+        this._observingSystemSettings = false;
+      }
     }
+
+    if (aPref == ACCESSFU_ENABLE)
+      this.enable();
+    else
+      this.disable();
   },
 
   addPresenter: function addPresenter(presenter) {
     this.presenters.push(presenter);
     presenter.attach(this.chromeWin);
   },
 
   handleEvent: function handleEvent(aEvent) {
@@ -154,23 +164,25 @@ var AccessFu = {
         this.presenters.forEach(function(p) { p.viewportChanged(); });
         break;
       }
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
+      case 'Accessibility:Settings':
+        if (JSON.parse(aData).enabled)
+          this.enable();
+        else
+          this.disable();
+        break;
       case 'nsPref:changed':
-        if (aData == 'accessfu') {
-          if (this.amINeeded(this.prefsBranch.getIntPref('accessfu')))
-            this.enable();
-          else
-            this.disable();
-        }
+        if (aData == 'accessfu')
+          this.processPreferences(this.prefsBranch.getIntPref('accessfu'));
         break;
       case 'accessible-event':
         let event;
         try {
           event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
           this.handleAccEvent(event);
         } catch (ex) {
           dump(ex);
@@ -387,17 +399,23 @@ var AccessFu = {
         newContext.push(newAncestor);
       i++;
     }
 
     return newContext;
   },
 
   // A hash of documents that don't yet have an accessible tree.
-  _pendingDocuments: {}
+  _pendingDocuments: {},
+
+  // So we don't enable/disable twice
+  _enabled: false,
+
+  // Observing accessibility settings
+  _observingSystemSettings: false
 };
 
 function getAccessible(aNode) {
   try {
     return Cc['@mozilla.org/accessibleRetrieval;1'].
       getService(Ci.nsIAccessibleRetrieval).getAccessibleFor(aNode);
   } catch (e) {
     return null;
--- a/accessible/src/mac/Makefile.in
+++ b/accessible/src/mac/Makefile.in
@@ -55,34 +55,19 @@ CMMSRCS = nsAccessNodeWrap.mm \
           mozDocAccessible.mm \
           mozActionElements.mm \
           mozTextAccessible.mm \
           mozHTMLAccessible.mm \
           MacUtils.mm \
           RootAccessibleWrap.mm \
           $(NULL)
 
-
 EXPORTS = \
-  ARIAGridAccessibleWrap.h \
   nsAccessNodeWrap.h \
-  nsTextAccessibleWrap.h \
-  nsAccessibleWrap.h \
-  nsDocAccessibleWrap.h \
-  nsXULMenuAccessibleWrap.h \
-  nsXULListboxAccessibleWrap.h \
-  nsXULTreeGridAccessibleWrap.h \
-  nsHyperTextAccessibleWrap.h \
-  nsHTMLImageAccessibleWrap.h \
-  nsHTMLTableAccessibleWrap.h \
-  mozDocAccessible.h \
-  mozAccessible.h \
   mozAccessibleProtocol.h \
-  mozActionElements.h \
-  mozTextAccessible.h \
   $(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -471,20 +471,20 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
 #undef ROLE
 }
 
 - (NSString*)subrole
 {
   switch (mRole) {
     case roles::LIST:
-      return NSAccessibilityContentListSubrole;
+      return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
 
     case roles::DEFINITION_LIST:
-      return NSAccessibilityDefinitionListSubrole;
+      return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
 
     case roles::TERM:
       return @"AXTerm";
 
     case roles::DEFINITION:
       return @"AXDefinition";
 
     default:
--- a/accessible/src/mac/nsAccessNodeWrap.mm
+++ b/accessible/src/mac/nsAccessNodeWrap.mm
@@ -62,16 +62,15 @@ nsAccessNodeWrap::
 //-----------------------------------------------------
 nsAccessNodeWrap::~nsAccessNodeWrap()
 {
 }
 
 
 void nsAccessNodeWrap::InitAccessibility()
 {
-  nsAccessNode::InitXPAccessibility();
 }
 
 void nsAccessNodeWrap::ShutdownAccessibility()
 {
   nsAccessNode::ShutdownXPAccessibility();
 }
 
--- a/accessible/src/msaa/Makefile.in
+++ b/accessible/src/msaa/Makefile.in
@@ -73,38 +73,17 @@ CPPSRCS = \
   CAccessibleTable.cpp \
   CAccessibleTableCell.cpp \
   CAccessibleValue.cpp \
   Compatibility.cpp \
   RootAccessibleWrap.cpp \
   $(NULL)
 
 EXPORTS = \
-  ARIAGridAccessibleWrap.h \
   nsAccessNodeWrap.h \
-  nsAccessibleWrap.h \
-  nsTextAccessibleWrap.h \
-  nsDocAccessibleWrap.h \
-  nsHTMLWin32ObjectAccessible.h \
-  nsXULMenuAccessibleWrap.h \
-  nsXULListboxAccessibleWrap.h \
-  nsXULTreeGridAccessibleWrap.h \
-  nsHyperTextAccessibleWrap.h \
-  nsHTMLImageAccessibleWrap.h \
-  nsHTMLTableAccessibleWrap.h \
-  ia2AccessibleAction.h \
-  ia2AccessibleComponent.h \
-  CAccessibleImage.h \
-  CAccessibleText.h \
-  CAccessibleEditableText.h \
-  CAccessibleHyperlink.h \
-  ia2AccessibleHypertext.h \
-  CAccessibleTable.h \
-  CAccessibleTableCell.h \
-  CAccessibleValue.h \
   $(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
--- a/accessible/src/msaa/nsAccessNodeWrap.cpp
+++ b/accessible/src/msaa/nsAccessNodeWrap.cpp
@@ -583,18 +583,16 @@ nsAccessNodeWrap::get_localInterface(
   return S_OK;
 }
  
 void nsAccessNodeWrap::InitAccessibility()
 {
   Compatibility::Init();
 
   nsWinUtils::MaybeStartWindowEmulation();
-
-  nsAccessNode::InitXPAccessibility();
 }
 
 void nsAccessNodeWrap::ShutdownAccessibility()
 {
   NS_IF_RELEASE(gTextEvent);
   ::DestroyCaret();
 
   nsWinUtils::ShutdownWindowEmulation();
--- a/accessible/src/other/Makefile.in
+++ b/accessible/src/other/Makefile.in
@@ -47,30 +47,16 @@ EXPORT_LIBRARY = ..
 LIBXUL_LIBRARY = 1
 
 
 CPPSRCS = \
   nsAccessNodeWrap.cpp \
   nsAccessibleWrap.cpp \
   $(NULL)
 
-EXPORTS = \
-  ARIAGridAccessibleWrap.h \
-  nsAccessNodeWrap.h \
-  nsTextAccessibleWrap.h \
-  nsAccessibleWrap.h \
-  nsDocAccessibleWrap.h \
-  nsXULMenuAccessibleWrap.h \
-  nsXULListboxAccessibleWrap.h \
-  nsXULTreeGridAccessibleWrap.h \
-  nsHyperTextAccessibleWrap.h \
-  nsHTMLImageAccessibleWrap.h \
-  nsHTMLTableAccessibleWrap.h \
-  $(NULL)
-
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
   -I$(srcdir) \
   -I$(srcdir)/../base \
--- a/accessible/src/other/nsAccessNodeWrap.cpp
+++ b/accessible/src/other/nsAccessNodeWrap.cpp
@@ -61,16 +61,15 @@ nsAccessNodeWrap::
 // destruction
 //-----------------------------------------------------
 nsAccessNodeWrap::~nsAccessNodeWrap()
 {
 }
 
 void nsAccessNodeWrap::InitAccessibility()
 {
-  nsAccessNode::InitXPAccessibility();
 }
 
 void nsAccessNodeWrap::ShutdownAccessibility()
 {
   nsAccessNode::ShutdownXPAccessibility();
 }
 
--- a/accessible/src/xforms/Makefile.in
+++ b/accessible/src/xforms/Makefile.in
@@ -61,8 +61,29 @@ FORCE_STATIC_LIB = 1
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES = \
   -I$(srcdir) \
   -I$(srcdir)/../base \
   -I$(srcdir)/../html \
   $(NULL)
 
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../atk \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../msaa \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../mac \
+  $(NULL)
+else
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../other \
+  $(NULL)
+endif
+endif
+endif
--- a/accessible/src/xpcom/Makefile.in
+++ b/accessible/src/xpcom/Makefile.in
@@ -57,8 +57,30 @@ CPPSRCS = \
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES = \
   -I$(srcdir)/../base \
   -I$(srcdir)/../generic \
   $(NULL)
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../atk \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../msaa \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../mac \
+  $(NULL)
+else
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../other \
+  $(NULL)
+endif
+endif
+endif
--- a/accessible/src/xpcom/xpcAccessibleTable.cpp
+++ b/accessible/src/xpcom/xpcAccessibleTable.cpp
@@ -43,16 +43,34 @@ xpcAccessibleTable::GetRowCount(PRInt32*
   if (!mTable)
     return NS_ERROR_FAILURE;
 
   *aRowCount = mTable->RowCount();
   return NS_OK;
 }
 
 nsresult
+xpcAccessibleTable::GetCellIndexAt(PRInt32 aRowIndex, PRInt32 aColumnIndex,
+                                   PRInt32* aCellIndex)
+{
+  NS_ENSURE_ARG_POINTER(aCellIndex);
+  *aCellIndex = -1;
+
+  if (!mTable)
+    return NS_ERROR_FAILURE;
+
+  if (aRowIndex < 0 || aRowIndex >= mTable->RowCount() ||
+      aColumnIndex < 0 || aColumnIndex >= mTable->ColCount())
+    return NS_ERROR_INVALID_ARG;
+
+  *aCellIndex = mTable->CellIndexAt(aRowIndex, aColumnIndex);
+  return NS_OK;
+}
+
+nsresult
 xpcAccessibleTable::GetSummary(nsAString& aSummary)
 {
   if (!mTable)
     return NS_ERROR_FAILURE;
 
   nsAutoString summary;
   mTable->Summary(summary);
   aSummary.Assign(summary);
--- a/accessible/src/xpcom/xpcAccessibleTable.h
+++ b/accessible/src/xpcom/xpcAccessibleTable.h
@@ -21,16 +21,18 @@ 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 GetCellIndexAt(PRInt32 aRowIndex, PRInt32 aColumnIndex,
+                          PRInt32* aCellIndex);
   nsresult UnselectColumn(PRInt32 aColIdx);
   nsresult UnselectRow(PRInt32 aRowIdx);
   nsresult IsProbablyForLayout(bool* aIsForLayout);
 
 protected:
   mozilla::a11y::TableAccessible* mTable;
 };
 
@@ -39,17 +41,18 @@ protected:
     { return xpcAccessibleTable::GetCaption(aCaption); } \
   NS_SCRIPTABLE NS_IMETHOD GetSummary(nsAString & aSummary) \
     { return xpcAccessibleTable::GetSummary(aSummary); } \
   NS_SCRIPTABLE NS_IMETHOD GetColumnCount(PRInt32* aColumnCount) \
     { return xpcAccessibleTable::GetColumnCount(aColumnCount); } \
   NS_SCRIPTABLE NS_IMETHOD GetRowCount(PRInt32* aRowCount) \
     { return xpcAccessibleTable::GetRowCount(aRowCount); } \
   NS_SCRIPTABLE NS_IMETHOD GetCellAt(PRInt32 rowIndex, PRInt32 columnIndex, nsIAccessible * *_retval NS_OUTPARAM); \
-  NS_SCRIPTABLE NS_IMETHOD GetCellIndexAt(PRInt32 rowIndex, PRInt32 columnIndex, PRInt32 *_retval NS_OUTPARAM); \
+  NS_SCRIPTABLE NS_IMETHOD GetCellIndexAt(PRInt32 rowIndex, PRInt32 columnIndex, PRInt32 *_retval NS_OUTPARAM) \
+    { return xpcAccessibleTable::GetCellIndexAt(rowIndex, columnIndex, _retval); } \
   NS_SCRIPTABLE NS_IMETHOD GetColumnIndexAt(PRInt32 cellIndex, PRInt32 *_retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetRowIndexAt(PRInt32 cellIndex, PRInt32 *_retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetRowAndColumnIndicesAt(PRInt32 cellIndex, PRInt32 *rowIndex NS_OUTPARAM, PRInt32 *columnIndex NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetColumnExtentAt(PRInt32 row, PRInt32 column, PRInt32 *_retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetRowExtentAt(PRInt32 row, PRInt32 column, PRInt32 *_retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetColumnDescription(PRInt32 columnIndex, nsAString & _retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD GetRowDescription(PRInt32 rowIndex, nsAString & _retval NS_OUTPARAM); \
   NS_SCRIPTABLE NS_IMETHOD IsColumnSelected(PRInt32 columnIndex, bool *_retval NS_OUTPARAM); \
--- a/accessible/src/xul/Makefile.in
+++ b/accessible/src/xul/Makefile.in
@@ -73,8 +73,30 @@ LOCAL_INCLUDES = \
   -I$(srcdir) \
   -I$(srcdir)/../base \
   -I$(srcdir)/../generic \
   -I$(srcdir)/../html \
   -I$(srcdir)/../xpcom \
   -I$(srcdir)/../../../layout/generic \
   -I$(srcdir)/../../../layout/xul/base/src \
   $(NULL)
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../atk \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../msaa \
+  $(NULL)
+else
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../mac \
+  $(NULL)
+else
+LOCAL_INCLUDES += \
+  -I$(srcdir)/../other \
+  $(NULL)
+endif
+endif
+endif
--- a/accessible/src/xul/nsXULListboxAccessible.cpp
+++ b/accessible/src/xul/nsXULListboxAccessible.cpp
@@ -303,37 +303,16 @@ nsXULListboxAccessible::GetCellAt(PRInt3
 
   nsresult rv = row->GetChildAt(aColumn, aAccessibleCell);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULListboxAccessible::GetCellIndexAt(PRInt32 aRow, PRInt32 aColumn,
-                                       PRInt32 *aIndex)
-{
-  NS_ENSURE_ARG_POINTER(aIndex);
-  *aIndex = -1;
-
-  PRInt32 rowCount = 0;
-  nsresult rv = GetRowCount(&rowCount);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(0 <= aRow && aRow <= rowCount, NS_ERROR_INVALID_ARG);
-
-  PRInt32 columnCount = 0;
-  rv = GetColumnCount(&columnCount);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(0 <= aColumn && aColumn <= columnCount, NS_ERROR_INVALID_ARG);
-
-  *aIndex = aRow * columnCount + aColumn;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsXULListboxAccessible::GetColumnIndexAt(PRInt32 aIndex, PRInt32 *aColumn)
 {
   NS_ENSURE_ARG_POINTER(aColumn);
   *aColumn = -1;
 
   PRInt32 columnCount = 0;
   nsresult rv = GetColumnCount(&columnCount);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp
@@ -352,34 +352,16 @@ nsXULTreeGridAccessible::GetCellAt(PRInt
 
   nsRefPtr<nsXULTreeItemAccessibleBase> rowAcc = do_QueryObject(rowAccessible);
 
   NS_IF_ADDREF(*aCell = rowAcc->GetCellAccessible(column));
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULTreeGridAccessible::GetCellIndexAt(PRInt32 aRowIndex, PRInt32 aColumnIndex,
-                                        PRInt32 *aCellIndex)
-{
-  NS_ENSURE_ARG_POINTER(aCellIndex);
-  *aCellIndex = -1;
-
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
-  PRInt32 columnCount = 0;
-  nsresult rv = GetColumnCount(&columnCount);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  *aCellIndex = aRowIndex * columnCount + aColumnIndex;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsXULTreeGridAccessible::GetColumnIndexAt(PRInt32 aCellIndex,
                                           PRInt32 *aColumnIndex)
 {
   NS_ENSURE_ARG_POINTER(aColumnIndex);
   *aColumnIndex = -1;
 
   PRInt32 columnCount = 0;
   nsresult rv = GetColumnCount(&columnCount);
--- a/accessible/tests/mochitest/text.js
+++ b/accessible/tests/mochitest/text.js
@@ -332,16 +332,128 @@ function testWords(aElement, aWords, aTo
 
   testWordCount(aElement, aWords.length, aToDoFlag);
 
   for (var i = 0; i < aWords.length; i++) {
     testWordAt(aElement, i, aWords[i], aToDoFlag);
   }
 }
 
+/**
+ * Remove all selections.
+ *
+ * @param aID  [in] Id, DOM node, or acc obj
+ */
+function cleanTextSelections(aID)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+
+  while (acc.selectionCount > 0)
+    acc.removeSelection(0);
+}
+
+/**
+ * Test addSelection method.
+ *
+ * @param aID               [in] Id, DOM node, or acc obj
+ * @param aStartOffset      [in] start offset for the new selection
+ * @param aEndOffset        [in] end offset for the new selection
+ * @param aSelectionsCount  [in] expected number of selections after addSelection
+ */
+function testTextAddSelection(aID, aStartOffset, aEndOffset, aSelectionsCount)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+  var text = acc.getText(0, -1);
+
+  acc.addSelection(aStartOffset, aEndOffset);
+
+  ok(acc.selectionCount, aSelectionsCount,
+     text + ": failed to add selection from offset '" + aStartOffset +
+     "' to offset '" + aEndOffset + "': selectionCount after");
+}
+
+/**
+ * Test removeSelection method.
+ *
+ * @param aID               [in] Id, DOM node, or acc obj
+ * @param aSelectionIndex   [in] index of the selection to be removed
+ * @param aSelectionsCount  [in] expected number of selections after
+ *                               removeSelection
+ */
+function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+  var text = acc.getText(0, -1);
+
+  acc.removeSelection(aSelectionIndex);
+
+  ok(acc.selectionCount, aSelectionsCount, 
+     text + ": failed to remove selection at index '" + 
+     aSelectionIndex + "': selectionCount after");
+}
+
+/**
+ * Test setSelectionBounds method.
+ *
+ * @param aID               [in] Id, DOM node, or acc obj
+ * @param aStartOffset      [in] new start offset for the selection
+ * @param aEndOffset        [in] new end offset for the selection
+ * @param aSelectionIndex   [in] index of the selection to set
+ * @param aSelectionsCount  [in] expected number of selections after
+ *                               setSelectionBounds
+ */
+function testTextSetSelection(aID, aStartOffset, aEndOffset,
+                              aSelectionIndex, aSelectionsCount)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+  var text = acc.getText(0, -1);
+
+  acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset);
+ 
+  is(acc.selectionCount, aSelectionsCount, 
+     text + ": failed to set selection at index '" + 
+     aSelectionIndex + "': selectionCount after");
+}
+
+/**
+ * Test selectionCount method.
+ *
+ * @param aID        [in] Id, DOM node, or acc obj
+ * @param aCount     [in] expected selection count
+ */
+function testTextSelectionCount(aID, aCount)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+  var text = acc.getText(0, -1);
+
+  is(acc.selectionCount, aCount, text + ": wrong selectionCount: ");
+}
+
+/**
+ * Test getSelectionBounds method.
+ *
+ * @param aID              [in] Id, DOM node, or acc obj
+ * @param aStartOffset     [in] expected start offset for the selection
+ * @param aEndOffset       [in] expected end offset for the selection
+ * @param aSelectionIndex  [in] index of the selection to get
+ */
+function testTextGetSelection(aID, aStartOffset, aEndOffset, aSelectionIndex)
+{
+  var acc = getAccessible(aID, [nsIAccessibleText]);
+  var text = acc.getText(0, -1);
+
+  var startObj = {}, endObj = {};
+  acc.getSelectionBounds(aSelectionIndex, startObj, endObj);
+
+  is(startObj.value, aStartOffset, text + ": wrong start offset for index '" +
+     aSelectionIndex + "'");
+  is(endObj.value, aEndOffset, text + ": wrong end offset for index '" +
+     aSelectionIndex + "'");
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Private
 
 function testTextHelper(aID, aOffset, aBoundaryType,
                         aText, aStartOffset, aEndOffset,
                         aToDoFlag1, aToDoFlag2, aToDoFlag3,
                         aTextFunc, aTextFuncName)
 {
--- a/accessible/tests/mochitest/text/Makefile.in
+++ b/accessible/tests/mochitest/text/Makefile.in
@@ -45,15 +45,16 @@ relativesrcdir  = accessible/text
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		doc.html \
 		test_doc.html \
 		test_hypertext.html \
 		test_passwords.html \
+		test_selection.html \
 		test_singleline.html \
 		test_whitespaces.html \
 		test_words.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_selection.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test text selection functions</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../text.js"></script>
+
+  <script type="application/javascript">
+    
+    function doTest()
+    {
+      // Test selection count: clean selection / check count.
+      testTextAddSelection("div0", 0, 2, 1); // |Test selection...
+      cleanTextSelections("div0");
+      testTextSelectionCount("div0", 0);
+
+      // Test addition: adding two equal selections, the second one should
+      // not be added.
+      testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+      testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+      testTextGetSelection("div1", 7, 9, 0);
+
+      // Test overlapping selections: adding three selections, one adjacent.
+      testTextAddSelection("div2", 0, 3, 1); // |Tes|t adding 3...
+      testTextAddSelection("div2", 7, 9, 2); // |Tes|t ad|di|ng 3...
+      testTextAddSelection("div2", 3, 4, 3); // |Tes||t| ad|di|ng 3...
+      testTextGetSelection("div2", 0, 3, 0);
+      testTextGetSelection("div2", 3, 4, 1);
+      testTextGetSelection("div2", 7, 9, 2);
+
+      // Test selection re-ordering: adding two selections.
+      // NOTE: removeSelections aSelectionIndex is from start of document.
+      testTextAddSelection("div3", 0, 3, 1); // |Tes|t adding 2...
+      testTextAddSelection("div3", 7, 9, 2); // |Tes|t ad|di|ng 2...
+      testTextRemoveSelection("div3", 4, 1); // Test ad|di|ng 2...
+
+      // Test extending existing selection.
+      // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+      testTextAddSelection("div4", 4, 5, 1); // Test| |extending...
+      testTextSetSelection("div4", 4, 9, 6, 1); // Test| exte|nding...
+
+      // Test moving an existing selection.
+      // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+      testTextAddSelection("div5", 1, 3, 1); // T|es|t moving...
+      testTextSetSelection("div5", 5, 9, 6, 1); // Test |movi|ng...
+
+      // Test adding selections to multiple inner elements.
+      testTextAddSelection("div71", 0, 3, 1); // |Tes|t adding...
+      testTextAddSelection("div71", 7, 8, 2); // |Tes|t ad|d|ing...
+      testTextAddSelection("div72", 4, 6, 1); // Test| a|dding...
+      testTextAddSelection("div72", 7, 8, 2); // Test| a|d|d|ing...
+
+      // Test adding selection to parent element.
+      // NOTE: If inner elements are represented as embedded chars
+      //       we count their internal selections.
+      testTextAddSelection("div7", 7, 8, 5); // Test ad|d|ing...
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+
+</script>
+</head>
+
+<body>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="div0">Test selection count</div>
+  </br>
+  <div id="div1">Test adding two equal selections </div>
+  <div id="div2">Test adding 3 selections one adjacent </div>
+  <div id="div3">Test adding 2 selections, remove first one </div>
+  <div id="div4">Test extending a selection </div>
+  <div id="div5">Test moving a selection </div>
+  </br>
+  <div id="div7">Test adding selections to parent element
+    <div id="div71">Test adding selections to inner element1 </div>
+    <div id="div72">Test adding selections to inner element2 </div>
+  </div>
+
+</body>
+
+</html>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1037,22 +1037,27 @@ 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");
+pref("devtools.inspector.highlighterShowVeil", true);
+pref("devtools.inspector.highlighterShowInfobar", true);
 
 // 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);
@@ -1083,18 +1088,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,31 +171,38 @@
             <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_chromeDebugger"
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -526,33 +526,41 @@
                         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_chromeDebugger"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -121,16 +121,17 @@
     <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:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" 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();"/>
@@ -159,16 +160,20 @@
     <command id="Inspector:HTMLPanel"
              oncommand="InspectorUI.toggleHTMLPanel();"/>
     <command id="Inspector:CopyInner"
              oncommand="InspectorUI.copyInnerHTML();"/>
     <command id="Inspector:CopyOuter"
              oncommand="InspectorUI.copyOuterHTML();"/>
     <command id="Inspector:DeleteNode"
              oncommand="InspectorUI.deleteNode();"/>
+    <command id="Inspector:ToggleVeil"
+             oncommand="InspectorUI.toggleVeil();"/>
+    <command id="Inspector:ToggleInfobar"
+             oncommand="InspectorUI.toggleInfobar();"/>
   </commandset>
 
   <broadcasterset id="mainBroadcasterSet">
     <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
                  oncommand="toggleSidebar('viewBookmarksSidebar');"/>
 
     <!-- for both places and non-places, the sidebar lives at
@@ -252,16 +257,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 = {};
@@ -1690,34 +1696,46 @@ 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
@@ -9316,20 +9334,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
@@ -1010,19 +1010,38 @@
 
   <vbox id="browser-bottombox" layer="true">
     <toolbar id="inspector-toolbar"
              class="devtools-toolbar"
              nowindowdrag="true"
              hidden="true">
 #ifdef XP_MACOSX
       <toolbarbutton id="highlighter-closebutton"
+                     class="devtools-closebutton"
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
+      <toolbarbutton id="inspector-option-toolbarbutton"
+                     type="menu"
+                     tabindex="0"
+                     tooltiptext="&inspectOptionButton.tooltiptext;">
+        <menupopup id="inspector-option-popup"
+                   position="before_start">
+          <menuitem id="inspectorToggleVeil"
+                    type="checkbox"
+                    label="&inspectorToggleVeil.label;"
+                    accesskey="&inspectorToggleVeil.accesskey;"
+                    command="Inspector:ToggleVeil"/>
+          <menuitem id="inspectorToggleInfobar"
+                    type="checkbox"
+                    label="&inspectorToggleInfobar.label;"
+                    accesskey="&inspectorToggleInfobar.accesskey;"
+                    command="Inspector:ToggleInfobar"/>
+        </menupopup>
+      </toolbarbutton>
       <toolbarbutton id="inspector-inspect-toolbutton"
                      class="devtools-toolbarbutton"
                      command="Inspector:Inspect"/>
       <toolbarbutton id="inspector-treepanel-toolbutton"
                      class="devtools-toolbarbutton"
                      tabindex="0"
                      aria-label="&markupButton.arialabel;"
                      accesskey="&markupButton.accesskey;"
@@ -1043,20 +1062,56 @@
                        label="&inspectStyleButton.label;"
                        accesskey="&inspectStyleButton.accesskey;"
                        tabindex="0"
                        command="Inspector:Sidebar"/>
         <!-- registered tools go here -->
       </hbox>
 #ifndef XP_MACOSX
       <toolbarbutton id="highlighter-closebutton"
+                     class="devtools-closebutton"
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
     </toolbar>
+
+    <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/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -7,16 +7,21 @@
   top: 0;
   left: 0;
 }
 
 #highlighter-veil-container {
   overflow: hidden;
 }
 
+#highlighter-veil-container:not([dim]) > .highlighter-veil,
+#highlighter-veil-container:not([dim]) > hbox > .highlighter-veil {
+  visibility: hidden;
+}
+
 #highlighter-veil-container:not([disable-transitions]) > .highlighter-veil,
 #highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox,
 #highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > .highlighter-veil,
 #highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
   -moz-transition-property: width, height;
   -moz-transition-duration: 0.1s;
   -moz-transition-timing-function: linear;
 }
@@ -96,16 +101,20 @@ html|*#highlighter-nodeinfobar-tagname {
 html|*#highlighter-nodeinfobar-tagname {
   text-transform: lowercase;
 }
 
 .devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
   display: none;
 }
 
+#inspector-option-toolbarbutton > .toolbarbutton-menu-dropmarker {
+  display: none;
+}
+
 #inspector-layoutview-container > iframe {
   -moz-transition-property: height;
   -moz-transition-duration: 0.1s;
   /* header size */
   height: 22px;
 }
 
 #inspector-layoutview-container > iframe[open] {
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -62,29 +62,43 @@ let EXPORTED_SYMBOLS = ["DebuggerUI"];
  * @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 window, or stops it if already started.
    * @return RemoteDebuggerWindow if the debugger is started, null if stopped.
    */
   toggleRemoteDebugger: function DUI_toggleRemoteDebugger() {
     let win = this.chromeWindow;
@@ -110,17 +124,17 @@ DebuggerUI.prototype = {
     return new ChromeDebuggerProcess(win, aOnClose, aOnRun, true);
   },
 
   /**
    * 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 remote debugger for the current chrome window.
    * @return RemoteDebuggerWindow if a remote debugger exists, null otherwise.
    */
   getRemoteDebugger: function DUI_getRemoteDebugger() {
     let win = this.chromeWindow;
@@ -148,17 +162,18 @@ DebuggerUI.prototype = {
 /**
  * Creates a pane that will host the debugger.
  *
  * @param DebuggerUI aDebuggerUI
  *        The parent instance creating the new 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 = {
 
@@ -177,17 +192,17 @@ DebuggerPane.prototype = {
    */
   _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._splitter.setAttribute("class", "devtools-horizontal-splitter");
 
     this._frame = ownerDocument.createElement("iframe");
     this._frame.height = DebuggerPreferences.height;
 
     this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this._frame);
 
@@ -202,16 +217,17 @@ DebuggerPane.prototype = {
       // Bind shortcuts for accessing the breakpoint methods in the debugger.
       let bkp = self.contentWindow.DebuggerController.Breakpoints;
       self.addBreakpoint = bkp.addBreakpoint;
       self.removeBreakpoint = bkp.removeBreakpoint;
       self.getBreakpoint = bkp.getBreakpoint;
     }, true);
 
     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;
@@ -224,16 +240,18 @@ DebuggerPane.prototype = {
     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 contentWindow() {
     return this._frame ? this._frame.contentWindow : null;
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -93,16 +93,17 @@
               tabindex="0"/>
       <toolbarbutton id="step-out"
               class="devtools-toolbarbutton"
               label="&debuggerUI.stepOutButton;"
               tabindex="0"/>
       <menulist id="scripts" class="devtools-menulist"
                 label="&debuggerUI.emptyScriptText;"/>
       <textbox id="scripts-search" type="search"
+               class="devtools-searchinput"
                emptytext="&debuggerUI.emptyFilterText;"/>
       <spacer flex="1"/>
 #ifndef XP_MACOSX
       <toolbarbutton id="close" class="devtools-closebutton"/>
 #endif
     </toolbar>
     <hbox id="dbg-content" flex="1">
       <vbox id="stack" flex="1">
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -163,16 +163,17 @@ TreePanel.prototype = {
     } catch(e) {
       treeBox.height = 112;
     }
 
     treeBox.minHeight = 64;
 
     this.splitter = this.document.createElement("splitter");
     this.splitter.id = "inspector-tree-splitter";
+    this.splitter.className = "devtools-horizontal-splitter";
 
     let container = this.document.getElementById("appcontent");
     container.appendChild(this.splitter);
     container.appendChild(treeBox);
 
     treeBox.appendChild(this.treeIFrame);
 
     this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -99,17 +99,23 @@ const PSEUDO_CLASSES = [":hover", ":acti
  *   boolean isHidden();
  *
  *   // Redraw the highlighter if the visible portion of the node has changed.
  *   void invalidateSize(aScroll);
  *
  *   // Is a node highlightable.
  *   boolean isNodeHighlightable(aNode);
  *
- *   // Add/Remove lsiteners
+ *   // Show/hide the veil and the infobar
+ *   void showInfobar();
+ *   void hideInfobar();
+ *   void showVeil();
+ *   void hideVeil();
+ *
+ *   // Add/Remove listeners
  *   // @param aEvent - event name
  *   // @param aListener - function callback
  *   void addListener(aEvent, aListener);
  *   void removeListener(aEvent, aListener);
  *
  * Events:
  *
  *   "closed" - Highlighter is closing
@@ -166,16 +172,17 @@ Highlighter.prototype = {
     this.highlighterContainer.appendChild(this.veilContainer);
     this.highlighterContainer.appendChild(controlsBox);
 
     stack.appendChild(this.highlighterContainer);
 
     // The veil will make the whole page darker except
     // for the region of the selected box.
     this.buildVeil(this.veilContainer);
+    this.showVeil();
 
     this.buildInfobar(controlsBox);
 
     this.transitionDisabler = null;
 
     this.computeZoomFactor();
     this.unlock();
     this.hide();
@@ -362,33 +369,62 @@ Highlighter.prototype = {
   isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
   {
     if (aNode.nodeType != aNode.ELEMENT_NODE) {
       return false;
     }
     let nodeName = aNode.nodeName.toLowerCase();
     return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
   },
+
+  /**
+   * Hide the veil
+   */
+   hideVeil: function Highlighter_hideVeil() {
+     this.veilContainer.removeAttribute("dim");
+   },
+
+  /**
+   * Show the veil
+   */
+   showVeil: function Highlighter_showVeil() {
+     this.veilContainer.setAttribute("dim", "true");
+   },
+
+   /**
+    * Hide the infobar
+    */
+    hideInfobar: function Highlighter_hideInfobar() {
+      this.nodeInfo.container.setAttribute("hidden", "true");
+    },
+
+   /**
+    * Show the infobar
+    */
+    showInfobar: function Highlighter_showInfobar() {
+      this.nodeInfo.container.removeAttribute("hidden");
+      this.moveInfobar();
+    },
+
   /**
    * Build the veil:
    *
    * <vbox id="highlighter-veil-container">
    *   <box id="highlighter-veil-topbox" class="highlighter-veil"/>
    *   <hbox id="highlighter-veil-middlebox">
    *     <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
    *     <box id="highlighter-veil-transparentbox"/>
    *     <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
    *   </hbox>
    *   <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
    * </vbox>
    *
    * @param nsIDOMElement aParent
    *        The container of the veil boxes.
    */
-
   buildVeil: function Highlighter_buildVeil(aParent)
   {
     // We will need to resize these boxes to surround a node.
     // See highlightRectangle().
 
     this.veilTopBox = this.chromeDoc.createElement("box");
     this.veilTopBox.id = "highlighter-veil-topbox";
     this.veilTopBox.className = "highlighter-veil";
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -378,17 +378,17 @@ InspectorUI.prototype = {
     } else {
       this.sidebar.hide();
     }
   },
 
   /**
    * Toggle the TreePanel.
    */
-  toggleHTMLPanel: function TP_toggle()
+  toggleHTMLPanel: function TP_toggleHTMLPanel()
   {
     if (this.treePanel.isOpen()) {
       this.treePanel.close();
       Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
       this.currentInspector._htmlPanelOpen = false;
     } else {
       this.treePanel.open();
       Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
@@ -398,17 +398,49 @@ 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);
+  },
+
+  /**
+   * Toggle highlighter veil.
+   */
+  toggleVeil: function IUI_toggleVeil()
+  {
+    if (this.currentInspector._highlighterShowVeil) {
+      this.highlighter.hideVeil();
+      this.currentInspector._highlighterShowVeil = false;
+      Services.prefs.setBoolPref("devtools.inspector.highlighterShowVeil", false);
+    } else {
+      this.highlighter.showVeil();
+      this.currentInspector._highlighterShowVeil = true;
+      Services.prefs.setBoolPref("devtools.inspector.highlighterShowVeil", true);
+    }
+  },
+
+  /**
+   * Toggle highlighter infobar.
+   */
+  toggleInfobar: function IUI_toggleInfobar()
+  {
+    if (this.currentInspector._highlighterShowInfobar) {
+      this.highlighter.hideInfobar();
+      this.currentInspector._highlighterShowInfobar = false;
+      Services.prefs.setBoolPref("devtools.inspector.highlighterShowInfobar", false);
+    } else {
+      this.highlighter.showInfobar();
+      this.currentInspector._highlighterShowInfobar = true;
+      Services.prefs.setBoolPref("devtools.inspector.highlighterShowInfobar", true);
+    }
   },
 
   /**
    * Return the default selection element for the inspected document.
    */
   get defaultSelection()
   {
     let doc = this.win.document;
@@ -497,17 +529,17 @@ InspectorUI.prototype = {
 
     // Focus the first focusable element in the toolbar
     this.chromeDoc.commandDispatcher.advanceFocusIntoSubtree(this.toolbar);
 
     // If nothing is focused in the toolbar, it means that the focus manager
     // is limited to some specific elements and has moved the focus somewhere else.
     // So in this case, we want to focus the content window.
     // See: https://developer.mozilla.org/en/XUL_Tutorial/Focus_and_Selection#Platform_Specific_Behaviors
-    if (!this.toolbar.querySelector("-moz-focusring")) {
+    if (!this.toolbar.querySelector(":-moz-focusring")) {
       this.win.focus();
     }
 
   },
 
   /**
    * Initialize the InspectorStore.
    */
@@ -538,16 +570,22 @@ InspectorUI.prototype = {
         Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen");
 
       inspector._sidebarOpen =
         Services.prefs.getBoolPref("devtools.inspector.sidebarOpen");
 
       inspector._activeSidebar =
         Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
+      inspector._highlighterShowVeil =
+        Services.prefs.getBoolPref("devtools.inspector.highlighterShowVeil");
+
+      inspector._highlighterShowInfobar =
+        Services.prefs.getBoolPref("devtools.inspector.highlighterShowInfobar");
+
       this.win.addEventListener("pagehide", this, true);
 
       this._currentInspector = inspector;
     }
   },
 
   /**
    * Browse nodes according to the breadcrumbs layout, only for some specific
@@ -647,17 +685,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;
@@ -844,16 +882,33 @@ InspectorUI.prototype = {
     if (this.currentInspector._htmlPanelOpen) {
       this.treePanel.open();
     }
 
     if (this.currentInspector._sidebarOpen) {
       this._sidebar.show();
     }
 
+    let menu = this.chromeDoc.getElementById("inspectorToggleVeil");
+    if (this.currentInspector._highlighterShowVeil) {
+      menu.setAttribute("checked", "true");
+    } else {
+      menu.removeAttribute("checked");
+      this.highlighter.hideVeil();
+    }
+
+    menu = this.chromeDoc.getElementById("inspectorToggleInfobar");
+    if (this.currentInspector._highlighterShowInfobar) {
+      menu.setAttribute("checked", "true");
+      this.highlighter.showInfobar();
+    } else {
+      menu.removeAttribute("checked");
+      this.highlighter.hideInfobar();
+    }
+
     Services.obs.notifyObservers({wrappedJSObject: this},
                                  INSPECTOR_NOTIFICATIONS.OPENED, null);
   },
 
   /**
    * Main callback handler for events.
    *
    * @param event
--- 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,671 @@
+/* ***** 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" ];
+
+const NS_XHTML = 'http://www.w3.org/1999/xhtml';
+
+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._lastState = NOTIFICATIONS.HIDE;
+  this._pendingShowCallback = undefined;
+  this._pendingHide = false;
+}
+
+/**
+ * Inspector notifications dispatched through the nsIObserverService
+ */
+const NOTIFICATIONS = {
+  /** DeveloperToolbar.show() has been called, and we're working on it */
+  LOAD: "developer-toolbar-load",
+
+  /** DeveloperToolbar.show() has completed */
+  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
+ * @param aCallback show events can be asynchronous. If supplied aCallback will
+ * be called when the DeveloperToolbar is visible
+ */
+DeveloperToolbar.prototype.show = function DT_show(aCallback)
+{
+  if (this._lastState != NOTIFICATIONS.HIDE) {
+    return;
+  }
+
+  this._notify(NOTIFICATIONS.LOAD);
+  this._pendingShowCallback = aCallback;
+  this._pendingHide = false;
+
+  let checkLoad = function() {
+    if (this.tooltipPanel && this.tooltipPanel.loaded &&
+        this.outputPanel && this.outputPanel.loaded) {
+      this._onload();
+    }
+  }.bind(this);
+
+  this._input = this._doc.querySelector(".gclitoolbar-input-node");
+  this.tooltipPanel = new TooltipPanel(this._doc, this._input, checkLoad);
+  this.outputPanel = new OutputPanel(this._doc, this._input, checkLoad);
+};
+
+/**
+ * Initializing GCLI can only be done when we've got content windows to write
+ * to, so this needs to be done asynchronously.
+ */
+DeveloperToolbar.prototype._onload = function DT_onload()
+{
+  this._doc.getElementById("Tools:DevToolbar").setAttribute("checked", "true");
+
+  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._chromeWindow.getBrowser().tabContainer.addEventListener("TabSelect", this, false);
+  this._chromeWindow.getBrowser().addEventListener("load", this, true); 
+
+  this._element.hidden = false;
+
+  this._notify(NOTIFICATIONS.SHOW);
+  if (this._pendingShowCallback) {
+    this._pendingShowCallback.call();
+    this._pendingShowCallback = undefined;
+  }
+
+  // If a hide event happened while we were loading, then we need to hide.
+  // We could make this check earlier, but then cleanup would be complex so
+  // we're being inefficient for now.
+  if (this._pendingHide) {
+    this.hide();
+    return;
+  }
+
+  if (!DeveloperToolbar.introShownThisSession) {
+    this.display.maybeShowIntro();
+    DeveloperToolbar.introShownThisSession = true;
+  }
+};
+
+/**
+ * Hide the developer toolbar.
+ */
+DeveloperToolbar.prototype.hide = function DT_hide()
+{
+  if (this._lastState == NOTIFICATIONS.HIDE) {
+    return;
+  }
+
+  if (this._lastState == NOTIFICATIONS.LOAD) {
+    this._pendingHide = true;
+    return;
+  }
+
+  this._element.hidden = true;
+
+  this._doc.getElementById("Tools:DevToolbar").setAttribute("checked", "false");
+  this.destroy();
+
+  this._notify(NOTIFICATIONS.HIDE);
+};
+
+/**
+ * Hide the developer toolbar
+ */
+DeveloperToolbar.prototype.destroy = function DT_destroy()
+{
+  this._chromeWindow.getBrowser().tabContainer.removeEventListener("TabSelect", this, false);
+  this._chromeWindow.getBrowser().removeEventListener("load", this, true); 
+
+  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();
+  this.outputPanel.destroy();
+  this.tooltipPanel.destroy();
+  delete this._input;
+
+  // 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
+  delete this.display;
+  delete this.outputPanel;
+  delete this.tooltipPanel;
+};
+
+/**
+ * Utility for sending notifications
+ * @param aTopic a NOTIFICATION constant
+ */
+DeveloperToolbar.prototype._notify = function DT_notify(aTopic)
+{
+  this._lastState = 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" || aEvent.type == "load") {
+    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.
+ * @param aLoadCallback called when the panel is loaded properly.
+ */
+function OutputPanel(aChromeDoc, aInput, aLoadCallback)
+{
+  this._input = aInput;
+  this._anchor = aChromeDoc.getElementById("developer-toolbar");
+
+  this._loadCallback = aLoadCallback;
+
+  /*
+  <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>
+  */
+  this._panel = aChromeDoc.createElement("panel");
+  this._panel.id = "gcli-output";
+  this._panel.classList.add("gcli-panel");
+  this._panel.setAttribute("type", "arrow");
+  this._panel.setAttribute("noautofocus", "true");
+  this._panel.setAttribute("noautohide", "true");
+  this._anchor.parentElement.insertBefore(this._panel, this._anchor);
+
+  this._frame = aChromeDoc.createElement("iframe");
+  this._frame.id = "gcli-output-frame";
+  this._frame.setAttribute("src", "chrome://browser/content/devtools/gcliblank.xhtml");
+  this._frame.setAttribute("flex", "1");
+  this._panel.appendChild(this._frame);
+
+  this.displayedOutput = undefined;
+
+  this._onload = this._onload.bind(this);
+  this._frame.addEventListener("load", this._onload, true);
+
+  this.loaded = false;
+}
+
+/**
+ * Wire up the element from the iframe, and inform the _loadCallback.
+ */
+OutputPanel.prototype._onload = function OP_onload()
+{
+  this._frame.removeEventListener("load", this._onload, true);
+  delete this._onload;
+
+  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.loaded = true;
+  if (this._loadCallback) {
+    this._loadCallback();
+    delete this._loadCallback;
+  }
+};
+
+/**
+ * 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;
+  }
+};
+
+/**
+ * Detach listeners from the currently displayed Output.
+ */
+OutputPanel.prototype.destroy = function OP_destroy()
+{
+  this.remove();
+
+  this._panel.removeChild(this._frame);
+  this._anchor.parentElement.removeChild(this._panel);
+
+  delete this._input;
+  delete this._anchor;
+  delete this._panel;
+  delete this._frame;
+  delete this._content;
+  delete this._div;
+  delete this.document;
+};
+
+/**
+ * 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.
+ * @param aLoadCallback called when the panel is loaded properly.
+ */
+function TooltipPanel(aChromeDoc, aInput, aLoadCallback)
+{
+  this._input = aInput;
+  this._anchor = aChromeDoc.getElementById("developer-toolbar");
+
+  this._onload = this._onload.bind(this);
+  this._loadCallback = aLoadCallback;
+  /*
+  <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>
+  */
+  this._panel = aChromeDoc.createElement("panel");
+  this._panel.id = "gcli-tooltip";
+  this._panel.classList.add("gcli-panel");
+  this._panel.setAttribute("type", "arrow");
+  this._panel.setAttribute("noautofocus", "true");
+  this._panel.setAttribute("noautohide", "true");
+  this._anchor.parentElement.insertBefore(this._panel, this._anchor);
+
+  this._frame = aChromeDoc.createElement("iframe");
+  this._frame.id = "gcli-tooltip-frame";
+  this._frame.setAttribute("src", "chrome://browser/content/devtools/gcliblank.xhtml");
+  this._frame.setAttribute("flex", "1");
+  this._panel.appendChild(this._frame);
+
+  this._frame.addEventListener("load", this._onload, true);
+  this.loaded = false;
+}
+
+/**
+ * Wire up the element from the iframe, and inform the _loadCallback.
+ */
+TooltipPanel.prototype._onload = function TP_onload()
+{
+  this._frame.removeEventListener("load", this._onload, true);
+
+  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");
+
+  this.loaded = true;
+
+  if (this._loadCallback) {
+    this._loadCallback();
+    delete this._loadCallback;
+  }
+};
+
+/**
+ * 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();
+};
+
+/**
+ * Hide the TooltipPanel.
+ */
+TooltipPanel.prototype.destroy = function TP_destroy()
+{
+  this.remove();
+
+  this._panel.removeChild(this._frame);
+  this._anchor.parentElement.removeChild(this._panel);
+
+  delete this._input;
+  delete this._onload;
+  delete this._panel;
+  delete this._frame;
+  delete this._anchor;
+  delete this._content;
+  delete this.document;
+  delete this.hintElement;
+};
+
+/**
+ * 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/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.contentWindow.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');
old mode 100644
new mode 100755
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;
-  }
-}
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,291 @@
  * 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 console;
+let define;
+let require;
+let gcli;
+
+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.contentDocument);
+  }
 
   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(aCallback) {
+    if (DeveloperToolbar.visible) {
+      ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
+    }
+    else {
+      DeveloperToolbar.show(aCallback);
+    }
+  },
+
+  /**
+   * 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(function() {
+
+        try {
+          testFunc(browser, tab);
+        }
+        catch (ex) {
+          ok(false, "" + ex);
+          console.error(ex);
+          finish();
+          throw ex;
+        }
+      });
+    });
+  },
+};
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -433,33 +433,40 @@ Rule.prototype = {
     this.applyProperties();
     return prop;
   },
 
   /**
    * Reapply all the properties in this rule, and update their
    * computed styles.  Store disabled properties in the element
    * style's store.  Will re-mark overridden properties.
+   *
+   * @param {string} [aName]
+   *        A text property name (such as "background" or "border-top") used
+   *        when calling from setPropertyValue & setPropertyName to signify that
+   *        the property should be saved in store.userProperties.
    */
-  applyProperties: function Rule_applyProperties()
+  applyProperties: function Rule_applyProperties(aName)
   {
     let disabledProps = [];
     let store = this.elementStyle.store;
 
     for each (let prop in this.textProps) {
       if (!prop.enabled) {
         disabledProps.push({
           name: prop.name,
           value: prop.value,
           priority: prop.priority
         });
         continue;
       }
 
-      store.userProperties.setProperty(this.style, prop.name, prop.value);
+      if (aName && prop.name == aName) {
+        store.userProperties.setProperty(this.style, prop.name, prop.value);
+      }
 
       this.style.setProperty(prop.name, prop.value, prop.priority);
       // Refresh the property's priority from the style, to reflect
       // any changes made during parsing.
       prop.priority = this.style.getPropertyPriority(prop.name);
       prop.updateComputed();
     }
     this.elementStyle._changed();
@@ -481,17 +488,17 @@ Rule.prototype = {
    */
   setPropertyName: function Rule_setPropertyName(aProperty, aName)
   {
     if (aName === aProperty.name) {
       return;
     }
     this.style.removeProperty(aProperty.name);
     aProperty.name = aName;
-    this.applyProperties();
+    this.applyProperties(aName);
   },
 
   /**
    * Sets the value and priority of a property.
    *
    * @param {TextProperty} aProperty
    *        The property to manipulate.
    * @param {string} aValue
@@ -501,17 +508,17 @@ Rule.prototype = {
    */
   setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
   {
     if (aValue === aProperty.value && aPriority === aProperty.priority) {
       return;
     }
     aProperty.value = aValue;
     aProperty.priority = aPriority;
-    this.applyProperties();
+    this.applyProperties(aProperty.name);
   },
 
   /**
    * Disables or enables given TextProperty.
    */
   setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
   {
     aProperty.enabled = !!aValue;
@@ -1417,27 +1424,36 @@ TextPropertyEditor.prototype = {
     }
 
     if (this.prop.overridden && !this.editing) {
       this.element.classList.add("ruleview-overridden");
     } else {
       this.element.classList.remove("ruleview-overridden");
     }
 
-    this.nameSpan.textContent = this.prop.name;
+    let name = this.prop.name;
+    this.nameSpan.textContent = name;
 
     // Combine the property's value and priority into one string for
     // the value.
     let val = this.prop.value;
     if (this.prop.priority) {
       val += " !" + this.prop.priority;
     }
     this.valueSpan.textContent = val;
     this.warning.hidden = this._validate();
 
+    let store = this.prop.rule.elementStyle.store;
+    let propDirty = store.userProperties.contains(this.prop.rule.style, name);
+    if (propDirty) {
+      this.element.setAttribute("dirty", "");
+    } else {
+      this.element.removeAttribute("dirty");
+    }
+
     // Populate the computed styles.
     this._updateComputed();
   },
 
   _onStartEditing: function TextPropertyEditor_onStartEditing()
   {
     this.element.classList.remove("ruleview-overridden");
   },
@@ -1874,16 +1890,29 @@ UserProperties.prototype = {
     if (entry) {
       entry[aName] = aValue;
     } else {
       let props = {};
       props[aName] = aValue;
       this.weakMap.set(aStyle, props);
     }
   },
+
+  /**
+   * Check whether a named property for a given CSSStyleDeclaration is stored.
+   *
+   * @param {CSSStyleDeclaration} aStyle
+   *        The CSSStyleDeclaration against which the property would be mapped.
+   * @param {String} aName
+   *        The name of the property to check.
+   */
+  contains: function UP_contains(aStyle, aName) {
+    let entry = this.weakMap.get(aStyle, null);
+    return !!entry && aName in entry;
+  },
 };
 
 /**
  * Helper functions
  */
 
 /**
  * Create a child element with a set of attributes.
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -150,16 +150,22 @@ function testEditProperty()
       expectChange();
       input = aEditor.input;
       is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
 
       waitForEditorBlur(aEditor, function() {
         expectChange();
         is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
            "border-color should have been set.");
+
+        let props = ruleView.element.querySelectorAll(".ruleview-property");
+        for (let i = 0; i < props.length; i++) {
+          is(props[i].hasAttribute("dirty"), i <= 1,
+            "props[" + i + "] marked dirty as appropriate");
+        }
         testDisableProperty();
       });
 
       for each (let ch in "red;") {
         EventUtils.sendChar(ch, ruleDialog);
       }
     });
     for each (let ch in "border-color:") {
--- 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
  */
 
@@ -928,26 +894,21 @@ canon.Command = Command;
 
 
 /**
  * A wrapper for a paramSpec so we can sort out shortened versions names for
  * option switches
  */
 function Parameter(paramSpec, command, groupName) {
   this.command = command || { name: 'unnamed' };
-
-  Object.keys(paramSpec).forEach(function(key) {
-    this[key] = paramSpec[key];
-  }, this);
-
-  this.description = 'description' in this ? this.description : undefined;
-  this.description = lookup(this.description, 'canonDescNone');
-  this.manual = 'manual' in this ? this.manual : undefined;
-  this.manual = lookup(this.manual);
+  this.paramSpec = paramSpec;
+  this.name = this.paramSpec.name;
+  this.type = this.paramSpec.type;
   this.groupName = groupName;
+  this.defaultValue = this.paramSpec.defaultValue;
 
   if (!this.name) {
     throw new Error('In ' + this.command.name +
       ': all params must have a name');
   }
 
   var typeSpec = this.type;
   this.type = types.getType(typeSpec);
@@ -955,17 +916,17 @@ function Parameter(paramSpec, command, g
     console.error('Known types: ' + types.getTypeNames().join(', '));
     throw new Error('In ' + this.command.name + '/' + this.name +
       ': can\'t find type for: ' + JSON.stringify(typeSpec));
   }
 
   // boolean parameters have an implicit defaultValue:false, which should
   // not be changed. See the docs.
   if (this.type instanceof BooleanType) {
-    if ('defaultValue' in this) {
+    if (this.defaultValue !== undefined) {
       console.error('In ' + this.command.name + '/' + this.name +
           ': boolean parameters can not have a defaultValue.' +
           ' Ignoring');
     }
     this.defaultValue = false;
   }
 
   // Check the defaultValue for validity.
@@ -982,45 +943,94 @@ function Parameter(paramSpec, command, g
             defaultConversion.getStatus());
       }
     }
     catch (ex) {
       console.error('In ' + this.command.name + '/' + this.name +
         ': ' + ex);
     }
   }
+
+  // Some typed (boolean, array) have a non 'undefined' blank value. Give the
+  // type a chance to override the default defaultValue of undefined
+  if (this.defaultValue === undefined) {
+    this.defaultValue = this.type.getBlank().value;
+  }
 }
 
 /**
  * Does the given name uniquely identify this param (among the other params
  * in this command)
  * @param name The name to check
  */
 Parameter.prototype.isKnownAs = function(name) {
   if (name === '--' + this.name) {
     return true;
   }
   return false;
 };
 
 /**
+ * Read the default value for this parameter either from the parameter itself
+ * (if this function has been over-ridden) or from the type, or from calling
+ * parseString on an empty string
+ */
+Parameter.prototype.getBlank = function() {
+  var conversion;
+
+  if (this.type.getBlank) {
+    return this.type.getBlank();
+  }
+
+  return this.type.parseString('');
+};
+
+/**
+ * Resolve the manual for this parameter, by looking in the paramSpec
+ * and doing a l10n lookup
+ */
+Object.defineProperty(Parameter.prototype, 'manual', {
+  get: function() {
+    return lookup(this.paramSpec.manual || undefined);
+  },
+  enumerable: true
+});
+
+/**
+ * Resolve the description for this parameter, by looking in the paramSpec
+ * and doing a l10n lookup
+ */
+Object.defineProperty(Parameter.prototype, 'description', {
+  get: function() {
+    return lookup(this.paramSpec.description || undefined, 'canonDescNone');
+  },
+  enumerable: true
+});
+
+/**
  * Is the user required to enter data for this parameter? (i.e. has
  * defaultValue been set to something other than undefined)
  */
-Parameter.prototype.isDataRequired = function() {
-  return this.defaultValue === undefined;
-};
+Object.defineProperty(Parameter.prototype, 'isDataRequired', {
+  get: function() {
+    return this.defaultValue === undefined;
+  },
+  enumerable: true
+});
 
 /**
  * Are we allowed to assign data to this parameter using positional
  * parameters?
  */
-Parameter.prototype.isPositionalAllowed = function() {
-  return this.groupName == null;
-};
+Object.defineProperty(Parameter.prototype, 'isPositionalAllowed', {
+  get: function() {
+    return this.groupName == null;
+  },
+  enumerable: true
+});
 
 canon.Parameter = Parameter;
 
 
 /**
  * Add a command to the canon of known commands.
  * This function is exposed to the outside world (via gcli/index). It is
  * documented in docs/index.md for all the world to see.
@@ -1028,34 +1038,34 @@ canon.Parameter = Parameter;
  * @return The new command
  */
 canon.addCommand = function addCommand(commandSpec) {
   var command = new Command(commandSpec);
   commands[commandSpec.name] = command;
   commandNames.push(commandSpec.name);
   commandNames.sort();
 
-  canon.canonChange();
+  canon.onCanonChange();
   return command;
 };
 
 /**
  * Remove an individual command. The opposite of #addCommand().
  * @param commandOrName Either a command name or the command itself.
  */
 canon.removeCommand = function removeCommand(commandOrName) {
   var name = typeof commandOrName === 'string' ?
           commandOrName :
           commandOrName.name;
   delete commands[name];
   commandNames = commandNames.filter(function(test) {
     return test !== name;
   });
 
-  canon.canonChange();
+  canon.onCanonChange();
 };
 
 /**
  * Retrieve a command by name
  * @param name The name of the command to retrieve
  */
 canon.getCommand = function getCommand(name) {
   // '|| undefined' is to silence 'reference to undefined property' warnings
@@ -1077,56 +1087,32 @@ canon.getCommands = function getCommands
  */
 canon.getCommandNames = function getCommandNames() {
   return commandNames.slice(0);
 };
 
 /**
  * Enable people to be notified of changes to the list of commands
  */
-canon.canonChange = util.createEvent('canon.canonChange');
+canon.onCanonChange = util.createEvent('canon.onCanonChange');
 
 /**
  * CommandOutputManager stores the output objects generated by executed
  * commands.
  *
  * CommandOutputManager is exposed (via canon.commandOutputManager) to the the
  * outside world and could (but shouldn't) be used before gcli.startup() has
  * been called. This could should be defensive to that where possible, and we
  * should certainly document if the use of it or similar will fail if used too
  * soon.
  */
 function CommandOutputManager() {
-  this._event = util.createEvent('CommandOutputManager');
+  this.onOutput = util.createEvent('CommandOutputManager.onOutput');
 }
 
-/**
- * Call this method to notify the manager (and therefore all listeners) of a
- * new or updated command output.
- * @param output The command output object that has been created or updated.
- */
-CommandOutputManager.prototype.sendCommandOutput = function(output) {
-  this._event({ output: output });
-};
-
-/**
- * Register a function to be called whenever there is a new command output
- * object.
- */
-CommandOutputManager.prototype.addListener = function(fn, ctx) {
-  this._event.add(fn, ctx);
-};
-
-/**
- * Undo the effects of CommandOutputManager.addListener()
- */
-CommandOutputManager.prototype.removeListener = function(fn, ctx) {
-  this._event.remove(fn, ctx);
-};
-
 canon.CommandOutputManager = CommandOutputManager;
 
 /**
  * We maintain a global command output manager for the majority case where
  * there is only one important set of outputs.
  */
 canon.commandOutputManager = new CommandOutputManager();
 
@@ -1142,50 +1128,91 @@ define('gcli/util', ['require', 'exports
 
 /*
  * A number of DOM manipulation and event handling utilities.
  */
 
 
 //------------------------------------------------------------------------------
 
+var eventDebug = false;
+
+/**
+ * Useful way to create a name for a handler, used in createEvent()
+ */
+function nameFunction(handler) {
+  var scope = handler.scope ? handler.scope.constructor.name + '.' : '';
+  var name = handler.func.name;
+  if (name) {
+    return scope + name;
+  }
+  for (var prop in handler.scope) {
+    if (handler.scope[prop] === handler.func) {
+      return scope + prop;
+    }
+  }
+  return scope + handler.func;
+}
+
 /**
  * Create an event.
  * For use as follows:
  *
  *   function Hat() {
- *     this.putOn = createEvent();
+ *     this.putOn = createEvent('Hat.putOn');
  *     ...
  *   }
  *   Hat.prototype.adorn = function(person) {
  *     this.putOn({ hat: hat, person: person });
  *     ...
  *   }
  *
  *   var hat = new Hat();
  *   hat.putOn.add(function(ev) {
  *     console.log('The hat ', ev.hat, ' has is worn by ', ev.person);
  *   }, scope);
  *
  * @param name Optional name to help with debugging
  */
 exports.createEvent = function(name) {
   var handlers = [];
+  var holdFire = false;
+  var heldEvents = [];
+  var eventCombiner = undefined;
 
   /**
    * This is how the event is triggered.
    * @param ev The event object to be passed to the event listeners
    */
   var event = function(ev) {
+    if (holdFire) {
+      heldEvents.push(ev);
+      if (eventDebug) {
+        console.log('Held fire: ' + name, ev);
+      }
+      return;
+    }
+
+    if (eventDebug) {
+      console.group('Fire: ' + name + ' to ' + handlers.length + ' listeners', ev);
+    }
+
     // Use for rather than forEach because it step debugs better, which is
     // important for debugging events
     for (var i = 0; i < handlers.length; i++) {
       var handler = handlers[i];
+      if (eventDebug) {
+        console.log(nameFunction(handler));
+      }
       handler.func.call(handler.scope, ev);
     }
+
+    if (eventDebug) {
+      console.groupEnd();
+    }
   };
 
   /**
    * Add a new handler function
    * @param func The function to call when this event is triggered
    * @param scope Optional 'this' object for the function call
    */
   event.add = function(func, scope) {
@@ -1207,158 +1234,284 @@ exports.createEvent = function(name) {
   /**
    * Remove all handlers.
    * Reset the state of this event back to it's post create state
    */
   event.removeAll = function() {
     handlers = [];
   };
 
+  /**
+   * Temporarily prevent this event from firing.
+   * @see resumeFire(ev)
+   */
+  event.holdFire = function() {
+    if (eventDebug) {
+      console.group('Holding fire: ' + name);
+    }
+
+    holdFire = true;
+  };
+
+  /**
+   * Resume firing events.
+   * If there are heldEvents, then we fire one event to cover them all. If an
+   * event combining function has been provided then we use that to combine the
+   * events. Otherwise the last held event is used.
+   * @see holdFire()
+   */
+  event.resumeFire = function() {
+    if (eventDebug) {
+      console.groupEnd('Resume fire: ' + name);
+    }
+
+    if (holdFire !== true) {
+      throw new Error('Event not held: ' + name);
+    }
+
+    holdFire = false;
+    if (heldEvents.length === 0) {
+      return;
+    }
+
+    if (heldEvents.length === 1) {
+      event(heldEvents[0]);
+    }
+    else {
+      var first = heldEvents[0];
+      var last = heldEvents[heldEvents.length - 1];
+      if (eventCombiner) {
+        event(eventCombiner(first, last, heldEvents));
+      }
+      else {
+        event(last);
+      }
+    }
+
+    heldEvents = [];
+  };
+
+  /**
+   * When resumeFire has a number of events to combine, by default it just
+   * picks the last, however you can provide an eventCombiner which returns a
+   * combined event.
+   * eventCombiners will be passed 3 parameters:
+   * - first The first event to be held
+   * - last The last event to be held
+   * - all An array containing all the held events
+   * The return value from an eventCombiner is expected to be an event object
+   */
+  Object.defineProperty(event, 'eventCombiner', {
+    set: function(newEventCombiner) {
+      if (typeof newEventCombiner !== 'function') {
+        throw new Error('eventCombiner is not a function');
+      }
+      eventCombiner = newEventCombiner;
+    },
+
+    enumerable: true
+  });
+
   return event;
 };
 
 
 //------------------------------------------------------------------------------
 
-var dom = {};
-
 /**
  * XHTML namespace
  */
-dom.NS_XHTML = 'http://www.w3.org/1999/xhtml';
+exports.NS_XHTML = 'http://www.w3.org/1999/xhtml';
 
 /**
  * XUL namespace
  */
-dom.NS_XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+exports.NS_XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
 
 /**
  * Create an HTML or XHTML element depending on whether the document is HTML
  * or XML based. Where HTML/XHTML elements are distinguished by whether they
  * are created using doc.createElementNS('http://www.w3.org/1999/xhtml', tag)
  * or doc.createElement(tag)
  * If you want to create a XUL element then you don't have a problem knowing
  * what namespace you want.
  * @param doc The document in which to create the element
  * @param tag The name of the tag to create
  * @returns The created element
  */
-dom.createElement = function(doc, tag) {
-  if (dom.isXmlDocument(doc)) {
-    return doc.createElementNS(dom.NS_XHTML, tag);
+exports.createElement = function(doc, tag) {
+  if (exports.isXmlDocument(doc)) {
+    return doc.createElementNS(exports.NS_XHTML, tag);
   }
   else {
     return doc.createElement(tag);
   }
 };
 
 /**
  * Remove all the child nodes from this node
  * @param elem The element that should have it's children removed
  */
-dom.clearElement = function(elem) {
+exports.clearElement = function(elem) {
   while (elem.hasChildNodes()) {
     elem.removeChild(elem.firstChild);
   }
 };
 
 var isAllWhitespace = /^\s*$/;
 
 /**
  * Iterate over the children of a node looking for TextNodes that have only
  * whitespace content and remove them.
  * This utility is helpful when you have a template which contains whitespace
  * so it looks nice, but where the whitespace interferes with the rendering of
  * the page
  * @param elem The element which should have blank whitespace trimmed
  * @param deep Should this node removal include child elements
  */
-dom.removeWhitespace = function(elem, deep) {
+exports.removeWhitespace = function(elem, deep) {
   var i = 0;
   while (i < elem.childNodes.length) {
     var child = elem.childNodes.item(i);
-    if (child.nodeType === Node.TEXT_NODE &&
+    if (child.nodeType === 3 /*Node.TEXT_NODE*/ &&
         isAllWhitespace.test(child.textContent)) {
       elem.removeChild(child);
     }
     else {
-      if (deep && child.nodeType === Node.ELEMENT_NODE) {
-        dom.removeWhitespace(child, deep);
+      if (deep && child.nodeType === 1 /*Node.ELEMENT_NODE*/) {
+        exports.removeWhitespace(child, deep);
       }
       i++;
     }
   }
 };
 
 /**
  * Create a style element in the document head, and add the given CSS text to
  * it.
  * @param cssText The CSS declarations to append
  * @param doc The document element to work from
- */
-dom.importCss = function(cssText, doc) {
+ * @param id Optional id to assign to the created style tag. If the id already
+ * exists on the document, we do not add the CSS again.
+ */
+exports.importCss = function(cssText, doc, id) {
+  if (!cssText) {
+    return undefined;
+  }
+
   doc = doc || document;
 
-  var style = dom.createElement(doc, 'style');
+  if (!id) {
+    id = 'hash-' + hash(cssText);
+  }
+
+  var found = doc.getElementById(id);
+  if (found) {
+    if (found.tagName.toLowerCase() !== 'style') {
+      console.error('Warning: importCss passed id=' + id +
+              ', but that pre-exists (and isn\'t a style tag)');
+    }
+    return found;
+  }
+
+  var style = exports.createElement(doc, 'style');
+  style.id = id;
   style.appendChild(doc.createTextNode(cssText));
 
   var head = doc.getElementsByTagName('head')[0] || doc.documentElement;
   head.appendChild(style);
 
   return style;
 };
 
 /**
+ * Simple hash function which happens to match Java's |String.hashCode()|
+ * Done like this because I we don't need crypto-security, but do need speed,
+ * and I don't want to spend a long time working on it.
+ * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
+ */
+function hash(str) {
+  var hash = 0;
+  if (str.length == 0) {
+    return hash;
+  }
+  for (var i = 0; i < str.length; i++) {
+    var char = str.charCodeAt(i);
+    hash = ((hash << 5) - hash) + char;
+    hash = hash & hash; // Convert to 32bit integer
+  }
+  return hash;
+}
+
+/**
  * There are problems with innerHTML on XML documents, so we need to do a dance
  * using document.createRange().createContextualFragment() when in XML mode
  */
-dom.setInnerHtml = function(elem, html) {
-  if (dom.isXmlDocument(elem.ownerDocument)) {
+exports.setContents = function(elem, contents) {
+  if (typeof HTMLElement !== 'undefined' && contents instanceof HTMLElement) {
+    exports.clearElement(elem);
+    elem.appendChild(contents);
+    return;
+  }
+
+  if (exports.isXmlDocument(elem.ownerDocument)) {
     try {
-      dom.clearElement(elem);
-      html = '<div xmlns="' + dom.NS_XHTML + '">' + html + '</div>';
+      var ns = elem.ownerDocument.documentElement.namespaceURI;
+      if (!ns) {
+        ns = exports.NS_XHTML;
+      }
+      exports.clearElement(elem);
+      contents = '<div xmlns="' + ns + '">' + contents + '</div>';
       var range = elem.ownerDocument.createRange();
-      var child = range.createContextualFragment(html).firstChild;
+      var child = range.createContextualFragment(contents).firstChild;
       while (child.hasChildNodes()) {
         elem.appendChild(child.firstChild);
       }
     }
     catch (ex) {
       console.error('Bad XHTML', ex);
       console.trace();
       throw ex;
     }
   }
   else {
-    elem.innerHTML = html;
-  }
+    elem.innerHTML = contents;
+  }
+};
+
+/**
+ * Load some HTML into the given document and return a DOM element.
+ * This utility assumes that the html has a single root (other than whitespace)
+ */
+exports.toDom = function(document, html) {
+  var div = exports.createElement(document, 'div');
+  exports.setContents(div, html);
+  return div.children[0];
 };
 
 /**
  * How to detect if we're in an XML document.
  * In a Mozilla we check that document.xmlVersion = null, however in Chrome
  * we use document.contentType = undefined.
  * @param doc The document element to work from (defaulted to the global
  * 'document' if missing
  */
-dom.isXmlDocument = function(doc) {
+exports.isXmlDocument = function(doc) {
   doc = doc || document;
   // Best test for Firefox
   if (doc.contentType && doc.contentType != 'text/html') {
     return true;
   }
   // Best test for Chrome
   if (doc.xmlVersion != null) {
     return true;
   }
   return false;
 };
 
-exports.dom = dom;
-
 /**
  * Find the position of [element] in [nodeList].
  * @returns an index of the match, or -1 if there is no match
  */
 function positionInNodeList(element, nodeList) {
   for (var i = 0; i < nodeList.length; i++) {
     if (element === nodeList[i]) {
       return i;
@@ -1367,17 +1520,17 @@ function positionInNodeList(element, nod
   return -1;
 }
 
 /**
  * Find a unique CSS selector for a given element
  * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  * and ele.ownerDocument.querySelectorAll(reply).length === 1
  */
-dom.findCssSelector = function(ele) {
+exports.findCssSelector = function(ele) {
   var document = ele.ownerDocument;
   if (ele.id && document.getElementById(ele.id) === ele) {
     return '#' + ele.id;
   }
 
   // Inherently unique by tag name
   var tagName = ele.tagName.toLowerCase();
   if (tagName === 'html') {
@@ -1417,46 +1570,68 @@ dom.findCssSelector = function(ele) {
       if (matches.length === 1) {
         return selector;
       }
     }
   }
 
   // So we can be unique w.r.t. our parent, and use recursion
   index = positionInNodeList(ele, ele.parentNode.children) + 1;
-  selector = dom.findCssSelector(ele.parentNode) + ' > ' +
+  selector = exports.findCssSelector(ele.parentNode) + ' > ' +
           tagName + ':nth-child(' + index + ')';
 
   return selector;
 };
 
+/**
+ * Work out the path for images.
+ */
+exports.createUrlLookup = function(callingModule) {
+  return function imageUrl(path) {
+    try {
+      return require('text!gcli/ui/' + path);
+    }
+    catch (ex) {
+      var filename = callingModule.id.split('/').pop() + '.js';
+