Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Fri, 11 May 2012 14:35:58 -0700
changeset 95197 ff94073c1907847eb47adad281abf16a9fb0892c
parent 95110 7b00180f4f3463a576fffb72f58a44956ff07205 (current diff)
parent 95196 7b5d636382b2e67ad277bfbbdf73c781ce954767 (diff)
child 95198 c0537e1c5e8b097d14d8b73251a077103e2ef44f
push idunknown
push userunknown
push dateunknown
milestone15.0a1
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) + ' >