Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 17 Mar 2015 11:59:24 +0100
changeset 234073 c912d22bfe3deae3d34ef8d7710654c418df3362
parent 234072 7fa0f476f6346d68251f283de1883d456ee75c3b (current diff)
parent 234008 008b3f65a7e0471abb6887812ee56dd87cc528df (diff)
child 234074 916906f39e5cb4cf38e81334b066b5b939095663
push id28429
push userryanvm@gmail.com
push dateTue, 17 Mar 2015 18:25:14 +0000
treeherdermozilla-central@e965a1a534ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone39.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 to b2g-inbound
browser/components/loop/test/shared/vendor/chai-1.9.0.js
browser/components/loop/test/shared/vendor/mocha-2.0.1.css
browser/components/loop/test/shared/vendor/mocha-2.0.1.js
browser/components/loop/test/shared/vendor/sinon-1.12.2.js
browser/devtools/inspector/test/browser_inspector_menu-01.js
browser/devtools/inspector/test/browser_inspector_menu-02.js
browser/devtools/inspector/test/doc_inspector_menu-01.html
browser/devtools/inspector/test/doc_inspector_menu-02.html
dom/media/MediaDecoderStateMachineScheduler.cpp
dom/media/MediaDecoderStateMachineScheduler.h
dom/tests/mochitest/fetch/test_fetch_basic_worker.html
dom/tests/mochitest/fetch/test_headers_mainthread.js
dom/tests/mochitest/fetch/worker_test_fetch_basic.js
dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js
dom/tests/mochitest/fetch/worker_test_fetch_cors.js
dom/workers/test/fetch/mochitest.ini
dom/workers/test/fetch/test_interfaces.html
dom/workers/test/fetch/test_request.html
dom/workers/test/fetch/test_response.html
dom/workers/test/fetch/worker_interfaces.js
dom/workers/test/fetch/worker_test_request.js
dom/workers/test/fetch/worker_test_response.js
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -131,89 +131,89 @@ MustBeAccessible(nsIContent* aContent, D
     return aDocument->IsDependentID(id);
 
   return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Accessible constructors
 
-Accessible*
+static Accessible*
 New_HTMLLink(nsIContent* aContent, Accessible* aContext)
 {
   // Only some roles truly enjoy life as HTMLLinkAccessibles, for details
   // see closed bug 494807.
   nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aContent);
   if (roleMapEntry && roleMapEntry->role != roles::NOTHING &&
       roleMapEntry->role != roles::LINK) {
     return new HyperTextAccessibleWrap(aContent, aContext->Document());
   }
 
   return new HTMLLinkAccessible(aContent, aContext->Document());
 }
 
-Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
   { return new HyperTextAccessibleWrap(aContent, aContext->Document()); }
 
-Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigureAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
   { return new HTMLLegendAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
   { return new HTMLListAccessible(aContent, aContext->Document()); }
 
-Accessible*
+static Accessible*
 New_HTMLListitem(nsIContent* aContent, Accessible* aContext)
 {
   // If list item is a child of accessible list then create an accessible for
   // it unconditionally by tag name. nsBlockFrame creates the list item
   // accessible for other elements styled as list items.
   if (aContext->IsList() && aContext->GetContent() == aContent->GetParent())
     return new HTMLLIAccessible(aContent, aContext->Document());
 
   return nullptr;
 }
 
-Accessible*
+static Accessible*
 New_HTMLDefinition(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsList())
     return new HyperTextAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
-Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
   { return new HTMLLabelAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
   { return new HTMLOutputAccessible(aContent, aContext->Document()); }
 
-Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
+static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
   { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); }
 
-Accessible*
+static Accessible*
 New_HTMLTableHeaderCell(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent())
     return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
-Accessible*
+static Accessible*
 New_HTMLTableHeaderCellIfScope(nsIContent* aContent, Accessible* aContext)
 {
   if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent() &&
       aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope))
     return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
   return nullptr;
 }
 
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -37,16 +37,19 @@ public:
     MOZ_ASSERT(!mWrapper);
   }
 
   void AddChildAt(uint32_t aIdx, ProxyAccessible* aChild)
   { mChildren.InsertElementAt(aIdx, aChild); }
 
   uint32_t ChildrenCount() const { return mChildren.Length(); }
   ProxyAccessible* ChildAt(uint32_t aIdx) const { return mChildren[aIdx]; }
+
+  // XXX evaluate if this is fast enough.
+  size_t IndexInParent() const { return mParent->mChildren.IndexOf(this); }
   bool MustPruneChildren() const;
 
   void Shutdown();
 
   void SetChildDoc(DocAccessibleParent*);
 
   /**
    * Remove The given child.
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -296,46 +296,46 @@ this.Utils = { // jshint ignore:line
   getVirtualCursor: function getVirtualCursor(aDocument) {
     let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
       this.AccRetrieval.getAccessibleFor(aDocument);
 
     return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
   },
 
   getContentResolution: function _getContentResolution(aAccessible) {
-    let resX = { value: 1 }, resY = { value: 1 };
+    let res = { value: 1 };
     aAccessible.document.window.QueryInterface(
       Ci.nsIInterfaceRequestor).getInterface(
-      Ci.nsIDOMWindowUtils).getResolution(resX, resY);
-    return [resX.value, resY.value];
+      Ci.nsIDOMWindowUtils).getResolution(res);
+    return res.value;
   },
 
   getBounds: function getBounds(aAccessible, aPreserveContentScale) {
     let objX = {}, objY = {}, objW = {}, objH = {};
     aAccessible.getBounds(objX, objY, objW, objH);
 
-    let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] :
+    let scale = aPreserveContentScale ? 1 :
       this.getContentResolution(aAccessible);
 
     return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
-      scaleX, scaleY);
+      scale, scale);
   },
 
   getTextBounds: function getTextBounds(aAccessible, aStart, aEnd,
                                         aPreserveContentScale) {
     let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
     let objX = {}, objY = {}, objW = {}, objH = {};
     accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
       Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
 
-    let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] :
+    let scale = aPreserveContentScale ? 1 :
       this.getContentResolution(aAccessible);
 
     return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
-      scaleX, scaleY);
+      scale, scale);
   },
 
   /**
    * Get current display DPI.
    */
   get dpi() {
     delete this.dpi;
     this.dpi = this.winUtils.displayDPI;
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -365,17 +365,18 @@ function testAccessibleTree(aAccOrElmOrI
     accTree = {
       role: nsIAccessibleRole[roleName],
       children: accTree[key]
     };
   }
 
   // Test accessible properties.
   for (var prop in accTree) {
-    var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
+    var msg = "Wrong value of property '" + prop + "' for " +
+               prettyName(acc) + ".";
 
     switch (prop) {
     case "actions": {
       testActionNames(acc, accTree.actions);
       break;
     }
 
     case "attributes":
@@ -446,20 +447,39 @@ function testAccessibleTree(aAccOrElmOrI
     }
   }
 
   // Test children.
   if ("children" in accTree && accTree["children"] instanceof Array) {
     var children = acc.children;
     var childCount = children.length;
 
-    is(childCount, accTree.children.length,
-       "Different amount of expected children of " + prettyName(acc) + ".");
 
-    if (accTree.children.length == childCount) {
+    if (accTree.children.length != childCount) {
+      for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
+        var accChild;
+        try {
+          accChild = children.queryElementAt(i, nsIAccessible);
+          if (!accTree.children[i]) {
+            ok(false, prettyName(acc) + " has an extra child at index " + i +
+              " : " + prettyName(accChild));
+          }
+          if (accChild.role !== accTree.children[i].role) {
+            ok(false, prettyName(accTree) + " and " + prettyName(acc) +
+              " have different children at index " + i + " : " +
+              prettyName(accTree.children[i]) + ", " + prettyName(accChild));
+          }
+          info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
+               " child at index " + i + " : " + prettyName(accChild));
+        } catch (e) {
+          ok(false, prettyName(accTree) + " has an extra child at index " + i +
+             " : " + prettyName(accTree.children[i]));
+        }
+      }
+    } else {
       if (aFlags & kSkipTreeFullCheck) {
         for (var i = 0; i < childCount; i++) {
           var child = children.queryElementAt(i, nsIAccessible);
           testAccessibleTree(child, accTree.children[i], aFlags);
         }
         return;
       }
 
@@ -532,17 +552,18 @@ function isAccessibleInCache(aNodeOrId)
  * @param  aNodeOrId  [in] the DOM node identifier for the defunct accessible
  */
 function testDefunctAccessible(aAcc, aNodeOrId)
 {
   if (aNodeOrId)
     ok(!isAccessible(aNodeOrId),
        "Accessible for " + aNodeOrId + " wasn't properly shut down!");
 
-  var msg = " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
+  var msg = " doesn't fail for shut down accessible " +
+             prettyName(aNodeOrId) + "!";
 
   // firstChild
   var success = false;
   try {
     aAcc.firstChild;
   } catch (e) {
     success = (e.result == Components.results.NS_ERROR_FAILURE)
   }
@@ -715,16 +736,20 @@ function prettyName(aIdentifier)
     msg += "]";
 
     return msg;
   }
 
   if (aIdentifier instanceof nsIDOMNode)
     return "[ " + getNodePrettyName(aIdentifier) + " ]";
 
+  if (aIdentifier && typeof aIdentifier === "object" ) {
+    return JSON.stringify(aIdentifier);
+  }
+
   return " '" + aIdentifier + "' ";
 }
 
 /**
  * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
  * @param aString the string to shorten.
  * @returns the shortened string.
  */
--- a/accessible/windows/ia2/ia2Accessible.cpp
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -11,24 +11,27 @@
 #include "AccessibleRole.h"
 #include "AccessibleStates.h"
 
 #include "Compatibility.h"
 #include "ia2AccessibleRelation.h"
 #include "IUnknownImpl.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleTypes.h"
+#include "mozilla/a11y/PDocAccessible.h"
 #include "Relation.h"
 
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
+template<typename String> static void EscapeAttributeChars(String& aStr);
+
 ////////////////////////////////////////////////////////////////////////////////
 // ia2Accessible
 ////////////////////////////////////////////////////////////////////////////////
 
 STDMETHODIMP
 ia2Accessible::QueryInterface(REFIID iid, void** ppv)
 {
   if (!ppv)
@@ -60,16 +63,25 @@ ia2Accessible::get_nRelations(long* aNRe
   if (!aNRelations)
     return E_INVALIDARG;
   *aNRelations = 0;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    // XXX evaluate performance of collecting all relation targets.
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+    *aNRelations = types.Length();
+    return S_OK;
+  }
+
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     Relation rel = acc->RelationByType(sRelationTypePairs[idx].first);
     if (rel.Next())
       (*aNRelations)++;
   }
@@ -79,24 +91,52 @@ ia2Accessible::get_nRelations(long* aNRe
 }
 
 STDMETHODIMP
 ia2Accessible::get_relation(long aRelationIndex,
                             IAccessibleRelation** aRelation)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aRelation)
+  if (!aRelation || aRelationIndex < 0)
     return E_INVALIDARG;
   *aRelation = nullptr;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+
+    size_t targetSetCount = targetSets.Length();
+    for (size_t i = 0; i < targetSetCount; i++) {
+      uint32_t relTypeIdx = static_cast<uint32_t>(types[i]);
+      MOZ_ASSERT(sRelationTypePairs[relTypeIdx].first == types[i]);
+      if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL)
+        continue;
+
+      if (static_cast<size_t>(aRelationIndex) == i) {
+        nsTArray<nsRefPtr<Accessible>> targets;
+        size_t targetCount = targetSets[i].Length();
+        for (size_t j = 0; j < targetCount; j++)
+          targets.AppendElement(WrapperFor(targetSets[i][j]));
+
+        nsRefPtr<ia2AccessibleRelation> rel =
+          new ia2AccessibleRelation(types[i], Move(targets));
+        rel.forget(aRelation);
+        return S_OK;
+      }
+    }
+
+    return E_INVALIDARG;
+  }
+
   long relIdx = 0;
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     RelationType relationType = sRelationTypePairs[idx].first;
     Relation rel = acc->RelationByType(relationType);
     nsRefPtr<ia2AccessibleRelation> ia2Relation =
@@ -118,24 +158,52 @@ ia2Accessible::get_relation(long aRelati
 
 STDMETHODIMP
 ia2Accessible::get_relations(long aMaxRelations,
                              IAccessibleRelation** aRelation,
                              long *aNRelations)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aRelation || !aNRelations)
+  if (!aRelation || !aNRelations || aMaxRelations <= 0)
     return E_INVALIDARG;
   *aNRelations = 0;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
+  if (acc->IsProxy()) {
+    nsTArray<RelationType> types;
+    nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+    acc->Proxy()->Relations(&types, &targetSets);
+
+    size_t count = std::min(targetSets.Length(),
+                            static_cast<size_t>(aMaxRelations));
+    size_t i = 0;
+    while (i < count) {
+      uint32_t relTypeIdx = static_cast<uint32_t>(types[i]);
+      if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL)
+        continue;
+
+      size_t targetCount = targetSets[i].Length();
+      nsTArray<nsRefPtr<Accessible>> targets(targetCount);
+      for (size_t j = 0; j < targetCount; j++)
+        targets.AppendElement(WrapperFor(targetSets[i][j]));
+
+      nsRefPtr<ia2AccessibleRelation> rel =
+        new ia2AccessibleRelation(types[i], Move(targets));
+      rel.forget(aRelation + i);
+      i++;
+    }
+
+    *aNRelations = i;
+    return S_OK;
+  }
+
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs) &&
        *aNRelations < aMaxRelations; idx++) {
     if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
       continue;
 
     RelationType relationType = sRelationTypePairs[idx].first;
     Relation rel = acc->RelationByType(relationType);
     nsRefPtr<ia2AccessibleRelation> ia2Rel =
@@ -164,31 +232,41 @@ ia2Accessible::role(long* aRole)
     return CO_E_OBJNOTCONNECTED;
 
 #define ROLE(_geckoRole, stringRole, atkRole, macRole, \
              msaaRole, ia2Role, nameRule) \
   case roles::_geckoRole: \
     *aRole = ia2Role; \
     break;
 
-  a11y::role geckoRole = acc->Role();
+  a11y::role geckoRole;
+  if (acc->IsProxy())
+    geckoRole = acc->Proxy()->Role();
+  else
+    geckoRole = acc->Role();
   switch (geckoRole) {
 #include "RoleMap.h"
     default:
       MOZ_CRASH("Unknown role.");
   };
 
 #undef ROLE
 
   // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call
   // the IA2 role a ROLE_OUTLINEITEM.
-  if (geckoRole == roles::ROW) {
-    Accessible* xpParent = acc->Parent();
-    if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+  if (acc->IsProxy()) {
+    if (geckoRole == roles::ROW && acc->Proxy()->Parent() &&
+        acc->Proxy()->Parent()->Role() == roles::TREE_TABLE)
       *aRole = ROLE_SYSTEM_OUTLINEITEM;
+  } else {
+    if (geckoRole == roles::ROW) {
+      Accessible* xpParent = acc->Parent();
+      if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+        *aRole = ROLE_SYSTEM_OUTLINEITEM;
+    }
   }
 
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
 STDMETHODIMP
@@ -269,17 +347,26 @@ ia2Accessible::get_states(AccessibleStat
 
   if (!aStates)
     return E_INVALIDARG;
   *aStates = 0;
 
   // XXX: bug 344674 should come with better approach that we have here.
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
-  uint64_t state = acc->State();
+  if (acc->IsDefunct()) {
+    *aStates = IA2_STATE_DEFUNCT;
+    return CO_E_OBJNOTCONNECTED;
+  }
+
+  uint64_t state;
+  if (acc->IsProxy())
+    state = acc->Proxy()->State();
+  else
+    state = acc->State();
 
   if (state & states::INVALID)
     *aStates |= IA2_STATE_INVALID_ENTRY;
   if (state & states::REQUIRED)
     *aStates |= IA2_STATE_REQUIRED;
 
   // The following IA2 states are not supported by Gecko
   // IA2_STATE_ARMED
@@ -441,17 +528,21 @@ ia2Accessible::get_indexInParent(long* a
   if (!aIndexInParent)
     return E_INVALIDARG;
   *aIndexInParent = -1;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  *aIndexInParent = acc->IndexInParent();
+  if (acc->IsProxy())
+    *aIndexInParent = acc->Proxy()->IndexInParent();
+  else
+    *aIndexInParent = acc->IndexInParent();
+
   if (*aIndexInParent == -1)
     return S_FALSE;
 
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
@@ -516,18 +607,39 @@ ia2Accessible::get_attributes(BSTR* aAtt
   *aAttributes = nullptr;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   // The format is name:value;name:value; with \ for escaping these
   // characters ":;=,\".
-  nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
-  return ConvertToIA2Attributes(attributes, aAttributes);
+  if (!acc->IsProxy()) {
+    nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
+    return ConvertToIA2Attributes(attributes, aAttributes);
+  }
+
+  nsTArray<Attribute> attrs;
+  acc->Proxy()->Attributes(&attrs);
+  nsString attrStr;
+  size_t attrCount = attrs.Length();
+  for (size_t i = 0; i < attrCount; i++) {
+    EscapeAttributeChars(attrs[i].Name());
+    EscapeAttributeChars(attrs[i].Value());
+    AppendUTF8toUTF16(attrs[i].Name(), attrStr);
+    attrStr.Append(':');
+    attrStr.Append(attrs[i].Value());
+    attrStr.Append(';');
+  }
+
+  if (attrStr.IsEmpty())
+    return S_FALSE;
+
+  *aAttributes = ::SysAllocStringLen(attrStr.get(), attrStr.Length());
+  return *aAttributes ? S_OK : E_OUTOFMEMORY;
 
   A11Y_TRYBLOCK_END
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // IAccessible2_2
 
 STDMETHODIMP
@@ -562,17 +674,17 @@ ia2Accessible::get_accessibleWithCaret(I
 STDMETHODIMP
 ia2Accessible::get_relationTargetsOfType(BSTR aType,
                                          long aMaxTargets,
                                          IUnknown*** aTargets,
                                          long* aNTargets)
 {
   A11Y_TRYBLOCK_BEGIN
 
-  if (!aTargets || !aNTargets)
+  if (!aTargets || !aNTargets || aMaxTargets < 0)
     return E_INVALIDARG;
   *aNTargets = 0;
 
   Maybe<RelationType> relationType;
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) {
       relationType.emplace(sRelationTypePairs[idx].first);
       break;
@@ -580,23 +692,33 @@ ia2Accessible::get_relationTargetsOfType
   }
   if (!relationType)
     return E_INVALIDARG;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
   if (acc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  Relation rel = acc->RelationByType(*relationType);
+  nsTArray<Accessible*> targets;
+  if (acc->IsProxy()) {
+    nsTArray<ProxyAccessible*> targetProxies =
+      acc->Proxy()->RelationByType(*relationType);
 
-  nsTArray<Accessible*> targets;
-  Accessible* target = nullptr;
-  while ((target = rel.Next()) &&
-         static_cast<long>(targets.Length()) <= aMaxTargets)
-    targets.AppendElement(target);
+    size_t targetCount = aMaxTargets;
+    if (targetProxies.Length() < targetCount)
+      targetCount = targetProxies.Length();
+    for (size_t i = 0; i < targetCount; i++)
+      targets.AppendElement(WrapperFor(targetProxies[i]));
+  } else {
+    Relation rel = acc->RelationByType(*relationType);
+    Accessible* target = nullptr;
+    while ((target = rel.Next()) &&
+           static_cast<long>(targets.Length()) <= aMaxTargets)
+      targets.AppendElement(target);
+  }
 
   *aNTargets = targets.Length();
   *aTargets = static_cast<IUnknown**>(
     ::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets));
   if (!*aTargets)
     return E_OUTOFMEMORY;
 
   for (int32_t i = 0; i < *aNTargets; i++) {
@@ -608,16 +730,28 @@ ia2Accessible::get_relationTargetsOfType
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Helpers
 
+template<typename String>
+static inline void
+EscapeAttributeChars(String& aStr)
+{
+  int32_t offset = 0;
+  static const char kCharsToEscape[] = ":;=,\\";
+  while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
+    aStr.Insert('\\', offset);
+    offset += 2;
+  }
+}
+
 HRESULT
 ia2Accessible::ConvertToIA2Attributes(nsIPersistentProperties* aAttributes,
                                       BSTR* aIA2Attributes)
 {
   *aIA2Attributes = nullptr;
 
   // The format is name:value;name:value; with \ for escaping these
   // characters ":;=,\".
@@ -627,46 +761,36 @@ ia2Accessible::ConvertToIA2Attributes(ns
 
   nsCOMPtr<nsISimpleEnumerator> propEnum;
   aAttributes->Enumerate(getter_AddRefs(propEnum));
   if (!propEnum)
     return E_FAIL;
 
   nsAutoString strAttrs;
 
-  const char kCharsToEscape[] = ":;=,\\";
-
   bool hasMore = false;
   while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
     nsCOMPtr<nsISupports> propSupports;
     propEnum->GetNext(getter_AddRefs(propSupports));
 
     nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(propSupports));
     if (!propElem)
       return E_FAIL;
 
     nsAutoCString name;
     if (NS_FAILED(propElem->GetKey(name)))
       return E_FAIL;
 
-    int32_t offset = 0;
-    while ((offset = name.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
-      name.Insert('\\', offset);
-      offset += 2;
-    }
+    EscapeAttributeChars(name);
 
     nsAutoString value;
     if (NS_FAILED(propElem->GetValue(value)))
       return E_FAIL;
 
-    offset = 0;
-    while ((offset = value.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
-      value.Insert('\\', offset);
-      offset += 2;
-    }
+    EscapeAttributeChars(value);
 
     AppendUTF8toUTF16(name, strAttrs);
     strAttrs.Append(':');
     strAttrs.Append(value);
     strAttrs.Append(';');
   }
 
   if (strAttrs.IsEmpty())
--- a/accessible/windows/ia2/ia2AccessibleRelation.h
+++ b/accessible/windows/ia2/ia2AccessibleRelation.h
@@ -19,16 +19,20 @@
 namespace mozilla {
 namespace a11y {
 
 class ia2AccessibleRelation MOZ_FINAL : public IAccessibleRelation
 {
 public:
   ia2AccessibleRelation(RelationType aType, Relation* aRel);
 
+  ia2AccessibleRelation(RelationType aType,
+                        nsTArray<nsRefPtr<Accessible>>&& aTargets) :
+    mType(aType), mTargets(Move(aTargets)) {}
+
   // IUnknown
   DECL_IUNKNOWN
 
   // IAccessibleRelation
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationType(
       /* [retval][out] */ BSTR *relationType);
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedRelationType(
--- a/accessible/windows/ia2/moz.build
+++ b/accessible/windows/ia2/moz.build
@@ -47,8 +47,10 @@ LOCAL_INCLUDES += [
 FINAL_LIBRARY = 'xul'
 
 # The midl generated code include Windows headers which defines min and max
 # macros which conflicts with std::min/max.  Suppress the macros:
 if CONFIG['OS_ARCH'] == 'WINNT':
     DEFINES['NOMINMAX'] = True
 
 FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -392,16 +392,17 @@ EXTRA_JS_MODULES.commonjs.sdk.preference
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [
     'source/lib/sdk/private-browsing/utils.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.remote += [
     'source/lib/sdk/remote/child.js',
+    'source/lib/sdk/remote/core.js',
     'source/lib/sdk/remote/parent.js',
     'source/lib/sdk/remote/utils.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [
     'source/lib/sdk/stylesheet/style.js',
     'source/lib/sdk/stylesheet/utils.js',
 ]
--- a/addon-sdk/source/lib/sdk/content/sandbox.js
+++ b/addon-sdk/source/lib/sdk/content/sandbox.js
@@ -16,16 +16,17 @@ const { Ci, Cu, Cc } = require('chrome')
 const timer = require('../timers');
 const { URL } = require('../url');
 const { sandbox, evaluate, load } = require('../loader/sandbox');
 const { merge } = require('../util/object');
 const { getTabForContentWindow } = require('../tabs/utils');
 const { getInnerId } = require('../window/utils');
 const { PlainTextConsole } = require('../console/plain-text');
 const { data } = require('../self');
+const { isChildLoader } = require('../remote/core');
 // WeakMap of sandboxes so we can access private values
 const sandboxes = new WeakMap();
 
 /* Trick the linker in order to ensure shipping these files in the XPI.
   require('./content-worker.js');
   Then, retrieve URL of these files in the XPI:
 */
 let prefix = module.uri.split('sandbox.js')[0];
@@ -42,16 +43,29 @@ const EXPANDED_PRINCIPALS = permissions[
 const waiveSecurityMembrane = !!permissions['unsafe-content-script'];
 
 const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager;
 const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
   getService(Ci.nsIScriptSecurityManager);
 
 const JS_VERSION = '1.8';
 
+// Tests whether this window is loaded in a tab
+function isWindowInTab(window) {
+  if (isChildLoader) {
+    let { frames } = require('../remote/child');
+    let frame = frames.getFrameForWindow(window.top);
+    return frame.isTab;
+  }
+  else {
+    // The deprecated sync worker API still does everything in the main process
+    return getTabForContentWindow(window);
+  }
+}
+
 const WorkerSandbox = Class({
   implements: [ EventTarget ],
 
   /**
    * Emit a message to the worker content sandbox
    */
   emit: function emit(type, ...args) {
     // JSON.stringify is buggy with cross-sandbox values,
@@ -197,17 +211,17 @@ const WorkerSandbox = Class({
           configurable: true
         }
       );
     }
 
     // Inject our `console` into target document if worker doesn't have a tab
     // (e.g Panel, PageWorker, Widget).
     // `worker.tab` can't be used because bug 804935.
-    if (!getTabForContentWindow(window)) {
+    if (!isWindowInTab(window)) {
       let win = getUnsafeWindow(window);
 
       // export our chrome console to content window, as described here:
       // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
       let con = Cu.createObjectIn(win);
 
       let genPropDesc = function genPropDesc(fun) {
         return { enumerable: true, configurable: true, writable: true,
--- a/addon-sdk/source/lib/sdk/remote/child.js
+++ b/addon-sdk/source/lib/sdk/remote/child.js
@@ -1,13 +1,17 @@
 /* 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/. */
 "use strict";
 
+const { isChildLoader } = require('./core');
+if (!isChildLoader)
+  throw new Error("Cannot load sdk/remote/child in a main process loader.");
+
 const { Ci, Cc } = require('chrome');
 const runtime = require('../system/runtime');
 const { Class } = require('../core/heritage');
 const { Namespace } = require('../core/namespace');
 const { omit } = require('../util/object');
 const { when } = require('../system/unload');
 const { EventTarget } = require('../event/target');
 const { emit } = require('../event/core');
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/remote/core.js
@@ -0,0 +1,8 @@
+/* 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/. */
+"use strict";
+
+const options = require("@loader/options");
+
+exports.isChildLoader = options.childLoader;
--- a/addon-sdk/source/lib/sdk/remote/parent.js
+++ b/addon-sdk/source/lib/sdk/remote/parent.js
@@ -1,13 +1,17 @@
 /* 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/. */
 "use strict";
 
+const { isChildLoader } = require('./core');
+if (isChildLoader)
+  throw new Error("Cannot load sdk/remote/parent in a child loader.");
+
 const { Cu, Ci, Cc } = require('chrome');
 const runtime = require('../system/runtime');
 
 const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 
 if (runtime.processType != MAIN_PROCESS) {
   throw new Error('Cannot use sdk/remote/parent in a child process.');
 }
@@ -48,16 +52,17 @@ childOptions.modules = {};
 try {
   childOptions.modules["@l10n/data"] = require("@l10n/data");
 }
 catch (e) {
   // There may be no l10n data
 }
 const loaderID = getNewLoaderID();
 childOptions.loaderID = loaderID;
+childOptions.childLoader = true;
 
 const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
              getService(Ci.nsIMessageBroadcaster);
 const gmm = Cc['@mozilla.org/globalmessagemanager;1'].
             getService(Ci.nsIMessageBroadcaster);
 
 const ns = Namespace();
 
--- a/addon-sdk/source/test/addons/e10s-remote/main.js
+++ b/addon-sdk/source/test/addons/e10s-remote/main.js
@@ -487,15 +487,44 @@ exports["test processID"] = function*(as
     let [p, ID] = yield promiseEvent(process.port, 'sdk/test/processid');
     if (process.isRemote) {
       assert.notEqual(ID, processID, "Remote processes should have a different process ID");
     }
     else {
       assert.equal(ID, processID, "Remote processes should have the same process ID");
     }
   }
+
+  loader.unload();
 }
 
+// Check that sdk/remote/parent and sdk/remote/child can only be loaded in the
+// appropriate loaders
+exports["test cannot load in wrong loader"] = function*(assert) {
+  let loader = new Loader(module);
+  let { processes } = yield waitForProcesses(loader);
+
+  try {
+    require('sdk/remote/child');
+    assert.fail("Should not have been able to load sdk/remote/child");
+  }
+  catch (e) {
+    assert.ok(/Cannot load sdk\/remote\/child in a main process loader/.test(e),
+              "Should have seen the right exception.");
+  }
+
+  for (let process of processes) {
+    processes.port.emit('sdk/test/parentload');
+    let [_, isChildLoader, loaded, message] = yield promiseEvent(processes.port, 'sdk/test/parentload');
+    assert.ok(isChildLoader, "Process should see itself in a child loader.");
+    assert.ok(!loaded, "Process couldn't load sdk/remote/parent.");
+    assert.ok(/Cannot load sdk\/remote\/parent in a child loader/.test(message),
+              "Should have seen the right exception.");
+  }
+
+  loader.unload();
+};
+
 after(exports, function*(name, assert) {
   yield cleanUI();
 });
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/e10s-remote/remote-module.js
+++ b/addon-sdk/source/test/addons/e10s-remote/remote-module.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { when } = require('sdk/system/unload');
 const { process, frames } = require('sdk/remote/child');
 const { loaderID } = require('@loader/options');
 const { processID } = require('sdk/system/runtime');
 const system = require('sdk/system/events');
 const { Cu } = require('chrome');
+const { isChildLoader } = require('sdk/remote/core');
 
 function log(str) {
   console.log("remote[" + loaderID + "][" + processID + "]: " + str);
 }
 
 log("module loaded");
 
 process.port.emit('sdk/test/load');
@@ -76,16 +77,34 @@ frames.port.on('sdk/test/sendevent', (fr
 
   system.on("Test:Reply", listener);
   let event = new frame.content.CustomEvent("Test:Event");
   doc.dispatchEvent(event);
   system.off("Test:Reply", listener);
   frame.port.emit('sdk/test/eventsent');
 });
 
+process.port.on('sdk/test/parentload', () => {
+  let loaded = false;
+  let message = "";
+  try {
+    require('sdk/remote/parent');
+    loaded = true;
+  }
+  catch (e) {
+    message = "" + e;
+  }
+
+  process.port.emit('sdk/test/parentload',
+    isChildLoader,
+    loaded,
+    message
+  )
+});
+
 function listener(event) {
   // Use the raw observer service here since it will be usable even if the
   // loader has unloaded
   let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
   Services.obs.notifyObservers(null, "Test:Reply", "");
 }
 
 frames.port.on('sdk/test/registerframesevent', (frame) => {
--- a/addon-sdk/source/test/addons/remote/main.js
+++ b/addon-sdk/source/test/addons/remote/main.js
@@ -487,15 +487,44 @@ exports["test processID"] = function*(as
     let [p, ID] = yield promiseEvent(process.port, 'sdk/test/processid');
     if (process.isRemote) {
       assert.notEqual(ID, processID, "Remote processes should have a different process ID");
     }
     else {
       assert.equal(ID, processID, "Remote processes should have the same process ID");
     }
   }
+
+  loader.unload();
 }
 
+// Check that sdk/remote/parent and sdk/remote/child can only be loaded in the
+// appropriate loaders
+exports["test cannot load in wrong loader"] = function*(assert) {
+  let loader = new Loader(module);
+  let { processes } = yield waitForProcesses(loader);
+
+  try {
+    require('sdk/remote/child');
+    assert.fail("Should not have been able to load sdk/remote/child");
+  }
+  catch (e) {
+    assert.ok(/Cannot load sdk\/remote\/child in a main process loader/.test(e),
+              "Should have seen the right exception.");
+  }
+
+  for (let process of processes) {
+    processes.port.emit('sdk/test/parentload');
+    let [_, isChildLoader, loaded, message] = yield promiseEvent(processes.port, 'sdk/test/parentload');
+    assert.ok(isChildLoader, "Process should see itself in a child loader.");
+    assert.ok(!loaded, "Process couldn't load sdk/remote/parent.");
+    assert.ok(/Cannot load sdk\/remote\/parent in a child loader/.test(message),
+              "Should have seen the right exception.");
+  }
+
+  loader.unload();
+};
+
 after(exports, function*(name, assert) {
   yield cleanUI();
 });
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/remote/remote-module.js
+++ b/addon-sdk/source/test/addons/remote/remote-module.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { when } = require('sdk/system/unload');
 const { process, frames } = require('sdk/remote/child');
 const { loaderID } = require('@loader/options');
 const { processID } = require('sdk/system/runtime');
 const system = require('sdk/system/events');
 const { Cu } = require('chrome');
+const { isChildLoader } = require('sdk/remote/core');
 
 function log(str) {
   console.log("remote[" + loaderID + "][" + processID + "]: " + str);
 }
 
 log("module loaded");
 
 process.port.emit('sdk/test/load');
@@ -76,16 +77,34 @@ frames.port.on('sdk/test/sendevent', (fr
 
   system.on("Test:Reply", listener);
   let event = new frame.content.CustomEvent("Test:Event");
   doc.dispatchEvent(event);
   system.off("Test:Reply", listener);
   frame.port.emit('sdk/test/eventsent');
 });
 
+process.port.on('sdk/test/parentload', () => {
+  let loaded = false;
+  let message = "";
+  try {
+    require('sdk/remote/parent');
+    loaded = true;
+  }
+  catch (e) {
+    message = "" + e;
+  }
+
+  process.port.emit('sdk/test/parentload',
+    isChildLoader,
+    loaded,
+    message
+  )
+});
+
 function listener(event) {
   // Use the raw observer service here since it will be usable even if the
   // loader has unloaded
   let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
   Services.obs.notifyObservers(null, "Test:Reply", "");
 }
 
 frames.port.on('sdk/test/registerframesevent', (frame) => {
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -2035,9 +2035,32 @@ exports.testUnloadWontAttach = function(
       closeTab(tab);
       done();
     }
   });
 
   let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
 }
 
+// Tests that the SDK console isn't injected into documents loaded in tabs
+exports.testDontInjectConsole = function(assert, done) {
+  const TEST_URL = 'data:text/html;charset=utf-8,consoleinject';
+
+  let loader = Loader(module);
+
+  let mod = PageMod({
+    include: TEST_URL,
+    contentScript: Isolate(function() {
+      // This relies on the fact that the SDK console doesn't have assert defined
+      self.postMessage((typeof unsafeWindow.console.assert) == "function");
+    }),
+    onMessage: isNativeConsole => {
+      assert.ok(isNativeConsole, "Shouldn't have injected the SDK console.");
+      mod.destroy();
+      closeTab(tab);
+      done();
+    }
+  });
+
+  let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
+}
+
 require('sdk/test').run(exports);
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -689,16 +689,19 @@ pref("javascript.options.mem.gc_max_empt
 pref("ui.showHideScrollbars", 1);
 pref("ui.useOverlayScrollbars", 1);
 pref("ui.scrollbarFadeBeginDelay", 450);
 pref("ui.scrollbarFadeDuration", 200);
 
 // Scrollbar position follows the document `dir` attribute
 pref("layout.scrollbar.side", 1);
 
+// CSS Scroll Snapping
+pref("layout.css.scroll-snap.enabled", true);
+
 // Enable the ProcessPriorityManager, and give processes with no visible
 // documents a 1s grace period before they're eligible to be marked as
 // background. Background processes that are perceivable due to playing
 // media are given a longer grace period to accomodate changing tracks, etc.
 pref("dom.ipc.processPriorityManager.enabled", true);
 pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
 pref("dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", 5000);
 pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -280,17 +280,17 @@ pref("browser.slowStartup.timeThreshold"
 pref("browser.slowStartup.maxSamples", 5);
 
 // This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
 // this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
 // repackager of this code using an alternate snippet url, please keep your users safe
 pref("browser.aboutHomeSnippets.updateUrl", "https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
 
 pref("browser.enable_automatic_image_resizing", true);
-pref("browser.casting.enabled", true);
+pref("browser.casting.enabled", false);
 pref("browser.chrome.site_icons", true);
 pref("browser.chrome.favicons", true);
 // browser.warnOnQuit == false will override all other possible prompts when quitting or restarting
 pref("browser.warnOnQuit", true);
 // browser.showQuitWarning specifically controls the quit warning dialog. We
 // might still show the window closing dialog with showQuitWarning == false.
 pref("browser.showQuitWarning", false);
 pref("browser.fullscreen.autohide", true);
@@ -1226,17 +1226,17 @@ pref("security.sandbox.windows.log.stack
 // This pref is discussed in bug 1083344, the naming is inspired from its Windows
 // counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "an imperfect sandbox designed to allow firefox to run reasonably well"
 // 2 -> "an ideal sandbox which may break many things"
 // This setting is read when the content process is started. On Mac the content
 // process is killed when all windows are closed, so a change will take effect
 // when the 1st window is opened.
-pref("security.sandbox.content.level", 0);
+pref("security.sandbox.content.level", 1);
 #endif
 
 // This pref governs whether we attempt to work around problems caused by
 // plugins using OS calls to manipulate the cursor while running out-of-
 // process.  These workarounds all involve intercepting (hooking) certain
 // OS calls in the plugin process, then arranging to make certain OS calls
 // in the browser process.  Eventually plugins will be required to use the
 // NPAPI to manipulate the cursor, and these workarounds will be removed.
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -177,17 +177,16 @@ skip-if = buildapp == 'mulet' || e10s # 
 skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug455852.js]
 skip-if = e10s
 [browser_bug460146.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa" || e10s # Bug 1102017 - middle-button mousedown on selected tab2 does not activate tab - Didn't expect [object XULElement], but got it
 [browser_bug462673.js]
-skip-if = e10s # Bug 1093404 - test expects sync window opening from content and is disappointed in that expectation
 [browser_bug477014.js]
 [browser_bug479408.js]
 skip-if = buildapp == 'mulet'
 [browser_bug481560.js]
 [browser_bug484315.js]
 skip-if = e10s
 [browser_bug491431.js]
 skip-if = buildapp == 'mulet'
--- a/browser/base/content/test/general/browser_bug462673.js
+++ b/browser/base/content/test/general/browser_bug462673.js
@@ -1,61 +1,36 @@
-var runs = [
-  function (win, tabbrowser, tab) {
-    is(tabbrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
-    is(tabbrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
-    tabbrowser.removeTab(tab);
-    // Closing a tab will also close its parent chrome window, but async
-    executeSoon(function() {
-      ok(win.closed, "Window is closed");
-      testComplete(win);
-    });
-  },
-  function (win, tabbrowser, tab) {
-    var newTab = tabbrowser.addTab();
-    var newBrowser = newTab.linkedBrowser;
-    tabbrowser.removeTab(tab);
-    ok(!win.closed, "Window stays open");
-    if (!win.closed) {
-      is(tabbrowser.tabContainer.childElementCount, 1, "Window has one tab");
-      is(tabbrowser.browsers.length, 1, "Window has one browser");
-      is(tabbrowser.selectedTab, newTab, "Remaining tab is selected");
-      is(tabbrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
-      is(tabbrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
-    }
-    testComplete(win);
-  }
-];
+add_task(function* () {
+  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  yield SimpleTest.promiseFocus(win);
+
+  let tab = win.gBrowser.tabContainer.firstChild;
+  yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+  is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
+  is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
+  win.gBrowser.removeTab(tab);
+
+  // Closing a tab will also close its parent chrome window, but async
+  yield promiseWindowWillBeClosed(win);
+});
 
-function test() {
-  waitForExplicitFinish();
-  runOneTest();
-}
+add_task(function* () {
+  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  yield SimpleTest.promiseFocus(win);
 
-function testComplete(win) {
-  win.close();
-  if (runs.length)
-    runOneTest();
-  else
-    finish();
-}
-
-function runOneTest() {
-  var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  let tab = win.gBrowser.tabContainer.firstChild;
+  yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
 
-  win.addEventListener("load", function () {
-    win.removeEventListener("load", arguments.callee, false);
-
-    var tab = win.gBrowser.tabContainer.firstChild;
-    var browser = tab.linkedBrowser;
-
-    browser.addEventListener("load", function () {
-      browser.removeEventListener("load", arguments.callee, true);
+  var newTab = win.gBrowser.addTab();
+  var newBrowser = newTab.linkedBrowser;
+  win.gBrowser.removeTab(tab);
+  ok(!win.closed, "Window stays open");
+  if (!win.closed) {
+    is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab");
+    is(win.gBrowser.browsers.length, 1, "Window has one browser");
+    is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected");
+    is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
+    is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
+  }
 
-      executeSoon(function () {
-        runs.shift()(win, win.gBrowser, tab);
-      });
-    }, true);
-
-    var rootDir = getRootDirectory(gTestPath);
-    browser.contentWindow.location = rootDir + "test_bug462673.html"
-  }, false);
-}
+  yield promiseWindowClosed(win);
+});
--- a/browser/base/content/test/general/browser_tab_detach_restore.js
+++ b/browser/base/content/test/general/browser_tab_detach_restore.js
@@ -17,15 +17,15 @@ add_task(function*() {
 
   is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey");
   yield promiseWindowClosed(win);
 
   is(SessionStore.getClosedWindowCount(), 1, "Should have restore data for the closed window");
 
   win = SessionStore.undoCloseWindow(0);
   yield BrowserTestUtils.waitForEvent(win, "load", 10000);
-  yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored");
+  yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored", 10000);
 
   is(win.gBrowser.tabs.length, 1, "Should have restored one tab");
   is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page");
 
   yield promiseWindowClosed(win);
 });
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -450,16 +450,17 @@ loop.contacts = (function(_, mozL10n) {
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
         this.props.notifications.successL10n("import_contacts_success_message", {
+          num: stats.total,
           total: stats.total
         });
       });
     },
 
     handleAddContactButtonClick: function() {
       this.props.startForm("contacts_add");
     },
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -450,16 +450,17 @@ loop.contacts = (function(_, mozL10n) {
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
         this.props.notifications.successL10n("import_contacts_success_message", {
+          num: stats.total,
           total: stats.total
         });
       });
     },
 
     handleAddContactButtonClick: function() {
       this.props.startForm("contacts_add");
     },
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -272,17 +272,18 @@ describe("loop.contacts", function() {
           cb(null, {total: 42});
         };
 
         listView.handleImportButtonClick();
 
         sinon.assert.calledWithExactly(
           notifications.successL10n,
           "import_contacts_success_message",
-          {total: 42});
+          // Num is for the plural selection.
+          {num: 42, total: 42});
       });
 
       it("should notify the end user from any encountered error", function() {
         sandbox.stub(notifications, "errorL10n");
         navigator.mozLoop.startImport = function(opts, cb) {
           cb(new Error("fake error"));
         };
 
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop desktop-local mocha tests</title>
-  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
+  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
   <script>
@@ -24,22 +24,22 @@
   <!-- libs -->
   <script src="../../content/libs/l10n.js"></script>
   <script src="../../content/shared/libs/react-0.12.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
 
   <!-- test dependencies -->
-  <script src="../shared/vendor/mocha-2.0.1.js"></script>
-  <script src="../shared/vendor/chai-1.9.0.js"></script>
-  <script src="../shared/vendor/sinon-1.12.2.js"></script>
+  <script src="../shared/vendor/mocha-2.2.1.js"></script>
+  <script src="../shared/vendor/chai-2.1.0.js"></script>
+  <script src="../shared/vendor/sinon-1.13.0.js"></script>
   <script>
     /*global chai,mocha */
-    chai.Assertion.includeStack = true;
+    chai.config.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop shared mocha tests</title>
-  <link rel="stylesheet" media="all" href="vendor/mocha-2.0.1.css">
+  <link rel="stylesheet" media="all" href="vendor/mocha-2.2.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
   <script>
@@ -24,22 +24,22 @@
   <!-- libs -->
   <script src="../../content/shared/libs/react-0.12.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
   <script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
 
   <!-- test dependencies -->
-  <script src="vendor/mocha-2.0.1.js"></script>
-  <script src="vendor/chai-1.9.0.js"></script>
-  <script src="vendor/sinon-1.12.2.js"></script>
+  <script src="vendor/mocha-2.2.1.js"></script>
+  <script src="vendor/chai-2.1.0.js"></script>
+  <script src="vendor/sinon-1.13.0.js"></script>
   <script>
     /*global chai, mocha */
-    chai.Assertion.includeStack = true;
+    chai.config.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
rename from browser/components/loop/test/shared/vendor/chai-1.9.0.js
rename to browser/components/loop/test/shared/vendor/chai-2.1.0.js
--- a/browser/components/loop/test/shared/vendor/chai-1.9.0.js
+++ b/browser/components/loop/test/shared/vendor/chai-2.1.0.js
@@ -1,211 +1,149 @@
+
 ;(function(){
 
 /**
- * Require the given path.
+ * Require the module at `name`.
  *
- * @param {String} path
+ * @param {String} name
  * @return {Object} exports
  * @api public
  */
 
-function require(path, parent, orig) {
-  var resolved = require.resolve(path);
-
-  // lookup failed
-  if (null == resolved) {
-    orig = orig || path;
-    parent = parent || 'root';
-    var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
-    err.path = orig;
-    err.parent = parent;
-    err.require = true;
-    throw err;
-  }
-
-  var module = require.modules[resolved];
-
-  // perform real require()
-  // by invoking the module's
-  // registered function
-  if (!module._resolving && !module.exports) {
-    var mod = {};
-    mod.exports = {};
-    mod.client = mod.component = true;
-    module._resolving = true;
-    module.call(this, mod.exports, require.relative(resolved), mod);
-    delete module._resolving;
-    module.exports = mod.exports;
+function require(name) {
+  var module = require.modules[name];
+  if (!module) throw new Error('failed to require "' + name + '"');
+
+  if (!('exports' in module) && typeof module.definition === 'function') {
+    module.client = module.component = true;
+    module.definition.call(this, module.exports = {}, module);
+    delete module.definition;
   }
 
   return module.exports;
 }
 
 /**
+ * Meta info, accessible in the global scope unless you use AMD option.
+ */
+
+require.loader = 'component';
+
+/**
+ * Internal helper object, contains a sorting function for semantiv versioning
+ */
+require.helper = {};
+require.helper.semVerSort = function(a, b) {
+  var aArray = a.version.split('.');
+  var bArray = b.version.split('.');
+  for (var i=0; i<aArray.length; ++i) {
+    var aInt = parseInt(aArray[i], 10);
+    var bInt = parseInt(bArray[i], 10);
+    if (aInt === bInt) {
+      var aLex = aArray[i].substr((""+aInt).length);
+      var bLex = bArray[i].substr((""+bInt).length);
+      if (aLex === '' && bLex !== '') return 1;
+      if (aLex !== '' && bLex === '') return -1;
+      if (aLex !== '' && bLex !== '') return aLex > bLex ? 1 : -1;
+      continue;
+    } else if (aInt > bInt) {
+      return 1;
+    } else {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/**
+ * Find and require a module which name starts with the provided name.
+ * If multiple modules exists, the highest semver is used. 
+ * This function can only be used for remote dependencies.
+
+ * @param {String} name - module name: `user~repo`
+ * @param {Boolean} returnPath - returns the canonical require path if true, 
+ *                               otherwise it returns the epxorted module
+ */
+require.latest = function (name, returnPath) {
+  function showError(name) {
+    throw new Error('failed to find latest module of "' + name + '"');
+  }
+  // only remotes with semvers, ignore local files conataining a '/'
+  var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/;
+  var remoteRegexp = /(.*)~(.*)/;
+  if (!remoteRegexp.test(name)) showError(name);
+  var moduleNames = Object.keys(require.modules);
+  var semVerCandidates = [];
+  var otherCandidates = []; // for instance: name of the git branch
+  for (var i=0; i<moduleNames.length; i++) {
+    var moduleName = moduleNames[i];
+    if (new RegExp(name + '@').test(moduleName)) {
+        var version = moduleName.substr(name.length+1);
+        var semVerMatch = versionRegexp.exec(moduleName);
+        if (semVerMatch != null) {
+          semVerCandidates.push({version: version, name: moduleName});
+        } else {
+          otherCandidates.push({version: version, name: moduleName});
+        } 
+    }
+  }
+  if (semVerCandidates.concat(otherCandidates).length === 0) {
+    showError(name);
+  }
+  if (semVerCandidates.length > 0) {
+    var module = semVerCandidates.sort(require.helper.semVerSort).pop().name;
+    if (returnPath === true) {
+      return module;
+    }
+    return require(module);
+  }
+  // if the build contains more than one branch of the same module
+  // you should not use this funciton
+  var module = otherCandidates.sort(function(a, b) {return a.name > b.name})[0].name;
+  if (returnPath === true) {
+    return module;
+  }
+  return require(module);
+}
+
+/**
  * Registered modules.
  */
 
 require.modules = {};
 
 /**
- * Registered aliases.
- */
-
-require.aliases = {};
-
-/**
- * Resolve `path`.
- *
- * Lookup:
- *
- *   - PATH/index.js
- *   - PATH.js
- *   - PATH
+ * Register module at `name` with callback `definition`.
  *
- * @param {String} path
- * @return {String} path or null
- * @api private
- */
-
-require.resolve = function(path) {
-  if (path.charAt(0) === '/') path = path.slice(1);
-
-  var paths = [
-    path,
-    path + '.js',
-    path + '.json',
-    path + '/index.js',
-    path + '/index.json'
-  ];
-
-  for (var i = 0; i < paths.length; i++) {
-    var path = paths[i];
-    if (require.modules.hasOwnProperty(path)) return path;
-    if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
-  }
-};
-
-/**
- * Normalize `path` relative to the current path.
- *
- * @param {String} curr
- * @param {String} path
- * @return {String}
- * @api private
- */
-
-require.normalize = function(curr, path) {
-  var segs = [];
-
-  if ('.' != path.charAt(0)) return path;
-
-  curr = curr.split('/');
-  path = path.split('/');
-
-  for (var i = 0; i < path.length; ++i) {
-    if ('..' == path[i]) {
-      curr.pop();
-    } else if ('.' != path[i] && '' != path[i]) {
-      segs.push(path[i]);
-    }
-  }
-
-  return curr.concat(segs).join('/');
-};
-
-/**
- * Register module at `path` with callback `definition`.
- *
- * @param {String} path
+ * @param {String} name
  * @param {Function} definition
  * @api private
  */
 
-require.register = function(path, definition) {
-  require.modules[path] = definition;
-};
-
-/**
- * Alias a module definition.
- *
- * @param {String} from
- * @param {String} to
- * @api private
- */
-
-require.alias = function(from, to) {
-  if (!require.modules.hasOwnProperty(from)) {
-    throw new Error('Failed to alias "' + from + '", it does not exist');
-  }
-  require.aliases[to] = from;
+require.register = function (name, definition) {
+  require.modules[name] = {
+    definition: definition
+  };
 };
 
 /**
- * Return a require function relative to the `parent` path.
+ * Define a module's exports immediately with `exports`.
  *
- * @param {String} parent
- * @return {Function}
+ * @param {String} name
+ * @param {Generic} exports
  * @api private
  */
 
-require.relative = function(parent) {
-  var p = require.normalize(parent, '..');
-
-  /**
-   * lastIndexOf helper.
-   */
-
-  function lastIndexOf(arr, obj) {
-    var i = arr.length;
-    while (i--) {
-      if (arr[i] === obj) return i;
-    }
-    return -1;
-  }
-
-  /**
-   * The relative require() itself.
-   */
-
-  function localRequire(path) {
-    var resolved = localRequire.resolve(path);
-    return require(resolved, parent, path);
-  }
-
-  /**
-   * Resolve relative to the parent.
-   */
-
-  localRequire.resolve = function(path) {
-    var c = path.charAt(0);
-    if ('/' == c) return path.slice(1);
-    if ('.' == c) return require.normalize(p, path);
-
-    // resolve deps by returning
-    // the dep in the nearest "deps"
-    // directory
-    var segs = parent.split('/');
-    var i = lastIndexOf(segs, 'deps') + 1;
-    if (!i) i = 0;
-    path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
-    return path;
+require.define = function (name, exports) {
+  require.modules[name] = {
+    exports: exports
   };
-
-  /**
-   * Check if module is defined at `path`.
-   */
-
-  localRequire.exists = function(path) {
-    return require.modules.hasOwnProperty(localRequire.resolve(path));
-  };
-
-  return localRequire;
 };
-require.register("chaijs-assertion-error/index.js", function(exports, require, module){
+require.register("chaijs~assertion-error@1.0.0", function (exports, module) {
 /*!
  * assertion-error
  * Copyright(c) 2013 Jake Luer <jake@qualiancy.com>
  * MIT Licensed
  */
 
 /*!
  * Return a function that will copy properties from
@@ -308,17 +246,18 @@ AssertionError.prototype.toJSON = functi
   if (false !== stack && this.stack) {
     props.stack = this.stack;
   }
 
   return props;
 };
 
 });
-require.register("chaijs-type-detect/lib/type.js", function(exports, require, module){
+
+require.register("chaijs~type-detect@0.1.1", function (exports, module) {
 /*!
  * type-detect
  * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Primary Exports
@@ -453,28 +392,29 @@ Library.prototype.test = function (obj, 
   } else if (test && 'function' === getType(test)) {
     return test(obj);
   } else {
     throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
   }
 };
 
 });
-require.register("chaijs-deep-eql/lib/eql.js", function(exports, require, module){
+
+require.register("chaijs~deep-eql@0.1.3", function (exports, module) {
 /*!
  * deep-eql
  * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependencies
  */
 
-var type = require('type-detect');
+var type = require('chaijs~type-detect@0.1.1');
 
 /*!
  * Buffer.isBuffer browser shim
  */
 
 var Buffer;
 try { Buffer = require('buffer').Buffer; }
 catch(ex) {
@@ -713,47 +653,49 @@ function objectEqual(a, b, m) {
       return false;
     }
   }
 
   return true;
 }
 
 });
-require.register("chai/index.js", function(exports, require, module){
-module.exports = require('./lib/chai');
+
+require.register("chai", function (exports, module) {
+module.exports = require('chai/lib/chai.js');
 
 });
-require.register("chai/lib/chai.js", function(exports, require, module){
+
+require.register("chai/lib/chai.js", function (exports, module) {
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var used = []
   , exports = module.exports = {};
 
 /*!
  * Chai version
  */
 
-exports.version = '1.8.1';
+exports.version = '2.1.0';
 
 /*!
  * Assertion Error
  */
 
-exports.AssertionError = require('assertion-error');
+exports.AssertionError = require('chaijs~assertion-error@1.0.0');
 
 /*!
  * Utils for plugins (not exported)
  */
 
-var util = require('./chai/utils');
+var util = require('chai/lib/chai/utils/index.js');
 
 /**
  * # .use(function)
  *
  * Provides a way to extend the internals of Chai
  *
  * @param {Function}
  * @returns {this} for chaining
@@ -765,59 +707,75 @@ exports.use = function (fn) {
     fn(this, util);
     used.push(fn);
   }
 
   return this;
 };
 
 /*!
+ * Utility Functions
+ */
+
+exports.util = util;
+
+/*!
+ * Configuration
+ */
+
+var config = require('chai/lib/chai/config.js');
+exports.config = config;
+
+/*!
  * Primary `Assertion` prototype
  */
 
-var assertion = require('./chai/assertion');
+var assertion = require('chai/lib/chai/assertion.js');
 exports.use(assertion);
 
 /*!
  * Core Assertions
  */
 
-var core = require('./chai/core/assertions');
+var core = require('chai/lib/chai/core/assertions.js');
 exports.use(core);
 
 /*!
  * Expect interface
  */
 
-var expect = require('./chai/interface/expect');
+var expect = require('chai/lib/chai/interface/expect.js');
 exports.use(expect);
 
 /*!
  * Should interface
  */
 
-var should = require('./chai/interface/should');
+var should = require('chai/lib/chai/interface/should.js');
 exports.use(should);
 
 /*!
  * Assert interface
  */
 
-var assert = require('./chai/interface/assert');
+var assert = require('chai/lib/chai/interface/assert.js');
 exports.use(assert);
 
 });
-require.register("chai/lib/chai/assertion.js", function(exports, require, module){
+
+require.register("chai/lib/chai/assertion.js", function (exports, module) {
 /*!
  * chai
  * http://chaijs.com
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
+var config = require('chai/lib/chai/config.js');
+
 module.exports = function (_chai, util) {
   /*!
    * Module dependencies.
    */
 
   var AssertionError = _chai.AssertionError
     , flag = util.flag;
 
@@ -836,43 +794,37 @@ module.exports = function (_chai, util) 
    */
 
   function Assertion (obj, msg, stack) {
     flag(this, 'ssfi', stack || arguments.callee);
     flag(this, 'object', obj);
     flag(this, 'message', msg);
   }
 
-  /*!
-    * ### Assertion.includeStack
-    *
-    * User configurable property, influences whether stack trace
-    * is included in Assertion error message. Default of false
-    * suppresses stack trace in the error message
-    *
-    *     Assertion.includeStack = true;  // enable stack on error
-    *
-    * @api public
-    */
-
-  Assertion.includeStack = false;
-
-  /*!
-   * ### Assertion.showDiff
-   *
-   * User configurable property, influences whether or not
-   * the `showDiff` flag should be included in the thrown
-   * AssertionErrors. `false` will always be `false`; `true`
-   * will be true when the assertion has requested a diff
-   * be shown.
-   *
-   * @api public
-   */
-
-  Assertion.showDiff = true;
+  Object.defineProperty(Assertion, 'includeStack', {
+    get: function() {
+      console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
+      return config.includeStack;
+    },
+    set: function(value) {
+      console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
+      config.includeStack = value;
+    }
+  });
+
+  Object.defineProperty(Assertion, 'showDiff', {
+    get: function() {
+      console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
+      return config.showDiff;
+    },
+    set: function(value) {
+      console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
+      config.showDiff = value;
+    }
+  });
 
   Assertion.addProperty = function (name, fn) {
     util.addProperty(this.prototype, name, fn);
   };
 
   Assertion.addMethod = function (name, fn) {
     util.addMethod(this.prototype, name, fn);
   };
@@ -895,36 +847,36 @@ module.exports = function (_chai, util) 
 
   /*!
    * ### .assert(expression, message, negateMessage, expected, actual)
    *
    * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
    *
    * @name assert
    * @param {Philosophical} expression to be tested
-   * @param {String} message to display if fails
-   * @param {String} negatedMessage to display if negated expression fails
+   * @param {String or Function} message or function that returns message to display if fails
+   * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
    * @param {Mixed} expected value (remember to check for negation)
    * @param {Mixed} actual (optional) will default to `this.obj`
    * @api private
    */
 
   Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
     var ok = util.test(this, arguments);
     if (true !== showDiff) showDiff = false;
-    if (true !== Assertion.showDiff) showDiff = false;
+    if (true !== config.showDiff) showDiff = false;
 
     if (!ok) {
       var msg = util.getMessage(this, arguments)
         , actual = util.getActual(this, arguments);
       throw new AssertionError(msg, {
           actual: actual
         , expected: expected
         , showDiff: showDiff
-      }, (Assertion.includeStack) ? this.assert : flag(this, 'ssfi'));
+      }, (config.includeStack) ? this.assert : flag(this, 'ssfi'));
     }
   };
 
   /*!
    * ### ._obj
    *
    * Quick reference to stored `actual` value for plugin developers.
    *
@@ -937,17 +889,72 @@ module.exports = function (_chai, util) 
       }
     , set: function (val) {
         flag(this, 'object', val);
       }
   });
 };
 
 });
-require.register("chai/lib/chai/core/assertions.js", function(exports, require, module){
+
+require.register("chai/lib/chai/config.js", function (exports, module) {
+module.exports = {
+
+  /**
+   * ### config.includeStack
+   *
+   * User configurable property, influences whether stack trace
+   * is included in Assertion error message. Default of false
+   * suppresses stack trace in the error message.
+   *
+   *     chai.config.includeStack = true;  // enable stack on error
+   *
+   * @param {Boolean}
+   * @api public
+   */
+
+   includeStack: false,
+
+  /**
+   * ### config.showDiff
+   *
+   * User configurable property, influences whether or not
+   * the `showDiff` flag should be included in the thrown
+   * AssertionErrors. `false` will always be `false`; `true`
+   * will be true when the assertion has requested a diff
+   * be shown.
+   *
+   * @param {Boolean}
+   * @api public
+   */
+
+  showDiff: true,
+
+  /**
+   * ### config.truncateThreshold
+   *
+   * User configurable property, sets length threshold for actual and
+   * expected values in assertion errors. If this threshold is exceeded,
+   * the value is truncated.
+   *
+   * Set it to zero if you want to disable truncating altogether.
+   *
+   *     chai.config.truncateThreshold = 0;  // disable truncating
+   *
+   * @param {Number}
+   * @api public
+   */
+
+  truncateThreshold: 40
+
+};
+
+});
+
+require.register("chai/lib/chai/core/assertions.js", function (exports, module) {
 /*!
  * chai
  * http://chaijs.com
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, _) {
@@ -955,41 +962,42 @@ module.exports = function (chai, _) {
     , toString = Object.prototype.toString
     , flag = _.flag;
 
   /**
    * ### Language Chains
    *
    * The following are provided as chainable getters to
    * improve the readability of your assertions. They
-   * do not provide an testing capability unless they
+   * do not provide testing capabilities unless they
    * have been overwritten by a plugin.
    *
    * **Chains**
    *
    * - to
    * - be
    * - been
    * - is
    * - that
+   * - which
    * - and
    * - has
    * - have
    * - with
    * - at
    * - of
    * - same
    *
    * @name language chains
    * @api public
    */
 
   [ 'to', 'be', 'been'
   , 'is', 'and', 'has', 'have'
-  , 'with', 'that', 'at'
+  , 'with', 'that', 'which', 'at'
   , 'of', 'same' ].forEach(function (chain) {
     Assertion.addProperty(chain, function () {
       return this;
     });
   });
 
   /**
    * ### .not
@@ -1023,16 +1031,51 @@ module.exports = function (chai, _) {
    * @api public
    */
 
   Assertion.addProperty('deep', function () {
     flag(this, 'deep', true);
   });
 
   /**
+   * ### .any
+   *
+   * Sets the `any` flag, (opposite of the `all` flag)
+   * later used in the `keys` assertion. 
+   *
+   *     expect(foo).to.have.any.keys('bar', 'baz');
+   *
+   * @name any
+   * @api public
+   */
+
+  Assertion.addProperty('any', function () {
+    flag(this, 'any', true);
+    flag(this, 'all', false)
+  });
+
+
+  /**
+   * ### .all
+   *
+   * Sets the `all` flag (opposite of the `any` flag) 
+   * later used by the `keys` assertion.
+   *
+   *     expect(foo).to.have.all.keys('bar', 'baz');
+   *
+   * @name all
+   * @api public
+   */
+
+  Assertion.addProperty('all', function () {
+    flag(this, 'all', true);
+    flag(this, 'any', false);
+  });
+
+  /**
    * ### .a(type)
    *
    * The `a` and `an` assertions are aliases that can be
    * used either as language chains or to assert a value's
    * type.
    *
    *     // typeof
    *     expect('test').to.be.a('string');
@@ -1067,56 +1110,67 @@ module.exports = function (chai, _) {
   Assertion.addChainableMethod('a', an);
 
   /**
    * ### .include(value)
    *
    * The `include` and `contain` assertions can be used as either property
    * based language chains or as methods to assert the inclusion of an object
    * in an array or a substring in a string. When used as language chains,
-   * they toggle the `contain` flag for the `keys` assertion.
+   * they toggle the `contains` flag for the `keys` assertion.
    *
    *     expect([1,2,3]).to.include(2);
    *     expect('foobar').to.contain('foo');
    *     expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
    *
    * @name include
    * @alias contain
+   * @alias includes
+   * @alias contains
    * @param {Object|String|Number} obj
    * @param {String} message _optional_
    * @api public
    */
 
   function includeChainingBehavior () {
     flag(this, 'contains', true);
   }
 
   function include (val, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
-
-    if (_.type(val) === 'object') {
+    var expected = false;
+    if (_.type(obj) === 'array' && _.type(val) === 'object') {
+      for (var i in obj) {
+        if (_.eql(obj[i], val)) {
+          expected = true;
+          break;
+        }
+      }
+    } else if (_.type(val) === 'object') {
       if (!flag(this, 'negate')) {
         for (var k in val) new Assertion(obj).property(k, val[k]);
         return;
       }
-      var subset = {}
-      for (var k in val) subset[k] = obj[k]
-      var expected = _.eql(subset, val);
+      var subset = {};
+      for (var k in val) subset[k] = obj[k];
+      expected = _.eql(subset, val);
     } else {
-      var expected = obj && ~obj.indexOf(val)
+      expected = obj && ~obj.indexOf(val);
     }
     this.assert(
         expected
       , 'expected #{this} to include ' + _.inspect(val)
       , 'expected #{this} to not include ' + _.inspect(val));
   }
 
   Assertion.addChainableMethod('include', include, includeChainingBehavior);
   Assertion.addChainableMethod('contain', include, includeChainingBehavior);
+  Assertion.addChainableMethod('contains', include, includeChainingBehavior);
+  Assertion.addChainableMethod('includes', include, includeChainingBehavior);
 
   /**
    * ### .ok
    *
    * Asserts that the target is truthy.
    *
    *     expect('everthing').to.be.ok;
    *     expect(1).to.be.ok;
@@ -1698,31 +1752,36 @@ module.exports = function (chai, _) {
    * @param {String} message _optional_
    * @returns value of property for chaining
    * @api public
    */
 
   Assertion.addMethod('property', function (name, val, msg) {
     if (msg) flag(this, 'message', msg);
 
-    var descriptor = flag(this, 'deep') ? 'deep property ' : 'property '
+    var isDeep = !!flag(this, 'deep')
+      , descriptor = isDeep ? 'deep property ' : 'property '
       , negate = flag(this, 'negate')
       , obj = flag(this, 'object')
-      , value = flag(this, 'deep')
-        ? _.getPathValue(name, obj)
+      , pathInfo = isDeep ? _.getPathInfo(name, obj) : null
+      , hasProperty = isDeep
+        ? pathInfo.exists
+        : _.hasProperty(name, obj)
+      , value = isDeep
+        ? pathInfo.value
         : obj[name];
 
     if (negate && undefined !== val) {
       if (undefined === value) {
         msg = (msg != null) ? msg + ': ' : '';
         throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name));
       }
     } else {
       this.assert(
-          undefined !== value
+          hasProperty
         , 'expected #{this} to have a ' + descriptor + _.inspect(name)
         , 'expected #{this} to not have ' + descriptor + _.inspect(name));
     }
 
     if (undefined !== val) {
       this.assert(
           val === value
         , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
@@ -1804,17 +1863,17 @@ module.exports = function (chai, _) {
       , 'expected #{this} to have a length of #{exp} but got #{act}'
       , 'expected #{this} to not have a length of #{act}'
       , n
       , len
     );
   }
 
   Assertion.addChainableMethod('length', assertLength, assertLengthChain);
-  Assertion.addMethod('lengthOf', assertLength, assertLengthChain);
+  Assertion.addMethod('lengthOf', assertLength);
 
   /**
    * ### .match(regexp)
    *
    * Asserts that the target matches a regular expression.
    *
    *     expect('foobar').to.match(/^foo/);
    *
@@ -1858,75 +1917,129 @@ module.exports = function (chai, _) {
       , 'expected #{this} to not contain ' + _.inspect(str)
     );
   });
 
 
   /**
    * ### .keys(key1, [key2], [...])
    *
-   * Asserts that the target has exactly the given keys, or
-   * asserts the inclusion of some keys when using the
-   * `include` or `contain` modifiers.
-   *
-   *     expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']);
-   *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar');
+   * Asserts that the target contains any or all of the passed-in keys.
+   * Use in combination with `any`, `all`, `contains`, or `have` will affect 
+   * what will pass.
+   * 
+   * When used in conjunction with `any`, at least one key that is passed 
+   * in must exist in the target object. This is regardless whether or not 
+   * the `have` or `contain` qualifiers are used. Note, either `any` or `all`
+   * should be used in the assertion. If neither are used, the assertion is
+   * defaulted to `all`.
+   * 
+   * When both `all` and `contain` are used, the target object must have at 
+   * least all of the passed-in keys but may have more keys not listed.
+   * 
+   * When both `all` and `have` are used, the target object must both contain
+   * all of the passed-in keys AND the number of keys in the target object must
+   * match the number of keys passed in (in other words, a target object must 
+   * have all and only all of the passed-in keys).
+   * 
+   *     expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz');
+   *     expect({ foo: 1, bar: 2 }).to.have.any.keys('foo');
+   *     expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz');
+   *     expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']);
+   *     expect({ foo: 1, bar: 2 }).to.contain.any.keys({'foo': 6});
+   *     expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']);
+   *     expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo', 7});
+   *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
+   *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys([{'bar': 6}}]);
+   *
    *
    * @name keys
    * @alias key
-   * @param {String...|Array} keys
+   * @param {String...|Array|Object} keys
    * @api public
    */
 
   function assertKeys (keys) {
     var obj = flag(this, 'object')
       , str
-      , ok = true;
-
-    keys = keys instanceof Array
-      ? keys
-      : Array.prototype.slice.call(arguments);
+      , ok = true
+      , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments';
+
+    switch (_.type(keys)) {
+      case "array":
+        if (arguments.length > 1) throw (new Error(mixedArgsMsg));
+        break;
+      case "object":
+        if (arguments.length > 1) throw (new Error(mixedArgsMsg));
+        keys = Object.keys(keys);
+        break;
+      default:
+        keys = Array.prototype.slice.call(arguments);
+    }
 
     if (!keys.length) throw new Error('keys required');
 
     var actual = Object.keys(obj)
-      , len = keys.length;
-
-    // Inclusion
-    ok = keys.every(function(key){
-      return ~actual.indexOf(key);
-    });
-
-    // Strict
-    if (!flag(this, 'negate') && !flag(this, 'contains')) {
-      ok = ok && keys.length == actual.length;
+      , expected = keys
+      , len = keys.length
+      , any = flag(this, 'any')
+      , all = flag(this, 'all');
+
+    if (!any && !all) {
+      all = true;
+    }
+
+    // Has any
+    if (any) {
+      var intersection = expected.filter(function(key) {
+        return ~actual.indexOf(key);
+      });
+      ok = intersection.length > 0;
+    }
+
+    // Has all
+    if (all) {
+      ok = keys.every(function(key){
+        return ~actual.indexOf(key);
+      });
+      if (!flag(this, 'negate') && !flag(this, 'contains')) {
+        ok = ok && keys.length == actual.length;
+      }
     }
 
     // Key string
     if (len > 1) {
       keys = keys.map(function(key){
         return _.inspect(key);
       });
       var last = keys.pop();
-      str = keys.join(', ') + ', and ' + last;
+      if (all) {
+        str = keys.join(', ') + ', and ' + last;
+      }
+      if (any) {
+        str = keys.join(', ') + ', or ' + last;
+      }
     } else {
       str = _.inspect(keys[0]);
     }
 
     // Form
     str = (len > 1 ? 'keys ' : 'key ') + str;
 
     // Have / include
     str = (flag(this, 'contains') ? 'contain ' : 'have ') + str;
 
     // Assertion
     this.assert(
         ok
       , 'expected #{this} to ' + str
       , 'expected #{this} to not ' + str
+      , expected.slice(0).sort()
+      , actual.sort()
+      , true
     );
   }
 
   Assertion.addMethod('keys', assertKeys);
   Assertion.addMethod('key', assertKeys);
 
   /**
    * ### .throw(constructor)
@@ -2152,22 +2265,23 @@ module.exports = function (chai, _) {
    * @param {Function} matcher
    * @param {String} message _optional_
    * @api public
    */
 
   Assertion.addMethod('satisfy', function (matcher, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
+    var result = matcher(obj);
     this.assert(
-        matcher(obj)
+        result
       , 'expected #{this} to satisfy ' + _.objDisplay(matcher)
       , 'expected #{this} to not satisfy' + _.objDisplay(matcher)
       , this.negate ? false : true
-      , matcher(obj)
+      , result
     );
   });
 
   /**
    * ### .closeTo(expected, delta)
    *
    * Asserts that the target is equal `expected`, to within a +/- `delta` range.
    *
@@ -2178,76 +2292,207 @@ module.exports = function (chai, _) {
    * @param {Number} delta
    * @param {String} message _optional_
    * @api public
    */
 
   Assertion.addMethod('closeTo', function (expected, delta, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
+
+    new Assertion(obj, msg).is.a('number');
+    if (_.type(expected) !== 'number' || _.type(delta) !== 'number') {
+      throw new Error('the arguments to closeTo must be numbers');
+    }
+
     this.assert(
         Math.abs(obj - expected) <= delta
       , 'expected #{this} to be close to ' + expected + ' +/- ' + delta
       , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
     );
   });
 
-  function isSubsetOf(subset, superset) {
+  function isSubsetOf(subset, superset, cmp) {
     return subset.every(function(elem) {
-      return superset.indexOf(elem) !== -1;
+      if (!cmp) return superset.indexOf(elem) !== -1;
+
+      return superset.some(function(elem2) {
+        return cmp(elem, elem2);
+      });
     })
   }
 
   /**
    * ### .members(set)
    *
    * Asserts that the target is a superset of `set`,
-   * or that the target and `set` have the same members.
+   * or that the target and `set` have the same strictly-equal (===) members.
+   * Alternately, if the `deep` flag is set, set members are compared for deep
+   * equality.
    *
    *     expect([1, 2, 3]).to.include.members([3, 2]);
    *     expect([1, 2, 3]).to.not.include.members([3, 2, 8]);
    *
    *     expect([4, 2]).to.have.members([2, 4]);
    *     expect([5, 2]).to.not.have.members([5, 2, 1]);
    *
+   *     expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]);
+   *
    * @name members
    * @param {Array} set
    * @param {String} message _optional_
    * @api public
    */
 
   Assertion.addMethod('members', function (subset, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
 
     new Assertion(obj).to.be.an('array');
     new Assertion(subset).to.be.an('array');
 
+    var cmp = flag(this, 'deep') ? _.eql : undefined;
+
     if (flag(this, 'contains')) {
       return this.assert(
-          isSubsetOf(subset, obj)
+          isSubsetOf(subset, obj, cmp)
         , 'expected #{this} to be a superset of #{act}'
         , 'expected #{this} to not be a superset of #{act}'
         , obj
         , subset
       );
     }
 
     this.assert(
-        isSubsetOf(obj, subset) && isSubsetOf(subset, obj)
+        isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp)
         , 'expected #{this} to have the same members as #{act}'
         , 'expected #{this} to not have the same members as #{act}'
         , obj
         , subset
     );
   });
+
+  /**
+   * ### .change(function)
+   *
+   * Asserts that a function changes an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val += 3 };
+   *     var noChangeFn = function() { return 'foo' + 'bar'; }
+   *     expect(fn).to.change(obj, 'val');
+   *     expect(noChangFn).to.not.change(obj, 'val')
+   *
+   * @name change
+   * @alias changes
+   * @alias Change
+   * @param {String} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  function assertChanges (object, prop, msg) {
+    if (msg) flag(this, 'message', msg);
+    var fn = flag(this, 'object');
+    new Assertion(object, msg).to.have.property(prop);
+    new Assertion(fn).is.a('function');
+
+    var initial = object[prop];
+    fn();
+
+    this.assert(
+      initial !== object[prop]
+      , 'expected .' + prop + ' to change'
+      , 'expected .' + prop + ' to not change'
+    );
+  }
+
+  Assertion.addChainableMethod('change', assertChanges);
+  Assertion.addChainableMethod('changes', assertChanges);
+
+  /**
+   * ### .increase(function)
+   *
+   * Asserts that a function increases an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 15 };
+   *     expect(fn).to.increase(obj, 'val');
+   *
+   * @name increase
+   * @alias increases
+   * @alias Increase
+   * @param {String} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  function assertIncreases (object, prop, msg) {
+    if (msg) flag(this, 'message', msg);
+    var fn = flag(this, 'object');
+    new Assertion(object, msg).to.have.property(prop);
+    new Assertion(fn).is.a('function');
+
+    var initial = object[prop];
+    fn();
+
+    this.assert(
+      object[prop] - initial > 0
+      , 'expected .' + prop + ' to increase'
+      , 'expected .' + prop + ' to not increase'
+    );
+  }
+
+  Assertion.addChainableMethod('increase', assertIncreases);
+  Assertion.addChainableMethod('increases', assertIncreases);
+
+  /**
+   * ### .decrease(function)
+   *
+   * Asserts that a function decreases an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 5 };
+   *     expect(fn).to.decrease(obj, 'val');
+   *
+   * @name decrease
+   * @alias decreases
+   * @alias Decrease
+   * @param {String} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  function assertDecreases (object, prop, msg) {
+    if (msg) flag(this, 'message', msg);
+    var fn = flag(this, 'object');
+    new Assertion(object, msg).to.have.property(prop);
+    new Assertion(fn).is.a('function');
+
+    var initial = object[prop];
+    fn();
+
+    this.assert(
+      object[prop] - initial < 0
+      , 'expected .' + prop + ' to decrease'
+      , 'expected .' + prop + ' to not decrease'
+    );
+  }
+
+  Assertion.addChainableMethod('decrease', assertDecreases);
+  Assertion.addChainableMethod('decreases', assertDecreases);
+
 };
 
 });
-require.register("chai/lib/chai/interface/assert.js", function(exports, require, module){
+
+require.register("chai/lib/chai/interface/assert.js", function (exports, module) {
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 
 module.exports = function (chai, util) {
@@ -2273,17 +2518,17 @@ module.exports = function (chai, util) {
    *
    * @param {Mixed} expression to test for truthiness
    * @param {String} message to display on error
    * @name assert
    * @api public
    */
 
   var assert = chai.assert = function (express, errmsg) {
-    var test = new Assertion(null);
+    var test = new Assertion(null, null, chai.assert);
     test.assert(
         express
       , errmsg
       , '[ negation message unavailable ]'
     );
   };
 
   /**
@@ -2354,17 +2599,17 @@ module.exports = function (chai, util) {
    * @name equal
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
    * @api public
    */
 
   assert.equal = function (act, exp, msg) {
-    var test = new Assertion(act, msg);
+    var test = new Assertion(act, msg, assert.equal);
 
     test.assert(
         exp == flag(test, 'object')
       , 'expected #{this} to equal #{exp}'
       , 'expected #{this} to not equal #{act}'
       , exp
       , act
     );
@@ -2380,17 +2625,17 @@ module.exports = function (chai, util) {
    * @name notEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
    * @api public
    */
 
   assert.notEqual = function (act, exp, msg) {
-    var test = new Assertion(act, msg);
+    var test = new Assertion(act, msg, assert.notEqual);
 
     test.assert(
         exp != flag(test, 'object')
       , 'expected #{this} to not equal #{exp}'
       , 'expected #{this} to equal #{act}'
       , exp
       , act
     );
@@ -2477,16 +2722,52 @@ module.exports = function (chai, util) {
    *     assert.isTrue(teaServed, 'the tea has been served');
    *
    * @name isTrue
    * @param {Mixed} value
    * @param {String} message
    * @api public
    */
 
+  assert.isAbove = function (val, abv, msg) {
+    new Assertion(val, msg).to.be.above(abv);
+  };
+
+   /**
+   * ### .isAbove(valueToCheck, valueToBeAbove, [message])
+   *
+   * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove`
+   *
+   *     assert.isAbove(5, 2, '5 is strictly greater than 2');
+   *
+   * @name isAbove
+   * @param {Mixed} valueToCheck
+   * @param {Mixed} valueToBeAbove
+   * @param {String} message
+   * @api public
+   */
+
+  assert.isBelow = function (val, blw, msg) {
+    new Assertion(val, msg).to.be.below(blw);
+  };
+
+   /**
+   * ### .isBelow(valueToCheck, valueToBeBelow, [message])
+   *
+   * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow`
+   *
+   *     assert.isBelow(3, 6, '3 is strictly less than 6');
+   *
+   * @name isBelow
+   * @param {Mixed} valueToCheck
+   * @param {Mixed} valueToBeBelow
+   * @param {String} message
+   * @api public
+   */
+
   assert.isTrue = function (val, msg) {
     new Assertion(val, msg).is['true'];
   };
 
   /**
    * ### .isFalse(value, [message])
    *
    * Asserts that `value` is false.
@@ -2631,18 +2912,18 @@ module.exports = function (chai, util) {
   };
 
   /**
    * ### .isNotObject(value, [message])
    *
    * Asserts that `value` is _not_ an object.
    *
    *     var selection = 'chai'
-   *     assert.isObject(selection, 'tea selection is not an object');
-   *     assert.isObject(null, 'null is not an object');
+   *     assert.isNotObject(selection, 'tea selection is not an object');
+   *     assert.isNotObject(null, 'null is not an object');
    *
    * @name isNotObject
    * @param {Mixed} value
    * @param {String} message
    * @api public
    */
 
   assert.isNotObject = function (val, msg) {
@@ -2896,17 +3177,17 @@ module.exports = function (chai, util) {
    * @name include
    * @param {Array|String} haystack
    * @param {Mixed} needle
    * @param {String} message
    * @api public
    */
 
   assert.include = function (exp, inc, msg) {
-    new Assertion(exp, msg).include(inc);
+    new Assertion(exp, msg, assert.include).include(inc);
   };
 
   /**
    * ### .notInclude(haystack, needle, [message])
    *
    * Asserts that `haystack` does not include `needle`. Works
    * for strings and arrays.
    *i
@@ -2916,17 +3197,17 @@ module.exports = function (chai, util) {
    * @name notInclude
    * @param {Array|String} haystack
    * @param {Mixed} needle
    * @param {String} message
    * @api public
    */
 
   assert.notInclude = function (exp, inc, msg) {
-    new Assertion(exp, msg).not.include(inc);
+    new Assertion(exp, msg, assert.notInclude).not.include(inc);
   };
 
   /**
    * ### .match(value, regexp, [message])
    *
    * Asserts that `value` matches the regular expression `regexp`.
    *
    *     assert.match('foobar', /^foo/, 'regexp matches');
@@ -3246,27 +3527,46 @@ module.exports = function (chai, util) {
    * ### .sameMembers(set1, set2, [message])
    *
    * Asserts that `set1` and `set2` have the same members.
    * Order is not taken into account.
    *
    *     assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members');
    *
    * @name sameMembers
-   * @param {Array} superset
-   * @param {Array} subset
+   * @param {Array} set1
+   * @param {Array} set2
    * @param {String} message
    * @api public
    */
 
   assert.sameMembers = function (set1, set2, msg) {
     new Assertion(set1, msg).to.have.same.members(set2);
   }
 
   /**
+   * ### .sameDeepMembers(set1, set2, [message])
+   *
+   * Asserts that `set1` and `set2` have the same members - using a deep equality checking.
+   * Order is not taken into account.
+   *
+   *     assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members');
+   *
+   * @name sameDeepMembers
+   * @param {Array} set1
+   * @param {Array} set2
+   * @param {String} message
+   * @api public
+   */
+
+  assert.sameDeepMembers = function (set1, set2, msg) {
+    new Assertion(set1, msg).to.have.same.deep.members(set2);
+  }
+
+  /**
    * ### .includeMembers(superset, subset, [message])
    *
    * Asserts that `subset` is included in `superset`.
    * Order is not taken into account.
    *
    *     assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members');
    *
    * @name includeMembers
@@ -3275,16 +3575,142 @@ module.exports = function (chai, util) {
    * @param {String} message
    * @api public
    */
 
   assert.includeMembers = function (superset, subset, msg) {
     new Assertion(superset, msg).to.include.members(subset);
   }
 
+   /**
+   * ### .changes(function, object, property)
+   *
+   * Asserts that a function changes the value of a property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 22 };
+   *     assert.changes(fn, obj, 'val');
+   *
+   * @name changes
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.changes = function (fn, obj, prop) {
+    new Assertion(fn).to.change(obj, prop);
+  }
+
+   /**
+   * ### .doesNotChange(function, object, property)
+   *
+   * Asserts that a function does not changes the value of a property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { console.log('foo'); };
+   *     assert.doesNotChange(fn, obj, 'val');
+   *
+   * @name doesNotChange
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.doesNotChange = function (fn, obj, prop) {
+    new Assertion(fn).to.not.change(obj, prop);
+  }
+
+   /**
+   * ### .increases(function, object, property)
+   *
+   * Asserts that a function increases an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 13 };
+   *     assert.increases(fn, obj, 'val');
+   *
+   * @name increases
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.increases = function (fn, obj, prop) {
+    new Assertion(fn).to.increase(obj, prop);
+  }
+
+   /**
+   * ### .doesNotIncrease(function, object, property)
+   *
+   * Asserts that a function does not increase object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 8 };
+   *     assert.doesNotIncrease(fn, obj, 'val');
+   *
+   * @name doesNotIncrease
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.doesNotIncrease = function (fn, obj, prop) {
+    new Assertion(fn).to.not.increase(obj, prop);
+  }
+
+   /**
+   * ### .decreases(function, object, property)
+   *
+   * Asserts that a function decreases an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 5 };
+   *     assert.decreases(fn, obj, 'val');
+   *
+   * @name decreases
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.decreases = function (fn, obj, prop) {
+    new Assertion(fn).to.decrease(obj, prop);
+  }
+
+   /**
+   * ### .doesNotDecrease(function, object, property)
+   *
+   * Asserts that a function does not decreases an object property
+   *
+   *     var obj = { val: 10 };
+   *     var fn = function() { obj.val = 15 };
+   *     assert.doesNotDecrease(fn, obj, 'val');
+   *
+   * @name doesNotDecrease
+   * @param {Function} modifier function
+   * @param {Object} object
+   * @param {String} property name
+   * @param {String} message _optional_
+   * @api public
+   */
+
+  assert.doesNotDecrease = function (fn, obj, prop) {
+    new Assertion(fn).to.not.decrease(obj, prop);
+  }
+
   /*!
    * Undocumented / untested
    */
 
   assert.ifError = function (val, msg) {
     new Assertion(val, msg).to.not.be.ok;
   };
 
@@ -3296,72 +3722,119 @@ module.exports = function (chai, util) {
     assert[as] = assert[name];
     return alias;
   })
   ('Throw', 'throw')
   ('Throw', 'throws');
 };
 
 });
-require.register("chai/lib/chai/interface/expect.js", function(exports, require, module){
+
+require.register("chai/lib/chai/interface/expect.js", function (exports, module) {
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, util) {
   chai.expect = function (val, message) {
     return new chai.Assertion(val, message);
   };
+
+  /**
+   * ### .fail(actual, expected, [message], [operator])
+   *
+   * Throw a failure.
+   *
+   * @name fail
+   * @param {Mixed} actual
+   * @param {Mixed} expected
+   * @param {String} message
+   * @param {String} operator
+   * @api public
+   */
+
+  chai.expect.fail = function (actual, expected, message, operator) {
+    message = message || 'expect.fail()';
+    throw new chai.AssertionError(message, {
+        actual: actual
+      , expected: expected
+      , operator: operator
+    }, chai.expect.fail);
+  };
 };
 
-
 });
-require.register("chai/lib/chai/interface/should.js", function(exports, require, module){
+
+require.register("chai/lib/chai/interface/should.js", function (exports, module) {
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, util) {
   var Assertion = chai.Assertion;
 
   function loadShould () {
+    // explicitly define this method as function as to have it's name to include as `ssfi`
+    function shouldGetter() {
+      if (this instanceof String || this instanceof Number) {
+        return new Assertion(this.constructor(this), null, shouldGetter);
+      } else if (this instanceof Boolean) {
+        return new Assertion(this == true, null, shouldGetter);
+      }
+      return new Assertion(this, null, shouldGetter);
+    }
+    function shouldSetter(value) {
+      // See https://github.com/chaijs/chai/issues/86: this makes
+      // `whatever.should = someValue` actually set `someValue`, which is
+      // especially useful for `global.should = require('chai').should()`.
+      //
+      // Note that we have to use [[DefineProperty]] instead of [[Put]]
+      // since otherwise we would trigger this very setter!
+      Object.defineProperty(this, 'should', {
+        value: value,
+        enumerable: true,
+        configurable: true,
+        writable: true
+      });
+    }
     // modify Object.prototype to have `should`
-    Object.defineProperty(Object.prototype, 'should',
-      {
-        set: function (value) {
-          // See https://github.com/chaijs/chai/issues/86: this makes
-          // `whatever.should = someValue` actually set `someValue`, which is
-          // especially useful for `global.should = require('chai').should()`.
-          //
-          // Note that we have to use [[DefineProperty]] instead of [[Put]]
-          // since otherwise we would trigger this very setter!
-          Object.defineProperty(this, 'should', {
-            value: value,
-            enumerable: true,
-            configurable: true,
-            writable: true
-          });
-        }
-      , get: function(){
-          if (this instanceof String || this instanceof Number) {
-            return new Assertion(this.constructor(this));
-          } else if (this instanceof Boolean) {
-            return new Assertion(this == true);
-          }
-          return new Assertion(this);
-        }
+    Object.defineProperty(Object.prototype, 'should', {
+      set: shouldSetter
+      , get: shouldGetter
       , configurable: true
     });
 
     var should = {};
 
+    /**
+     * ### .fail(actual, expected, [message], [operator])
+     *
+     * Throw a failure.
+     *
+     * @name fail
+     * @param {Mixed} actual
+     * @param {Mixed} expected
+     * @param {String} message
+     * @param {String} operator
+     * @api public
+     */
+
+    should.fail = function (actual, expected, message, operator) {
+      message = message || 'should.fail()';
+      throw new chai.AssertionError(message, {
+          actual: actual
+        , expected: expected
+        , operator: operator
+      }, should.fail);
+    };
+
     should.equal = function (val1, val2, msg) {
       new Assertion(val1, msg).to.equal(val2);
     };
 
     should.Throw = function (fn, errt, errs, msg) {
       new Assertion(fn, msg).to.Throw(errt, errs);
     };
 
@@ -3390,28 +3863,31 @@ module.exports = function (chai, util) {
     return should;
   };
 
   chai.should = loadShould;
   chai.Should = loadShould;
 };
 
 });
-require.register("chai/lib/chai/utils/addChainableMethod.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/addChainableMethod.js", function (exports, module) {
 /*!
  * Chai - addChainingMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependencies
  */
 
-var transferFlags = require('./transferFlags');
+var transferFlags = require('chai/lib/chai/utils/transferFlags.js');
+var flag = require('chai/lib/chai/utils/flag.js');
+var config = require('chai/lib/chai/config.js');
 
 /*!
  * Module variables
  */
 
 // Check whether `__proto__` is supported
 var hasProtoSupport = '__proto__' in Object;
 
@@ -3467,17 +3943,20 @@ module.exports = function (ctx, name, me
     ctx.__methods = {};
   }
   ctx.__methods[name] = chainableBehavior;
 
   Object.defineProperty(ctx, name,
     { get: function () {
         chainableBehavior.chainingBehavior.call(this);
 
-        var assert = function () {
+        var assert = function assert() {
+          var old_ssfi = flag(this, 'ssfi');
+          if (old_ssfi && config.includeStack === false)
+            flag(this, 'ssfi', assert);
           var result = chainableBehavior.method.apply(this, arguments);
           return result === undefined ? this : result;
         };
 
         // Use `__proto__` if available
         if (hasProtoSupport) {
           // Inherit all properties from the object by replacing the `Function` prototype
           var prototype = assert.__proto__ = Object.create(this);
@@ -3499,23 +3978,26 @@ module.exports = function (ctx, name, me
         transferFlags(this, assert);
         return assert;
       }
     , configurable: true
   });
 };
 
 });
-require.register("chai/lib/chai/utils/addMethod.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/addMethod.js", function (exports, module) {
 /*!
  * Chai - addMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
+var config = require('chai/lib/chai/config.js');
+
 /**
  * ### .addMethod (ctx, name, method)
  *
  * Adds a method to the prototype of an object.
  *
  *     utils.addMethod(chai.Assertion.prototype, 'foo', function (str) {
  *       var obj = utils.flag(this, 'object');
  *       new chai.Assertion(obj).to.be.equal(str);
@@ -3530,26 +4012,31 @@ require.register("chai/lib/chai/utils/ad
  *     expect(fooStr).to.be.foo('bar');
  *
  * @param {Object} ctx object to which the method is added
  * @param {String} name of method to add
  * @param {Function} method function to be used for name
  * @name addMethod
  * @api public
  */
+var flag = require('chai/lib/chai/utils/flag.js');
 
 module.exports = function (ctx, name, method) {
   ctx[name] = function () {
+    var old_ssfi = flag(this, 'ssfi');
+    if (old_ssfi && config.includeStack === false)
+      flag(this, 'ssfi', ctx[name]);
     var result = method.apply(this, arguments);
     return result === undefined ? this : result;
   };
 };
 
 });
-require.register("chai/lib/chai/utils/addProperty.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/addProperty.js", function (exports, module) {
 /*!
  * Chai - addProperty utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### addProperty (ctx, name, getter)
@@ -3582,74 +4069,76 @@ module.exports = function (ctx, name, ge
         var result = getter.call(this);
         return result === undefined ? this : result;
       }
     , configurable: true
   });
 };
 
 });
-require.register("chai/lib/chai/utils/flag.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/flag.js", function (exports, module) {
 /*!
  * Chai - flag utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
- * ### flag(object ,key, [value])
+ * ### flag(object, key, [value])
  *
  * Get or set a flag value on an object. If a
  * value is provided it will be set, else it will
  * return the currently set value or `undefined` if
  * the value is not set.
  *
  *     utils.flag(this, 'foo', 'bar'); // setter
  *     utils.flag(this, 'foo'); // getter, returns `bar`
  *
- * @param {Object} object (constructed Assertion
+ * @param {Object} object constructed Assertion
  * @param {String} key
  * @param {Mixed} value (optional)
  * @name flag
  * @api private
  */
 
 module.exports = function (obj, key, value) {
   var flags = obj.__flags || (obj.__flags = Object.create(null));
   if (arguments.length === 3) {
     flags[key] = value;
   } else {
     return flags[key];
   }
 };
 
 });
-require.register("chai/lib/chai/utils/getActual.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getActual.js", function (exports, module) {
 /*!
  * Chai - getActual utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * # getActual(object, [actual])
  *
  * Returns the `actual` value for an Assertion
  *
  * @param {Object} object (constructed Assertion)
  * @param {Arguments} chai.Assertion.prototype.assert arguments
  */
 
 module.exports = function (obj, args) {
-  var actual = args[4];
-  return 'undefined' !== typeof actual ? actual : obj._obj;
+  return args.length > 4 ? args[4] : obj._obj;
 };
 
 });
-require.register("chai/lib/chai/utils/getEnumerableProperties.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getEnumerableProperties.js", function (exports, module) {
 /*!
  * Chai - getEnumerableProperties utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### .getEnumerableProperties(object)
@@ -3667,31 +4156,32 @@ module.exports = function getEnumerableP
   var result = [];
   for (var name in object) {
     result.push(name);
   }
   return result;
 };
 
 });
-require.register("chai/lib/chai/utils/getMessage.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getMessage.js", function (exports, module) {
 /*!
  * Chai - message composition utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
  */
 
-var flag = require('./flag')
-  , getActual = require('./getActual')
-  , inspect = require('./inspect')
-  , objDisplay = require('./objDisplay');
+var flag = require('chai/lib/chai/utils/flag.js')
+  , getActual = require('chai/lib/chai/utils/getActual.js')
+  , inspect = require('chai/lib/chai/utils/inspect.js')
+  , objDisplay = require('chai/lib/chai/utils/objDisplay.js');
 
 /**
  * ### .getMessage(object, message, negateMessage)
  *
  * Construct the error message based on flags
  * and template tags. Template tags will return
  * a stringified inspection of the object referenced.
  *
@@ -3709,27 +4199,29 @@ var flag = require('./flag')
 module.exports = function (obj, args) {
   var negate = flag(obj, 'negate')
     , val = flag(obj, 'object')
     , expected = args[3]
     , actual = getActual(obj, args)
     , msg = negate ? args[2] : args[1]
     , flagMsg = flag(obj, 'message');
 
+  if(typeof msg === "function") msg = msg();
   msg = msg || '';
   msg = msg
     .replace(/#{this}/g, objDisplay(val))
     .replace(/#{act}/g, objDisplay(actual))
     .replace(/#{exp}/g, objDisplay(expected));
 
   return flagMsg ? flagMsg + ': ' + msg : msg;
 };
 
 });
-require.register("chai/lib/chai/utils/getName.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getName.js", function (exports, module) {
 /*!
  * Chai - getName utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * # getName(func)
@@ -3742,24 +4234,27 @@ require.register("chai/lib/chai/utils/ge
 module.exports = function (func) {
   if (func.name) return func.name;
 
   var match = /^\s?function ([^(]*)\(/.exec(func);
   return match && match[1] ? match[1] : "";
 };
 
 });
-require.register("chai/lib/chai/utils/getPathValue.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getPathValue.js", function (exports, module) {
 /*!
  * Chai - getPathValue utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * @see https://github.com/logicalparadox/filtr
  * MIT Licensed
  */
 
+var getPathInfo = require('chai/lib/chai/utils/getPathInfo.js');
+
 /**
  * ### .getPathValue(path, object)
  *
  * This allows the retrieval of values in an
  * object given a string path.
  *
  *     var obj = {
  *         prop1: {
@@ -3779,22 +4274,68 @@ require.register("chai/lib/chai/utils/ge
  *     getPathValue('prop2.arr[0].nested', obj); // Universe
  *
  * @param {String} path
  * @param {Object} object
  * @returns {Object} value or `undefined`
  * @name getPathValue
  * @api public
  */
-
-var getPathValue = module.exports = function (path, obj) {
-  var parsed = parsePath(path);
-  return _getPathValue(parsed, obj);
+module.exports = function(path, obj) {
+  var info = getPathInfo(path, obj);
+  return info.value;
+}; 
+
+});
+
+require.register("chai/lib/chai/utils/getPathInfo.js", function (exports, module) {
+/*!
+ * Chai - getPathInfo utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var hasProperty = require('chai/lib/chai/utils/hasProperty.js');
+
+/**
+ * ### .getPathInfo(path, object)
+ *
+ * This allows the retrieval of property info in an
+ * object given a string path.
+ *
+ * The path info consists of an object with the
+ * following properties:
+ *
+ * * parent - The parent object of the property referenced by `path`
+ * * name - The name of the final property, a number if it was an array indexer
+ * * value - The value of the property, if it exists, otherwise `undefined`
+ * * exists - Whether the property exists or not
+ *
+ * @param {String} path
+ * @param {Object} object
+ * @returns {Object} info
+ * @name getPathInfo
+ * @api public
+ */
+
+module.exports = function getPathInfo(path, obj) {
+  var parsed = parsePath(path),
+      last = parsed[parsed.length - 1];
+
+  var info = {
+    parent: _getPathValue(parsed, obj, parsed.length - 1),
+    name: last.p || last.i,
+    value: _getPathValue(parsed, obj),
+  };
+  info.exists = hasProperty(info.name, info.parent);
+
+  return info;
 };
 
+
 /*!
  * ## parsePath(path)
  *
  * Helper function used to parse string object
  * paths. Use in conjunction with `_getPathValue`.
  *
  *      var parsed = parsePath('myobject.property.subprop');
  *
@@ -3808,56 +4349,129 @@ var getPathValue = module.exports = func
  * @api private
  */
 
 function parsePath (path) {
   var str = path.replace(/\[/g, '.[')
     , parts = str.match(/(\\\.|[^.]+?)+/g);
   return parts.map(function (value) {
     var re = /\[(\d+)\]$/
-      , mArr = re.exec(value)
+      , mArr = re.exec(value);
     if (mArr) return { i: parseFloat(mArr[1]) };
     else return { p: value };
   });
-};
+}
+
 
 /*!
  * ## _getPathValue(parsed, obj)
  *
  * Helper companion function for `.parsePath` that returns
  * the value located at the parsed address.
  *
  *      var value = getPathValue(parsed, obj);
  *
  * @param {Object} parsed definition from `parsePath`.
  * @param {Object} object to search against
+ * @param {Number} object to search against
  * @returns {Object|Undefined} value
  * @api private
  */
 
-function _getPathValue (parsed, obj) {
+function _getPathValue (parsed, obj, index) {
   var tmp = obj
     , res;
-  for (var i = 0, l = parsed.length; i < l; i++) {
+
+  index = (index === undefined ? parsed.length : index);
+
+  for (var i = 0, l = index; i < l; i++) {
     var part = parsed[i];
     if (tmp) {
       if ('undefined' !== typeof part.p)
         tmp = tmp[part.p];
       else if ('undefined' !== typeof part.i)
         tmp = tmp[part.i];
       if (i == (l - 1)) res = tmp;
     } else {
       res = undefined;
     }
   }
   return res;
+}
+
+});
+
+require.register("chai/lib/chai/utils/hasProperty.js", function (exports, module) {
+/*!
+ * Chai - hasProperty utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var type = require('chai/lib/chai/utils/type.js');
+
+/**
+ * ### .hasProperty(object, name)
+ *
+ * This allows checking whether an object has
+ * named property or numeric array index.
+ *
+ * Basically does the same thing as the `in`
+ * operator but works properly with natives
+ * and null/undefined values.
+ *
+ *     var obj = {
+ *         arr: ['a', 'b', 'c']
+ *       , str: 'Hello'
+ *     }
+ *
+ * The following would be the results.
+ *
+ *     hasProperty('str', obj);  // true
+ *     hasProperty('constructor', obj);  // true
+ *     hasProperty('bar', obj);  // false
+ *     
+ *     hasProperty('length', obj.str); // true
+ *     hasProperty(1, obj.str);  // true
+ *     hasProperty(5, obj.str);  // false
+ *
+ *     hasProperty('length', obj.arr);  // true
+ *     hasProperty(2, obj.arr);  // true
+ *     hasProperty(3, obj.arr);  // false
+ *
+ * @param {Objuect} object
+ * @param {String|Number} name
+ * @returns {Boolean} whether it exists
+ * @name getPathInfo
+ * @api public
+ */
+
+var literals = {
+    'number': Number
+  , 'string': String
+};
+
+module.exports = function hasProperty(name, obj) {
+  var ot = type(obj);
+
+  // Bad Object, obviously no props at all
+  if(ot === 'null' || ot === 'undefined')
+    return false;
+
+  // The `in` operator does not work with certain literals
+  // box these before the check
+  if(literals[ot] && typeof obj !== 'object')
+    obj = new literals[ot](obj);
+
+  return name in obj;
 };
 
 });
-require.register("chai/lib/chai/utils/getProperties.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/getProperties.js", function (exports, module) {
 /*!
  * Chai - getProperties utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### .getProperties(object)
@@ -3885,140 +4499,154 @@ module.exports = function getProperties(
     Object.getOwnPropertyNames(proto).forEach(addProperty);
     proto = Object.getPrototypeOf(proto);
   }
 
   return result;
 };
 
 });
-require.register("chai/lib/chai/utils/index.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/index.js", function (exports, module) {
 /*!
  * chai
  * Copyright(c) 2011 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Main exports
  */
 
 var exports = module.exports = {};
 
 /*!
  * test utility
  */
 
-exports.test = require('./test');
+exports.test = require('chai/lib/chai/utils/test.js');
 
 /*!
  * type utility
  */
 
-exports.type = require('./type');
+exports.type = require('chai/lib/chai/utils/type.js');
 
 /*!
  * message utility
  */
 
-exports.getMessage = require('./getMessage');
+exports.getMessage = require('chai/lib/chai/utils/getMessage.js');
 
 /*!
  * actual utility
  */
 
-exports.getActual = require('./getActual');
+exports.getActual = require('chai/lib/chai/utils/getActual.js');
 
 /*!
  * Inspect util
  */
 
-exports.inspect = require('./inspect');
+exports.inspect = require('chai/lib/chai/utils/inspect.js');
 
 /*!
  * Object Display util
  */
 
-exports.objDisplay = require('./objDisplay');
+exports.objDisplay = require('chai/lib/chai/utils/objDisplay.js');
 
 /*!
  * Flag utility
  */
 
-exports.flag = require('./flag');
+exports.flag = require('chai/lib/chai/utils/flag.js');
 
 /*!
  * Flag transferring utility
  */
 
-exports.transferFlags = require('./transferFlags');
+exports.transferFlags = require('chai/lib/chai/utils/transferFlags.js');
 
 /*!
  * Deep equal utility
  */
 
-exports.eql = require('deep-eql');
+exports.eql = require('chaijs~deep-eql@0.1.3');
 
 /*!
  * Deep path value
  */
 
-exports.getPathValue = require('./getPathValue');
+exports.getPathValue = require('chai/lib/chai/utils/getPathValue.js');
+
+/*!
+ * Deep path info
+ */
+
+exports.getPathInfo = require('chai/lib/chai/utils/getPathInfo.js');
+
+/*!
+ * Check if a property exists
+ */
+
+exports.hasProperty = require('chai/lib/chai/utils/hasProperty.js');
 
 /*!
  * Function name
  */
 
-exports.getName = require('./getName');
+exports.getName = require('chai/lib/chai/utils/getName.js');
 
 /*!
  * add Property
  */
 
-exports.addProperty = require('./addProperty');
+exports.addProperty = require('chai/lib/chai/utils/addProperty.js');
 
 /*!
  * add Method
  */
 
-exports.addMethod = require('./addMethod');
+exports.addMethod = require('chai/lib/chai/utils/addMethod.js');
 
 /*!
  * overwrite Property
  */
 
-exports.overwriteProperty = require('./overwriteProperty');
+exports.overwriteProperty = require('chai/lib/chai/utils/overwriteProperty.js');
 
 /*!
  * overwrite Method
  */
 
-exports.overwriteMethod = require('./overwriteMethod');
+exports.overwriteMethod = require('chai/lib/chai/utils/overwriteMethod.js');
 
 /*!
  * Add a chainable method
  */
 
-exports.addChainableMethod = require('./addChainableMethod');
+exports.addChainableMethod = require('chai/lib/chai/utils/addChainableMethod.js');
 
 /*!
  * Overwrite chainable method
  */
 
-exports.overwriteChainableMethod = require('./overwriteChainableMethod');
+exports.overwriteChainableMethod = require('chai/lib/chai/utils/overwriteChainableMethod.js');
 
 
 });
-require.register("chai/lib/chai/utils/inspect.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/inspect.js", function (exports, module) {
 // This is (almost) directly from Node.js utils
 // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js
 
-var getName = require('./getName');
-var getProperties = require('./getProperties');
-var getEnumerableProperties = require('./getEnumerableProperties');
+var getName = require('chai/lib/chai/utils/getName.js');
+var getProperties = require('chai/lib/chai/utils/getProperties.js');
+var getEnumerableProperties = require('chai/lib/chai/utils/getEnumerableProperties.js');
 
 module.exports = inspect;
 
 /**
  * Echos the value of a value. Trys to print the value out
  * in the best way possible given the different types.
  *
  * @param {Object} obj The object to print out.
@@ -4032,34 +4660,16 @@ function inspect(obj, showHidden, depth,
   var ctx = {
     showHidden: showHidden,
     seen: [],
     stylize: function (str) { return str; }
   };
   return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth));
 }
 
-// https://gist.github.com/1044128/
-var getOuterHTML = function(element) {
-  if ('outerHTML' in element) return element.outerHTML;
-  var ns = "http://www.w3.org/1999/xhtml";
-  var container = document.createElementNS(ns, '_');
-  var elemProto = (window.HTMLElement || window.Element).prototype;
-  var xmlSerializer = new XMLSerializer();
-  var html;
-  if (document.xmlVersion) {
-    return xmlSerializer.serializeToString(element);
-  } else {
-    container.appendChild(element.cloneNode(false));
-    html = container.innerHTML.replace('><', '>' + element.innerHTML + '<');
-    container.innerHTML = '';
-    return html;
-  }
-};
-
 // Returns true if object is a DOM element.
 var isDOMElement = function (object) {
   if (typeof HTMLElement === 'object') {
     return object instanceof HTMLElement;
   } else {
     return object &&
       typeof object === 'object' &&
       object.nodeType === 1 &&
@@ -4083,19 +4693,47 @@ function formatValue(ctx, value, recurse
   }
 
   // Primitive types cannot have properties
   var primitive = formatPrimitive(ctx, value);
   if (primitive) {
     return primitive;
   }
 
-  // If it's DOM elem, get outer HTML.
+  // If this is a DOM element, try to get the outer HTML.
   if (isDOMElement(value)) {
-    return getOuterHTML(value);
+    if ('outerHTML' in value) {
+      return value.outerHTML;
+      // This value does not have an outerHTML attribute,
+      //   it could still be an XML element
+    } else {
+      // Attempt to serialize it
+      try {
+        if (document.xmlVersion) {
+          var xmlSerializer = new XMLSerializer();
+          return xmlSerializer.serializeToString(value);
+        } else {
+          // Firefox 11- do not support outerHTML
+          //   It does, however, support innerHTML
+          //   Use the following to render the element
+          var ns = "http://www.w3.org/1999/xhtml";
+          var container = document.createElementNS(ns, '_');
+
+          container.appendChild(value.cloneNode(false));
+          html = container.innerHTML
+            .replace('><', '>' + value.innerHTML + '<');
+          container.innerHTML = '';
+          return html;
+        }
+      } catch (err) {
+        // This could be a non-native DOM implementation,
+        //   continue with the normal flow:
+        //   printing the element as if it is an object.
+      }
+    }
   }
 
   // Look up the keys of the object.
   var visibleKeys = getEnumerableProperties(value);
   var keys = ctx.showHidden ? getProperties(value) : visibleKeys;
 
   // Some type of object without properties can be shortcutted.
   // In IE, errors have a single `stack` property, or if they are vanilla `Error`,
@@ -4186,16 +4824,19 @@ function formatPrimitive(ctx, value) {
 
     case 'string':
       var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
                                                .replace(/'/g, "\\'")
                                                .replace(/\\"/g, '"') + '\'';
       return ctx.stylize(simple, 'string');
 
     case 'number':
+      if (value === 0 && (1/value) === -Infinity) {
+        return ctx.stylize('-0', 'number');
+      }
       return ctx.stylize('' + value, 'number');
 
     case 'boolean':
       return ctx.stylize('' + value, 'boolean');
   }
   // For some reason typeof null is "object", so special case here.
   if (value === null) {
     return ctx.stylize('null', 'null');
@@ -4325,28 +4966,30 @@ function isError(e) {
   return typeof e === 'object' && objectToString(e) === '[object Error]';
 }
 
 function objectToString(o) {
   return Object.prototype.toString.call(o);
 }
 
 });
-require.register("chai/lib/chai/utils/objDisplay.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/objDisplay.js", function (exports, module) {
 /*!
  * Chai - flag utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
  */
 
-var inspect = require('./inspect');
+var inspect = require('chai/lib/chai/utils/inspect.js');
+var config = require('chai/lib/chai/config.js');
 
 /**
  * ### .objDisplay (object)
  *
  * Determines if an object or an array matches
  * criteria to be inspected in-line for error
  * messages or should be truncated.
  *
@@ -4354,17 +4997,17 @@ var inspect = require('./inspect');
  * @name objDisplay
  * @api public
  */
 
 module.exports = function (obj) {
   var str = inspect(obj)
     , type = Object.prototype.toString.call(obj);
 
-  if (str.length >= 40) {
+  if (config.truncateThreshold && str.length >= config.truncateThreshold) {
     if (type === '[object Function]') {
       return !obj.name || obj.name === ''
         ? '[Function]'
         : '[Function: ' + obj.name + ']';
     } else if (type === '[object Array]') {
       return '[ Array(' + obj.length + ') ]';
     } else if (type === '[object Object]') {
       var keys = Object.keys(obj)
@@ -4376,17 +5019,18 @@ module.exports = function (obj) {
       return str;
     }
   } else {
     return str;
   }
 };
 
 });
-require.register("chai/lib/chai/utils/overwriteMethod.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/overwriteMethod.js", function (exports, module) {
 /*!
  * Chai - overwriteMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### overwriteMethod (ctx, name, fn)
@@ -4430,17 +5074,18 @@ module.exports = function (ctx, name, me
 
   ctx[name] = function () {
     var result = method(_super).apply(this, arguments);
     return result === undefined ? this : result;
   }
 };
 
 });
-require.register("chai/lib/chai/utils/overwriteProperty.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/overwriteProperty.js", function (exports, module) {
 /*!
  * Chai - overwriteProperty utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### overwriteProperty (ctx, name, fn)
@@ -4487,25 +5132,26 @@ module.exports = function (ctx, name, ge
         var result = getter(_super).call(this);
         return result === undefined ? this : result;
       }
     , configurable: true
   });
 };
 
 });
-require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function (exports, module) {
 /*!
  * Chai - overwriteChainableMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
- * ### overwriteChainableMethod (ctx, name, fn)
+ * ### overwriteChainableMethod (ctx, name, method, chainingBehavior)
  *
  * Overwites an already existing chainable method
  * and provides access to the previous function or
  * property.  Must return functions to be used for
  * name.
  *
  *     utils.overwriteChainableMethod(chai.Assertion.prototype, 'length',
  *       function (_super) {
@@ -4543,28 +5189,29 @@ module.exports = function (ctx, name, me
   var _method = chainableBehavior.method;
   chainableBehavior.method = function () {
     var result = method(_method).apply(this, arguments);
     return result === undefined ? this : result;
   };
 };
 
 });
-require.register("chai/lib/chai/utils/test.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/test.js", function (exports, module) {
 /*!
  * Chai - test utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
  */
 
-var flag = require('./flag');
+var flag = require('chai/lib/chai/utils/flag.js');
 
 /**
  * # test(object, expression)
  *
  * Test and object for expression.
  *
  * @param {Object} object (constructed Assertion)
  * @param {Arguments} chai.Assertion.prototype.assert arguments
@@ -4572,17 +5219,18 @@ var flag = require('./flag');
 
 module.exports = function (obj, args) {
   var negate = flag(obj, 'negate')
     , expr = args[0];
   return negate ? !expr : expr;
 };
 
 });
-require.register("chai/lib/chai/utils/transferFlags.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/transferFlags.js", function (exports, module) {
 /*!
  * Chai - transferFlags utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### transferFlags(assertion, object, includeAll = true)
@@ -4595,19 +5243,19 @@ require.register("chai/lib/chai/utils/tr
  *
  *     var newAssertion = new Assertion();
  *     utils.transferFlags(assertion, newAssertion);
  *
  *     var anotherAsseriton = new Assertion(myObj);
  *     utils.transferFlags(assertion, anotherAssertion, false);
  *
  * @param {Assertion} assertion the assertion to transfer the flags from
- * @param {Object} object the object to transfer the flags too; usually a new assertion
+ * @param {Object} object the object to transfer the flags to; usually a new assertion
  * @param {Boolean} includeAll
- * @name getAllFlags
+ * @name transferFlags
  * @api private
  */
 
 module.exports = function (assertion, object, includeAll) {
   var flags = assertion.__flags || (assertion.__flags = Object.create(null));
 
   if (!object.__flags) {
     object.__flags = Object.create(null);
@@ -4619,17 +5267,18 @@ module.exports = function (assertion, ob
     if (includeAll ||
         (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) {
       object.__flags[flag] = flags[flag];
     }
   }
 };
 
 });
-require.register("chai/lib/chai/utils/type.js", function(exports, require, module){
+
+require.register("chai/lib/chai/utils/type.js", function (exports, module) {
 /*!
  * Chai - type utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Detectable javascript natives
@@ -4668,29 +5317,16 @@ module.exports = function (obj) {
   if (obj === null) return 'null';
   if (obj === undefined) return 'undefined';
   if (obj === Object(obj)) return 'object';
   return typeof obj;
 };
 
 });
 
-
-
-
-require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
-require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
-require.alias("chaijs-assertion-error/index.js", "assertion-error/index.js");
-require.alias("chaijs-assertion-error/index.js", "chaijs-assertion-error/index.js");
-require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/lib/eql.js");
-require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/index.js");
-require.alias("chaijs-deep-eql/lib/eql.js", "deep-eql/index.js");
-require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/lib/type.js");
-require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/index.js");
-require.alias("chaijs-type-detect/lib/type.js", "chaijs-type-detect/index.js");
-require.alias("chaijs-deep-eql/lib/eql.js", "chaijs-deep-eql/index.js");
-require.alias("chai/index.js", "chai/index.js");if (typeof exports == "object") {
+if (typeof exports == "object") {
   module.exports = require("chai");
 } else if (typeof define == "function" && define.amd) {
-  define([], function(){ return require("chai"); });
+  define("chai", [], function(){ return require("chai"); });
 } else {
-  this["chai"] = require("chai");
-}})();
\ No newline at end of file
+  (this || window)["chai"] = require("chai");
+}
+})()
rename from browser/components/loop/test/shared/vendor/mocha-2.0.1.css
rename to browser/components/loop/test/shared/vendor/mocha-2.2.1.css
rename from browser/components/loop/test/shared/vendor/mocha-2.0.1.js
rename to browser/components/loop/test/shared/vendor/mocha-2.2.1.js
--- a/browser/components/loop/test/shared/vendor/mocha-2.0.1.js
+++ b/browser/components/loop/test/shared/vendor/mocha-2.2.1.js
@@ -218,17 +218,32 @@ var JsDiff = (function() {
 
   var CssDiff = new Diff(true);
   CssDiff.tokenize = function(value) {
     return removeEmpty(value.split(/([{}:;,]|\s+)/));
   };
 
   var LineDiff = new Diff();
   LineDiff.tokenize = function(value) {
-    return value.split(/^/m);
+    var retLines = [],
+        lines = value.split(/^/m);
+
+    for(var i = 0; i < lines.length; i++) {
+      var line = lines[i],
+          lastLine = lines[i - 1];
+
+      // Merge lines that may contain windows new lines
+      if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
+        retLines[retLines.length - 1] += '\n';
+      } else if (line) {
+        retLines.push(line);
+      }
+    }
+
+    return retLines;
   };
 
   return {
     Diff: Diff,
 
     diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
     diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
     diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
@@ -832,16 +847,28 @@ Context.prototype.enableTimeouts = funct
  */
 
 Context.prototype.slow = function(ms){
   this.runnable().slow(ms);
   return this;
 };
 
 /**
+ * Mark a test as skipped.
+ *
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.skip = function(){
+    this.runnable().skip();
+    return this;
+};
+
+/**
  * Inspect the context void of `._runnable`.
  *
  * @return {String}
  * @api private
  */
 
 Context.prototype.inspect = function(){
   return JSON.stringify(this, function(key, val){
@@ -936,48 +963,23 @@ var Suite = require('../suite')
  *
  */
 
 module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
-    /**
-     * Execute before running tests.
-     */
-
-    context.before = function(name, fn){
-      suites[0].beforeAll(name, fn);
-    };
-
-    /**
-     * Execute after running tests.
-     */
-
-    context.after = function(name, fn){
-      suites[0].afterAll(name, fn);
-    };
-
-    /**
-     * Execute before each test case.
-     */
-
-    context.beforeEach = function(name, fn){
-      suites[0].beforeEach(name, fn);
-    };
-
-    /**
-     * Execute after each test case.
-     */
-
-    context.afterEach = function(name, fn){
-      suites[0].afterEach(name, fn);
-    };
-
+    var common = require('./common')(suites, context);
+
+    context.before = common.before;
+    context.after = common.after;
+    context.beforeEach = common.beforeEach;
+    context.afterEach = common.afterEach;
+    context.run = mocha.options.delay && common.runWithSuite(suite);
     /**
      * Describe a "suite" with the given `title`
      * and callback `fn` containing nested suites
      * and/or tests.
      */
 
     context.describe = context.context = function(title, fn){
       var suite = Suite.create(suites[0], title);
@@ -1042,21 +1044,84 @@ module.exports = function(suite){
      * Pending test case.
      */
 
     context.xit =
     context.xspecify =
     context.it.skip = function(title){
       context.it(title);
     };
+
   });
 };
 
 }); // module: interfaces/bdd.js
 
+require.register("interfaces/common.js", function(module, exports, require){
+/**
+ * Functions common to more than one interface
+ * @module lib/interfaces/common
+ */
+
+'use strict';
+
+module.exports = function (suites, context) {
+
+  return {
+    /**
+     * This is only present if flag --delay is passed into Mocha.  It triggers
+     * root suite execution.  Returns a function which runs the root suite.
+     */
+    runWithSuite: function runWithSuite(suite) {
+      return function run() {
+        suite.run();
+      };
+    },
+
+    /**
+     * Execute before running tests.
+     */
+    before: function (name, fn) {
+      suites[0].beforeAll(name, fn);
+    },
+
+    /**
+     * Execute after running tests.
+     */
+    after: function (name, fn) {
+      suites[0].afterAll(name, fn);
+    },
+
+    /**
+     * Execute before each test case.
+     */
+    beforeEach: function (name, fn) {
+      suites[0].beforeEach(name, fn);
+    },
+
+    /**
+     * Execute after each test case.
+     */
+    afterEach: function (name, fn) {
+      suites[0].afterEach(name, fn);
+    },
+
+    test: {
+      /**
+       * Pending test case.
+       */
+      skip: function (title) {
+        context.test(title);
+      }
+    }
+  }
+};
+
+}); // module: interfaces/common.js
+
 require.register("interfaces/exports.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite')
   , Test = require('../test');
 
@@ -1160,48 +1225,23 @@ var Suite = require('../suite')
  *
  */
 
 module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
-    /**
-     * Execute before running tests.
-     */
-
-    context.before = function(name, fn){
-      suites[0].beforeAll(name, fn);
-    };
-
-    /**
-     * Execute after running tests.
-     */
-
-    context.after = function(name, fn){
-      suites[0].afterAll(name, fn);
-    };
-
-    /**
-     * Execute before each test case.
-     */
-
-    context.beforeEach = function(name, fn){
-      suites[0].beforeEach(name, fn);
-    };
-
-    /**
-     * Execute after each test case.
-     */
-
-    context.afterEach = function(name, fn){
-      suites[0].afterEach(name, fn);
-    };
-
+    var common = require('./common')(suites, context);
+
+    context.before = common.before;
+    context.after = common.after;
+    context.beforeEach = common.beforeEach;
+    context.afterEach = common.afterEach;
+    context.run = mocha.options.delay && common.runWithSuite(suite);
     /**
      * Describe a "suite" with the given `title`.
      */
 
     context.suite = function(title){
       if (suites.length > 1) suites.shift();
       var suite = Suite.create(suites[0], title);
       suite.file = file;
@@ -1236,23 +1276,18 @@ module.exports = function(suite){
      */
 
     context.test.only = function(title, fn){
       var test = context.test(title, fn);
       var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
-    /**
-     * Pending test case.
-     */
-
-    context.test.skip = function(title){
-      context.test(title);
-    };
+    context.test.skip = common.test.skip;
+
   });
 };
 
 }); // module: interfaces/qunit.js
 
 require.register("interfaces/tdd.js", function(module, exports, require){
 /**
  * Module dependencies.
@@ -1288,48 +1323,23 @@ var Suite = require('../suite')
  *
  */
 
 module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
-    /**
-     * Execute before each test case.
-     */
-
-    context.setup = function(name, fn){
-      suites[0].beforeEach(name, fn);
-    };
-
-    /**
-     * Execute after each test case.
-     */
-
-    context.teardown = function(name, fn){
-      suites[0].afterEach(name, fn);
-    };
-
-    /**
-     * Execute before the suite.
-     */
-
-    context.suiteSetup = function(name, fn){
-      suites[0].beforeAll(name, fn);
-    };
-
-    /**
-     * Execute after the suite.
-     */
-
-    context.suiteTeardown = function(name, fn){
-      suites[0].afterAll(name, fn);
-    };
-
+    var common = require('./common')(suites, context);
+
+    context.setup = common.beforeEach;
+    context.teardown = common.afterEach;
+    context.suiteSetup = common.before;
+    context.suiteTeardown = common.after;
+    context.run = mocha.options.delay && common.runWithSuite(suite);
     /**
      * Describe a "suite" with the given `title`
      * and callback `fn` containing nested suites
      * and/or tests.
      */
 
     context.suite = function(title, fn){
       var suite = Suite.create(suites[0], title);
@@ -1380,23 +1390,17 @@ module.exports = function(suite){
      */
 
     context.test.only = function(title, fn){
       var test = context.test(title, fn);
       var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
-    /**
-     * Pending test case.
-     */
-
-    context.test.skip = function(title){
-      context.test(title);
-    };
+    context.test.skip = common.test.skip;
   });
 };
 
 }); // module: interfaces/tdd.js
 
 require.register("mocha.js", function(module, exports, require){
 /*!
  * mocha
@@ -1471,21 +1475,22 @@ function image(name) {
  * @param {Object} options
  * @api public
  */
 
 function Mocha(options) {
   options = options || {};
   this.files = [];
   this.options = options;
-  this.grep(options.grep);
+  if (options.grep) this.grep(new RegExp(options.grep));
+  if (options.fgrep) this.grep(options.fgrep);
   this.suite = new exports.Suite('', new exports.Context);
   this.ui(options.ui);
   this.bail(options.bail);
-  this.reporter(options.reporter);
+  this.reporter(options.reporter, options.reporterOptions);
   if (null != options.timeout) this.timeout(options.timeout);
   this.useColors(options.useColors)
   if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts);
   if (options.slow) this.slow(options.slow);
 
   this.suite.on('pre-require', function (context) {
     exports.afterEach = context.afterEach || context.teardown;
     exports.after = context.after || context.suiteTeardown;
@@ -1494,16 +1499,17 @@ function Mocha(options) {
     exports.describe = context.describe || context.suite;
     exports.it = context.it || context.test;
     exports.setup = context.setup || context.beforeEach;
     exports.suiteSetup = context.suiteSetup || context.before;
     exports.suiteTeardown = context.suiteTeardown || context.after;
     exports.suite = context.suite || context.describe;
     exports.teardown = context.teardown || context.afterEach;
     exports.test = context.test || context.it;
+    exports.run = context.run;
   });
 }
 
 /**
  * Enable or disable bailing on the first failure.
  *
  * @param {Boolean} [bail]
  * @api public
@@ -1526,34 +1532,35 @@ Mocha.prototype.addFile = function(file)
   this.files.push(file);
   return this;
 };
 
 /**
  * Set reporter to `reporter`, defaults to "spec".
  *
  * @param {String|Function} reporter name or constructor
+ * @param {Object} reporterOptions optional options
  * @api public
  */
-
-Mocha.prototype.reporter = function(reporter){
+Mocha.prototype.reporter = function(reporter, reporterOptions){
   if ('function' == typeof reporter) {
     this._reporter = reporter;
   } else {
     reporter = reporter || 'spec';
     var _reporter;
     try { _reporter = require('./reporters/' + reporter); } catch (err) {};
     if (!_reporter) try { _reporter = require(reporter); } catch (err) {};
     if (!_reporter && reporter === 'teamcity')
       console.warn('The Teamcity reporter was moved to a package named ' +
         'mocha-teamcity-reporter ' +
         '(https://npmjs.org/package/mocha-teamcity-reporter).');
     if (!_reporter) throw new Error('invalid reporter "' + reporter + '"');
     this._reporter = _reporter;
   }
+  this.options.reporterOptions = reporterOptions;
   return this;
 };
 
 /**
  * Set test UI `name`, defaults to "bdd".
  *
  * @param {String} bdd
  * @api public
@@ -1692,19 +1699,19 @@ Mocha.prototype.globals = function(globa
  * Emit color output.
  *
  * @param {Boolean} colors
  * @return {Mocha}
  * @api public
  */
 
 Mocha.prototype.useColors = function(colors){
-  this.options.useColors = arguments.length && colors != undefined
-    ? colors
-    : true;
+  if (colors !== undefined) {
+    this.options.useColors = colors;
+  }
   return this;
 };
 
 /**
  * Use inline diffs rather than +/-.
  *
  * @param {Boolean} inlineDiffs
  * @return {Mocha}
@@ -1777,38 +1784,56 @@ Mocha.prototype.asyncOnly = function(){
  * @api public
  */
 Mocha.prototype.noHighlighting = function() {
   this.options.noHighlighting = true;
   return this;
 };
 
 /**
+ * Delay root suite execution.
+ * @returns {Mocha}
+ * @api public
+ */
+Mocha.prototype.delay = function delay() {
+  this.options.delay = true;
+  return this;
+};
+
+/**
  * Run tests and invoke `fn()` when complete.
  *
  * @param {Function} fn
  * @return {Runner}
  * @api public
  */
-
 Mocha.prototype.run = function(fn){
   if (this.files.length) this.loadFiles();
   var suite = this.suite;
   var options = this.options;
   options.files = this.files;
-  var runner = new exports.Runner(suite);
+  var runner = new exports.Runner(suite, options.delay);
   var reporter = new this._reporter(runner, options);
   runner.ignoreLeaks = false !== options.ignoreLeaks;
   runner.asyncOnly = options.asyncOnly;
   if (options.grep) runner.grep(options.grep, options.invert);
   if (options.globals) runner.globals(options.globals);
   if (options.growl) this._growl(runner, reporter);
-  exports.reporters.Base.useColors = options.useColors;
+  if (options.useColors !== undefined) {
+    exports.reporters.Base.useColors = options.useColors;
+  }
   exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
-  return runner.run(fn);
+
+  function done(failures) {
+      if (reporter.done) {
+          reporter.done(failures, fn);
+      } else fn && fn(failures);
+  }
+
+  return runner.run(done);
 };
 
 }); // module: mocha.js
 
 require.register("ms.js", function(module, exports, require){
 /**
  * Helpers.
  */
@@ -1916,25 +1941,46 @@ function longFormat(ms) {
 function plural(ms, n, name) {
   if (ms < n) return;
   if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
   return Math.ceil(ms / n) + ' ' + name + 's';
 }
 
 }); // module: ms.js
 
+require.register("pending.js", function(module, exports, require){
+
+/**
+ * Expose `Pending`.
+ */
+
+module.exports = Pending;
+
+/**
+ * Initialize a new `Pending` error with the given message.
+ *
+ * @param {String} message
+ */
+
+function Pending(message) {
+    this.message = message;
+}
+
+}); // module: pending.js
+
 require.register("reporters/base.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var tty = require('browser/tty')
   , diff = require('browser/diff')
   , ms = require('../ms')
-  , utils = require('../utils');
+  , utils = require('../utils')
+  , supportsColor = process.env ? require('supports-color') : null;
 
 /**
  * Save timer references to avoid Sinon interfering (see GH-237).
  */
 
 var Date = global.Date
   , setTimeout = global.setTimeout
   , setInterval = global.setInterval
@@ -1949,20 +1995,22 @@ var isatty = tty.isatty(1) && tty.isatty
 
 /**
  * Expose `Base`.
  */
 
 exports = module.exports = Base;
 
 /**
- * Enable coloring by default.
- */
-
-exports.useColors = isatty || (process.env.MOCHA_COLORS !== undefined);
+ * Enable coloring by default, except in the browser interface.
+ */
+
+exports.useColors = process.env
+  ? (supportsColor || (process.env.MOCHA_COLORS !== undefined))
+  : false;
 
 /**
  * Inline diffs instead of +/-
  */
 
 exports.inlineDiffs = false;
 
 /**
@@ -2016,17 +2064,17 @@ if ('win32' == process.platform) {
  *
  * @param {String} type
  * @param {String} str
  * @return {String}
  * @api private
  */
 
 var color = exports.color = function(type, str) {
-  if (!exports.useColors) return str;
+  if (!exports.useColors) return String(str);
   return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
 };
 
 /**
  * Expose term window size, with some
  * defaults for when stderr is not a tty.
  */
 
@@ -2073,17 +2121,17 @@ exports.cursor = {
 /**
  * Outut the given `failures` as a list.
  *
  * @param {Array} failures
  * @api public
  */
 
 exports.list = function(failures){
-  console.error();
+  console.log();
   failures.forEach(function(test, i){
     // format
     var fmt = color('error title', '  %s) %s:\n')
       + color('error message', '     %s')
       + color('error stack', '\n%s\n');
 
     // msg
     var err = test.err
@@ -2094,42 +2142,41 @@ exports.list = function(failures){
       , actual = err.actual
       , expected = err.expected
       , escape = true;
 
     // uncaught
     if (err.uncaught) {
       msg = 'Uncaught ' + msg;
     }
-
     // explicitly show diff
     if (err.showDiff && sameType(actual, expected)) {
-      escape = false;
-      err.actual = actual = utils.stringify(actual);
-      err.expected = expected = utils.stringify(expected);
-    }
-
-    // actual / expected diff
-    if (err.showDiff && 'string' == typeof actual && 'string' == typeof expected) {
+
+      if ('string' !== typeof actual) {
+        escape = false;
+        err.actual = actual = utils.stringify(actual);
+        err.expected = expected = utils.stringify(expected);
+      }
+
       fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n');
       var match = message.match(/^([^:]+): expected/);
       msg = '\n      ' + color('error message', match ? match[1] : msg);
 
       if (exports.inlineDiffs) {
         msg += inlineDiff(err, escape);
       } else {
         msg += unifiedDiff(err, escape);
       }
     }
 
     // indent stack trace without msg
     stack = stack.slice(index ? index + 1 : index)
       .replace(/^/gm, '  ');
 
-    console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
+    console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
   });
 };
 
 /**
  * Initialize a new `Base` reporter.
  *
  * All other reporters generally
  * inherit from this reporter, providing
@@ -2224,21 +2271,20 @@ Base.prototype.epilogue = function(){
 
     console.log(fmt, stats.pending);
   }
 
   // failures
   if (stats.failures) {
     fmt = color('fail', '  %d failing');
 
-    console.error(fmt,
-      stats.failures);
+    console.log(fmt, stats.failures);
 
     Base.list(this.failures);
-    console.error();
+    console.log();
   }
 
   console.log();
 };
 
 /**
  * Pad the given `str` to `len`.
  *
@@ -2306,17 +2352,17 @@ function unifiedDiff(err, escape) {
     if (line[0] === '-') return indent + colorLines('diff removed', line);
     if (line.match(/\@\@/)) return null;
     if (line.match(/\\ No newline/)) return null;
     else return indent + line;
   }
   function notBlank(line) {
     return line != null;
   }
-  msg = diff.createPatch('string', err.actual, err.expected);
+  var msg = diff.createPatch('string', err.actual, err.expected);
   var lines = msg.split('\n').splice(4);
   return '\n      '
          + colorLines('diff added',   '+ expected') + ' '
          + colorLines('diff removed', '- actual')
          + '\n\n'
          + lines.map(cleanUp).filter(notBlank).join('\n');
 }
 
@@ -2706,17 +2752,17 @@ function HTML(runner) {
 
     // test
     if ('passed' == test.state) {
       var url = self.testURL(test);
       var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
     } else if (test.pending) {
       var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
     } else {
-      var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle()));
+      var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
       var str = test.err.stack || test.err.toString();
 
       // FF / Opera do not add the message
       if (!~str.indexOf(test.err.message)) {
         str = test.err.message + '\n' + str;
       }
 
       // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
@@ -2754,17 +2800,23 @@ function HTML(runner) {
 
 /**
  * Makes a URL, preserving querystring ("search") parameters.
  * @param {string} s
  * @returns {string} your new URL
  */
 var makeUrl = function makeUrl(s) {
   var search = window.location.search;
-  return (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
+
+  // Remove previous grep query parameter if present
+  if (search) {
+    search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
+  }
+
+  return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
 };
 
 /**
  * Provide suite URL
  *
  * @param {Object} [suite]
  */
 HTML.prototype.suiteURL = function(suite){
@@ -3376,16 +3428,22 @@ require.register("reporters/markdown.js"
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , utils = require('../utils');
 
 /**
+ * Constants
+ */
+
+var SUITE_PREFIX = '$';
+
+/**
  * Expose `Markdown`.
  */
 
 exports = module.exports = Markdown;
 
 /**
  * Initialize a new `Markdown` reporter.
  *
@@ -3405,35 +3463,38 @@ function Markdown(runner) {
     return Array(level).join('#') + ' ' + str;
   }
 
   function indent() {
     return Array(level).join('  ');
   }
 
   function mapTOC(suite, obj) {
-    var ret = obj;
-    obj = obj[suite.title] = obj[suite.title] || { suite: suite };
+    var ret = obj,
+        key = SUITE_PREFIX + suite.title;
+    obj = obj[key] = obj[key] || { suite: suite };
     suite.suites.forEach(function(suite){
       mapTOC(suite, obj);
     });
     return ret;
   }
 
   function stringifyTOC(obj, level) {
     ++level;
     var buf = '';
     var link;
     for (var key in obj) {
       if ('suite' == key) continue;
-      if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
-      if (key) buf += Array(level).join('  ') + link;
+      if (key !== SUITE_PREFIX) {
+        link = ' - [' + key.substring(1) + ']';
+        link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
+        buf += Array(level).join('  ') + link;
+      }
       buf += stringifyTOC(obj[key], level);
     }
-    --level;
     return buf;
   }
 
   function generateTOC(suite) {
     var obj = mapTOC(suite, {});
     return stringifyTOC(obj, 0);
   }
 
@@ -3512,18 +3573,17 @@ Min.prototype.constructor = Min;
 
 }); // module: reporters/min.js
 
 require.register("reporters/nyan.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
-var Base = require('./base')
-  , color = Base.color;
+var Base = require('./base');
 
 /**
  * Expose `Dot`.
  */
 
 exports = module.exports = NyanCat;
 
 /**
@@ -3590,27 +3650,26 @@ NyanCat.prototype.draw = function(){
  * Draw the "scoreboard" showing the number
  * of passes, failures and pending tests.
  *
  * @api private
  */
 
 NyanCat.prototype.drawScoreboard = function(){
   var stats = this.stats;
-  var colors = Base.colors;
-
-  function draw(color, n) {
+
+  function draw(type, n) {
     write(' ');
-    write('\u001b[' + color + 'm' + n + '\u001b[0m');
+    write(Base.color(type, n));
     write('\n');
   }
 
-  draw(colors.green, stats.passes);
-  draw(colors.fail, stats.failures);
-  draw(colors.pending, stats.pending);
+  draw('green', stats.passes);
+  draw('fail', stats.failures);
+  draw('pending', stats.pending);
   write('\n');
 
   this.cursorUp(this.numberOfLines);
 };
 
 /**
  * Append the rainbow.
  *
@@ -3650,36 +3709,36 @@ NyanCat.prototype.drawRainbow = function
  * Draw the nyan cat
  *
  * @api private
  */
 
 NyanCat.prototype.drawNyanCat = function() {
   var self = this;
   var startWidth = this.scoreboardWidth + this.trajectories[0].length;
-  var color = '\u001b[' + startWidth + 'C';
+  var dist = '\u001b[' + startWidth + 'C';
   var padding = '';
 
-  write(color);
+  write(dist);
   write('_,------,');
   write('\n');
 
-  write(color);
+  write(dist);
   padding = self.tick ? '  ' : '   ';
   write('_|' + padding + '/\\_/\\ ');
   write('\n');
 
-  write(color);
+  write(dist);
   padding = self.tick ? '_' : '__';
   var tail = self.tick ? '~' : '^';
   var face;
   write(tail + '|' + padding + this.face() + ' ');
   write('\n');
 
-  write(color);
+  write(dist);
   padding = self.tick ? ' ' : '  ';
   write(padding + '""  "" ');
   write('\n');
 
   this.cursorUp(this.numberOfLines);
 };
 
 /**
@@ -3750,16 +3809,18 @@ NyanCat.prototype.generateColors = funct
  * Apply rainbow to the given `str`.
  *
  * @param {String} str
  * @return {String}
  * @api private
  */
 
 NyanCat.prototype.rainbowify = function(str){
+  if (!Base.useColors)
+    return str;
   var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
   this.colorIndex += 1;
   return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
 };
 
 /**
  * Stdout helper.
  */
@@ -4048,16 +4109,17 @@ function title(test) {
 
 require.register("reporters/xunit.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , utils = require('../utils')
+  , fs = require('browser/fs')
   , escape = utils.escape;
 
 /**
  * Save timer references to avoid Sinon interfering (see GH-237).
  */
 
 var Date = global.Date
   , setTimeout = global.setTimeout
@@ -4073,80 +4135,111 @@ exports = module.exports = XUnit;
 
 /**
  * Initialize a new `XUnit` reporter.
  *
  * @param {Runner} runner
  * @api public
  */
 
-function XUnit(runner) {
+function XUnit(runner, options) {
   Base.call(this, runner);
   var stats = this.stats
     , tests = []
     , self = this;
 
+  if (options.reporterOptions && options.reporterOptions.output) {
+      if (! fs.createWriteStream) {
+          throw new Error('file output not supported in browser');
+      }
+      self.fileStream = fs.createWriteStream(options.reporterOptions.output);
+  }
+
   runner.on('pending', function(test){
     tests.push(test);
   });
 
   runner.on('pass', function(test){
     tests.push(test);
   });
 
   runner.on('fail', function(test){
     tests.push(test);
   });
 
   runner.on('end', function(){
-    console.log(tag('testsuite', {
+    self.write(tag('testsuite', {
         name: 'Mocha Tests'
       , tests: stats.tests
       , failures: stats.failures
       , errors: stats.failures
       , skipped: stats.tests - stats.failures - stats.passes
       , timestamp: (new Date).toUTCString()
       , time: (stats.duration / 1000) || 0
     }, false));
 
-    tests.forEach(test);
-    console.log('</testsuite>');
+    tests.forEach(function(t) { self.test(t); });
+    self.write('</testsuite>');
   });
 }
 
 /**
+ * Override done to close the stream (if it's a file).
+ */
+XUnit.prototype.done = function(failures, fn) {
+    if (this.fileStream) {
+        this.fileStream.end(function() {
+            fn(failures);
+        });
+    } else {
+        fn(failures);
+    }
+};
+
+/**
  * Inherit from `Base.prototype`.
  */
 
 function F(){};
 F.prototype = Base.prototype;
 XUnit.prototype = new F;
 XUnit.prototype.constructor = XUnit;
 
 
 /**
+ * Write out the given line
+ */
+XUnit.prototype.write = function(line) {
+    if (this.fileStream) {
+        this.fileStream.write(line + '\n');
+    } else {
+        console.log(line);
+    }
+};
+
+/**
  * Output tag for the given `test.`
  */
 
-function test(test) {
+XUnit.prototype.test = function(test, ostream) {
   var attrs = {
       classname: test.parent.fullTitle()
     , name: test.title
     , time: (test.duration / 1000) || 0
   };
 
   if ('failed' == test.state) {
     var err = test.err;
-    console.log(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
+    this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
   } else if (test.pending) {
-    console.log(tag('testcase', attrs, false, tag('skipped', {}, true)));
+    this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
   } else {
-    console.log(tag('testcase', attrs, true) );
+    this.write(tag('testcase', attrs, true) );
   }
-}
+};
 
 /**
  * HTML tag helper.
  */
 
 function tag(name, attrs, close, content) {
   var end = close ? '/>' : '>'
     , pairs = []
@@ -4173,17 +4266,19 @@ function cdata(str) {
 
 require.register("runnable.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('browser/events').EventEmitter
   , debug = require('browser/debug')('mocha:runnable')
-  , milliseconds = require('./ms');
+  , Pending = require('./pending')
+  , milliseconds = require('./ms')
+  , utils = require('./utils');
 
 /**
  * Save timer references to avoid Sinon interfering (see GH-237).
  */
 
 var Date = global.Date
   , setTimeout = global.setTimeout
   , setInterval = global.setInterval
@@ -4277,16 +4372,26 @@ Runnable.prototype.slow = function(ms){
 Runnable.prototype.enableTimeouts = function(enabled){
   if (arguments.length === 0) return this._enableTimeouts;
   debug('enableTimeouts %s', enabled);
   this._enableTimeouts = enabled;
   return this;
 };
 
 /**
+ * Halt and mark as pending.
+ *
+ * @api private
+ */
+
+Runnable.prototype.skip = function(){
+    throw new Pending();
+};
+
+/**
  * Return the full title generated by recursively
  * concatenating the parent's full title.
  *
  * @return {String}
  * @api public
  */
 
 Runnable.prototype.fullTitle = function(){
@@ -4328,17 +4433,17 @@ Runnable.prototype.inspect = function(){
 Runnable.prototype.resetTimeout = function(){
   var self = this;
   var ms = this.timeout() || 1e9;
 
   if (!this._enableTimeouts) return;
   this.clearTimeout();
   this.timer = setTimeout(function(){
     if (!self._enableTimeouts) return;
-    self.callback(new Error('timeout of ' + ms + 'ms exceeded'));
+    self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
     self.timedOut = true;
   }, ms);
 };
 
 /**
  * Whitelist these globals for this test run
  *
  * @api private
@@ -4372,20 +4477,24 @@ Runnable.prototype.run = function(fn){
     self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
   }
 
   // finished
   function done(err) {
     var ms = self.timeout();
     if (self.timedOut) return;
     if (finished) return multiple(err || self._trace);
+
+    // Discard the resolution if this test has already failed asynchronously
+    if (self.state) return;
+
     self.clearTimeout();
     self.duration = new Date - start;
     finished = true;
-    if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded');
+    if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
     fn(err);
   }
 
   // for .resetTimeout()
   this.callback = done;
 
   // explicit async with `done` argument
   if (this.async) {
@@ -4399,34 +4508,34 @@ Runnable.prototype.run = function(fn){
             return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
           } else {
             return done(new Error('done() invoked with non-Error: ' + err));
           }
         }
         done();
       });
     } catch (err) {
-      done(err);
+      done(utils.getError(err));
     }
     return;
   }
 
   if (this.asyncOnly) {
     return done(new Error('--async-only option in use without declaring `done()`'));
   }
 
   // sync or promise-returning
   try {
     if (this.pending) {
       done();
     } else {
       callFn(this.fn);
     }
   } catch (err) {
-    done(err);
+    done(utils.getError(err));
   }
 
   function callFn(fn) {
     var result = fn.call(ctx);
     if (result && typeof result.then === 'function') {
       self.resetTimeout();
       result
         .then(function() {
@@ -4445,32 +4554,37 @@ Runnable.prototype.run = function(fn){
 
 require.register("runner.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('browser/events').EventEmitter
   , debug = require('browser/debug')('mocha:runner')
+  , Pending = require('./pending')
   , Test = require('./test')
   , utils = require('./utils')
   , filter = utils.filter
-  , keys = utils.keys;
+  , keys = utils.keys
+  , type = utils.type
+  , stringify = utils.stringify;
 
 /**
  * Non-enumerable globals.
  */
 
 var globals = [
   'setTimeout',
   'clearTimeout',
   'setInterval',
   'clearInterval',
   'XMLHttpRequest',
-  'Date'
+  'Date',
+  'setImmediate',
+  'clearImmediate'
 ];
 
 /**
  * Expose `Runner`.
  */
 
 module.exports = Runner;
 
@@ -4486,23 +4600,27 @@ module.exports = Runner;
  *   - `test`  (test) test execution started
  *   - `test end`  (test) test completed
  *   - `hook`  (hook) hook execution started
  *   - `hook end`  (hook) hook complete
  *   - `pass`  (test) test passed
  *   - `fail`  (test, err) test failed
  *   - `pending`  (test) test pending
  *
+ * @param {Suite} suite Root suite
+ * @param {boolean} [delay] Whether or not to delay execution of root suite
+ *   until ready.
  * @api public
  */
 
-function Runner(suite) {
+function Runner(suite, delay) {
   var self = this;
   this._globals = [];
   this._abort = false;
+  this._delay = delay;
   this.suite = suite;
   this.total = suite.total();
   this.failures = 0;
   this.on('test end', function(test){ self.checkGlobals(test); });
   this.on('hook end', function(hook){ self.checkGlobals(hook); });
   this.grep(/.*/);
   this.globals(this.globalProps().concat(extraGlobals()));
 }
@@ -4639,16 +4757,18 @@ Runner.prototype.checkGlobals = function
  */
 
 Runner.prototype.fail = function(test, err){
   ++this.failures;
   test.state = 'failed';
 
   if ('string' == typeof err) {
     err = new Error('the string "' + err + '" was thrown, throw an Error :)');
+  } else if (!(err instanceof Error)) {
+    err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
   }
 
   this.emit('fail', test, err);
 };
 
 /**
  * Fail the given `hook` with `err`.
  *
@@ -4689,36 +4809,39 @@ Runner.prototype.hook = function(name, f
   var suite = this.suite
     , hooks = suite['_' + name]
     , self = this
     , timer;
 
   function next(i) {
     var hook = hooks[i];
     if (!hook) return fn();
-    if (self.failures && suite.bail()) return fn();
     self.currentRunnable = hook;
 
     hook.ctx.currentTest = self.test;
 
     self.emit('hook', hook);
 
     hook.on('error', function(err){
       self.failHook(hook, err);
     });
 
     hook.run(function(err){
       hook.removeAllListeners('error');
       var testError = hook.error();
       if (testError) self.fail(self.test, testError);
       if (err) {
-        self.failHook(hook, err);
-
-        // stop executing hooks, notify callee of hook err
-        return fn(err);
+        if (err instanceof Pending) {
+          suite.pending = true;
+        } else {
+          self.failHook(hook, err);
+
+          // stop executing hooks, notify callee of hook err
+          return fn(err);
+        }
       }
       self.emit('hook end', hook);
       delete hook.ctx.currentTest;
       next(++i);
     });
   }
 
   Runner.immediately(function(){
@@ -4890,25 +5013,39 @@ Runner.prototype.runTests = function(sui
       self.emit('test end', test);
       return next();
     }
 
     // execute test and hook(s)
     self.emit('test', self.test = test);
     self.hookDown('beforeEach', function(err, errSuite){
 
+      if (suite.pending) {
+        self.emit('pending', test);
+        self.emit('test end', test);
+        return next();
+      }
       if (err) return hookErr(err, errSuite, false);
 
       self.currentRunnable = self.test;
       self.runTest(function(err){
         test = self.test;
 
         if (err) {
-          self.fail(test, err);
+          if (err instanceof Pending) {
+            self.emit('pending', test);
+          } else {
+            self.fail(test, err);
+          }
           self.emit('test end', test);
+
+          if (err instanceof Pending) {
+            return next();
+          }
+
           return self.hookUp('afterEach', next);
         }
 
         test.state = 'passed';
         self.emit('pass', test);
         self.emit('test end', test);
         self.hookUp('afterEach', next);
       });
@@ -4983,30 +5120,29 @@ Runner.prototype.runSuite = function(sui
 
 Runner.prototype.uncaught = function(err){
   if (err) {
     debug('uncaught exception %s', err !== function () {
       return this;
     }.call(err) ? err : ( err.message || err ));
   } else {
     debug('uncaught undefined exception');
-    err = new Error('Caught undefined error, did you throw without specifying what?');
+    err = utils.undefinedError();
   }
   err.uncaught = true;
 
   var runnable = this.currentRunnable;
   if (!runnable) return;
 
-  var wasAlreadyDone = runnable.state;
+  runnable.clearTimeout();
+
+  // Ignore errors if complete
+  if (runnable.state) return;
   this.fail(runnable, err);
 
-  runnable.clearTimeout();
-
-  if (wasAlreadyDone) return;
-
   // recover from test
   if ('test' == runnable.type) {
     this.emit('test end', runnable);
     this.hookUp('afterEach', this.next);
     return;
   }
 
   // bail on hooks
@@ -5018,42 +5154,55 @@ Runner.prototype.uncaught = function(err
  * on completion.
  *
  * @param {Function} fn
  * @return {Runner} for chaining
  * @api public
  */
 
 Runner.prototype.run = function(fn){
-  var self = this
-    , fn = fn || function(){};
+  var self = this,
+    rootSuite = this.suite;
+
+  fn = fn || function(){};
 
   function uncaught(err){
     self.uncaught(err);
   }
 
+  function start() {
+    self.emit('start');
+    self.runSuite(rootSuite, function(){
+      debug('finished running');
+      self.emit('end');
+    });
+  }
+
   debug('start');
 
   // callback
   this.on('end', function(){
     debug('end');
     process.removeListener('uncaughtException', uncaught);
     fn(self.failures);
   });
 
-  // run suites
-  this.emit('start');
-  this.runSuite(this.suite, function(){
-    debug('finished running');
-    self.emit('end');
-  });
-
   // uncaught exception
   process.on('uncaughtException', uncaught);
 
+  if (this._delay) {
+    // for reporters, I guess.
+    // might be nice to debounce some dots while we wait.
+    this.emit('waiting', rootSuite);
+    rootSuite.once('run', start);
+  }
+  else {
+    start();
+  }
+
   return this;
 };
 
 /**
  * Cleanly abort execution
  *
  * @return {Runner} for chaining
  * @api public
@@ -5184,16 +5333,17 @@ function Suite(title, parentContext) {
   this._beforeAll = [];
   this._afterEach = [];
   this._afterAll = [];
   this.root = !title;
   this._timeout = 2000;
   this._enableTimeouts = true;
   this._slow = 75;
   this._bail = false;
+  this.delayed = false;
 }
 
 /**
  * Inherit from `EventEmitter.prototype`.
  */
 
 function F(){};
 F.prototype = EventEmitter.prototype;
@@ -5224,17 +5374,17 @@ Suite.prototype.clone = function(){
  *
  * @param {Number|String} ms
  * @return {Suite|Number} for chaining
  * @api private
  */
 
 Suite.prototype.timeout = function(ms){
   if (0 == arguments.length) return this._timeout;
-  if (ms === 0) this._enableTimeouts = false;
+  if (ms.toString() === '0') this._enableTimeouts = false;
   if ('string' == typeof ms) ms = milliseconds(ms);
   debug('timeout %d', ms);
   this._timeout = parseInt(ms, 10);
   return this;
 };
 
 /**
   * Set timeout `enabled`.
@@ -5265,17 +5415,17 @@ Suite.prototype.slow = function(ms){
   debug('slow %d', ms);
   this._slow = ms;
   return this;
 };
 
 /**
  * Sets whether to bail after first error.
  *
- * @parma {Boolean} bail
+ * @param {Boolean} bail
  * @return {Suite|Number} for chaining
  * @api private
  */
 
 Suite.prototype.bail = function(bail){
   if (0 == arguments.length) return this._bail;
   debug('bail %s', bail);
   this._bail = bail;
@@ -5470,16 +5620,25 @@ Suite.prototype.total = function(){
 Suite.prototype.eachTest = function(fn){
   utils.forEach(this.tests, fn);
   utils.forEach(this.suites, function(suite){
     suite.eachTest(fn);
   });
   return this;
 };
 
+/**
+ * This will run the root suite if we happen to be running in delayed mode.
+ */
+Suite.prototype.run = function run() {
+  if (this.root) {
+    this.emit('run');
+  }
+};
+
 }); // module: suite.js
 
 require.register("test.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var Runnable = require('./runnable');
@@ -5572,17 +5731,17 @@ exports.forEach = function(arr, fn, scop
  * @param {Function} fn
  * @param {Object} scope
  * @api private
  */
 
 exports.map = function(arr, fn, scope){
   var result = [];
   for (var i = 0, l = arr.length; i < l; i++)
-    result.push(fn.call(scope, arr[i], i));
+    result.push(fn.call(scope, arr[i], i, arr));
   return result;
 };
 
 /**
  * Array#indexOf (<=IE8)
  *
  * @parma {Array} arr
  * @param {Object} obj to find index of
@@ -5641,17 +5800,17 @@ exports.filter = function(arr, fn){
  *
  * @param {Object} obj
  * @return {Array} keys
  * @api private
  */
 
 exports.keys = Object.keys || function(obj) {
   var keys = []
-    , has = Object.prototype.hasOwnProperty // for `window` on <=IE8
+    , has = Object.prototype.hasOwnProperty; // for `window` on <=IE8
 
   for (var key in obj) {
     if (has.call(obj, key)) {
       keys.push(key);
     }
   }
 
   return keys;
@@ -5672,16 +5831,38 @@ exports.watch = function(files, fn){
     debug('file %s', file);
     fs.watchFile(file, options, function(curr, prev){
       if (prev.mtime < curr.mtime) fn(file);
     });
   });
 };
 
 /**
+ * Array.isArray (<=IE8)
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api private
+ */
+var isArray = Array.isArray || function (obj) {
+  return '[object Array]' == {}.toString.call(obj);
+};
+
+/**
+ * @description
+ * Buffer.prototype.toJSON polyfill
+ * @type {Function}
+ */
+if(typeof Buffer !== 'undefined' && Buffer.prototype) {
+  Buffer.prototype.toJSON = Buffer.prototype.toJSON || function () {
+    return Array.prototype.slice.call(this, 0);
+  };
+}
+
+/**
  * Ignored files.
  */
 
 function ignored(path){
   return !~ignore.indexOf(path);
 }
 
 /**
@@ -5693,25 +5874,25 @@ function ignored(path){
 
 exports.files = function(dir, ext, ret){
   ret = ret || [];
   ext = ext || ['js'];
 
   var re = new RegExp('\\.(' + ext.join('|') + ')$');
 
   fs.readdirSync(dir)
-  .filter(ignored)
-  .forEach(function(path){
-    path = join(dir, path);
-    if (fs.statSync(path).isDirectory()) {
-      exports.files(path, ext, ret);
-    } else if (path.match(re)) {
-      ret.push(path);
-    }
-  });
+    .filter(ignored)
+    .forEach(function(path){
+      path = join(dir, path);
+      if (fs.statSync(path).isDirectory()) {
+        exports.files(path, ext, ret);
+      } else if (path.match(re)) {
+        ret.push(path);
+      }
+    });
 
   return ret;
 };
 
 /**
  * Compute a slug from the given `str`.
  *
  * @param {String} str
@@ -5806,64 +5987,263 @@ function highlight(js) {
 
 exports.highlightTags = function(name) {
   var code = document.getElementById('mocha').getElementsByTagName(name);
   for (var i = 0, len = code.length; i < len; ++i) {
     code[i].innerHTML = highlight(code[i].innerHTML);
   }
 };
 
-
-/**
- * Stringify `obj`.
- *
- * @param {Object} obj
- * @return {String}
+/**
+ * If a value could have properties, and has none, this function is called, which returns
+ * a string representation of the empty value.
+ *
+ * Functions w/ no properties return `'[Function]'`
+ * Arrays w/ length === 0 return `'[]'`
+ * Objects w/ no properties return `'{}'`
+ * All else: return result of `value.toString()`
+ *
+ * @param {*} value Value to inspect
+ * @param {string} [type] The type of the value, if known.
+ * @returns {string}
+ */
+var emptyRepresentation = function emptyRepresentation(value, type) {
+  type = type || exports.type(value);
+
+  switch(type) {
+    case 'function':
+      return '[Function]';
+    case 'object':
+      return '{}';
+    case 'array':
+      return '[]';
+    default:
+      return value.toString();
+  }
+};
+
+/**
+ * Takes some variable and asks `{}.toString()` what it thinks it is.
+ * @param {*} value Anything
+ * @example
+ * type({}) // 'object'
+ * type([]) // 'array'
+ * type(1) // 'number'
+ * type(false) // 'boolean'
+ * type(Infinity) // 'number'
+ * type(null) // 'null'
+ * type(new Date()) // 'date'
+ * type(/foo/) // 'regexp'
+ * type('type') // 'string'
+ * type(global) // 'global'
  * @api private
- */
-
-exports.stringify = function(obj) {
-  if (obj instanceof RegExp) return obj.toString();
-  return JSON.stringify(exports.canonicalize(obj), null, 2).replace(/,(\n|$)/g, '$1');
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
+ * @returns {string}
+ */
+exports.type = function type(value) {
+  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
+    return 'buffer';
+  }
+  return Object.prototype.toString.call(value)
+    .replace(/^\[.+\s(.+?)\]$/, '$1')
+    .toLowerCase();
 };
 
 /**
- * Return a new object that has the keys in sorted order.
- * @param {Object} obj
- * @param {Array} [stack]
- * @return {Object}
+ * @summary Stringify `value`.
+ * @description Different behavior depending on type of value.
+ * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
+ * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
+ * - If `value` is an *empty* object, function, or array, return result of function
+ *   {@link emptyRepresentation}.
+ * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
+ *   JSON.stringify().
+ *
+ * @see exports.type
+ * @param {*} value
+ * @return {string}
  * @api private
  */
 
-exports.canonicalize = function(obj, stack) {
+exports.stringify = function(value) {
+  var type = exports.type(value);
+
+  if (!~exports.indexOf(['object', 'array', 'function'], type)) {
+    if(type != 'buffer') {
+      return jsonStringify(value);
+    }
+    var json = value.toJSON();
+    // Based on the toJSON result
+    return jsonStringify(json.data && json.type ? json.data : json, 2)
+      .replace(/,(\n|$)/g, '$1');
+  }
+
+  for (var prop in value) {
+    if (Object.prototype.hasOwnProperty.call(value, prop)) {
+      return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1');
+    }
+  }
+
+  return emptyRepresentation(value, type);
+};
+
+/**
+ * @description
+ * like JSON.stringify but more sense.
+ * @param {Object}  object
+ * @param {Number=} spaces
+ * @param {number=} depth
+ * @returns {*}
+ * @private
+ */
+function jsonStringify(object, spaces, depth) {
+  if(typeof spaces == 'undefined') return _stringify(object);  // primitive types
+
+  depth = depth || 1;
+  var space = spaces * depth
+    , str = isArray(object) ? '[' : '{'
+    , end = isArray(object) ? ']' : '}'
+    , length = object.length || exports.keys(object).length
+    , repeat = function(s, n) { return new Array(n).join(s); }; // `.repeat()` polyfill
+
+  function _stringify(val) {
+    switch (exports.type(val)) {
+      case 'null':
+      case 'undefined':
+        val = '[' + val + ']';
+        break;
+      case 'array':
+      case 'object':
+        val = jsonStringify(val, spaces, depth + 1);
+        break;
+      case 'boolean':
+      case 'regexp':
+      case 'number':
+        val = val === 0 && (1/val) === -Infinity // `-0`
+          ? '-0'
+          : val.toString();
+        break;
+      case 'date':
+        val = '[Date: ' + val.toISOString() + ']';
+        break;
+      case 'buffer':
+        var json = val.toJSON();
+        // Based on the toJSON result
+        json = json.data && json.type ? json.data : json;
+        val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
+        break;
+      default:
+        val = (val == '[Function]' || val == '[Circular]')
+          ? val
+          : '"' + val + '"'; //string
+    }
+    return val;
+  }
+
+  for(var i in object) {
+    if(!object.hasOwnProperty(i)) continue;        // not my business
+    --length;
+    str += '\n ' + repeat(' ', space)
+      + (isArray(object) ? '' : '"' + i + '": ') // key
+      +  _stringify(object[i])                   // value
+      + (length ? ',' : '');                     // comma
+  }
+
+  return str + (str.length != 1                    // [], {}
+    ? '\n' + repeat(' ', --space) + end
+    : end);
+}
+
+/**
+ * Return if obj is a Buffer
+ * @param {Object} arg
+ * @return {Boolean}
+ * @api private
+ */
+exports.isBuffer = function (arg) {
+  return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg);
+};
+
+/**
+ * @summary Return a new Thing that has the keys in sorted order.  Recursive.
+ * @description If the Thing...
+ * - has already been seen, return string `'[Circular]'`
+ * - is `undefined`, return string `'[undefined]'`
+ * - is `null`, return value `null`
+ * - is some other primitive, return the value
+ * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
+ * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
+ * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
+ *
+ * @param {*} value Thing to inspect.  May or may not have properties.
+ * @param {Array} [stack=[]] Stack of seen values
+ * @return {(Object|Array|Function|string|undefined)}
+ * @see {@link exports.stringify}
+ * @api private
+ */
+
+exports.canonicalize = function(value, stack) {
+  var canonicalizedObj,
+    type = exports.type(value),
+    prop,
+    withStack = function withStack(value, fn) {
+      stack.push(value);
+      fn();
+      stack.pop();
+    };
+
   stack = stack || [];
 
-  if (exports.indexOf(stack, obj) !== -1) return '[Circular]';
-
-  var canonicalizedObj;
-
-  if ({}.toString.call(obj) === '[object Array]') {
-    stack.push(obj);
-    canonicalizedObj = exports.map(obj, function (item) {
-      return exports.canonicalize(item, stack);
-    });
-    stack.pop();
-  } else if (typeof obj === 'object' && obj !== null) {
-    stack.push(obj);
-    canonicalizedObj = {};
-    exports.forEach(exports.keys(obj).sort(), function (key) {
-      canonicalizedObj[key] = exports.canonicalize(obj[key], stack);
-    });
-    stack.pop();
-  } else {
-    canonicalizedObj = obj;
+  if (exports.indexOf(stack, value) !== -1) {
+    return '[Circular]';
+  }
+
+  switch(type) {
+    case 'undefined':
+    case 'buffer':
+    case 'null':
+      canonicalizedObj = value;
+      break;
+    case 'array':
+      withStack(value, function () {
+        canonicalizedObj = exports.map(value, function (item) {
+          return exports.canonicalize(item, stack);
+        });
+      });
+      break;
+    case 'function':
+      for (prop in value) {
+        canonicalizedObj = {};
+        break;
+      }
+      if (!canonicalizedObj) {
+        canonicalizedObj = emptyRepresentation(value, type);
+        break;
+      }
+    /* falls through */
+    case 'object':
+      canonicalizedObj = canonicalizedObj || {};
+      withStack(value, function () {
+        exports.forEach(exports.keys(value).sort(), function (key) {
+          canonicalizedObj[key] = exports.canonicalize(value[key], stack);
+        });
+      });
+      break;
+    case 'date':
+    case 'number':
+    case 'regexp':
+    case 'boolean':
+      canonicalizedObj = value;
+      break;
+    default:
+      canonicalizedObj = value.toString();
   }
 
   return canonicalizedObj;
- };
+};
 
 /**
  * Lookup file names at the given `path`.
  */
 exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
   var files = [];
   var re = new RegExp('\\.(' + extensions.join('|') + ')$');
 
@@ -5880,17 +6260,17 @@ exports.lookupFiles = function lookupFil
   try {
     var stat = fs.statSync(path);
     if (stat.isFile()) return path;
   }
   catch (ignored) {
     return;
   }
 
-  fs.readdirSync(path).forEach(function(file){
+  fs.readdirSync(path).forEach(function(file) {
     file = join(path, file);
     try {
       var stat = fs.statSync(file);
       if (stat.isDirectory()) {
         if (recursive) {
           files = files.concat(lookupFiles(file, extensions, recursive));
         }
         return;
@@ -5901,16 +6281,38 @@ exports.lookupFiles = function lookupFil
     }
     if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return;
     files.push(file);
   });
 
   return files;
 };
 
+/**
+ * Generate an undefined error with a message warning the user.
+ *
+ * @return {Error}
+ */
+
+exports.undefinedError = function() {
+  return new Error('Caught undefined error, did you throw without specifying what?');
+};
+
+/**
+ * Generate an undefined error if `err` is not defined.
+ *
+ * @param {Error} err
+ * @return {Error}
+ */
+
+exports.getError = function(err) {
+  return err || exports.undefinedError();
+};
+
+
 }); // module: utils.js
 // The global object is "self" in Web Workers.
 var global = (function() { return this; })();
 
 /**
  * Save timer references to avoid Sinon interfering (see GH-237).
  */
 
@@ -6043,17 +6445,18 @@ mocha.setup = function(opts){
  * Run mocha, returning the Runner.
  */
 
 mocha.run = function(fn){
   var options = mocha.options;
   mocha.globals('location');
 
   var query = Mocha.utils.parseQuery(global.location.search || '');
-  if (query.grep) mocha.grep(query.grep);
+  if (query.grep) mocha.grep(new RegExp(query.grep));
+  if (query.fgrep) mocha.grep(query.fgrep);
   if (query.invert) mocha.invert();
 
   return Mocha.prototype.run.call(mocha, function(err){
     // The DOM Document is not available in Web Workers.
     var document = global.document;
     if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
       Mocha.utils.highlightTags('code');
     }
rename from browser/components/loop/test/shared/vendor/sinon-1.12.2.js
rename to browser/components/loop/test/shared/vendor/sinon-1.13.0.js
--- a/browser/components/loop/test/shared/vendor/sinon-1.12.2.js
+++ b/browser/components/loop/test/shared/vendor/sinon-1.13.0.js
@@ -1,10 +1,10 @@
 /**
- * Sinon.JS 1.12.2, 2014/12/12
+ * Sinon.JS 1.13.0, 2015/03/05
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
  *
  * (The BSD License)
  * 
  * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
  * All rights reserved.
@@ -30,17 +30,17 @@
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 (function (root, factory) {
   if (typeof define === 'function' && define.amd) {
-    define([], function () {
+    define('sinon', [], function () {
       return (root.sinon = factory());
     });
   } else if (typeof exports === 'object') {
     module.exports = factory();
   } else {
     root.sinon = factory();
   }
 }(this, function () {
@@ -1450,17 +1450,17 @@ var sinon = (function () {
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
- * @depend ../sinon.js
+ * @depend util/core.js
  */
 
 (function (sinon) {
     function makeApi(sinon) {
 
         // Adapted from https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
         var hasDontEnumBug = (function () {
             var obj = {
@@ -1553,17 +1553,17 @@ var sinon = (function () {
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
- * @depend ../sinon.js
+ * @depend util/core.js
  */
 
 (function (sinon) {
     function makeApi(sinon) {
 
         function timesInWords(count) {
             switch (count) {
                 case 1:
@@ -1596,17 +1596,17 @@ var sinon = (function () {
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
- * @depend ../sinon.js
+ * @depend util/core.js
  */
 /**
  * Format functions
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
  * Copyright (c) 2010-2014 Christian Johansen
@@ -1884,32 +1884,33 @@ var sinon = (function () {
         return match;
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
+        require("./typeOf");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
- * @depend ../sinon.js
+ * @depend util/core.js
  */
 /**
  * Format functions
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
  * Copyright (c) 2010-2014 Christian Johansen
@@ -2023,27 +2024,35 @@ var sinon = (function () {
             calledOn: function calledOn(thisValue) {
                 if (sinon.match && sinon.match.isMatcher(thisValue)) {
                     return thisValue.test(this.thisValue);
                 }
                 return this.thisValue === thisValue;
             },
 
             calledWith: function calledWith() {
-                for (var i = 0, l = arguments.length; i < l; i += 1) {
+                var l = arguments.length;
+                if (l > this.args.length) {
+                    return false;
+                }
+                for (var i = 0; i < l; i += 1) {
                     if (!sinon.deepEqual(arguments[i], this.args[i])) {
                         return false;
                     }
                 }
 
                 return true;
             },
 
             calledWithMatch: function calledWithMatch() {
-                for (var i = 0, l = arguments.length; i < l; i += 1) {
+                var l = arguments.length;
+                if (l > this.args.length) {
+                    return false;
+                }
+                for (var i = 0; i < l; i += 1) {
                     var actual = this.args[i];
                     var expectation = arguments[i];
                     if (!sinon.match || !sinon.match(expectation).test(actual)) {
                         return false;
                     }
                 }
                 return true;
             },
@@ -2182,16 +2191,17 @@ var sinon = (function () {
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
         require("./match");
+        require("./format");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
@@ -2260,21 +2270,21 @@ var sinon = (function () {
         function createCallProperties() {
             this.firstCall = this.getCall(0);
             this.secondCall = this.getCall(1);
             this.thirdCall = this.getCall(2);
             this.lastCall = this.getCall(this.callCount - 1);
         }
 
         var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
-        function createProxy(func) {
+        function createProxy(func, proxyLength) {
             // Retain the function length:
             var p;
-            if (func.length) {
-                eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +
+            if (proxyLength) {
+                eval("p = (function proxy(" + vars.substring(0, proxyLength * 2 - 1) +
                     ") { return p.invoke(func, this, slice.call(arguments)); });");
             } else {
                 p = function proxy() {
                     return p.invoke(func, this, slice.call(arguments));
                 };
             }
             return p;
         }
@@ -2306,28 +2316,34 @@ var sinon = (function () {
                 this.thisValues = [];
                 this.exceptions = [];
                 this.callIds = [];
                 if (this.fakes) {
                     for (var i = 0; i < this.fakes.length; i++) {
                         this.fakes[i].reset();
                     }
                 }
+
+                return this;
             },
 
-            create: function create(func) {
+            create: function create(func, spyLength) {
                 var name;
 
                 if (typeof func != "function") {
                     func = function () { };
                 } else {
                     name = sinon.functionName(func);
                 }
 
-                var proxy = createProxy(func);
+                if (!spyLength) {
+                    spyLength = func.length;
+                }
+
+                var proxy = createProxy(func, spyLength);
 
                 sinon.extend(proxy, spy);
                 delete proxy.create;
                 sinon.extend(proxy, func);
 
                 proxy.reset();
                 proxy.prototype = func.prototype;
                 proxy.displayName = name || "spy";
@@ -2618,16 +2634,19 @@ var sinon = (function () {
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
         require("./call");
+        require("./extend");
+        require("./times_in_words");
+        require("./format");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
@@ -2649,16 +2668,18 @@ var sinon = (function () {
  * @license BSD
  *
  * Copyright (c) 2010-2013 Christian Johansen
  */
 
 (function (sinon) {
     var slice = Array.prototype.slice;
     var join = Array.prototype.join;
+    var useLeftMostCallback = -1;
+    var useRightMostCallback = -2;
 
     var nextTick = (function () {
         if (typeof process === "object" && typeof process.nextTick === "function") {
             return process.nextTick;
         } else if (typeof setImmediate === "function") {
             return setImmediate;
         } else {
             return function (callback) {
@@ -2678,34 +2699,44 @@ var sinon = (function () {
         }
 
         return this;
     }
 
     function getCallback(behavior, args) {
         var callArgAt = behavior.callArgAt;
 
-        if (callArgAt < 0) {
-            var callArgProp = behavior.callArgProp;
-
-            for (var i = 0, l = args.length; i < l; ++i) {
-                if (!callArgProp && typeof args[i] == "function") {
-                    return args[i];
-                }
-
-                if (callArgProp && args[i] &&
-                    typeof args[i][callArgProp] == "function") {
-                    return args[i][callArgProp];
-                }
+        if (callArgAt >= 0) {
+            return args[callArgAt];
+        }
+
+        var argumentList;
+
+        if (callArgAt === useLeftMostCallback) {
+            argumentList = args;
+        }
+
+        if (callArgAt === useRightMostCallback) {
+            argumentList = slice.call(args).reverse();
+        }
+
+        var callArgProp = behavior.callArgProp;
+
+        for (var i = 0, l = argumentList.length; i < l; ++i) {
+            if (!callArgProp && typeof argumentList[i] == "function") {
+                return argumentList[i];
             }
 
-            return null;
+            if (callArgProp && argumentList[i] &&
+                typeof argumentList[i][callArgProp] == "function") {
+                return argumentList[i][callArgProp];
+            }
         }
 
-        return args[callArgAt];
+        return null;
     }
 
     function makeApi(sinon) {
         function getCallbackError(behavior, func, args) {
             if (behavior.callArgAt < 0) {
                 var msg;
 
                 if (behavior.callArgProp) {
@@ -2855,55 +2886,65 @@ var sinon = (function () {
                 this.callbackContext = context;
                 this.callArgProp = undefined;
                 this.callbackAsync = false;
 
                 return this;
             },
 
             yields: function () {
-                this.callArgAt = -1;
+                this.callArgAt = useLeftMostCallback;
+                this.callbackArguments = slice.call(arguments, 0);
+                this.callbackContext = undefined;
+                this.callArgProp = undefined;
+                this.callbackAsync = false;
+
+                return this;
+            },
+
+            yieldsRight: function () {
+                this.callArgAt = useRightMostCallback;
                 this.callbackArguments = slice.call(arguments, 0);
                 this.callbackContext = undefined;
                 this.callArgProp = undefined;
                 this.callbackAsync = false;
 
                 return this;
             },
 
             yieldsOn: function (context) {
                 if (typeof context != "object") {
                     throw new TypeError("argument context is not an object");
                 }
 
-                this.callArgAt = -1;
+                this.callArgAt = useLeftMostCallback;
                 this.callbackArguments = slice.call(arguments, 1);
                 this.callbackContext = context;
                 this.callArgProp = undefined;
                 this.callbackAsync = false;
 
                 return this;
             },
 
             yieldsTo: function (prop) {
-                this.callArgAt = -1;
+                this.callArgAt = useLeftMostCallback;
                 this.callbackArguments = slice.call(arguments, 1);
                 this.callbackContext = undefined;
                 this.callArgProp = prop;
                 this.callbackAsync = false;
 
                 return this;
             },
 
             yieldsToOn: function (prop, context) {
                 if (typeof context != "object") {
                     throw new TypeError("argument context is not an object");
                 }
 
-                this.callArgAt = -1;
+                this.callArgAt = useLeftMostCallback;
                 this.callbackArguments = slice.call(arguments, 2);
                 this.callbackContext = context;
                 this.callArgProp = prop;
                 this.callbackAsync = false;
 
                 return this;
             },
 
@@ -2954,16 +2995,17 @@ var sinon = (function () {
         return proto;
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
+        require("./extend");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
@@ -2995,17 +3037,21 @@ var sinon = (function () {
                 throw new TypeError("Custom stub should be function");
             }
 
             var wrapper;
 
             if (func) {
                 wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
             } else {
-                wrapper = stub.create();
+                var stubLength = 0;
+                if (typeof object == "object" && typeof object[property] == "function") {
+                    stubLength = object[property].length;
+                }
+                wrapper = stub.create(stubLength);
             }
 
             if (!object && typeof property === "undefined") {
                 return sinon.stub.create();
             }
 
             if (typeof property === "undefined" && typeof object == "object") {
                 for (var prop in object) {
@@ -3031,24 +3077,24 @@ var sinon = (function () {
         function getCurrentBehavior(stub) {
             var behavior = stub.behaviors[stub.callCount - 1];
             return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub);
         }
 
         var uuid = 0;
 
         var proto = {
-            create: function create() {
+            create: function create(stubLength) {
                 var functionStub = function () {
                     return getCurrentBehavior(functionStub).invoke(this, arguments);
                 };
 
                 functionStub.id = "stub#" + uuid++;
                 var orig = functionStub;
-                functionStub = sinon.spy.create(functionStub);
+                functionStub = sinon.spy.create(functionStub, stubLength);
                 functionStub.func = orig;
 
                 sinon.extend(functionStub, stub);
                 functionStub.instantiateFake = sinon.stub.create;
                 functionStub.displayName = "stub";
                 functionStub.toString = sinon.functionToString;
 
                 functionStub.defaultBehavior = null;
@@ -3119,16 +3165,17 @@ var sinon = (function () {
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
         require("./behavior");
         require("./spy");
+        require("./extend");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
@@ -3136,17 +3183,20 @@ var sinon = (function () {
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
  * @depend times_in_words.js
  * @depend util/core.js
+ * @depend call.js
  * @depend extend.js
+ * @depend match.js
+ * @depend spy.js
  * @depend stub.js
  * @depend format.js
  */
 /**
  * Mock functions.
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
@@ -3564,35 +3614,41 @@ var sinon = (function () {
         return mock;
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
+        require("./times_in_words");
         require("./call");
+        require("./extend");
         require("./match");
         require("./spy");
+        require("./stub");
+        require("./format");
+
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
  * @depend util/core.js
+ * @depend spy.js
  * @depend stub.js
  * @depend mock.js
  */
 /**
  * Collections of stubs, spies and mocks.
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
@@ -3809,26 +3865,26 @@ if (typeof sinon == "undefined") {
             clearInterval: clearInterval,
             Date: Date
         };
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
-    function loadDependencies(require, epxorts, module) {
+    function loadDependencies(require, epxorts, module, lolex) {
         var sinon = require("./core");
-        makeApi(sinon, require("lolex"));
+        makeApi(sinon, lolex);
         module.exports = sinon;
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
-        loadDependencies(require, module.exports, module);
+        loadDependencies(require, module.exports, module, require("lolex"));
     } else {
         makeApi(sinon);
     }
 }(typeof global != "undefined" && typeof global !== "function" ? global : this));
 
 /**
  * Minimal Event interface implementation
  *
@@ -3868,16 +3924,17 @@ if (typeof sinon == "undefined") {
                 this.defaultPrevented = true;
             }
         };
 
         sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) {
             this.initEvent(type, false, false, target);
             this.loaded = progressEventRaw.loaded || null;
             this.total = progressEventRaw.total || null;
+            this.lengthComputable = !!progressEventRaw.total;
         };
 
         sinon.ProgressEvent.prototype = new sinon.Event();
 
         sinon.ProgressEvent.prototype.constructor =  sinon.ProgressEvent;
 
         sinon.CustomEvent = function CustomEvent(type, customData, target) {
             this.initEvent(type, false, false, target);
@@ -3935,17 +3992,17 @@ if (typeof sinon == "undefined") {
     } else if (isNode) {
         loadDependencies(require);
     } else {
         makeApi(sinon);
     }
 }());
 
 /**
- * @depend ../sinon.js
+ * @depend util/core.js
  */
 /**
  * Logs errors
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
  * Copyright (c) 2010-2014 Christian Johansen
@@ -4009,16 +4066,239 @@ if (typeof sinon == "undefined") {
 
 /**
  * @depend core.js
  * @depend ../extend.js
  * @depend event.js
  * @depend ../log_error.js
  */
 /**
+ * Fake XDomainRequest object
+ */
+
+if (typeof sinon == "undefined") {
+    this.sinon = {};
+}
+
+// wrapper for global
+(function (global) {
+    var xdr = { XDomainRequest: global.XDomainRequest };
+    xdr.GlobalXDomainRequest = global.XDomainRequest;
+    xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
+    xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest :  false;
+
+    function makeApi(sinon) {
+        sinon.xdr = xdr;
+
+        function FakeXDomainRequest() {
+            this.readyState = FakeXDomainRequest.UNSENT;
+            this.requestBody = null;
+            this.requestHeaders = {};
+            this.status = 0;
+            this.timeout = null;
+
+            if (typeof FakeXDomainRequest.onCreate == "function") {
+                FakeXDomainRequest.onCreate(this);
+            }
+        }
+
+        function verifyState(xdr) {
+            if (xdr.readyState !== FakeXDomainRequest.OPENED) {
+                throw new Error("INVALID_STATE_ERR");
+            }
+
+            if (xdr.sendFlag) {
+                throw new Error("INVALID_STATE_ERR");
+            }
+        }
+
+        function verifyRequestSent(xdr) {
+            if (xdr.readyState == FakeXDomainRequest.UNSENT) {
+                throw new Error("Request not sent");
+            }
+            if (xdr.readyState == FakeXDomainRequest.DONE) {
+                throw new Error("Request done");
+            }
+        }
+
+        function verifyResponseBodyType(body) {
+            if (typeof body != "string") {
+                var error = new Error("Attempted to respond to fake XDomainRequest with " +
+                                    body + ", which is not a string.");
+                error.name = "InvalidBodyException";
+                throw error;
+            }
+        }
+
+        sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
+            open: function open(method, url) {
+                this.method = method;
+                this.url = url;
+
+                this.responseText = null;
+                this.sendFlag = false;
+
+                this.readyStateChange(FakeXDomainRequest.OPENED);
+            },
+
+            readyStateChange: function readyStateChange(state) {
+                this.readyState = state;
+                var eventName = "";
+                switch (this.readyState) {
+                case FakeXDomainRequest.UNSENT:
+                    break;
+                case FakeXDomainRequest.OPENED:
+                    break;
+                case FakeXDomainRequest.LOADING:
+                    if (this.sendFlag) {
+                        //raise the progress event
+                        eventName = "onprogress";
+                    }
+                    break;
+                case FakeXDomainRequest.DONE:
+                    if (this.isTimeout) {
+                        eventName = "ontimeout"
+                    } else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
+                        eventName = "onerror";
+                    } else {
+                        eventName = "onload"
+                    }
+                    break;
+                }
+
+                // raising event (if defined)
+                if (eventName) {
+                    if (typeof this[eventName] == "function") {
+                        try {
+                            this[eventName]();
+                        } catch (e) {
+                            sinon.logError("Fake XHR " + eventName + " handler", e);
+                        }
+                    }
+                }
+            },
+
+            send: function send(data) {
+                verifyState(this);
+
+                if (!/^(get|head)$/i.test(this.method)) {
+                    this.requestBody = data;
+                }
+                this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
+
+                this.errorFlag = false;
+                this.sendFlag = true;
+                this.readyStateChange(FakeXDomainRequest.OPENED);
+
+                if (typeof this.onSend == "function") {
+                    this.onSend(this);
+                }
+            },
+
+            abort: function abort() {
+                this.aborted = true;
+                this.responseText = null;
+                this.errorFlag = true;
+
+                if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
+                    this.readyStateChange(sinon.FakeXDomainRequest.DONE);
+                    this.sendFlag = false;
+                }
+            },
+
+            setResponseBody: function setResponseBody(body) {
+                verifyRequestSent(this);
+                verifyResponseBodyType(body);
+
+                var chunkSize = this.chunkSize || 10;
+                var index = 0;
+                this.responseText = "";
+
+                do {
+                    this.readyStateChange(FakeXDomainRequest.LOADING);
+                    this.responseText += body.substring(index, index + chunkSize);
+                    index += chunkSize;
+                } while (index < body.length);
+
+                this.readyStateChange(FakeXDomainRequest.DONE);
+            },
+
+            respond: function respond(status, contentType, body) {
+                // content-type ignored, since XDomainRequest does not carry this
+                // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
+                // test integration across browsers
+                this.status = typeof status == "number" ? status : 200;
+                this.setResponseBody(body || "");
+            },
+
+            simulatetimeout: function simulatetimeout() {
+                this.status = 0;
+                this.isTimeout = true;
+                // Access to this should actually throw an error
+                this.responseText = undefined;
+                this.readyStateChange(FakeXDomainRequest.DONE);
+            }
+        });
+
+        sinon.extend(FakeXDomainRequest, {
+            UNSENT: 0,
+            OPENED: 1,
+            LOADING: 3,
+            DONE: 4
+        });
+
+        sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
+            sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
+                if (xdr.supportsXDR) {
+                    global.XDomainRequest = xdr.GlobalXDomainRequest;
+                }
+
+                delete sinon.FakeXDomainRequest.restore;
+
+                if (keepOnCreate !== true) {
+                    delete sinon.FakeXDomainRequest.onCreate;
+                }
+            };
+            if (xdr.supportsXDR) {
+                global.XDomainRequest = sinon.FakeXDomainRequest;
+            }
+            return sinon.FakeXDomainRequest;
+        };
+
+        sinon.FakeXDomainRequest = FakeXDomainRequest;
+    }
+
+    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
+    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
+
+    function loadDependencies(require, exports, module) {
+        var sinon = require("./core");
+        require("../extend");
+        require("./event");
+        require("../log_error");
+        makeApi(sinon);
+        module.exports = sinon;
+    }
+
+    if (isAMD) {
+        define(loadDependencies);
+    } else if (isNode) {
+        loadDependencies(require, module.exports, module);
+    } else {
+        makeApi(sinon);
+    }
+})(this);
+
+/**
+ * @depend core.js
+ * @depend ../extend.js
+ * @depend event.js
+ * @depend ../log_error.js
+ */
+/**
  * Fake XMLHttpRequest object
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
  * Copyright (c) 2010-2013 Christian Johansen
  */
 
@@ -4377,16 +4657,17 @@ if (typeof sinon == "undefined") {
 
                 switch (this.readyState) {
                     case FakeXMLHttpRequest.DONE:
                         this.dispatchEvent(new sinon.Event("load", false, false, this));
                         this.dispatchEvent(new sinon.Event("loadend", false, false, this));
                         this.upload.dispatchEvent(new sinon.Event("load", false, false, this));
                         if (supportsProgress) {
                             this.upload.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
+                            this.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
                         }
                         break;
                 }
             },
 
             setRequestHeader: function setRequestHeader(header, value) {
                 verifyState(this);
 
@@ -4423,17 +4704,17 @@ if (typeof sinon == "undefined") {
             send: function send(data) {
                 verifyState(this);
 
                 if (!/^(get|head)$/i.test(this.method)) {
                     var contentType = getHeader(this.requestHeaders, "Content-Type");
                     if (this.requestHeaders[contentType]) {
                         var value = this.requestHeaders[contentType].split(";");
                         this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
-                    } else {
+                    } else if (!(data instanceof FormData)) {
                         this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
                     }
 
                     this.requestBody = data;
                 }
 
                 this.errorFlag = false;
                 this.sendFlag = this.async;
@@ -4539,16 +4820,22 @@ if (typeof sinon == "undefined") {
             },
 
             uploadProgress: function uploadProgress(progressEventRaw) {
                 if (supportsProgress) {
                     this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw));
                 }
             },
 
+            downloadProgress: function downloadProgress(progressEventRaw) {
+                if (supportsProgress) {
+                    this.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw));
+                }
+            },
+
             uploadError: function uploadError(error) {
                 if (supportsCustomEvent) {
                     this.upload.dispatchEvent(new sinon.CustomEvent("error", {detail: error}));
                 }
             }
         });
 
         sinon.extend(FakeXMLHttpRequest, {
@@ -4596,34 +4883,37 @@ if (typeof sinon == "undefined") {
         sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./core");
+        require("../extend");
         require("./event");
+        require("../log_error");
         makeApi(sinon);
         module.exports = sinon;
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (typeof sinon === "undefined") {
         return;
     } else {
         makeApi(sinon);
     }
 
-})(typeof self !== "undefined" ? self : this);
+})(typeof global !== "undefined" ? global : this);
 
 /**
+ * @depend fake_xdomain_request.js
  * @depend fake_xml_http_request.js
  * @depend ../format.js
  * @depend ../log_error.js
  */
 /**
  * The Sinon "server" mimics a web server that receives requests from
  * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
  * both synchronously and asynchronously. To respond synchronuously, canned
@@ -4694,17 +4984,21 @@ if (typeof sinon == "undefined") {
 
         return false;
     }
 
     function makeApi(sinon) {
         sinon.fakeServer = {
             create: function () {
                 var server = create(this);
-                this.xhr = sinon.useFakeXMLHttpRequest();
+                if (!sinon.xhr.supportsCORS) {
+                    this.xhr = sinon.useFakeXDomainRequest();
+                } else {
+                    this.xhr = sinon.useFakeXMLHttpRequest();
+                }
                 server.requests = [];
 
                 this.xhr.onCreate = function (xhrObj) {
                     server.addRequest(xhrObj);
                 };
 
                 return server;
             },
@@ -4830,17 +5124,19 @@ if (typeof sinon == "undefined") {
         };
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./core");
+        require("./fake_xdomain_request");
         require("./fake_xml_http_request");
+        require("../format");
         makeApi(sinon);
         module.exports = sinon;
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
@@ -5089,17 +5385,18 @@ if (typeof sinon == "undefined") {
         return sinon.sandbox;
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
-        require("./util/fake_server");
+        require("./extend");
+        require("./util/fake_server_with_clock");
         require("./util/fake_timers");
         require("./collection");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
@@ -5108,81 +5405,78 @@ if (typeof sinon == "undefined") {
         return;
     } else {
         makeApi(sinon);
     }
 }());
 
 /**
  * @depend util/core.js
- * @depend stub.js
- * @depend mock.js
  * @depend sandbox.js
  */
 /**
  * Test function, sandboxes fakes
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
  * Copyright (c) 2010-2013 Christian Johansen
  */
 
 (function (sinon) {
     function makeApi(sinon) {
+        var slice = Array.prototype.slice;
+
         function test(callback) {
             var type = typeof callback;
 
             if (type != "function") {
                 throw new TypeError("sinon.test needs to wrap a test function, got " + type);
             }
 
             function sinonSandboxedTest() {
                 var config = sinon.getConfig(sinon.config);
                 config.injectInto = config.injectIntoThis && this || config.injectInto;
                 var sandbox = sinon.sandbox.create(config);
+                var args = slice.call(arguments);
+                var oldDone = args.length && args[args.length - 1];
                 var exception, result;
-                var doneIsWrapped = false;
-                var argumentsCopy = Array.prototype.slice.call(arguments);
-                if (argumentsCopy.length > 0 && typeof argumentsCopy[arguments.length - 1] == "function") {
-                    var oldDone = argumentsCopy[arguments.length - 1];
-                    argumentsCopy[arguments.length - 1] = function done(result) {
+
+                if (typeof oldDone == "function") {
+                    args[args.length - 1] = function sinonDone(result) {
                         if (result) {
                             sandbox.restore();
                             throw exception;
                         } else {
                             sandbox.verifyAndRestore();
                         }
                         oldDone(result);
-                    }
-                    doneIsWrapped = true;
-                }
-
-                var args = argumentsCopy.concat(sandbox.args);
+                    };
+                }
 
                 try {
-                    result = callback.apply(this, args);
+                    result = callback.apply(this, args.concat(sandbox.args));
                 } catch (e) {
                     exception = e;
                 }
 
-                if (!doneIsWrapped) {
+                if (typeof oldDone != "function") {
                     if (typeof exception !== "undefined") {
                         sandbox.restore();
                         throw exception;
                     } else {
                         sandbox.verifyAndRestore();
                     }
                 }
 
                 return result;
-            };
+            }
 
             if (callback.length) {
-                return function sinonAsyncSandboxedTest(callback) {
+                return function sinonAsyncSandboxedTest() {
                     return sinonSandboxedTest.apply(this, arguments);
                 };
             }
 
             return sinonSandboxedTest;
         }
 
         test.config = {
@@ -5205,19 +5499,17 @@ if (typeof sinon == "undefined") {
         require("./sandbox");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
-    } else if (!sinon) {
-        return;
-    } else {
+    } else if (sinon) {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
  * @depend util/core.js
  * @depend test.js
  */
@@ -5318,17 +5610,17 @@ if (typeof sinon == "undefined") {
     } else {
         makeApi(sinon);
     }
 }(typeof sinon == "object" && sinon || null));
 
 /**
  * @depend times_in_words.js
  * @depend util/core.js
- * @depend stub.js
+ * @depend match.js
  * @depend format.js
  */
 /**
  * Assertions matching the test spy retrieval interface.
  *
  * @author Christian Johansen (christian@cjohansen.no)
  * @license BSD
  *
@@ -5346,23 +5638,28 @@ if (typeof sinon == "undefined") {
 
             for (var i = 0, l = arguments.length; i < l; ++i) {
                 method = arguments[i];
 
                 if (!method) {
                     assert.fail("fake is not a spy");
                 }
 
-                if (typeof method != "function") {
-                    assert.fail(method + " is not a function");
-                }
-
-                if (typeof method.getCall != "function") {
-                    assert.fail(method + " is not stubbed");
-                }
+                if (method.proxy) {
+                    verifyIsStub(method.proxy);
+                } else {
+                    if (typeof method != "function") {
+                        assert.fail(method + " is not a function");
+                    }
+
+                    if (typeof method.getCall != "function") {
+                        assert.fail(method + " is not stubbed");
+                    }
+                }
+
             }
         }
 
         function failAssertion(object, msg) {
             object = object || global;
             var failMethod = object.fail || assert.fail;
             failMethod.call(object, msg);
         }
@@ -5382,17 +5679,17 @@ if (typeof sinon == "undefined") {
                 if (typeof method == "function") {
                     failed = !method(fake);
                 } else {
                     failed = typeof fake[method] == "function" ?
                         !fake[method].apply(fake, args) : !fake[method];
                 }
 
                 if (failed) {
-                    failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
+                    failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args)));
                 } else {
                     assert.pass(name);
                 }
             };
         }
 
         function exposedName(prefix, prop) {
             return !prefix || /^fail/.test(prop) ? prop :
@@ -5508,246 +5805,26 @@ if (typeof sinon == "undefined") {
     }
 
     var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
     var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
 
     function loadDependencies(require, exports, module) {
         var sinon = require("./util/core");
         require("./match");
+        require("./format");
         module.exports = makeApi(sinon);
     }
 
     if (isAMD) {
         define(loadDependencies);
     } else if (isNode) {
         loadDependencies(require, module.exports, module);
     } else if (!sinon) {
         return;
     } else {
         makeApi(sinon);
     }
 
 }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global));
 
-/**
- * @depend core.js
- * @depend ../extend.js
- * @depend event.js
- * @depend ../log_error.js
- */
-/**
- * Fake XDomainRequest object
- */
-
-if (typeof sinon == "undefined") {
-    this.sinon = {};
-}
-
-// wrapper for global
-(function (global) {
-    var xdr = { XDomainRequest: global.XDomainRequest };
-    xdr.GlobalXDomainRequest = global.XDomainRequest;
-    xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
-    xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest :  false;
-
-    function makeApi(sinon) {
-        sinon.xdr = xdr;
-
-        function FakeXDomainRequest() {
-            this.readyState = FakeXDomainRequest.UNSENT;
-            this.requestBody = null;
-            this.requestHeaders = {};
-            this.status = 0;
-            this.timeout = null;
-
-            if (typeof FakeXDomainRequest.onCreate == "function") {
-                FakeXDomainRequest.onCreate(this);
-            }
-        }
-
-        function verifyState(xdr) {
-            if (xdr.readyState !== FakeXDomainRequest.OPENED) {
-                throw new Error("INVALID_STATE_ERR");
-            }
-
-            if (xdr.sendFlag) {
-                throw new Error("INVALID_STATE_ERR");
-            }
-        }
-
-        function verifyRequestSent(xdr) {
-            if (xdr.readyState == FakeXDomainRequest.UNSENT) {
-                throw new Error("Request not sent");
-            }
-            if (xdr.readyState == FakeXDomainRequest.DONE) {
-                throw new Error("Request done");
-            }
-        }
-
-        function verifyResponseBodyType(body) {
-            if (typeof body != "string") {
-                var error = new Error("Attempted to respond to fake XDomainRequest with " +
-                                    body + ", which is not a string.");
-                error.name = "InvalidBodyException";
-                throw error;
-            }
-        }
-
-        sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
-            open: function open(method, url) {
-                this.method = method;
-                this.url = url;
-
-                this.responseText = null;
-                this.sendFlag = false;
-
-                this.readyStateChange(FakeXDomainRequest.OPENED);
-            },
-
-            readyStateChange: function readyStateChange(state) {
-                this.readyState = state;
-                var eventName = "";
-                switch (this.readyState) {
-                case FakeXDomainRequest.UNSENT:
-                    break;
-                case FakeXDomainRequest.OPENED:
-                    break;
-                case FakeXDomainRequest.LOADING:
-                    if (this.sendFlag) {
-                        //raise the progress event
-                        eventName = "onprogress";
-                    }
-                    break;
-                case FakeXDomainRequest.DONE:
-                    if (this.isTimeout) {
-                        eventName = "ontimeout"
-                    } else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
-                        eventName = "onerror";
-                    } else {
-                        eventName = "onload"
-                    }
-                    break;
-                }
-
-                // raising event (if defined)
-                if (eventName) {
-                    if (typeof this[eventName] == "function") {
-                        try {
-                            this[eventName]();
-                        } catch (e) {
-                            sinon.logError("Fake XHR " + eventName + " handler", e);
-                        }
-                    }
-                }
-            },
-
-            send: function send(data) {
-                verifyState(this);
-
-                if (!/^(get|head)$/i.test(this.method)) {
-                    this.requestBody = data;
-                }
-                this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
-
-                this.errorFlag = false;
-                this.sendFlag = true;
-                this.readyStateChange(FakeXDomainRequest.OPENED);
-
-                if (typeof this.onSend == "function") {
-                    this.onSend(this);
-                }
-            },
-
-            abort: function abort() {
-                this.aborted = true;
-                this.responseText = null;
-                this.errorFlag = true;
-
-                if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
-                    this.readyStateChange(sinon.FakeXDomainRequest.DONE);
-                    this.sendFlag = false;
-                }
-            },
-
-            setResponseBody: function setResponseBody(body) {
-                verifyRequestSent(this);
-                verifyResponseBodyType(body);
-
-                var chunkSize = this.chunkSize || 10;
-                var index = 0;
-                this.responseText = "";
-
-                do {
-                    this.readyStateChange(FakeXDomainRequest.LOADING);
-                    this.responseText += body.substring(index, index + chunkSize);
-                    index += chunkSize;
-                } while (index < body.length);
-
-                this.readyStateChange(FakeXDomainRequest.DONE);
-            },
-
-            respond: function respond(status, contentType, body) {
-                // content-type ignored, since XDomainRequest does not carry this
-                // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
-                // test integration across browsers
-                this.status = typeof status == "number" ? status : 200;
-                this.setResponseBody(body || "");
-            },
-
-            simulatetimeout: function simulatetimeout() {
-                this.status = 0;
-                this.isTimeout = true;
-                // Access to this should actually throw an error
-                this.responseText = undefined;
-                this.readyStateChange(FakeXDomainRequest.DONE);
-            }
-        });
-
-        sinon.extend(FakeXDomainRequest, {
-            UNSENT: 0,
-            OPENED: 1,
-            LOADING: 3,
-            DONE: 4
-        });
-
-        sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
-            sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
-                if (xdr.supportsXDR) {
-                    global.XDomainRequest = xdr.GlobalXDomainRequest;
-                }
-
-                delete sinon.FakeXDomainRequest.restore;
-
-                if (keepOnCreate !== true) {
-                    delete sinon.FakeXDomainRequest.onCreate;
-                }
-            };
-            if (xdr.supportsXDR) {
-                global.XDomainRequest = sinon.FakeXDomainRequest;
-            }
-            return sinon.FakeXDomainRequest;
-        };
-
-        sinon.FakeXDomainRequest = FakeXDomainRequest;
-    }
-
-    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
-    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
-
-    function loadDependencies(require, exports, module) {
-        var sinon = require("./core");
-        require("./event");
-        makeApi(sinon);
-        module.exports = sinon;
-    }
-
-    if (isAMD) {
-        define(loadDependencies);
-    } else if (isNode) {
-        loadDependencies(require, module.exports, module);
-    } else {
-        makeApi(sinon);
-    }
-})(this);
-
   return sinon;
 }));
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop mocha tests</title>
-  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
+  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.2.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
     <p><a href="../shared/">Shared Tests</a></p>
  </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
@@ -24,22 +24,22 @@
 
   <!-- libs -->
   <script src="../../content/shared/libs/react-0.12.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
   <script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
   <!-- test dependencies -->
-  <script src="../shared/vendor/mocha-2.0.1.js"></script>
-  <script src="../shared/vendor/chai-1.9.0.js"></script>
-  <script src="../shared/vendor/sinon-1.12.2.js"></script>
+  <script src="../shared/vendor/mocha-2.2.1.js"></script>
+  <script src="../shared/vendor/chai-2.1.0.js"></script>
+  <script src="../shared/vendor/sinon-1.13.0.js"></script>
   <script src="../shared/sdk_mock.js"></script>
   <script>
-    chai.Assertion.includeStack = true;
+    chai.config.includeStack = true;
     mocha.setup('bdd');
   </script>
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -94,49 +94,50 @@ var gContentPane = {
 
   // FONTS
 
   /**
    * Populates the default font list in UI.
    */
   _rebuildFonts: function ()
   {
+    var preferences = document.getElementById("contentPreferences");
+    // Ensure preferences are "visible" to ensure bindings work.
+    preferences.hidden = false;
+    // Force flush:
+    preferences.clientHeight;
     var langGroupPref = document.getElementById("font.language.group");
     this._selectDefaultLanguageGroup(langGroupPref.value,
                                      this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
   },
 
   /**
    * 
    */
   _selectDefaultLanguageGroup: function (aLanguageGroup, aIsSerif)
   {
     const kFontNameFmtSerif         = "font.name.serif.%LANG%";
     const kFontNameFmtSansSerif     = "font.name.sans-serif.%LANG%";
     const kFontNameListFmtSerif     = "font.name-list.serif.%LANG%";
     const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
     const kFontSizeFmtVariable      = "font.size.variable.%LANG%";
 
+    var preferences = document.getElementById("contentPreferences");
     var prefs = [{ format   : aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
                    type     : "fontname",
                    element  : "defaultFont",
                    fonttype : aIsSerif ? "serif" : "sans-serif" },
                  { format   : aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif,
                    type     : "unichar",
                    element  : null,
                    fonttype : aIsSerif ? "serif" : "sans-serif" },
                  { format   : kFontSizeFmtVariable,
                    type     : "int",
                    element  : "defaultFontSize",
                    fonttype : null }];
-    var preferences = document.getElementById("contentPreferences");
-    // Ensure preferences are "visible" to ensure bindings work.
-    preferences.hidden = false;
-    // Force flush:
-    preferences.clientHeight;
     for (var i = 0; i < prefs.length; ++i) {
       var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
       if (!preference) {
         preference = document.createElement("preference");
         var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
         preference.id = name;
         preference.setAttribute("name", name);
         preference.setAttribute("type", prefs[i].type);
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -45,16 +45,17 @@ let gSubDialog = {
     let features = (!!aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
     let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
     if (aClosingCallback) {
       this._closingCallback = aClosingCallback.bind(dialog);
     }
 
     this._closingEvent = null;
     this._isClosing = false;
+    this._openedURL = aURL;
 
     features = features.replace(/,/g, "&");
     let featureParams = new URLSearchParams(features.toLowerCase());
     this._box.setAttribute("resizable", featureParams.has("resizable") &&
                                         featureParams.get("resizable") != "no" &&
                                         featureParams.get("resizable") != "0");
     return dialog;
   },
@@ -119,17 +120,17 @@ let gSubDialog = {
         this._onParentWinFocus(aEvent);
         break;
     }
   },
 
   /* Private methods */
 
   _onUnload: function(aEvent) {
-    if (aEvent.target.location.href != "about:blank") {
+    if (aEvent.target.location.href == this._openedURL) {
       this.close(this._closingEvent);
     }
   },
 
   _onContentLoaded: function(aEvent) {
     if (aEvent.target != this._frame || aEvent.target.contentWindow.location == "about:blank")
       return;
 
@@ -217,16 +218,21 @@ let gSubDialog = {
   },
 
   _onDialogClosing: function(aEvent) {
     this._frame.contentWindow.removeEventListener("dialogclosing", this);
     this._closingEvent = aEvent;
   },
 
   _onKeyDown: function(aEvent) {
+    if (aEvent.currentTarget == window && aEvent.keyCode == aEvent.DOM_VK_ESCAPE &&
+        !aEvent.defaultPrevented) {
+      this.close(aEvent);
+      return;
+    }
     if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
         aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
       return;
     }
 
     let fm = Services.focus;
 
     function isLastFocusableElement(el) {
@@ -273,28 +279,33 @@ let gSubDialog = {
     // Similarly DOMFrameContentLoaded only fires on the top window
     window.addEventListener("DOMFrameContentLoaded", this, true);
 
     // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
     // otherwise there is a flicker of the stylesheet applying.
     this._frame.addEventListener("load", this);
 
     chromeBrowser.addEventListener("unload", this, true);
+    // Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
+    // (happens on OS X when only text inputs and lists are focusable, and
+    //  the subdialog only has checkboxes/radiobuttons/buttons)
+    window.addEventListener("keydown", this, true);
   },
 
   _removeDialogEventListeners: function() {
     let chromeBrowser = this._getBrowser();
     chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
     chromeBrowser.removeEventListener("unload", this, true);
 
     this._closeButton.removeEventListener("command", this);
 
     window.removeEventListener("DOMFrameContentLoaded", this, true);
     this._frame.removeEventListener("load", this);
     this._frame.contentWindow.removeEventListener("dialogclosing", this);
+    window.removeEventListener("keydown", this, true);
     this._untrapFocus();
   },
 
   _trapFocus: function() {
     let fm = Services.focus;
     fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, 0);
     this._frame.contentDocument.addEventListener("keydown", this, true);
     this._closeButton.addEventListener("keydown", this);
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 skip-if = buildapp == "mulet"
 support-files =
   head.js
   privacypane_tests_perwindow.js
 
 [browser_advanced_update.js]
+[browser_basic_rebuild_fonts_test.js]
 [browser_bug410900.js]
 [browser_bug731866.js]
 [browser_bug795764_cachedisabled.js]
 [browser_bug1018066_resetScrollPosition.js]
 [browser_bug1020245_openPreferences_to_paneContent.js]
 [browser_change_app_handler.js]
 skip-if = os != "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows
 [browser_connection.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -0,0 +1,26 @@
+Services.prefs.setBoolPref("browser.preferences.inContent", true);
+Services.prefs.setBoolPref("browser.preferences.instantApply", true);
+
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("browser.preferences.inContent");
+  Services.prefs.clearUserPref("browser.preferences.instantApply");
+});
+
+add_task(function() {
+  yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+  var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
+  is(doc.getElementById("font.language.group").value, langGroup,
+     "Language group should be set correctly.");
+
+  let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
+  let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
+  let fontFamilyField = doc.getElementById("defaultFont");
+  is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
+
+  let defaultFontSize = Services.prefs.getIntPref("font.size.variable." + langGroup);
+  let fontSizeField = doc.getElementById("defaultFontSize");
+  is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/components/privatebrowsing/test/browser/browser.ini
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = buildapp == "mulet" || e10s
+skip-if = buildapp == "mulet"
 support-files =
   browser_privatebrowsing_concurrent_page.html
   browser_privatebrowsing_cookieacceptdialog.html
   browser_privatebrowsing_geoprompt_page.html
   browser_privatebrowsing_localStorage_before_after_page.html
   browser_privatebrowsing_localStorage_before_after_page2.html
   browser_privatebrowsing_localStorage_page1.html
   browser_privatebrowsing_localStorage_page2.html
@@ -12,35 +12,43 @@ support-files =
   browser_privatebrowsing_protocolhandler_page.html
   browser_privatebrowsing_windowtitle_page.html
   head.js
   popup.html
   title.sjs
 
 [browser_privatebrowsing_DownloadLastDirWithCPS.js]
 [browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
+skip-if = true # Bug 1142678 - Loading a message sending frame script into a private about:home tab causes leaks
+[browser_privatebrowsing_aboutHomeButtonAfterWindowClose_old.js]
+skip-if = e10s
 [browser_privatebrowsing_aboutSessionRestore.js]
 [browser_privatebrowsing_cache.js]
 [browser_privatebrowsing_certexceptionsui.js]
 [browser_privatebrowsing_concurrent.js]
 [browser_privatebrowsing_cookieacceptdialog.js]
+skip-if = e10s # Bug 1139953 - Accept cookie dialog shown in private window when e10s enabled
 [browser_privatebrowsing_crh.js]
 [browser_privatebrowsing_downloadLastDir.js]
 [browser_privatebrowsing_downloadLastDir_c.js]
 [browser_privatebrowsing_downloadLastDir_toggle.js]
 [browser_privatebrowsing_geoprompt.js]
 [browser_privatebrowsing_lastpbcontextexited.js]
 [browser_privatebrowsing_localStorage.js]
 [browser_privatebrowsing_localStorage_before_after.js]
 [browser_privatebrowsing_noSessionRestoreMenuOption.js]
 [browser_privatebrowsing_nonbrowser.js]
 [browser_privatebrowsing_opendir.js]
 [browser_privatebrowsing_placesTitleNoUpdate.js]
 [browser_privatebrowsing_placestitle.js]
 [browser_privatebrowsing_popupblocker.js]
 [browser_privatebrowsing_protocolhandler.js]
+skip-if = e10s # Bug 940206 -  nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
 [browser_privatebrowsing_sidebar.js]
 [browser_privatebrowsing_theming.js]
 [browser_privatebrowsing_ui.js]
 [browser_privatebrowsing_urlbarfocus.js]
 [browser_privatebrowsing_windowtitle.js]
+skip-if = e10s
 [browser_privatebrowsing_zoom.js]
+skip-if = e10s
 [browser_privatebrowsing_zoomrestore.js]
+skip-if = e10s
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js
@@ -1,50 +1,26 @@
 /* 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/. */
 
-// This test checks that the Session Restore about:home button
-// is disabled in private mode
+// This test checks that the Session Restore "Restore Previous Session"
+// button on about:home is disabled in private mode
+add_task(function* test_no_sessionrestore_button() {
+  // Opening, then closing, a private window shouldn't create session data.
+  (yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
 
-function test() {
-  waitForExplicitFinish();
+  let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  let tab = win.gBrowser.addTab("about:home");
+  let browser = tab.linkedBrowser;
 
-  function testNoSessionRestoreButton() {
-    let win = OpenBrowserWindow({private: true});
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
-      executeSoon(function() {
-        info("The second private window got loaded");
-        let newTab = win.gBrowser.addTab();
-        win.gBrowser.selectedTab = newTab;
-        let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
-        tabBrowser.addEventListener("load", function tabLoadListener() {
-          if (win.content.location != "about:home") {
-            win.content.location = "about:home";
-            return;
-          }
-          tabBrowser.removeEventListener("load", tabLoadListener, true);
-          executeSoon(function() {
-            info("about:home got loaded");
-            let sessionRestoreButton = win.gBrowser
-                                          .contentDocument
-                                          .getElementById("restorePreviousSession");
-            is(win.getComputedStyle(sessionRestoreButton).display, 
-               "none", "The Session Restore about:home button should be disabled");
-            win.close();
-            finish();
-          });
-        }, true);
-      });
-    }, false);
-  }
+  yield BrowserTestUtils.browserLoaded(browser);
 
-  let win = OpenBrowserWindow({private: true});
-  win.addEventListener("load", function onload() {
-    win.removeEventListener("load", onload, false);
-    executeSoon(function() {
-      info("The first private window got loaded");
-      win.close();
-      testNoSessionRestoreButton();
-    });
-  }, false);
-}
+  let display = yield ContentTask.spawn(browser, {}, function* (){
+    let button = content.document.getElementById("restorePreviousSession");
+    return content.getComputedStyle(button).display;
+  });
+
+  is(display, "none",
+    "The Session Restore about:home button should be disabled");
+
+  win.close();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose_old.js
@@ -0,0 +1,50 @@
+/* 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/. */
+
+// This test checks that the Session Restore about:home button
+// is disabled in private mode
+
+function test() {
+  waitForExplicitFinish();
+
+  function testNoSessionRestoreButton() {
+    let win = OpenBrowserWindow({private: true});
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad, false);
+      executeSoon(function() {
+        info("The second private window got loaded");
+        let newTab = win.gBrowser.addTab();
+        win.gBrowser.selectedTab = newTab;
+        let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
+        tabBrowser.addEventListener("load", function tabLoadListener() {
+          if (win.content.location != "about:home") {
+            win.content.location = "about:home";
+            return;
+          }
+          tabBrowser.removeEventListener("load", tabLoadListener, true);
+          executeSoon(function() {
+            info("about:home got loaded");
+            let sessionRestoreButton = win.gBrowser
+                                          .contentDocument
+                                          .getElementById("restorePreviousSession");
+            is(win.getComputedStyle(sessionRestoreButton).display, 
+               "none", "The Session Restore about:home button should be disabled");
+            win.close();
+            finish();
+          });
+        }, true);
+      });
+    }, false);
+  }
+
+  let win = OpenBrowserWindow({private: true});
+  win.addEventListener("load", function onload() {
+    win.removeEventListener("load", onload, false);
+    executeSoon(function() {
+      info("The first private window got loaded");
+      win.close();
+      testNoSessionRestoreButton();
+    });
+  }, false);
+}
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js
@@ -1,49 +1,24 @@
 /* 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/. */
 
 // This test checks that the session restore button from about:sessionrestore
 // is disabled in private mode
+add_task(function* testNoSessionRestoreButton() {
+  // Opening, then closing, a private window shouldn't create session data.
+  (yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
 
-function test() {
-  waitForExplicitFinish();
+  let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  let tab = win.gBrowser.addTab("about:sessionrestore");
+  let browser = tab.linkedBrowser;
 
-  function testNoSessionRestoreButton() {
-    let win = OpenBrowserWindow({private: true});
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
-      executeSoon(function() {
-        info("The second private window got loaded");
-        let newTab = win.gBrowser.addTab("about:sessionrestore");
-        win.gBrowser.selectedTab = newTab;
-        let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
-        tabBrowser.addEventListener("load", function tabLoadListener() {
-          if (win.gBrowser.contentWindow.location != "about:sessionrestore") {
-            win.gBrowser.selectedBrowser.loadURI("about:sessionrestore");
-            return;
-          }
-          tabBrowser.removeEventListener("load", tabLoadListener, true);
-          executeSoon(function() {
-            info("about:sessionrestore got loaded");
-            let restoreButton = win.gBrowser.contentDocument
-                                            .getElementById("errorTryAgain");
-            ok(restoreButton.disabled,
-               "The Restore about:sessionrestore button should be disabled");
-            win.close();
-            finish();
-          });
-        }, true);
-      });
-    }, false);
-  }
+  yield BrowserTestUtils.browserLoaded(browser);
 
-  let win = OpenBrowserWindow({private: true});
-  win.addEventListener("load", function onload() {
-    win.removeEventListener("load", onload, false);
-    executeSoon(function() {
-      info("The first private window got loaded");
-      win.close();
-      testNoSessionRestoreButton();
-    });
-  }, false);
-}
+  let disabled = yield ContentTask.spawn(browser, {}, function* (){
+    return content.document.getElementById("errorTryAgain").disabled;
+  });
+
+  ok(disabled, "The Restore about:sessionrestore button should be disabled");
+
+  win.close();
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -6,68 +6,79 @@
 // Ensure that values from one don't leak into the other, and that values from
 // earlier private storage sessions aren't visible later.
 
 // Step 1: create new tab, load a page that sets test=value in non-private storage
 // Step 2: create a new tab, load a page that sets test2=value2 in private storage
 // Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1
 // Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1
 
-function test() {
+add_task(function test() {
   let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html';
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  let non_private_tab = gBrowser.selectedBrowser;
-  non_private_tab.addEventListener('load', function() {
-    non_private_tab.removeEventListener('load', arguments.callee, true);
-    gBrowser.selectedTab = gBrowser.addTab();
-    let private_tab = gBrowser.selectedBrowser;
-    private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
-    private_tab.addEventListener('load', function() {
-      private_tab.removeEventListener('load', arguments.callee, true);
+
+  function setUsePrivateBrowsing(browser, val) {
+    return ContentTask.spawn(browser, val, function* (val) {
+      docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val;
+    });
+  };
+
+  function getElts(browser) {
+    return browser.contentTitle.split('|');
+  };
+
+  // Step 1
+  gBrowser.selectedTab = gBrowser.addTab(prefix + '?action=set&name=test&value=value&initial=true');
+  let non_private_browser = gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(non_private_browser);
 
-      non_private_tab.addEventListener('load', function() {
-        non_private_tab.removeEventListener('load', arguments.callee, true);
-        var elts = non_private_tab.contentWindow.document.title.split('|');
-        isnot(elts[0], 'value2', "public window shouldn't see private storage");
-        is(elts[1], '1', "public window should only see public items");
+
+  // Step 2
+  gBrowser.selectedTab = gBrowser.addTab();
+  let private_browser = gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(private_browser);
+  yield setUsePrivateBrowsing(private_browser, true);
+  private_browser.loadURI(prefix + '?action=set&name=test2&value=value2');
+  yield BrowserTestUtils.browserLoaded(private_browser);
 
-        private_tab.addEventListener('load', function() {
-          private_tab.removeEventListener('load', arguments.callee, true);
-          var elts = private_tab.contentWindow.document.title.split('|');
-          isnot(elts[0], 'value', "private window shouldn't see public storage");
-          is(elts[1], '1', "private window should only see private items");
-          private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = false;
+
+  // Step 3
+  non_private_browser.loadURI(prefix + '?action=get&name=test2');
+  yield BrowserTestUtils.browserLoaded(non_private_browser);
+  let elts = yield getElts(non_private_browser);
+  isnot(elts[0], 'value2', "public window shouldn't see private storage");
+  is(elts[1], '1', "public window should only see public items");
+
 
-          Components.utils.schedulePreciseGC(function() {
-            private_tab.addEventListener('load', function() {
-              private_tab.removeEventListener('load', arguments.callee, true);
-              var elts = private_tab.contentWindow.document.title.split('|');
-              isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
-              is(elts[1], '1', "public window should only see public items");
+  // Step 4
+  private_browser.loadURI(prefix + '?action=get&name=test');
+  yield BrowserTestUtils.browserLoaded(private_browser);
+  elts = yield getElts(private_browser);
+  isnot(elts[0], 'value', "private window shouldn't see public storage");
+  is(elts[1], '1', "private window should only see private items");
 
-              private_tab.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
-              private_tab.addEventListener('load', function() {
-                private_tab.removeEventListener('load', arguments.callee, true);
-                var elts = private_tab.contentWindow.document.title.split('|');
-                is(elts[1], '1', "private window should only see new private items");
+
+  // Make the private tab public again, which should clear the
+  // the private storage.
+  yield setUsePrivateBrowsing(private_browser, false);
+  yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
 
-                non_private_tab.addEventListener('load', function() {
-                  gBrowser.removeCurrentTab();
-                  gBrowser.removeCurrentTab();
-                  finish();
-                }, true);
-                non_private_tab.loadURI(prefix + '?final=true');
+  private_browser.loadURI(prefix + '?action=get&name=test2');
+  yield BrowserTestUtils.browserLoaded(private_browser);
+  elts = yield getElts(private_browser);
+  isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
+  is(elts[1], '1', "public window should only see public items");
+
+
+  // Making it private again should clear the storage and it shouldn't
+  // be able to see the old private storage as well.
+  yield setUsePrivateBrowsing(private_browser, true);
 
-              }, true);
-              private_tab.loadURI(prefix + '?action=set&name=test3&value=value3');
-            }, true);
-            private_tab.loadURI(prefix + '?action=get&name=test2');
-          });
-        }, true);
-        private_tab.loadURI(prefix + '?action=get&name=test');
-      }, true);
-      non_private_tab.loadURI(prefix + '?action=get&name=test2');
-    }, true);
-    private_tab.loadURI(prefix + '?action=set&name=test2&value=value2');
-  }, true);
-  non_private_tab.loadURI(prefix + '?action=set&name=test&value=value&initial=true');
-}
+  private_browser.loadURI(prefix + '?action=set&name=test3&value=value3');
+  BrowserTestUtils.browserLoaded(private_browser);
+  elts = yield getElts(private_browser);
+  is(elts[1], '1', "private window should only see new private items");
+
+  // Cleanup.
+  non_private_browser.loadURI(prefix + '?final=true');
+  yield BrowserTestUtils.browserLoaded(non_private_browser);
+  gBrowser.removeCurrentTab();
+  gBrowser.removeCurrentTab();
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js
@@ -1,54 +1,26 @@
 /* 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/. */
 
 // This test makes sure that private browsing mode disables the "remember"
 // option in the cookie accept dialog.
 
-function test() {
+add_task(function* test() {
   // initialization
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
                    "privatebrowsing/test/browser/" +
                    "browser_privatebrowsing_cookieacceptdialog.html";
   const BLANK_URL = "http://mochi.test:8888/";
   let cp = Cc["@mozilla.org/embedcomp/cookieprompt-service;1"].
            getService(Ci.nsICookiePromptService);
 
-  waitForExplicitFinish();
 
-  function checkRememberOption(expectedDisabled, aWindow, callback) {
-    function observer(aSubject, aTopic, aData) {
-      if (aTopic != "domwindowopened")
-        return;
-
-      Services.ww.unregisterNotification(observer);
-      let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      win.addEventListener("load", function onLoad(event) {
-        win.removeEventListener("load", onLoad, false);
-
-        executeSoon(function () {
-          let doc = win.document;
-          let remember = doc.getElementById("persistDomainAcceptance");
-          ok(remember, "The remember checkbox should exist");
-
-          if (expectedDisabled)
-            is(remember.getAttribute("disabled"), "true",
-               "The checkbox should be disabled");
-          else
-            ok(!remember.hasAttribute("disabled"),
-               "The checkbox should not be disabled");
-
-          waitForWindowClose(win, callback);
-        });
-      }, false);
-    }
-    Services.ww.registerNotification(observer);
-
+  function openCookieDialog(aWindow) {
     let remember = {};
     const time = (new Date("Jan 1, 2030")).getTime() / 1000;
     let cookie = {
       name: "foo",
       value: "bar",
       isDomain: true,
       host: "mozilla.org",
       path: "/baz",
@@ -62,100 +34,95 @@ function test() {
       QueryInterface: function(iid) {
         const validIIDs = [Ci.nsISupports, Ci.nsICookie, Ci.nsICookie2];
         for (var i = 0; i < validIIDs.length; ++i)
           if (iid == validIIDs[i])
             return this;
         throw Cr.NS_ERROR_NO_INTERFACE;
       }
     };
-    cp.cookieDialog(aWindow, cookie, "mozilla.org", 10, false, remember);
-  }
 
-  function checkSettingDialog(aIsPrivateWindow, aWindow, aCallback) {
-    let selectedBrowser = aWindow.gBrowser.selectedBrowser;
+    executeSoon(function () {
+      cp.cookieDialog(aWindow, cookie, "mozilla.org", 10, false, remember);
+    });
+    return BrowserTestUtils.domWindowOpened();
+  };
+
+
+  function checkRememberOption(expectedDisabled, aWindow) {
+    return Task.spawn(function* () {
+      let dialogWin = yield openCookieDialog(aWindow);
 
-    function onLoad() {
-      if (aWindow.content.location.href != TEST_URL) {
-        selectedBrowser.loadURI(TEST_URL);
-        return;
-      }
-      selectedBrowser.removeEventListener("load", onLoad, true);
-      Services.ww.unregisterNotification(observer);
+      yield new Promise(resolve => {
+        dialogWin.addEventListener("load", function onLoad(event) {
+          dialogWin.removeEventListener("load", onLoad, false);
+          resolve();
+        }, false);
+      });
 
-      ok(aIsPrivateWindow,
-         "Confirm setting dialog is not displayed for private window");
+      let doc = dialogWin.document;
+      let remember = doc.getElementById("persistDomainAcceptance");
+      ok(remember, "The remember checkbox should exist");
 
-      executeSoon(aCallback);
-    }
-    selectedBrowser.addEventListener("load", onLoad, true);
+      if (expectedDisabled)
+        is(remember.getAttribute("disabled"), "true",
+           "The checkbox should be disabled");
+      else
+        ok(!remember.hasAttribute("disabled"),
+           "The checkbox should not be disabled");
 
-    function observer(aSubject, aTopic, aData) {
-      if (aTopic != "domwindowopened")
-        return;
-      selectedBrowser.removeEventListener("load", onLoad, true);
-      Services.ww.unregisterNotification(observer);
-      ok(!aIsPrivateWindow,
-         "Confirm setting dialog is displayed for normal window");
-      let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      executeSoon(function () {
-        info("Wait for window close");
-        waitForWindowClose(win, aCallback);
-      });
-    }
-    Services.ww.registerNotification(observer);
+      yield BrowserTestUtils.closeWindow(dialogWin);
+    });
+  };
 
-    selectedBrowser.loadURI(TEST_URL);
-  }
+  function checkSettingDialog(aIsPrivateWindow, aWindow) {
+    return Task.spawn(function* () {
+      let dialogOpened = false;
+      let promiseDialogClosed = null;
+
+      function observer(subject, topic, data) {
+        if (topic != "domwindowopened") { return; }
+        Services.ww.unregisterNotification(observer);
+        dialogOpened = true;
 
-  let windowsToClose = [];
-  function testOnWindow(aIsPrivate, aCallback) {
-    whenNewWindowLoaded({private: aIsPrivate}, function(aWin) {
-      windowsToClose.push(aWin);
-      let selectedBrowser = aWin.gBrowser.selectedBrowser;
-      selectedBrowser.addEventListener("load", function onLoad() {
-        if (aWin.content.location.href != BLANK_URL) {
-          selectedBrowser.loadURI(BLANK_URL);
-          return;
-        }
-        selectedBrowser.removeEventListener("load", onLoad, true);
-        executeSoon(function() aCallback(aWin));
-      }, true);
-      selectedBrowser.loadURI(BLANK_URL);
+        promiseDialogClosed = BrowserTestUtils.closeWindow(
+          subject.QueryInterface(Ci.nsIDOMWindow));
+      }
+      Services.ww.registerNotification(observer);
+
+      let selectedBrowser = aWindow.gBrowser.selectedBrowser;
+      selectedBrowser.loadURI(TEST_URL);
+      yield BrowserTestUtils.browserLoaded(selectedBrowser);;
+
+      if (dialogOpened) {
+        ok(!aIsPrivateWindow,
+           "Setting dialog shown, confirm normal window");
+      } else {
+        Services.ww.unregisterNotification(observer);
+        ok(aIsPrivateWindow,
+           "Confirm setting dialog is not displayed for private window");
+      }
+
+      yield promiseDialogClosed;
     });
-  }
-
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
-  });
+  };
 
   // Ask all cookies
   Services.prefs.setIntPref("network.cookie.lifetimePolicy", 1);
 
-  testOnWindow(false, function(aWin) {
-    info("Test on public window");
-    checkRememberOption(false, aWin, function() {
-      checkSettingDialog(false, aWin, function() {
-        testOnWindow(true, function(aPrivWin) {
-          info("Test on private window");
-          checkRememberOption(true, aPrivWin, function() {
-            checkSettingDialog(true, aPrivWin, finish);
-          });
-        });
-      });
-    });
-  });
-}
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  info("Test on public window");
+
+  yield checkRememberOption(false, win);
+  yield checkSettingDialog(false, win);
+
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  info("Test on private window");
 
-function waitForWindowClose(aWin, aCallback) {
-  function observer(aSubject, aTopic, aData) {
-    if (aTopic == "domwindowclosed") {
-      info("Window closed");
-      Services.ww.unregisterNotification(observer);
-      executeSoon(aCallback);
-    }
-  }
-  Services.ww.registerNotification(observer);
-  aWin.close();
-}
+  yield checkRememberOption(true, privateWin);
+  yield checkSettingDialog(true, privateWin);
+
+
+  // Cleanup
+  Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
+  yield BrowserTestUtils.closeWindow(win);
+  yield BrowserTestUtils.closeWindow(privateWin);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
@@ -1,59 +1,42 @@
 /* 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/. */
 
 // This test makes sure that the Clear Recent History menu item and command 
 // is disabled inside the private browsing mode.
 
-function test() {
-  waitForExplicitFinish();
-
-  function checkDisableOption(aPrivateMode, aWindow, aCallback) {
-    executeSoon(function() {
-      let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
-      ok(crhCommand, "The clear recent history command should exist");
+add_task(function test() {
+  function checkDisableOption(aPrivateMode, aWindow) {
+    let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
+    ok(crhCommand, "The clear recent history command should exist");
 
-      is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
-        "PrivateBrowsingUtils should report the correct per-window private browsing status");
-      is(crhCommand.hasAttribute("disabled"), aPrivateMode,
-        "Clear Recent History command should be disabled according to the private browsing mode");
-
-      executeSoon(aCallback);
-    });
+    is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
+      "PrivateBrowsingUtils should report the correct per-window private browsing status");
+    is(crhCommand.hasAttribute("disabled"), aPrivateMode,
+      "Clear Recent History command should be disabled according to the private browsing mode");
   };
 
-  let windowsToClose = [];
   let testURI = "http://mochi.test:8888/";
 
-  function testOnWindow(aIsPrivate, aCallback) {
-    whenNewWindowLoaded({private: aIsPrivate}, function(aWin) {
-      windowsToClose.push(aWin);
-      aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-        if (aWin.content.location.href != testURI) {
-          aWin.gBrowser.loadURI(testURI);
-          return;
-        }
-        aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-        executeSoon(function() aCallback(aWin));
-      }, true);
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  let privateBrowser = privateWin.gBrowser.selectedBrowser;
+  privateBrowser.loadURI(testURI);
+  yield BrowserTestUtils.browserLoaded(privateBrowser);
 
-      aWin.gBrowser.loadURI(testURI);
-    });
-  };
+  info("Test on private window");
+  checkDisableOption(true, privateWin);
+
 
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
-  });
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let browser = win.gBrowser.selectedBrowser;
+  browser.loadURI(testURI);
+  yield BrowserTestUtils.browserLoaded(browser);
 
-  testOnWindow(true, function(aWin) {
-    info("Test on private window");
-    checkDisableOption(true, aWin, function() {
-      testOnWindow(false, function(aPrivWin) {
-        info("Test on public window");
-        checkDisableOption(false, aPrivWin, finish);
-      });
-    });
-  });
-}
+  info("Test on public window");
+  checkDisableOption(false, win);
+
+
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(privateWin);
+  yield BrowserTestUtils.closeWindow(win);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
@@ -1,69 +1,54 @@
 /* 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/. */
 
 // This test makes sure that the geolocation prompt does not show a remember
 // control inside the private browsing mode.
 
-function test() {
+add_task(function* test() {
   const testPageURL = "http://mochi.test:8888/browser/" +
     "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
-  waitForExplicitFinish();
 
-    function checkGeolocation(aPrivateMode, aWindow, aCallback) {
-    executeSoon(function() {
-      aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
-      aWindow.gBrowser.selectedBrowser.addEventListener("load", function () {
-        if (aWindow.content.location != testPageURL) {
-          aWindow.content.location = testPageURL;
-          return;
-        }
-        aWindow.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  function checkGeolocation(aPrivateMode, aWindow) {
+    return Task.spawn(function* () {
+      aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testPageURL);
+      yield BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
+
+      let notification = aWindow.PopupNotifications.getNotification("geolocation");
 
-        function runTest() {
-          let notification = aWindow.PopupNotifications.getNotification("geolocation");
-          if (!notification) {
-            // Wait until the notification is available
-            executeSoon(runTest);
-            return;
-          }
-          if (aPrivateMode) {
-            // Make sure the notification is correctly displayed without a remember control
-            is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
-          } else {
-            ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
-          }
-          notification.remove();
+      // Wait until the notification is available.
+      while (!notification){
+        yield new Promise(resolve => { executeSoon(resolve); });
+        let notification = aWindow.PopupNotifications.getNotification("geolocation");
+      }
 
-          aWindow.gBrowser.removeCurrentTab();
-          aCallback();
-        }
-        runTest();
-      }, true);
+      if (aPrivateMode) {
+        // Make sure the notification is correctly displayed without a remember control
+        is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
+      } else {
+        ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
+      }
+      notification.remove();
+
+      aWindow.gBrowser.removeCurrentTab();
     });
   };
 
-  let windowsToClose = [];
-  function testOnWindow(options, callback) {
-    let win = OpenBrowserWindow(options);
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
-      windowsToClose.push(win);
-      callback(win);
-    }, false);
-  };
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let browser = win.gBrowser.selectedBrowser;
+  browser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  yield checkGeolocation(false, win);
 
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(win) {
-      win.close();
-    });
-  });
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  let privateBrowser = privateWin.gBrowser.selectedBrowser;
+  privateBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(privateBrowser);
 
-  testOnWindow({private: false}, function(win) {
-    checkGeolocation(false, win, function() {
-      testOnWindow({private: true}, function(win) {
-        checkGeolocation(true, win, finish);
-      });
-    });
-  });
-}
+  yield checkGeolocation(true, privateWin);
+
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(win);
+  yield BrowserTestUtils.closeWindow(privateWin);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
@@ -1,54 +1,25 @@
 /* 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/. */
 
-function test() {
+ add_task(function test() {
   requestLongerTimeout(2);
-  waitForExplicitFinish();
-
   const page1 = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
                 'browser_privatebrowsing_localStorage_page1.html'
 
-  function checkLocalStorage(aWindow, aCallback) {
-    executeSoon(function() {
-      let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
-      let browser = aWindow.gBrowser.selectedBrowser;
-      browser.addEventListener('load', function() {
-        if (browser.contentWindow.location != page1) {
-          browser.loadURI(page1);
-          return;
-        }
-        browser.removeEventListener('load', arguments.callee, true);
-        let tab2 = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab();
-        browser.contentWindow.location = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
-                         'browser_privatebrowsing_localStorage_page2.html';
-        browser.addEventListener('load', function() {
-          browser.removeEventListener('load', arguments.callee, true);
-          is(browser.contentWindow.document.title, '2', "localStorage should contain 2 items");
-          aCallback();
-        }, true);
-      }, true);
-    });
-  }
+  let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+  let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(page1);
+  let browser = win.gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(browser);
 
-  let windowsToClose = [];
-  function testOnWindow(options, callback) {
-    let win = OpenBrowserWindow(options);
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
-      windowsToClose.push(win);
-      callback(win);
-    }, false);
-  };
+  browser.loadURI(
+    'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
+    'browser_privatebrowsing_localStorage_page2.html');
+  yield BrowserTestUtils.browserLoaded(browser);
 
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(win) {
-      win.close();
-    });
-  });
+  is(browser.contentTitle, '2', "localStorage should contain 2 items");
 
-  testOnWindow({private: true}, function(win) {
-    checkLocalStorage(win, finish);
-  });
-
-}
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(win);
+ });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
@@ -5,62 +5,32 @@
 // Ensure that a storage instance used by both private and public sessions at different times does not
 // allow any data to leak due to cached values.
 
 // Step 1: Load browser_privatebrowsing_localStorage_before_after_page.html in a private tab, causing a storage
 //   item to exist. Close the tab.
 // Step 2: Load the same page in a non-private tab, ensuring that the storage instance reports only one item
 //   existing.
 
-function test() {
-  // initialization
-  waitForExplicitFinish();
-  let windowsToClose = [];
+add_task(function test() {
   let testURI = "about:blank";
   let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/';
 
-  function doTest(aIsPrivateMode, aWindow, aCallback) {
-    aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-      aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+  // Step 1.
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  let privateBrowser = privateWin.gBrowser.addTab(
+    prefix + 'browser_privatebrowsing_localStorage_before_after_page.html').linkedBrowser;
+  yield BrowserTestUtils.browserLoaded(privateBrowser);
 
-      if (aIsPrivateMode) {
-        // do something when aIsPrivateMode is true
-        is(aWindow.gBrowser.contentWindow.document.title, '1', "localStorage should contain 1 item");
-      } else {
-        // do something when aIsPrivateMode is false
-        is(aWindow.gBrowser.contentWindow.document.title, 'null|0', 'localStorage should contain 0 items');
-      }
-
-      aCallback();
-    }, true);
-
-    aWindow.gBrowser.selectedBrowser.loadURI(testURI);
-  }
+  is(privateBrowser.contentTitle, '1', "localStorage should contain 1 item");
 
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      windowsToClose.push(aWin);
-      // execute should only be called when need, like when you are opening
-      // web pages on the test. If calling executeSoon() is not necesary, then
-      // call whenNewWindowLoaded() instead of testOnWindow() on your test.
-      executeSoon(function() aCallback(aWin));
-    });
-  };
+  // Step 2.
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let browser = win.gBrowser.addTab(
+    prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html').linkedBrowser;
+  yield BrowserTestUtils.browserLoaded(browser);
 
-   // this function is called after calling finish() on the test.
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
-  });
+  is(browser.contentTitle, 'null|0', 'localStorage should contain 0 items');
 
-  // test first when on private mode
-  testOnWindow({private: true}, function(aWin) {
-    testURI = prefix + 'browser_privatebrowsing_localStorage_before_after_page.html';
-    doTest(true, aWin, function() {
-      // then test when not on private mode
-      testOnWindow({}, function(aWin) {
-        testURI = prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html';
-        doTest(false, aWin, finish);
-      });
-    });
-  });
-}
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(privateWin);
+  yield BrowserTestUtils.closeWindow(win);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -1,111 +1,72 @@
 /* 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/. */
 
 // Test to make sure that the visited page titles do not get updated inside the
 // private browsing mode.
+"use strict";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
-function test() {
-  waitForExplicitFinish();
+add_task(function* test() {
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html"
   const TEST_URI = Services.io.newURI(TEST_URL, null, null);
   const TITLE_1 = "Title 1";
   const TITLE_2 = "Title 2";
 
-  let selectedWin = null;
-  let windowsToClose = [];
-  let tabToClose = null;
-  let testNumber = 0;
-  let historyObserver;
-
-
-  registerCleanupFunction(function() {
-    PlacesUtils.history.removeObserver(historyObserver, false);
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
-    gBrowser.removeTab(tabToClose);
-  });
-
+  function waitForTitleChanged() {
+    return new Promise(resolve => {
+      let historyObserver = {
+        onTitleChanged: function(uri, pageTitle) {
+          PlacesUtils.history.removeObserver(historyObserver, false);
+          resolve({uri: uri, pageTitle: pageTitle});
+        },
+        onBeginUpdateBatch: function () {},
+        onEndUpdateBatch: function () {},
+        onVisit: function () {},
+        onDeleteURI: function () {},
+        onClearHistory: function () {},
+        onPageChanged: function () {},
+        onDeleteVisits: function() {},
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
+      };
 
-  PlacesTestUtils.clearHistory().then(() => {
-    historyObserver = {
-      onTitleChanged: function(aURI, aPageTitle) {
-        switch (++testNumber) {
-          case 1:
-            afterFirstVisit();
-          break;
-          case 2:
-            afterUpdateVisit();
-          break;
-        }
-      },
-      onBeginUpdateBatch: function () {},
-      onEndUpdateBatch: function () {},
-      onVisit: function () {},
-      onDeleteURI: function () {},
-      onClearHistory: function () {},
-      onPageChanged: function () {},
-      onDeleteVisits: function() {},
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
-    };
-    PlacesUtils.history.addObserver(historyObserver, false);
+      PlacesUtils.history.addObserver(historyObserver, false);
+    });
+  };
+
+  yield PlacesTestUtils.clearHistory();
+
+  let tabToClose = gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+  yield waitForTitleChanged();
+  is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
 
-    tabToClose = gBrowser.addTab();
-    gBrowser.selectedTab = tabToClose;
-    whenPageLoad(window, function() {});
+  let place = {
+    uri: TEST_URI,
+    title: TITLE_2,
+    visits: [{
+      visitDate: Date.now() * 1000,
+      transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+    }]
+  };
+  PlacesUtils.asyncHistory.updatePlaces(place, {
+    handleError: function () ok(false, "Unexpected error in adding visit."),
+    handleResult: function () { },
+    handleCompletion: function () {}
   });
 
-  function afterFirstVisit() {
-    is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
+  yield waitForTitleChanged();
+  is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
 
-    let place = {
-      uri: TEST_URI,
-      title: TITLE_2,
-      visits: [{
-        visitDate: Date.now() * 1000,
-        transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
-      }]
-    };
-    PlacesUtils.asyncHistory.updatePlaces(place, {
-      handleError: function () do_throw("Unexpected error in adding visit."),
-      handleResult: function () { },
-      handleCompletion: function () {}
-    });
-  }
-
-  function afterUpdateVisit() {
-    is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private:true});
+  yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.addTab(TEST_URL).linkedBrowser);
 
-    testOnWindow(true, function(aWin) {
-      whenPageLoad(aWin, function() {
-        executeSoon(afterFirstVisitInPrivateWindow);
-      });
-    });
-  }
-
-  function afterFirstVisitInPrivateWindow() {
-     is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
-     PlacesTestUtils.clearHistory().then(finish);
-  }
+  is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
+  yield PlacesTestUtils.clearHistory();
 
-  function whenPageLoad(aWin, aCallback) {
-    aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-      aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-      aCallback();
-    }, true);
-    aWin.gBrowser.selectedBrowser.loadURI(TEST_URL);
-  }
+  // Cleanup
+  BrowserTestUtils.closeWindow(privateWin);
+  gBrowser.removeTab(tabToClose);
+});
 
-  function testOnWindow(aPrivate, aCallback) {
-    whenNewWindowLoaded({ private: aPrivate }, function(aWin) {
-      selectedWin = aWin;
-      windowsToClose.push(aWin);
-      executeSoon(function() { aCallback(aWin) });
-    });
-  }
-}
-
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
@@ -1,58 +1,50 @@
 /* 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/. */
 
 // This test makes sure that the title of existing history entries does not
 // change inside a private window.
 
-function test() {
-  waitForExplicitFinish();
-
+add_task(function* test() {
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
                    "privatebrowsing/test/browser/title.sjs";
-  let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+  let cm = Services.cookies;
 
-  function waitForCleanup(aCallback) {
+  function cleanup() {
     // delete all cookies
     cm.removeAll();
     // delete all history items
-    PlacesTestUtils.clearHistory().then(aCallback);
+    return PlacesTestUtils.clearHistory();
   }
 
+  yield cleanup();
+
+  let deferredFirst = PromiseUtils.defer();
+  let deferredSecond = PromiseUtils.defer();
+  let deferredThird = PromiseUtils.defer();
+
   let testNumber = 0;
   let historyObserver = {
     onTitleChanged: function(aURI, aPageTitle) {
       if (aURI.spec != TEST_URL)
         return;
       switch (++testNumber) {
         case 1:
           // The first time that the page is loaded
-          is(aPageTitle, "No Cookie",
-             "The page should be loaded without any cookie for the first time");
-          openTestPage(selectedWin);
+          deferredFirst.resolve(aPageTitle);
           break;
         case 2:
           // The second time that the page is loaded
-          is(aPageTitle, "Cookie",
-             "The page should be loaded with a cookie for the second time");
-          waitForCleanup(function () {
-            openTestPage(selectedWin);
-          });
+          deferredSecond.resolve(aPageTitle);
           break;
         case 3:
           // After clean up
-          is(aPageTitle, "No Cookie",
-             "The page should be loaded without any cookie again");
-          testOnWindow(true, function(win) {
-            whenPageLoad(win, function() {
-              waitForCleanup(finish);
-            });
-          });
+          deferredThird.resolve(aPageTitle);
           break;
         default:
           // Checks that opening the page in a private window should not fire a
           // title change.
           ok(false, "Title changed. Unexpected pass: " + testNumber);
       }
     },
 
@@ -62,43 +54,42 @@ function test() {
     onDeleteURI: function () {},
     onClearHistory: function () {},
     onPageChanged: function () {},
     onDeleteVisits: function() {},
     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
   };
   PlacesUtils.history.addObserver(historyObserver, false);
 
-  let selectedWin = null;
-  let windowsToClose = [];
-  registerCleanupFunction(function() {
-    PlacesUtils.history.removeObserver(historyObserver);
-    windowsToClose.forEach(function(win) {
-      win.close();
-    });
-  });
+
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+  let aPageTitle = yield deferredFirst.promise;
+  // The first time that the page is loaded
+  is(aPageTitle, "No Cookie",
+     "The page should be loaded without any cookie for the first time");
 
-  function openTestPage(aWin) {
-    aWin.gBrowser.selectedTab = aWin.gBrowser.addTab(TEST_URL);
-  }
+  win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+  aPageTitle = yield deferredSecond.promise;
+  // The second time that the page is loaded
+  is(aPageTitle, "Cookie",
+     "The page should be loaded with a cookie for the second time");
+
+  yield cleanup();
 
-  function whenPageLoad(aWin, aCallback) {
-    aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-      aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-      aCallback();
-    }, true);
-    aWin.gBrowser.selectedBrowser.loadURI(TEST_URL);
-  }
+  win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+  aPageTitle = yield deferredThird.promise;
+  // After clean up
+  is(aPageTitle, "No Cookie",
+     "The page should be loaded without any cookie again");
+
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
 
-  function testOnWindow(aPrivate, aCallback) {
-    whenNewWindowLoaded({ private: aPrivate }, function(win) {
-      selectedWin = win;
-      windowsToClose.push(win);
-      executeSoon(function() { aCallback(win) });
-    });
-  }
+  let private_tab = win2.gBrowser.addTab(TEST_URL);
+  win2.gBrowser.selectedTab = private_tab;
+  yield BrowserTestUtils.browserLoaded(private_tab.linkedBrowser);
 
-  waitForCleanup(function() {
-    testOnWindow(false, function(win) {
-      openTestPage(win);
-    });
-  });
-}
+  // Cleanup
+  yield cleanup();
+  PlacesUtils.history.removeObserver(historyObserver);
+  yield BrowserTestUtils.closeWindow(win);
+  yield BrowserTestUtils.closeWindow(win2);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js
@@ -1,23 +1,23 @@
 /* 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/. */
 
 // This test makes sure that private browsing mode disables the remember option
 // for the popup blocker menu.
-function test() {
-  // initialization
-  waitForExplicitFinish();
-
+add_task(function* test() {
   let testURI = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/popup.html";
-  let windowsToClose = [];
   let oldPopupPolicy = gPrefService.getBoolPref("dom.disable_open_during_load");
   gPrefService.setBoolPref("dom.disable_open_during_load", true);
 
+  registerCleanupFunction(() => {
+    gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy);
+  });
+
   function testPopupBlockerMenuItem(aExpectedDisabled, aWindow, aCallback) {
 
     aWindow.gBrowser.addEventListener("DOMUpdatePageReport", function() {
       aWindow.gBrowser.removeEventListener("DOMUpdatePageReport", arguments.callee, false);
 
       executeSoon(function() {
         let notification = aWindow.gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked");
         ok(notification, "The notification box should be displayed");
@@ -46,37 +46,22 @@ function test() {
         notification.querySelector("button").doCommand();
       });
 
     }, false);
 
     aWindow.gBrowser.selectedBrowser.loadURI(testURI);
   }
 
-  function finishTest() {
-    // cleanup
-    gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy);
-    finish();
-  };
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  yield new Promise(resolve => testPopupBlockerMenuItem(false, win1, resolve));
 
-  function testOnWindow(options, callback) {
-    let win = whenNewWindowLoaded(options, callback);
-    windowsToClose.push(win);
-  };
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  yield new Promise(resolve => testPopupBlockerMenuItem(true, win2, resolve));
 
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(win) {
-      win.close();
-    });
-  });
+  let win3 = yield BrowserTestUtils.openNewBrowserWindow();
+  yield new Promise(resolve => testPopupBlockerMenuItem(false, win3, resolve));
 
-  testOnWindow({}, function(win) {
-    testPopupBlockerMenuItem(false, win, function() {
-      testOnWindow({private: true}, function(win) {
-        testPopupBlockerMenuItem(true, win, function() {
-          testOnWindow({}, function(win) {
-            testPopupBlockerMenuItem(false, win, finishTest);
-          })
-        });
-      })
-    });
-  });
-}
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+  yield BrowserTestUtils.closeWindow(win3);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
@@ -1,65 +1,47 @@
 /* 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/. */
 
 // This test makes sure that the web pages can't register protocol handlers
 // inside the private browsing mode.
 
-function test() {
-  // initialization
-  waitForExplicitFinish();
-  let windowsToClose = [];
+add_task(function* test() {
   let notificationValue = "Protocol Registration: testprotocol";
   let testURI = "http://example.com/browser/" +
     "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html";
 
-  function doTest(aIsPrivateMode, aWindow, aCallback) {
-    aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-      aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-
-      setTimeout(function() {
-        let notificationBox = aWindow.gBrowser.getNotificationBox();
-        let notification = notificationBox.getNotificationWithValue(notificationValue);
+  let doTest = Task.async(function* (aIsPrivateMode, aWindow) {
+    let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testURI);
+    yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
-        if (aIsPrivateMode) {
-          // Make sure the notification is correctly displayed without a remember control
-          ok(!notification, "Notification box should not be displayed inside of private browsing mode");
-        } else {
-          // Make sure the notification is correctly displayed with a remember control
-          ok(notification, "Notification box should be displaying outside of private browsing mode");
-        }
+    let promiseFinished = PromiseUtils.defer();
+    setTimeout(function() {
+      let notificationBox = aWindow.gBrowser.getNotificationBox();
+      let notification = notificationBox.getNotificationWithValue(notificationValue);
 
-        aCallback();
-      }, 100); // remember control is added in a setTimeout(0) call
-    }, true);
-
-    aWindow.gBrowser.selectedBrowser.loadURI(testURI);
-  }
+      if (aIsPrivateMode) {
+        // Make sure the notification is correctly displayed without a remember control
+        ok(!notification, "Notification box should not be displayed inside of private browsing mode");
+      } else {
+        // Make sure the notification is correctly displayed with a remember control
+        ok(notification, "Notification box should be displaying outside of private browsing mode");
+      }
 
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      windowsToClose.push(aWin);
-      // execute should only be called when need, like when you are opening
-      // web pages on the test. If calling executeSoon() is not necesary, then
-      // call whenNewWindowLoaded() instead of testOnWindow() on your test.
-      executeSoon(function() aCallback(aWin));
-    });
-  };
+      promiseFinished.resolve();
+    }, 100); // remember control is added in a setTimeout(0) call
 
-   // this function is called after calling finish() on the test.
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
+    yield promiseFinished.promise;
   });
 
   // test first when not on private mode
-  testOnWindow({}, function(aWin) {
-    doTest(false, aWin, function() {
-      // then test when on private mode
-      testOnWindow({private: true}, function(aWin) {
-        doTest(true, aWin, finish);
-      });
-    });
-  });
-}
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  yield doTest(false, win);
+
+  // then test when on private mode
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  yield doTest(true, privateWin);
+
+  // Cleanup
+  yield BrowserTestUtils.closeWindow(win);
+  yield BrowserTestUtils.closeWindow(privateWin);
+});
--- a/browser/components/privatebrowsing/test/browser/head.js
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -1,11 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+let {PromiseUtils} = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   let gotLoad = false;
   let gotActivate = Services.focus.activeWindow == win;
 
--- a/browser/components/readinglist/sidebar.js
+++ b/browser/components/readinglist/sidebar.js
@@ -265,19 +265,17 @@ let RLSidebar = {
 
   /**
    * Open a given URL. The event is used to determine where it should be opened
    * (current tab, new tab, new window).
    * @param {string} url - URL to open.
    * @param {Event} event - KeyEvent or MouseEvent that triggered this action.
    */
   openURL(url, event) {
-    // TODO: Disabled while working on the listbox mechanics.
     log.debug(`Opening page ${url}`);
-    return;
 
     let mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIWebNavigation)
                            .QueryInterface(Ci.nsIDocShellTreeItem)
                            .rootTreeItem
                            .QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindow);
     mainWindow.openUILink(url, event);
--- a/browser/devtools/commandline/test/browser.ini
+++ b/browser/devtools/commandline/test/browser.ini
@@ -28,16 +28,17 @@ support-files =
   browser_cmd_appcache_valid_index.html
   browser_cmd_appcache_valid_page1.html
   browser_cmd_appcache_valid_page2.html
   browser_cmd_appcache_valid_page3.html
 [browser_cmd_commands.js]
 [browser_cmd_cookie.js]
 support-files =
  browser_cmd_cookie.html
+[browser_cmd_cookie_host.js]
 [browser_cmd_csscoverage_oneshot.js]
 support-files =
  browser_cmd_csscoverage_page1.html
  browser_cmd_csscoverage_page2.html
  browser_cmd_csscoverage_page3.html
  browser_cmd_csscoverage_sheetA.css
  browser_cmd_csscoverage_sheetB.css
  browser_cmd_csscoverage_sheetC.css
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_cookie_host.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the cookie command works for host with a port specified
+
+const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/commandline/"+
+                 "test/browser_cmd_cookie.html";
+
+function test() {
+  helpers.addTabWithToolbar(TEST_URI, function(options) {
+    return helpers.audit(options, [
+        {
+          setup: 'cookie list',
+          exec: {
+            output: [ /zap=zep/, /zip=zop/ ],
+          }
+        },
+        {
+          setup: "cookie set zup banana",
+          check: {
+            args: {
+              name: { value: 'zup' },
+              value: { value: 'banana' },
+            }
+          },
+          exec: {
+            output: ""
+          }
+        },
+        {
+          setup: "cookie list",
+          exec: {
+            output: [ /zap=zep/, /zip=zop/, /zup=banana/, /Edit/ ]
+          }
+        }
+    ]);
+  }).then(finish, helpers.handleError);
+}
+
--- a/browser/devtools/debugger/test/browser_dbg_parser-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_parser-09.js
@@ -29,262 +29,262 @@ function test() {
 
   // VariableDeclarator
 
   verify("var foo=()=>{}", e => e.type == "ArrowFunctionExpression", {
     name: "foo",
     chain: null,
     loc: [[1, 4], [1, 7]]
   });
-  verify("\nvar\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", {
+  verify("\nvar\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", {
     name: "foo",
     chain: null,
     loc: [[3, 0], [3, 3]]
   });
 
   // AssignmentExpression
 
   verify("foo=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
 
-  verify("\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
 
   verify("foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
 
-  verify("\nfoo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
 
   verify("this.foo=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
 
-  verify("\nthis.foo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
 
   verify("this.foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
 
-  verify("\nthis.foo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
 
   verify("foo.this.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
 
-  verify("\nfoo.this.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo.this.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
 
   // ObjectExpression
 
   verify("({foo:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
 
-  verify("(\n{\nfoo\n:\n(\n)\n=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+  verify("(\n{\nfoo\n:\n(\n)=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
     { name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
 
   verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
 
-  verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+  verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
 
   // AssignmentExpression + ObjectExpression
 
   verify("foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
 
-  verify("\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
 
   verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
 
-  verify("\nnested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nthis.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
 
-  verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
 
   verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
 
   // VariableDeclarator + AssignmentExpression + ObjectExpression
 
   verify("let foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
 
-  verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
 
   verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
 
-  verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
 
   // New/CallExpression + AssignmentExpression + ObjectExpression
 
   verify("foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
 
-  verify("\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
 
-  verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
 
-  verify("\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
 
-  verify("\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
 
-  verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
 
-  verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
 
-  verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
 
   verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
 
-  verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
 
   // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
 
   verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
 
-  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
 
-  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
 
-  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
 
-  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
 
-  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
 
-  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
 
-  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
 
   verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
 
-  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+  verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
     { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
 
   finish();
 }
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -803,17 +803,17 @@ InspectorPanel.prototype = {
    * Show DOM properties
    */
   showDOMProperties: function InspectorPanel_showDOMProperties() {
     this._toolbox.openSplitConsole().then(() => {
       let panel = this._toolbox.getPanel("webconsole");
       let jsterm = panel.hud.jsterm;
 
       jsterm.execute("inspect($0)");
-      jsterm.focusInput();
+      jsterm.inputNode.focus();
     });
   },
 
   /**
    * Clear any pseudo-class locks applied to the current hierarchy.
    */
   clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
     if (!this.walker) {
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -7,23 +7,23 @@ support-files =
   doc_inspector_delete-selected-node-02.html
   doc_inspector_gcli-inspect-command.html
   doc_inspector_highlight_after_transition.html
   doc_inspector_highlighter-comments.html
   doc_inspector_highlighter-geometry_01.html
   doc_inspector_highlighter-geometry_02.html
   doc_inspector_highlighter_csstransform.html
   doc_inspector_highlighter_dom.html
+  doc_inspector_highlighter_inline.html
   doc_inspector_highlighter.html
   doc_inspector_highlighter_rect.html
   doc_inspector_highlighter_rect_iframe.html
   doc_inspector_infobar_01.html
   doc_inspector_infobar_02.html
-  doc_inspector_menu-01.html
-  doc_inspector_menu-02.html
+  doc_inspector_menu.html
   doc_inspector_remove-iframe-during-load.html
   doc_inspector_search.html
   doc_inspector_search-suggestions.html
   doc_inspector_select-last-selected-01.html
   doc_inspector_select-last-selected-02.html
   head.js
 
 [browser_inspector_breadcrumbs.js]
@@ -47,33 +47,36 @@ skip-if = e10s # GCLI isn't e10s compati
 [browser_inspector_highlighter-geometry_02.js]
 [browser_inspector_highlighter-geometry_03.js]
 [browser_inspector_highlighter-geometry_04.js]
 [browser_inspector_highlighter-geometry_05.js]
 [browser_inspector_highlighter-hover_01.js]
 [browser_inspector_highlighter-hover_02.js]
 [browser_inspector_highlighter-hover_03.js]
 [browser_inspector_highlighter-iframes.js]
+[browser_inspector_highlighter-inline.js]
 [browser_inspector_highlighter-keybinding_01.js]
 [browser_inspector_highlighter-keybinding_02.js]
 [browser_inspector_highlighter-keybinding_03.js]
 [browser_inspector_highlighter-options.js]
 [browser_inspector_highlighter-rect_01.js]
 [browser_inspector_highlighter-rect_02.js]
 [browser_inspector_highlighter-selector_01.js]
 [browser_inspector_highlighter-selector_02.js]
 [browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
 [browser_inspector_initialization.js]
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts.js]
-[browser_inspector_menu-01.js]
-[browser_inspector_menu-02.js]
+[browser_inspector_menu-01-sensitivity.js]
+[browser_inspector_menu-02-copy-items.js]
+[browser_inspector_menu-03-paste-items.js]
+[browser_inspector_menu-04-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
 [browser_inspector_pseudoclass-lock.js]
 [browser_inspector_pseudoclass-menu.js]
 [browser_inspector_reload-01.js]
 [browser_inspector_reload-02.js]
 [browser_inspector_remove-iframe-during-load.js]
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
@@ -16,27 +16,27 @@ add_task(function*() {
   info("Selecting the simple, non-transformed DIV");
   yield selectAndHighlightNode("#simple-div", inspector);
 
   let isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
   let highlightedNode = yield getHighlitNode(toolbox);
   is(highlightedNode, getNode("#simple-div"),
     "The highlighter's outline corresponds to the simple div");
-  yield isNodeCorrectlyHighlighted(getNode("#simple-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#simple-div", toolbox,
     "non-zoomed");
 
   info("Selecting the rotated DIV");
   yield selectAndHighlightNode("#rotated-div", inspector);
 
   isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
-  yield isNodeCorrectlyHighlighted(getNode("#rotated-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#rotated-div", toolbox,
     "rotated");
 
   info("Selecting the zero width height DIV");
   yield selectAndHighlightNode("#widthHeightZero-div", inspector);
 
   isVisible = yield isHighlighting(toolbox);
   ok(isVisible, "The highlighter is shown");
-  yield isNodeCorrectlyHighlighted(getNode("#widthHeightZero-div"), toolbox,
+  yield isNodeCorrectlyHighlighted("#widthHeightZero-div", toolbox,
     "zero width height");
 });
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-03.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-03.js
@@ -40,30 +40,30 @@ add_task(function* () {
 
   info("Waiting for element picker to become active.");
   yield toolbox.highlighterUtils.startPicker();
 
   info("Moving mouse over iframe padding.");
   yield moveMouseOver(iframeNode, 1, 1);
 
   info("Performing checks");
-  yield isNodeCorrectlyHighlighted(iframeNode, toolbox);
+  yield isNodeCorrectlyHighlighted("iframe", toolbox);
 
   info("Scrolling the document");
   iframeNode.style.marginBottom = content.innerHeight + "px";
   content.scrollBy(0, 40);
 
   let iframeBodyNode = iframeNode.contentDocument.body;
 
   info("Moving mouse over iframe body");
   yield moveMouseOver(iframeNode, 40, 40);
 
   let highlightedNode = yield getHighlitNode(toolbox);
   is(highlightedNode, iframeBodyNode, "highlighter shows the right node");
-  yield isNodeCorrectlyHighlighted(iframeBodyNode, toolbox);
+  yield isNodeCorrectlyHighlighted("iframe || body", toolbox);
 
   info("Waiting for the element picker to deactivate.");
   yield inspector.toolbox.highlighterUtils.stopPicker();
 
   function moveMouseOver(node, x, y) {
     info("Waiting for element " + node + " to be highlighted");
     executeInContent("Test:SynthesizeMouse", {x, y, options: {type: "mousemove"}},
                      {node}, false);
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
@@ -21,24 +21,25 @@ transform highlighter applies those valu
 const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_csstransform.html";
 
 add_task(function*() {
   let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
   let front = inspector.inspector;
 
   let highlighter = yield front.getHighlighterByType("CssTransformHighlighter");
 
-  let node = getNode("#test-node");
   let nodeFront = yield getNodeFront("#test-node", inspector);
 
   info("Displaying the transform highlighter on test node");
   yield highlighter.show(nodeFront);
 
-  let {data} = yield executeInContent("Test:GetAllAdjustedQuads", null, {node});
-  let expected = data.border;
+  let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {
+    selector: "#test-node"
+  });
+  let [expected] = data.border;
 
   let points = yield getHighlighterNodeAttribute(highlighter,
     "css-transform-transformed", "points");
   let polygonPoints = points.split(" ").map(p => {
     return {
       x: +p.substring(0, p.indexOf(",")),
       y: +p.substring(p.indexOf(",")+1)
     };
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-inline.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+"use strict";
+
+// Test that highlighting various inline boxes displays the right number of
+// polygons in the page.
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_inline.html";
+const TEST_DATA = [
+  "body",
+  "h1",
+  "h2",
+  "h2 em",
+  "p",
+  "p span",
+  // The following test case used to fail. See bug 1139925.
+  "[dir=rtl] > span"
+];
+
+add_task(function*() {
+  info("Loading the test document and opening the inspector");
+  let {toolbox, inspector} = yield openInspectorForURL(TEST_URL);
+
+  for (let selector of TEST_DATA) {
+    info("Selecting and highlighting node " + selector);
+    yield selectAndHighlightNode(selector, inspector);
+
+    info("Get all quads for this node");
+    let {data} = yield executeInContent("Test:GetAllAdjustedQuads", {selector});
+
+    info("Iterate over the box-model regions and verify that the highlighter is correct");
+    for (let region of ["margin", "border", "padding", "content"]) {
+      let {points} = yield getHighlighterRegionPath(region, toolbox.highlighter);
+      is(points.length, data[region].length,
+        "The highlighter's " + region + " path defines the correct number of boxes");
+    }
+
+    info("Verify that the guides define a rectangle that contains all content boxes");
+
+    let expectedContentRect = {
+      p1: {x: Infinity, y: Infinity},
+      p2: {x: -Infinity, y: Infinity},
+      p3: {x: -Infinity, y: -Infinity},
+      p4: {x: Infinity, y: -Infinity}
+    };
+    for (let {p1, p2, p3, p4} of data.content) {
+      expectedContentRect.p1.x = Math.min(expectedContentRect.p1.x, p1.x);
+      expectedContentRect.p1.y = Math.min(expectedContentRect.p1.y, p1.y);
+      expectedContentRect.p2.x = Math.max(expectedContentRect.p2.x, p2.x);
+      expectedContentRect.p2.y = Math.min(expectedContentRect.p2.y, p2.y);
+      expectedContentRect.p3.x = Math.max(expectedContentRect.p3.x, p3.x);
+      expectedContentRect.p3.y = Math.max(expectedContentRect.p3.y, p3.y);
+      expectedContentRect.p4.x = Math.min(expectedContentRect.p4.x, p4.x);
+      expectedContentRect.p4.y = Math.max(expectedContentRect.p4.y, p4.y);
+    }
+
+    let contentRect = yield getGuidesRectangle(toolbox);
+
+    for (let point of ["p1", "p2", "p3", "p4"]) {
+      is((contentRect[point].x),
+         (expectedContentRect[point].x),
+         "x coordinate of point " + point +
+         " of the content rectangle defined by the outer guides is correct");
+      is((contentRect[point].y),
+         (expectedContentRect[point].y),
+         "y coordinate of point " + point +
+         " of the content rectangle defined by the outer guides is correct");
+    }
+  }
+});
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
@@ -36,19 +36,18 @@ const TEST_DATA = [
       }
     }
   },
   {
     desc: "All regions should be shown by default",
     options: {},
     checkHighlighter: function*(toolbox) {
       for (let region of ["margin", "border", "padding", "content"]) {
-        let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-          "box-model-" + region, "points");
-        ok(points, "Region " + region + " has set coordinates");
+        let {d} = yield getHighlighterRegionPath(region, toolbox.highlighter);
+        ok(d, "Region " + region + " has set coordinates");
       }
     }
   },
   {
     desc: "Guides can be hidden",
     options: {hideGuides: true},
     checkHighlighter: function*(toolbox) {
       for (let side of ["top", "right", "bottom", "left"]) {
@@ -66,70 +65,61 @@ const TEST_DATA = [
         "box-model-nodeinfobar-container", "hidden");
       is(hidden, "true", "nodeinfobar has been hidden");
     }
   },
   {
     desc: "One region only can be shown (1)",
     options: {showOnly: "content"},
     checkHighlighter: function*(toolbox) {
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
-      ok(!points, "margin region is hidden");
+      let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      ok(!d, "margin region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-border", "points");
-      ok(!points, "border region is hidden");
+      ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
+      ok(!d, "border region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      ok(!points, "padding region is hidden");
+      ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
+      ok(!d, "padding region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-content", "points");
-      ok(points, "content region is shown");
+      ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
+      ok(d, "content region is shown");
     }
   },
   {
     desc: "One region only can be shown (2)",
     options: {showOnly: "margin"},
     checkHighlighter: function*(toolbox) {
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
-      ok(points, "margin region is shown");
+      let {d} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      ok(d, "margin region is shown");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-border", "points");
-      ok(!points, "border region is hidden");
+      ({d}) = yield getHighlighterRegionPath("border", toolbox.highlighter);
+      ok(!d, "border region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      ok(!points, "padding region is hidden");
+      ({d}) = yield getHighlighterRegionPath("padding", toolbox.highlighter);
+      ok(!d, "padding region is hidden");
 
-      points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-content", "points");
-      ok(!points, "content region is hidden");
+      ({d}) = yield getHighlighterRegionPath("content", toolbox.highlighter);
+      ok(!d, "content region is hidden");
     }
   },
   {
     desc: "Guides can be drawn around a given region (1)",
     options: {region: "padding"},
     checkHighlighter: function*(toolbox) {
       let topY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-top", "y1");
       let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-right", "x1");
       let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-bottom", "y1");
       let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-left", "x1");
 
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-padding", "points");
-      points = points.split(" ").map(xy => xy.split(","));
+      let {points} = yield getHighlighterRegionPath("padding", toolbox.highlighter);
+      points = points[0];
 
       is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct");
       is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct");
       is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct");
       is(Math.ceil(leftX1), points[3][0], "Left guide's x1 is correct");
     }
   },
   {
@@ -140,20 +130,19 @@ const TEST_DATA = [
         "box-model-guide-top", "y1");
       let rightX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-right", "x1");
       let bottomY1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-bottom", "y1");
       let leftX1 = yield getHighlighterNodeAttribute(toolbox.highlighter,
         "box-model-guide-left", "x1");
 
-      let points = yield getHighlighterNodeAttribute(toolbox.highlighter,
-        "box-model-margin", "points");
+      let {points} = yield getHighlighterRegionPath("margin", toolbox.highlighter);
+      points = points[0];
 
-      points = points.split(" ").map(xy => xy.split(","));
       is(Math.ceil(topY1), points[0][1], "Top guide's y1 is correct");
       is(Math.floor(rightX1), points[1][0], "Right guide's x1 is correct");
       is(Math.floor(bottomY1), points[2][1], "Bottom guide's y1 is correct");
       is(Math.ceil(leftX1), points[3][0], "Left guide's x1 is correct");
     }
   }
 ];
 
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
@@ -36,17 +36,17 @@ add_task(function*() {
   for (let {level, expected} of TEST_LEVELS) {
     info("Zoom to level " + level + " and check that the highlighter is correct");
 
     let {actorID, connPrefix} = getHighlighterActorID(toolbox.highlighter);
     yield zoomPageTo(level, actorID, connPrefix);
     isVisible = yield isHighlighting(toolbox);
     ok(isVisible, "The highlighter is still visible at zoom level " + level);
 
-    yield isNodeCorrectlyHighlighted(getNode("div"), toolbox);
+    yield isNodeCorrectlyHighlighted("div", toolbox);
 
     info("Check that the highlighter root wrapper node was scaled down");
 
     let style = yield getRootNodeStyle(toolbox);
     is(style, expected, "The style attribute of the root element is correct");
   }
 });
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -0,0 +1,202 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that context menu items are enabled / disabled correctly.
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
+
+const PASTE_MENU_ITEMS = [
+  "node-menu-pasteinnerhtml",
+  "node-menu-pasteouterhtml",
+  "node-menu-pastebefore",
+  "node-menu-pasteafter",
+  "node-menu-pastefirstchild",
+  "node-menu-pastelastchild",
+];
+
+const ALL_MENU_ITEMS = [
+  "node-menu-edithtml",
+  "node-menu-copyinner",
+  "node-menu-copyouter",
+  "node-menu-copyuniqueselector",
+  "node-menu-copyimagedatauri",
+  "node-menu-showdomproperties",
+  "node-menu-delete",
+  "node-menu-pseudo-hover",
+  "node-menu-pseudo-active",
+  "node-menu-pseudo-focus"
+].concat(PASTE_MENU_ITEMS);
+
+const ITEMS_WITHOUT_SHOWDOMPROPS =
+  ALL_MENU_ITEMS.filter(item => item != "node-menu-showdomproperties");
+
+const TEST_CASES = [
+  {
+    desc: "doctype node with empty clipboard",
+    selector: null,
+    disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
+  },
+  {
+    desc: "doctype node with html on clipboard",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    selector: null,
+    disabled: ITEMS_WITHOUT_SHOWDOMPROPS,
+  },
+  {
+    desc: "element node HTML on the clipboard",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    disabled: ["node-menu-copyimagedatauri"],
+    selector: "#sensitivity",
+  },
+  {
+    desc: "<html> element",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    selector: "html",
+    disabled: [
+      "node-menu-copyimagedatauri",
+      "node-menu-pastebefore",
+      "node-menu-pasteafter",
+      "node-menu-pastefirstchild",
+      "node-menu-pastelastchild",
+    ],
+  },
+  {
+    desc: "<body> with HTML on clipboard",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    selector: "body",
+    disabled: [
+      "node-menu-copyimagedatauri",
+      "node-menu-pastebefore",
+      "node-menu-pasteafter",
+    ]
+  },
+  {
+    desc: "<img> with HTML on clipboard",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    selector: "img",
+    disabled: []
+  },
+  {
+    desc: "<head> with HTML on clipboard",
+    clipboardData: "<p>some text</p>",
+    clipboardDataType: "html",
+    selector: "head",
+    disabled: [
+      "node-menu-copyimagedatauri",
+      "node-menu-pastebefore",
+      "node-menu-pasteafter",
+    ]
+  },
+  {
+    desc: "<element> with text on clipboard",
+    clipboardData: "some text",
+    clipboardDataType: undefined,
+    selector: "#paste-area",
+    disabled: ["node-menu-copyimagedatauri"],
+  },
+  {
+    desc: "<element> with base64 encoded image data uri on clipboard",
+    clipboardData:
+      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
+      "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
+    clipboardDataType: undefined,
+    selector: "#paste-area",
+    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+  },
+  {
+    desc: "<element> with empty string on clipboard",
+    clipboardData: "",
+    clipboardDataType: undefined,
+    selector: "#paste-area",
+    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+  },
+  {
+    desc: "<element> with whitespace only on clipboard",
+    clipboardData: " \n\n\t\n\n  \n",
+    clipboardDataType: undefined,
+    selector: "#paste-area",
+    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+  },
+];
+
+let clipboard = require("sdk/clipboard");
+registerCleanupFunction(() => {
+  clipboard = null;
+});
+
+add_task(function *() {
+  let { inspector } = yield openInspectorForURL(TEST_URL);
+  for (let test of TEST_CASES) {
+    let { desc, disabled, selector } = test;
+
+    info(`Test ${desc}`);
+    setupClipboard(test.clipboardData, test.clipboardDataType);
+
+    let front = yield getNodeFrontForSelector(selector, inspector);
+
+    info("Selecting the specified node.");
+    yield selectNode(front, inspector);
+
+    info("Simulating context menu click on the selected node container.");
+    contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+
+    for (let menuitem of ALL_MENU_ITEMS) {
+      let elt = inspector.panelDoc.getElementById(menuitem);
+      let shouldBeDisabled = disabled.indexOf(menuitem) !== -1;
+      let isDisabled = elt.hasAttribute("disabled");
+
+      is(isDisabled, shouldBeDisabled,
+        `#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
+    }
+  }
+});
+
+/**
+ * A helper that fetches a front for a node that matches the given selector or
+ * doctype node if the selector is falsy.
+ */
+function* getNodeFrontForSelector(selector, inspector) {
+  if (selector) {
+    info("Retrieving front for selector " + selector);
+    return getNodeFront(selector, inspector);
+  } else {
+    info("Retrieving front for doctype node");
+    let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
+    return nodes[0];
+  }
+}
+
+/**
+ * A helper that populates the clipboard with data of given type. Clears the
+ * clipboard if data is falsy.
+ */
+function setupClipboard(data, type) {
+  if (data) {
+    info("Populating clipboard with " + type + " data.");
+    clipboard.set(data, type);
+  } else {
+    info("Clearing clipboard.");
+    clipboard.set("", "text");
+  }
+}
+
+/**
+ * A helper that simulates a contextmenu event on the given chrome DOM element.
+ */
+function contextMenuClick(element) {
+  let evt = element.ownerDocument.createEvent('MouseEvents');
+  let button = 2;  // right click
+
+  evt.initMouseEvent('contextmenu', true, true,
+       element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
+       false, false, false, button, null);
+
+  element.dispatchEvent(evt);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_menu-02-copy-items.js
@@ -0,0 +1,51 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the various copy items in the context menu works correctly.
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
+const COPY_ITEMS_TEST_DATA = [
+  {
+    desc: "copy inner html",
+    id: "node-menu-copyinner",
+    selector: "[data-id=\"copy\"]",
+    text: "Paragraph for testing copy",
+  },
+  {
+    desc: "copy outer html",
+    id: "node-menu-copyouter",
+    selector: "[data-id=\"copy\"]",
+    text: "<p data-id=\"copy\">Paragraph for testing copy</p>",
+  },
+  {
+    desc: "copy unique selector",
+    id: "node-menu-copyuniqueselector",
+    selector: "[data-id=\"copy\"]",
+    text: "body > div:nth-child(1) > p:nth-child(2)",
+  },
+  {
+    desc: "copy image data uri",
+    id: "node-menu-copyimagedatauri",
+    selector: "#copyimage",
+    text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
+      "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
+  },
+];
+
+add_task(function *() {
+  let { inspector } = yield openInspectorForURL(TEST_URL);
+  for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) {
+    info("Testing " + desc);
+    yield selectNode(selector, inspector);
+
+    let item = inspector.panelDoc.getElementById(id);
+    ok(item, "The popup has a " + desc + " menu item.");
+
+    let deferred = promise.defer();
+    waitForClipboard(text, () => item.doCommand(),
+                     deferred.resolve, deferred.reject);
+    yield deferred.promise;
+  }
+});
rename from browser/devtools/inspector/test/browser_inspector_menu-02.js
rename to browser/devtools/inspector/test/browser_inspector_menu-03-paste-items.js
--- a/browser/devtools/inspector/test/browser_inspector_menu-02.js
+++ b/browser/devtools/inspector/test/browser_inspector_menu-03-paste-items.js
@@ -1,100 +1,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-// Test context menu functionality:
-// 1) menu items are d