Merge mozilla-central into services-central
authorGregory Szorc <gps@mozilla.com>
Sun, 08 Jul 2012 20:48:04 -0700
changeset 98770 24312b9671c64fc218772bf1320b33690c54d0fd
parent 98769 5099eb0d0287ed8214ce5c650e43ec2eb8322bcc (current diff)
parent 98663 1ee6c61e0a0bcfe916f577d60439aadc7a181e97 (diff)
child 98771 37fc31499d8a9013a26549f432be1faf0e1d78e7
push id11640
push userryanvm@gmail.com
push dateTue, 10 Jul 2012 00:53:29 +0000
treeherdermozilla-inbound@6b0d194eabed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central into services-central
build/update-settings.ini.in
content/events/test/bug426082.html
content/events/test/bug656379-1.html
dom/battery/nsIDOMNavigatorBattery.idl
editor/libeditor/base/DeleteElementTxn.cpp
editor/libeditor/base/DeleteElementTxn.h
editor/reftests/spellcheck-textarea-ref2.html
gfx/layers/LayerTreeInvalidation.cpp
gfx/layers/LayerTreeInvalidation.h
js/src/shell/jsworkers.cpp
js/src/shell/jsworkers.h
js/src/tests/js1_8_5/extensions/worker-error-child.js
js/src/tests/js1_8_5/extensions/worker-error-propagation-child.js
js/src/tests/js1_8_5/extensions/worker-error-propagation.js
js/src/tests/js1_8_5/extensions/worker-error.js
js/src/tests/js1_8_5/extensions/worker-fib-child.js
js/src/tests/js1_8_5/extensions/worker-fib.js
js/src/tests/js1_8_5/extensions/worker-init-child.js
js/src/tests/js1_8_5/extensions/worker-init.js
js/src/tests/js1_8_5/extensions/worker-simple-child.js
js/src/tests/js1_8_5/extensions/worker-simple.js
js/src/tests/js1_8_5/extensions/worker-terminate-child.js
js/src/tests/js1_8_5/extensions/worker-terminate-iloop.js
js/src/tests/js1_8_5/extensions/worker-terminate.js
js/src/tests/js1_8_5/extensions/worker-timeout-child.js
js/src/tests/js1_8_5/extensions/worker-timeout.js
layout/base/nsDisplayListInvalidation.h
layout/base/tests/bug450930.xhtml
layout/base/tests/decoration_line_rendering.js
layout/reftests/css-gradients/radial-onestopposition-1.html
layout/reftests/css-gradients/repeating-radial-onestopposition-1.html
media/webrtc/trunk/tools/python_charts/gviz_api.py
media/webrtc/trunk/tools/quality_tracking/dashboard/gaeunit.py
media/webrtc/trunk/tools/quality_tracking/dashboard/gviz_api.py
media/webrtc/trunk/tools/quality_tracking/oauth2
mobile/android/base/resources/drawable-hdpi/ic_menu_find_in_page.png
mobile/android/base/resources/drawable/ic_menu_find_in_page.png
mobile/android/chrome/content/cursor.css
toolkit/components/social/test/xpcshell/test_getProvider.js
--- a/accessible/build/Makefile.in
+++ b/accessible/build/Makefile.in
@@ -27,14 +27,18 @@ SHARED_LIBRARY_LIBS = \
   ../src/base/$(LIB_PREFIX)accessibility_base_s.$(LIB_SUFFIX) \
   ../src/generic/$(LIB_PREFIX)accessibility_generic_s.$(LIB_SUFFIX) \
   ../src/html/$(LIB_PREFIX)accessibility_html_s.$(LIB_SUFFIX) \
   ../src/xpcom/$(LIB_PREFIX)accessibility_xpcom_s.$(LIB_SUFFIX) \
   ../src/$(LIB_PREFIX)accessibility_toolkit_s.$(LIB_SUFFIX) \
   ../src/xforms/$(LIB_PREFIX)accessibility_xforms_s.$(LIB_SUFFIX) \
   $(NULL)
 
+ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
+SHARED_LIBRARY_LIBS += ../src/windows/uia/$(LIB_PREFIX)accessibility_toolkit_uia_s.$(LIB_SUFFIX)
+endif
+
 ifdef MOZ_XUL
 SHARED_LIBRARY_LIBS += ../src/xul/$(LIB_PREFIX)accessibility_xul_s.$(LIB_SUFFIX)
 endif
 
 include $(topsrcdir)/config/rules.mk
 
--- a/accessible/public/nsIAccessibleProvider.idl
+++ b/accessible/public/nsIAccessibleProvider.idl
@@ -57,18 +57,18 @@ interface nsIAccessibleProvider : nsISup
   const long XULStatusBar = 0x00001014;
   const long XULRadioButton = 0x00001015;
   const long XULRadioGroup = 0x00001016;
 
   /** Used for XUL tab element */
   const long XULTab = 0x00001017;
   /** Used for XUL tabs element, a container for tab elements */
   const long XULTabs = 0x00001018;
-  /** Used for XUL tabpanels container element */
-  const long XULTabpanels = 0x00001019;
+  /** Used for XUL deck frame */
+  const long XULDeck = 0x00001019;
 
   const long XULText             = 0x0000101A;
   const long XULTextBox          = 0x0000101B;
   const long XULThumb            = 0x0000101C;
   const long XULTree             = 0x0000101D;
   const long XULTreeColumns      = 0x0000101E;
   const long XULTreeColumnItem   = 0x0000101F;
   const long XULToolbar          = 0x00001020;
--- a/accessible/src/Makefile.in
+++ b/accessible/src/Makefile.in
@@ -9,17 +9,20 @@ srcdir = @srcdir@
 VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 PLATFORM_DIR = atk
 else
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
-PLATFORM_DIR = msaa
+PLATFORM_DIR = \
+  msaa \
+  windows \
+  $(null)
 else
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 PLATFORM_DIR += mac
 else
 PLATFORM_DIR += other
 endif
 endif
 endif
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -1148,21 +1148,24 @@ nsAccessibilityService::GetOrCreateAcces
 
   if (!newAcc) {
     // Elements may implement nsIAccessibleProvider via XBL. This allows them to
     // say what kind of accessible to create.
     newAcc = CreateAccessibleByType(content, docAcc);
   }
 
   if (!newAcc) {
-    // Create generic accessibles for SVG and MathML nodes.
-    if (content->IsSVG(nsGkAtoms::svg)) {
+    // xul:deck does not have XBL and nsIFrame::CreateAccessible() is only called 
+    // on HTML elements
+    nsIAtom* tag = content->Tag();
+    if ((tag == nsGkAtoms::deck) || (tag == nsGkAtoms::tabpanels)) {
+      newAcc = new XULDeckAccessible(content, docAcc);
+    } else if (content->IsSVG(nsGkAtoms::svg)) {
       newAcc = new EnumRoleAccessible(content, docAcc, roles::DIAGRAM);
-    }
-    else if (content->IsMathML(nsGkAtoms::math)) {
+    } else if (content->IsMathML(nsGkAtoms::math)) {
       newAcc = new EnumRoleAccessible(content, docAcc, roles::EQUATION);
     }
   }
 
   if (!newAcc) {
     newAcc = CreateAccessibleForDeckChild(weakFrame.GetFrame(), content,
                                           docAcc);
   }
@@ -1321,16 +1324,20 @@ nsAccessibilityService::CreateAccessible
     case nsIAccessibleProvider::XULColorPickerTile:
       accessible = new XULColorPickerTileAccessible(aContent, aDoc);
       break;
 
     case nsIAccessibleProvider::XULCombobox:
       accessible = new XULComboboxAccessible(aContent, aDoc);
       break;
 
+    case nsIAccessibleProvider::XULDeck:
+      accessible = new XULDeckAccessible(aContent, aDoc);
+      break;
+
     case nsIAccessibleProvider::XULDropmarker:
       accessible = new XULDropmarkerAccessible(aContent, aDoc);
       break;
 
     case nsIAccessibleProvider::XULGroupbox:
       accessible = new XULGroupboxAccessible(aContent, aDoc);
       break;
 
@@ -1425,20 +1432,16 @@ nsAccessibilityService::CreateAccessible
     case nsIAccessibleProvider::XULTab:
       accessible = new XULTabAccessible(aContent, aDoc);
       break;
 
     case nsIAccessibleProvider::XULTabs:
       accessible = new XULTabsAccessible(aContent, aDoc);
       break;
 
-    case nsIAccessibleProvider::XULTabpanels:
-      accessible = new XULTabpanelsAccessible(aContent, aDoc);
-      break;
-
     case nsIAccessibleProvider::XULText:
       accessible = new XULLabelAccessible(aContent, aDoc);
       break;
 
     case nsIAccessibleProvider::XULTextBox:
       accessible = new XULTextFieldAccessible(aContent, aDoc);
       break;
 
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -503,16 +503,18 @@ public:
   mozilla::a11y::ImageAccessible* AsImage();
 
   bool IsImageMapAccessible() const { return mFlags & eImageMapAccessible; }
   mozilla::a11y::HTMLImageMapAccessible* AsImageMap();
 
   inline bool IsXULTree() const { return mFlags & eXULTreeAccessible; }
   mozilla::a11y::XULTreeAccessible* AsXULTree();
 
+  inline bool IsXULDeck() const { return mFlags & eXULDeckAccessible; }
+
   inline bool IsListControl() const { return mFlags & eListControlAccessible; }
 
   inline bool IsMenuButton() const { return mFlags & eMenuButtonAccessible; }
 
   inline bool IsMenuPopup() const { return mFlags & eMenuPopupAccessible; }
 
   inline bool IsRoot() const { return mFlags & eRootAccessible; }
   mozilla::a11y::RootAccessible* AsRoot();
@@ -761,17 +763,18 @@ protected:
     eHTMLListItemAccessible = 1 << 11,
     eImageAccessible = 1 << 12,
     eImageMapAccessible = 1 << 13,
     eListControlAccessible = 1 << 14,
     eMenuButtonAccessible = 1 << 15,
     eMenuPopupAccessible = 1 << 16,
     eRootAccessible = 1 << 17,
     eTextLeafAccessible = 1 << 18,
-    eXULTreeAccessible = 1 << 19
+    eXULDeckAccessible = 1 << 19,
+    eXULTreeAccessible = 1 << 20
   };
 
   //////////////////////////////////////////////////////////////////////////////
   // Miscellaneous helpers
 
   /**
    * Return ARIA role (helper method).
    */
--- a/accessible/src/mac/AccessibleWrap.mm
+++ b/accessible/src/mac/AccessibleWrap.mm
@@ -54,16 +54,19 @@ AccessibleWrap::GetNativeInterface (void
 
 // overridden in subclasses to create the right kind of object. by default we create a generic
 // 'mozAccessible' node.
 Class
 AccessibleWrap::GetNativeType () 
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
+  if (IsXULDeck())
+    return [mozPaneAccessible class];
+  
   roles::Role role = Role();
   switch (role) {
     case roles::PUSHBUTTON:
     case roles::SPLITBUTTON:
     case roles::TOGGLE_BUTTON:
     {
       // if this button may show a popup, let's make it of the popupbutton type.
       return HasPopup() ? [mozPopupButtonAccessible class] : 
@@ -76,25 +79,27 @@ AccessibleWrap::GetNativeType ()
     case roles::CHECKBUTTON:
       return [mozCheckboxAccessible class];
       
     case roles::HEADING:
       return [mozHeadingAccessible class];
 
     case roles::PAGETABLIST:
       return [mozTabsAccessible class];
-      
+
     case roles::ENTRY:
     case roles::STATICTEXT:
     case roles::CAPTION:
     case roles::ACCEL_LABEL:
-    case roles::TEXT_LEAF:
     case roles::PASSWORD_TEXT:
       // normal textfield (static or editable)
-      return [mozTextAccessible class]; 
+      return [mozTextAccessible class];
+
+    case roles::TEXT_LEAF:
+      return [mozTextLeafAccessible class];
 
     case roles::LINK:
       return [mozLinkAccessible class];
 
     case roles::COMBOBOX:
       return [mozPopupButtonAccessible class];
       
     default:
--- a/accessible/src/mac/Makefile.in
+++ b/accessible/src/mac/Makefile.in
@@ -9,18 +9,16 @@ VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = accessibility
 LIBRARY_NAME = accessibility_toolkit_s
 EXPORT_LIBRARY = ..
 LIBXUL_LIBRARY = 1
 
-  
-  
 CMMSRCS = \
           AccessibleWrap.mm \
           ApplicationAccessibleWrap.mm \
           DocAccessibleWrap.mm \
           nsAccessNodeWrap.mm \
           mozAccessible.mm \
           mozDocAccessible.mm \
           mozActionElements.mm \
@@ -43,10 +41,12 @@ include $(topsrcdir)/config/rules.mk
 LOCAL_INCLUDES += \
   -I$(srcdir) \
   -I$(srcdir)/../base \
   -I$(srcdir)/../generic \
   -I$(srcdir)/../html \
   -I$(srcdir)/../xul \
   -I$(topsrcdir)/widget/cocoa \
   -I$(topsrcdir)/widget/xpwidgets \
+  -I$(topsrcdir)/layout/xul/base/src \
+  -I$(topsrcdir)/layout/generic \
   $(NULL)
 
--- a/accessible/src/mac/mozAccessible.h
+++ b/accessible/src/mac/mozAccessible.h
@@ -18,41 +18,41 @@
  * to give it the represented view, in the latter case.
  */
 inline id <mozAccessible>
 GetObjectOrRepresentedView(id <mozAccessible> aObject)
 {
   return [aObject hasRepresentedView] ? [aObject representedView] : aObject;
 }
 
+inline mozAccessible*
+GetNativeFromGeckoAccessible(nsIAccessible* aAccessible)
+{
+  mozAccessible* native = nil;
+  aAccessible->GetNativeInterface((void**)&native);
+  return native;
+}
+
 @interface mozAccessible : NSObject <mozAccessible>
 {
   /**
    * Weak reference; it owns us.
    */
   AccessibleWrap* mGeckoAccessible;
   
   /**
    * Strong ref to array of children
    */
   NSMutableArray* mChildren;
   
   /** 
    * Weak reference to the parent
    */
   mozAccessible* mParent;
-  
-  /**
-   * We can be marked as 'expired' if Shutdown() is called on our geckoAccessible.
-   * since we might still be retained by some third-party, we need to do cleanup
-   * in |expire|, and prevent any potential harm that could come from someone using us
-   * after this point.
-   */
-  BOOL mIsExpired;
-  
+
   /**
    * The nsIAccessible role of our gecko accessible.
    */
   mozilla::a11y::role        mRole;
 }
 
 // inits with the gecko owner.
 - (id)initWithAccessible:(AccessibleWrap*)geckoParent;
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -4,28 +4,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
 #import "mozAccessible.h"
 
 #import "MacUtils.h"
 #import "mozView.h"
 
 #include "Accessible-inl.h"
+#include "nsAccUtils.h"
 #include "nsIAccessibleRelation.h"
 #include "nsIAccessibleText.h"
 #include "nsIAccessibleEditableText.h"
 #include "Relation.h"
 #include "Role.h"
 #include "RootAccessible.h"
 
 #include "mozilla/Services.h"
 #include "nsRect.h"
 #include "nsCocoaUtils.h"
 #include "nsCoord.h"
 #include "nsObjCExceptions.h"
+#include "nsWhitespaceTokenizer.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 // converts a screen-global point in the cocoa coordinate system (with origo in the bottom-left corner
 // of the screen), into a top-left screen point, that gecko can use.
 static inline void
 ConvertCocoaToGeckoPoint(NSPoint &aInPoint, nsPoint &aOutPoint)
@@ -58,39 +60,26 @@ GetClosestInterestingAccessible(id anObj
   if ([unignoredObject respondsToSelector:@selector(hasRepresentedView)])
     return GetObjectOrRepresentedView(unignoredObject);
   
   return unignoredObject;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
-static inline mozAccessible* 
-GetNativeFromGeckoAccessible(nsIAccessible *anAccessible)
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
-
-  mozAccessible *native = nil;
-  anAccessible->GetNativeInterface ((void**)&native);
-  return native;
-
-  NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
-}
-
 #pragma mark -
 
 @implementation mozAccessible
  
 - (id)initWithAccessible:(AccessibleWrap*)geckoAccessible
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ((self = [super init])) {
     mGeckoAccessible = geckoAccessible;
-    mIsExpired = NO;
     mRole = geckoAccessible->Role();
   }
    
   return self;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
@@ -107,27 +96,27 @@ GetNativeFromGeckoAccessible(nsIAccessib
 #pragma mark -
 
 - (BOOL)accessibilityIsIgnored
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   // unknown (either unimplemented, or irrelevant) elements are marked as ignored
   // as well as expired elements.
-  return mIsExpired || [[self role] isEqualToString:NSAccessibilityUnknownRole];
+  return !mGeckoAccessible || [[self role] isEqualToString:NSAccessibilityUnknownRole];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
 }
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   // if we're expired, we don't support any attributes.
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return [NSArray array];
   
   static NSArray *generalAttributes = nil;
   
   if (!generalAttributes) {
     // standard attributes that are shared and supported by all generic elements.
     generalAttributes = [[NSArray alloc] initWithObjects:  NSAccessibilityChildrenAttribute, 
                                                            NSAccessibilityParentAttribute,
@@ -155,17 +144,17 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute
 {  
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return nil;
 
 #if DEBUG
   if ([attribute isEqualToString:@"AXMozDescription"])
     return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
 #endif
   
   if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
@@ -241,17 +230,17 @@ GetNativeFromGeckoAccessible(nsIAccessib
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
     [self focus];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (id)accessibilityHitTest:(NSPoint)point
 {
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return nil;
 
   // Convert from cocoa's coordinate system to gecko's. According to the docs
   // the point we're given is guaranteed to be bottom-left screen coordinates.
   nsPoint geckoPoint;
   ConvertCocoaToGeckoPoint (point, geckoPoint);
 
   nsCOMPtr<nsIAccessible> deepestFoundChild;
@@ -283,17 +272,17 @@ GetNativeFromGeckoAccessible(nsIAccessib
 }
 
 - (void)accessibilityPerformAction:(NSString*)action 
 {
 }
 
 - (id)accessibilityFocusedUIElement
 {
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return nil;
   
   Accessible* focusedGeckoChild = mGeckoAccessible->FocusedChild();
   if (focusedGeckoChild) {
     mozAccessible *focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
     if (focusedChild)
       return GetClosestInterestingAccessible(focusedChild);
   }
@@ -415,17 +404,17 @@ GetNativeFromGeckoAccessible(nsIAccessib
   mGeckoAccessible->GetBounds (&x, &y, &width, &height);  
   return [NSValue valueWithSize:NSMakeSize (width, height)];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSString*)role
 {
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return nil;
 
 #ifdef DEBUG
   NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(mGeckoAccessible),
                "Does not support nsIAccessibleText when it should");
 #endif
 
 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role) \
@@ -439,16 +428,52 @@ GetNativeFromGeckoAccessible(nsIAccessib
       return NSAccessibilityUnknownRole;
   }
 
 #undef ROLE
 }
 
 - (NSString*)subrole
 {
+  if (!mGeckoAccessible)
+    return nil;
+
+  // XXX maybe we should cache the subrole.
+  nsAutoString xmlRoles;
+  nsCOMPtr<nsIPersistentProperties> attributes;
+
+  // XXX we don't need all the attributes (see bug 771113)
+  nsresult rv = mGeckoAccessible->GetAttributes(getter_AddRefs(attributes));
+  if (NS_SUCCEEDED(rv) && attributes)
+    nsAccUtils::GetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
+
+  nsWhitespaceTokenizer tokenizer(xmlRoles);
+
+  while (tokenizer.hasMoreTokens()) {
+    const nsDependentSubstring token(tokenizer.nextToken());
+
+    if (token.EqualsLiteral("banner"))
+      return @"AXLandmarkBanner";
+
+    if (token.EqualsLiteral("complementary"))
+      return @"AXLandmarkComplementary";
+
+    if (token.EqualsLiteral("contentinfo"))
+      return @"AXLandmarkContentInfo";
+
+    if (token.EqualsLiteral("main"))
+      return @"AXLandmarkMain";
+
+    if (token.EqualsLiteral("navigation"))
+      return @"AXLandmarkNavigation";
+
+    if (token.EqualsLiteral("search"))
+      return @"AXLandmarkSearch";
+  }
+
   switch (mRole) {
     case roles::LIST:
       return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
 
     case roles::DEFINITION_LIST:
       return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
 
     case roles::TERM:
@@ -463,34 +488,52 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
   return nil;
 }
 
 - (NSString*)roleDescription
 {
   if (mRole == roles::DOCUMENT)
     return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
-  
+
   NSString* subrole = [self subrole];
-  
+
   if ((mRole == roles::LISTITEM) && [subrole isEqualToString:@"AXTerm"])
     return utils::LocalizedString(NS_LITERAL_STRING("term"));
   if ((mRole == roles::PARAGRAPH) && [subrole isEqualToString:@"AXDefinition"])
     return utils::LocalizedString(NS_LITERAL_STRING("definition"));
-  
-  return NSAccessibilityRoleDescription([self role], subrole);
+
+  NSString* role = [self role];
+
+  // the WAI-ARIA Landmarks
+  if ([role isEqualToString:NSAccessibilityGroupRole]) {
+    if ([subrole isEqualToString:@"AXLandmarkBanner"])
+      return utils::LocalizedString(NS_LITERAL_STRING("banner"));
+    if ([subrole isEqualToString:@"AXLandmarkComplementary"])
+      return utils::LocalizedString(NS_LITERAL_STRING("complementary"));
+    if ([subrole isEqualToString:@"AXLandmarkContentInfo"])
+      return utils::LocalizedString(NS_LITERAL_STRING("content"));
+    if ([subrole isEqualToString:@"AXLandmarkMain"])
+      return utils::LocalizedString(NS_LITERAL_STRING("main"));
+    if ([subrole isEqualToString:@"AXLandmarkNavigation"])
+      return utils::LocalizedString(NS_LITERAL_STRING("navigation"));
+    if ([subrole isEqualToString:@"AXLandmarkSearch"])
+      return utils::LocalizedString(NS_LITERAL_STRING("search"));
+  }
+
+  return NSAccessibilityRoleDescription(role, subrole);
 }
 
 - (NSString*)title
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   nsAutoString title;
   mGeckoAccessible->Name(title);
-  return title.IsEmpty() ? nil : [NSString stringWithCharacters:title.BeginReading() length:title.Length()];
+  return nsCocoaUtils::ToNSString(title);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)value
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -523,17 +566,18 @@ GetNativeFromGeckoAccessible(nsIAccessib
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (mGeckoAccessible->IsDefunct())
     return nil;
 
   nsAutoString desc;
   mGeckoAccessible->Description(desc);
-  return desc.IsEmpty() ? nil : [NSString stringWithCharacters:desc.BeginReading() length:desc.Length()];
+
+  return nsCocoaUtils::ToNSString(desc);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSString*)help
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -556,28 +600,31 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
 - (BOOL)isFocused
 {
   return FocusMgr()->IsFocused(mGeckoAccessible);
 }
 
 - (BOOL)canBeFocused
 {
-  return mGeckoAccessible->InteractiveState() & states::FOCUSABLE;
+  return mGeckoAccessible && (mGeckoAccessible->InteractiveState() & states::FOCUSABLE);
 }
 
 - (BOOL)focus
 {
+  if (!mGeckoAccessible)
+    return NO;
+  
   nsresult rv = mGeckoAccessible->TakeFocus();
   return NS_SUCCEEDED(rv);
 }
 
 - (BOOL)isEnabled
 {
-  return (mGeckoAccessible->InteractiveState() & states::UNAVAILABLE) == 0;
+  return mGeckoAccessible && ((mGeckoAccessible->InteractiveState() & states::UNAVAILABLE) == 0);
 }
 
 // The root accessible calls this when the focused node was
 // changed to us.
 - (void)didReceiveFocus
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
@@ -632,25 +679,24 @@ GetNativeFromGeckoAccessible(nsIAccessib
 }
 
 - (void)expire
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [self invalidateChildren];
 
-  mIsExpired = YES;
   mGeckoAccessible = nsnull;
   
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (BOOL)isExpired
 {
-  return mIsExpired;
+  return !mGeckoAccessible;
 }
 
 #pragma mark -
 #pragma mark Debug methods
 #pragma mark -
 
 #ifdef DEBUG
 
--- a/accessible/src/mac/mozActionElements.h
+++ b/accessible/src/mac/mozActionElements.h
@@ -24,8 +24,15 @@
 
 /* Class for tabs - not individual tabs */
 @interface mozTabsAccessible : mozAccessible
 {
   NSMutableArray* mTabs;
 }
 -(id)tabs;
 @end
+
+/**
+ * Accessible for a PANE
+ */
+@interface mozPaneAccessible : mozAccessible
+
+@end
--- a/accessible/src/mac/mozActionElements.mm
+++ b/accessible/src/mac/mozActionElements.mm
@@ -2,18 +2,20 @@
 /* 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/. */
 
 #import "mozActionElements.h"
 
 #import "MacUtils.h"
 #include "Accessible-inl.h"
+#include "DocAccessible.h"
 #include "XULTabAccessible.h"
 
+#include "nsDeckFrame.h"
 #include "nsObjCExceptions.h"
 
 using namespace mozilla::a11y;
 
 enum CheckboxValue {
   // these constants correspond to the values in the OS
   kUnchecked = 0,
   kChecked = 1,
@@ -66,17 +68,17 @@ enum CheckboxValue {
   
   return [super accessibilityAttributeValue:attribute];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)accessibilityIsIgnored
 {
-  return mIsExpired;
+  return !mGeckoAccessible;
 }
 
 - (NSArray*)accessibilityActionNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ([self isEnabled])
     return [NSArray arrayWithObject:NSAccessibilityPressAction];
@@ -328,8 +330,35 @@ enum CheckboxValue {
 {
   [super invalidateChildren];
 
   [mTabs release];
   mTabs = nil;
 }
 
 @end
+
+@implementation mozPaneAccessible
+
+- (NSArray*)children
+{
+  if (!mGeckoAccessible)
+    return nil;
+
+  nsDeckFrame* deckFrame = do_QueryFrame(mGeckoAccessible->GetFrame());
+  nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nsnull;
+
+  Accessible* selectedAcc = nsnull;
+  if (selectedFrame) {
+    nsINode* node = selectedFrame->GetContent();
+    selectedAcc = mGeckoAccessible->Document()->GetAccessible(node);
+  }
+
+  if (selectedAcc) {
+    mozAccessible *curNative = GetNativeFromGeckoAccessible(selectedAcc);
+    if (curNative)
+      return [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil];
+  }
+
+  return nil;
+}
+
+@end
--- a/accessible/src/mac/mozDocAccessible.mm
+++ b/accessible/src/mac/mozDocAccessible.mm
@@ -26,17 +26,17 @@ getNativeViewFromRootAccessible(Accessib
 
 @implementation mozRootAccessible
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   
   // if we're expired, we don't support any attributes.
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return [NSArray array];
   
   // standard attributes that are shared and supported by root accessible (AXMain) elements.
   static NSMutableArray* attributes = nil;
   
   if (!attributes) {
     attributes = [[super accessibilityAttributeNames] mutableCopy];
     [attributes addObject:NSAccessibilityMainAttribute];
--- a/accessible/src/mac/mozHTMLAccessible.mm
+++ b/accessible/src/mac/mozHTMLAccessible.mm
@@ -8,16 +8,27 @@
 #import "mozHTMLAccessible.h"
 
 #import "HyperTextAccessible.h"
 
 #import "nsCocoaUtils.h"
 
 @implementation mozHeadingAccessible
 
+- (NSString*)title
+{
+  nsAutoString title;
+  // XXX use the flattening API when there are available
+  // see bug 768298
+  nsresult rv = mGeckoAccessible->GetContent()->GetTextContent(title);
+  NS_ENSURE_SUCCESS(rv, nil);
+
+  return nsCocoaUtils::ToNSString(title);
+}
+
 - (id)value
 {
   if (!mGeckoAccessible || !mGeckoAccessible->IsHyperText())
     return nil;
 
   PRUint32 level = mGeckoAccessible->AsHyperText()->GetLevelInternal();
   return [NSNumber numberWithInt:level];
 }
@@ -28,17 +39,17 @@
 -(NSURL*)url;
 @end
 
 @implementation mozLinkAccessible
 
 - (NSArray*)accessibilityAttributeNames
 {
   // if we're expired, we don't support any attributes.
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return [NSArray array];
   
   static NSMutableArray* attributes = nil;
   
   if (!attributes) {
     attributes = [[super accessibilityAttributeNames] mutableCopy];
     [attributes addObject:NSAccessibilityURLAttribute];
   }
@@ -52,17 +63,17 @@
     return [self url];
 
   return [super accessibilityAttributeValue:attribute];
 }
 
 - (NSArray*)accessibilityActionNames 
 {
     // if we're expired, we don't support any attributes.
-  if (mIsExpired)
+  if (!mGeckoAccessible)
     return [NSArray array];
 
   static NSArray* actionNames = nil;
 
   if (!actionNames) {
     actionNames = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction,
                                    nil];
   }
--- a/accessible/src/mac/mozTextAccessible.h
+++ b/accessible/src/mac/mozTextAccessible.h
@@ -9,8 +9,13 @@
 @interface mozTextAccessible : mozAccessible
 {
   // both of these are the same old mGeckoAccessible, but already
   // QI'd for us, to the right type, for convenience.
   HyperTextAccessible *mGeckoTextAccessible; // strong
   nsIAccessibleEditableText *mGeckoEditableTextAccessible; // strong
 }
 @end
+
+@interface mozTextLeafAccessible : mozAccessible
+{
+}
+@end
--- a/accessible/src/mac/mozTextAccessible.mm
+++ b/accessible/src/mac/mozTextAccessible.mm
@@ -1,15 +1,16 @@
 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "AccessibleWrap.h"
+#include "TextLeafAccessible.h"
 
 #include "nsCocoaUtils.h"
 #include "nsObjCExceptions.h"
 
 #import "mozTextAccessible.h"
 
 using namespace mozilla::a11y;
 
@@ -62,17 +63,17 @@ ToNSString(id aValue)
   }
   return self;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)accessibilityIsIgnored
 {
-  return mIsExpired;
+  return !mGeckoAccessible;
 }
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   static NSMutableArray* supportedAttributes = nil;
   if (!supportedAttributes) {
@@ -105,23 +106,28 @@ ToNSString(id aValue)
     return [self caretLineNumber];
 
   if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute])
     return [self selectedTextRange];
 
   if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute])
     return [self selectedText];
 
+  if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+    return @"";
+
   if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
     // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
     // object's AXSelectedText attribute. See bug 674612 for details.
     // Also if there is no selected text, we return the full text. 
     // See bug 369710 for details.
-    if ([[self role] isEqualToString:NSAccessibilityStaticTextRole])
-      return [self selectedText] ? : [self text];
+    if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) {
+      NSString* selectedText = [self selectedText];
+      return (selectedText && [selectedText length]) ? selectedText : [self text];
+    }
 
     return [self text];
   }
 
   if ([attribute isEqualToString:@"AXRequired"])
     return [NSNumber numberWithBool:!!(mGeckoAccessible->State() & states::REQUIRED)];
 
   if ([attribute isEqualToString:@"AXInvalid"])
@@ -221,17 +227,17 @@ ToNSString(id aValue)
   return nil;
 }
 
 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if ([attribute isEqualToString:NSAccessibilityValueAttribute])
-    return [self isReadOnly];
+    return ![self isReadOnly];
   
   if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] ||
       [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] ||
       [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
     return YES;
 
   return [super accessibilityIsAttributeSettable:attribute];
 
@@ -354,17 +360,17 @@ ToNSString(id aValue)
     mGeckoEditableTextAccessible->SetTextContents(text);
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (NSString*)text
 {
-  if (!mGeckoTextAccessible)
+  if (!mGeckoAccessible || !mGeckoTextAccessible)
     return nil;
 
   // A password text field returns an empty value
   if (mRole == roles::PASSWORD_TEXT)
     return @"";
 
   nsAutoString text;
   nsresult rv = mGeckoTextAccessible->
@@ -373,16 +379,19 @@ ToNSString(id aValue)
 
   return nsCocoaUtils::ToNSString(text);
 }
 
 - (long)textLength
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
+  if (!mGeckoAccessible || !mGeckoTextAccessible)
+    return 0;
+
   return mGeckoTextAccessible ? mGeckoTextAccessible->CharacterCount() : 0;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
 }
 
 - (long)selectedTextLength
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
@@ -479,8 +488,50 @@ ToNSString(id aValue)
 
   nsAutoString text;
   mGeckoTextAccessible->GetText(range->location, 
                                 range->location + range->length, text);
   return nsCocoaUtils::ToNSString(text);
 }
 
 @end
+
+@implementation mozTextLeafAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+  static NSMutableArray* supportedAttributes = nil;
+  if (!supportedAttributes) {
+    supportedAttributes = [[super accessibilityAttributeNames] mutableCopy];
+    [supportedAttributes removeObject:NSAccessibilityChildrenAttribute];
+  }
+
+  return supportedAttributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+  if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+    return @"";
+
+  if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+    return [self text];
+
+  return [super accessibilityAttributeValue:attribute];
+}
+
+- (NSString*)text
+{
+  if (!mGeckoAccessible)
+    return nil;
+
+  return nsCocoaUtils::ToNSString(mGeckoAccessible->AsTextLeaf()->Text());
+}
+
+- (long)textLength
+{
+  if (!mGeckoAccessible)
+    return 0;
+
+  return mGeckoAccessible->AsTextLeaf()->Text().Length();
+}
+
+@end
--- a/accessible/src/msaa/AccessibleWrap.cpp
+++ b/accessible/src/msaa/AccessibleWrap.cpp
@@ -3,49 +3,48 @@
  * 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 "AccessibleWrap.h"
 
 #include "Compatibility.h"
 #include "DocAccessible-inl.h"
 #include "EnumVariant.h"
+#include "ia2AccessibleRelation.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIAccessibleRelation.h"
 #include "nsWinUtils.h"
 #include "Relation.h"
 #include "Role.h"
+#include "RootAccessible.h"
 #include "States.h"
-
-#include "ia2AccessibleRelation.h"
-
-#include "nsIAccessibleEvent.h"
-#include "nsIAccessibleRelation.h"
-
-#include "Accessible2_i.c"
-#include "AccessibleRole.h"
-#include "AccessibleStates.h"
-#include "RootAccessible.h"
+#include "uiaRawElmProvider.h"
 
 #ifdef DEBUG
 #include "Logging.h"
 #endif
 
 #include "nsIMutableArray.h"
 #include "nsIFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsINameSpaceManager.h"
 #include "nsINodeInfo.h"
 #include "nsIServiceManager.h"
 #include "nsTextFormatter.h"
 #include "nsIView.h"
 #include "nsIViewManager.h"
 #include "nsEventMap.h"
 #include "nsArrayUtils.h"
+#include "mozilla/Preferences.h"
 
+#include "Accessible2_i.c"
+#include "AccessibleRole.h"
+#include "AccessibleStates.h"
 #include "OLEACC.H"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 const PRUint32 USE_ROLE_STRING = 0;
 
 /* For documentation of the accessibility architecture,
@@ -117,16 +116,42 @@ AccessibleWrap::QueryInterface(REFIID ii
     return nsAccessNodeWrap::QueryInterface(iid, ppv);
 
   (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
 
   return S_OK;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// IServiceProvider
+
+STDMETHODIMP
+AccessibleWrap::QueryService(REFGUID aGuidService, REFIID aIID,
+                             void** aInstancePtr)
+{
+  if (!aInstancePtr)
+    return E_INVALIDARG;
+
+  *aInstancePtr = NULL;
+
+  // UIA IAccessibleEx
+  if (aGuidService == IID_IAccessibleEx &&
+      Preferences::GetBool("accessibility.uia.enable")) {
+    IAccessibleEx* accEx = new uiaRawElmProvider(this);
+    HRESULT hr = accEx->QueryInterface(aIID, aInstancePtr);
+    if (FAILED(hr))
+      delete accEx;
+
+    return hr;
+  }
+
+  return nsAccessNodeWrap::QueryService(aGuidService, aIID, aInstancePtr);
+}
+
 //-----------------------------------------------------
 // IAccessible methods
 //-----------------------------------------------------
 
 STDMETHODIMP
 AccessibleWrap::get_accParent( IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
 {
 __try {
--- a/accessible/src/msaa/AccessibleWrap.h
+++ b/accessible/src/msaa/AccessibleWrap.h
@@ -12,61 +12,114 @@
 
 #include "nsCOMPtr.h"
 #include "Accessible.h"
 #include "Accessible2.h"
 #include "ia2AccessibleComponent.h"
 #include "ia2AccessibleHyperlink.h"
 #include "CAccessibleValue.h"
 
-#define DECL_IUNKNOWN_INHERITED                                               \
-public:                                                                       \
-STDMETHODIMP QueryInterface(REFIID, void**);                                  \
+#define DECL_IUNKNOWN                                                          \
+public:                                                                        \
+  virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);            \
+  virtual ULONG STDMETHODCALLTYPE AddRef() MOZ_FINAL                           \
+    {  return ++mRefCnt; }                                                     \
+  virtual ULONG STDMETHODCALLTYPE Release() MOZ_FINAL                          \
+  {                                                                            \
+     mRefCnt--;                                                                \
+     if (mRefCnt)                                                              \
+       return mRefCnt;                                                         \
+                                                                               \
+     delete this;                                                              \
+     return 0;                                                                 \
+  }                                                                            \
+private:                                                                       \
+  ULONG mRefCnt;                                                               \
+public:
+
+#define DECL_IUNKNOWN_INHERITED                                                \
+public:                                                                        \
+virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);              \
 
-#define IMPL_IUNKNOWN_QUERY_HEAD(Class)                                       \
-STDMETHODIMP                                                                  \
-Class::QueryInterface(REFIID iid, void** ppv)                                 \
-{                                                                             \
-  HRESULT hr = E_NOINTERFACE;                                                 \
-  *ppv = NULL;                                                                \
+#define IMPL_IUNKNOWN_QUERY_HEAD(Class)                                        \
+STDMETHODIMP                                                                   \
+Class::QueryInterface(REFIID aIID, void** aInstancePtr)                        \
+{                                                                              \
+__try {                                                                        \
+  if (!aInstancePtr)                                                           \
+    return E_INVALIDARG;                                                       \
+  *aInstancePtr = NULL;                                                        \
+                                                                               \
+  HRESULT hr = E_NOINTERFACE;
 
-#define IMPL_IUNKNOWN_QUERY_TAIL                                              \
-  return hr;                                                                  \
-}                                                                             \
+#define IMPL_IUNKNOWN_QUERY_TAIL                                               \
+  return hr;                                                                   \
+} __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(),        \
+                                                  GetExceptionInformation())) { } \
+  return E_NOINTERFACE;                                                        \
+}
 
-#define IMPL_IUNKNOWN_QUERY_ENTRY(Class)                                      \
-  hr = Class::QueryInterface(iid, ppv);                                       \
-  if (SUCCEEDED(hr))                                                          \
-    return hr;                                                                \
+#define IMPL_IUNKNOWN_QUERY_IFACE(Iface)                                       \
+  if (aIID == IID_##Iface) {                                                   \
+    *aInstancePtr = static_cast<Iface*>(this);                                 \
+    AddRef();                                                                  \
+    return S_OK;                                                               \
+  }
 
-#define IMPL_IUNKNOWN_QUERY_ENTRY_COND(Class, Cond)                           \
-  if (Cond) {                                                                 \
-    hr = Class::QueryInterface(iid, ppv);                                     \
-    if (SUCCEEDED(hr))                                                        \
-      return hr;                                                              \
-  }                                                                           \
+#define IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(Iface, aResolveIface)              \
+  if (aIID == IID_##Iface) {                                                   \
+    *aInstancePtr = static_cast<Iface*>(static_cast<aResolveIface*>(this));    \
+    AddRef();                                                                  \
+    return S_OK;                                                               \
+  }
+
+#define IMPL_IUNKNOWN_QUERY_CLASS(Class)                                       \
+  hr = Class::QueryInterface(aIID, aInstancePtr);                              \
+  if (SUCCEEDED(hr))                                                           \
+    return hr;
 
-#define IMPL_IUNKNOWN_INHERITED0(Class, Super)                                \
-  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                             \
-  IMPL_IUNKNOWN_QUERY_ENTRY(Super)                                            \
-  IMPL_IUNKNOWN_QUERY_TAIL                                                    \
+#define IMPL_IUNKNOWN_QUERY_CLASS_COND(Class, Cond)                            \
+  if (Cond) {                                                                  \
+    hr = Class::QueryInterface(aIID, aInstancePtr);                            \
+    if (SUCCEEDED(hr))                                                         \
+      return hr;                                                               \
+  }
+
+#define IMPL_IUNKNOWN_QUERY_AGGR_COND(Member, Cond)                            \
+  if (Cond) {                                                                  \
+    hr = Member->QueryInterface(aIID, aInstancePtr);                           \
+    if (SUCCEEDED(hr))                                                         \
+      return hr;                                                               \
+  }
 
-#define IMPL_IUNKNOWN_INHERITED1(Class, Super, I1)                            \
-  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                             \
-  IMPL_IUNKNOWN_QUERY_ENTRY(I1);                                              \
-  IMPL_IUNKNOWN_QUERY_ENTRY(Super)                                            \
-  IMPL_IUNKNOWN_QUERY_TAIL                                                    \
+#define IMPL_IUNKNOWN1(Class, I1)                                              \
+  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                              \
+  IMPL_IUNKNOWN_QUERY_IFACE(I1);                                               \
+  IMPL_IUNKNOWN_QUERY_IFACE(IUnknown);                                         \
+  IMPL_IUNKNOWN_QUERY_TAIL                                                     \
+
+#define IMPL_IUNKNOWN2(Class, I1, I2)                                          \
+  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                              \
+  IMPL_IUNKNOWN_QUERY_IFACE(I1);                                               \
+  IMPL_IUNKNOWN_QUERY_IFACE(I2);                                               \
+  IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, I1);                           \
+  IMPL_IUNKNOWN_QUERY_TAIL                                                     \
 
-#define IMPL_IUNKNOWN_INHERITED2(Class, Super, I1, I2)                        \
-  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                             \
-  IMPL_IUNKNOWN_QUERY_ENTRY(I1);                                              \
-  IMPL_IUNKNOWN_QUERY_ENTRY(I2);                                              \
-  IMPL_IUNKNOWN_QUERY_ENTRY(Super)                                            \
-  IMPL_IUNKNOWN_QUERY_TAIL                                                    \
+#define IMPL_IUNKNOWN_INHERITED1(Class, Super0, Super1)                        \
+  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                              \
+  IMPL_IUNKNOWN_QUERY_CLASS(Super1);                                           \
+  IMPL_IUNKNOWN_QUERY_CLASS(Super0)                                            \
+  IMPL_IUNKNOWN_QUERY_TAIL                                                     \
 
+#define IMPL_IUNKNOWN_INHERITED2(Class, Super0, Super1, Super2)                \
+  IMPL_IUNKNOWN_QUERY_HEAD(Class)                                              \
+  IMPL_IUNKNOWN_QUERY_CLASS(Super1);                                           \
+  IMPL_IUNKNOWN_QUERY_CLASS(Super2);                                           \
+  IMPL_IUNKNOWN_QUERY_CLASS(Super0)                                            \
+  IMPL_IUNKNOWN_QUERY_TAIL
 
 class AccessibleWrap : public Accessible,
                        public ia2AccessibleComponent,
                        public ia2AccessibleHyperlink,
                        public CAccessibleValue,
                        public IAccessible2
 {
 public: // construction, destruction
@@ -78,16 +131,21 @@ public: // construction, destruction
     NS_DECL_ISUPPORTS_INHERITED
 
   public: // IUnknown methods - see iunknown.h for documentation
     STDMETHODIMP QueryInterface(REFIID, void**);
 
   // Return the registered OLE class ID of this object's CfDataObj.
     CLSID GetClassID() const;
 
+  // IServiceProvider
+  virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID aGuidService,
+                                                 REFIID aIID,
+                                                 void** aInstancePtr);
+
   public: // COM interface IAccessible
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accParent( 
         /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent);
 
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChildCount( 
         /* [retval][out] */ long __RPC_FAR *pcountChildren);
 
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild( 
--- a/accessible/src/msaa/EnumVariant.cpp
+++ b/accessible/src/msaa/EnumVariant.cpp
@@ -8,61 +8,21 @@
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // ChildrenEnumVariant
 ////////////////////////////////////////////////////////////////////////////////
 
-STDMETHODIMP
-ChildrenEnumVariant::QueryInterface(REFIID aIID, void** aObject)
-{
-__try {
-  if (!aObject)
-    return E_INVALIDARG;
-
-  if (aIID == IID_IEnumVARIANT) {
-    *aObject = static_cast<IEnumVARIANT*>(this);
-    AddRef();
-    return S_OK;
-  }
-
-  if (aIID == IID_IUnknown) {
-    *aObject = static_cast<IUnknown*>(this);
-    AddRef();
-    return S_OK;
-  }
-
-  // Redirect QI to IAccessible this enum was retrieved for.
-  if (!mAnchorAcc->IsDefunct())
-    return mAnchorAcc->QueryInterface(aIID, aObject);
-
-} __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(),
-                                                  GetExceptionInformation())) { }
-
-  return E_NOINTERFACE;
-}
-
-ULONG STDMETHODCALLTYPE
-ChildrenEnumVariant::AddRef()
-{
-  return ++mRefCnt;
-}
-
-ULONG STDMETHODCALLTYPE
-ChildrenEnumVariant::Release()
-{
-  mRefCnt--;
-  ULONG r = mRefCnt;
-  if (r == 0)
-    delete this;
-
-  return r;
-}
+IMPL_IUNKNOWN_QUERY_HEAD(ChildrenEnumVariant)
+IMPL_IUNKNOWN_QUERY_IFACE(IEnumVARIANT);
+IMPL_IUNKNOWN_QUERY_IFACE(IUnknown);
+IMPL_IUNKNOWN_QUERY_AGGR_COND(mAnchorAcc, !mAnchorAcc->IsDefunct());
+IMPL_IUNKNOWN_QUERY_TAIL
 
 STDMETHODIMP
 ChildrenEnumVariant::Next(ULONG aCount, VARIANT FAR* aItems,
                           ULONG FAR* aCountFetched)
 {
 __try {
   if (!aItems || !aCountFetched)
     return E_INVALIDARG;
--- a/accessible/src/msaa/EnumVariant.h
+++ b/accessible/src/msaa/EnumVariant.h
@@ -17,22 +17,17 @@ namespace a11y {
  */
 class ChildrenEnumVariant MOZ_FINAL : public IEnumVARIANT
 {
 public:
   ChildrenEnumVariant(AccessibleWrap* aAnchor) : mAnchorAcc(aAnchor),
     mCurAcc(mAnchorAcc->GetChildAt(0)), mCurIndex(0), mRefCnt(0) { }
 
   // IUnknown
-  virtual HRESULT STDMETHODCALLTYPE QueryInterface(
-    /* [in] */ REFIID aRefIID,
-    /* [annotation][iid_is][out] */ void** aObject);
-
-  virtual ULONG STDMETHODCALLTYPE AddRef();
-  virtual ULONG STDMETHODCALLTYPE Release();
+  DECL_IUNKNOWN
 
   // IEnumVariant
   virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next(
     /* [in] */ ULONG aCount,
     /* [length_is][size_is][out] */ VARIANT* aItems,
     /* [out] */ ULONG* aCountFetched);
 
   virtual HRESULT STDMETHODCALLTYPE Skip(
@@ -51,17 +46,14 @@ private:
     mAnchorAcc(aEnumVariant.mAnchorAcc), mCurAcc(aEnumVariant.mCurAcc),
     mCurIndex(aEnumVariant.mCurIndex), mRefCnt(0) { }
   virtual ~ChildrenEnumVariant() { }
 
 protected:
   nsRefPtr<AccessibleWrap> mAnchorAcc;
   Accessible* mCurAcc;
   PRUint32 mCurIndex;
-
-private:
-  ULONG mRefCnt;
 };
 
 } // a11y namespace
 } // mozilla namespace
 
 #endif
--- a/accessible/src/msaa/Makefile.in
+++ b/accessible/src/msaa/Makefile.in
@@ -64,11 +64,12 @@ include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
   -I$(srcdir) \
   -I$(srcdir)/../base \
   -I$(srcdir)/../generic \
   -I$(srcdir)/../html \
   -I$(srcdir)/../xpcom \
   -I$(srcdir)/../xul \
+  -I$(srcdir)/../windows/uia \
   -I$(srcdir)/../../../content/base/src \
   -I$(srcdir)/../../../content/events/src \
   $(NULL)
--- a/accessible/src/msaa/XULListboxAccessibleWrap.cpp
+++ b/accessible/src/msaa/XULListboxAccessibleWrap.cpp
@@ -16,18 +16,18 @@ XULListboxAccessibleWrap::
   XULListboxAccessible(aContent, aDoc)
 {
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(XULListboxAccessibleWrap,
                              XULListboxAccessible)
 
 IMPL_IUNKNOWN_QUERY_HEAD(XULListboxAccessibleWrap)
-IMPL_IUNKNOWN_QUERY_ENTRY_COND(CAccessibleTable, IsMulticolumn());
-IMPL_IUNKNOWN_QUERY_ENTRY(AccessibleWrap)
+IMPL_IUNKNOWN_QUERY_CLASS_COND(CAccessibleTable, IsMulticolumn());
+IMPL_IUNKNOWN_QUERY_CLASS(AccessibleWrap)
 IMPL_IUNKNOWN_QUERY_TAIL
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULListCellAccessibleWrap
 ////////////////////////////////////////////////////////////////////////////////
 
 XULListCellAccessibleWrap::
--- a/accessible/src/msaa/nsAccessNodeWrap.cpp
+++ b/accessible/src/msaa/nsAccessNodeWrap.cpp
@@ -19,18 +19,16 @@
 #include "nsAttrName.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIFrame.h"
 #include "nsINameSpaceManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsIServiceManager.h"
 
-#include "mozilla/Preferences.h"
-
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 AccTextChangeEvent* nsAccessNodeWrap::gTextEvent = nsnull;
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessNodeWrap
 ////////////////////////////////////////////////////////////////////////////////
@@ -82,32 +80,23 @@ STDMETHODIMP nsAccessNodeWrap::QueryInte
   return S_OK;
 }
 
 STDMETHODIMP
 nsAccessNodeWrap::QueryService(REFGUID guidService, REFIID iid, void** ppv)
 {
   *ppv = nsnull;
 
-  static const GUID IID_SimpleDOMDeprecated = {0x0c539790,0x12e4,0x11cf,0xb6,0x61,0x00,0xaa,0x00,0x4c,0xd6,0xd8};
-
   // Provide a special service ID for getting the accessible for the browser tab
   // document that contains this accessible object. If this accessible object
   // is not inside a browser tab then the service fails with E_NOINTERFACE.
   // A use case for this is for screen readers that need to switch context or
   // 'virtual buffer' when focus moves from one browser tab area to another.
-  static const GUID SID_IAccessibleContentDocument = {0xa5d8e1f3,0x3571,0x4d8f,0x95,0x21,0x07,0xed,0x28,0xfb,0x07,0x2e};
-
-  if (guidService != IID_ISimpleDOMNode &&
-      guidService != IID_SimpleDOMDeprecated &&
-      guidService != IID_IAccessible &&  guidService != IID_IAccessible2 &&
-      guidService != IID_IAccessibleApplication &&
-      guidService != SID_IAccessibleContentDocument)
-    return E_INVALIDARG;
-
+  static const GUID SID_IAccessibleContentDocument =
+    { 0xa5d8e1f3,0x3571,0x4d8f,0x95,0x21,0x07,0xed,0x28,0xfb,0x07,0x2e };
   if (guidService == SID_IAccessibleContentDocument) {
     if (iid != IID_IAccessible)
       return E_NOINTERFACE;
 
     nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = 
       nsCoreUtils::GetDocShellTreeItemFor(mContent);
     if (!docShellTreeItem)
       return E_UNEXPECTED;
@@ -134,17 +123,17 @@ nsAccessNodeWrap::QueryService(REFGUID g
 
     *ppv = static_cast<IAccessible*>(docAcc);
 
     (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
     return NS_OK;
   }
 
   // Can get to IAccessibleApplication from any node via QS
-  if (iid == IID_IAccessibleApplication) {
+  if (guidService == IID_IAccessibleApplication) {
     ApplicationAccessible* applicationAcc = GetApplicationAccessible();
     if (!applicationAcc)
       return E_NOINTERFACE;
 
     nsresult rv = applicationAcc->QueryNativeInterface(iid, ppv);
     return NS_SUCCEEDED(rv) ? S_OK : E_NOINTERFACE;
   }
 
@@ -157,17 +146,24 @@ nsAccessNodeWrap::QueryService(REFGUID g
    * pAcc->QueryInterface(IID_IServiceProvider, (void**)&pServProv);
    * if (pServProv) {
    *   const GUID unused;
    *   pServProv->QueryService(unused, IID_ISimpleDOMDocument, (void**)&pAccDoc);
    *   pServProv->Release();
    * }
    */
 
-  return QueryInterface(iid, ppv);
+  static const GUID IID_SimpleDOMDeprecated =
+    { 0x0c539790,0x12e4,0x11cf,0xb6,0x61,0x00,0xaa,0x00,0x4c,0xd6,0xd8 };
+  if (guidService == IID_ISimpleDOMNode ||
+      guidService == IID_SimpleDOMDeprecated ||
+      guidService == IID_IAccessible ||  guidService == IID_IAccessible2)
+    return QueryInterface(iid, ppv);
+
+  return E_INVALIDARG;
 }
 
 //-----------------------------------------------------
 // ISimpleDOMNode methods
 //-----------------------------------------------------
 
 STDMETHODIMP nsAccessNodeWrap::get_nodeInfo( 
     /* [out] */ BSTR __RPC_FAR *aNodeName,
--- a/accessible/src/msaa/nsAccessNodeWrap.h
+++ b/accessible/src/msaa/nsAccessNodeWrap.h
@@ -29,40 +29,50 @@
 #include "OLEACC.H"
 #include <winuser.h>
 #ifdef MOZ_CRASHREPORTER
 #include "nsICrashReporter.h"
 #endif
 
 #include "nsRefPtrHashtable.h"
 
+#define A11Y_TRYBLOCK_BEGIN                                                    \
+  __try {
+
+#define A11Y_TRYBLOCK_END                                                      \
+  } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(),      \
+                                                    GetExceptionInformation()))\
+  { }                                                                          \
+  return E_FAIL;
 
 class AccTextChangeEvent;
 
 class nsAccessNodeWrap :  public nsAccessNode,
                           public nsIWinAccessNode,
                           public ISimpleDOMNode,
                           public IServiceProvider
 {
   public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIWINACCESSNODE
 
-  public: // IServiceProvider
-    STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void** ppv);
-
 public: // construction, destruction
   nsAccessNodeWrap(nsIContent* aContent, DocAccessible* aDoc);
   virtual ~nsAccessNodeWrap();
 
-    // IUnknown
-    STDMETHODIMP QueryInterface(REFIID, void**);
+  // IUnknown
+  virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID aIID,
+                                                   void** aInstancePtr);
 
-  public:
+  // IServiceProvider
+  virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID aGuidService,
+                                                 REFIID aIID,
+                                                 void** aInstancePtr);
 
+  // ISimpleDOMNode
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo( 
         /* [out] */ BSTR __RPC_FAR *tagName,
         /* [out] */ short __RPC_FAR *nameSpaceID,
         /* [out] */ BSTR __RPC_FAR *nodeValue,
         /* [out] */ unsigned int __RPC_FAR *numChildren,
         /* [out] */ unsigned int __RPC_FAR *aUniqueID,
         /* [out][retval] */ unsigned short __RPC_FAR *nodeType);
   
new file mode 100644
--- /dev/null
+++ b/accessible/src/windows/Makefile.in
@@ -0,0 +1,16 @@
+#
+# 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/.
+
+DEPTH = ../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS += uia \
+  $(null)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/accessible/src/windows/uia/Makefile.in
@@ -0,0 +1,36 @@
+# 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/.
+
+DEPTH = ../../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = accessibility
+LIBRARY_NAME = accessibility_toolkit_uia_s
+EXPORT_LIBRARY = ..
+LIBXUL_LIBRARY = 1
+
+
+CPPSRCS += \
+  uiaRawElmProvider.cpp \
+  $(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)/config/rules.mk
+
+LOCAL_INCLUDES += \
+  -I$(srcdir) \
+  -I$(srcdir)/../../base \
+  -I$(srcdir)/../../generic \
+  -I$(srcdir)/../../html \
+  -I$(srcdir)/../../msaa \
+  -I$(srcdir)/../../xpcom \
+  -I$(srcdir)/../../xul \
+  $(NULL)
new file mode 100644
--- /dev/null
+++ b/accessible/src/windows/uia/uiaRawElmProvider.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "uiaRawElmProvider.h"
+
+#include "AccessibleWrap.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// uiaRawElmProvider
+////////////////////////////////////////////////////////////////////////////////
+
+IMPL_IUNKNOWN2(uiaRawElmProvider,
+               IAccessibleEx,
+               IRawElementProviderSimple)
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleEx
+
+STDMETHODIMP
+uiaRawElmProvider::GetObjectForChild(long aIdChild,
+                                     __RPC__deref_out_opt IAccessibleEx** aAccEx)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aAccEx)
+    return E_INVALIDARG;
+
+  *aAccEx = NULL;
+
+  return mAcc->IsDefunct() ? CO_E_OBJNOTCONNECTED : S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc,
+                                      __RPC__out long* aIdChild)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aAcc || !aIdChild)
+    return E_INVALIDARG;
+
+  *aAcc = NULL;
+  *aIdChild = 0;
+
+  if (mAcc->IsDefunct())
+    return CO_E_OBJNOTCONNECTED;
+
+  *aIdChild = CHILDID_SELF;
+  *aAcc = mAcc;
+  mAcc->AddRef();
+
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetRuntimeId(__RPC__deref_out_opt SAFEARRAY** aRuntimeIds)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aRuntimeIds)
+    return E_INVALIDARG;
+
+  int ids[] = { UiaAppendRuntimeId, reinterpret_cast<int>(mAcc->UniqueID()) };
+  *aRuntimeIds = SafeArrayCreateVector(VT_I4, 0, 2);
+  if (!*aRuntimeIds)
+    return E_OUTOFMEMORY;
+
+  for (LONG i = 0; i < ArrayLength(ids); i++)
+    SafeArrayPutElement(*aRuntimeIds, &i, (void*)&(ids[i]));
+
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::ConvertReturnedElement(__RPC__in_opt IRawElementProviderSimple* aRawElmProvider,
+                                          __RPC__deref_out_opt IAccessibleEx** aAccEx)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aRawElmProvider || !aAccEx)
+    return E_INVALIDARG;
+
+  *aAccEx = NULL;
+
+  void* instancePtr = NULL;
+  HRESULT hr = aRawElmProvider->QueryInterface(IID_IAccessibleEx, &instancePtr);
+  if (SUCCEEDED(hr))
+    *aAccEx = static_cast<IAccessibleEx*>(instancePtr);
+
+  return hr;
+
+  A11Y_TRYBLOCK_END
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IRawElementProviderSimple
+
+STDMETHODIMP
+uiaRawElmProvider::get_ProviderOptions(__RPC__out enum ProviderOptions* aOptions)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aOptions)
+    return E_INVALIDARG;
+
+  // This method is not used with IAccessibleEx implementations.
+  *aOptions = ProviderOptions_ServerSideProvider;
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetPatternProvider(PATTERNID aPatternId,
+                                      __RPC__deref_out_opt IUnknown** aPatternProvider)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aPatternProvider)
+    return E_INVALIDARG;
+
+  *aPatternProvider = NULL;
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
+                                    __RPC__out VARIANT* aPropertyValue)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aPropertyValue)
+    return E_INVALIDARG;
+
+  // UI Automation will attempt to get the property from the host
+  //window provider.
+  aPropertyValue->vt = VT_EMPTY;
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_HostRawElementProvider(__RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aRawElmProvider)
+    return E_INVALIDARG;
+
+  // This method is not used with IAccessibleEx implementations.
+  *aRawElmProvider = NULL;
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
new file mode 100644
--- /dev/null
+++ b/accessible/src/windows/uia/uiaRawElmProvider.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_uiaRawElmProvider_h__
+#define mozilla_a11y_uiaRawElmProvider_h__
+
+#include "objbase.h"
+#include "AccessibleWrap.h"
+#include "UIAutomation.h"
+
+class AccessibleWrap;
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * IRawElementProviderSimple implementation (maintains IAccessibleEx approach).
+ */
+class uiaRawElmProvider MOZ_FINAL : public IAccessibleEx,
+                                    public IRawElementProviderSimple
+{
+public:
+  uiaRawElmProvider(AccessibleWrap* aAcc) : mAcc(aAcc), mRefCnt(0) { }
+
+  // IUnknown
+  DECL_IUNKNOWN
+
+  // IAccessibleEx
+  virtual HRESULT STDMETHODCALLTYPE GetObjectForChild(
+    /* [in] */ long aIdChild,
+    /* [retval][out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx);
+
+  virtual HRESULT STDMETHODCALLTYPE GetIAccessiblePair(
+    /* [out] */ __RPC__deref_out_opt IAccessible** aAcc,
+    /* [out] */ __RPC__out long* aIdChild);
+
+  virtual HRESULT STDMETHODCALLTYPE GetRuntimeId(
+    /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRuntimeIds);
+
+  virtual HRESULT STDMETHODCALLTYPE ConvertReturnedElement(
+    /* [in] */ __RPC__in_opt IRawElementProviderSimple* aRawElmProvider,
+    /* [out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx);
+
+  // IRawElementProviderSimple
+  virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ProviderOptions(
+    /* [retval][out] */ __RPC__out enum ProviderOptions* aProviderOptions);
+
+  virtual HRESULT STDMETHODCALLTYPE GetPatternProvider(
+    /* [in] */ PATTERNID aPatternId,
+    /* [retval][out] */ __RPC__deref_out_opt IUnknown** aPatternProvider);
+
+  virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
+    /* [in] */ PROPERTYID aPropertyId,
+    /* [retval][out] */ __RPC__out VARIANT* aPropertyValue);
+
+  virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_HostRawElementProvider(
+    /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider);
+
+private:
+  uiaRawElmProvider() MOZ_DELETE;
+  uiaRawElmProvider& operator =(const uiaRawElmProvider&) MOZ_DELETE;
+  uiaRawElmProvider(const uiaRawElmProvider&) MOZ_DELETE;
+
+protected:
+  nsRefPtr<AccessibleWrap> mAcc;
+};
+
+} // a11y namespace
+} // mozilla namespace
+
+#endif
--- a/accessible/src/xul/XULTabAccessible.cpp
+++ b/accessible/src/xul/XULTabAccessible.cpp
@@ -159,32 +159,25 @@ nsresult
 XULTabsAccessible::GetNameInternal(nsAString& aName)
 {
   // no name
   return NS_OK;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// XULTabpanelsAccessible
+// XULDeckAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
-XULTabpanelsAccessible::
-  XULTabpanelsAccessible(nsIContent* aContent, DocAccessible* aDoc) :
-  AccessibleWrap(aContent, aDoc)
-{
-}
-
 role
-XULTabpanelsAccessible::NativeRole()
+XULDeckAccessible::NativeRole()
 {
   return roles::PANE;
 }
 
-
 ////////////////////////////////////////////////////////////////////////////////
 // XULTabpanelAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 XULTabpanelAccessible::
   XULTabpanelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   AccessibleWrap(aContent, aDoc)
 {
--- a/accessible/src/xul/XULTabAccessible.h
+++ b/accessible/src/xul/XULTabAccessible.h
@@ -51,23 +51,25 @@ public:
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual a11y::role NativeRole();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 };
 
 
-/** 
+/**
  * A container of tab panels, xul:tabpanels element.
  */
-class XULTabpanelsAccessible : public AccessibleWrap
+class XULDeckAccessible : public AccessibleWrap
 {
 public:
-  XULTabpanelsAccessible(nsIContent* aContent, DocAccessible* aDoc);
+  XULDeckAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+    AccessibleWrap(aContent, aDoc)
+    { mFlags |= eXULDeckAccessible; }
 
   // Accessible
   virtual a11y::role NativeRole();
 };
 
 
 /**
  * A tabpanel object, child elements of xul:tabpanels element. Note,the object
--- a/accessible/tests/mochitest/attributes/test_text.html
+++ b/accessible/tests/mochitest/attributes/test_text.html
@@ -537,16 +537,33 @@
 
       var attrs = {
         "auto-generated": "true"
       };
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 2);
       testTextAttrs(ID, 2, { }, defAttrs, 2, 6);
       testTextAttrs(ID, 6, attrs, defAttrs, 6, 7);
 
+       //////////////////////////////////////////////////////////////////////////
+      // area19, "HTML5 mark tag" test
+      // text enclosed in mark tag will have a different background color
+      ID = "area19";
+      defAttrs = buildDefaultTextAttrs(ID, "12pt");
+
+      attrs = {};
+      testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
+      
+      tempElem = getNode(ID).firstChild.nextSibling;
+      gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+      attrs = { "background-color": gComputedStyle.backgroundColor };
+      testTextAttrs(ID, 11, attrs, defAttrs, 10, 17);
+
+      attrs = {};
+      testTextAttrs(ID, 18, attrs, defAttrs, 17, 28);
+
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body style="font-size: 12pt">
@@ -687,10 +704,14 @@
     </span><span style="text-decoration: line-through; -moz-text-decoration-style: wavy;">wavy
     </span>
   </p>
 
   <ul>
     <li id="area18" class="gencontent">item</li>
   </ul>
 
+  <p id="area19">uncolored
+    <mark>colored</mark> uncolored
+  </p>
+   
 </body>
 </html>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -373,16 +373,19 @@ pref("dom.ipc.tabs.disabled", false);
 pref("dom.ipc.browser_frames.oop_by_default", false);
 
 // Temporary permission hack for WebSMS
 pref("dom.sms.enabled", true);
 
 // Temporary permission hack for WebContacts
 pref("dom.mozContacts.enabled", true);
 
+// WebAlarms
+pref("dom.mozAlarms.enabled", true);
+
 // WebSettings
 pref("dom.mozSettings.enabled", true);
 
 // Ignore X-Frame-Options headers.
 pref("b2g.ignoreXFrameOptions", true);
 
 // controls if we want camera support
 pref("device.camera.enabled", true);
--- a/b2g/chrome/content/dbg-browser-actors.js
+++ b/b2g/chrome/content/dbg-browser-actors.js
@@ -156,17 +156,17 @@ DeviceTabActor.prototype = {
 
   grip: function DTA_grip() {
     dbg_assert(!this.exited,
                'grip() should not be called on exited browser actor.');
     dbg_assert(this.actorID,
                'tab should have an actorID.');
     return {
       'actor': this.actorID,
-      'title': this.browser.contentTitle,
+      'title': this.browser.title,
       'url': this.browser.document.documentURI
     }
   },
 
   /**
    * Called when the actor is removed from the connection.
    */
   disconnect: function DTA_disconnect() {
@@ -217,17 +217,17 @@ DeviceTabActor.prototype = {
    */
   _pushContext: function DTA_pushContext() {
     dbg_assert(!this._contextPool, "Can't push multiple contexts");
 
     this._contextPool = new ActorPool(this.conn);
     this.conn.addActorPool(this._contextPool);
 
     this.threadActor = new ThreadActor(this);
-    this._addDebuggees(this.browser.content.wrappedJSObject);
+    this._addDebuggees(this.browser.wrappedJSObject);
     this._contextPool.addActor(this.threadActor);
   },
 
   /**
    * Add the provided window and all windows in its frame tree as debuggees.
    */
   _addDebuggees: function DTA__addDebuggees(content) {
     this.threadActor.addDebuggee(content);
@@ -288,28 +288,28 @@ DeviceTabActor.prototype = {
 
     return { type: 'detached' };
   },
 
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest: function DTA_preNest() {
-    let windowUtils = this.browser.content
+    let windowUtils = this.browser
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.suppressEventHandling(true);
     windowUtils.suspendTimeouts();
   },
 
   /**
    * Prepare to exit a nested event loop by enabling debuggee events.
    */
   postNest: function DTA_postNest(aNestData) {
-    let windowUtils = this.browser.content
+    let windowUtils = this.browser
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.resumeTimeouts();
     windowUtils.suppressEventHandling(false);
   }
 
 };
 
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -74,20 +74,23 @@ SettingsListener.observe('language.curre
     SettingsListener.observe(key, false, function(value) {
       Services.prefs.setIntPref(key, value);
     });
   });
 })();
 
 
 // =================== Debugger ====================
-SettingsListener.observe('devtools.debugger.enabled', false, function(enabled) {
-  Services.prefs.setBoolPref('devtools.debugger.enabled', value);
+SettingsListener.observe('devtools.debugger.remote-enabled', false, function(enabled) {
+  Services.prefs.setBoolPref('devtools.debugger.remote-enabled', value);
 });
 
 SettingsListener.observe('devtools.debugger.log', false, function(value) {
   Services.prefs.setBoolPref('devtools.debugger.log', value);
 });
 
-SettingsListener.observe('devtools.debugger.port', 6000, function(value) {
-  Services.prefs.setIntPref('devtools.debugger.port', value);
+SettingsListener.observe('devtools.debugger.remote-port', 6000, function(value) {
+  Services.prefs.setIntPref('devtools.debugger.remote-port', value);
 });
 
+SettingsListener.observe('devtools.debugger.force-local', true, function(value) {
+  Services.prefs.setBoolPref('devtools.debugger.force-local', value);
+});
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -9,16 +9,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/ContactService.jsm');
 Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
 Cu.import('resource://gre/modules/Webapps.jsm');
+Cu.import('resource://gre/modules/AlarmService.jsm');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'env',
                                    '@mozilla.org/process/environment;1',
                                    'nsIEnvironment');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
                                    '@mozilla.org/content/style-sheet-service;1',
                                    'nsIStyleSheetService');
@@ -43,20 +44,20 @@ XPCOMUtils.defineLazyGetter(this, 'Debug
 });
 
 // FIXME Bug 707625
 // until we have a proper security model, add some rights to
 // the pre-installed web applications
 // XXX never grant 'content-camera' to non-gaia apps
 function addPermissions(urls) {
   let permissions = [
-    'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'pin-app',
+    'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'pin-app',
     'websettings-read', 'websettings-readwrite',
     'content-camera', 'webcontacts-manage', 'wifi-manage', 'desktop-notification',
-    'geolocation', 'device-storage'
+    'geolocation', 'device-storage', 'alarms'
   ];
   urls.forEach(function(url) {
     url = url.trim();
     let uri = Services.io.newURI(url, null, null);
     let allow = Ci.nsIPermissionManager.ALLOW_ACTION;
 
     permissions.forEach(function(permission) {
       Services.perms.add(uri, permission, allow);
--- a/b2g/config/mozconfigs/gb_armv7a_gecko/debug
+++ b/b2g/config/mozconfigs/gb_armv7a_gecko/debug
@@ -2,17 +2,16 @@ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/ob
 
 mk_add_options MOZ_MAKE_FLAGS="-j8"
 
 ac_add_options --enable-application=b2g
 
 ac_add_options --target=arm-android-eabi
 ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
 ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-eabi-4.4.3/bin/arm-eabi-"
-ac_add_options --with-endian=little
 ac_add_options --disable-elf-hack
 ac_add_options --enable-debug-symbols
 ac_add_options --enable-debug
 ac_add_options --with-ccache
 ac_add_options --enable-marionette
 
 # Enable dump() from JS.
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/config/mozconfigs/gb_armv7a_gecko/nightly
+++ b/b2g/config/mozconfigs/gb_armv7a_gecko/nightly
@@ -2,17 +2,16 @@ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/ob
 
 mk_add_options MOZ_MAKE_FLAGS="-j8"
 
 ac_add_options --enable-application=b2g
 
 ac_add_options --target=arm-android-eabi
 ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
 ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-eabi-4.4.3/bin/arm-eabi-"
-ac_add_options --with-endian=little
 ac_add_options --disable-elf-hack
 ac_add_options --enable-debug-symbols
 ac_add_options --enable-profiling
 ac_add_options --with-ccache
 ac_add_options --enable-marionette
 
 # Enable dump() from JS.
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -50,16 +50,20 @@ ifeq (bundle, $(MOZ_FS_LAYOUT))
 BINPATH = $(_BINPATH)
 DEFINES += -DAPPNAME=$(_APPNAME)
 else
 # Every other platform just winds up in dist/bin
 BINPATH = bin
 endif
 DEFINES += -DBINPATH=$(BINPATH)
 
+ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
+DEFINES += -DMOZ_SHARED_MOZGLUE=1
+endif
+
 ifdef MOZ_PKG_MANIFEST_P
 $(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) FORCE
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $< > $@
 
 GARBAGE += $(MOZ_PKG_MANIFEST)
 endif
 
 ifneq (,$(filter mac cocoa,$(MOZ_WIDGET_TOOLKIT)))
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -77,21 +77,23 @@
 @BINPATH@/msvcp100.dll
 @BINPATH@/msvcr100.dll
 #endif
 #else
 @BINPATH@/mozcrt19.dll
 @BINPATH@/mozcpp19.dll
 #endif
 #endif
+#ifdef MOZ_SHARED_MOZGLUE
+@BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
+#endif
 #ifdef ANDROID
 @BINPATH@/AndroidManifest.xml
 @BINPATH@/resources.arsc
 @BINPATH@/classes.dex
-@BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
 @BINPATH@/res/drawable
 @BINPATH@/res/drawable-hdpi
 @BINPATH@/res/layout
 #endif
 
 [browser]
 ; [Base Browser Files]
 #ifndef XP_UNIX
@@ -156,16 +158,17 @@
 @BINPATH@/components/dom_system_gonk.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
+@BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
@@ -312,16 +315,18 @@
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
+@BINPATH@/components/AlarmsManager.js
+@BINPATH@/components/AlarmsManager.manifest
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1018,17 +1018,17 @@ pref("services.sync.prefs.sync.signon.re
 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);
 
 // Developer toolbar and GCLI preferences
-pref("devtools.toolbar.enabled", false);
+pref("devtools.toolbar.enabled", true);
 pref("devtools.toolbar.visible", false);
 pref("devtools.gcli.allowSet", false);
 pref("devtools.commands.dir", "");
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
 pref("devtools.inspector.htmlPanelOpen", false);
@@ -1068,16 +1068,21 @@ pref("devtools.tilt.intro_transition", t
 pref("devtools.tilt.outro_transition", true);
 
 // Enable the rules view
 pref("devtools.ruleview.enabled", true);
 
 // Enable the Scratchpad tool.
 pref("devtools.scratchpad.enabled", true);
 
+// The maximum number of recently-opened files stored.
+// Setting this preference to 0 will not clear any recent files, but rather hide
+// the 'Open Recent'-menu.
+pref("devtools.scratchpad.recentFilesMax", 10);
+
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.transitions", true);
 
 // Enable tools for Chrome development.
 pref("devtools.chrome.enabled", false);
 
 // Display the introductory text
@@ -1172,8 +1177,11 @@ pref("pdfjs.firstRun", true);
 // became the default.
 pref("pdfjs.previousHandler.preferredAction", 0);
 pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false);
 
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 // (This is intentionally on the high side; see bug 746055.)
 pref("image.mem.max_decoded_image_kb", 256000);
+
+// Example social provider
+pref("social.manifest.motown", "{\"origin\":\"https://motown-dev.mozillalabs.com\",\"name\":\"MoTown\",\"workerURL\":\"https://motown-dev.mozillalabs.com/social/worker.js\"}");
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -113,17 +113,17 @@
                 key="key_find"/>
       <menuseparator class="appmenu-menuseparator"/>
       <menuitem id="appmenu_savePage"
                 class="menuitem-tooltip"
                 label="&savePageCmd.label;"
                 command="Browser:SavePage"
                 key="key_savePage"/>
       <menuitem id="appmenu_sendLink"
-                label="&sendPageCmd.label;"
+                label="&emailPageCmd.label;"
                 command="Browser:SendLink"/>
       <splitmenu id="appmenu_print"
                  iconic="true"
                  label="&printCmd.label;"
                  command="cmd_print">
           <menupopup>
             <menuitem id="appmenu_print_popup"
                       class="menuitem-iconic"
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -32,20 +32,16 @@
       <menuitem id="context-bookmarklink"
                 label="&bookmarkThisLinkCmd.label;"
                 accesskey="&bookmarkThisLinkCmd.accesskey;"
                 oncommand="gContextMenu.bookmarkLink();"/>
       <menuitem id="context-savelink"
                 label="&saveLinkCmd.label;"
                 accesskey="&saveLinkCmd.accesskey;"
                 oncommand="gContextMenu.saveLink();"/>
-      <menuitem id="context-sendlink"
-                label="&sendLinkCmd.label;"
-                accesskey="&sendLinkCmd.accesskey;"
-                oncommand="gContextMenu.sendLink();"/>
       <menuitem id="context-copyemail"
                 label="&copyEmailCmd.label;"
                 accesskey="&copyEmailCmd.accesskey;"
                 oncommand="gContextMenu.copyEmail();"/>
       <menuitem id="context-copylink"
                 label="&copyLinkCmd.label;"
                 accesskey="&copyLinkCmd.accesskey;"
                 oncommand="goDoCommand('cmd_copyLink');"/>
@@ -124,18 +120,18 @@
                 accesskey="&copyAudioURLCmd.accesskey;"
                 oncommand="gContextMenu.copyMediaLocation();"/>
       <menuseparator id="context-sep-copyimage"/>
       <menuitem id="context-saveimage"
                 label="&saveImageCmd.label;"
                 accesskey="&saveImageCmd.accesskey;"
                 oncommand="gContextMenu.saveMedia();"/>
       <menuitem id="context-sendimage"
-                label="&sendImageCmd.label;"
-                accesskey="&sendImageCmd.accesskey;"
+                label="&emailImageCmd.label;"
+                accesskey="&emailImageCmd.accesskey;"
                 oncommand="gContextMenu.sendMedia();"/>
       <menuitem id="context-setDesktopBackground"
                 label="&setDesktopBackgroundCmd.label;"
                 accesskey="&setDesktopBackgroundCmd.accesskey;"
                 oncommand="gContextMenu.setDesktopBackground();"/>
       <menuitem id="context-viewimageinfo"
                 label="&viewImageInfoCmd.label;"
                 accesskey="&viewImageInfoCmd.accesskey;"
@@ -148,22 +144,22 @@
                 label="&saveAudioCmd.label;"
                 accesskey="&saveAudioCmd.accesskey;"
                 oncommand="gContextMenu.saveMedia();"/>
       <menuitem id="context-video-saveimage"
                 accesskey="&videoSaveImage.accesskey;"
                 label="&videoSaveImage.label;"
                 oncommand="gContextMenu.saveVideoFrameAsImage();"/>
       <menuitem id="context-sendvideo"
-                label="&sendVideoCmd.label;"
-                accesskey="&sendVideoCmd.accesskey;"
+                label="&emailVideoCmd.label;"
+                accesskey="&emailVideoCmd.accesskey;"
                 oncommand="gContextMenu.sendMedia();"/>
       <menuitem id="context-sendaudio"
-                label="&sendAudioCmd.label;"
-                accesskey="&sendAudioCmd.accesskey;"
+                label="&emailAudioCmd.label;"
+                accesskey="&emailAudioCmd.accesskey;"
                 oncommand="gContextMenu.sendMedia();"/>
       <menuitem id="context-back"
                 label="&backCmd.label;"
                 accesskey="&backCmd.accesskey;"
                 command="Browser:BackOrBackDuplicate"
                 onclick="checkForMiddleClick(this, event);"/>
       <menuitem id="context-forward"
                 label="&forwardCmd.label;"
@@ -183,20 +179,16 @@
       <menuitem id="context-bookmarkpage"
                 label="&bookmarkPageCmd2.label;"
                 accesskey="&bookmarkPageCmd2.accesskey;"
                 oncommand="gContextMenu.bookmarkThisPage();"/>
       <menuitem id="context-savepage"
                 label="&savePageCmd.label;"
                 accesskey="&savePageCmd.accesskey2;"
                 oncommand="gContextMenu.savePageAs();"/>
-      <menuitem id="context-sendpage"
-                label="&sendPageCmd.label;"
-                accesskey="&sendPageCmd.accesskey;"
-                oncommand="gContextMenu.sendPage();"/>
       <menuseparator id="context-sep-viewbgimage"/>
       <menuitem id="context-viewbgimage"
                 label="&viewBGImageCmd.label;"
                 accesskey="&viewBGImageCmd.accesskey;"
                 oncommand="gContextMenu.viewBGImage(event);"
                 onclick="checkForMiddleClick(this, event);"/>
       <menuitem id="context-undo"
                 label="&undoCmd.label;"
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -48,18 +48,18 @@
                           accesskey="&closeWindow.accesskey;"/>
                 <menuseparator/>
                 <menuitem id="menu_savePage"
                           label="&savePageCmd.label;"
                           accesskey="&savePageCmd.accesskey;"
                           key="key_savePage"
                           command="Browser:SavePage"/>
                 <menuitem id="menu_sendLink"
-                          label="&sendPageCmd.label;"
-                          accesskey="&sendPageCmd.accesskey;"
+                          label="&emailPageCmd.label;"
+                          accesskey="&emailPageCmd.accesskey;"
                           command="Browser:SendLink"/>
                 <menuseparator/>
                 <menuitem id="menu_printSetup"
                           label="&printSetupCmd.label;"
                           accesskey="&printSetupCmd.accesskey;"
                           command="cmd_pageSetup"/>
 #ifndef XP_MACOSX
                 <menuitem id="menu_printPreview"
@@ -514,17 +514,16 @@
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
                 <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"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -220,23 +220,16 @@
 #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.css
+++ b/browser/base/content/browser.css
@@ -563,16 +563,26 @@ html|*#gcli-output-frame,
 }
 
 .gclitoolbar-input-node,
 .gclitoolbar-complete-node,
 .gclitoolbar-prompt {
   direction: ltr;
 }
 
+#developer-toolbar-webconsole[error-count] > .toolbarbutton-icon {
+  display: none;
+}
+
+#developer-toolbar-webconsole[error-count]:before {
+  content: attr(error-count);
+  display: -moz-box;
+  -moz-box-pack: center;
+}
+
 /* Responsive Mode */
 
 vbox[anonid=browserContainer][responsivemode] {
   overflow: auto;
 }
 
 .devtools-responsiveui-toolbar:-moz-locale-dir(rtl) {
   -moz-box-pack: end;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1395,17 +1395,17 @@ var gBrowserInit = {
       document.getElementById("menu_devToolbar").hidden = false;
       document.getElementById("Tools:DevToolbar").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
       document.getElementById("appmenu_devToolbar").hidden = false;
 #endif
 
       // Show the toolbar if it was previously visible
       if (gPrefService.getBoolPref("devtools.toolbar.visible")) {
-        DeveloperToolbar.show();
+        DeveloperToolbar.show(false);
       }
     }
 
     // Enable Inspector?
     let enabled = gPrefService.getBoolPref("devtools.inspector.enabled");
     if (enabled) {
       document.getElementById("menu_pageinspect").hidden = false;
       document.getElementById("Tools:Inspect").removeAttribute("disabled");
@@ -2605,23 +2605,26 @@ function BrowserOnClick(event) {
 
         let notificationBox = gBrowser.getNotificationBox();
         let value = "blocked-badware-page";
 
         let previousNotification = notificationBox.getNotificationWithValue(value);
         if (previousNotification)
           notificationBox.removeNotification(previousNotification);
 
-        notificationBox.appendNotification(
+        let notification = notificationBox.appendNotification(
           title,
           value,
           "chrome://global/skin/icons/blacklist_favicon.png",
           notificationBox.PRIORITY_CRITICAL_HIGH,
           buttons
         );
+        // Persist the notification until the user removes so it
+        // doesn't get removed on redirects.
+        notification.persistence = -1;
       }
     }
     else if (/^about:home$/i.test(ownerDoc.documentURI)) {
       if (ot == ownerDoc.getElementById("restorePreviousSession")) {
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].
                  getService(Ci.nsISessionStore);
         if (ss.canRestoreLastSession)
           ss.restoreLastSession();
@@ -5256,20 +5259,19 @@ function middleMousePaste(event) {
   try {
     addToUrlbarHistory(url);
   } catch (ex) {
     // Things may go wrong when adding url to session history,
     // but don't let that interfere with the loading of the url.
     Cu.reportError(ex);
   }
 
-  // FIXME: Bug 631500, use openUILink directly
-  let where = whereToOpenLink(event, true);
-  openUILinkIn(url, where,
-               { disallowInheritPrincipal: !mayInheritPrincipal.value });
+  openUILink(url, event,
+             { ignoreButton: true,
+               disallowInheritPrincipal: !mayInheritPrincipal.value });
 
   event.stopPropagation();
 }
 
 function handleDroppedLink(event, url, name)
 {
   let postData = { };
   let uri = getShortcutOrURI(url, postData);
@@ -6909,38 +6911,53 @@ function getTabModalPromptBox(aWindow) {
 function getBrowser() gBrowser;
 function getNavToolbox() gNavToolbox;
 
 let gPrivateBrowsingUI = {
   _privateBrowsingService: null,
   _searchBarValue: null,
   _findBarValue: null,
   _inited: false,
+  _initCallbacks: [],
 
   init: function PBUI_init() {
     Services.obs.addObserver(this, "private-browsing", false);
     Services.obs.addObserver(this, "private-browsing-transition-complete", false);
 
     this._privateBrowsingService = Cc["@mozilla.org/privatebrowsing;1"].
                                    getService(Ci.nsIPrivateBrowsingService);
 
     if (this.privateBrowsingEnabled)
       this.onEnterPrivateBrowsing(true);
 
     this._inited = true;
+
+    this._initCallbacks.forEach(function (callback) callback.apply());
+    this._initCallbacks = [];
   },
 
   uninit: function PBUI_unint() {
     if (!this._inited)
       return;
 
     Services.obs.removeObserver(this, "private-browsing");
     Services.obs.removeObserver(this, "private-browsing-transition-complete");
   },
 
+  get initialized() {
+    return this._inited;
+  },
+
+  addInitializationCallback: function PBUI_addInitializationCallback(aCallback) {
+    if (this._inited)
+      return;
+
+    this._initCallbacks.push(aCallback);
+  },
+
   get _disableUIOnToggle() {
     if (this._privateBrowsingService.autoStarted)
       return false;
 
     try {
       return !gPrefService.getBoolPref("browser.privatebrowsing.keep_current_session");
     }
     catch (e) {
@@ -7133,24 +7150,26 @@ let gPrivateBrowsingUI = {
     if (!this.privateBrowsingEnabled)
       if (!this._shouldEnter())
         return;
 
     this._privateBrowsingService.privateBrowsingEnabled =
       !this.privateBrowsingEnabled;
   },
 
+  get autoStarted() {
+    return this._privateBrowsingService.autoStarted;
+  },
+
   get privateBrowsingEnabled() {
     return this._privateBrowsingService.privateBrowsingEnabled;
   },
 
   /**
-   * These accessors are used to support per-window Private Browsing mode.
-   * For now the getter returns nsIPrivateBrowsingService.privateBrowsingEnabled,
-   * and the setter should only be used in tests.
+   * This accessor is used to support per-window Private Browsing mode.
    */
   get privateWindow() {
     if (!gBrowser)
       return false;
 
     return gBrowser.docShell.QueryInterface(Ci.nsILoadContext)
                             .usePrivateBrowsing;
   }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1032,31 +1032,31 @@
             <hbox class="gclitoolbar-prompt">
               <label class="gclitoolbar-prompt-label">&#187;</label>
             </hbox>
             <hbox class="gclitoolbar-complete-node"/>
             <textbox class="gclitoolbar-input-node" rows="1"/>
           </stack>
           <toolbarbutton id="developer-toolbar-webconsole"
                          label="&webConsoleButton.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          command="Tools:WebConsole"/>
           <toolbarbutton id="developer-toolbar-inspector"
                          label="&inspectorButton.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:Inspect"/>
           <toolbarbutton id="developer-toolbar-styleeditor"
                          label="&styleeditor.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:StyleEditor"/>
           <toolbarbutton id="developer-toolbar-debugger"
                          label="&debuggerMenu.label2;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:Debugger"/>
 #ifndef XP_MACOSX
           <toolbarbutton id="developer-toolbar-closebutton"
                          class="devtools-closebutton"
                          oncommand="DeveloperToolbar.hide();"
                          tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
 #endif
--- a/browser/base/content/newtab/drag.js
+++ b/browser/base/content/newtab/drag.js
@@ -33,20 +33,23 @@ let gDrag = {
    * @param aSite The site that's being dragged.
    * @param aEvent The 'dragstart' event.
    */
   start: function Drag_start(aSite, aEvent) {
     this._draggedSite = aSite;
 
     // Mark nodes as being dragged.
     let selector = ".newtab-site, .newtab-control, .newtab-thumbnail";
-    let nodes = aSite.node.parentNode.querySelectorAll(selector);
+    let parentCell = aSite.node.parentNode;
+    let nodes = parentCell.querySelectorAll(selector);
     for (let i = 0; i < nodes.length; i++)
       nodes[i].setAttribute("dragged", "true");
 
+    parentCell.setAttribute("dragged", "true");
+
     this._setDragData(aSite, aEvent);
 
     // Store the cursor offset.
     let node = aSite.node;
     let rect = node.getBoundingClientRect();
     this._offsetX = aEvent.clientX - rect.left;
     this._offsetY = aEvent.clientY - rect.top;
 
@@ -83,17 +86,17 @@ let gDrag = {
   },
 
   /**
    * Ends the current drag operation.
    * @param aSite The site that's being dragged.
    * @param aEvent The 'dragend' event.
    */
   end: function Drag_end(aSite, aEvent) {
-    let nodes = aSite.node.parentNode.querySelectorAll("[dragged]");
+    let nodes = gGrid.node.querySelectorAll("[dragged]")
     for (let i = 0; i < nodes.length; i++)
       nodes[i].removeAttribute("dragged");
 
     // Slide the dragged site back into its cell (may be the old or the new cell).
     gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true});
 
     this._draggedSite = null;
   },
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -143,18 +143,19 @@ nsContextMenu.prototype = {
   },
 
   initNavigationItems: function CM_initNavigationItems() {
     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
                        this.onCanvas || this.onVideo || this.onAudio ||
                        this.onTextInput);
     this.showItem("context-back", shouldShow);
     this.showItem("context-forward", shouldShow);
-    this.showItem("context-reload", shouldShow);
-    this.showItem("context-stop", shouldShow);
+    var shouldShowReload = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+    this.showItem("context-reload", shouldShow && shouldShowReload);
+    this.showItem("context-stop", shouldShow && !shouldShowReload);
     this.showItem("context-sep-stop", shouldShow);
 
     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
     //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
   },
 
   initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
     // only show the option if the user is in DOM fullscreen
@@ -166,21 +167,19 @@ nsContextMenu.prototype = {
         this.showItem("context-media-sep-commands", true);
   },
 
   initSaveItems: function CM_initSaveItems() {
     var shouldShow = !(this.onTextInput || this.onLink ||
                        this.isContentSelected || this.onImage ||
                        this.onCanvas || this.onVideo || this.onAudio);
     this.showItem("context-savepage", shouldShow);
-    this.showItem("context-sendpage", shouldShow);
 
-    // Save+Send link depends on whether we're in a link, or selected text matches valid URL pattern.
+    // Save link depends on whether we're in a link, or selected text matches valid URL pattern.
     this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
-    this.showItem("context-sendlink", this.onSaveableLink || this.onPlainTextLink);
 
     // Save image depends on having loaded its content, video and audio don't.
     this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
     this.showItem("context-savevideo", this.onVideo);
     this.showItem("context-saveaudio", this.onAudio);
     this.showItem("context-video-saveimage", this.onVideo);
     this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
     this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
@@ -1045,21 +1044,16 @@ nsContextMenu.prototype = {
       linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
     else
       linkText = this.linkText();
     urlSecurityCheck(this.linkURL, doc.nodePrincipal);
 
     this.saveHelper(this.linkURL, linkText, null, true, doc);
   },
 
-  sendLink: function() {
-    // we don't know the title of the link so pass in an empty string
-    MailIntegration.sendMessage( this.linkURL, "" );
-  },
-
   // Backwards-compatibility wrapper
   saveImage : function() {
     if (this.onCanvas || this.onImage)
         this.saveMedia();
   },
 
   // Save URL of the clicked upon image, video, or audio.
   saveMedia: function() {
@@ -1398,20 +1392,16 @@ nsContextMenu.prototype = {
                                        }, window.top);
     }
   },
 
   savePageAs: function CM_savePageAs() {
     saveDocument(this.browser.contentDocument);
   },
 
-  sendPage: function CM_sendPage() {
-    MailIntegration.sendLinkForWindow(this.browser.contentWindow);  
-  },
-
   printFrame: function CM_printFrame() {
     PrintUtils.print(this.target.ownerDocument.defaultView);
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     SwitchDocumentDirection(this.browser.contentWindow);
   },
 
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -349,27 +349,32 @@
               <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
         <vbox class="permission">
           <label class="permissionLabel" id="permIndexedDBLabel"
                  value="&permIndexedDB;" control="indexedDBRadioGroup"/>
           <hbox role="group" aria-labelledby="permIndexedDBLabel">
-            <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permAskAlways;"/>
+            <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permUseDefault;"/>
+            <spacer flex="1"/>
+            <radiogroup id="indexedDBRadioGroup" orient="horizontal">
+              <!-- Ask and Allow are purposefully reversed here! -->
+              <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAskAlways;"/>
+              <radio id="indexedDB#0" command="cmd_indexedDBToggle" label="&permAllow;"/>
+              <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
+            </radiogroup>
+          </hbox>
+          <hbox>
             <spacer flex="1"/>
             <vbox pack="center">
-              <label id="indexedDBStatus" control="indexedDBClear"/>
+              <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
             </vbox>
-            <button id="indexedDBClear" label="&permClearStorage;"
+            <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
                     accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
-            <radiogroup id="indexedDBRadioGroup" orient="horizontal">
-              <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAllow;"/>
-              <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
-            </radiogroup>
           </hbox>
         </vbox>
         <vbox class="permission" id="permPluginsRow">
           <label class="permissionLabel" id="permPluginsLabel"
                  value="&permPlugins;" control="pluginsRadioGroup"/>
           <hbox role="group" aria-labelledby="permPluginsLabel">
             <checkbox id="pluginsDef" command="cmd_pluginsDef" label="&permAskAlways;"/>
             <spacer flex="1"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -46,17 +46,17 @@ var gPermObj = {
     return BLOCK;
   },
   geo: function getGeoDefaultPermissions()
   {
     return BLOCK;
   },
   indexedDB: function getIndexedDBDefaultPermissions()
   {
-    return BLOCK;
+    return UNKNOWN;
   },
   plugins: function getPluginsDefaultPermissions()
   {
     if (gPrefs.getBoolPref("plugins.click_to_play"))
       return BLOCK;
     return ALLOW;
   },
   fullscreen: function getFullscreenDefaultPermissions()
@@ -144,19 +144,16 @@ function onCheckboxClick(aPartId)
 {
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
 
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
   var checkbox = document.getElementById(aPartId + "Def");
   if (checkbox.checked) {
     permissionManager.remove(gPermURI.host, aPartId);
-    if (aPartId == "indexedDB") {
-      permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
-    }
     command.setAttribute("disabled", "true");
     var perm = gPermObj[aPartId]();
     setRadioState(aPartId, perm);
   }
   else {
     onRadioClick(aPartId);
     command.removeAttribute("disabled");
   }
@@ -166,17 +163,18 @@ function onRadioClick(aPartId)
 {
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
 
   var radioGroup = document.getElementById(aPartId + "RadioGroup");
   var id = radioGroup.selectedItem.id;
   var permission = id.split('#')[1];
   permissionManager.add(gPermURI, aPartId, permission);
-  if (aPartId == "indexedDB" && permission == BLOCK) {
+  if (aPartId == "indexedDB" &&
+      (permission == ALLOW || permission == BLOCK)) {
     permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
   }
   if (aPartId == "fullscreen" && permission == UNKNOWN) {
     permissionManager.remove(gPermURI.host, "fullscreen");
   }  
 }
 
 function setRadioState(aPartId, aValue)
@@ -202,17 +200,16 @@ function initIndexedDBRow()
 function onIndexedDBClear()
 {
   Components.classes["@mozilla.org/dom/indexeddb/manager;1"]
             .getService(nsIIndexedDatabaseManager)
             .clearDatabasesForURI(gPermURI);
 
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
-  permissionManager.remove(gPermURI.host, "indexedDB");
   permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
   initIndexedDBRow();
 }
 
 function onIndexedDBUsageCallback(uri, usage, fileUsage)
 {
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1408,20 +1408,19 @@
         <![CDATA[
           var tabsToClose = (aAll ?
                                this.tabs.length - this._removingTabs.length :
                                this.visibleTabs.length - 1)
                             - gBrowser._numPinnedTabs;
           if (tabsToClose <= 1)
             return true;
 
+          var canDisablePrompt = !!aAll;
           const pref = "browser.tabs.warnOnClose";
-          var shouldPrompt = Services.prefs.getBoolPref(pref);
-
-          if (!shouldPrompt)
+          if (canDisablePrompt && !Services.prefs.getBoolPref(pref))
             return true;
 
           var ps = Services.prompt;
 
           // default to true: if it were false, we wouldn't get this far
           var warnOnClose = { value: true };
           var bundle = this.mStringBundle;
 
@@ -1435,21 +1434,23 @@
             ps.confirmEx(window,
                          bundle.getString("tabs.closeWarningTitle"),
                          bundle.getFormattedString("tabs.closeWarningMultipleTabs",
                                                    [tabsToClose]),
                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
                          bundle.getString("tabs.closeButtonMultiple"),
                          null, null,
-                         bundle.getString('tabs.closeWarningPromptMe'),
+                         canDisablePrompt ?
+                           bundle.getString("tabs.closeWarningPromptMe") : null,
                          warnOnClose);
           var reallyClose = (buttonPressed == 0);
+
           // don't set the pref unless they press OK and it's false
-          if (reallyClose && !warnOnClose.value)
+          if (canDisablePrompt && reallyClose && !warnOnClose.value)
             Services.prefs.setBoolPref(pref, false);
 
           return reallyClose;
         ]]>
       </body>
       </method>
 
       <method name="removeAllTabsBut">
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -148,16 +148,17 @@ endif
                  browser_bug655584.js \
                  browser_bug664672.js \
                  browser_bug710878.js \
                  browser_bug719271.js \
                  browser_bug735471.js \
                  browser_bug743421.js \
                  browser_bug749738.js \
                  browser_bug763468.js \
+                 browser_bug767836.js \
                  browser_canonizeURL.js \
                  browser_customize.js \
                  browser_findbarClose.js \
                  browser_homeDrop.js \
                  browser_keywordBookmarklets.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_customize_popupNotification.js \
--- a/browser/base/content/test/browser_bug477014.js
+++ b/browser/base/content/test/browser_bug477014.js
@@ -6,22 +6,22 @@
 const iconURLSpec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 var testPage="data:text/plain,test bug 477014";
 
 function test() {
   waitForExplicitFinish();
 
   var newWindow;
   var tabToDetach;
+  var documentToDetach;
 
   function onPageShow(event) {
     // we get here if the test is executed before the pageshow
     // event for the window's first tab
-    if (!tabToDetach ||
-        tabToDetach.linkedBrowser.contentDocument != event.target)
+    if (!tabToDetach || documentToDetach != event.target)
       return;
 
     event.currentTarget.removeEventListener("pageshow", onPageShow, false);
 
     if (!newWindow) {
       // prepare the tab (set icon and busy state)
       // we have to set these only after onState* notification, otherwise
       // they're overriden
@@ -41,11 +41,15 @@ function test() {
     }
 
     is(newWindow.gBrowser.selectedTab.hasAttribute("busy"), true);
     is(newWindow.gBrowser.getIcon(), iconURLSpec);
     newWindow.close();
     finish();
   }
 
-  gBrowser.addEventListener("pageshow", onPageShow, false);
   tabToDetach = gBrowser.addTab(testPage);
+  tabToDetach.linkedBrowser.addEventListener("load", function onLoad() {
+    tabToDetach.linkedBrowser.removeEventListener("load", onLoad, true);
+    documentToDetach = tabToDetach.linkedBrowser.contentDocument;
+    gBrowser.addEventListener("pageshow", onPageShow, false);
+  }, true);
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug767836.js
@@ -0,0 +1,93 @@
+/* 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/. */
+
+// initialization
+const pb = Cc["@mozilla.org/privatebrowsing;1"].
+           getService(Ci.nsIPrivateBrowsingService);
+const PREF = "browser.newtab.url";
+const NEWTABURL = Services.prefs.getCharPref(PREF) || "about:blank";
+const TESTURL = "http://example.com/";
+  
+function test() {
+
+  waitForExplicitFinish();
+  // check whether the mode that we start off with is normal or not
+  ok(!pb.privateBrowsingEnabled, "private browsing is disabled");
+  // check whether any custom new tab url has been configured
+  ok(!Services.prefs.prefHasUserValue(PREF), "No custom newtab url is set");
+  
+  openNewTab(function () {
+    // Check the new tab opened while in normal mode
+    is(gBrowser.selectedBrowser.currentURI.spec, NEWTABURL,
+       "URL of NewTab should be browser.newtab.url in Normal mode");
+    // Set the custom newtab url
+    Services.prefs.setCharPref(PREF, TESTURL);
+    ok(Services.prefs.prefHasUserValue(PREF), "Custom newtab url is set");
+    
+    // Open a newtab after setting the custom newtab url
+    openNewTab(function () {
+      is(gBrowser.selectedBrowser.currentURI.spec, TESTURL,
+         "URL of NewTab should be the custom url");
+      
+      // clear the custom url preference
+      Services.prefs.clearUserPref(PREF);
+      ok(!Services.prefs.prefHasUserValue(PREF), "No custom newtab url is set");
+      
+      // enter private browsing mode
+      togglePrivateBrowsing(function () {
+        ok(pb.privateBrowsingEnabled, "private browsing is enabled");
+        
+        // Open a new tab page in private browsing mode
+        openNewTab(function () {
+          // Check the new tab opened while in private browsing mode
+          is(gBrowser.selectedBrowser.currentURI.spec, "about:privatebrowsing",
+             "URL of NewTab should be about:privatebrowsing in PB mode");
+          
+          Services.prefs.setCharPref(PREF, TESTURL);
+          ok(Services.prefs.prefHasUserValue(PREF), "Custom newtab url is set");
+          
+          // Open a newtab after setting the custom newtab url
+          openNewTab(function () {
+            is(gBrowser.selectedBrowser.currentURI.spec, TESTURL,
+               "URL of NewTab should be the custom url");
+
+            Services.prefs.clearUserPref(PREF);
+            ok(!Services.prefs.prefHasUserValue(PREF), "No custom newtab url is set");
+            
+            // exit private browsing mode
+            togglePrivateBrowsing(function () {
+              ok(!pb.privateBrowsingEnabled, "private browsing is disabled");
+              
+              gBrowser.removeTab(gBrowser.selectedTab);
+              gBrowser.removeTab(gBrowser.selectedTab);
+              finish();
+            });
+          });
+        });
+      });
+    });
+  });
+}
+
+function togglePrivateBrowsing(aCallback) {
+  let topic = "private-browsing-transition-complete";
+
+  Services.obs.addObserver(function observe() {
+    Services.obs.removeObserver(observe, topic);
+    executeSoon(aCallback);
+  }, topic, false);
+
+  pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
+}
+
+function openNewTab(aCallback) {
+  // Open a new tab
+  BrowserOpenTab();
+  
+  let browser = gBrowser.selectedBrowser;
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    executeSoon(aCallback);
+  }, true);
+}
--- a/browser/base/content/test/newtab/Makefile.in
+++ b/browser/base/content/test/newtab/Makefile.in
@@ -11,16 +11,17 @@ relativesrcdir  = browser/base/content/t
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	browser_newtab_block.js \
 	browser_newtab_disable.js \
 	browser_newtab_drag_drop.js \
 	browser_newtab_drop_preview.js \
+	browser_newtab_focus.js \
 	browser_newtab_private_browsing.js \
 	browser_newtab_reset.js \
 	browser_newtab_tabsync.js \
 	browser_newtab_unpin.js \
 	browser_newtab_bug721442.js \
 	browser_newtab_bug722273.js \
 	browser_newtab_bug723102.js \
 	browser_newtab_bug723121.js \
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -4,58 +4,58 @@
 /*
  * These tests make sure that blocking/removing sites from the grid works
  * as expected. Pinned tabs should not be moved. Gaps will be re-filled
  * if more sites are available.
  */
 function runTests() {
   // we remove sites and expect the gaps to be filled as long as there still
   // are some sites available
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield blockCell(4);
   checkGrid("0,1,2,3,5,6,7,8,9");
 
   yield blockCell(4);
   checkGrid("0,1,2,3,6,7,8,9,");
 
   yield blockCell(4);
   checkGrid("0,1,2,3,7,8,9,,");
 
   // we removed a pinned site
   yield restore();
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1");
 
   yield addNewTabPageTab();
   checkGrid("0,1p,2,3,4,5,6,7,8");
 
   yield blockCell(1);
   checkGrid("0,2,3,4,5,6,7,8,");
 
   // we remove the last site on the grid (which is pinned) and expect the gap
   // to be re-filled and the new site to be unpinned
   yield restore();
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(8);
   checkGrid("0,1,2,3,4,5,6,7,9");
 
   // we remove the first site on the grid with the last one pinned. all cells
   // but the last one should shift to the left and a new site fades in
   yield restore();
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(0);
   checkGrid("1,2,3,4,5,6,7,9,8p");
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug721442.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  setLinks("0,1,2,3,4,5,6,7,8");
-  NewTabUtils.pinnedLinks._links = [
-    {url: "about:blank#7", title: ""},
-    {url: "about:blank#8", title: "title"},
-    {url: "about:blank#9", title: "about:blank#9"}
-  ];
+  yield setLinks("0,1,2,3,4,5,6,7,8");
+  setPinnedLinks([
+    {url: "http://example.com/#7", title: ""},
+    {url: "http://example.com/#8", title: "title"},
+    {url: "http://example.com/#9", title: "http://example.com/#9"}
+  ]);
 
   yield addNewTabPageTab();
   checkGrid("7p,8p,9p,0,1,2,3,4,5");
 
-  checkTooltip(0, "about:blank#7", "1st tooltip is correct");
-  checkTooltip(1, "title\nabout:blank#8", "2nd tooltip is correct");
-  checkTooltip(2, "about:blank#9", "3rd tooltip is correct");
+  checkTooltip(0, "http://example.com/#7", "1st tooltip is correct");
+  checkTooltip(1, "title\nhttp://example.com/#8", "2nd tooltip is correct");
+  checkTooltip(2, "http://example.com/#9", "3rd tooltip is correct");
 }
 
 function checkTooltip(aIndex, aExpected, aMessage) {
   let link = getCell(aIndex).node.querySelector(".newtab-link");
   is(link.getAttribute("title"), aExpected, aMessage);
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug722273.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -1,59 +1,60 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const NOW = Date.now() * 1000;
 const URL = "http://fake-site.com/";
 
 let tmp = {};
-Cu.import("resource:///modules/NewTabUtils.jsm", tmp);
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 
-let {NewTabUtils, Sanitizer} = tmp;
-
-let bhist = Cc["@mozilla.org/browser/global-history;2"]
-  .getService(Ci.nsIBrowserHistory);
+let {Sanitizer} = tmp;
 
 function runTests() {
-  clearHistory();
-  yield fillHistory();
+  sanitizeHistory();
+  yield addFakeVisits();
   yield addNewTabPageTab();
 
   is(getCell(0).site.url, URL, "first site is our fake site");
 
   whenPagesUpdated();
-  yield clearHistory();
+  yield sanitizeHistory();
 
   ok(!getCell(0).site, "the fake site is gone");
 }
 
-function fillHistory() {
+function addFakeVisits() {
   let visits = [];
   for (let i = 59; i > 0; i--) {
     visits.push({
       visitDate: NOW - i * 60 * 1000000,
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
     });
   }
   let place = {
     uri: makeURI(URL),
     title: "fake site",
     visits: visits
   };
   PlacesUtils.asyncHistory.updatePlaces(place, {
-    handleError: function () do_throw("Unexpected error in adding visit."),
-    handleResult: function () { },
-    handleCompletion: function () TestRunner.next()
+    handleError: function () ok(false, "couldn't add visit"),
+    handleResult: function () {},
+    handleCompletion: function () {
+      NewTabUtils.links.populateCache(function () {
+        NewTabUtils.allPages.update();
+        TestRunner.next();
+      }, true);
+    }
   });
 }
 
-function clearHistory() {
+function sanitizeHistory() {
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
 
   let prefs = gPrefService.getBranch(s.prefDomain);
   prefs.setBoolPref("history", true);
   prefs.setBoolPref("downloads", false);
   prefs.setBoolPref("cache", false);
   prefs.setBoolPref("cookies", false);
--- a/browser/base/content/test/newtab/browser_newtab_bug723102.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
   // create a new tab page and hide it.
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   let firstTab = gBrowser.selectedTab;
 
   yield addNewTabPageTab();
   gBrowser.removeTab(firstTab);
 
-  ok(NewTabUtils.allPages.enabled, true, "page is enabled");
+  ok(NewTabUtils.allPages.enabled, "page is enabled");
   NewTabUtils.allPages.enabled = false;
   ok(getGrid().node.hasAttribute("page-disabled"), "page is disabled");
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug723121.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGridLocked(false, "grid is unlocked");
 
   let cell = getCell(0).node;
   let site = getCell(0).site.node;
   let link = site.querySelector(".newtab-link");
--- a/browser/base/content/test/newtab/browser_newtab_bug725996.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   let cell = getCell(0).node;
 
-  sendDragEvent("drop", cell, "about:blank#99\nblank");
-  is(NewTabUtils.pinnedLinks.links[0].url, "about:blank#99",
+  sendDragEvent("drop", cell, "http://example.com/#99\nblank");
+  is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
      "first cell is pinned and contains the dropped site");
 
   yield whenPagesUpdated();
   checkGrid("99p,0,1,2,3,4,5,6,7");
 
   sendDragEvent("drop", cell, "");
-  is(NewTabUtils.pinnedLinks.links[0].url, "about:blank#99",
+  is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
      "first cell is still pinned with the site we dropped before");
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug734043.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   let receivedError = false;
   let block = getContentDocument().querySelector(".newtab-control-block");
 
--- a/browser/base/content/test/newtab/browser_newtab_bug735987.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield simulateDrop(1);
   checkGrid("0,99p,1,2,3,4,5,6,7");
 
--- a/browser/base/content/test/newtab/browser_newtab_disable.js
+++ b/browser/base/content/test/newtab/browser_newtab_disable.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests make sure that the 'New Tab Page' feature can be disabled if the
  * decides not to use it.
  */
 function runTests() {
   // create a new tab page and hide it.
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   let gridNode = getGrid().node;
 
   ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
 
   NewTabUtils.allPages.enabled = false;
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -4,112 +4,112 @@
 /*
  * These tests make sure that dragging and dropping sites works as expected.
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  */
 function runTests() {
   // test a simple drag-and-drop scenario
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield simulateDrop(1, 0);
   checkGrid("1,0p,2,3,4,5,6,7,8");
 
   // drag a cell to its current cell and make sure it's not pinned afterwards
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   yield simulateDrop(0, 0);
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   // ensure that pinned pages aren't moved if that's not necessary
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1,2");
 
   yield addNewTabPageTab();
   checkGrid("0,1p,2p,3,4,5,6,7,8");
 
   yield simulateDrop(3, 0);
   checkGrid("3,1p,2p,0p,4,5,6,7,8");
 
   // pinned sites should always be moved around as blocks. if a pinned site is
   // moved around, neighboring pinned are affected as well
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2,3,4,5,6,7,8");
 
   yield simulateDrop(0, 2);
   checkGrid("2p,0p,1p,3,4,5,6,7,8");
 
   // pinned sites should not be pushed out of the grid (unless there are only
   // pinned ones left on the grid)
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   yield simulateDrop(8, 2);
   checkGrid("0,1,3,4,5,6,7p,8p,2p");
 
   // make sure that pinned sites are re-positioned correctly
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,5");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p,3,4,5p,6,7,8");
 
   yield simulateDrop(4, 0);
   checkGrid("3,1p,2p,4,0p,5p,6,7,8");
 
   // drag a new site onto the very first cell
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   yield simulateDrop(0);
   checkGrid("99p,0,1,2,3,4,5,7p,8p");
 
   // drag a new site onto the grid and make sure that pinned cells don't get
   // pushed out
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   yield simulateDrop(7);
   checkGrid("0,1,2,3,4,5,7p,99p,8p");
 
   // drag a new site beneath a pinned cell and make sure the pinned cell is
   // not moved
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield simulateDrop(7);
   checkGrid("0,1,2,3,4,5,6,99p,8p");
 
   // drag a new site onto a block of pinned sites and make sure they're shifted
   // around accordingly
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,,,,");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p");
 
   yield simulateDrop(1);
   checkGrid("0p,99p,1p,2p,3,4,5,6,7");
 }
--- a/browser/base/content/test/newtab/browser_newtab_drop_preview.js
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * These tests ensure that the drop preview correctly arranges sites when
  * dragging them around.
  */
 function runTests() {
   // the first three sites are pinned - make sure they're re-arranged correctly
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,5");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p,3,4,5p,6,7,8");
 
   let cw = getContentWindow();
   cw.gDrag._draggedSite = getCell(0).site;
   let sites = cw.gDropPreview.rearrange(getCell(4));
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that focusing the 'New Tage Page' works as expected.
+ */
+function runTests() {
+  // Focus count in new tab page.
+  // 28 = 9 * 3 + 1 = 9 sites and 1 toggle button, each site has a link, a pin
+  // and a remove button.
+  let FOCUS_COUNT = 28; 
+  if ("nsILocalFileMac" in Ci) {
+    // 19 = Mac doesn't focus links, so 9 focus targets less than Windows/Linux.
+    FOCUS_COUNT = 19;
+  }
+
+  // Create a new tab page.
+  yield setLinks("0,1,2,3,4,5,6,7,8");
+  setPinnedLinks("");
+
+  yield addNewTabPageTab();
+  gURLBar.focus();
+
+  // Count the focus with the enabled page.
+  yield countFocus(FOCUS_COUNT);
+
+  // Disable page and count the focus with the disabled page.
+  NewTabUtils.allPages.enabled = false;
+  yield countFocus(1);
+}
+
+/**
+ * Focus the urlbar and count how many focus stops to return again to the urlbar.
+ */
+function countFocus(aExpectedCount) {
+  let focusCount = 0;
+  let contentDoc = getContentDocument();
+
+  window.addEventListener("focus", function onFocus() {
+    let focusedElement = document.commandDispatcher.focusedElement;
+    if (focusedElement && focusedElement.classList.contains("urlbar-input")) {
+      window.removeEventListener("focus", onFocus, true);
+      is(focusCount, aExpectedCount, "Validate focus count in the new tab page.");
+      executeSoon(TestRunner.next);
+    } else {
+      if (focusedElement && focusedElement.ownerDocument == contentDoc &&
+          focusedElement instanceof HTMLElement) {
+        focusCount++;
+      }
+      document.commandDispatcher.advanceFocus();
+    }
+  }, true);
+
+  document.commandDispatcher.advanceFocus();
+}
--- a/browser/base/content/test/newtab/browser_newtab_private_browsing.js
+++ b/browser/base/content/test/newtab/browser_newtab_private_browsing.js
@@ -7,17 +7,17 @@
  * The private browsing mode should start with the current grid shown in normal
  * mode.
  */
 let pb = Cc["@mozilla.org/privatebrowsing;1"]
          .getService(Ci.nsIPrivateBrowsingService);
 
 function runTests() {
   // prepare the grid
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   ok(!pb.privateBrowsingEnabled, "private browsing is disabled");
 
   yield addNewTabPageTab();
   pinCell(0);
   checkGrid("0p,1,2,3,4,5,6,7,8");
 
   // enter private browsing mode
   yield togglePrivateBrowsing();
--- a/browser/base/content/test/newtab/browser_newtab_reset.js
+++ b/browser/base/content/test/newtab/browser_newtab_reset.js
@@ -4,17 +4,17 @@
 /*
  * These tests make sure that resetting the 'New Tage Page' works as expected.
  */
 function runTests() {
   // Disabled until bug 716543 is fixed.
   return;
 
   // create a new tab page and check its modified state after blocking a site
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   let resetButton = getContentDocument().getElementById("toolbar-button-reset");
 
   checkGrid("0,1,2,3,4,5,6,7,8");
   ok(!resetButton.hasAttribute("modified"), "page is not modified");
 
--- a/browser/base/content/test/newtab/browser_newtab_tabsync.js
+++ b/browser/base/content/test/newtab/browser_newtab_tabsync.js
@@ -6,17 +6,17 @@
  * 'New Tab Page' are synchronized with all other open 'New Tab Pages'
  * automatically. All about:newtab pages should always be in the same
  * state.
  */
 function runTests() {
   // Disabled until bug 716543 is fixed.
   return;
 
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",1");
 
   yield addNewTabPageTab();
   checkGrid("0,1p,2,3,4,5,6,7,8");
 
   let resetButton = getContentDocument().getElementById("toolbar-button-reset");
   ok(!resetButton.hasAttribute("modified"), "page is not modified");
 
--- a/browser/base/content/test/newtab/browser_newtab_unpin.js
+++ b/browser/base/content/test/newtab/browser_newtab_unpin.js
@@ -3,54 +3,54 @@
 
 /*
  * These tests make sure that when a site gets unpinned it is either moved to
  * its actual place in the grid or removed in case it's not on the grid anymore.
  */
 function runTests() {
   // we have a pinned link that didn't change its position since it was pinned.
   // nothing should happend when we unpin it.
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1");
 
   yield addNewTabPageTab();
   checkGrid("0,1p,2,3,4,5,6,7,8");
 
   yield unpinCell(1);
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   // we have a pinned link that is not anymore in the list of the most-visited
   // links. this should disappear, the remaining links adjust their positions
   // and a new link will appear at the end of the grid.
-  setLinks("0,1,2,3,4,5,6,7,8");
+  yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",99");
 
   yield addNewTabPageTab();
   checkGrid("0,99p,1,2,3,4,5,6,7");
 
   yield unpinCell(1);
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   // we have a pinned link that changed its position since it was pinned. it
   // should be moved to its new position after being unpinned.
-  setLinks("0,1,2,3,4,5,6,7");
+  yield setLinks("0,1,2,3,4,5,6,7");
   setPinnedLinks(",1,,,,,,,0");
 
   yield addNewTabPageTab();
   checkGrid("2,1p,3,4,5,6,7,,0p");
 
   yield unpinCell(1);
   checkGrid("1,2,3,4,5,6,7,,0p");
 
   yield unpinCell(8);
   checkGrid("0,1,2,3,4,5,6,7,");
 
   // we have pinned link that changed its position since it was pinned. the
   // link will disappear from the grid because it's now a much lower priority
-  setLinks("0,1,2,3,4,5,6,7,8,9");
+  yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("9");
 
   yield addNewTabPageTab();
   checkGrid("9p,0,1,2,3,4,5,6,7");
 
   yield unpinCell(0);
   checkGrid("0,1,2,3,4,5,6,7,8");
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -2,31 +2,36 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
 
 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
 
 let tmp = {};
 Cu.import("resource:///modules/NewTabUtils.jsm", tmp);
-let NewTabUtils = tmp.NewTabUtils;
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+let {NewTabUtils, Sanitizer} = tmp;
+
+let uri = Services.io.newURI("about:newtab", null, null);
+let principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
+
+let sm = Services.domStorageManager;
+let storage = sm.getLocalStorageForPrincipal(principal, "");
 
 registerCleanupFunction(function () {
   while (gBrowser.tabs.length > 1)
     gBrowser.removeTab(gBrowser.tabs[1]);
 
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
 });
 
 /**
- * We'll want to restore the original links provider later.
- */
-let originalProvider = NewTabUtils.links._provider;
-
-/**
  * Provide the default test function to start our test runner.
  */
 function test() {
   TestRunner.run();
 }
 
 /**
  * The test runner that controls the execution flow of our tests.
@@ -53,19 +58,17 @@ let TestRunner = {
     }
   },
 
   /**
    * Finishes all tests and cleans up.
    */
   finish: function () {
     function cleanupAndFinish() {
-      // Restore the old provider.
-      NewTabUtils.links._provider = originalProvider;
-
+      clearHistory();
       whenPagesUpdated(finish);
       NewTabUtils.restore();
     }
 
     let callbacks = NewTabUtils.links._populateCallbacks;
     let numCallbacks = callbacks.length;
 
     if (numCallbacks)
@@ -108,52 +111,86 @@ function getCell(aIndex) {
   return getGrid().cells[aIndex];
 }
 
 /**
  * Allows to provide a list of links that is used to construct the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setLinks("1,2,3")
- * Result: [{url: "about:blank#1", title: "site#1"},
- *          {url: "about:blank#2", title: "site#2"}
- *          {url: "about:blank#3", title: "site#3"}]
+ * Result: [{url: "http://example.com/#1", title: "site#1"},
+ *          {url: "http://example.com/#2", title: "site#2"}
+ *          {url: "http://example.com/#3", title: "site#3"}]
  */
-function setLinks(aLinksPattern) {
-  let links = aLinksPattern.split(/\s*,\s*/).map(function (id) {
-    return {url: "about:blank#" + id, title: "site#" + id};
+function setLinks(aLinks) {
+  let links = aLinks;
+
+  if (typeof links == "string") {
+    links = aLinks.split(/\s*,\s*/).map(function (id) {
+      return {url: "http://example.com/#" + id, title: "site#" + id};
+    });
+  }
+
+  clearHistory();
+  fillHistory(links, function () {
+    NewTabUtils.links.populateCache(function () {
+      NewTabUtils.allPages.update();
+      TestRunner.next();
+    }, true);
   });
+}
 
-  NewTabUtils.links._provider = {getLinks: function (c) c(links)};
-  NewTabUtils.links._links = links;
+function clearHistory() {
+  PlacesUtils.history.removeAllPages();
+}
+
+function fillHistory(aLinks, aCallback) {
+  let numLinks = aLinks.length;
+  let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
+
+  for (let link of aLinks.reverse()) {
+    let place = {
+      uri: makeURI(link.url),
+      title: link.title,
+      visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}]
+    };
+
+    PlacesUtils.asyncHistory.updatePlaces(place, {
+      handleError: function () ok(false, "couldn't add visit to history"),
+      handleResult: function () {},
+      handleCompletion: function () {
+        if (--numLinks == 0)
+          aCallback();
+      }
+    });
+  }
 }
 
 /**
  * Allows to specify the list of pinned links (that have a fixed position in
  * the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setPinnedLinks("3,,1")
- * Result: 'about:blank#3' is pinned in the first cell. 'about:blank#1' is
+ * Result: 'http://example.com/#3' is pinned in the first cell. 'http://example.com/#1' is
  *         pinned in the third cell.
  */
-function setPinnedLinks(aLinksPattern) {
-  let pinnedLinks = [];
-
-  aLinksPattern.split(/\s*,\s*/).forEach(function (id, index) {
-    let link;
+function setPinnedLinks(aLinks) {
+  let links = aLinks;
 
-    if (id)
-      link = {url: "about:blank#" + id, title: "site#" + id};
+  if (typeof links == "string") {
+    links = aLinks.split(/\s*,\s*/).map(function (id) {
+      if (id)
+        return {url: "http://example.com/#" + id, title: "site#" + id};
+    });
+  }
 
-    pinnedLinks[index] = link;
-  });
-
-  // Inject the list of pinned links to work with.
-  NewTabUtils.pinnedLinks._links = pinnedLinks;
+  storage.setItem("pinnedLinks", JSON.stringify(links));
+  NewTabUtils.pinnedLinks.resetCache();
+  NewTabUtils.allPages.update();
 }
 
 /**
  * Restore the grid state.
  */
 function restore() {
   whenPagesUpdated();
   NewTabUtils.restore();
@@ -182,35 +219,35 @@ function addNewTabPageTab() {
 }
 
 /**
  * Compares the current grid arrangement with the given pattern.
  * @param the pattern (see below)
  * @param the array of sites to compare with (optional)
  *
  * Example: checkGrid("3p,2,,1p")
- * Result: We expect the first cell to contain the pinned site 'about:blank#3'.
- *         The second cell contains 'about:blank#2'. The third cell is empty.
- *         The fourth cell contains the pinned site 'about:blank#4'.
+ * Result: We expect the first cell to contain the pinned site 'http://example.com/#3'.
+ *         The second cell contains 'http://example.com/#2'. The third cell is empty.
+ *         The fourth cell contains the pinned site 'http://example.com/#4'.
  */
 function checkGrid(aSitesPattern, aSites) {
   let length = aSitesPattern.split(",").length;
   let sites = (aSites || getGrid().sites).slice(0, length);
   let current = sites.map(function (aSite) {
     if (!aSite)
       return "";
 
     let pinned = aSite.isPinned();
     let pinButton = aSite.node.querySelector(".newtab-control-pin");
     let hasPinnedAttr = pinButton.hasAttribute("pinned");
 
     if (pinned != hasPinnedAttr)
       ok(false, "invalid state (site.isPinned() != site[pinned])");
 
-    return aSite.url.replace(/^about:blank#(\d+)$/, "$1") + (pinned ? "p" : "");
+    return aSite.url.replace(/^http:\/\/example\.com\/#(\d+)$/, "$1") + (pinned ? "p" : "");
   });
 
   is(current, aSitesPattern, "grid status = " + aSitesPattern);
 }
 
 /**
  * Blocks a site from the grid.
  * @param aIndex The cell index.
@@ -241,17 +278,17 @@ function unpinCell(aIndex) {
 /**
  * Simulates a drop and drop operation.
  * @param aDropIndex The cell index of the drop target.
  * @param aDragIndex The cell index containing the dragged site (optional).
  */
 function simulateDrop(aDropIndex, aDragIndex) {
   let draggedSite;
   let {gDrag: drag, gDrop: drop} = getContentWindow();
-  let event = createDragEvent("drop", "about:blank#99\nblank");
+  let event = createDragEvent("drop", "http://example.com/#99\nblank");
 
   if (typeof aDragIndex != "undefined")
     draggedSite = getCell(aDragIndex).site;
 
   if (draggedSite)
     drag.start(draggedSite, event);
 
   whenPagesUpdated();
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -267,21 +267,19 @@ function runTest(testNum) {
         openContextMenuFor(text);
         break;
 
     case 2:
         // Context menu for plain text
         plainTextItems = ["context-back",         false,
                           "context-forward",      false,
                           "context-reload",       true,
-                          "context-stop",         false,
                           "---",                  null,
                           "context-bookmarkpage", true,
                           "context-savepage",     true,
-                          "context-sendpage",     true,
                           "---",                  null,
                           "context-viewbgimage",  false,
                           "context-selectall",    true,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true
                          ].concat(inspectItems);
         checkContextMenu(plainTextItems);
@@ -291,17 +289,16 @@ function runTest(testNum) {
 
     case 3:
         // Context menu for text link
         checkContextMenu(["context-openlinkintab", true,
                           "context-openlink",      true,
                           "---",                   null,
                           "context-bookmarklink",  true,
                           "context-savelink",      true,
-                          "context-sendlink",      true,
                           "context-copylink",      true
                          ].concat(inspectItems));
         closeContextMenu();
         openContextMenuFor(mailto); // Invoke context menu for next test.
         break;
 
     case 4:
         // Context menu for text mailto-link
@@ -425,21 +422,19 @@ function runTest(testNum) {
         openContextMenuFor(iframe); // Invoke context menu for next test.
         break;
 
     case 12:
         // Context menu for an iframe
         checkContextMenu(["context-back",         false,
                           "context-forward",      false,
                           "context-reload",       true,
-                          "context-stop",         false,
                           "---",                  null,
                           "context-bookmarkpage", true,
                           "context-savepage",     true,
-                          "context-sendpage",     true,
                           "---",                  null,
                           "context-viewbgimage",  false,
                           "context-selectall",    true,
                           "frame",                null,
                               ["context-showonlythisframe", true,
                                "context-openframeintab",    true,
                                "context-openframe",         true,
                                "---",                       null,
@@ -651,21 +646,19 @@ function runTest(testNum) {
                                "+Radio2",             {type: "checkbox", icon: "", checked: true, disabled: false},
                                "+Radio3",             {type: "checkbox", icon: "", checked: false, disabled: false},
                                "---",                 null,
                                "+Checkbox",           {type: "checkbox", icon: "", checked: false, disabled: false}], null,
                           "---",                  null,
                           "context-back",         false,
                           "context-forward",      false,
                           "context-reload",       true,
-                          "context-stop",         false,
                           "---",                  null,
                           "context-bookmarkpage", true,
                           "context-savepage",     true,
-                          "context-sendpage",     true,
                           "---",                  null,
                           "context-viewbgimage",  false,
                           "context-selectall",    true,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true
                          ].concat(inspectItems));
 
@@ -686,21 +679,19 @@ function runTest(testNum) {
 
         case 22:
         // Context menu for DOM Fullscreen mode (NOTE: this is *NOT* on an img)
         checkContextMenu(["context-leave-dom-fullscreen", true,
                           "---",                          null,
                           "context-back",                 false,
                           "context-forward",              false,
                           "context-reload",               true,
-                          "context-stop",                 false,
                           "---",                          null,
                           "context-bookmarkpage",         true,
                           "context-savepage",             true,
-                          "context-sendpage",             true,
                           "---",                          null,
                           "context-viewbgimage",          false,
                           "context-selectall",            true,
                           "---",                          null,
                           "context-viewsource",           true,
                           "context-viewinfo",             true
                          ].concat(inspectItems));
         closeContextMenu();
@@ -716,21 +707,19 @@ function runTest(testNum) {
         break;
 
     case 23:
         // Context menu for element with assigned content context menu
         // The shift key should bypass content context menu processing
         checkContextMenu(["context-back",         false,
                           "context-forward",      false,
                           "context-reload",       true,
-                          "context-stop",         false,
                           "---",                  null,
                           "context-bookmarkpage", true,
                           "context-savepage",     true,
-                          "context-sendpage",     true,
                           "---",                  null,
                           "context-viewbgimage",  false,
                           "context-selectall",    true,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true
                          ].concat(inspectItems));
         closeContextMenu();
@@ -759,17 +748,16 @@ function runTest(testNum) {
         if (Services.appinfo.OS == "Darwin") {
           // This test is only enabled on Mac due to bug 736399.
           checkContextMenu(["context-openlinkincurrent",           true,
                             "context-openlinkintab",               true,
                             "context-openlink",                    true,
                             "---",                                 null,
                             "context-bookmarklink",                true,
                             "context-savelink",                    true,
-                            "context-sendlink",                    true,
                             "context-copy",                        true,
                             "context-selectall",                   true,
                             "---",                                 null,
                             "context-searchselect",                true,
                             "context-viewpartialsource-selection", true
                            ].concat(inspectItems));
         }
         closeContextMenu();
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -1,26 +1,33 @@
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 # 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/.
 
 // Services = object with smart getters for common XPCOM services
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () {
   const PREF = "browser.newtab.url";
   const TOPIC = "private-browsing-transition-complete";
 
   function getNewTabPageURL() {
-    if (("gPrivateBrowsingUI" in window) && gPrivateBrowsingUI.privateWindow)
-      return "about:privatebrowsing";
-    else
-      return Services.prefs.getCharPref(PREF) || "about:blank";
+    if (("gPrivateBrowsingUI" in window) &&
+        !Services.prefs.prefHasUserValue(PREF)) {
+      // gPrivateBrowsingUI may not be initialized yet, in that case we'll
+      // update BROWSER_NEW_TAB_URL when it gets initialized.
+      if (!gPrivateBrowsingUI.initialized)
+        gPrivateBrowsingUI.addInitializationCallback(update);
+      else if (gPrivateBrowsingUI.privateWindow &&
+               !gPrivateBrowsingUI.autoStarted)
+        return "about:privatebrowsing";
+    }
+    return Services.prefs.getCharPref(PREF) || "about:blank";
   }
 
   function update() {
     BROWSER_NEW_TAB_URL = getNewTabPageURL();
   }
 
   Services.prefs.addObserver(PREF, update, false);
   Services.obs.addObserver(update, TOPIC, false);
--- a/browser/components/dirprovider/DirectoryProvider.h
+++ b/browser/components/dirprovider/DirectoryProvider.h
@@ -4,32 +4,33 @@
 
 #ifndef DirectoryProvider_h__
 #define DirectoryProvider_h__
 
 #include "nsIDirectoryService.h"
 #include "nsComponentManagerUtils.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIFile.h"
+#include "mozilla/Attributes.h"
 
 #define NS_BROWSERDIRECTORYPROVIDER_CONTRACTID \
   "@mozilla.org/browser/directory-provider;1"
 
 namespace mozilla {
 namespace browser {
 
-class DirectoryProvider : public nsIDirectoryServiceProvider2
+class DirectoryProvider MOZ_FINAL : public nsIDirectoryServiceProvider2
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDIRECTORYSERVICEPROVIDER
   NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
 
 private:
-  class AppendingEnumerator : public nsISimpleEnumerator
+  class AppendingEnumerator MOZ_FINAL : public nsISimpleEnumerator
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSISIMPLEENUMERATOR
 
     AppendingEnumerator(nsISimpleEnumerator* aBase,
                         char const *const *aAppendList);
 
--- a/browser/components/feeds/src/FeedWriter.js
+++ b/browser/components/feeds/src/FeedWriter.js
@@ -46,17 +46,17 @@ function makeURI(aURLSpec, aCharset) {
 
 const XML_NS = "http://www.w3.org/XML/1998/namespace"
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
 const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
-const SUBSCRIBE_PAGE_URI = "chrome://browser/content/feeds/subscribe.xhtml";
+const FEEDHANDLER_URI = "about:feeds";
 
 const PREF_SELECTED_APP = "browser.feeds.handlers.application";
 const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
 const PREF_SELECTED_ACTION = "browser.feeds.handler";
 const PREF_SELECTED_READER = "browser.feeds.handler.default";
 
 const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
 const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
@@ -668,17 +668,17 @@ FeedWriter.prototype = {
   /**
    * Get moz-icon url for a file
    * @param   file
    *          A nsIFile object for which the moz-icon:// is returned
    * @returns moz-icon url of the given file as a string
    */
   _getFileIconURL: function FW__getFileIconURL(file) {
     var ios = Cc["@mozilla.org/network/io-service;1"].
-              getService(Components.interfaces.nsIIOService);
+              getService(Ci.nsIIOService);
     var fph = ios.getProtocolHandler("file")
                  .QueryInterface(Ci.nsIFileProtocolHandler);
     var urlSpec = fph.getURLSpecFromFile(file);
     return "moz-icon://" + urlSpec + "?size=16";
   },
 
   /**
    * Helper method to set the selected application and system default
@@ -1084,20 +1084,19 @@ FeedWriter.prototype = {
    * @param aWindow 
    *        The window of the document invoking the BrowserFeedWriter
    */
   _getOriginalURI: function FW__getOriginalURI(aWindow) {
     var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                getInterface(Ci.nsIWebNavigation).
                QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
 
-    var uri = makeURI(SUBSCRIBE_PAGE_URI);
-    var resolvedURI = Cc["@mozilla.org/chrome/chrome-registry;1"].
-                      getService(Ci.nsIChromeRegistry).
-                      convertChromeURL(uri);
+    var resolvedURI = Cc["@mozilla.org/network/io-service;1"].
+                      getService(Ci.nsIIOService).
+                      newChannel(FEEDHANDLER_URI, null, null).URI;
 
     if (resolvedURI.equals(chan.URI))
       return chan.originalURI;
 
     return null;
   },
 
   _window: null,
--- a/browser/components/feeds/src/nsFeedSniffer.h
+++ b/browser/components/feeds/src/nsFeedSniffer.h
@@ -2,18 +2,20 @@
 /* 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 "nsIContentSniffer.h"
 #include "nsIStreamListener.h"
 #include "nsStringAPI.h"
+#include "mozilla/Attributes.h"
 
-class nsFeedSniffer : public nsIContentSniffer, nsIStreamListener
+class nsFeedSniffer MOZ_FINAL : public nsIContentSniffer,
+                                       nsIStreamListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTSNIFFER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   static NS_METHOD AppendSegmentToString(nsIInputStream* inputStream,
--- a/browser/components/privatebrowsing/src/nsPrivateBrowsingServiceWrapper.h
+++ b/browser/components/privatebrowsing/src/nsPrivateBrowsingServiceWrapper.h
@@ -1,20 +1,21 @@
 /* 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 "nsCOMPtr.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsIObserver.h"
+#include "mozilla/Attributes.h"
 
 class nsIJSContextStack;
 
-class nsPrivateBrowsingServiceWrapper : public nsIPrivateBrowsingService,
-                                        public nsIObserver
+class nsPrivateBrowsingServiceWrapper MOZ_FINAL : public nsIPrivateBrowsingService,
+                                                  public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRIVATEBROWSINGSERVICE
   NS_DECL_NSIOBSERVER
 
   nsresult Init();
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
@@ -7,18 +7,16 @@
 
 function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
   waitForExplicitFinish();
 
   let tabBlank = gBrowser.selectedTab;
-  gBrowser.removeAllTabsBut(tabBlank);
-
   let blankBrowser = gBrowser.getBrowserForTab(tabBlank);
   blankBrowser.addEventListener("load", function() {
     blankBrowser.removeEventListener("load", arguments.callee, true);
 
     // change the zoom on the blank page
     FullZoom.enlarge();
     isnot(ZoomManager.zoom, 1, "Zoom level for about:blank should be changed");
 
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -2819,16 +2819,118 @@ let SessionStoreInternal = {
 
     // set smoothScroll back to the original value
     tabstrip.smoothScroll = smoothScroll;
 
     this._sendRestoreCompletedNotifications();
   },
 
   /**
+   * Sets the tabs restoring order with the following priority:
+   * Selected tab, pinned tabs, visible tabs, unhidden tabs and hidden tabs.
+   * @param aTabBrowser
+   *        Tab browser object
+   * @param aTabs
+   *        Array of tab references
+   * @param aTabData
+   *        Array of tab data
+   * @param aSelectedTab
+   *        Index of selected tab
+   */
+  _setTabsRestoringOrder : function ssi__setTabsRestoringOrder(
+    aTabBrowser, aTabs, aTabData, aSelectedTab) {
+    // Temporally store the pinned tabs before moving the hidden tabs and
+    // optimizing the visible tabs. In case the selected tab is a pinned tab,
+    // the index is also stored.
+    let pinnedTabs = aTabData.filter(function (aData) aData.pinned).length;
+    let pinnedTabsArray = [];
+    let pinnedTabsDataArray = [];
+    let pinnedSelectedTab = null;
+    if (pinnedTabs && aTabs.length > 1) {
+      for (let t = aTabs.length - 1; t >= 0; t--) {
+        if (aTabData[t].pinned) {
+          pinnedTabsArray.unshift(aTabs.splice(t, 1)[0]);
+          pinnedTabsDataArray.unshift(aTabData.splice(t, 1)[0]);
+          if (aSelectedTab) {
+            if (aSelectedTab > (t + 1))
+              --aSelectedTab;
+            else if (aSelectedTab == (t + 1)) {
+              aSelectedTab = null;
+              pinnedSelectedTab = 1;
+            }
+          } else if (pinnedSelectedTab) 
+            ++pinnedSelectedTab;
+        }
+      }
+    }
+
+    // Without the pinned tabs, we move the hidden tabs to the end of the list
+    // and optimize the visible tabs. 
+    let unhiddenTabs = aTabData.filter(function (aData) !aData.hidden).length;
+    if (unhiddenTabs && aTabs.length > 1) {
+      // Load hidden tabs last, by pushing them to the end of the list.
+      for (let t = 0, tabsToReorder = aTabs.length - unhiddenTabs; tabsToReorder > 0; ) {
+        if (aTabData[t].hidden) {
+          aTabs = aTabs.concat(aTabs.splice(t, 1));
+          aTabData = aTabData.concat(aTabData.splice(t, 1));
+          if (aSelectedTab && aSelectedTab > t)
+            --aSelectedTab;
+          --tabsToReorder;
+          continue;
+        }
+        ++t;
+      }
+
+      // Determine if we can optimize & load visible tabs first
+      let maxVisibleTabs = Math.ceil(aTabBrowser.tabContainer.mTabstrip.scrollClientSize /
+                                     aTabs[unhiddenTabs - 1].getBoundingClientRect().width);
+
+      // Make sure we restore visible tabs first, if there are enough
+      if (aSelectedTab && maxVisibleTabs < unhiddenTabs && aSelectedTab > 1) {
+        let firstVisibleTab = 0;
+        if (unhiddenTabs - maxVisibleTabs > aSelectedTab) {
+          // aSelectedTab is leftmost since we scroll to it when possible
+          firstVisibleTab = aSelectedTab - 1;
+        } else {
+          // aSelectedTab is rightmost or no more room to scroll right
+          firstVisibleTab = unhiddenTabs - maxVisibleTabs;
+        }
+        aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs);
+        aTabData = aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData);
+        aSelectedTab -= firstVisibleTab;
+      }
+    }
+
+    // Load the pinned tabs at the beginning of the list and restore the
+    // selected tab index.
+    if (pinnedTabsArray) {
+      // Restore the selected tab index.
+      if (pinnedSelectedTab) {
+        aSelectedTab = pinnedSelectedTab;
+      } else {
+        aSelectedTab += pinnedTabsArray.length;
+      }
+      // Load the pinned tabs at the beginning of the list.
+      for (let t = pinnedTabsArray.length - 1; t >= 0; t--) {
+        aTabs.unshift(pinnedTabsArray.splice(t, 1)[0]);
+        aTabData.unshift(pinnedTabsDataArray.splice(t, 1)[0]);
+      }
+    }
+
+    // Load the selected tab to the first position.
+    if (aSelectedTab-- && aTabs[aSelectedTab]) {
+      aTabs.unshift(aTabs.splice(aSelectedTab, 1)[0]);
+      aTabData.unshift(aTabData.splice(aSelectedTab, 1)[0]);
+      aTabBrowser.selectedTab = aTabs[0];
+    }
+    
+    return [aTabs, aTabData];
+  },
+  
+  /**
    * Manage history restoration for a window
    * @param aWindow
    *        Window to restore the tabs into
    * @param aTabs
    *        Array of tab references
    * @param aTabData
    *        Array of tab data
    * @param aSelectTab
@@ -2870,58 +2972,19 @@ let SessionStoreInternal = {
 
     if (aTabs.length == 0) {
       // this is normally done in restoreHistory() but as we're returning early
       // here we need to take care of it.
       this._setWindowStateReady(aWindow);
       return;
     }
 
-    let unhiddenTabs = aTabData.filter(function (aData) !aData.hidden).length;
-
-    if (unhiddenTabs && aTabs.length > 1) {
-      // Load hidden tabs last, by pushing them to the end of the list
-      for (let t = 0, tabsToReorder = aTabs.length - unhiddenTabs; tabsToReorder > 0; ) {
-        if (aTabData[t].hidden) {
-          aTabs = aTabs.concat(aTabs.splice(t, 1));
-          aTabData = aTabData.concat(aTabData.splice(t, 1));
-          if (aSelectTab > t)
-            --aSelectTab;
-          --tabsToReorder;
-          continue;
-        }
-        ++t;
-      }
-
-      // Determine if we can optimize & load visible tabs first
-      let maxVisibleTabs = Math.ceil(tabbrowser.tabContainer.mTabstrip.scrollClientSize /
-                                     aTabs[unhiddenTabs - 1].getBoundingClientRect().width);
-
-      // make sure we restore visible tabs first, if there are enough
-      if (maxVisibleTabs < unhiddenTabs && aSelectTab > 1) {
-        let firstVisibleTab = 0;
-        if (unhiddenTabs - maxVisibleTabs > aSelectTab) {
-          // aSelectTab is leftmost since we scroll to it when possible
-          firstVisibleTab = aSelectTab - 1;
-        } else {
-          // aSelectTab is rightmost or no more room to scroll right
-          firstVisibleTab = unhiddenTabs - maxVisibleTabs;
-        }
-        aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs);
-        aTabData = aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData);
-        aSelectTab -= firstVisibleTab;
-      }
-    }
-
-    // make sure to restore the selected tab first (if any)
-    if (aSelectTab-- && aTabs[aSelectTab]) {
-      aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
-      aTabData.unshift(aTabData.splice(aSelectTab, 1)[0]);
-      tabbrowser.selectedTab = aTabs[0];
-    }
+    // Sets the tabs restoring order. 
+    [aTabs, aTabData] =
+      this._setTabsRestoringOrder(tabbrowser, aTabs, aTabData, aSelectTab);
 
     // Prepare the tabs so that they can be properly restored. We'll pin/unpin
     // and show/hide tabs as necessary. We'll also set the labels, user typed
     // value, and attach a copy of the tab's data in case we close it before
     // it's been restored.
     for (t = 0; t < aTabs.length; t++) {
       let tab = aTabs[t];
       let browser = tabbrowser.getBrowserForTab(tab);
--- a/browser/components/sessionstore/test/browser_480148.js
+++ b/browser/components/sessionstore/test/browser_480148.js
@@ -3,123 +3,208 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function test() {
   /** Test for Bug 484108 **/
   waitForExplicitFinish();
   requestLongerTimeout(3);
 
   // builds the tests state based on a few parameters
-  function buildTestState(num, selected, hidden) {
-    let state = { windows: [ { "tabs": [], "selected": selected } ] };
+  function buildTestState(num, selected, hidden, pinned) {
+    let state = { windows: [ { "tabs": [], "selected": selected + 1 } ] };
     while (num--) {
-      state.windows[0].tabs.push({entries: [{url: "http://example.com/"}]});
+      state.windows[0].tabs.push({
+        entries: [
+          { url: "http://example.com/?t=" + state.windows[0].tabs.length }
+        ]
+      });
       let i = state.windows[0].tabs.length - 1;
       if (hidden.length > 0 && i == hidden[0]) {
         state.windows[0].tabs[i].hidden = true;
         hidden.splice(0, 1);
       }
+      if (pinned.length > 0 && i == pinned[0]) {
+        state.windows[0].tabs[i].pinned = true;
+        pinned.splice(0, 1);
+      }
     }
     return state;
   }
 
   let tests = [
     { testNum: 1,
       totalTabs: 13,
-      selectedTab: 1,
+      selectedTab: 0,
       shownTabs: 6,
       hiddenTabs: [],
+      pinnedTabs: [],
       order: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
     },
     { testNum: 2,
       totalTabs: 13,
-      selectedTab: 13,
+      selectedTab: 12,
       shownTabs: 6,
       hiddenTabs: [],
+      pinnedTabs: [],
       order: [12, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6]
     },
     { testNum: 3,
       totalTabs: 13,
-      selectedTab: 4,
+      selectedTab: 3,
       shownTabs: 6,
       hiddenTabs: [],
+      pinnedTabs: [],
       order: [3, 4, 5, 6, 7, 8, 0, 1, 2, 9, 10, 11, 12]
     },
     { testNum: 4,
       totalTabs: 13,
-      selectedTab: 11,
+      selectedTab: 10,
       shownTabs: 6,
       hiddenTabs: [],
+      pinnedTabs: [],
       order: [10, 7, 8, 9, 11, 12, 0, 1, 2, 3, 4, 5, 6]
     },
     { testNum: 5,
       totalTabs: 13,
-      selectedTab: 13,
+      selectedTab: 12,
       shownTabs: 6,
       hiddenTabs: [0, 4, 9],
+      pinnedTabs: [],
       order: [12, 6, 7, 8, 10, 11, 1, 2, 3, 5, 0, 4, 9]
     },
     { testNum: 6,
       totalTabs: 13,
-      selectedTab: 4,
+      selectedTab: 3,
       shownTabs: 6,
       hiddenTabs: [1, 7, 12],
+      pinnedTabs: [],
       order: [3, 4, 5, 6, 8, 9, 0, 2, 10, 11, 1, 7, 12]
     },
     { testNum: 7,
       totalTabs: 13,
-      selectedTab: 4,
+      selectedTab: 3,
       shownTabs: 6,
       hiddenTabs: [0, 1, 2],
+      pinnedTabs: [],
       order: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2]
+    },
+    { testNum: 8,
+      totalTabs: 13,
+      selectedTab: 0,
+      shownTabs: 6,
+      hiddenTabs: [],
+      pinnedTabs: [0],
+      order: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    },
+    { testNum: 9,
+      totalTabs: 13,
+      selectedTab: 1,
+      shownTabs: 6,
+      hiddenTabs: [],
+      pinnedTabs: [0],
+      order: [1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    },
+    { testNum: 10,
+      totalTabs: 13,
+      selectedTab: 3,
+      shownTabs: 6,
+      hiddenTabs: [2],
+      pinnedTabs: [0,1],
+      order: [3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 2]
+    },
+    { testNum: 11,
+      totalTabs: 13,
+      selectedTab: 12,
+      shownTabs: 6,
+      hiddenTabs: [],
+      pinnedTabs: [0,1,2],
+      order: [12, 0, 1, 2, 7, 8, 9, 10, 11, 3, 4, 5, 6]
+    },
+    { testNum: 12,
+      totalTabs: 13,
+      selectedTab: 6,
+      shownTabs: 6,
+      hiddenTabs: [3,4,5],
+      pinnedTabs: [0,1,2],
+      order: [6, 0, 1, 2, 7, 8, 9, 10, 11, 12, 3, 4, 5]
+    },
+    { testNum: 13,
+      totalTabs: 13,
+      selectedTab: 1,
+      shownTabs: 6,
+      hiddenTabs: [3,4,5],
+      pinnedTabs: [0,1,2],
+      order: [1, 0, 2, 6, 7, 8, 9, 10, 11, 12, 3, 4, 5]
+    },
+    { testNum: 14,
+      totalTabs: 13,
+      selectedTab: 2,
+      shownTabs: 6,
+      hiddenTabs: [],
+      pinnedTabs: [0,1,2],
+      order: [2, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    },
+    { testNum: 15,
+      totalTabs: 13,
+      selectedTab: 3,
+      shownTabs: 6,
+      hiddenTabs: [1,4],
+      pinnedTabs: [0,1,2],
+      order: [3, 0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 4]
     }
   ];
 
-  let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
+  let tabMinWidth =
+    parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
   let testIndex = 0;
 
   function runNextTest() {
     if (tests.length == 0) {
       finish();
       return;
     }
 
     info ("Starting test " + (++testIndex));
     let test = tests.shift();
-    let state = buildTestState(test.totalTabs, test.selectedTab, test.hiddenTabs);
+    let state = buildTestState(test.totalTabs, test.selectedTab,
+                               test.hiddenTabs, test.pinnedTabs);
     let tabbarWidth = Math.floor((test.shownTabs - 0.5) * tabMinWidth);
     let win = openDialog(location, "_blank", "chrome,all,dialog=no");
-    let actualOrder = [];
+    let tabsRestored = [];
 
     win.addEventListener("SSTabRestoring", function onSSTabRestoring(aEvent) {
       let tab = aEvent.originalTarget;
-      let currentIndex = Array.indexOf(win.gBrowser.tabs, tab);
-      actualOrder.push(currentIndex);
+      let tabLink = tab.linkedBrowser.currentURI.spec;
+      let tabIndex =
+        tabLink.substring(tabLink.indexOf("?t=") + 3, tabLink.length);
 
-      if (actualOrder.length < state.windows[0].tabs.length)
+      // we need to compare with the tab's restoring index, no with the
+      // position index, since the pinned tabs change the positions in the
+      // tabbar. 
+      tabsRestored.push(tabIndex);
+
+      if (tabsRestored.length < state.windows[0].tabs.length)
         return;
 
       // all of the tabs should be restoring or restored by now
-      is(actualOrder.length, state.windows[0].tabs.length,
+      is(tabsRestored.length, state.windows[0].tabs.length,
          "Test #" + testIndex + ": Number of restored tabs is as expected");
 
-      is(actualOrder.join(" "), test.order.join(" "),
+      is(tabsRestored.join(" "), test.order.join(" "),
          "Test #" + testIndex + ": 'visible' tabs restored first");
 
-      // Cleanup.
+      // cleanup
       win.removeEventListener("SSTabRestoring", onSSTabRestoring, false);
       win.close();
       executeSoon(runNextTest);
     }, false);
 
-    win.addEventListener("load", function onLoad(aEvent) {
-      win.removeEventListener("load", onLoad, false);
-      executeSoon(function () {
-        let extent = win.outerWidth - win.gBrowser.tabContainer.mTabstrip.scrollClientSize;
-        let windowWidth = tabbarWidth + extent;
-        win.resizeTo(windowWidth, win.outerHeight);
-        ss.setWindowState(win, JSON.stringify(state), true);
-      });
-    }, false);
+    whenWindowLoaded(win, function(aEvent) {
+      let extent =
+        win.outerWidth - win.gBrowser.tabContainer.mTabstrip.scrollClientSize;
+      let windowWidth = tabbarWidth + extent;
+      win.resizeTo(windowWidth, win.outerHeight);
+      ss.setWindowState(win, JSON.stringify(state), true);
+    });
   };
 
   runNextTest();
 }
--- a/browser/components/tabview/content.js
+++ b/browser/components/tabview/content.js
@@ -36,19 +36,17 @@ let WindowEventHandler = {
     sendSyncMessage("Panorama:DOMWillOpenModalDialog");
   },
 
   // ----------
   // Function: onMozAfterPaint
   // Sends an asynchronous message when the "onMozAfterPaint" event
   // is fired.
   onMozAfterPaint: function WEH_onMozAfterPaint(event) {
-    if (event.clientRects.length > 0) {
-      sendAsyncMessage("Panorama:MozAfterPaint");
-    }
+    sendAsyncMessage("Panorama:MozAfterPaint");
   }
 };
 
 // add event listeners
 addEventListener("DOMWillOpenModalDialog", WindowEventHandler.onDOMWillOpenModalDialog, false);
 addEventListener("MozAfterPaint", WindowEventHandler.onMozAfterPaint, false);
 
 // ----------
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -1,13 +1,13 @@
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 ac_add_options --enable-signmar
-ac_add_options --enable-profiling
+#ac_add_options --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
--- a/browser/config/tooltool-manifests/linux32/clang.manifest
+++ b/browser/config/tooltool-manifests/linux32/clang.manifest
@@ -1,15 +1,15 @@
 [
-{"clang_version": "r159409"},
+{"clang_version": "r159509"},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 74004324,
-"digest": "c5bb558a5958458b11da385d57897a1f7e57a7be2f452438f6813cf1e9f2a4ce93769c36e304126f97dbd753732b70446c7fa3c779267e80152d7ac0175057fc",
+"size": 74071397,
+"digest": "390e499161e8b5e91c179f3352ecbb07431e3d5a190298de7f338b48fe3807f3ddbeca72d8df39d11f89864fb1135f14500471faa741d3886b875b8e2b1d4416",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/clang.manifest
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest
@@ -1,15 +1,15 @@
 [
-{"clang_version": "r159409"},
+{"clang_version": "r159509"},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 72533541,
-"digest": "f092080caed28db1ed7d9f0612aef1d885d2587b4e069d3662af5c241950ee772b6bc249d766a47b4fe2c170a46cfe05010269a3cbc1123d1f4126bc182b7b40",
+"size": 72518043,
+"digest": "447dac319a8d7fa902cc065b758440bf6d4a7a98104362162fbdb0479a44d9b84c5878506f09be4398053a6ddc07935c7fb4f71e59b178073876fb5f90a45219",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest
+++ b/browser/config/tooltool-manifests/macosx64/clang.manifest
@@ -1,15 +1,15 @@
 [
-{"clang_version": "r159409"},
+{"clang_version": "r159509"},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 63604770,
-"digest": "c664beadb01a4e8ba7f32698b3ef694ade9044a9117d90d57f1475f2cefea82d74d1b0f3da89dcb9932ba90e048ae3d908bff05ab09d0ce2c82731e949e62a47",
+"size": 63679229,
+"digest": "5257503e537b8d440b17e40aa06f0f70f1b124129c02f10e45b46ac642fc4170bfa77ae737a8bcac3ed7602ccd934a88cbe349986eb971d66a6fb553ae31f13c",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -143,31 +143,28 @@ define('gcli/index', ['require', 'export
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell', 'gcli/types/selection', 'gcli/argument'], function(require, exports, module) {
+define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/selection', 'gcli/argument'], function(require, exports, module) {
 
 
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var Type = require('gcli/types').Type;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 var ArrayConversion = require('gcli/types').ArrayConversion;
-var Speller = require('gcli/types/spell').Speller;
 var SelectionType = require('gcli/types/selection').SelectionType;
 
-var Argument = require('gcli/argument').Argument;
-var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
-var FalseNamedArgument = require('gcli/argument').FalseNamedArgument;
+var BlankArgument = require('gcli/argument').BlankArgument;
 var ArrayArgument = require('gcli/argument').ArrayArgument;
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(StringType);
@@ -258,17 +255,17 @@ NumberType.prototype.getMax = function()
     if (typeof this._max === 'number') {
       return this._max;
     }
   }
   return undefined;
 };
 
 NumberType.prototype.parse = function(arg) {
-  if (arg.text.replace(/\s/g, '').length === 0) {
+  if (arg.text.replace(/^\s*-?/, '').length === 0) {
     return new Conversion(undefined, arg, Status.INCOMPLETE, '');
   }
 
   var value = parseInt(arg.text, 10);
   if (isNaN(value)) {
     return new Conversion(undefined, arg, Status.ERROR,
         l10n.lookupFormat('typesNumberNan', [ arg.text ]));
   }
@@ -343,34 +340,34 @@ function BooleanType(typeSpec) {
 BooleanType.prototype = Object.create(SelectionType.prototype);
 
 BooleanType.prototype.lookup = [
   { name: 'false', value: false },
   { name: 'true', value: true }
 ];
 
 BooleanType.prototype.parse = function(arg) {
-  if (arg instanceof TrueNamedArgument) {
+  if (arg.type === 'TrueNamedArgument') {
     return new Conversion(true, arg);
   }
-  if (arg instanceof FalseNamedArgument) {
+  if (arg.type === 'FalseNamedArgument') {
     return new Conversion(false, arg);
   }
   return SelectionType.prototype.parse.call(this, arg);
 };
 
 BooleanType.prototype.stringify = function(value) {
   if (value == null) {
     return '';
   }
   return '' + value;
 };
 
 BooleanType.prototype.getBlank = function() {
-  return new Conversion(false, new Argument(), Status.VALID, '', this.lookup);
+  return new Conversion(false, new BlankArgument(), Status.VALID, '', this.lookup);
 };
 
 BooleanType.prototype.name = 'boolean';
 
 exports.BooleanType = BooleanType;
 
 
 /**
@@ -470,17 +467,17 @@ ArrayType.prototype.stringify = function
   if (values == null) {
     return '';
   }
   // BUG 664204: Check for strings with spaces and add quotes
   return values.join(' ');
 };
 
 ArrayType.prototype.parse = function(arg) {
-  if (arg instanceof ArrayArgument) {
+  if (arg.type === 'ArrayArgument') {
     var conversions = arg.getArguments().map(function(subArg) {
       var conversion = this.subtype.parse(subArg);
       // Hack alert. ArrayConversion needs to be able to answer questions
       // about the status of individual conversions in addition to the
       // overall state. This allows us to do that easily.
       subArg.conversion = conversion;
       return conversion;
     }, this);
@@ -604,20 +601,20 @@ exports.lookupFormat = function(key, swa
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 define('gcli/types', ['require', 'exports', 'module' , 'gcli/argument'], function(require, exports, module) {
-var types = exports;
 
 
 var Argument = require('gcli/argument').Argument;
+var BlankArgument = require('gcli/argument').BlankArgument;
 
 
 /**
  * Some types can detect validity, that is to say they can distinguish between
  * valid and invalid values.
  * We might want to change these constants to be numbers for better performance
  */
 var Status = {
@@ -666,17 +663,19 @@ var Status = {
       }
       if (status > combined) {
         combined = status;
       }
     }
     return combined;
   }
 };
-types.Status = Status;
+
+exports.Status = Status;
+
 
 /**
  * The type.parse() method converts an Argument into a value, Conversion is
  * a wrapper to that value.
  * Conversion is needed to collect a number of properties related to that
  * conversion in one place, i.e. to handle errors and provide traceability.
  * @param value The result of the conversion
  * @param arg The data from which the conversion was made
@@ -715,33 +714,31 @@ function Conversion(value, arg, status, 
     throw new Error('Missing arg');
   }
 
   this._status = status || Status.VALID;
   this.message = message;
   this.predictions = predictions;
 }
 
-types.Conversion = Conversion;
-
 /**
  * Ensure that all arguments that are part of this conversion know what they
  * are assigned to.
  * @param assignment The Assignment (param/conversion link) to inform the
  * argument about.
  */
 Conversion.prototype.assign = function(assignment) {
   this.arg.assign(assignment);
 };
 
 /**
  * Work out if there is information provided in the contained argument.
  */
 Conversion.prototype.isDataProvided = function() {
-  return !this.arg.isBlank();
+  return this.arg.type !== 'BlankArgument';
 };
 
 /**
  * 2 conversions are equal if and only if their args are equal (argEquals) and
  * their values are equal (valueEquals).
  * @param that The conversion object to compare against.
  */
 Conversion.prototype.equals = function(that) {
@@ -853,16 +850,25 @@ Conversion.prototype.constrainPrediction
   index = index % predictions.length;
   if (index < 0) {
     index = predictions.length + index;
   }
   return index;
 };
 
 /**
+ * Constant to allow everyone to agree on the maximum number of predictions
+ * that should be provided. We actually display 1 less than this number.
+ */
+Conversion.maxPredictions = 11;
+
+exports.Conversion = Conversion;
+
+
+/**
  * ArrayConversion is a special Conversion, needed because arrays are converted
  * member by member rather then as a whole, which means we can track the
  * conversion if individual array elements. So an ArrayConversion acts like a
  * normal Conversion (which is needed as Assignment requires a Conversion) but
  * it can also be devolved into a set of Conversions for each array member.
  */
 function ArrayConversion(conversions, arg) {
   this.arg = arg;
@@ -926,17 +932,17 @@ ArrayConversion.prototype.valueEquals = 
 };
 
 ArrayConversion.prototype.toString = function() {
   return '[ ' + this.conversions.map(function(conversion) {
     return conversion.toString();
   }, this).join(', ') + ' ]';
 };
 
-types.ArrayConversion = ArrayConversion;
+exports.ArrayConversion = ArrayConversion;
 
 
 /**
  * Most of our types are 'static' e.g. there is only one type of 'string',
  * however some types like 'selection' and 'deferred' are customizable.
  * The basic Type type isn't useful, but does provide documentation about what
  * types do.
  */
@@ -998,50 +1004,50 @@ Type.prototype.decrement = function(valu
 
 /**
  * The 'blank value' of most types is 'undefined', but there are exceptions;
  * This allows types to specify a better conversion from empty string than
  * 'undefined'.
  * 2 known examples of this are boolean -> false and array -> []
  */
 Type.prototype.getBlank = function() {
-  return this.parse(new Argument());
+  return this.parse(new BlankArgument());
 };
 
 /**
  * This is something of a hack for the benefit of DeferredType which needs to
  * be able to lie about it's type for fields to accept it as one of their own.
  * Sub-types can ignore this unless they're DeferredType.
  */
 Type.prototype.getType = function() {
   return this;
 };
 
-types.Type = Type;
+exports.Type = Type;
 
 /**
  * Private registry of types
  * Invariant: types[name] = type.name
  */
 var registeredTypes = {};
 
-types.getTypeNames = function() {
+exports.getTypeNames = function() {
   return Object.keys(registeredTypes);
 };
 
 /**
  * Add a new type to the list available to the system.
  * You can pass 2 things to this function - either an instance of Type, in
  * which case we return this instance when #getType() is called with a 'name'
  * that matches type.name.
  * Also you can pass in a constructor (i.e. function) in which case when
  * #getType() is called with a 'name' that matches Type.prototype.name we will
  * pass the typeSpec into this constructor.
  */
-types.registerType = function(type) {
+exports.registerType = function(type) {
   if (typeof type === 'object') {
     if (type instanceof Type) {
       if (!type.name) {
         throw new Error('All registered types must have a name');
       }
       registeredTypes[type.name] = type;
     }
     else {
@@ -1054,35 +1060,35 @@ types.registerType = function(type) {
     }
     registeredTypes[type.prototype.name] = type;
   }
   else {
     throw new Error('Unknown type: ' + type);
   }
 };
 
-types.registerTypes = function registerTypes(newTypes) {
+exports.registerTypes = function registerTypes(newTypes) {
   Object.keys(newTypes).forEach(function(name) {
     var type = newTypes[name];
     type.name = name;
     newTypes.registerType(type);
   });
 };
 
 /**
  * Remove a type from the list available to the system
  */
-types.deregisterType = function(type) {
+exports.deregisterType = function(type) {
   delete registeredTypes[type.name];
 };
 
 /**
  * Find a type, previously registered using #registerType()
  */
-types.getType = function(typeSpec) {
+exports.getType = function(typeSpec) {
   var type;
   if (typeof typeSpec === 'string') {
     type = registeredTypes[typeSpec];
     if (typeof type === 'function') {
       type = new type({});
     }
     return type;
   }
@@ -1120,16 +1126,27 @@ types.getType = function(typeSpec) {
  * limitations under the License.
  */
 
 define('gcli/argument', ['require', 'exports', 'module' ], function(require, exports, module) {
 var argument = exports;
 
 
 /**
+ * Thinking out loud here:
+ * Arguments are an area where we could probably refactor things a bit better.
+ * The split process in Requisition creates a set of Arguments, which are then
+ * assigned. The assign process sometimes converts them into subtypes of
+ * Argument. We might consider that what gets assigned is _always_ one of the
+ * subtypes (or actually a different type hierarchy entirely) and that we
+ * don't manipulate the prefix/text/suffix but just use the 'subtypes' as
+ * filters which present a view of the underlying original Argument.
+ */
+
+/**
  * We record where in the input string an argument comes so we can report
  * errors against those string positions.
  * @param text The string (trimmed) that contains the argument
  * @param prefix Knowledge of quotation marks and whitespace used prior to the
  * text in the input string allows us to re-generate the original input from
  * the arguments.
  * @param suffix Any quotation marks and whitespace used after the text.
  * Whitespace is normally placed in the prefix to the succeeding argument, but
@@ -1144,16 +1161,18 @@ function Argument(text, prefix, suffix) 
   }
   else {
     this.text = text;
     this.prefix = prefix !== undefined ? prefix : '';
     this.suffix = suffix !== undefined ? suffix : '';
   }
 }
 
+Argument.prototype.type = 'Argument';
+
 /**
  * Return the result of merging these arguments.
  * case and some of the arguments are in quotation marks?
  */
 Argument.prototype.merge = function(following) {
   // Is it possible that this gets called when we're merging arguments
   // for the single string?
   return new Argument(
@@ -1178,25 +1197,16 @@ Argument.prototype.beget = function(repl
     prefix = (options.prefixSpace ? ' ' : '') + quote;
     suffix = quote;
   }
 
   return new Argument(replText, prefix, suffix);
 };
 
 /**
- * Is there any visible content to this argument?
- */
-Argument.prototype.isBlank = function() {
-  return this.text === '' &&
-      this.prefix.trim() === '' &&
-      this.suffix.trim() === '';
-};
-
-/**
  * We need to keep track of which assignment we've been assigned to
  */
 Argument.prototype.assign = function(assignment) {
   this.assignment = assignment;
 };
 
 /**
  * Sub-classes of Argument are collections of arguments, getArgs() gets access
@@ -1251,20 +1261,53 @@ Argument.merge = function(argArray, star
     }
     else {
       joined = joined.merge(arg);
     }
   }
   return joined;
 };
 
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Argument.prototype, '_summaryJson', {
+  get: function() {
+    var assignStatus = this.assignment == null ?
+            'null' :
+            this.assignment.param.name;
+    return '<' + this.prefix + ':' + this.text + ':' + this.suffix + '>' +
+        ' (a=' + assignStatus + ',' + ' t=' + this.type + ')';
+  },
+  enumerable: true
+});
+
 argument.Argument = Argument;
 
 
 /**
+ * BlankArgument is a marker that the argument wasn't typed but is there to
+ * fill a slot. Assignments begin with their arg set to a BlankArgument.
+ */
+function BlankArgument() {
+  this.text = '';
+  this.prefix = '';
+  this.suffix = '';
+}
+
+BlankArgument.prototype = Object.create(Argument.prototype);
+
+BlankArgument.prototype.type = 'BlankArgument';
+
+argument.BlankArgument = BlankArgument;
+
+
+/**
  * ScriptArgument is a marker that the argument is designed to be Javascript.
  * It also implements the special rules that spaces after the { or before the
  * } are part of the pre/suffix rather than the content, and that they are
  * never 'blank' so they can be used by Requisition._split() and not raise an
  * ERROR status due to being blank.
  */
 function ScriptArgument(text, prefix, suffix) {
   this.text = text !== undefined ? text : '';
@@ -1279,16 +1322,18 @@ function ScriptArgument(text, prefix, su
   while (this.text.charAt(this.text.length - 1) === ' ') {
     this.suffix = ' ' + this.suffix;
     this.text = this.text.slice(0, -1);
   }
 }
 
 ScriptArgument.prototype = Object.create(Argument.prototype);
 
+ScriptArgument.prototype.type = 'ScriptArgument';
+
 /**
  * Returns a new Argument like this one but with the text set to
  * <tt>replText</tt> and the end adjusted to fit.
  * @param replText Text to replace the old text value
  */
 ScriptArgument.prototype.beget = function(replText, options) {
   var prefix = this.prefix;
   var suffix = this.suffix;
@@ -1296,25 +1341,16 @@ ScriptArgument.prototype.beget = functio
   if (options && options.normalize) {
     prefix = '{ ';
     suffix = ' }';
   }
 
   return new ScriptArgument(replText, prefix, suffix);
 };
 
-/**
- * ScriptArguments are never blank due to the '{' and '}' and their special use
- * for the command argument requires them not to be blank even when there is
- * no text.
- */
-ScriptArgument.prototype.isBlank = function() {
-  return false;
-};
-
 argument.ScriptArgument = ScriptArgument;
 
 
 /**
  * Commands like 'echo' with a single string argument, and used with the
  * special format like: 'echo a b c' effectively have a number of arguments
  * merged together.
  */
@@ -1333,16 +1369,18 @@ function MergedArgument(args, start, end
   var arg = Argument.merge(this.args);
   this.text = arg.text;
   this.prefix = arg.prefix;
   this.suffix = arg.suffix;
 }
 
 MergedArgument.prototype = Object.create(Argument.prototype);
 
+MergedArgument.prototype.type = 'MergedArgument';
+
 /**
  * Keep track of which assignment we've been assigned to, and allow the
  * original args to do the same.
  */
 MergedArgument.prototype.assign = function(assignment) {
   this.args.forEach(function(arg) {
     arg.assign(assignment);
   }, this);
@@ -1379,16 +1417,18 @@ function TrueNamedArgument(name, arg) {
   this.arg = arg;
   this.text = arg ? arg.text : '--' + name;
   this.prefix = arg ? arg.prefix : ' ';
   this.suffix = arg ? arg.suffix : '';
 }
 
 TrueNamedArgument.prototype = Object.create(Argument.prototype);
 
+TrueNamedArgument.prototype.type = 'TrueNamedArgument';
+
 TrueNamedArgument.prototype.assign = function(assignment) {
   if (this.arg) {
     this.arg.assign(assignment);
   }
   this.assignment = assignment;
 };
 
 TrueNamedArgument.prototype.getArgs = function() {
@@ -1422,16 +1462,18 @@ argument.TrueNamedArgument = TrueNamedAr
 function FalseNamedArgument() {
   this.text = '';
   this.prefix = '';
   this.suffix = '';
 }
 
 FalseNamedArgument.prototype = Object.create(Argument.prototype);
 
+FalseNamedArgument.prototype.type = 'FalseNamedArgument';
+
 FalseNamedArgument.prototype.getArgs = function() {
   return [ ];
 };
 
 FalseNamedArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
@@ -1447,39 +1489,44 @@ argument.FalseNamedArgument = FalseNamed
 
 
 /**
  * A named argument is for cases where we have input in one of the following
  * formats:
  * <ul>
  * <li>--param value
  * <li>-p value
- * <li>--pa value
- * <li>-p:value
- * <li>--param=value
- * <li>etc
  * </ul>
- * The general format is:
- * /--?{unique-param-name-prefix}[ :=]{value}/
  * We model this as a normal argument but with a long prefix.
  */
 function NamedArgument(nameArg, valueArg) {
   this.nameArg = nameArg;
   this.valueArg = valueArg;
 
-  this.text = valueArg.text;
-  this.prefix = nameArg.toString() + valueArg.prefix;
-  this.suffix = valueArg.suffix;
+  if (valueArg == null) {
+    this.text = '';
+    this.prefix = nameArg.toString();
+    this.suffix = '';
+  }
+  else {
+    this.text = valueArg.text;
+    this.prefix = nameArg.toString() + valueArg.prefix;
+    this.suffix = valueArg.suffix;
+  }
 }
 
 NamedArgument.prototype = Object.create(Argument.prototype);
 
+NamedArgument.prototype.type = 'NamedArgument';
+
 NamedArgument.prototype.assign = function(assignment) {
   this.nameArg.assign(assignment);
-  this.valueArg.assign(assignment);
+  if (this.valueArg != null) {
+    this.valueArg.assign(assignment);
+  }
   this.assignment = assignment;
 };
 
 NamedArgument.prototype.getArgs = function() {
   return [ this.nameArg, this.valueArg ];
 };
 
 NamedArgument.prototype.equals = function(that) {
@@ -1508,16 +1555,18 @@ argument.NamedArgument = NamedArgument;
  * can be jointly assigned to a single array parameter
  */
 function ArrayArgument() {
   this.args = [];
 }
 
 ArrayArgument.prototype = Object.create(Argument.prototype);
 
+ArrayArgument.prototype.type = 'ArrayArgument';
+
 ArrayArgument.prototype.addArgument = function(arg) {
   this.args.push(arg);
 };
 
 ArrayArgument.prototype.addArguments = function(args) {
   Array.prototype.push.apply(this.args, args);
 };
 
@@ -1540,17 +1589,17 @@ ArrayArgument.prototype.getArgs = functi
 ArrayArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
   if (that == null) {
     return false;
   }
 
-  if (!(that instanceof ArrayArgument)) {
+  if (!(that.type === 'ArrayArgument')) {
     return false;
   }
 
   if (this.args.length !== that.args.length) {
     return false;
   }
 
   for (var i = 0; i < this.args.length; i++) {
@@ -1571,16 +1620,318 @@ ArrayArgument.prototype.toString = funct
   }, this).join(',') + '}';
 };
 
 argument.ArrayArgument = ArrayArgument;
 
 
 });
 /*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
+
+
+var l10n = require('gcli/l10n');
+var types = require('gcli/types');
+var Type = require('gcli/types').Type;
+var Status = require('gcli/types').Status;
+var Conversion = require('gcli/types').Conversion;
+var Speller = require('gcli/types/spell').Speller;
+
+
+/**
+ * Registration and de-registration.
+ */
+exports.startup = function() {
+  types.registerType(SelectionType);
+};
+
+exports.shutdown = function() {
+  types.unregisterType(SelectionType);
+};
+
+
+/**
+ * A selection allows the user to pick a value from known set of options.
+ * An option is made up of a name (which is what the user types) and a value
+ * (which is passed to exec)
+ * @param typeSpec Object containing properties that describe how this
+ * selection functions. Properties include:
+ * - lookup: An array of objects, one for each option, which contain name and
+ *   value properties. lookup can be a function which returns this array
+ * - data: An array of strings - alternative to 'lookup' where the valid values
+ *   are strings. i.e. there is no mapping between what is typed and the value
+ *   that is used by the program
+ * - stringifyProperty: Conversion from value to string is generally a process
+ *   of looking through all the valid options for a matching value, and using
+ *   the associated name. However the name maybe available directly from the
+ *   value using a property lookup. Setting 'stringifyProperty' allows
+ *   SelectionType to take this shortcut.
+ * - cacheable : If lookup is a function, then we normally assume that
+ *   the values fetched can change. Setting 'cacheable' enables internal
+ *   caching.
+ */
+function SelectionType(typeSpec) {
+  if (typeSpec) {
+    Object.keys(typeSpec).forEach(function(key) {
+      this[key] = typeSpec[key];
+    }, this);
+  }
+}
+
+SelectionType.prototype = Object.create(Type.prototype);
+
+SelectionType.prototype.stringify = function(value) {
+  if (value == null) {
+    return '';
+  }
+  if (this.stringifyProperty != null) {
+    return value[this.stringifyProperty];
+  }
+  var name = null;
+  var lookup = this.getLookup();
+  lookup.some(function(item) {
+    if (item.value === value) {
+      name = item.name;
+      return true;
+    }
+    return false;
+  }, this);
+  return name;
+};
+
+/**
+ * If typeSpec contained cacheable:true then calls to parse() work on cached
+ * data. clearCache() enables the cache to be cleared.
+ */
+SelectionType.prototype.clearCache = function() {
+  delete this._cachedLookup;
+};
+
+/**
+ * There are several ways to get selection data. This unifies them into one
+ * single function.
+ * @return An array of objects with name and value properties.
+ */
+SelectionType.prototype.getLookup = function() {
+  if (this._cachedLookup) {
+    return this._cachedLookup;
+  }
+
+  if (this.lookup) {
+    if (typeof this.lookup === 'function') {
+      if (this.cacheable) {
+        this._cachedLookup = this.lookup();
+        return this._cachedLookup;
+      }
+      return this.lookup();
+    }
+    return this.lookup;
+  }
+
+  if (Array.isArray(this.data)) {
+    this.lookup = this._dataToLookup(this.data);
+    return this.lookup;
+  }
+
+  if (typeof(this.data) === 'function') {
+    return this._dataToLookup(this.data());
+  }
+
+  throw new Error('SelectionType has no data');
+};
+
+/**
+ * Selection can be provided with either a lookup object (in the 'lookup'
+ * property) or an array of strings (in the 'data' property). Internally we
+ * always use lookup, so we need a way to convert a 'data' array to a lookup.
+ */
+SelectionType.prototype._dataToLookup = function(data) {
+  return data.map(function(option) {
+    return { name: option, value: option };
+  }, this);
+};
+
+/**
+ * Return a list of possible completions for the given arg.
+ * @param arg The initial input to match
+ * @return A trimmed array of string:value pairs
+ */
+SelectionType.prototype._findPredictions = function(arg) {
+  var predictions = [];
+  var lookup = this.getLookup();
+  var i, option;
+  var maxPredictions = Conversion.maxPredictions;
+
+  // If the arg has a suffix then we're kind of 'done'. Only an exact match
+  // will do.
+  if (arg.suffix.length > 0) {
+    for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+      option = lookup[i];
+      if (option.name === arg.text) {
+        this._addToPredictions(predictions, option, arg);
+      }
+    }
+
+    return predictions;
+  }
+
+  // Start with prefix matching
+  for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+    option = lookup[i];
+    if (option.name.indexOf(arg.text) === 0) {
+      this._addToPredictions(predictions, option, arg);
+    }
+  }
+
+  // Try infix matching if we get less half max matched
+  if (predictions.length < (maxPredictions / 2)) {
+    for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+      option = lookup[i];
+      if (option.name.indexOf(arg.text) !== -1) {
+        if (predictions.indexOf(option) === -1) {
+          this._addToPredictions(predictions, option, arg);
+        }
+      }
+    }
+  }
+
+  // Try fuzzy matching if we don't get a prefix match
+  if (false && predictions.length === 0) {
+    var speller = new Speller();
+    var names = lookup.map(function(opt) {
+      return opt.name;
+    });
+    speller.train(names);
+    var corrected = speller.correct(arg.text);
+    if (corrected) {
+      lookup.forEach(function(opt) {
+        if (opt.name === corrected) {
+          predictions.push(opt);
+        }
+      }, this);
+    }
+  }
+
+  return predictions;
+};
+
+/**
+ * Add an option to our list of predicted options.
+ * We abstract out this portion of _findPredictions() because CommandType needs
+ * to make an extra check before actually adding which SelectionType does not
+ * need to make.
+ */
+SelectionType.prototype._addToPredictions = function(predictions, option, arg) {
+  predictions.push(option);
+};
+
+SelectionType.prototype.parse = function(arg) {
+  var predictions = this._findPredictions(arg);
+
+  if (predictions.length === 0) {
+    var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
+    return new Conversion(undefined, arg, Status.ERROR, msg, predictions);
+  }
+
+  // This is something of a hack it basically allows us to tell the
+  // setting type to forget its last setting hack.
+  if (this.noMatch) {
+    this.noMatch();
+  }
+
+  var value = predictions[0].value;
+
+  if (predictions[0].name === arg.text) {
+    return new Conversion(value, arg, Status.VALID, '', predictions);
+  }
+
+  return new Conversion(undefined, arg, Status.INCOMPLETE, '', predictions);
+};
+
+/**
+ * For selections, up is down and black is white. It's like this, given a list
+ * [ a, b, c, d ], it's natural to think that it starts at the top and that
+ * going up the list, moves towards 'a'. However 'a' has the lowest index, so
+ * for SelectionType, up is down and down is up.
+ * Sorry.
+ */
+SelectionType.prototype.decrement = function(value) {
+  var lookup = this.getLookup();
+  var index = this._findValue(lookup, value);
+  if (index === -1) {
+    index = 0;
+  }
+  index++;
+  if (index >= lookup.length) {
+    index = 0;
+  }
+  return lookup[index].value;
+};
+
+/**
+ * See note on SelectionType.decrement()
+ */
+SelectionType.prototype.increment = function(value) {
+  var lookup = this.getLookup();
+  var index = this._findValue(lookup, value);
+  if (index === -1) {
+    // For an increment operation when there is nothing to start from, we
+    // want to start from the top, i.e. index 0, so the value before we
+    // 'increment' (see note above) must be 1.
+    index = 1;
+  }
+  index--;
+  if (index < 0) {
+    index = lookup.length - 1;
+  }
+  return lookup[index].value;
+};
+
+/**
+ * Walk through an array of { name:.., value:... } objects looking for a
+ * matching value (using strict equality), returning the matched index (or -1
+ * if not found).
+ * @param lookup Array of objects with name/value properties to search through
+ * @param value The value to search for
+ * @return The index at which the match was found, or -1 if no match was found
+ */
+SelectionType.prototype._findValue = function(lookup, value) {
+  var index = -1;
+  for (var i = 0; i < lookup.length; i++) {
+    var pair = lookup[i];
+    if (pair.value === value) {
+      index = i;
+      break;
+    }
+  }
+  return index;
+};
+
+SelectionType.prototype.name = 'selection';
+
+exports.SelectionType = SelectionType;
+
+
+});
+/*
  * 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
@@ -1737,339 +2088,74 @@ exports.Speller = Speller;
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
-
-
-var l10n = require('gcli/l10n');
-var types = require('gcli/types');
-var Type = require('gcli/types').Type;
-var Status = require('gcli/types').Status;
-var Conversion = require('gcli/types').Conversion;
-var Speller = require('gcli/types/spell').Speller;
-
-
-/**
- * Registration and de-registration.
- */
-exports.startup = function() {
-  types.registerType(SelectionType);
-};
-
-exports.shutdown = function() {
-  types.unregisterType(SelectionType);
-};
-
-
-/**
- * A selection allows the user to pick a value from known set of options.
- * An option is made up of a name (which is what the user types) and a value
- * (which is passed to exec)
- * @param typeSpec Object containing properties that describe how this
- * selection functions. Properties include:
- * - lookup: An array of objects, one for each option, which contain name and
- *   value properties. lookup can be a function which returns this array
- * - data: An array of strings - alternative to 'lookup' where the valid values
- *   are strings. i.e. there is no mapping between what is typed and the value
- *   that is used by the program
- * - stringifyProperty: Conversion from value to string is generally a process
- *   of looking through all the valid options for a matching value, and using
- *   the associated name. However the name maybe available directly from the
- *   value using a property lookup. Setting 'stringifyProperty' allows
- *   SelectionType to take this shortcut.
- * - cacheable : If lookup is a function, then we normally assume that
- *   the values fetched can change. Setting 'cacheable' enables internal
- *   caching.
- */
-function SelectionType(typeSpec) {
-  if (typeSpec) {
-    Object.keys(typeSpec).forEach(function(key) {
-      this[key] = typeSpec[key];
-    }, this);
-  }
-}
-
-SelectionType.prototype = Object.create(Type.prototype);
-
-SelectionType.prototype.maxPredictions = 10;
-
-SelectionType.prototype.stringify = function(value) {
-  if (value == null) {
-    return '';
-  }
-  if (this.stringifyProperty != null) {
-    return value[this.stringifyProperty];
-  }
-  var name = null;
-  var lookup = this.getLookup();
-  lookup.some(function(item) {
-    if (item.value === value) {
-      name = item.name;
-      return true;
-    }
-    return false;
-  }, this);
-  return name;
-};
-
-/**
- * If typeSpec contained cacheable:true then calls to parse() work on cached
- * data. clearCache() enables the cache to be cleared.
- */
-SelectionType.prototype.clearCache = function() {
-  delete this._cachedLookup;
-};
-
-/**
- * There are several ways to get selection data. This unifies them into one
- * single function.
- * @return An array of objects with name and value properties.
- */
-SelectionType.prototype.getLookup = function() {
-  if (this._cachedLookup) {
-    return this._cachedLookup;
-  }
-
-  if (this.lookup) {
-    if (typeof this.lookup === 'function') {
-      if (this.cacheable) {
-        this._cachedLookup = this.lookup();
-        return this._cachedLookup;
-      }
-      return this.lookup();
-    }
-    return this.lookup;
-  }
-
-  if (Array.isArray(this.data)) {
-    this.lookup = this._dataToLookup(this.data);
-    return this.lookup;
-  }
-
-  if (typeof(this.data) === 'function') {
-    return this._dataToLookup(this.data());
-  }
-
-  throw new Error('SelectionType has no data');
-};
-
-/**
- * Selection can be provided with either a lookup object (in the 'lookup'
- * property) or an array of strings (in the 'data' property). Internally we
- * always use lookup, so we need a way to convert a 'data' array to a lookup.
- */
-SelectionType.prototype._dataToLookup = function(data) {
-  return data.map(function(option) {
-    return { name: option, value: option };
-  }, this);
-};
-
-/**
- * Return a list of possible completions for the given arg.
- * @param arg The initial input to match
- * @return A trimmed array of string:value pairs
- */
-SelectionType.prototype._findPredictions = function(arg) {
-  var predictions = [];
-  var lookup = this.getLookup();
-  var i, option;
-
-  // If the arg has a suffix then we're kind of 'done'. Only an exact match
-  // will do.
-  if (arg.suffix.length > 0) {
-    for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-      option = lookup[i];
-      if (option.name === arg.text) {
-        this._addToPredictions(predictions, option, arg);
-      }
-    }
-
-    return predictions;
-  }
-
-  // Start with prefix matching
-  for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-    option = lookup[i];
-    if (option.name.indexOf(arg.text) === 0) {
-      this._addToPredictions(predictions, option, arg);
-    }
-  }
-
-  // Try infix matching if we get less half max matched
-  if (predictions.length < (this.maxPredictions / 2)) {
-    for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-      option = lookup[i];
-      if (option.name.indexOf(arg.text) !== -1) {
-        if (predictions.indexOf(option) === -1) {
-          this._addToPredictions(predictions, option, arg);
-        }
-      }
-    }
-  }
-
-  // Try fuzzy matching if we don't get a prefix match
-  if (false && predictions.length === 0) {
-    var speller = new Speller();
-    var names = lookup.map(function(opt) {
-      return opt.name;
-    });
-    speller.train(names);
-    var corrected = speller.correct(arg.text);
-    if (corrected) {
-      lookup.forEach(function(opt) {
-        if (opt.name === corrected) {
-          predictions.push(opt);
-        }
-      }, this);
-    }
-  }
-
-  return predictions;
-};
-
-/**
- * Add an option to our list of predicted options.
- * We abstract out this portion of _findPredictions() because CommandType needs
- * to make an extra check before actually adding which SelectionType does not
- * need to make.
- */
-SelectionType.prototype._addToPredictions = function(predictions, option, arg) {
-  predictions.push(option);
-};
-
-SelectionType.prototype.parse = function(arg) {
-  var predictions = this._findPredictions(arg);
-
-  if (predictions.length === 0) {
-    var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
-    return new Conversion(undefined, arg, Status.ERROR, msg, predictions);
-  }
-
-  // This is something of a hack it basically allows us to tell the
-  // setting type to forget its last setting hack.
-  if (this.noMatch) {
-    this.noMatch();
-  }
-
-  var value = predictions[0].value;
-
-  if (predictions[0].name === arg.text) {
-    return new Conversion(value, arg, Status.VALID, '', predictions);
-  }
-
-  return new Conversion(undefined, arg, Status.INCOMPLETE, '', predictions);
-};
-
-/**
- * For selections, up is down and black is white. It's like this, given a list
- * [ a, b, c, d ], it's natural to think that it starts at the top and that
- * going up the list, moves towards 'a'. However 'a' has the lowest index, so
- * for SelectionType, up is down and down is up.
- * Sorry.
- */
-SelectionType.prototype.decrement = function(value) {
-  var lookup = this.getLookup();
-  var index = this._findValue(lookup, value);
-  if (index === -1) {
-    index = 0;
-  }
-  index++;
-  if (index >= lookup.length) {
-    index = 0;
-  }
-  return lookup[index].value;
-};
-
-/**
- * See note on SelectionType.decrement()
- */
-SelectionType.prototype.increment = function(value) {
-  var lookup = this.getLookup();
-  var index = this._findValue(lookup, value);
-  if (index === -1) {
-    // For an increment operation when there is nothing to start from, we
-    // want to start from the top, i.e. index 0, so the value before we
-    // 'increment' (see note above) must be 1.
-    index = 1;
-  }
-  index--;
-  if (index < 0) {
-    index = lookup.length - 1;
-  }
-  return lookup[index].value;
-};
-
-/**
- * Walk through an array of { name:.., value:... } objects looking for a
- * matching value (using strict equality), returning the matched index (or -1
- * if not found).
- * @param lookup Array of objects with name/value properties to search through
- * @param value The value to search for
- * @return The index at which the match was found, or -1 if no match was found
- */
-SelectionType.prototype._findValue = function(lookup, value) {
-  var index = -1;
-  for (var i = 0; i < lookup.length; i++) {
-    var pair = lookup[i];
-    if (pair.value === value) {
-      index = i;
-      break;
-    }
-  }
-  return index;
-};
-
-SelectionType.prototype.name = 'selection';
-
-exports.SelectionType = SelectionType;
-
-
-});
-/*
- * Copyright 2012, Mozilla Foundation and contributors
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 define('gcli/types/command', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/l10n', 'gcli/types', 'gcli/types/selection'], function(require, exports, module) {
 
 
 var canon = require('gcli/canon');
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var SelectionType = require('gcli/types/selection').SelectionType;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(CommandType);
+  types.registerType(ParamType);
 };
 
 exports.shutdown = function() {
   types.unregisterType(CommandType);
+  types.unregisterType(ParamType);
+};
+
+
+/**
+ * Select from the available commands.
+ * This is very similar to a SelectionType, however the level of hackery in
+ * SelectionType to make it handle Commands correctly was to high, so we
+ * simplified.
+ * If you are making changes to this code, you should check there too.
+ */
+function ParamType(typeSpec) {
+  this.requisition = typeSpec.requisition;
+  this.isIncompleteName = typeSpec.isIncompleteName;
+  this.stringifyProperty = 'name';
+}
+
+ParamType.prototype = Object.create(SelectionType.prototype);
+
+ParamType.prototype.name = 'param';
+
+ParamType.prototype.lookup = function() {
+  var displayedParams = [];
+  var command = this.requisition.commandAssignment.value;
+  command.params.forEach(function(param) {
+    var arg = this.requisition.getAssignment(param.name).arg;
+    if (!param.isPositionalAllowed && arg.type === "BlankArgument") {
+      displayedParams.push({ name: '--' + param.name, value: param });
+    }
+  }, this);
+  return displayedParams;
+};
+
+ParamType.prototype.parse = function(arg) {
+  return this.isIncompleteName ?
+      SelectionType.prototype.parse.call(this, arg) :
+      new Conversion(undefined, arg, Status.ERROR, l10n.lookup('cliUnusedArg'));
 };
 
 
 /**
  * Select from the available commands.
  * This is very similar to a SelectionType, however the level of hackery in
  * SelectionType to make it handle Commands correctly was to high, so we
  * simplified.
@@ -2155,26 +2241,27 @@ CommandType.prototype.parse = function(a
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic'], function(require, exports, module) {
+define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic', 'gcli/types/selection'], function(require, exports, module) {
 var canon = exports;
 
 
 var util = require('gcli/util');
 var l10n = require('gcli/l10n');
 
 var types = require('gcli/types');
 var Status = require('gcli/types').Status;
 var BooleanType = require('gcli/types/basic').BooleanType;
+var SelectionType = require('gcli/types/selection').SelectionType;
 
 /**
  * Implement the localization algorithm for any documentation objects (i.e.
  * description and manual) in a command.
  * @param data The data assigned to a description or manual property
  * @param onUndefined If data == null, should we return the data untouched or
  * lookup a 'we don't know' key in it's place.
  */
@@ -2210,16 +2297,17 @@ function lookup(data, onUndefined) {
             'locales=' + JSON.stringify(locales) + ', ' +
             'description=' + JSON.stringify(data));
     return '(No description)';
   }
 
   return l10n.lookup(onUndefined);
 }
 
+
 /**
  * The command object is mostly just setup around a commandSpec (as passed to
  * #addCommand()).
  */
 function Command(commandSpec) {
   Object.keys(commandSpec).forEach(function(key) {
     this[key] = commandSpec[key];
   }, this);
@@ -2336,21 +2424,28 @@ function Parameter(paramSpec, command, g
       }
     }
     catch (ex) {
       console.error('In ' + this.command.name + '/' + this.name +
         ': ' + ex);
     }
   }
 
-  // Some typed (boolean, array) have a non 'undefined' blank value. Give the
+  // Some types (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;
   }
+
+  // All parameters that can only be set via a named parameter must have a
+  // non-undefined default value
+  if (!this.isPositionalAllowed && this.defaultValue === undefined) {
+    console.error('In ' + this.command.name + '/' + this.name +
+            ': Missing defaultValue for optional parameter.');
+  }
 }
 
 /**
  * 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) {
@@ -4853,30 +4948,32 @@ define('gcli/ui/domtemplate', ['require'
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
+define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/l10n', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
 var view = require('gcli/ui/view');
+var l10n = require('gcli/l10n');
 
 var canon = require('gcli/canon');
 var Promise = require('gcli/promise').Promise;
 
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 var ArrayType = require('gcli/types/basic').ArrayType;
 var StringType = require('gcli/types/basic').StringType;
 var BooleanType = require('gcli/types/basic').BooleanType;
+var NumberType = require('gcli/types/basic').NumberType;
 
 var Argument = require('gcli/argument').Argument;
 var ArrayArgument = require('gcli/argument').ArrayArgument;
 var NamedArgument = require('gcli/argument').NamedArgument;
 var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
 var MergedArgument = require('gcli/argument').MergedArgument;
 var ScriptArgument = require('gcli/argument').ScriptArgument;
 
@@ -4911,39 +5008,32 @@ exports.shutdown = function() {
  * <li>onAssignmentChange: Either the value or the text has changed. It is
  * likely that any UI component displaying this argument will need to be
  * updated.
  * The event object looks like:
  * <tt>{ assignment: ..., conversion: ..., oldConversion: ... }</tt>
  * @constructor
  */
 function Assignment(param, paramIndex) {
+  // The parameter that we are assigning to
   this.param = param;
+
+  this.conversion = undefined;
+
+  // The index of this parameter in the parent Requisition. paramIndex === -1
+  // is the command assignment although this should not be relied upon, it is
+  // better to test param instanceof CommandAssignment
   this.paramIndex = paramIndex;
+
   this.onAssignmentChange = util.createEvent('Assignment.onAssignmentChange');
 
   this.setBlank();
 }
 
 /**
- * The parameter that we are assigning to
- * @readonly
- */
-Assignment.prototype.param = undefined;
-
-Assignment.prototype.conversion = undefined;
-
-/**
- * The index of this parameter in the parent Requisition. paramIndex === -1
- * is the command assignment although this should not be relied upon, it is
- * better to test param instanceof CommandAssignment
- */
-Assignment.prototype.paramIndex = undefined;
-
-/**
  * Easy accessor for conversion.arg.
  * This is a read-only property because writes to arg should be done through
  * the 'conversion' property.
  */
 Object.defineProperty(Assignment.prototype, 'arg', {
   get: function() {
     return this.conversion.arg;
   },
@@ -5016,17 +5106,17 @@ Assignment.prototype.setBlank = function
  * Argument of '' if needed.
  */
 Assignment.prototype.ensureVisibleArgument = function() {
   // It isn't clear if we should be sending events from this method.
   // It should only be called when structural changes are happening in which
   // case we're going to ignore the event anyway. But on the other hand
   // perhaps this function shouldn't need to know how it is used, and should
   // do the inefficient thing.
-  if (!this.conversion.arg.isBlank()) {
+  if (this.conversion.arg.type !== 'BlankArgument') {
     return false;
   }
 
   var arg = this.conversion.arg.beget('', {
     prefixSpace: this.param instanceof CommandAssignment
   });
   this.conversion = this.param.type.parse(arg);
   this.conversion.assign(this);
@@ -5038,23 +5128,23 @@ Assignment.prototype.ensureVisibleArgume
  * Work out what the status of the current conversion is which involves looking
  * not only at the conversion, but also checking if data has been provided
  * where it should.
  * @param arg For assignments with multiple args (e.g. array assignments) we
  * can narrow the search for status to a single argument.
  */
 Assignment.prototype.getStatus = function(arg) {
   if (this.param.isDataRequired && !this.conversion.isDataProvided()) {
-    return Status.ERROR;
+    return Status.INCOMPLETE;
   }
 
   // Selection/Boolean types with a defined range of values will say that
   // '' is INCOMPLETE, but the parameter may be optional, so we don't ask
   // if the user doesn't need to enter something and hasn't done so.
-  if (!this.param.isDataRequired && this.arg.isBlank()) {
+  if (!this.param.isDataRequired && this.arg.type === 'BlankArgument') {
     return Status.VALID;
   }
 
   return this.conversion.getStatus(arg);
 };
 
 /**
  * Replace the current value with the lower value if such a concept exists.
@@ -5084,16 +5174,36 @@ Assignment.prototype.increment = functio
 
 /**
  * Helper when we're rebuilding command lines.
  */
 Assignment.prototype.toString = function() {
   return this.conversion.toString();
 };
 
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Assignment.prototype, '_summaryJson', {
+  get: function() {
+    return {
+      param: this.param.name + '/' + this.param.type.name,
+      defaultValue: this.param.defaultValue,
+      arg: this.conversion.arg._summaryJson,
+      value: this.value,
+      message: this.getMessage(),
+      status: this.getStatus().toString(),
+      predictionCount: this.getPredictions().length
+    };
+  },
+  enumerable: true
+});
+
 exports.Assignment = Assignment;
 
 
 /**
  * How to dynamically execute JavaScript code
  */
 var customEval = eval;
 
@@ -5177,41 +5287,37 @@ CommandAssignment.prototype.getStatus = 
 };
 
 exports.CommandAssignment = CommandAssignment;
 
 
 /**
  * Special assignment used when ignoring parameters that don't have a home
  */
-function UnassignedAssignment() {
+function UnassignedAssignment(requisition, arg, isIncompleteName) {
   this.param = new canon.Parameter({
     name: '__unassigned',
-    type: 'string'
+    description: l10n.lookup('cliOptions'),
+    type: {
+      name: 'param',
+      requisition: requisition,
+      isIncompleteName: isIncompleteName
+    },
   });
   this.paramIndex = -1;
   this.onAssignmentChange = util.createEvent('UnassignedAssignment.onAssignmentChange');
 
-  this.setBlank();
+  this.conversion = this.param.type.parse(arg);
+  this.conversion.assign(this);
 }
 
 UnassignedAssignment.prototype = Object.create(Assignment.prototype);
 
 UnassignedAssignment.prototype.getStatus = function(arg) {
-  return Status.ERROR;
-};
-
-UnassignedAssignment.prototype.setUnassigned = function(args) {
-  if (!args || args.length === 0) {
-    this.setBlank();
-  }
-  else {
-    var conversion = this.param.type.parse(new MergedArgument(args));
-    this.setConversion(conversion);
-  }
+  return this.conversion.getStatus();
 };
 
 
 /**
  * A Requisition collects the information needed to execute a command.
  *
  * (For a definition of the term, see http://en.wikipedia.org/wiki/Requisition)
  * This term is used because carries the notion of a work-flow, or process to
@@ -5263,17 +5369,17 @@ function Requisition(environment, doc) {
 
   // The count of assignments. Excludes the commandAssignment
   this.assignmentCount = 0;
 
   // Used to store cli arguments in the order entered on the cli
   this._args = [];
 
   // Used to store cli arguments that were not assigned to parameters
-  this._unassigned = new UnassignedAssignment();
+  this._unassigned = [];
 
   // Temporarily set this to true to prevent _assignmentChanged resetting
   // argument positions
   this._structuralChangeInProgress = false;
 
   this.commandAssignment.onAssignmentChange.add(this._commandAssignmentChanged, this);
   this.commandAssignment.onAssignmentChange.add(this._assignmentChanged, this);
 
@@ -5426,17 +5532,17 @@ Requisition.prototype.cloneAssignments =
  * The overall status is the most severe status.
  * There is no such thing as an INCOMPLETE overall status because the
  * definition of INCOMPLETE takes into account the cursor position to say 'this
  * isn't quite ERROR because the user can fix it by typing', however overall,
  * this is still an error status.
  */
 Requisition.prototype.getStatus = function() {
   var status = Status.VALID;
-  if (!this._unassigned.arg.isBlank()) {
+  if (this._unassigned.length !== 0) {
     return Status.ERROR;
   }
   this.getAssignments(true).forEach(function(assignment) {
     var assignStatus = assignment.getStatus();
     if (assignStatus > status) {
       status = assignStatus;
     }
   }, this);
@@ -5473,16 +5579,38 @@ Requisition.prototype.getAssignments = f
   }
   Object.keys(this._assignments).forEach(function(name) {
     assignments.push(this.getAssignment(name));
   }, this);
   return assignments;
 };
 
 /**
+ * Alter the given assignment using the given arg. This function is better than
+ * calling assignment.setConversion(assignment.param.type.parse(arg)) because
+ * it adjusts the args in this requisition to keep things up to date
+ */
+Requisition.prototype.setAssignment = function(assignment, arg) {
+  var originalArg = assignment.arg;
+  var conversion = assignment.param.type.parse(arg);
+  assignment.setConversion(conversion);
+
+  // If this argument isn't assigned to anything (i.e. it was created by
+  // assignment.setBlank) we need to add it into the _args array so
+  // requisition.toString can make sense
+  if (originalArg.type === 'BlankArgument') {
+    this._args.push(arg);
+  }
+  else {
+    var index = this._args.indexOf(originalArg);
+    this._args[index] = conversion.arg;
+  }
+};
+
+/**
  * Reset all the assignments to their default values
  */
 Requisition.prototype.setBlankArguments = function() {
   this.getAssignments().forEach(function(assignment) {
     assignment.setBlank();
   }, this);
 };
 
@@ -5505,25 +5633,17 @@ Requisition.prototype.complete = functio
   var predictions = assignment.conversion.getPredictions();
   if (predictions.length > 0) {
     this.onTextChange.holdFire();
 
     var prediction = assignment.conversion.getPredictionAt(predictionChoice);
 
     // Mutate this argument to hold the completion
     var arg = assignment.arg.beget(prediction.name);
-    var conversion = assignment.param.type.parse(arg);
-    assignment.setConversion(conversion);
-
-    // If this argument isn't assigned to anything (i.e. it was created by
-    // assignment.setBlank) we need to add it into the _args array so
-    // requisition.toString can make sense
-    if (this._args.indexOf(arg) === -1) {
-      this._args.push(arg);
-    }
+    this.setAssignment(assignment, arg);
 
     if (prediction.incomplete) {
       // This is the easy case - the prediction is incomplete - no need to add
       // any spaces
       return;
     }
 
     // The prediction reported !incomplete, which means it's complete so we
@@ -5537,46 +5657,40 @@ Requisition.prototype.complete = functio
     // Also if there is already a space in those positions, don't add another
 
     var nextIndex = assignment.paramIndex + 1;
     var nextAssignment = this.getAssignment(nextIndex);
     if (nextAssignment) {
       // Add a space onto the next argument (if there isn't one there already)
       var nextArg = nextAssignment.conversion.arg;
       if (nextArg.prefix.charAt(0) !== ' ') {
-        nextArg.prefix = ' ' + nextArg.prefix;
-        var nextConversion = nextAssignment.param.type.parse(nextArg);
-        nextAssignment.setConversion(nextConversion);
-
-        // If this argument isn't assigned to anything (i.e. it was created by
-        // assignment.setBlank) we need to add it into the _args array so
-        // requisition.toString can make sense
-        if (this._args.indexOf(nextArg) === -1) {
-          this._args.push(nextArg);
-        }
+        nextArg = new Argument(nextArg.text, ' ' + nextArg.prefix, nextArg.suffix);
+        this.setAssignment(nextAssignment, nextArg);
       }
     }
     else {
       // There is no next argument, this must be the last assignment, so just
       // add the space to the prefix of this argument
-      var conversion = assignment.conversion;
-      var arg = conversion.arg;
+      arg = assignment.conversion.arg;
       if (arg.suffix.charAt(arg.suffix.length - 1) !== ' ') {
-        arg.suffix = arg.suffix + ' ';
-
-        // It's tempting to think - "we're calling setConversion twice in one
+        // It's tempting to think - "we're calling setAssignment twice in one
         // call to complete, the first time to complete the text, the second
         // to add a space, why not save the event cascade and do it once"
         // However if we're setting up the command, the number of parameters
         // changes as a result, so our call to getAssignment(nextIndex) will
         // produce the wrong answer
-        assignment.setConversion(conversion);
+        arg = new Argument(arg.text, arg.prefix, arg.suffix + ' ');
+        this.setAssignment(assignment, arg);
       }
     }
 
+    if (assignment instanceof UnassignedAssignment) {
+      this.update(this.toString());
+    }
+
     this.onTextChange();
     this.onTextChange.resumeFire();
   }
 };
 
 /**
  * Extract a canonical version of the input
  */
@@ -5672,19 +5786,26 @@ Requisition.prototype.toString = functio
  * the end don't need a space prefix.
  * While this is quite a niche function, it has 2 benefits:
  * - it's more correct because we can distinguish between final whitespace that
  *   is part of an unclosed string, and parameter separating whitespace.
  * - also it's faster than toString() the whole thing and checking the end char
  * @return true iff the last character is interpreted as parameter separating
  * whitespace
  */
-Requisition.prototype.typedEndsWithWhitespace = function() {
+Requisition.prototype.typedEndsWithSeparator = function() {
+  // This is not as easy as doing (this.toString().slice(-1) === ' ')
+  // See the doc comments above; We're checking for separators, not spaces
   if (this._args) {
-    return this._args.slice(-1)[0].suffix.slice(-1) === ' ';
+    var lastArg = this._args.slice(-1)[0];
+    if (lastArg.suffix.slice(-1) === ' ') {
+      return true;
+    }
+    return lastArg.text === '' && lastArg.suffix === ''
+        && lastArg.prefix.slice(-1) === ' ';
   }
 
   return this.toCanonicalString().slice(-1) === ' ';
 };
 
 /**
  * Return an array of Status scores so we can create a marked up
  * version of the command line input.
@@ -5705,18 +5826,26 @@ Requisition.prototype.getInputStatusMark
   for (var i = 0; i < argTraces.length; i++) {
     var argTrace = argTraces[i];
     var arg = argTrace.arg;
     var status = Status.VALID;
     if (argTrace.part === 'text') {
       status = arg.assignment.getStatus(arg);
       // Promote INCOMPLETE to ERROR  ...
       if (status === Status.INCOMPLETE) {
-        // If the cursor is not in a position to be able to complete it
-        if (arg !== cTrace.arg || cTrace.part !== 'text') {
+        // If the cursor is in the prefix or suffix of an argument then we
+        // don't consider it in the argument for the purposes of preventing
+        // the escalation to ERROR. However if this is a NamedArgument, then we
+        // allow the suffix (as space between 2 parts of the argument) to be in.
+        // We use arg.assignment.arg not arg because we're looking at the arg
+        // that got put into the assignment not as returned by tokenize()
+        var isNamed = (cTrace.arg.assignment.arg.type === 'NamedArgument');
+        var isInside = cTrace.part === 'text' ||
+                        (isNamed && cTrace.part === 'suffix');
+        if (arg.assignment !== cTrace.arg.assignment || !isInside) {
           // And if we're not in the command
           if (!(arg.assignment instanceof CommandAssignment)) {
             status = Status.ERROR;
           }
         }
       }
     }
 
@@ -5764,40 +5893,43 @@ Requisition.prototype.getAssignmentAt = 
     // prefix and text are clearly part of the argument
     for (j = 0; j < arg.prefix.length; j++) {
       assignForPos.push(assignment);
     }
     for (j = 0; j < arg.text.length; j++) {
       assignForPos.push(assignment);
     }
 
-    // suffix looks forwards
-    if (this._args.length > i + 1) {
+    // suffix is part of the argument only if this is a named parameter,
+    // otherwise it looks forwards
+    if (arg.assignment.arg.type === 'NamedArgument') {
+      // leave the argument as it is
+    }
+    else if (this._args.length > i + 1) {
       // first to the next argument
       assignment = this._args[i + 1].assignment;
     }
-    else if (assignment &&
-        assignment.paramIndex + 1 < this.assignmentCount) {
+    else if (assignment && assignment.paramIndex + 1 < this.assignmentCount) {
       // then to the next assignment
       assignment = this.getAssignment(assignment.paramIndex + 1);
     }
 
     for (j = 0; j < arg.suffix.length; j++) {
       assignForPos.push(assignment);
     }
   }
 
   // Possible shortcut, we don't really need to go through all the args
   // to work out the solution to this
 
   var reply = assignForPos[cursor - 1];
 
   if (!reply) {
     throw new Error('Missing assignment.' +
-      ' cursor=' + cursor + ' text.length=' + this.toString().length);
+        ' cursor=' + cursor + ' text=' + this.toString());
   }
 
   return reply;
 };
 
 /**
  * Entry point for keyboard accelerators or anything else that wants to execute
  * a command. There are 3 ways to call <tt>exec()</tt>:
@@ -5893,36 +6025,56 @@ Requisition.prototype.exec = function(in
 
   this.update('');
   return output;
 };
 
 /**
  * Called by the UI when ever the user interacts with a command line input
  * @param typed The contents of the input field
- * <p>The general sequence is:
- * <ul>
- * <li>_tokenize(): convert _typed into _parts
- * <li>_split(): convert _parts into _command and _unparsedArgs
- * <li>_assign(): convert _unparsedArgs into requisition
- * </ul>
  */
 Requisition.prototype.update = function(typed) {
   this._structuralChangeInProgress = true;
 
   this._args = this._tokenize(typed);
   var args = this._args.slice(0); // i.e. clone
   this._split(args);
   this._assign(args);
 
   this._structuralChangeInProgress = false;
   this.onTextChange();
 };
 
 /**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Requisition.prototype, '_summaryJson', {
+  get: function() {
+    var summary = {
+      $args: this._args.map(function(arg) {
+        return arg._summaryJson;
+      }),
+      _command: this.commandAssignment._summaryJson,
+      _unassigned: this._unassigned.forEach(function(assignment) {
+        return assignment._summaryJson;
+      })
+    };
+
+    Object.keys(this._assignments).forEach(function(name) {
+      summary[name] = this.getAssignment(name)._summaryJson;
+    }.bind(this));
+
+    return summary;
+  },
+  enumerable: true
+});
+
+/**
  * Requisition._tokenize() is a state machine. These are the states.
  */
 var In = {
   /**
    * The last character was ' '.
    * Typing a ' ' character will not change the mode
    * Typing one of '"{ will change mode to SINGLE_Q, DOUBLE_Q or SCRIPT.
    * Anything else goes into SIMPLE mode.
@@ -6156,17 +6308,17 @@ function isSimple(typed) {
  * Looks in the canon for a command extension that matches what has been
  * typed at the command line.
  */
 Requisition.prototype._split = function(args) {
   // Handle the special case of the user typing { javascript(); }
   // We use the hidden 'eval' command directly rather than shift()ing one of
   // the parameters, and parse()ing it.
   var conversion;
-  if (args[0] instanceof ScriptArgument) {
+  if (args[0].type === 'ScriptArgument') {
     // Special case: if the user enters { console.log('foo'); } then we need to
     // use the hidden 'eval' command
     conversion = new Conversion(evalCommand, new ScriptArgument());
     this.commandAssignment.setConversion(conversion);
     return;
   }
 
   var argsUsed = 1;
@@ -6197,48 +6349,57 @@ Requisition.prototype._split = function(
   for (var i = 0; i < argsUsed; i++) {
     args.shift();
   }
 
   // This could probably be re-written to consume args as we go
 };
 
 /**
+ * Add all the passed args to the list of unassigned assignments.
+ */
+Requisition.prototype._addUnassignedArgs = function(args) {
+  args.forEach(function(arg) {
+    this._unassigned.push(new UnassignedAssignment(this, arg, false));
+  }.bind(this));
+};
+
+/**
  * Work out which arguments are applicable to which parameters.
  */
 Requisition.prototype._assign = function(args) {
+  this._unassigned = [];
+
   if (!this.commandAssignment.value) {
-    this._unassigned.setUnassigned(args);
+    this._addUnassignedArgs(args);
     return;
   }
 
   if (args.length === 0) {
     this.setBlankArguments();
-    this._unassigned.setBlank();
     return;
   }
 
   // Create an error if the command does not take parameters, but we have
   // been given them ...
   if (this.assignmentCount === 0) {
-    this._unassigned.setUnassigned(args);
+    this._addUnassignedArgs(args);
     return;
   }
 
   // Special case: if there is only 1 parameter, and that's of type
   // text, then we put all the params into the first param
   if (this.assignmentCount === 1) {
     var assignment = this.getAssignment(0);
     if (assignment.param.type instanceof StringType) {
       var arg = (args.length === 1) ?
         args[0] :
         new MergedArgument(args);
       var conversion = assignment.param.type.parse(arg);
       assignment.setConversion(conversion);
-      this._unassigned.setBlank();
       return;
     }
   }
 
   // Positional arguments can still be specified by name, but if they are
   // then we need to ignore them when working them out positionally
   var names = this.getParameterNames();
 
@@ -6258,17 +6419,17 @@ Requisition.prototype._assign = function
         });
 
         // boolean parameters don't have values, default to false
         if (assignment.param.type instanceof BooleanType) {
           arg = new TrueNamedArgument(null, arg);
         }
         else {
           var valueArg = null;
-          if (i + 1 >= args.length) {
+          if (i + 1 <= args.length) {
             valueArg = args.splice(i, 1)[0];
           }
           arg = new NamedArgument(arg, valueArg);
         }
 
         if (assignment.param.type instanceof ArrayType) {
           var arrayArg = arrayArgs[assignment.param.name];
           if (!arrayArg) {
@@ -6307,33 +6468,47 @@ Requisition.prototype._assign = function
       if (!arrayArg) {
         arrayArg = new ArrayArgument();
         arrayArgs[assignment.param.name] = arrayArg;
       }
       arrayArg.addArguments(args);
       args = [];
     }
     else {
-      var arg = (args.length > 0) ?
-          args.splice(0, 1)[0] :
-          new Argument();
-
-      var conversion = assignment.param.type.parse(arg);
-      assignment.setConversion(conversion);
+      if (args.length === 0) {
+        assignment.setBlank();
+      }
+      else {
+        var arg = args.splice(0, 1)[0];
+        // --foo and -f are named parameters, -4 is a number. So '-' is either
+        // the start of a named parameter or a number depending on the context
+        var isIncompleteName = assignment.param.type instanceof NumberType ?
+            /-[-a-zA-Z_]/.test(arg.text) :
+            arg.text.charAt(0) === '-';
+
+        if (isIncompleteName) {
+          this._unassigned.push(new UnassignedAssignment(this, arg, true));
+        }
+        else {
+          var conversion = assignment.param.type.parse(arg);
+          assignment.setConversion(conversion);
+        }
+      }
     }
   }, this);
 
   // Now we need to assign the array argument (if any)
   Object.keys(arrayArgs).forEach(function(name) {
     var assignment = this.getAssignment(name);
     var conversion = assignment.param.type.parse(arrayArgs[name]);
     assignment.setConversion(conversion);
   }, this);
 
-  this._unassigned.setUnassigned(args);
+  // What's left is can't be assigned, but we need to extract
+  this._addUnassignedArgs(args);
 };
 
 exports.Requisition = Requisition;
 
 /**
  * A simple object to hold information about the output of a command
  */
 function Output(options) {
@@ -7690,22 +7865,24 @@ JavascriptField.DEFAULT_VALUE = '__Javas
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/ui/fields/menu', ['require', 'exports', 'module' , 'gcli/util', 'gcli/argument', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/fields/menu.css', 'text!gcli/ui/fields/menu.html'], function(require, exports, module) {
+define('gcli/ui/fields/menu', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/argument', 'gcli/types', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/fields/menu.css', 'text!gcli/ui/fields/menu.html'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
+var l10n = require('gcli/l10n');
 
 var Argument = require('gcli/argument').Argument;
+var Conversion = require('gcli/types').Conversion;
 var canon = require('gcli/canon');
 
 var domtemplate = require('gcli/ui/domtemplate');
 
 var menuCss = require('text!gcli/ui/fields/menu.css');
 var menuHtml = require('text!gcli/ui/fields/menu.html');
 
 
@@ -7746,16 +7923,21 @@ function Menu(options) {
 
   // Contains the items that should be displayed
   this.items = null;
 
   this.onItemClick = util.createEvent('Menu.onItemClick');
 }
 
 /**
+ * Allow the template engine to get at localization strings
+ */
+Menu.prototype.l10n = l10n.propertyLookup;
+
+/**
  * Avoid memory leaks
  */
 Menu.prototype.destroy = function() {
   delete this.element;
   delete this.template;
   delete this.document;
 };
 
@@ -7790,16 +7972,21 @@ Menu.prototype.show = function(items, ma
     }.bind(this));
   }
 
   if (this.items.length === 0) {
     this.element.style.display = 'none';
     return;
   }
 
+  if (this.items.length >= Conversion.maxPredictions) {
+    this.items.splice(-1);
+    this.items.hasMore = true;
+  }
+
   var options = this.template.cloneNode(true);
   domtemplate.template(options, this, this.templateOptions);
 
   util.clearElement(this.element);
   this.element.appendChild(options);
 
   this.element.style.display = 'block';
 };
@@ -7890,23 +8077,26 @@ Menu.prototype.setMaxHeight = function(h
 
 exports.Menu = Menu;
 
 
 });
 define("text!gcli/ui/fields/menu.css", [], "");
 
 define("text!gcli/ui/fields/menu.html", [], "\n" +
-  "<table class=\"gcli-menu-template\" aria-live=\"polite\">\n" +
-  "  <tr class=\"gcli-menu-option\" foreach=\"item in ${items}\"\n" +
-  "      onclick=\"${onItemClickInternal}\" title=\"${item.manual}\">\n" +
-  "    <td class=\"gcli-menu-name\">${item.name}</td>\n" +
-  "    <td class=\"gcli-menu-desc\">${item.description}</td>\n" +
-  "  </tr>\n" +
-  "</table>\n" +
+  "<div>\n" +
+  "  <table class=\"gcli-menu-template\" aria-live=\"polite\">\n" +
+  "    <tr class=\"gcli-menu-option\" foreach=\"item in ${items}\"\n" +
+  "        onclick=\"${onItemClickInternal}\" title=\"${item.manual}\">\n" +
+  "      <td class=\"gcli-menu-name\">${item.name}</td>\n" +
+  "      <td class=\"gcli-menu-desc\">${item.description}</td>\n" +
+  "    </tr>\n" +
+  "  </table>\n" +
+  "  <div class=\"gcli-menu-more\" if=\"${items.hasMore}\">${l10n.fieldMenuMore}</div>\n" +
+  "</div>\n" +
   "");
 
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -9232,23 +9422,26 @@ Inputter.prototype.onKeyUp = function(ev
       // should select the incorrect part of the input for an easy fix
     }
 
     this._choice = null;
     return;
   }
 
   if (ev.keyCode === KeyEvent.DOM_VK_TAB && !ev.shiftKey) {
+    // Being able to complete 'nothing' is OK if there is some context, but
+    // when there is nothing on the command line it jsut looks bizarre.
+    var hasContents = (this.element.value.length > 0);
     // If the TAB keypress took the cursor from another field to this one,
     // then they get the keydown/keypress, and we get the keyup. In this
     // case we don't want to do any completion.
     // If the time of the keydown/keypress of TAB was close (i.e. within
     // 1 second) to the time of the keyup then we assume that we got them
     // both, and do the completion.
-    if (this.lastTabDownAt + 1000 > ev.timeStamp) {
+    if (hasContents && this.lastTabDownAt + 1000 > ev.timeStamp) {
       // It's possible for TAB to not change the input, in which case the
       // textChanged event will not fire, and the caret move will not be
       // processed. So we check that this is done first
       this._caretChange = Caret.TO_ARG_END;
       var inputState = this.getInputState();
       this._processCaretChange(inputState);
       if (this._choice == null) {
         this._choice = 0;
@@ -9502,185 +9695,147 @@ Completer.prototype.resized = function(e
   this.element.style.top = ev.top + 'px';
   this.element.style.height = ev.height + 'px';
   this.element.style.lineHeight = ev.height + 'px';
   this.element.style.left = ev.left + 'px';
   this.element.style.width = ev.width + 'px';
 };
 
 /**
- * Is the completion given, a "strict" completion of the user inputted value?
- * A completion is considered "strict" only if it the user inputted value is an
- * exact prefix of the completion (ignoring leading whitespace)
- */
-function isStrictCompletion(inputValue, completion) {
-  // Strip any leading whitespace from the user inputted value because the
-  // completion will never have leading whitespace.
-  inputValue = inputValue.replace(/^\s*/, '');
-  // Strict: "ec" -> "echo"
-  // Non-Strict: "ls *" -> "ls foo bar baz"
-  return completion.indexOf(inputValue) === 0;
-}
-
-/**
  * Bring the completion element up to date with what the requisition says
  */
 Completer.prototype.update = function(ev) {
   if (ev && ev.choice != null) {
     this.choice = ev.choice;
   }
-  this._preTemplateUpdate();
-
+
+  var data = this._getCompleterTemplateData();
   var template = this.template.cloneNode(true);
-  domtemplate.template(template, this, { stack: 'completer.html' });
+  domtemplate.template(template, data, { stack: 'completer.html' });
 
   util.clearElement(this.element);
   while (template.hasChildNodes()) {
     this.element.appendChild(template.firstChild);
   }
 };
 
 /**
- * Update the state of a number of internal variables in preparation for
- * templating. Some of these properties are interdependent, so it makes sense
- * to do them in one go.
- */
-Completer.prototype._preTemplateUpdate = function() {
-  this.input = this.inputter.getInputState();
-
-  this.directTabText = '';
-  this.arrowTabText = '';
-
-  // What text should we display as the tab text, and should it be given as a
-  // '-> full' or as 'suffix' (which depends on if the completion is a strict
-  // completion or not)
-  if (this.input.typed.trim().length === 0) {
-    return;
-  }
-
+ * Calculate the properties required by the template process for completer.html
+ */
+Completer.prototype._getCompleterTemplateData = function() {
+  var input = this.inputter.getInputState();
+
+  // directTabText is for when the current input is a prefix of the completion
+  // arrowTabText is for when we need to use an -> to show what will be used
+  var directTabText = '';
+  var arrowTabText = '';
   var current = this.inputter.assignment;
-  var prediction = current.conversion.getPredictionAt(this.choice);
-  if (!prediction) {
-    return;
-  }
-
-  var tabText = prediction.name;
-  var existing = current.arg.text;
-
-  if (existing === tabText) {
-    return;
-  }
-
-  if (isStrictCompletion(existing, tabText) &&
-          this.input.cursor.start === this.input.typed.length) {
-    // Display the suffix of the prediction as the completion
-    var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
-
-    this.directTabText = tabText.slice(existing.length - numLeadingSpaces);
-  }
-  else {
-    // Display the '-> prediction' at the end of the completer element
-    // These JS escapes are aka &nbsp;&rarr; the right arrow
-    this.arrowTabText = ' \u00a0\u21E5 ' + tabText;
-  }
-};
-
-/**
- * A proxy to requisition.getInputStatusMarkup which converts space to &nbsp;
- * in the string member (for HTML display) and converts status to an
- * appropriate class name (i.e. lower cased, prefixed with gcli-in-)
- */
-Object.defineProperty(Completer.prototype, 'statusMarkup', {
-  get: function() {
-    var markup = this.requisition.getInputStatusMarkup(this.input.cursor.start);
-    markup.forEach(function(member) {
-      member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
-      member.className = 'gcli-in-' + member.status.toString().toLowerCase();
-    }, this);
-    return markup;
-  },
-  enumerable: true
-});
-
-/**
- * The text for the 'jump to scratchpad' feature, or null if it is disabled
- */
-Object.defineProperty(Completer.prototype, 'scratchLink', {
-  get: function() {
-    if (!this.scratchpad) {
-      return null;
-    }
-    var command = this.requisition.commandAssignment.value;
-    return command && command.name === '{' ? this.scratchpad.linkText : null;
-  },
-  enumerable: true
-});
-
-/**
- * Is the entered command a JS command with no closing '}'?
- * TWEAK: This code should be considered for promotion to Requisition
- */
-Object.defineProperty(Completer.prototype, 'unclosedJs', {
-  get: function() {
-    var command = this.requisition.commandAssignment.value;
-    var jsCommand = command && command.name === '{';
-    var unclosedJs = jsCommand &&
-        this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
-    return unclosedJs;
-  },
-  enumerable: true
-});
-
-/**
- * Accessor for the list of parameters to be filled in
- */
-Object.defineProperty(Completer.prototype, 'emptyParameters', {
-  get: function() {
-    var typedEndSpace = this.requisition.typedEndsWithWhitespace();
-    // Cache computed property
-    var directTabText = this.directTabText;
-    // If this is the first blank assignment we might not need a space prefix
-    // also we skip [param] text if we have directTabText, but only for the
-    // first blank param.
-    var firstBlankParam = true;
-    var params = [];
-    this.requisition.getAssignments().forEach(function(assignment) {
-      if (!assignment.param.isPositionalAllowed) {
-        return;
+
+  if (input.typed.trim().length !== 0) {
+    var prediction = current.conversion.getPredictionAt(this.choice);
+    if (prediction) {
+      var tabText = prediction.name;
+      var existing = current.arg.text;
+
+      if (existing !== tabText) {
+        // Decide to use directTabText or arrowTabText
+        // Strip any leading whitespace from the user inputted value because the
+        // tabText will never have leading whitespace.
+        var inputValue = existing.replace(/^\s*/, '');
+        var isStrictCompletion = tabText.indexOf(inputValue) === 0;
+        if (isStrictCompletion && input.cursor.start === input.typed.length) {
+          // Display the suffix of the prediction as the completion
+          var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
+
+          directTabText = tabText.slice(existing.length - numLeadingSpaces);
+        }
+        else {
+          // Display the '-> prediction' at the end of the completer element
+          // These JS escapes are aka &nbsp;&rarr; the right arrow
+          arrowTabText = ' \u00a0\u21E5 ' + tabText;
+        }
       }
-
-      if (!assignment.arg.isBlank()) {
-        if (directTabText !== '') {
-          firstBlankParam = false;
-        }
-        return;
-      }
-
-      if (directTabText !== '' && firstBlankParam) {
+    }
+  }
+
+  // statusMarkup is wrapper around requisition.getInputStatusMarkup converting
+  // space to &nbsp; in the string member (for HTML display) and status to an
+  // appropriate class name (i.e. lower cased, prefixed with gcli-in-)
+  var statusMarkup = this.requisition.getInputStatusMarkup(input.cursor.start);
+  statusMarkup.forEach(function(member) {
+    member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
+    member.className = 'gcli-in-' + member.status.toString().toLowerCase();
+  }, this);
+
+  // Calculate the list of parameters to be filled in
+  var trailingSeparator = this.requisition.typedEndsWithSeparator();
+  // We generate an array of emptyParameter markers for each positional
+  // parameter to the current command.
+  // Generally each emptyParameter marker begins with a space to separate it
+  // from whatever came before, unless what comes before ends in a space.
+  // Also if we've got a directTabText prediction or we're in a NamedParameter
+  // then we don't want any text for that parameter at all.
+  // The algorithm to add spaces needs to take this into account.
+
+  var firstBlankParam = true;
+  var emptyParameters = [];
+  this.requisition.getAssignments().forEach(function(assignment) {
+    if (!assignment.param.isPositionalAllowed) {
+      return;
+    }
+    if (current.arg.type === 'NamedArgument') {
+      return;
+    }
+
+    if (assignment.arg.toString().trim() !== '') {
+      if (directTabText !== '') {
         firstBlankParam = false;
-        return;
       }
-
-      var text = (assignment.param.isDataRequired) ?
-          '<' + assignment.param.name + '>' :
-          '[' + assignment.param.name + ']';
-
-      // Add a space if we don't have one at the end of the input or if
-      // this isn't the first param we've mentioned
-      if (!typedEndSpace || !firstBlankParam) {
-        text = '\u00a0' + text; // i.e. &nbsp;
-      }
-
+      return;
+    }
+
+    if (directTabText !== '' && firstBlankParam) {
       firstBlankParam = false;
-      params.push(text);
-    }.bind(this));
-    return params;
-  },
-  enumerable: true
-});
+      return;
+    }
+
+    var text = (assignment.param.isDataRequired) ?
+        '<' + assignment.param.name + '>' :
+        '[' + assignment.param.name + ']';
+
+    // Add a space if we don't have one at the end of the input or if
+    // this isn't the first param we've mentioned
+    if (!trailingSeparator || !firstBlankParam) {
+      text = '\u00a0' + text; // i.e. &nbsp;
+    }
+
+    firstBlankParam = false;
+    emptyParameters.push(text);
+  }.bind(this));
+
+  var command = this.requisition.commandAssignment.value;
+  var jsCommand = command && command.name === '{';
+
+  // Is the entered command a JS command with no closing '}'?
+  // TWEAK: This code should be considered for promotion to Requisition
+  var unclosedJs = jsCommand &&
+      this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
+
+  // The text for the 'jump to scratchpad' feature, or '' if it is disabled
+  var link = this.scratchpad && jsCommand ? this.scratchpad.linkText : '';
+
+  return {
+    statusMarkup: statusMarkup,
+    directTabText: directTabText,
+    emptyParameters: emptyParameters,
+    arrowTabText: arrowTabText,
+    unclosedJs: unclosedJs,
+    scratchLink: link
+  };
+};
 
 exports.Completer = Completer;
 
 
 });
 define("text!gcli/ui/completer.html", [], "\n" +
   "<description>\n" +
   "  <loop foreach=\"member in ${statusMarkup}\">\n" +
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -80,19 +80,21 @@ registerCleanupFunction(function tearDow
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite'], function(require, exports, module) {
+define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite', 'gcli/settings', 'gcli/ui/display'], function(require, exports, module) {
 
   var examiner = require('gclitest/suite').examiner;
+  var settings = require('gcli/settings');
+  var Display = require('gcli/ui/display').Display;
 
   // A minimum fake dom to get us through the JS tests
   var fakeWindow = {
     isFake: true,
     document: { title: 'Fake DOM' }
   };
   fakeWindow.window = fakeWindow;
   examiner.defaultOptions = {
@@ -113,27 +115,73 @@ define('gclitest/index', ['require', 'ex
    *   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 || {});
+    options = options || {};
+    examiner.mergeDefaultOptions(options);
+
+    examiner.reset();
+    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
     };
   };
 
+  /**
+   * This is the equivalent of gcli/index.createDisplay() except it:
+   * - Sets window.display: to actual Display object (not the thin proxy
+   *   returned by gcli/index.createDisplay() and this function)
+   * - Registers all the test commands, and provides a function to re-register
+   *   them - window.testCommands() (running the test suite un-registers them)
+   * - Runs the unit tests automatically on startup
+   * - Registers a 'test' command to re-run the unit tests
+   */
+  exports.createDisplay = function(options) {
+    options = options || {};
+    if (options.settings != null) {
+      settings.setDefaults(options.settings);
+    }
+
+    window.display = new Display(options);
+    var requisition = window.display.requisition;
+
+    exports.run({
+      window: window,
+      display: window.display,
+      hideExec: true
+    });
+
+    window.testCommands = function() {
+      require([ 'gclitest/mockCommands' ], function(mockCommands) {
+        mockCommands.setup();
+      });
+    };
+    window.testCommands();
+
+    return {
+      /**
+       * The exact shape of the object returned by exec is likely to change in
+       * the near future. If you do use it, please expect your code to break.
+       */
+      exec: requisition.exec.bind(requisition),
+      update: requisition.update.bind(requisition),
+      destroy: window.display.destroy.bind(window.display)
+    };
+  };
+
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -141,33 +189,34 @@ define('gclitest/index', ['require', 'ex
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
+define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIncomplete', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', '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/testCanon', require('gclitest/testCanon'));
   examiner.addSuite('gclitest/testCli', require('gclitest/testCli'));
   examiner.addSuite('gclitest/testCompletion', require('gclitest/testCompletion'));
   examiner.addSuite('gclitest/testExec', require('gclitest/testExec'));
   examiner.addSuite('gclitest/testHelp', require('gclitest/testHelp'));
   examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
   examiner.addSuite('gclitest/testInputter', require('gclitest/testInputter'));
+  examiner.addSuite('gclitest/testIncomplete', require('gclitest/testIncomplete'));
   examiner.addSuite('gclitest/testIntro', require('gclitest/testIntro'));
   examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
   examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard'));
   examiner.addSuite('gclitest/testPref', require('gclitest/testPref'));
   examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire'));
   examiner.addSuite('gclitest/testResource', require('gclitest/testResource'));
   examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
   examiner.addSuite('gclitest/testSettings', require('gclitest/testSettings'));
@@ -226,30 +275,28 @@ examiner.addSuite = function(name, suite
  * 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) {
+examiner.mergeDefaultOptions = function(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
  */
 examiner.run = function(options) {
-  mergeDefaultOptions(options);
-
   Object.keys(examiner.suites).forEach(function(suiteName) {
     var suite = examiner.suites[suiteName];
     suite.run(options);
   }.bind(this));
 
   if (options.detailedResultLog) {
     examiner.detailedResultLog();
   }
@@ -259,17 +306,16 @@ examiner.run = function(options) {
 
   return examiner.suites;
 };
 
 /**
  * Run all the tests asynchronously
  */
 examiner.runAsync = function(options, callback) {
-  mergeDefaultOptions(options);
   this._runAsyncInternal(0, options, callback);
 };
 
 /**
  * Run all the test suits asynchronously
  */
 examiner._runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(examiner.suites).length) {
@@ -298,16 +344,25 @@ examiner.toRemote = function() {
     summary: {
       checks: this.checks,
       status: this.status
     }
   };
 };
 
 /**
+ * Reset all the tests to their original state
+ */
+examiner.reset = function() {
+  Object.keys(examiner.suites).forEach(function(suiteName) {
+    examiner.suites[suiteName].reset();
+  }, this);
+};
+
+/**
  * 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);
@@ -371,27 +426,35 @@ function Suite(suiteName, suite) {
     if (testName !== 'setup' && testName !== 'shutdown') {
       var test = new Test(this, testName, suite[testName]);
       this.tests[testName] = test;
     }
   }.bind(this));
 }
 
 /**
+ * Reset all the tests to their original state
+ */
+Suite.prototype.reset = function() {
+  Object.keys(this.tests).forEach(function(testName) {
+    this.tests[testName].reset();
+  }, this);
+};
+
+/**
  * Run all the tests in this suite synchronously
  */
 Suite.prototype.run = function(options) {
   if (!this._setup(options)) {
     return;
   }
 
   Object.keys(this.tests).forEach(function(testName) {
-    var test = this.tests[testName];
-    test.run(options);
-  }.bind(this));
+    this.tests[testName].run(options);
+  }, this);
 
   this._shutdown(options);
 };
 
 /**
  * Run all the tests in this suite asynchronously
  */
 Suite.prototype.runAsync = function(options, callback) {
@@ -536,30 +599,39 @@ function Test(suite, name, func) {
   this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1');
 
   this.failures = [];
   this.status = stati.notrun;
   this.checks = 0;
 }
 
 /**
+ * Reset the test to its original state
+ */
+Test.prototype.reset = function() {
+  this.failures = [];
+  this.status = stati.notrun;
+  this.checks = 0;
+};
+
+/**
  * Run just a single test
  */
 Test.prototype.run = function(options) {
   assert.currentTest = this;
   this.status = stati.executing;
   this.failures = [];
   this.checks = 0;
 
   try {
     this.func.apply(this.suite, [ options ]);
   }
   catch (ex) {
     assert.ok(false, '' + ex);
-    console.error(ex);
+    console.error(ex.stack);
     if ((options.isNode || options.isFirefox) && ex.stack) {
       console.error(ex.stack);
     }
   }
 
   if (this.status === stati.executing) {
     this.status = stati.pass;
   }
@@ -765,20 +837,32 @@ define('gclitest/testCanon', ['require',
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
+define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert', 'gcli/util'], function(require, exports, module) {
 
 
 var test = require('test/assert');
+var util = require('gcli/util');
+
+
+var cachedOptions = undefined;
+
+exports.setup = function(opts) {
+  cachedOptions = opts;
+};
+
+exports.shutdown = function(opts) {
+  cachedOptions = undefined;
+};
 
 /**
  * Check that we can parse command input.
  * Doesn't execute the command, just checks that we grok the input properly:
  *
  * helpers.status({
  *   // Test inputs
  *   typed: "ech",           // Required
@@ -787,68 +871,243 @@ var test = require('test/assert');
  *   // 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
  *   markup: "VVVIIIEEE",    // What state should the error markup be in
  * });
  */
-exports.status = function(options, tests) {
+exports.status = function(options, checks) {
   var requisition = options.display.requisition;
   var inputter = options.display.inputter;
   var completer = options.display.completer;
 
-  if (tests.typed) {
-    inputter.setInput(tests.typed);
+  if (checks.typed) {
+    inputter.setInput(checks.typed);
   }
   else {
-    test.ok(false, "Missing typed for " + JSON.stringify(tests));
+    test.ok(false, "Missing typed for " + JSON.stringify(checks));
     return;
   }
 
-  if (tests.cursor) {
-    inputter.setCursor(tests.cursor);
+  if (checks.cursor) {
+    inputter.setCursor(checks.cursor);
   }
 
-  if (tests.status) {
-    test.is(requisition.getStatus().toString(), tests.status,
-            "status for " + tests.typed);
+  if (checks.status) {
+    test.is(requisition.getStatus().toString(),
+            checks.status,
+            "status for " + checks.typed);
   }
 
-  if (tests.emptyParameters != null) {
-    var realParams = completer.emptyParameters;
-    test.is(realParams.length, tests.emptyParameters.length,
-            'emptyParameters.length for \'' + tests.typed + '\'');
-
-    if (realParams.length === tests.emptyParameters.length) {
+  var data = completer._getCompleterTemplateData();
+  if (checks.emptyParameters != null) {
+    var realParams = data.emptyParameters;
+    test.is(realParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length for \'' + checks.typed + '\'');
+
+    if (realParams.length === checks.emptyParameters.length) {
       for (var i = 0; i < realParams.length; i++) {
-        test.is(realParams[i].replace(/\u00a0/g, ' '), tests.emptyParameters[i],
-                'emptyParameters[' + i + '] for \'' + tests.typed + '\'');
+        test.is(realParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + '] for \'' + checks.typed + '\'');
       }
     }
   }
 
-  if (tests.markup) {
-    var cursor = tests.cursor ? tests.cursor.start : tests.typed.length;
+  if (checks.markup) {
+    var cursor = checks.cursor ? checks.cursor.start : checks.typed.length;
     var statusMarkup = requisition.getInputStatusMarkup(cursor);
     var actualMarkup = statusMarkup.map(function(s) {
       return Array(s.string.length + 1).join(s.status.toString()[0]);
     }).join('');
-    test.is(tests.markup, actualMarkup, 'markup for ' + tests.typed);
+
+    test.is(checks.markup,
+            actualMarkup,
+            'markup for ' + checks.typed);
+  }
+
+  if (checks.directTabText) {
+    test.is(data.directTabText,
+            checks.directTabText,
+            'directTabText for \'' + checks.typed + '\'');
+  }
+
+  if (checks.arrowTabText) {
+    test.is(' \u00a0\u21E5 ' + checks.arrowTabText,
+            data.arrowTabText,
+            'arrowTabText for \'' + checks.typed + '\'');
+  }
+};
+
+/**
+ * We're splitting status into setup() which alters the state of the system
+ * and check() which ensures that things are in the right place afterwards.
+ */
+exports.setInput = function(typed, cursor) {
+  cachedOptions.display.inputter.setInput(typed);
+
+  if (cursor) {
+    cachedOptions.display.inputter.setCursor({ start: cursor, end: cursor });
+  }
+};
+
+/**
+ * Simulate pressing TAB in the input field
+ */
+exports.pressTab = function() {
+  // requisition.complete({ start: 5, end: 5 }, 0);
+
+  var fakeEvent = {
+    keyCode: util.KeyEvent.DOM_VK_TAB,
+    preventDefault: function() { },
+    timeStamp: new Date().getTime()
+  };
+  cachedOptions.display.inputter.onKeyDown(fakeEvent);
+  cachedOptions.display.inputter.onKeyUp(fakeEvent);
+};
+
+/**
+ * check() is the new status. Similar API except that it doesn't attempt to
+ * alter the display/requisition at all, and it makes extra checks.
+ * Available checks:
+ *   input: The text displayed in the input field
+ *   cursor: The position of the start of the cursor
+ *   status: One of "VALID", "ERROR", "INCOMPLETE"
+ *   emptyParameters: Array of parameters still to type. e.g. [ "<message>" ]
+ *   directTabText: Simple completion text
+ *   arrowTabText: When the completion is not an extension (without arrow)
+ *   markup: What state should the error markup be in. e.g. "VVVIIIEEE"
+ *   args: Maps of checks to make against the arguments:
+ *     value: i.e. assignment.value (which ignores defaultValue)
+ *     type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned
+ *           Care should be taken with this since it's something of an
+ *           implementation detail
+ *     arg: The toString value of the argument
+ *     status: i.e. assignment.getStatus
+ *     message: i.e. assignment.getMessage
+ *     name: For commands - checks assignment.value.name
+ */
+exports.check = function(checks) {
+  var requisition = cachedOptions.display.requisition;
+  var completer = cachedOptions.display.completer;
+  var actual = completer._getCompleterTemplateData();
+
+  if (checks.input) {
+    test.is(cachedOptions.display.inputter.element.value,
+            checks.input,
+            'input');
+  }
+
+  if (checks.cursor) {
+    test.is(cachedOptions.display.inputter.element.selectionStart,
+            checks.cursor,
+            'cursor');
+  }
+
+  if (checks.status) {
+    test.is(requisition.getStatus().toString(),
+            checks.status,
+            'status');
   }
 
-  if (tests.directTabText) {
-    test.is(completer.directTabText, tests.directTabText,
-            'directTabText for \'' + tests.typed + '\'');
+  if (checks.markup) {
+    var cursor = cachedOptions.display.inputter.element.selectionStart;
+    var statusMarkup = requisition.getInputStatusMarkup(cursor);
+    var actualMarkup = statusMarkup.map(function(s) {
+      return Array(s.string.length + 1).join(s.status.toString()[0]);
+    }).join('');
+
+    test.is(checks.markup,
+            actualMarkup,
+            'markup');
+  }
+
+  if (checks.emptyParameters) {
+    var actualParams = actual.emptyParameters;
+    test.is(actualParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length');
+
+    if (actualParams.length === checks.emptyParameters.length) {
+      for (var i = 0; i < actualParams.length; i++) {
+        test.is(actualParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + ']');
+      }
+    }
+  }
+
+  if (checks.directTabText) {
+    test.is(actual.directTabText,
+            checks.directTabText,
+            'directTabText');
+  }
+
+  if (checks.arrowTabText) {
+    test.is(actual.arrowTabText,
+            ' \u00a0\u21E5 ' + checks.arrowTabText,
+            'arrowTabText');
   }
 
-  if (tests.arrowTabText) {
-    test.is(completer.arrowTabText, ' \u00a0\u21E5 ' + tests.arrowTabText,
-            'arrowTabText for \'' + tests.typed + '\'');
+  if (checks.args) {
+    Object.keys(checks.args).forEach(function(paramName) {
+      var check = checks.args[paramName];
+
+      var assignment;
+      if (paramName === 'command') {
+        assignment = requisition.commandAssignment;
+      }
+      else {
+        assignment = requisition.getAssignment(paramName);
+      }
+
+      if (assignment == null) {
+        test.ok(false, 'Unknown parameter: ' + paramName);
+        return;
+      }
+
+      if (check.value) {
+        test.is(assignment.value,
+                check.value,
+                'checkStatus value for ' + paramName);
+      }
+
+      if (check.name) {
+        test.is(assignment.value.name,
+                check.name,
+                'checkStatus name for ' + paramName);
+      }
+
+      if (check.type) {
+        test.is(assignment.arg.type,
+                check.type,
+                'checkStatus type for ' + paramName);
+      }
+
+      if (check.arg) {
+        test.is(assignment.arg.toString(),
+                check.arg,
+                'checkStatus arg for ' + paramName);
+      }
+
+      if (check.status) {
+        test.is(assignment.getStatus().toString(),
+                check.status,
+                'checkStatus status for ' + paramName);
+      }
+
+      if (check.message) {
+        test.is(assignment.getMessage(),
+                check.message,
+                'checkStatus message for ' + paramName);
+      }
+    });
   }
 };
 
 /**
  * Execute a command:
  *
  * helpers.exec({
  *   // Test inputs
@@ -1163,45 +1422,46 @@ exports.testTsv = function() {
   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(0, requ._unassigned.length);
   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(0, requ._unassigned.length);
   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);
+  test.is(1, requ._unassigned.length);
+  test.is('one', requ._unassigned[0].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.ok(assign1.arg.type === 'BlankArgument');
   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.ok(assign1.arg.type === 'BlankArgument');
   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);
@@ -1252,17 +1512,17 @@ exports.testSingleNumber = function() {
   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.ok(assign1.arg.type === 'BlankArgument');
   test.is(undefined, assign1.value);
 
   if (!options.isNode) {
     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);
@@ -1598,17 +1858,18 @@ exports.tsu = {
 };
 
 exports.tsn = {
   name: 'tsn'
 };
 
 exports.tsnDif = {
   name: 'tsn dif',
-  params: [ { name: 'text', type: 'string' } ],
+  description: 'tsn dif',
+  params: [ { name: 'text', type: 'string', description: 'tsn dif text' } ],
   exec: createExec('tsnDif')
 };
 
 exports.tsnExt = {
   name: 'tsn ext',
   params: [ { name: 'text', type: 'string' } ],
   exec: createExec('tsnExt')
 };
@@ -1667,29 +1928,52 @@ exports.tsm = {
   ],
   exec: createExec('tsm')
 };
 
 exports.tsg = {
   name: 'tsg',
   description: 'a param group test',
   params: [
-    { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } },
+    {
+      name: 'solo',
+      type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] },
+      description: 'solo param'
+    },
     {
       group: 'First',
       params: [
-        { name: 'txt1', type: 'string', defaultValue: null },
-        { name: 'bool', type: 'boolean' }
+        {
+          name: 'txt1',
+          type: 'string',
+          defaultValue: null,
+          description: 'txt1 param'
+        },
+        {
+          name: 'bool',
+          type: 'boolean',
+          description: 'bool param'
+        }
       ]
     },
     {
       group: 'Second',
       params: [
-        { name: 'txt2', type: 'string', defaultValue: 'd' },
-        { name: 'num', type: { name: 'number', min: 40 }, defaultValue: 42 }
+        {
+          name: 'txt2',
+          type: 'string',
+          defaultValue: 'd',
+          description: 'txt2 param'
+        },
+        {
+          name: 'num',
+          type: { name: 'number', min: 40 },
+          defaultValue: 42,
+          description: 'num param'
+        }
       ]
     }
   ],
   exec: createExec('tsg')
 };
 
 
 });
@@ -1704,211 +1988,312 @@ exports.tsg = {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/mockCommands'], function(require, exports, module) {
+define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
 
 
 var test = require('test/assert');
+var helpers = require('gclitest/helpers');
 var mockCommands = require('gclitest/mockCommands');
 
 
-exports.setup = function() {
+exports.setup = function(options) {
   mockCommands.setup();
-};
-
-exports.shutdown = function() {
-  mockCommands.shutdown();
+  helpers.setup(options);
 };
 
-
-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.shutdown = function(options) {
+  mockCommands.shutdown();
+  helpers.shutdown(options);
+};
 
 exports.testActivate = function(options) {
   if (!options.display) {
     test.log('No display. Skipping activate tests');
     return;
   }
 
-  type('', { }, options);
-
-  type(' ', { }, options);
-
-  type('tsr', {
+  helpers.setInput('');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput(' ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsr');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <text>' ]
-  }, options);
-
-  type('tsr ', {
+  });
+
+  helpers.setInput('tsr ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<text>' ]
-  }, options);
-
-  type('tsr b', { }, options);
-
-  type('tsb', {
+  });
+
+  helpers.setInput('tsr b');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsb');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' [toggle]' ]
-  }, options);
-
-  type('tsm', {
+  });
+
+  helpers.setInput('tsm');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <abc>', ' <txt>', ' <num>' ]
-  }, options);
-
-  type('tsm ', {
+  });
+
+  helpers.setInput('tsm ');
+  helpers.check({
     emptyParameters: [ ' <txt>', ' <num>' ],
+    arrowTabText: '',
     directTabText: 'a'
-  }, options);
-
-  type('tsm a', {
+  });
+
+  helpers.setInput('tsm a');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a ', {
+  });
+
+  helpers.setInput('tsm a ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a  ', {
+  });
+
+  helpers.setInput('tsm a  ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a  d', {
+  });
+
+  helpers.setInput('tsm a  d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d d"', {
+  });
+
+  helpers.setInput('tsm a "d d"');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d ', {
+  });
+
+  helpers.setInput('tsm a "d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d d" ', {
+  });
+
+  helpers.setInput('tsm a "d d" ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<num>' ]
-  }, options);
-
-  type('tsm a "d d ', {
+  });
+
+  helpers.setInput('tsm a "d d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm d r', {
+  });
+
+  helpers.setInput('tsm d r');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a d ', {
+  });
+
+  helpers.setInput('tsm a d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<num>' ]
-  }, options);
-
-  type('tsm a d 4', { }, options);
-
-  type('tsg', {
+  });
+
+  helpers.setInput('tsm a d 4');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <solo>' ]
-  }, options);
-
-  type('tsg ', {
+  });
+
+  helpers.setInput('tsg ');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'aaa'
-  }, options);
-
-  type('tsg a', {
+  });
+
+  helpers.setInput('tsg a');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'aa'
-  }, options);
-
-  type('tsg b', {
+  });
+
+  helpers.setInput('tsg b');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'bb'
-  }, options);
-
-  type('tsg d', { }, options);
-
-  type('tsg aa', {
+  });
+
+  helpers.setInput('tsg d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aa');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     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', {
+  });
+
+  helpers.setInput('tsg aaa');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa dddddd');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa dddddd ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d d"');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsn ex ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('selarr');
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
-
-  type('tselar 1', { }, options);
-
-  type('tselar 1', {
-    cursor: 7
-  }, options);
-
-  type('tselar 1', {
-    cursor: 6,
+  });
+
+  helpers.setInput('tselar 1');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tselar 1', 7);
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tselar 1', 6);
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
-
-  type('tselar 1', {
-    cursor: 5,
+  });
+
+  helpers.setInput('tselar 1', 5);
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
+  });
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -2342,16 +2727,204 @@ exports.testOutput = function(options) {
   test.ok(!focusManager._helpRequested, 'ESCAPE = anti help');
 
   latestOutput.onClose();
 };
 
 
 });
 /*
+ * 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/testIncomplete', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+var helpers = require('gclitest/helpers');
+var mockCommands = require('gclitest/mockCommands');
+
+
+exports.setup = function(options) {
+  mockCommands.setup();
+  helpers.setup(options);
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+  helpers.shutdown(options);
+};
+
+exports.testBasic = function(options) {
+  var requisition = options.display.requisition;
+
+  helpers.setInput('tsu 2 extra');
+  helpers.check({
+    args: {
+      num: { value: 2, type: 'Argument' }
+    }
+  });
+  test.is(requisition._unassigned.length, 1, 'single unassigned: tsu 2 extra');
+  test.is(requisition._unassigned[0].param.type.isIncompleteName, false,
+          'unassigned.isIncompleteName: tsu 2 extra');
+
+  helpers.setInput('tsu');
+  helpers.check({
+    args: {
+      num: { value: undefined, type: 'BlankArgument' }
+    }
+  });
+
+  helpers.setInput('tsg');
+  helpers.check({
+    args: {
+      solo: { type: 'BlankArgument' },
+      txt1: { type: 'BlankArgument' },
+      bool: { type: 'BlankArgument' },
+      txt2: { type: 'BlankArgument' },
+      num: { type: 'BlankArgument' }
+    }
+  });
+};
+
+exports.testCompleted = function(options) {
+  helpers.setInput('tsela');
+  helpers.pressTab();
+  helpers.check({
+    args: {
+      command: { name: 'tselarr', type: 'Argument' },
+      num: { type: 'Argument' },
+      arr: { type: 'ArrayArgument' },
+    }
+  });
+
+  helpers.setInput('tsn dif ');
+  helpers.check({
+    input:  'tsn dif ',
+    markup: 'VVVVVVVV',
+    cursor: 8,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ '<text>' ],
+    args: {
+      command: { name: 'tsn dif', type: 'MergedArgument' },
+      text: { type: 'BlankArgument', status: 'INCOMPLETE' }
+    }
+  });
+
+  helpers.setInput('tsn di');
+  helpers.pressTab();
+  helpers.check({
+    input:  'tsn dif ',
+    markup: 'VVVVVVVV',
+    cursor: 8,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ '<text>' ],
+    args: {
+      command: { name: 'tsn dif', type: 'Argument' },
+      text: { type: 'Argument', status: 'INCOMPLETE' }
+    }
+  });
+
+  // The above 2 tests take different routes to 'tsn dif '. The results should
+  // be similar. The difference is in args.command.type.
+
+  helpers.setInput('tsg -');
+  helpers.check({
+    input:  'tsg -',
+    markup: 'VVVVI',
+    cursor: 5,
+    directTabText: '-txt1',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: undefined, status: 'VALID' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.pressTab();
+  helpers.check({
+    input:  'tsg --txt1 ',
+    markup: 'VVVVIIIIIIV',
+    cursor: 11,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ], // Bug 770830: '<txt1>', ' <solo>'
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: undefined, status: 'INCOMPLETE' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tsg --txt1 fred');
+  helpers.check({
+    input:  'tsg --txt1 fred',
+    markup: 'VVVVVVVVVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ], // Bug 770830: ' <solo>'
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: 'fred', status: 'VALID' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  // Expand out to christmas tree command line
+};
+
+exports.testIncomplete = function(options) {
+  var requisition = options.display.requisition;
+
+  helpers.setInput('tsm a a -');
+  helpers.check({
+    args: {
+      abc: { value: 'a', type: 'Argument' },
+      txt: { value: 'a', type: 'Argument' },
+      num: { value: undefined, arg: ' -', type: 'Argument', status: 'INCOMPLETE' }
+    }
+  });
+
+  helpers.setInput('tsg -');
+  helpers.check({
+    args: {
+      solo: { type: 'BlankArgument' },
+      txt1: { type: 'BlankArgument' },
+      bool: { type: 'BlankArgument' },
+      txt2: { type: 'BlankArgument' },
+      num: { type: 'BlankArgument' }
+    }
+  });
+  test.is(requisition._unassigned[0], requisition.getAssignmentAt(5),
+          'unassigned -');
+  test.is(requisition._unassigned.length, 1, 'single unassigned - tsg -');
+  test.is(requisition._unassigned[0].param.type.isIncompleteName, true,
+          'unassigned.isIncompleteName: tsg -');
+};
+
+
+});
+/*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
  *
@@ -3686,23 +4259,21 @@ exports.testJavascript = function() {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli', 'gcli/argument'], function(require, exports, module) {
+define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli'], 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;
 
 exports.testBlanks = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('');
   test.is(1, args.length);
   test.is('', args[0].text);
@@ -3720,257 +4291,257 @@ exports.testTokSimple = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('s');
   test.is(1, args.length);
   test.is('s', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   args = requ._tokenize('s s');
   test.is(2, args.length);
   test.is('s', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
   test.is('s', args[1].text);
   test.is(' ', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 };
 
 exports.testJavascript = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{x}');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{ x }');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{ ', args[0].prefix);
   test.is(' }', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{x} {y}');
   test.is(2, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
   test.is('y', args[1].text);
   test.is(' {', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   args = requ._tokenize('{x}{y}');
   test.is(2, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
   test.is('y', args[1].text);
   test.is('{', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   args = requ._tokenize('{');
   test.is(1, args.length);
   test.is('', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{ ');
   test.is(1, args.length);
   test.is('', args[0].text);
   test.is('{ ', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{x');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 };
 
 exports.testRegularNesting = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{"x"}');
   test.is(1, args.length);
   test.is('"x"', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{\'x\'}');
   test.is(1, args.length);
   test.is('\'x\'', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('"{x}"');
   test.is(1, args.length);
   test.is('{x}', args[0].text);
   test.is('"', args[0].prefix);
   test.is('"', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   args = requ._tokenize('\'{x}\'');
   test.is(1, args.length);
   test.is('{x}', args[0].text);
   test.is('\'', args[0].prefix);
   test.is('\'', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 };
 
 exports.testDeepNesting = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{{}}');
   test.is(1, args.length);
   test.is('{}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{{x} {y}}');
   test.is(1, args.length);
   test.is('{x} {y}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{{w} {{{x}}}} {y} {{{z}}}');
 
   test.is(3, args.length);
 
   test.is('{w} {{{x}}}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   test.is('y', args[1].text);
   test.is(' {', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   test.is('{{z}}', args[2].text);
   test.is(' {', args[2].prefix);
   test.is('}', args[2].suffix);
-  test.ok(args[2] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[2].type);
 
   args = requ._tokenize('{{w} {{{x}}} {y} {{{z}}}');
 
   test.is(1, args.length);
 
   test.is('{w} {{{x}}} {y} {{{z}}}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 };
 
 exports.testStrangeNesting = function() {
   var args;
   var requ = new Requisition();
 
   // Note: When we get real JS parsing this should break
   args = requ._tokenize('{"x}"}');
 
   test.is(2, args.length);
 
   test.is('"x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   test.is('}', args[1].text);
   test.is('"', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 };
 
 exports.testComplex = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize(' 1234  \'12 34\'');
 
   test.is(2, args.length);
 
   test.is('1234', args[0].text);
   test.is(' ', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('12 34', args[1].text);
   test.is('  \'', args[1].prefix);
   test.is('\'', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   args = requ._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
 
   test.is(3, args.length);
 
   test.is('12\'34', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('12 34', args[1].text);
   test.is(' "', args[1].prefix);
   test.is('"', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   test.is('\\', args[2].text);
   test.is(' ', args[2].prefix);
   test.is('', args[2].suffix);
-  test.ok(args[2] instanceof Argument);
+  test.is('Argument', args[2].type);
 };
 
 exports.testPathological = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
 
   test.is(4, args.length);
 
   test.is('a b', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('\t\n\r', args[1].text);
   test.is(' ', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   test.is('\'x"', args[2].text);
   test.is(' ', args[2].prefix);
   test.is('', args[2].suffix);
-  test.ok(args[2] instanceof Argument);
+  test.is('Argument', args[2].type);
 
   test.is('d', args[3].text);
   test.is(' \'', args[3].prefix);
   test.is('', args[3].suffix);
-  test.ok(args[3] instanceof Argument);
+  test.is('Argument', args[3].type);
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -4189,66 +4760,761 @@ exports.testFindCssSelector = function(o
 
     test.is(matches.length, 1, 'multiple matches for ' + selector);
     test.is(matches[0], nodes[i], 'non-matching selector: ' + selector);
   }
 };
 
 
 });
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/display', ['require', 'exports', 'module' , 'gcli/util', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/domtemplate', 'gcli/ui/tooltip', 'gcli/ui/output_terminal', 'gcli/ui/inputter', 'gcli/ui/completer', 'gcli/ui/focus', 'gcli/ui/prompt', 'gcli/cli', 'text!gcli/ui/display.css', 'text!gcli/ui/display.html'], function(require, exports, module) {
+
+
+var util = require('gcli/util');
+var settings = require('gcli/settings');
+var intro = require('gcli/ui/intro');
+var domtemplate = require('gcli/ui/domtemplate');
+
+var Tooltip = require('gcli/ui/tooltip').Tooltip;
+var OutputTerminal = require('gcli/ui/output_terminal').OutputTerminal;
+var Inputter = require('gcli/ui/inputter').Inputter;
+var Completer = require('gcli/ui/completer').Completer;
+var FocusManager = require('gcli/ui/focus').FocusManager;
+var Prompt = require('gcli/ui/prompt').Prompt;
+
+var Requisition = require('gcli/cli').Requisition;
+
+var displayCss = require('text!gcli/ui/display.css');
+var displayHtml = require('text!gcli/ui/display.html');
+
+
+/**
+ * createDisplay() calls 'new Display()' but returns an object which exposes a
+ * much restricted set of functions rather than all those exposed by Display.
+ * This allows for robust testing without exposing too many internals.
+ * @param options See Display() for a description of the available options.
+ */
+exports.createDisplay = function(options) {
+  if (options.settings != null) {
+    settings.setDefaults(options.settings);
+  }
+  var display = new Display(options);
+  var requisition = display.requisition;
+  return {
+    /**
+     * The exact shape of the object returned by exec is likely to change in
+     * the near future. If you do use it, please expect your code to break.
+     */
+    exec: requisition.exec.bind(requisition),
+    update: requisition.update.bind(requisition),
+    destroy: display.destroy.bind(display)
+  };
+};
+
+/**
+ * View is responsible for generating the web UI for GCLI.
+ * @param options Object containing user customization properties.
+ * See the documentation for the other components for more details.
+ * Options supported directly include:
+ * - document (default=document):
+ * - environment (default={}):
+ * - dontDecorate (default=false):
+ * - inputElement (default=#gcli-input):
+ * - completeElement (default=#gcli-row-complete):
+ * - displayElement (default=#gcli-display):
+ * - promptElement (default=#gcli-prompt):
+ */
+function Display(options) {
+  var doc = options.document || document;
+
+  this.displayStyle = undefined;
+  if (displayCss != null) {
+    this.displayStyle = util.importCss(displayCss, doc, 'gcli-css-display');
+  }
+
+  // Configuring the document is complex because on the web side, there is an
+  // active desire to have nothing to configure, where as when embedded in
+  // Firefox there could be up to 4 documents, some of which can/should be
+  // derived from some root element.
+  // When a component uses a document to create elements for use under a known
+  // root element, then we pass in the element (if we have looked it up
+  // already) or an id/document
+  this.requisition = new Requisition(options.enviroment || {}, doc);
+
+  this.focusManager = new FocusManager(options, {
+    document: doc
+  });
+
+  this.inputElement = find(doc, options.inputElement || null, 'gcli-input');
+  this.inputter = new Inputter(options, {
+    requisition: this.requisition,
+    focusManager: this.focusManager,
+    element: this.inputElement
+  });
+
+  // autoResize logic: we want Completer to keep the elements at the same
+  // position if we created the completion element, but if someone else created
+  // it, then it's their job.
+  this.completeElement = insert(this.inputElement,
+                         options.completeElement || null, 'gcli-row-complete');
+  this.completer = new Completer(options, {
+    requisition: this.requisition,
+    inputter: this.inputter,
+    autoResize: this.completeElement.gcliCreated,
+    element: this.completeElement
+  });
+
+  this.prompt = new Prompt(options, {
+    inputter: this.inputter,
+    element: insert(this.inputElement,
+                    options.promptElement || null, 'gcli-prompt')
+  });
+
+  this.element = find(doc, options.displayElement || null, 'gcli-display');
+  this.element.classList.add('gcli-display');
+
+  this.template = util.toDom(doc, displayHtml);
+  this.elements = {};
+  domtemplate.template(this.template, this.elements, { stack: 'display.html' });
+  this.element.appendChild(this.template);
+
+  this.tooltip = new Tooltip(options, {
+    requisition: this.requisition,
+    inputter: this.inputter,
+    focusManager: this.focusManager,
+    element: this.elements.tooltip,
+    panelElement: this.elements.panel
+  });
+
+  this.inputter.tooltip = this.tooltip;
+
+  this.outputElement = util.createElement(doc, 'div');
+  this.outputElement.classList.add('gcli-output');
+  this.outputList = new OutputTerminal(options, {
+    requisition: this.requisition,
+    element: this.outputElement
+  });
+
+  this.element.appendChild(this.outputElement);
+
+  intro.maybeShowIntro(this.outputList.commandOutputManager, this.requisition);
+}
+
+/**
+ * Call the destroy functions of the components that we created
+ */
+Display.prototype.destroy = function() {
+  delete this.element;
+  delete this.template;
+
+  this.outputList.destroy();
+  delete this.outputList;
+  delete this.outputElement;
+
+  this.tooltip.destroy();
+  delete this.tooltip;
+
+  this.prompt.destroy();
+  delete this.prompt;
+
+  this.completer.destroy();
+  delete this.completer;
+  delete this.completeElement;
+
+  this.inputter.destroy();
+  delete this.inputter;
+  delete this.inputElement;
+
+  this.focusManager.destroy();
+  delete this.focusManager;
+
+  this.requisition.destroy();
+  delete this.requisition;
+
+  if (this.displayStyle) {
+    this.displayStyle.parentNode.removeChild(this.displayStyle);
+  }
+  delete this.displayStyle;
+};
+
+exports.Display = Display;
+
+/**
+ * Utility to help find an element by id, throwing if it wasn't found
+ */
+function find(doc, element, id) {
+  if (!element) {
+    element = doc.getElementById(id);
+    if (!element) {
+      throw new Error('Missing element, id=' + id);
+    }
+  }
+  return element;
+}
+
+/**
+ * Utility to help find an element by id, creating it if it wasn't found
+ */
+function insert(sibling, element, id) {
+  var doc = sibling.ownerDocument;
+  if (!element) {
+    element = doc.getElementById('gcli-row-complete');
+    if (!element) {
+      element = util.createElement(doc, 'div');
+      sibling.parentNode.insertBefore(element, sibling.nextSibling);
+      element.gcliCreated = true;
+    }
+  }
+  return element;
+}
+
+
+});
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/output_terminal', ['require', 'exports', 'module' , 'gcli/util', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/output_view.css', 'text!gcli/ui/output_terminal.html'], function(require, exports, module) {
+
+var util = require('gcli/util');
+
+var canon = require('gcli/canon');
+var domtemplate = require('gcli/ui/domtemplate');
+
+var outputViewCss = require('text!gcli/ui/output_view.css');
+var outputViewHtml = require('text!gcli/ui/output_terminal.html');
+
+
+/**
+ * A wrapper for a set of rows|command outputs.
+ * Register with the canon to be notified when commands have output to be
+ * displayed.
+ * @param options Object containing user customization properties, including:
+ * - commandOutputManager
+ * @param components Object that links to other UI components. GCLI provided:
+ * - element: Root element to populate
+ * - requisition (optional): A click/double-click to an input row causes the
+ *   command to be sent to the input/executed if we know the requisition use
+ */
+function OutputTerminal(options, components) {
+  this.element = components.element;
+  this.requisition = components.requisition;
+
+  this.commandOutputManager = options.commandOutputManager ||
+          canon.commandOutputManager;
+  this.commandOutputManager.onOutput.add(this.outputted, this);
+
+  var document = components.element.ownerDocument;
+  if (outputViewCss != null) {
+    this.style = util.importCss(outputViewCss, document, 'gcli-output-view');
+  }
+
+  this.template = util.toDom(document, outputViewHtml);
+  this.templateOptions = { allowEval: true, stack: 'output_terminal.html' };
+}
+
+/**
+ * Avoid memory leaks
+ */
+OutputTerminal.prototype.destroy = function() {
+  if (this.style) {
+    this.style.parentNode.removeChild(this.style);
+    delete this.style;
+  }
+
+  this.commandOutputManager.onOutput.remove(this.outputted, this);
+
+  delete this.commandOutputManager;
+  delete this.requisition;
+  delete this.element;
+  delete this.template;
+};
+
+/**
+ * Monitor for new command executions
+ */
+OutputTerminal.prototype.outputted = function(ev) {
+  if (ev.output.hidden) {
+    return;
+  }
+
+  ev.output.view = new OutputView(ev.output, this);
+};
+
+/**
+ * Display likes to be able to control the height of its children
+ */
+OutputTerminal.prototype.setHeight = function(height) {
+  this.element.style.height = height + 'px';
+};
+
+exports.OutputTerminal = OutputTerminal;
+
+
+/**
+ * Adds a row to the CLI output display
+ */
+function OutputView(outputData, outputTerminal) {
+  this.outputData = outputData;
+  this.outputTerminal = outputTerminal;
+
+  this.url = util.createUrlLookup(module);
+
+  // Elements attached to this by template().
+  this.elems = {
+    rowin: null,
+    rowout: null,
+    hide: null,
+    show: null,
+    duration: null,
+    throb: null,
+    prompt: null
+  };
+
+  var template = this.outputTerminal.template.cloneNode(true);
+  domtemplate.template(template, this, this.outputTerminal.templateOptions);
+
+  this.outputTerminal.element.appendChild(this.elems.rowin);
+  this.outputTerminal.element.appendChild(this.elems.rowout);
+
+  this.outputData.onClose.add(this.closed, this);
+  this.outputData.onChange.add(this.changed, this);
+}
+
+OutputView.prototype.destroy = function() {
+  this.outputData.onChange.remove(this.changed, this);
+  this.outputData.onClose.remove(this.closed, this);
+
+  this.outputTerminal.element.removeChild(this.elems.rowin);
+  this.outputTerminal.element.removeChild(this.elems.rowout);
+
+  delete this.outputData;
+  delete this.outputTerminal;
+  delete this.url;
+  delete this.elems;
+};
+
+/**
+ * Only display a prompt if there is a command, otherwise, leave blank
+ */
+Object.defineProperty(OutputView.prototype, 'prompt', {
+  get: function() {
+    return this.outputData.canonical ? '\u00bb' : '';
+  },
+  enumerable: true
+});
+
+/**
+ * A single click on an invocation line in the console copies the command
+ * to the command line
+ */
+OutputView.prototype.copyToInput = function() {
+  if (this.outputTerminal.requisition) {
+    this.outputTerminal.requisition.update(this.outputData.typed);
+  }
+};
+
+/**
+ * A double click on an invocation line in the console executes the command
+ */
+OutputView.prototype.execute = function(ev) {
+  if (this.outputTerminal.requisition) {
+    this.outputTerminal.requisition.exec({ typed: this.outputData.typed });
+  }
+};
+
+OutputView.prototype.hideOutput = function(ev) {
+  this.elems.rowout.style.display = 'none';
+  this.elems.hide.classList.add('cmd_hidden');
+  this.elems.show.classList.remove('cmd_hidden');
+
+  ev.stopPropagation();
+};
+
+OutputView.prototype.showOutput = function(ev) {
+  this.elems.rowout.style.display = 'block';
+  this.elems.hide.classList.remove('cmd_hidden');
+  this.elems.show.classList.add('cmd_hidden');
+
+  ev.stopPropagation();
+};
+
+OutputView.prototype.closed = function(ev) {
+  this.destroy();
+};
+
+OutputView.prototype.changed = function(ev) {
+  var document = this.elems.rowout.ownerDocument;
+  var duration = this.outputData.duration != null ?
+          'completed in ' + (this.outputData.duration / 1000) + ' sec ' :
+          '';
+  duration = document.createTextNode(duration);
+  this.elems.duration.appendChild(duration);
+
+  if (this.outputData.completed) {
+    this.elems.prompt.classList.add('gcli-row-complete');
+  }
+  if (this.outputData.error) {
+    this.elems.prompt.classList.add('gcli-row-error');
+  }
+
+  this.outputData.toDom(this.elems.rowout);
+
+  // We need to see the output of the latest command entered
+  // Certain browsers have a bug such that scrollHeight is too small
+  // when content does not fill the client area of the element
+  var scrollHeight = Math.max(this.outputTerminal.element.scrollHeight,
+      this.outputTerminal.element.clientHeight);
+  this.outputTerminal.element.scrollTop =
+      scrollHeight - this.outputTerminal.element.clientHeight;
+
+  this.elems.throb.style.display = this.outputData.completed ? 'none' : 'block';
+};
+
+exports.OutputView = OutputView;
+
+
+});
+define("text!gcli/ui/output_view.css", [], "\n" +
+  ".gcli-row-in {\n" +
+  "  padding: 0 4px;\n" +
+  "  box-shadow: 0 -6px 10px -6px #ddd;\n" +
+  "  border-top: 1px solid #bbb;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in > img {\n" +
+  "  cursor: pointer;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-hover {\n" +
+  "  display: none;\n" +
+  "  float: right;\n" +
+  "  padding: 2px 2px 0;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in:hover > .gcli-row-hover {\n" +
+  "  display: inline;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in:hover > .gcli-row-hover.gcli-row-hidden {\n" +
+  "  display: none;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-duration {\n" +
+  "  color: #666;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt {\n" +
+  "  color: #00F;\n" +
+  "  font-weight: bold;\n" +
+  "  font-size: 120%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt.gcli-row-complete {\n" +
+  "  color: #060;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt.gcli-row-error {\n" +
+  "  color: #F00;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-duration {\n" +
+  "  font-size: 80%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out {\n" +
+  "  margin: 0 10px 15px;\n" +
+  "  padding: 0 10px;\n" +
+  "  line-height: 1.2em;\n" +
+  "  font-size: 95%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out strong,\n" +
+  ".gcli-row-out b,\n" +
+  ".gcli-row-out th,\n" +
+  ".gcli-row-out h1,\n" +
+  ".gcli-row-out h2,\n" +
+  ".gcli-row-out h3 {\n" +
+  "  color: #000;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out p {\n" +
+  "  margin: 5px 0;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out a {\n" +
+  "  color: hsl(200,40%,40%);\n" +
+  "  text-decoration: none;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out a:hover {\n" +
+  "  cursor: pointer;\n" +
+  "  border-bottom: 1px dotted hsl(200,40%,60%);\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out input[type=password],\n" +
+  ".gcli-row-out input[type=text],\n" +
+  ".gcli-row-out textarea {\n" +
+  "  font-size: 120%;\n" +
+  "  background: transparent;\n" +
+  "  padding: 3px;\n" +
+  "  border-radius: 3px;\n" +
+  "  border: 1px solid #bbb;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out table,\n" +
+  ".gcli-row-out td,\n" +
+  ".gcli-row-out th {\n" +
+  "  border: 0;\n" +
+  "  padding: 0 2px;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-terminal,\n" +
+  ".gcli-row-subterminal {\n" +
+  "  border-radius: 3px;\n" +
+  "  border: 1px solid #ddd;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-terminal {\n" +
+  "  height: 200px;\n" +
+  "  width: 620px;\n" +
+  "  font-size: 80%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-subterminal {\n" +
+  "  height: 150px;\n" +
+  "  width: 300px;\n" +
+  "  font-size: 75%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-out-shortcut {\n" +
+  "  font-weight: normal;\n" +
+  "  border: 1px solid #999;\n" +
+  "  border-radius: 3px;\n" +
+  "  color: #666;\n" +
+  "  cursor: pointer;\n" +
+  "  padding: 0 3px 1px;\n" +
+  "  margin: 1px 4px;\n" +
+  "  display: inline-block;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-out-shortcut:before {\n" +
+  "  content: '\\bb';\n" +
+  "  padding-right: 2px;\n" +
+  "  color: hsl(25,78%,50%);\n" +
+  "  font-weight: bold;\n" +
+  "  font-size: 110%;\n" +
+  "}\n" +
+  "");
+
+define("text!gcli/ui/output_terminal.html", [], "\n" +
+  "<div class=\"gcli-row\">\n" +
+  "  <!-- The div for the input (i.e. what was typed) -->\n" +
+  "  <div class=\"gcli-row-in\" save=\"${elems.rowin}\" aria-live=\"assertive\"\n" +
+  "      onclick=\"${copyToInput}\" ondblclick=\"${execute}\">\n" +
+  "\n" +
+  "    <!-- What the user actually typed -->\n" +
+  "    <span save=\"${elems.prompt}\" class=\"gcli-row-prompt ${elems.error ? 'gcli-row-error' : ''} ${elems.completed ? 'gcli-row-complete' : ''}\">${prompt}</span>\n" +
+  "    <span class=\"gcli-row-in-typed\">${outputData.canonical}</span>\n" +
+  "\n" +
+  "    <!-- The extra details that appear on hover -->\n" +
+  "    <span class=\"gcli-row-duration gcli-row-hover\" save=\"${elems.duration}\"></span>\n" +
+  "    <!--\n" +
+  "    <img class=\"gcli-row-hover\" onclick=\"${hideOutput}\" save=\"${elems.hide}\"\n" +
+  "        alt=\"Hide command output\" _src=\"${url('images/minus.png')}\"/>\n" +
+  "    <img class=\"gcli-row-hover gcli-row-hidden\" onclick=\"${showOutput}\" save=\"${elems.show}\"\n" +
+  "        alt=\"Show command output\" _src=\"${url('images/plus.png')}\"/>\n" +
+  "    <img class=\"gcli-row-hover\" onclick=\"${remove}\"\n" +
+  "        alt=\"Remove this command from the history\"\n" +
+  "        _src=\"${url('images/closer.png')}\"/>\n" +
+  "    -->\n" +
+  "    <img style=\"float:right;\" _src=\"${url('images/throbber.gif')}\" save=\"${elems.throb}\"/>\n" +
+  "  </div>\n" +
+  "\n" +
+  "  <!-- The div for the command output -->\n" +
+  "  <div class=\"gcli-row-out\" aria-live=\"assertive\" save=\"${elems.rowout}\">\n" +
+  "  </div>\n" +
+  "</div>\n" +
+  "");
+
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/prompt', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * Prompt is annoying because some systems provide a UI elements (i.e. firefox)
+ * while some expect you to overlay them on an input element (i.e. the web)
+ * Also we want to provide click -> show menu ability.
+ * @param options Object containing user customization properties, including:
+ * - promptChar (default='\u00bb') (double greater-than, a.k.a right guillemet)
+ *   The prompt is used directly in a TextNode, so no HTML entities.
+ * @param components Object that links to other UI components. GCLI provided:
+ * - element
+ * - inputter
+ */
+function Prompt(options, components) {
+  this.element = components.element;
+  this.element.classList.add('gcli-prompt');
+
+  var prompt = options.promptChar || '\u00bb';
+  var text = this.element.ownerDocument.createTextNode(prompt);
+  this.element.appendChild(text);
+
+  this.inputter = components.inputter;
+  if (this.inputter) {
+    this.inputter.onResize.add(this.resized, this);
+
+    var dimensions = this.inputter.getDimensions();
+    if (dimensions) {
+      this.resized(dimensions);
+    }
+  }
+}
+
+/**
+ * Avoid memory leaks
+ */
+Prompt.prototype.destroy = function() {
+  if (this.inputter) {
+    this.inputter.onResize.remove(this.resized, this);
+  }
+
+  delete this.element;
+};
+
+/**
+ * Ensure that the completion element is the same size and the inputter element
+ */
+Prompt.prototype.resized = function(ev) {
+  this.element.style.top = ev.top + 'px';
+  this.element.style.height = ev.height + 'px';
+  this.element.style.left = ev.left + 'px';
+  this.element.style.width = ev.width + 'px';
+};
+
+exports.Prompt = Prompt;
+
+
+});
+define("text!gcli/ui/display.css", [], "\n" +
+  ".gcli-output {\n" +
+  "  height: 100%;\n" +
+  "  overflow-x: hidden;\n" +
+  "  overflow-y: auto;\n" +
+  "  font-family: Segoe UI, Helvetica Neue, Verdana, Arial, sans-serif;\n" +
+  "}\n" +
+  "");
+
+define("text!gcli/ui/display.html", [], "\n" +
+  "<div class=\"gcli-panel\" save=\"${panel}\">\n" +
+  "  <div save=\"${tooltip}\"></div>\n" +
+  "  <div class=\"gcli-panel-connector\"></div>\n" +
+  "</div>\n" +
+  "");
+
 
 let testModuleNames = [
   'gclitest/index',
   'gclitest/suite',
   'test/examiner',
   'test/assert',
   'test/status',
   'gclitest/testCanon',
   'gclitest/helpers',
   'gclitest/testCli',
   'gclitest/mockCommands',
   'gclitest/testCompletion',
   'gclitest/testExec',
   'gclitest/testHelp',
   'gclitest/testHistory',
   'gclitest/testInputter',
+  'gclitest/testIncomplete',
   'gclitest/testIntro',
   'gclitest/testJs',
   'gclitest/testKeyboard',
   'gclitest/testPref',
   'gclitest/mockSettings',
   'gclitest/testRequire',
   'gclitest/requirable',
   'gclitest/testResource',
   'gclitest/testScratchpad',
   'gclitest/testSettings',
   'gclitest/testSpell',
   'gclitest/testSplit',
   'gclitest/testTokenize',
   'gclitest/testTooltip',
   'gclitest/testTypes',
   'gclitest/testUtil',
+  'gcli/ui/display',
+  'gcli/ui/output_terminal',
+  'text!gcli/ui/output_view.css',
+  'text!gcli/ui/output_terminal.html',
+  'gcli/ui/prompt',
+  'text!gcli/ui/display.css',
+  'text!gcli/ui/display.html',
 ];
 
 // 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({
+    var examiner = define.globalDomain.require('gclitest/suite').examiner;
+    examiner.runAsync({
       display: DeveloperToolbar.display,
       isFirefox: true,
       window: browser.contentDocument.defaultView
-    });
-    finish();
+    }, finish);
   });
 }
 
 registerCleanupFunction(function() {
   testModuleNames.forEach(function(moduleName) {
     delete localDefine.modules[moduleName];
     delete localDefine.globalDomain.modules[moduleName];
   });
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -41,280 +41,368 @@ registerCleanupFunction(function tearDow
 });
 
 /**
  * Various functions for testing DeveloperToolbar.
  * Parts of this code exist in:
  * - browser/devtools/commandline/test/head.js
  * - browser/devtools/shared/test/head.js
  */
-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);
-    }
-  },
+let DeveloperToolbarTest = { };
+
+/**
+ * Paranoid DeveloperToolbar.show();
+ */
+DeveloperToolbarTest.show = function DTT_show(aCallback) {
+  if (DeveloperToolbar.visible) {
+    ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
+  }
+  else {
+    DeveloperToolbar.show(true, aCallback);
+  }
+};
+
+/**
+ * Paranoid DeveloperToolbar.hide();
+ */
+DeveloperToolbarTest.hide = function DTT_hide() {
+  if (!DeveloperToolbar.visible) {
+    ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar");
+  }
+  else {
+    DeveloperToolbar.display.inputter.setInput("");
+    DeveloperToolbar.hide();
+  }
+};
+
+/**
+ * check() is the new status. Similar API except that it doesn't attempt to
+ * alter the display/requisition at all, and it makes extra checks.
+ * Test inputs
+ *   typed: The text to type at the input
+ * Available checks:
+ *   input: The text displayed in the input field
+ *   cursor: The position of the start of the cursor
+ *   status: One of "VALID", "ERROR", "INCOMPLETE"
+ *   emptyParameters: Array of parameters still to type. e.g. [ "<message>" ]
+ *   directTabText: Simple completion text
+ *   arrowTabText: When the completion is not an extension (without arrow)
+ *   markup: What state should the error markup be in. e.g. "VVVIIIEEE"
+ *   args: Maps of checks to make against the arguments:
+ *     value: i.e. assignment.value (which ignores defaultValue)
+ *     type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned
+ *           Care should be taken with this since it's something of an
+ *           implementation detail
+ *     arg: The toString value of the argument
+ *     status: i.e. assignment.getStatus
+ *     message: i.e. assignment.getMessage
+ *     name: For commands - checks assignment.value.name
+ */
+DeveloperToolbarTest.checkInputStatus = function DTT_checkInputStatus(checks) {
+  if (!checks.emptyParameters) {
+    checks.emptyParameters = [];
+  }
+  if (!checks.directTabText) {
+    checks.directTabText = '';
+  }
+  if (!checks.arrowTabText) {
+    checks.arrowTabText = '';
+  }
 
-  /**
-   * 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();
+  var display = DeveloperToolbar.display;
+
+  if (checks.typed) {
+    display.inputter.setInput(checks.typed);
+  }
+  else {
+    ok(false, "Missing typed for " + JSON.stringify(checks));
+    return;
+  }
+
+  if (checks.cursor) {
+    display.inputter.setCursor(checks.cursor)
+  }
+
+  var cursor = checks.cursor ? checks.cursor.start : checks.typed.length;
+
+  var requisition = display.requisition;
+  var completer = display.completer;
+  var actual = completer._getCompleterTemplateData();
+
+  /*
+  if (checks.input) {
+    is(display.inputter.element.value,
+            checks.input,
+            'input');
+  }
+
+  if (checks.cursor) {
+    is(display.inputter.element.selectionStart,
+            checks.cursor,
+            'cursor');
+  }
+  */
+
+  if (checks.status) {
+    is(requisition.getStatus().toString(),
+            checks.status,
+            'status');
+  }
+
+  if (checks.markup) {
+    var statusMarkup = requisition.getInputStatusMarkup(cursor);
+    var actualMarkup = statusMarkup.map(function(s) {
+      return Array(s.string.length + 1).join(s.status.toString()[0]);
+    }).join('');
+
+    is(checks.markup,
+            actualMarkup,
+            'markup');
+  }
+
+  if (checks.emptyParameters) {
+    var actualParams = actual.emptyParameters;
+    is(actualParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length');
+
+    if (actualParams.length === checks.emptyParameters.length) {
+      for (var i = 0; i < actualParams.length; i++) {
+        is(actualParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + ']');
+      }
     }
-  },
+  }
+
+  if (checks.directTabText) {
+    is(actual.directTabText,
+            checks.directTabText,
+            'directTabText');
+  }
+
+  if (checks.arrowTabText) {
+    is(actual.arrowTabText,
+            ' \u00a0\u21E5 ' + checks.arrowTabText,
+            'arrowTabText');
+  }
+
+  if (checks.args) {
+    Object.keys(checks.args).forEach(function(paramName) {
+      var check = checks.args[paramName];
+
+      var assignment;
+      if (paramName === 'command') {
+        assignment = requisition.commandAssignment;
+      }
+      else {
+        assignment = requisition.getAssignment(paramName);
+      }
+
+      if (assignment == null) {
+        ok(false, 'Unknown parameter: ' + paramName);
+        return;
+      }
+
+      if (check.value) {
+        is(assignment.value,
+                check.value,
+                'checkStatus value for ' + paramName);
+      }
+
+      if (check.name) {
+        is(assignment.value.name,
+                check.name,
+                'checkStatus name for ' + paramName);
+      }
+
+      if (check.type) {
+        is(assignment.arg.type,
+                check.type,
+                'checkStatus type for ' + paramName);
+      }
+
+      if (check.arg) {
+        is(assignment.arg.toString(),
+                check.arg,
+                'checkStatus arg for ' + paramName);
+      }
+
+      if (check.status) {
+        is(assignment.getStatus().toString(),
+                check.status,
+                'checkStatus status for ' + paramName);
+      }
+
+      if (check.message) {
+        is(assignment.getMessage(),
+                check.message,
+                'checkStatus message for ' + paramName);
+      }
+    });
+  }
+};
 
-  /**
-   * 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
-   *   markup: "VVVIIIEEE",    // What state should the error markup be in
-   * });
-   */
-  checkInputStatus: function DTT_checkInputStatus(tests) {
-    let display = DeveloperToolbar.display;
+/**
+ * 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$/,     // RegExp to test against textContent of output
+ *                            // (can also be array of RegExps)
+ *   blankOutput: true,       // Special checks when there is no output
+ * });
+ */
+DeveloperToolbarTest.exec = function DTT_exec(tests) {
+  tests = tests || {};
+
+  if (tests.typed) {
+    DeveloperToolbar.display.inputter.setInput(tests.typed);
+  }
+
+  let typed = DeveloperToolbar.display.inputter.getInputState().typed;
+  let output = DeveloperToolbar.display.requisition.exec();
+
+  is(typed, output.typed, 'output.command for: ' + typed);
+
+  if (tests.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 (tests.args != null) {
+    is(Object.keys(tests.args).length, Object.keys(output.args).length,
+       'arg count for ' + typed);
 
-    if (tests.typed) {
-      display.inputter.setInput(tests.typed);
+    Object.keys(output.args).forEach(function(arg) {
+      let expectedArg = tests.args[arg];
+      let actualArg = output.args[arg];
+
+      if (typeof expectedArg === 'function') {
+        ok(expectedArg(actualArg), 'failed test func. ' + typed + '/' + arg);
+      }
+      else {
+        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 (tests.outputMatch) {
+    function doTest(match, against) {
+      if (!match.test(against)) {
+        ok(false, "html output for " + typed + " against " + match.source +
+                " (textContent sent to info)");
+        info("Actual textContent");
+        info(against);
+      }
+    }
+    if (Array.isArray(tests.outputMatch)) {
+      tests.outputMatch.forEach(function(match) {
+        doTest(match, displayed);
+      });
     }
     else {
-      ok(false, "Missing typed for " + JSON.stringify(tests));
-      return;
-    }
-
-    if (tests.cursor) {
-      display.inputter.setCursor(tests.cursor)
+      doTest(tests.outputMatch, displayed);
     }
-
-    if (tests.status) {
-      is(display.requisition.getStatus().toString(),
-              tests.status, "status for " + tests.typed);
-    }
-
-    if (tests.emptyParameters == null) {
-      tests.emptyParameters = [];
-    }
+  }
 
-    let realParams = display.completer.emptyParameters;
-    is(realParams.length, tests.emptyParameters.length,
-            'emptyParameters.length for \'' + tests.typed + '\'');
-
-    if (realParams.length === tests.emptyParameters.length) {
-      for (let i = 0; i < realParams.length; i++) {
-        is(realParams[i].replace(/\u00a0/g, ' '), tests.emptyParameters[i],
-                'emptyParameters[' + i + '] for \'' + tests.typed + '\'');
-      }
+  if (tests.blankOutput != null) {
+    if (!/^$/.test(displayed)) {
+      ok(false, "html output for " + typed + " (textContent sent to info)");
+      info("Actual textContent");
+      info(displayed);
     }
-
-    if (tests.directTabText) {
-      is(display.completer.directTabText, tests.directTabText,
-              'directTabText for \'' + tests.typed + '\'');
-    }
-    else {
-      is(display.completer.directTabText, '',
-              'directTabText for \'' + tests.typed + '\'');
-    }
+  }
+};
 
-    if (tests.arrowTabText) {
-      is(display.completer.arrowTabText, ' \u00a0\u21E5 ' + tests.arrowTabText,
-              'arrowTabText for \'' + tests.typed + '\'');
-    }
-    else {
-      is(display.completer.arrowTabText, '',
-              'arrowTabText for \'' + tests.typed + '\'');
-    }
-
-    if (tests.markup) {
-      let cursor = tests.cursor ? tests.cursor.start : tests.typed.length;
-      let statusMarkup = display.requisition.getInputStatusMarkup(cursor);
-      let actualMarkup = statusMarkup.map(function(s) {
-        return Array(s.string.length + 1).join(s.status.toString()[0]);
-      }).join('');
-      is(tests.markup, actualMarkup, 'markup for ' + tests.typed);
-    }
-  },
+/**
+ * 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.
+ */
+DeveloperToolbarTest.test = function DTT_test(uri, testFunc) {
+  let menuItem = document.getElementById("menu_devToolbar");
+  let command = document.getElementById("Tools:DevToolbar");
+  let appMenuItem = document.getElementById("appmenu_devToolbar");
 
-  /**
-   * 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$/,     // RegExp to test against textContent of output
-   *                            // (can also be array of RegExps)
-   *   blankOutput: true,       // Special checks when there is no output
-   * });
-   */
-  exec: function DTT_exec(tests) {